From 0263d1511dfc2a87ce6abe2c4e47ce532e53fb8f Mon Sep 17 00:00:00 2001 From: zongor Date: Sat, 5 Jul 2025 12:40:33 -0400 Subject: [PATCH] WIP function calls --- src/compiler.c | 162 ++++++++----------------------------------- src/main.c | 14 ++-- src/opcodes.h | 123 ++++++++++++++++---------------- src/parser.c | 8 +++ src/parser.h | 3 + src/vm.c | 90 +++++++++++++----------- test/add.zre | 3 +- test/fib.zre | 6 ++ test/func-simple.zre | 6 ++ test/funcs.zre | 14 ++++ 10 files changed, 188 insertions(+), 241 deletions(-) create mode 100644 test/fib.zre create mode 100644 test/func-simple.zre create mode 100644 test/funcs.zre diff --git a/src/compiler.c b/src/compiler.c index 8a3377c..61dfec3 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -33,141 +33,39 @@ void demo_add_compile(Value *memory) { memory[i++].u = OP(OP_HALT, 0, 0, 0); } + +static void letDeclaration() { +/* uint8_t global = parseVariable("Expect variable name."); */ + +/* if (match(TOKEN_EQUAL)) { */ +/* expression(); */ +/* } else { */ +/* emitByte(OP_NIL); */ +/* } */ +/* consume(TOKEN_SEMICOLON, */ +/* "Expect ';' after variable declaration."); */ + +/* defineVariable(global); */ +} + + +static void declaration(Token t) { + if (t.type == TOKEN_LET) { + letDeclaration(); + } else { + /* statement(); */ + } +} + +/** + * Compile. + */ void compile(Value *memory, char *buffer) { initTokenizer(buffer); Token t = nextToken(); - do { + while (t.type != TOKEN_EOF) { printToken(t); - switch (t.type) { - case TOKEN_LEFT_PAREN: - break; - case TOKEN_RIGHT_PAREN: - break; - case TOKEN_LEFT_BRACE: - break; - case TOKEN_RIGHT_BRACE: - break; - case TOKEN_SEMICOLON: - break; - case TOKEN_IDENTIFIER: - break; - case TOKEN_STRING: - break; - case TOKEN_FLOAT: - break; - case TOKEN_U8: - break; - case TOKEN_I8: - break; - case TOKEN_U16: - break; - case TOKEN_I16: - break; - case TOKEN_U64: - break; - case TOKEN_I64: - break; - case TOKEN_INT: - break; - case TOKEN_UINT: - break; - case TOKEN_FALSE: - break; - case TOKEN_TRUE: - break; - case TOKEN_NULL: - break; - case TOKEN_EOF: - break; - case TOKEN_ERROR: - break; - case TOKEN_ADD: - break; - case TOKEN_SUB: - break; - case TOKEN_MUL: - break; - case TOKEN_DIV: - break; - case TOKEN_MOD: - break; - case TOKEN_GT: - break; - case TOKEN_LT: - break; - case TOKEN_EQ: - break; - case TOKEN_GE: - break; - case TOKEN_LE: - break; - case TOKEN_NE: - break; - case TOKEN_AND: - break; - case TOKEN_OR: - break; - case TOKEN_XOR: - break; - case TOKEN_SHIFTRIGHT: - break; - case TOKEN_SHIFTLEFT: - break; - case TOKEN_FN: - break; - case TOKEN_TO: - break; - case TOKEN_IN: - break; - case TOKEN_IS: - break; - case TOKEN_AS: - break; - case TOKEN_USE: - break; - case TOKEN_IF: - break; - case TOKEN_ELSE: - break; - case TOKEN_DEFAULT: - break; - case TOKEN_FOR: - break; - case TOKEN_TRY: - break; - case TOKEN_CATCH: - break; - case TOKEN_WHILE: - break; - case TOKEN_DO: - break; - case TOKEN_EXIT: - break; - case TOKEN_SWITCH: - break; - case TOKEN_RETURN: - break; - case TOKEN_CONST: - break; - case TOKEN_TYPE: - break; - case TOKEN_THIS: - break; - case TOKEN_YIELD: - break; - case TOKEN_CASE: - break; - case TOKEN_ASSERT: - break; - case TOKEN_BREAK: - break; - case TOKEN_LET: - break; - case TOKEN_PRINT: - break; - default: - break; - } + declaration(t); t = nextToken(); - } while (t.type != TOKEN_EOF); + } } diff --git a/src/main.c b/src/main.c index 38ccea5..42f2f12 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ -#include "parser.h" #include "compiler.h" #include "debug.h" +#include "parser.h" #include "vm.h" #include #include @@ -16,14 +16,14 @@ #include #endif -VM *vm; +VM vm = {0}; void mainloop() { - if (!step_vm(vm)) { + if (!step_vm(&vm)) { #ifdef __EMSCRIPTEN__ emscripten_cancel_main_loop(); /* this should "kill" the app. */ #else - core_dump(vm); + core_dump(&vm); exit(0); #endif } @@ -50,10 +50,8 @@ int main(int argc, char **argv) { } initTokenMap(); - vm = init_vm(); - vm->frame = (Frame*)malloc(sizeof(Frame)); - compile(vm->memory, buffer); - demo_add_compile(vm->memory); + compile(vm.memory, buffer); + /* demo_add_compile(vm.memory); */ #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(mainloop, 0, 1); diff --git a/src/opcodes.h b/src/opcodes.h index 24f535c..50d6ef0 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -10,76 +10,81 @@ typedef union { char c[4]; /* 4 Byte char array for string packing */ } Value; -typedef union { - uint32_t length; - char *string; - Value *array; - Value *code; -} Object; +typedef struct { + uint32_t start; + uint32_t end; +} Slice; #define MAX_REGS 256 typedef struct { Value registers[MAX_REGS]; /* R0-R255 */ - Object *locals; /* Short lived object */ + Slice allocated; } Frame; -#define STACK_SIZE 128 +#define MEMORY_SIZE 1024 +#define STACK_SIZE 128 +#define RETURN_STACK_SIZE 256 typedef struct { - Value stack[STACK_SIZE]; - uint32_t stack_size; - uint32_t pc; /* Program counter */ - Value *memory; + Value memory[MEMORY_SIZE]; /* Memory array */ uint32_t memory_size; - Frame *frame; + Value return_stack[RETURN_STACK_SIZE]; + uint32_t return_stack_size; + Frame stack[STACK_SIZE]; + uint32_t stack_size; + uint32_t rp; /* Return stack pointer (top of stack) */ + uint32_t sp; /* Stack pointer (top of stack) */ + uint32_t pc; /* Program counter */ } VM; typedef enum { - OP_HALT, /* terminate execution */ - OP_LOADI, /* dest = next memory location as int */ - OP_LOADU, /* dest = next memory location as uint */ - OP_LOADF, /* dest = next memory location as float */ - OP_STOREI, /* next memory location = src1 as int */ - OP_STOREU, /* next memory location = src1 as uint */ - OP_STOREF, /* next memory location = src1 as float */ - OP_ADD_INT, /* dest = src1 + src2 */ - OP_SUB_INT, /* dest = src1 - src2 */ - OP_MUL_INT, /* dest = src1 * src2 */ - OP_DIV_INT, /* dest = src1 / src2 */ - OP_JEQ_INT, /* jump to address dest if src1 as int == src2 as int */ - OP_JGT_INT, /* jump to address dest if src1 as int > src2 as int*/ - OP_JLT_INT, /* jump to address dest if src1 as int < src2 as int */ - OP_JLE_INT, /* jump to address dest if src1 as int <= src2 as int */ - OP_JGE_INT, /* jump to address dest if src1 as int >= src2 as int*/ - OP_INT_TO_REAL, /* dest = src1 as f32 */ - OP_ADD_UINT, /* dest = src1 + src2 */ - OP_SUB_UINT, /* dest = src1 - src2 */ - OP_MUL_UINT, /* dest = src1 * src2 */ - OP_DIV_UINT, /* dest = src1 / src2 */ - OP_JEQ_UINT, /* jump to address dest if src1 as int == src2 as uint */ - OP_JGT_UINT, /* jump to address dest if src1 as int > src2 as uint*/ - OP_JLT_UINT, /* jump to address dest if src1 as int < src2 as uint */ - OP_JLE_UINT, /* jump to address dest if src1 as int <= src2 as uint */ - OP_JGE_UINT, /* jump to address dest if src1 as int >= src2 as uint*/ - OP_UINT_TO_REAL, /* dest = src1 as f32 */ - OP_ADD_REAL, /* dest = src1 + src2 */ - OP_SUB_REAL, /* dest = src1 - src2 */ - OP_MUL_REAL, /* dest = src1 * src2 */ - OP_DIV_REAL, /* dest = src1 / src2 */ - OP_JEQ_REAL, /* jump to address dest if src1 as real == src2 as real */ - OP_JGE_REAL, /* jump to address dest if src1 as real >= src2 as real */ - OP_JGT_REAL, /* jump to address dest if src1 as real > src2 as real */ - OP_JLT_REAL, /* jump to address dest if src1 as real < src2 as real */ - OP_JLE_REAL, /* jump to address dest if src1 as real <= src2 as real */ - OP_REAL_TO_INT, /* dest = src1 as int */ - OP_REAL_TO_UINT, /* dest = src1 as uint */ - OP_MOV, /* dest = src1 */ - OP_JMP, /* jump to address src1 unconditionally */ - OP_INT_TO_STRING, /* dest = src1 as str */ - OP_UINT_TO_STRING, /* dest = src1 as str */ - OP_REAL_TO_STRING, /* dest = src1 as str */ - OP_READ_STRING, /* dest = read as str */ - OP_PRINT_STRING, /* write src1 to stdout */ - OP_CMP_STRING, /* dest = (str == src2) as bool */ + OP_HALT, /* halt : terminate execution */ + OP_LOADI, /* lodi : dest = next memory location as int */ + OP_LOADU, /* lodu : dest = next memory location as uint */ + OP_LOADF, /* lodf : dest = next memory location as float */ + OP_STOREI, /* stri : next memory location = src1 as int */ + OP_STOREU, /* stru : next memory location = src1 as uint */ + OP_STOREF, /* strf : next memory location = src1 as float */ + OP_ADD_INT, /* addi : dest = src1 + src2 */ + OP_SUB_INT, /* subs : dest = src1 - src2 */ + OP_MUL_INT, /* mulm : dest = src1 * src2 */ + OP_DIV_INT, /* divd : dest = src1 / src2 */ + OP_JEQ_INT, /* jeqi : jump to address dest if src1 as int == src2 as int */ + OP_JGT_INT, /* jgti : jump to address dest if src1 as int > src2 as int*/ + OP_JLT_INT, /* jlti : jump to address dest if src1 as int < src2 as int */ + OP_JLE_INT, /* jlei : jump to address dest if src1 as int <= src2 as int */ + OP_JGE_INT, /* jgei : jump to address dest if src1 as int >= src2 as int*/ + OP_INT_TO_REAL, /* itor : dest = src1 as f32 */ + OP_ADD_UINT, /* addu : dest = src1 + src2 */ + OP_SUB_UINT, /* subu : dest = src1 - src2 */ + OP_MUL_UINT, /* mulu : dest = src1 * src2 */ + OP_DIV_UINT, /* divu : dest = src1 / src2 */ + OP_JEQ_UINT, /* jequ : jump to address dest if src1 as int == src2 as uint */ + OP_JGT_UINT, /* jgtu : jump to address dest if src1 as int > src2 as uint*/ + OP_JLT_UINT, /* jltu : jump to address dest if src1 as int < src2 as uint */ + OP_JLE_UINT, /* jleu : jump to address dest if src1 as int <= src2 as uint */ + OP_JGE_UINT, /* jgeu : jump to address dest if src1 as int >= src2 as uint*/ + OP_UINT_TO_REAL, /* utor : dest = src1 as f32 */ + OP_ADD_REAL, /* addr : dest = src1 + src2 */ + OP_SUB_REAL, /* subr : dest = src1 - src2 */ + OP_MUL_REAL, /* mulr : dest = src1 * src2 */ + OP_DIV_REAL, /* divr : dest = src1 / src2 */ + OP_JEQ_REAL, /* jeqr : jump to address dest if src1 as real == src2 as real */ + OP_JGE_REAL, /* jgtr : jump to address dest if src1 as real >= src2 as real */ + OP_JGT_REAL, /* jltr : jump to address dest if src1 as real > src2 as real */ + OP_JLT_REAL, /* jler : jump to address dest if src1 as real < src2 as real */ + OP_JLE_REAL, /* jger : jump to address dest if src1 as real <= src2 as real */ + OP_REAL_TO_INT, /* rtoi : dest = src1 as int */ + OP_REAL_TO_UINT, /* rtou : dest = src1 as uint */ + OP_MOV, /* move : dest = src1 */ + OP_JMP, /* jump : jump to address src1 unconditionally */ + OP_CALL, /* creates a new frame */ + OP_RETURN, /* returns from a frame to the parent frame */ + OP_INT_TO_STRING, /* itos : dest = src1 as str */ + OP_UINT_TO_STRING, /* utos : dest = src1 as str */ + OP_REAL_TO_STRING, /* rtos : dest = src1 as str */ + OP_READ_STRING, /* read : dest = read as str */ + OP_PRINT_STRING, /* wrte : write src1 to stdout */ + OP_CMP_STRING, /* cmps : dest = (str == src2) as bool */ } Opcode; /* defines a uint32 opcode */ diff --git a/src/parser.c b/src/parser.c index b82fd99..9e01e36 100644 --- a/src/parser.c +++ b/src/parser.c @@ -48,6 +48,7 @@ void initTokenMap() { put_TokenMap(tokenMap, "ge", TOKEN_GE); put_TokenMap(tokenMap, "srl", TOKEN_SHIFTRIGHT); put_TokenMap(tokenMap, "sll", TOKEN_SHIFTLEFT); + put_TokenMap(tokenMap, "toS", TOKEN_TO_S); } typedef struct Tokenizer Tokenizer; @@ -222,6 +223,10 @@ Token nextToken() { return makeToken(TOKEN_MUL); case ';': return makeToken(TOKEN_SEMICOLON); + case '=': + return makeToken(TOKEN_EQUALS); + case '.': + return makeToken(TOKEN_DOT); case '"': return string(); } @@ -234,6 +239,8 @@ void printToken(Token t) { char *str = currentTokenToS(); switch (t.type) { + PRINT_TOKEN_CASE(TOKEN_EQUALS) + PRINT_TOKEN_CASE(TOKEN_DOT) PRINT_TOKEN_CASE(TOKEN_LEFT_PAREN) PRINT_TOKEN_CASE(TOKEN_RIGHT_PAREN) PRINT_TOKEN_CASE(TOKEN_LEFT_BRACE) @@ -299,6 +306,7 @@ void printToken(Token t) { PRINT_TOKEN_CASE(TOKEN_BREAK) PRINT_TOKEN_CASE(TOKEN_LET) PRINT_TOKEN_CASE(TOKEN_PRINT) + PRINT_TOKEN_CASE(TOKEN_TO_S) } free(str); } diff --git a/src/parser.h b/src/parser.h index d89f0aa..a86cb97 100644 --- a/src/parser.h +++ b/src/parser.h @@ -12,6 +12,8 @@ typedef enum TokenType TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN_SEMICOLON, + TOKEN_EQUALS, + TOKEN_DOT, /* Literals */ TOKEN_IDENTIFIER, TOKEN_STRING, @@ -75,6 +77,7 @@ typedef enum TokenType TOKEN_BREAK, TOKEN_LET, TOKEN_PRINT, + TOKEN_TO_S, } TokenType; #define PRINT_TOKEN_CASE(token_suffix) \ diff --git a/src/vm.c b/src/vm.c index 6fb142a..f407ff5 100644 --- a/src/vm.c +++ b/src/vm.c @@ -3,19 +3,21 @@ #define COMPARE_AND_JUMP(type, accessor, op) \ do { \ - type value = vm->frame->registers[src1].accessor; \ - type value2 = vm->frame->registers[src2].accessor; \ + type value = vm->stack[vm->sp].registers[src1].accessor; \ + type value2 = vm->stack[vm->sp].registers[src2].accessor; \ vm->pc = (value op value2) ? dest : vm->pc; \ - return true; \ + return true; \ } while (0) #define MATH_OP(accessor, op) \ do { \ - vm->frame->registers[dest].accessor = \ - vm->frame->registers[src1] \ - .accessor op vm->frame->registers[src2] \ + vm->stack[vm->sp].registers[dest].accessor = \ + vm->stack[vm->sp] \ + .registers[src1] \ + .accessor op vm->stack[vm->sp] \ + .registers[src2] \ .accessor; \ - return true; \ + return true; \ } while (0) /** @@ -31,18 +33,6 @@ void mem_strcpy(Value *memory, const char *str, uint32_t length, } } -#define MEMORY_SIZE 1024 - -VM *init_vm() { - Value memory[MEMORY_SIZE] = {0}; /* Memory array */ - VM *vm = (VM *)malloc(sizeof(VM)); - vm->memory = memory; - vm->memory_size = MEMORY_SIZE; - vm->stack_size = 0; - - return vm; -} - /** * Step to the next opcode in the vm. */ @@ -62,23 +52,37 @@ bool step_vm(VM *vm) { switch (opcode) { case OP_HALT: return false; + case OP_CALL: + vm->return_stack[vm->rp++].u = vm->pc; /* push the return address */ + vm->sp++; /* increment to the next free frame */ + + return true; + case OP_RETURN: + vm->pc = vm->return_stack[vm->rp].u; /* set pc to return address */ + Slice s = vm->stack[vm->rp].allocated; + memset(&vm->memory[s.start], 0, s.end - s.start); /* deallocate memory from slice */ + vm->return_stack[vm->rp--].u = 0; /* deallocate the stack */ + vm->return_stack[vm->rp] = vm->stack[vm->sp].registers[0]; /* push the return value onto the return stack (always register 0) */ + + + return true; case OP_LOADI: - vm->frame->registers[dest].i = vm->memory[vm->pc++].i; + vm->stack[vm->sp].registers[dest].i = vm->memory[vm->pc++].i; return true; case OP_LOADU: - vm->frame->registers[dest].u = vm->memory[vm->pc++].u; + vm->stack[vm->sp].registers[dest].u = vm->memory[vm->pc++].u; return true; case OP_LOADF: - vm->frame->registers[dest].f = vm->memory[vm->pc++].f; + vm->stack[vm->sp].registers[dest].f = vm->memory[vm->pc++].f; return true; case OP_STOREI: - vm->memory[vm->pc++].i = vm->frame->registers[src1].i; + vm->memory[vm->pc++].i = vm->stack[vm->sp].registers[src1].i; return true; case OP_STOREU: - vm->memory[vm->pc++].u = vm->frame->registers[src1].u; + vm->memory[vm->pc++].u = vm->stack[vm->sp].registers[src1].u; return true; case OP_STOREF: - vm->memory[vm->pc++].f = vm->frame->registers[src1].f; + vm->memory[vm->pc++].f = vm->stack[vm->sp].registers[src1].f; return true; case OP_ADD_INT: MATH_OP(i, +); @@ -105,22 +109,26 @@ bool step_vm(VM *vm) { case OP_DIV_REAL: MATH_OP(f, /); case OP_REAL_TO_INT: - vm->frame->registers[dest].i = (int32_t)(vm->frame->registers[src1].f); + vm->stack[vm->sp].registers[dest].i = + (int32_t)(vm->stack[vm->sp].registers[src1].f); return true; case OP_INT_TO_REAL: - vm->frame->registers[dest].f = (float)(vm->frame->registers[src1].i); + vm->stack[vm->sp].registers[dest].f = + (float)(vm->stack[vm->sp].registers[src1].i); return true; case OP_REAL_TO_UINT: - vm->frame->registers[dest].u = (uint32_t)(vm->frame->registers[src1].f); + vm->stack[vm->sp].registers[dest].u = + (uint32_t)(vm->stack[vm->sp].registers[src1].f); return true; case OP_UINT_TO_REAL: - vm->frame->registers[dest].f = (float)(vm->frame->registers[src1].u); + vm->stack[vm->sp].registers[dest].f = + (float)(vm->stack[vm->sp].registers[src1].u); return true; case OP_MOV: - vm->frame->registers[dest] = vm->frame->registers[src1]; + vm->stack[vm->sp].registers[dest] = vm->stack[vm->sp].registers[src1]; return true; case OP_JMP: - vm->pc = vm->frame->registers[src1].u; /* Jump to address */ + vm->pc = vm->stack[vm->sp].registers[src1].u; /* Jump to address */ return true; case OP_JEQ_UINT: { COMPARE_AND_JUMP(uint32_t, u, ==); @@ -168,31 +176,31 @@ bool step_vm(VM *vm) { COMPARE_AND_JUMP(float, u, <=); } case OP_INT_TO_STRING: { - int32_t a = (int32_t)vm->frame->registers[src1].i; - uint32_t str_dest = (uint32_t)vm->frame->registers[dest].u; + int32_t a = (int32_t)vm->stack[vm->sp].registers[src1].i; + uint32_t str_dest = (uint32_t)vm->stack[vm->sp].registers[dest].u; char buffer[32]; int len = sprintf(buffer, "%d", a); mem_strcpy(vm->memory, buffer, len, str_dest); return true; } case OP_UINT_TO_STRING: { - uint32_t a = (uint32_t)vm->frame->registers[src1].u; - uint32_t str_dest = (uint32_t)vm->frame->registers[dest].u; + uint32_t a = (uint32_t)vm->stack[vm->sp].registers[src1].u; + uint32_t str_dest = (uint32_t)vm->stack[vm->sp].registers[dest].u; char buffer[32]; int len = sprintf(buffer, "%d", a); mem_strcpy(vm->memory, buffer, len, str_dest); return true; } case OP_REAL_TO_STRING: { - float a = (float)vm->frame->registers[src1].f; - uint32_t str_dest = (uint32_t)vm->frame->registers[dest].u; + float a = (float)vm->stack[vm->sp].registers[src1].f; + uint32_t str_dest = (uint32_t)vm->stack[vm->sp].registers[dest].u; char buffer[32]; int len = sprintf(buffer, "%f", a); mem_strcpy(vm->memory, buffer, len, str_dest); return true; } case OP_READ_STRING: { - uint32_t str_dest = (uint32_t)vm->frame->registers[dest].u; + uint32_t str_dest = (uint32_t)vm->stack[vm->sp].registers[dest].u; uint32_t buffer = str_dest + 1; uint32_t length = 0; while (1) { @@ -208,7 +216,7 @@ bool step_vm(VM *vm) { return true; } case OP_PRINT_STRING: { - uint32_t ptr = (uint32_t)vm->frame->registers[src1].u; + uint32_t ptr = (uint32_t)vm->stack[vm->sp].registers[src1].u; uint32_t length = vm->memory[ptr].u; uint32_t str_src = ptr + 1; uint32_t i; @@ -222,8 +230,8 @@ bool step_vm(VM *vm) { return true; } case OP_CMP_STRING: { - uint32_t addr1 = (uint32_t)vm->frame->registers[src1].u; - uint32_t addr2 = (uint32_t)vm->frame->registers[src2].u; + uint32_t addr1 = (uint32_t)vm->stack[vm->sp].registers[src1].u; + uint32_t addr2 = (uint32_t)vm->stack[vm->sp].registers[src2].u; uint32_t length1 = vm->memory[addr1 - 1].u; uint32_t length2 = vm->memory[addr2 - 1].u; uint32_t equal = 1; diff --git a/test/add.zre b/test/add.zre index c7b7cfd..a10e033 100644 --- a/test/add.zre +++ b/test/add.zre @@ -1 +1,2 @@ -print(1 + 2); +let sum = 1 + 2; +print(sum.toS()); diff --git a/test/fib.zre b/test/fib.zre new file mode 100644 index 0000000..9f37ba1 --- /dev/null +++ b/test/fib.zre @@ -0,0 +1,6 @@ +fn fib(int n) { + if (n < 2) return n; + return fib(n - 2) + fib(n - 1); +} + +print fib(35); diff --git a/test/func-simple.zre b/test/func-simple.zre new file mode 100644 index 0000000..cc77d10 --- /dev/null +++ b/test/func-simple.zre @@ -0,0 +1,6 @@ +fn add(a int, b int) int { + return a + b; +} + +let sum = add(1, 1); +print(sum.toS()); diff --git a/test/funcs.zre b/test/funcs.zre new file mode 100644 index 0000000..0f2fd45 --- /dev/null +++ b/test/funcs.zre @@ -0,0 +1,14 @@ +fn first() { + int a = 1; + second(a, -1); + int b = 2; + second(a, b); +} + +fn second(int c, int d) { + str numbers = c.toS() + " " + d.toS(); + print(numbers); + ! implied return because return type is null +} + +first();