diff --git a/README.md b/README.md index 686c5f6..ec28791 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@
@@ -62,7 +62,6 @@ _This will not execute the code._ The following features are planned for future releases, but are not yet implemented: -- **Loops**: Support for loops. - **Lists**: Support for list data structures and related operations. - **Dictionaries**: Support for dictionary (key-value) data structures and related operations. - **Image Processing**: Capabilities for handling and processing images within Drib programs. diff --git a/examples/loop.drib b/examples/loop.drib new file mode 100644 index 0000000..d2788f7 --- /dev/null +++ b/examples/loop.drib @@ -0,0 +1,15 @@ +val i = "0"; + +loop { // this will be transpiled as "while True:" + when (eqs(i, "4")) { + i = add(i, "1"); + next; // this will be transpiled as "continue" + } + + println(i); + i = add(i, "1"); + + when (eqs(i, "10")) { + out; // this will be transpiled as “break" + } +} diff --git a/src/lexer.py b/src/lexer.py index cdcaf07..e460ee8 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -61,6 +61,9 @@ def lookup_ident(self, ident: str): "true": TokenType.TRUE, "false": TokenType.FALSE, "nil": TokenType.NIL, + "loop": TokenType.LOOP, + "next": TokenType.NEXT, + "out": TokenType.OUT, } return keywords.get(ident, TokenType.IDENTIFIER) diff --git a/src/parser.py b/src/parser.py index 553a3f9..221c03e 100644 --- a/src/parser.py +++ b/src/parser.py @@ -43,6 +43,12 @@ def parse_program(self): lines.append(self.parse_function()) elif tok.token_type == TokenType.WHEN: lines.append(self.parse_when_statement()) + elif tok.token_type == TokenType.LOOP: + lines.append(self.parse_loop_statement()) + elif tok.token_type == TokenType.NEXT: + lines.append(self.parse_next_statement()) + elif tok.token_type == TokenType.OUT: + lines.append(self.parse_out_statement()) else: lines.append(self.parse_expression_statement()) return "\n".join(lines) @@ -102,6 +108,12 @@ def parse_function(self): body_lines.append(self.parse_when_statement()) elif tok.token_type == TokenType.VAL: body_lines.append(self.parse_val_statement()) + elif tok.token_type == TokenType.LOOP: + body_lines.append(self.parse_loop_statement()) + elif tok.token_type == TokenType.NEXT: + body_lines.append(self.parse_next_statement()) + elif tok.token_type == TokenType.OUT: + body_lines.append(self.parse_out_statement()) else: body_lines.append(self.parse_expression_statement()) @@ -194,6 +206,12 @@ def parse_when_statement(self): body_lines.append(self.parse_val_statement()) elif tok.token_type == TokenType.RETURN: body_lines.append(self.parse_return_statement()) + elif tok.token_type == TokenType.LOOP: + body_lines.append(self.parse_loop_statement()) + elif tok.token_type == TokenType.NEXT: + body_lines.append(self.parse_next_statement()) + elif tok.token_type == TokenType.OUT: + body_lines.append(self.parse_out_statement()) else: expr = self.parse_expression_statement() body_lines.append(expr) @@ -203,18 +221,80 @@ def parse_when_statement(self): else_lines = [] if self.current_token().token_type == TokenType.OTHERWISE: - self.next_token() - self.next_token() + self.next_token() # skip 'otherwise' + self._expect(TokenType.LBRACE) # expect '{' + indent_else = " " * self.indent_level + else_lines.append(f"{indent_else}else:") self.indent_level += 1 - else_lines.append(" " * (self.indent_level - 1) + "else:") + # now inside else block while self.current_token().token_type != TokenType.RBRACE: tok = self.current_token() if tok.token_type == TokenType.WHEN: else_lines.append(self.parse_when_statement()) + elif tok.token_type == TokenType.VAL: + else_lines.append(self.parse_val_statement()) + elif tok.token_type == TokenType.RETURN: + else_lines.append(self.parse_return_statement()) + elif tok.token_type == TokenType.LOOP: + else_lines.append(self.parse_loop_statement()) + elif tok.token_type == TokenType.NEXT: + else_lines.append(self.parse_next_statement()) + elif tok.token_type == TokenType.OUT: + else_lines.append(self.parse_out_statement()) else: expr = self.parse_expression_statement() else_lines.append(expr) self.indent_level -= 1 - self.next_token() + self.next_token() # skip '}' return "\n".join([header] + body_lines + else_lines) + + def parse_loop_statement(self): + # current token is 'loop' + self.next_token() # move to '{' + if self.current_token().token_type != TokenType.LBRACE: + raise Exception("Syntax error: expected '{' after 'loop'") + self.next_token() # skip '{' + + indent = " " * self.indent_level + header = f"{indent}while True:" + self.indent_level += 1 + + body_lines = [] + while self.current_token().token_type != TokenType.RBRACE: + tok = self.current_token() + if tok.token_type == TokenType.WHEN: + body_lines.append(self.parse_when_statement()) + elif tok.token_type == TokenType.VAL: + body_lines.append(self.parse_val_statement()) + elif tok.token_type == TokenType.RETURN: + body_lines.append(self.parse_return_statement()) + elif tok.token_type == TokenType.LOOP: + body_lines.append(self.parse_loop_statement()) + elif tok.token_type == TokenType.NEXT: + body_lines.append(self.parse_next_statement()) + elif tok.token_type == TokenType.OUT: + body_lines.append(self.parse_out_statement()) + else: + body_lines.append(self.parse_expression_statement()) + + self.indent_level -= 1 + self.next_token() # skip '}' + + return "\n".join([header] + body_lines) + + def parse_next_statement(self): + # 'next;' -> 'continue' + self.next_token() # move past 'next' + if self.current_token().token_type == TokenType.SEMICOLON: + self.next_token() + indent = " " * self.indent_level + return f"{indent}continue" + + def parse_out_statement(self): + # 'out;' -> 'break' + self.next_token() # move past 'out' + if self.current_token().token_type == TokenType.SEMICOLON: + self.next_token() + indent = " " * self.indent_level + return f"{indent}break" diff --git a/src/token_.py b/src/token_.py index 0771e0b..e33c19f 100644 --- a/src/token_.py +++ b/src/token_.py @@ -25,6 +25,11 @@ class TokenType: STRING = "STRING" NIL = "NIL" + # loop-related + LOOP = "LOOP" + NEXT = "NEXT" + OUT = "OUT" + class Token: def __init__(self, token_type: str, literal: str): @@ -32,7 +37,7 @@ def __init__(self, token_type: str, literal: str): self.literal = literal def __repr__(self): - return f'{{"token_type": "{self.token_type}", "literal": "{self.literal}"}}' + return f'{"token_type": "{self.token_type}", "literal": "{self.literal}"}' def __str__(self) -> str: return self.__repr__() diff --git a/test/test_lexer.py b/test/test_lexer.py index 1d359f0..80cf9d2 100644 --- a/test/test_lexer.py +++ b/test/test_lexer.py @@ -70,3 +70,31 @@ def test_function_declaration(): assert token_type == e["token_type"] assert token_literal == e["literal"] + + +def test_loop_next_out_tokens(): + c = """ + loop { + next; + out; + } + """ + l = Lexer(c) + toks = l.tokenize() + expected = [ + {"token_type": "LOOP", "literal": "loop"}, + {"token_type": "{", "literal": "{"}, + {"token_type": "NEXT", "literal": "next"}, + {"token_type": ";", "literal": ";"}, + {"token_type": "OUT", "literal": "out"}, + {"token_type": ";", "literal": ";"}, + {"token_type": "}", "literal": "}"}, + {"token_type": "EOF", "literal": ""}, + ] + + for i, e in enumerate(expected): + token_type = toks[i].token_type + token_literal = toks[i].literal + + assert token_type == e["token_type"] + assert token_literal == e["literal"] diff --git a/test/test_parser.py b/test/test_parser.py index b334915..a5c2c5f 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -47,3 +47,21 @@ def test_function_declaration(): py_code = p.parse_program() expected = "def my_function(a, b):\n" + " return add(a,b)" assert py_code == expected + + +def test_loop_with_next_and_out(): + # loop { next; out; } + tokens = [ + Token(TokenType.LOOP, "loop"), + Token(TokenType.LBRACE, "{"), + Token(TokenType.NEXT, "next"), + Token(TokenType.SEMICOLON, ";"), + Token(TokenType.OUT, "out"), + Token(TokenType.SEMICOLON, ";"), + Token(TokenType.RBRACE, "}"), + Token(TokenType.EOF, ""), + ] + p = Parser(tokens) + py_code = p.parse_program() + expected = "while True:\n" + " continue\n" + " break" + assert py_code == expected