Lua virtual machine in Rust, why the heck not.
						commit
						b07de4810d
					
				| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
/target
 | 
			
		||||
/untracked
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lua_why_not"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "lua_why_not"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,176 @@
 | 
			
		|||
Lua source code
 | 
			
		||||
 | 
			
		||||
`hello.lua`
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
print "Hello."
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
`math.lua`
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
local function add (a, b)
 | 
			
		||||
	return a + b
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
print (("1 + 2 = %i"):format (add (1, 2)))
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
luac5.4 listing
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
main <hello.lua:0,0> (5 instructions at 0x564f4fd74cc0)
 | 
			
		||||
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
 | 
			
		||||
	1	[1]	VARARGPREP	0
 | 
			
		||||
	2	[1]	GETTABUP 	0 0 0	; _ENV "print"
 | 
			
		||||
	3	[1]	LOADK    	1 1	; "Hello."
 | 
			
		||||
	4	[1]	CALL     	0 2 1	; 1 in 0 out
 | 
			
		||||
	5	[1]	RETURN   	0 1 1	; 0 out
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
main <math.lua:0,0> (12 instructions at 0x55ee2417acc0)
 | 
			
		||||
0+ params, 7 slots, 1 upvalue, 1 local, 3 constants, 1 function
 | 
			
		||||
        1       [1]     VARARGPREP      0
 | 
			
		||||
        2       [3]     CLOSURE         0 0     ; 0x55ee2417b000
 | 
			
		||||
        3       [5]     GETTABUP        1 0 0   ; _ENV "print"
 | 
			
		||||
        4       [5]     LOADK           2 1     ; "1 + 2 = %i"
 | 
			
		||||
        5       [5]     SELF            2 2 2k  ; "format"
 | 
			
		||||
        6       [5]     MOVE            4 0
 | 
			
		||||
        7       [5]     LOADI           5 1
 | 
			
		||||
        8       [5]     LOADI           6 2
 | 
			
		||||
        9       [5]     CALL            4 3 0   ; 2 in all out
 | 
			
		||||
        10      [5]     CALL            2 0 0   ; all in all out
 | 
			
		||||
        11      [5]     CALL            1 0 1   ; all in 0 out
 | 
			
		||||
        12      [5]     RETURN          1 1 1   ; 0 out
 | 
			
		||||
 | 
			
		||||
function <math.lua:1,3> (4 instructions at 0x55ee2417b000)
 | 
			
		||||
2 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 0 functions
 | 
			
		||||
        1       [2]     ADD             2 0 1
 | 
			
		||||
        2       [2]     MMBIN           0 1 6   ; __add
 | 
			
		||||
        3       [2]     RETURN1         2
 | 
			
		||||
        4       [3]     RETURN0  
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
main <test_vectors/is_93.lua:0,0> (14 instructions at 0x559f55e1ecd0)
 | 
			
		||||
0+ params, 2 slots, 1 upvalue, 1 local, 5 constants, 1 function
 | 
			
		||||
        1       [1]     VARARGPREP      0
 | 
			
		||||
        2       [1]     GETTABUP        0 0 0   ; _ENV "arg"
 | 
			
		||||
        3       [1]     GETI            0 0 1
 | 
			
		||||
        4       [1]     EQK             0 1 0   ; "93"
 | 
			
		||||
        5       [1]     JMP             4       ; to 10
 | 
			
		||||
        6       [2]     GETTABUP        0 0 2   ; _ENV "print"
 | 
			
		||||
        7       [2]     LOADK           1 3     ; "it's 93"
 | 
			
		||||
        8       [2]     CALL            0 2 1   ; 1 in 0 out
 | 
			
		||||
        9       [2]     JMP             3       ; to 13
 | 
			
		||||
        10      [4]     GETTABUP        0 0 2   ; _ENV "print"
 | 
			
		||||
        11      [4]     LOADK           1 4     ; "it's not 93"
 | 
			
		||||
        12      [4]     CALL            0 2 1   ; 1 in 0 out
 | 
			
		||||
        13      [9]     CLOSURE         0 0     ; 0x559f55e1f3d0
 | 
			
		||||
        14      [9]     RETURN          1 1 1   ; 0 out
 | 
			
		||||
