Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<p align="center">
<a href="https://github.com/drib-lang/adi/actions/workflows/ci.yml"><img alt="Code style: black" src="https://github.com/drib-lang/adi/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<img alt="Drib: 0.2.0" src="https://img.shields.io/badge/drib-0.2.0-5594EC.svg">
<img alt="Drib: 0.3.0" src="https://img.shields.io/badge/drib-0.3.0-5594EC.svg">
<img alt="Python: 3.11" src="https://img.shields.io/badge/python-3.11-3572A5.svg">
</p>

Expand Down Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions examples/loop.drib
Original file line number Diff line number Diff line change
@@ -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"
}
}
3 changes: 3 additions & 0 deletions src/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
88 changes: 84 additions & 4 deletions src/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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)
Expand All @@ -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"
7 changes: 6 additions & 1 deletion src/token_.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@ class TokenType:
STRING = "STRING"
NIL = "NIL"

# loop-related
LOOP = "LOOP"
NEXT = "NEXT"
OUT = "OUT"


class Token:
def __init__(self, token_type: str, literal: str):
self.token_type = token_type
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__()
28 changes: 28 additions & 0 deletions test/test_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
18 changes: 18 additions & 0 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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