diff --git a/examples/switch.porth b/examples/switch.porth new file mode 100644 index 00000000..826acec4 --- /dev/null +++ b/examples/switch.porth @@ -0,0 +1,19 @@ +include "std.porth" + +2 switch + 1 case + "case 1\n" puts + break + 2 case + "case 2\n" puts + break + 3 case + "case 3\n" puts + break + 4 case + "case 4\n" puts + break + case + "Default\n" puts + break +end diff --git a/examples/switch.txt b/examples/switch.txt new file mode 100644 index 00000000..6e91e9a2 --- /dev/null +++ b/examples/switch.txt @@ -0,0 +1,9 @@ +:i argc 0 +:b stdin 0 + +:i returncode 0 +:b stdout 7 +case 2 + +:b stderr 0 + diff --git a/porth.py b/porth.py index 17687171..162c575b 100755 --- a/porth.py +++ b/porth.py @@ -26,6 +26,10 @@ class Keyword(Enum): IF=auto() + SWITCH=auto() + CASE=auto() + DEFAULT=auto() + BREAK=auto() END=auto() ELSE=auto() WHILE=auto() @@ -81,6 +85,10 @@ class OpType(Enum): PUSH_STR=auto() INTRINSIC=auto() IF=auto() + SWITCH=auto() + CASE=auto() + DEFAULT=auto() + BREAK=auto() END=auto() ELSE=auto() WHILE=auto() @@ -110,7 +118,7 @@ class Token: class Op: typ: OpType token: Token - operand: Optional[Union[int, str, Intrinsic, OpAddr]] = None + operand: Optional[Union[int, str, Intrinsic, OpAddr, list]] = None Program=List[Op] @@ -158,7 +166,7 @@ def simulate_little_endian_linux(program: Program, argv: List[str]): ip = 0 while ip < len(program): - assert len(OpType) == 8, "Exhaustive op handling in simulate_little_endian_linux" + assert len(OpType) == 12, "Exhaustive op handling in simulate_little_endian_linux" op = program[ip] try: if op.typ == OpType.PUSH_INT: @@ -180,6 +188,23 @@ def simulate_little_endian_linux(program: Program, argv: List[str]): ip += 1 elif op.typ == OpType.IF: ip += 1 + elif op.typ == OpType.SWITCH: + a = stack.pop() + if a in cast(list, op.operand): + for i in range(0, len(cast(list, op.operand)) - 3, 2): + if cast(list, op.operand)[i] == a: + ip = cast(list, op.operand)[i+1] + else: + if cast(list, op.operand)[-2] == -1: # If there is no default case jump to it otherwise jump to end + ip = cast(list, op.operand)[-1] + else: + ip = cast(list, op.operand)[-2] + elif op.typ == OpType.BREAK: + ip = cast(list, program[cast(int, op.operand)].operand)[-1] + elif op.typ == OpType.CASE: + ip += 1 + elif op.typ == OpType.DEFAULT: + ip += 1 elif op.typ == OpType.WHILE: ip += 1 elif op.typ == OpType.ELSE: @@ -527,7 +552,7 @@ def type_check_program(program: Program): block_stack: List[Tuple[DataStack, OpType]] = [] for ip in range(len(program)): op = program[ip] - assert len(OpType) == 8, "Exhaustive ops handling in type_check_program()" + assert len(OpType) == 12, "Exhaustive ops handling in type_check_program()" if op.typ == OpType.PUSH_INT: stack.append((DataType.INT, op.token)) elif op.typ == OpType.PUSH_STR: @@ -973,9 +998,26 @@ def type_check_program(program: Program): block_stack.append((copy(stack), op.typ)) elif op.typ == OpType.WHILE: block_stack.append((copy(stack), op.typ)) + elif op.typ == OpType.SWITCH: + if len(stack) < 1: + not_enough_arguments(op) + a_type, a_loc = stack.pop() + if a_type != DataType.INT: + compiler_error_with_expansion_stack(op.token, "Invalid argument for the switch condition. Expected INT.") + block_stack.append((copy(stack), op.typ)) + elif op.typ == OpType.CASE: + if len(stack) < 1: + not_enough_arguments(op) + a_type, a_loc = stack.pop() + if a_type != DataType.INT: + compiler_error_with_expansion_stack(op.token, "Invalid argument for the case condition. Expected INT.") + elif op.typ == OpType.DEFAULT: + pass # Default is only a indicator. + elif op.typ == OpType.BREAK: + pass # Break is only a indicator. elif op.typ == OpType.END: block_snapshot, block_type = block_stack.pop() - assert len(OpType) == 8, "Exhaustive handling of op types" + assert len(OpType) == 12, "Exhaustive handling of op types" if block_type == OpType.ELSE: expected_types = list(map(lambda x: x[0], block_snapshot)) actual_types = list(map(lambda x: x[0], stack)) @@ -984,6 +1026,14 @@ def type_check_program(program: Program): compiler_note(op.token.loc, 'Expected types: %s' % expected_types) compiler_note(op.token.loc, 'Actual types: %s' % actual_types) exit(1) + elif block_type == OpType.SWITCH: + expected_types = list(map(lambda x: x[0], block_snapshot)) + actual_types = list(map(lambda x: x[0], stack)) + if expected_types != actual_types: + compiler_error_with_expansion_stack(op.token, 'switch block is not allowed to alter the types of the arguments on the data stack') + compiler_note(op.token.loc, 'Expected types: %s' % expected_types) + compiler_note(op.token.loc, 'Actual types: %s' % actual_types) + exit(1) elif block_type == OpType.DO: begin_snapshot, begin_type = block_stack.pop() @@ -1080,7 +1130,7 @@ def generate_nasm_linux_x86_64(program: Program, out_file_path: str): out.write(" mov [args_ptr], rsp\n") for ip in range(len(program)): op = program[ip] - assert len(OpType) == 8, "Exhaustive ops handling in generate_nasm_linux_x86_64" + assert len(OpType) == 12, "Exhaustive ops handling in generate_nasm_linux_x86_64" out.write("addr_%d:\n" % ip) if op.typ == OpType.PUSH_INT: assert isinstance(op.operand, int), "This could be a bug in the compilation step" @@ -1104,6 +1154,29 @@ def generate_nasm_linux_x86_64(program: Program, out_file_path: str): out.write(" ;; -- else --\n") assert isinstance(op.operand, int), "This could be a bug in the compilation step" out.write(" jmp addr_%d\n" % op.operand) + elif op.typ == OpType.SWITCH: + out.write(" ;; -- switch --\n") + out.write(" pop rax\n") + for i in range(0, len(cast(list, op.operand)) - 2, 2): + assert isinstance(cast(list, op.operand)[i], int), "This could be a bug in the compilation step" + out.write(" cmp rax, %d\n" % cast(list, op.operand)[i]) + i+=1 + assert isinstance(cast(list, op.operand)[i], int), "This could be a bug in the compilation step" + out.write(" je addr_%d\n" % cast(list, op.operand)[i]) + if cast(list, op.operand)[-2] != -1: # If there is a default statement jump to it otherwise jump to the end of the switch statement + out.write(" jmp addr_%d\n" % cast(list, op.operand)[-2]) + else: + out.write(" jmp addr_%d\n" % cast(list, op.operand)[-1]) + ip += 1 + elif op.typ == OpType.BREAK: + out.write(" ;; -- break --\n") + out.write(" jmp addr_%d\n" % cast(list, program[cast(int, op.operand)].operand)[-1]) # jump to the end of the switch statement + elif op.typ == OpType.CASE: + out.write(" ;; -- case --\n") + ip += 1 + elif op.typ == OpType.DEFAULT: + out.write(" ;; -- default --\n") + ip += 1 elif op.typ == OpType.END: assert isinstance(op.operand, int), "This could be a bug in the compilation step" out.write(" ;; -- end --\n") @@ -1398,9 +1471,13 @@ def generate_nasm_linux_x86_64(program: Program, out_file_path: str): out.write("args_ptr: resq 1\n") out.write("mem: resb %d\n" % MEM_CAPACITY) -assert len(Keyword) == 7, "Exhaustive KEYWORD_NAMES definition." +assert len(Keyword) == 11, "Exhaustive KEYWORD_NAMES definition." KEYWORD_NAMES = { 'if': Keyword.IF, + 'switch': Keyword.SWITCH, + 'case': Keyword.CASE, + 'default': Keyword.DEFAULT, + 'break': Keyword.BREAK, 'end': Keyword.END, 'else': Keyword.ELSE, 'while': Keyword.WHILE, @@ -1518,7 +1595,7 @@ def compile_tokens_to_program(tokens: List[Token], include_paths: List[str], exp program.append(Op(typ=OpType.PUSH_INT, operand=token.value, token=token)); ip += 1 elif token.typ == TokenType.KEYWORD: - assert len(Keyword) == 7, "Exhaustive keywords handling in compile_tokens_to_program()" + assert len(Keyword) == 11, "Exhaustive keywords handling in compile_tokens_to_program()" if token.value == Keyword.IF: program.append(Op(typ=OpType.IF, token=token)) stack.append(ip) @@ -1543,6 +1620,9 @@ def compile_tokens_to_program(tokens: List[Token], include_paths: List[str], exp if program[block_ip].typ == OpType.ELSE: program[block_ip].operand = ip program[ip].operand = ip + 1 + elif program[block_ip].typ == OpType.SWITCH: + cast(list, program[block_ip].operand)[-1] = ip + program[ip].operand = ip + 1 elif program[block_ip].typ == OpType.DO: assert program[block_ip].operand is not None block_begin_ip = program[block_ip].operand @@ -1561,6 +1641,34 @@ def compile_tokens_to_program(tokens: List[Token], include_paths: List[str], exp compiler_error_with_expansion_stack(program[block_ip].token, '`end` can only close `else`, `do` or `macro` blocks for now') exit(1) ip += 1 + elif token.value == Keyword.SWITCH: + program.append(Op(typ=OpType.SWITCH, token=token)) + stack.append(ip) + program[ip].operand = list([-1, -1]) + ip += 1 + elif token.value == Keyword.CASE: + program.append(Op(typ=OpType.CASE, token=token)) + block_ip = stack[-1] + if program[block_ip].typ != OpType.SWITCH: + compiler_error_with_expansion_stack(program[block_ip].token, '`case` may only exist within switch block') + compare_value = program[ip-1] + if compare_value.typ != OpType.PUSH_INT: + compiler_error_with_expansion_stack(program[block_ip].token, '`case` must have a compare value') + else: + cast(list, program[block_ip].operand).insert(-2, compare_value.operand) + cast(list, program[block_ip].operand).insert(-2, ip) + program[ip].operand = ip + 1 + ip += 1 + elif token.value == Keyword.DEFAULT: + if program[block_ip].typ != OpType.SWITCH: + compiler_error_with_expansion_stack(program[block_ip].token, '`default` may only exist within switch block') + if cast(list, program[block_ip].operand)[-2] != -1: + compiler_error_with_expansion_stack(program[block_ip].token, 'only one `default` may exist without an switch statement') + cast(list, program[block_ip].operand)[-2] = ip + elif token.value == Keyword.BREAK: + program.append(Op(typ=OpType.BREAK, token=token)) + program[ip].operand = stack[-1] + ip += 1 elif token.value == Keyword.WHILE: program.append(Op(typ=OpType.WHILE, token=token)) stack.append(ip) @@ -1620,7 +1728,7 @@ def compile_tokens_to_program(tokens: List[Token], include_paths: List[str], exp else: macro.tokens.append(token) if token.typ == TokenType.KEYWORD: - if token.value in [Keyword.IF, Keyword.WHILE, Keyword.MACRO]: + if token.value in [Keyword.IF, Keyword.WHILE, Keyword.MACRO, Keyword.SWITCH]: nesting_depth += 1 elif token.value == Keyword.END: nesting_depth -= 1 diff --git a/tests/default-less-switch.porth b/tests/default-less-switch.porth new file mode 100644 index 00000000..5b8d90e1 --- /dev/null +++ b/tests/default-less-switch.porth @@ -0,0 +1,18 @@ +include "std.porth" + +6 switch + 1 case + "case 1\n" puts + break + 2 case + "case 2\n" puts + break + 3 case + "case 3\n" puts + break + 4 case + "case 4\n" puts + break +end + +"after switch\n" puts diff --git a/tests/default-less-switch.txt b/tests/default-less-switch.txt new file mode 100644 index 00000000..7ad5db3a --- /dev/null +++ b/tests/default-less-switch.txt @@ -0,0 +1,9 @@ +:i argc 0 +:b stdin 0 + +:i returncode 0 +:b stdout 13 +after switch + +:b stderr 0 + diff --git a/tests/switch-default.porth b/tests/switch-default.porth new file mode 100644 index 00000000..796dc6b0 --- /dev/null +++ b/tests/switch-default.porth @@ -0,0 +1,19 @@ +include "std.porth" + +6 switch + 1 case + "case 1\n" puts + break + 2 case + "case 2\n" puts + break + 3 case + "case 3\n" puts + break + 4 case + "case 4\n" puts + break + default + "Default\n" puts + break +end diff --git a/tests/switch-default.txt b/tests/switch-default.txt new file mode 100644 index 00000000..b5ecd200 --- /dev/null +++ b/tests/switch-default.txt @@ -0,0 +1,9 @@ +:i argc 0 +:b stdin 0 + +:i returncode 0 +:b stdout 8 +Default + +:b stderr 0 + diff --git a/tests/switch.porth b/tests/switch.porth new file mode 100644 index 00000000..603c4398 --- /dev/null +++ b/tests/switch.porth @@ -0,0 +1,20 @@ +include "std.porth" + +2 switch + 1 case + "case 1\n" puts + break + 2 case + "case 2\n" puts + break + 3 case + "case 3\n" puts + break + 4 case + "case 4\n" puts + break + default + "Default\n" puts + break +end + diff --git a/tests/switch.txt b/tests/switch.txt new file mode 100644 index 00000000..6e91e9a2 --- /dev/null +++ b/tests/switch.txt @@ -0,0 +1,9 @@ +:i argc 0 +:b stdin 0 + +:i returncode 0 +:b stdout 7 +case 2 + +:b stderr 0 +