constants (5) for 0x559f55e1ecd0:
 | 
			
		||||
        0       S       "arg"
 | 
			
		||||
        1       S       "93"
 | 
			
		||||
        2       S       "print"
 | 
			
		||||
        3       S       "it's 93"
 | 
			
		||||
        4       S       "it's not 93"
 | 
			
		||||
locals (1) for 0x559f55e1ecd0:
 | 
			
		||||
        0       unused_fn       14      15
 | 
			
		||||
upvalues (1) for 0x559f55e1ecd0:
 | 
			
		||||
        0       _ENV    1       0
 | 
			
		||||
 | 
			
		||||
function <test_vectors/is_93.lua:7,9> (4 instructions at 0x559f55e1f3d0)
 | 
			
		||||
0 params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
 | 
			
		||||
        1       [8]     GETTABUP        0 0 0   ; _ENV "print"
 | 
			
		||||
        2       [8]     LOADK           1 1     ; "unused"
 | 
			
		||||
        3       [8]     CALL            0 2 1   ; 1 in 0 out
 | 
			
		||||
        4       [9]     RETURN0  
 | 
			
		||||
constants (2) for 0x559f55e1f3d0:
 | 
			
		||||
        0       S       "print"
 | 
			
		||||
        1       S       "unused"
 | 
			
		||||
locals (0) for 0x559f55e1f3d0:
 | 
			
		||||
upvalues (1) for 0x559f55e1f3d0:
 | 
			
		||||
        0       _ENV    0       0
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Octal dump of luac5.4 byte code
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
0000000 1b 4c 75 61 54 00 19 93 0d 0a 1a 0a 04 08 08 78  >.LuaT..........x<
 | 
			
		||||
0000020 56 00 00 00 00 00 00 00 00 00 00 00 28 77 40 01  >V...........(w@.<
 | 
			
		||||
0000040 8b 40 68 65 6c 6c 6f 2e 6c 75 61 80 80 00 01 02  >.@hello.lua.....<
 | 
			
		||||
0000060 85 51 00 00 00 0b 00 00 00 83 80 00 00 44 00 02  >.Q...........D..<
 | 
			
		||||
0000100 01 46 00 01 01 82 04 86 70 72 69 6e 74 04 87 48  >.F......print..H<
 | 
			
		||||
0000120 65 6c 6c 6f 2e 81 01 00 00 80 85 01 00 00 00 00  >ello............<
 | 
			
		||||
0000140 80 80 81 85 5f 45 4e 56                          >...._ENV<
 | 
			
		||||
0000150
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
0000000 1b 4c 75 61 54 00 19 93 0d 0a 1a 0a 04 08 08 78  >.LuaT..........x<
 | 
			
		||||
0000020 56 00 00 00 00 00 00 00 00 00 00 00 28 77 40 01  >V...........(w@.<
 | 
			
		||||
0000040 8a 40 6d 61 74 68 2e 6c 75 61 80 80 00 01 07 8c  >.@math.lua......<
 | 
			
		||||
0000060 51 00 00 00 4f 00 00 00 8b 00 00 00 03 81 00 00  >Q...O...........<
 | 
			
		||||
0000100 14 81 02 02 00 02 00 00 81 02 00 80 01 83 00 80  >................<
 | 
			
		||||
0000120 44 02 03 00 44 01 00 00 c4 00 00 01 c6 00 01 01  >D...D...........<
 | 
			
		||||
0000140 83 04 86 70 72 69 6e 74 04 8b 31 20 2b 20 32 20  >...print..1 + 2 <
 | 
			
		||||
0000160 3d 20 25 69 04 87 66 6f 72 6d 61 74 81 01 00 00  >= %i..format....<
 | 
			
		||||
0000200 81 80 81 83 02 00 03 84 22 01 00 01 2e 00 01 06  >........".......<
 | 
			
		||||
0000220 48 01 02 00 47 01 01 00 80 80 80 84 01 00 00 01  >H...G...........<
 | 
			
		||||
0000240 80 82 82 61 80 84 82 62 80 84 80 8c 01 02 02 00  >...a...b........<
 | 
			
		||||
0000260 00 00 00 00 00 00 00 00 80 81 84 61 64 64 82 8c  >...........add..<
 | 
			
		||||
0000300 81 85 5f 45 4e 56                                >.._ENV<
 | 
			
		||||
0000306
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Interpretation of byte code
 | 
			
		||||
 | 
			
		||||
Overall structure
 | 
			
		||||
 | 
			
		||||
- Roughly 32 byte header with magic number, version number, etc.
 | 
			
		||||
- File name
 | 
			
		||||
- `80 80 00 01 02 85` header for main function
 | 
			
		||||
- Packed 4-byte instructions for main function
 | 
			
		||||
- `82` or `83` length prefix for string table
 | 
			
		||||
- String table for main function
 | 
			
		||||
- `81 01 00 00 81 80 81 83 02 00 03 84` header for "add" function
 | 
			
		||||
- Packed 4-byte instructions for "add" function
 | 
			
		||||
- `80 80 80 84 01 00 00 01 80` Header for file-scope debug symbols?
 | 
			
		||||
- File-scope debug symbols?
 | 
			
		||||
- String table for entire file?
 | 
			
		||||
 | 
			
		||||
## Bytecodes
 | 
			
		||||
 | 
			
		||||
Per lopcodes.h, instructions are 32 bits long, always.
 | 
			
		||||
The opcode is encoded in the first (highest?) 7 bits.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
83 80 00 00 ; 0x03 =  3 = LOADK
 | 
			
		||||
0b 00 00 00 ; 0x0b = 11 = GETTABUP
 | 
			
		||||
22 01 00 01 ; 0x22 = 34 = ADD
 | 
			
		||||
2e 00 01 06 ; 0x2e = 46 = MMBIN
 | 
			
		||||
c4 00 00 01 ; 0xc4 = 68 = CALL
 | 
			
		||||
44 00 02 01 ; 0x44 = 68 = CALL
 | 
			
		||||
46 00 01 01 ; 0x46 = 70 = RETURN
 | 
			
		||||
47 01 01 00 ; 0x47 = 71 = RETURN0
 | 
			
		||||
48 01 02 00 ; 0x48 = 72 = RETURN1
 | 
			
		||||
51 00 00 00 ; 0x51 = 81 = VARARGPREP
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Strings
 | 
			
		||||
 | 
			
		||||
Filenames are encoded at the top, and there's a string table at the bottom.
 | 
			
		||||
 | 
			
		||||
Strings appear to be prefixed with a variable-length length prefix.
 | 
			
		||||
There is an extra byte before each string which I can't account for,
 | 
			
		||||
and the lengths seem to be off by one, e.g. 0x84 is a length of 3, not 4.
 | 
			
		||||
 | 
			
		||||
| --- |
 | 
			
		||||
| "add"        | 81 84 61 64 64
 | 
			
		||||
| "_ENV"       | 81 85 5f 45 4e 56
 | 
			
		||||
| "print"      | 04 86 70 72 69 6e 74
 | 
			
		||||
| "format"     | 04 87 66 6f 72 6d 61 74
 | 
			
		||||
| "@math.lua"  | 01 8a 40 6d 61 74 68 2e 6c 75 61
 | 
			
		||||
| "1 + 2 = %i" | 04 8b 31 20 2b 20 32 20 3d 20 25 69
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,171 @@
 | 
			
		|||
enum Instruction {
 | 
			
		||||
	VarArgPrep (i32),
 | 
			
		||||
	GetTabUp (u8, u8, u8),
 | 
			
		||||
	GetI (u8, u8, u8),
 | 
			
		||||
	EqK (u8, u8, u8),
 | 
			
		||||
	Jmp (i32),
 | 
			
		||||
	LoadK (u8, i32),
 | 
			
		||||
	Call (u8, u8, u8),
 | 
			
		||||
	Closure (u8, i32),
 | 
			
		||||
	Return (u8, u8, u8),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive (Clone, Debug, PartialEq)]
 | 
			
		||||
enum Value {
 | 
			
		||||
	Nil,
 | 
			
		||||
	False,
 | 
			
		||||
	True,
 | 
			
		||||
	Float (f64),
 | 
			
		||||
	String (String),
 | 
			
		||||
	
 | 
			
		||||
	// These are all bogus, I haven't figured out how to implement
 | 
			
		||||
	// tables and function pointers yet
 | 
			
		||||
	
 | 
			
		||||
	BogusArg,
 | 
			
		||||
	BogusPrint,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Value {
 | 
			
		||||
	fn default () -> Self {
 | 
			
		||||
		Self::Nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From <String> for Value {
 | 
			
		||||
	fn from (x: String) -> Self {
 | 
			
		||||
		Self::String (x)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From <&str> for Value {
 | 
			
		||||
	fn from (x: &str) -> Self {
 | 
			
		||||
		Self::from (String::from (x))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Chunk {
 | 
			
		||||
	instructions: Vec <Instruction>,
 | 
			
		||||
	constants: Vec <Value>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	let arg: Vec <_> = std::env::args ().collect ();
 | 
			
		||||
	
 | 
			
		||||
	let chunk = Chunk {
 | 
			
		||||
		instructions: vec! [
 | 
			
		||||
			Instruction::VarArgPrep (0),
 | 
			
		||||
			Instruction::GetTabUp (0, 0, 0),
 | 
			
		||||
			Instruction::GetI (0, 0, 1),
 | 
			
		||||
			Instruction::EqK (0, 1, 0),
 | 
			
		||||
			Instruction::Jmp (4),
 | 
			
		||||
			Instruction::GetTabUp (0, 0, 2),
 | 
			
		||||
			Instruction::LoadK (1, 3),
 | 
			
		||||
			Instruction::Call (0, 2, 1),
 | 
			
		||||
			Instruction::Jmp (3),
 | 
			
		||||
			Instruction::GetTabUp (0, 0, 2),
 | 
			
		||||
			Instruction::LoadK (1, 4),
 | 
			
		||||
			Instruction::Call (0, 2, 1),
 | 
			
		||||
			Instruction::Return (1, 1, 1),
 | 
			
		||||
		],
 | 
			
		||||
		constants: vec! [
 | 
			
		||||
			"arg",
 | 
			
		||||
			"93",
 | 
			
		||||
			"print",
 | 
			
		||||
			"it's 93",
 | 
			
		||||
			"it's not 93",
 | 
			
		||||
		].into_iter ().map (|s| Value::from (s)).collect (),
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	let mut registers = vec! [Value::default (); 256];
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	let mut program_counter = 0i32;
 | 
			
		||||
	let max_iters = 2000;
 | 
			
		||||
	
 | 
			
		||||
	for _ in 0..max_iters {
 | 
			
		||||
		let instruction = chunk.instructions.get (usize::try_from (program_counter).unwrap ()).unwrap ();
 | 
			
		||||
		
 | 
			
		||||
		let r = &mut registers;
 | 
			
		||||
		let k = &chunk.constants;
 | 
			
		||||
		
 | 
			
		||||
		match instruction {
 | 
			
		||||
			Instruction::Call (a, b, c) => {
 | 
			
		||||
				// Take arguments from registers [a + 1, a + b)
 | 
			
		||||
				// Call the function in register [a]
 | 
			
		||||
				// Return values in registers [a, a + c - 1)
 | 
			
		||||
				// 
 | 
			
		||||
				// That is, call a with b - 1 arguments and expect c returns
 | 
			
		||||
				// 
 | 
			
		||||
				// e.g. CALL 0 2 1 mean "Call 0 with 1 argument, return 1 value", like for printing a constant
 | 
			
		||||
				
 | 
			
		||||
				// TODO: Only implement printing constants for now
 | 
			
		||||
				
 | 
			
		||||
				let a = usize::try_from (*a).unwrap ();
 | 
			
		||||
				
 | 
			
		||||
				assert_eq! (*b, 2);
 | 
			
		||||
				assert_eq! (*c, 1);
 | 
			
		||||
				
 | 
			
		||||
				println! ("{:?}", r [a + 1]);
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::EqK (a, b, c_k) => {
 | 
			
		||||
				let a = usize::try_from (*a).unwrap ();
 | 
			
		||||
				let b = usize::try_from (*b).unwrap ();
 | 
			
		||||
				
 | 
			
		||||
				let equal = r [a] == k [b];
 | 
			
		||||
				
 | 
			
		||||
				match (equal, c_k) {
 | 
			
		||||
					(true, 0) => program_counter += 1,
 | 
			
		||||
					(false, 1) => program_counter += 1,
 | 
			
		||||
					_ => (),
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::GetTabUp (a, b, c) => {
 | 
			
		||||
				let a = usize::try_from (*a).unwrap ();
 | 
			
		||||
				let b = usize::try_from (*b).unwrap ();
 | 
			
		||||
				let c = usize::try_from (*c).unwrap ();
 | 
			
		||||
				
 | 
			
		||||
				// Only supported upvalue is `_ENV`
 | 
			
		||||
				assert_eq! (b, 0);
 | 
			
		||||
				
 | 
			
		||||
				let key = k.get (c).unwrap ();
 | 
			
		||||
				let value = match key {
 | 
			
		||||
					Value::String (s) => match s.as_str() {
 | 
			
		||||
						"arg" => Value::BogusArg,
 | 
			
		||||
						"print" => Value::BogusPrint,
 | 
			
		||||
						_ => panic! ("key not in _ENV upvalue"),
 | 
			
		||||
					},
 | 
			
		||||
					_ => unimplemented!(),
 | 
			
		||||
				};
 | 
			
		||||
				
 | 
			
		||||
				r [a] = value;
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::GetI (a, b, c) => {
 | 
			
		||||
				let a = usize::try_from (*a).unwrap ();
 | 
			
		||||
				let b = usize::try_from (*b).unwrap ();
 | 
			
		||||
				let c = usize::try_from (*c).unwrap ();
 | 
			
		||||
				
 | 
			
		||||
				let table = r.get (b).unwrap ();
 | 
			
		||||
				let value = match table {
 | 
			
		||||
					Value::BogusArg => arg.get (c).unwrap ().as_str().into (),
 | 
			
		||||
					_ => unimplemented!(),
 | 
			
		||||
				};
 | 
			
		||||
				
 | 
			
		||||
				r [a] = value;
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::Jmp (sJ) => program_counter += sJ,
 | 
			
		||||
			Instruction::LoadK (a, bx) => {
 | 
			
		||||
				let a  = usize::try_from  (*a).unwrap ();
 | 
			
		||||
				let bx = usize::try_from (*bx).unwrap ();
 | 
			
		||||
				
 | 
			
		||||
				r [a] = k [bx].clone ();
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::Return (_a, _b, _c) => {
 | 
			
		||||
				break;
 | 
			
		||||
			},
 | 
			
		||||
			Instruction::VarArgPrep (_) => (),
 | 
			
		||||
			_ => (),
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		program_counter += 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
-- Put some very long comment here so the error will
 | 
			
		||||
-- have an interesting line number in the traceback
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
-- 
 | 
			
		||||
 | 
			
		||||
error ("bogus")
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
print "Hello."
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
if arg [1] == "93" then
 | 
			
		||||
	print "it's 93"
 | 
			
		||||
else
 | 
			
		||||
	print "it's not 93"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function unused_fn ()
 | 
			
		||||
	print "unused"
 | 
			
		||||
end
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
return 0
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
local function add (a, b)
 | 
			
		||||
	return a + b
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
print (("1 + 2 = %i"):format (add (1, 2)))
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
local function add (a, b)
 | 
			
		||||
	return a + b
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function sub (a, b)
 | 
			
		||||
	return a - b
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
print (("1 + 2 = %i"):format (add (1, 2)))
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue