diff --git a/examples/playground.sb b/examples/playground.sb index 523052e..08800c1 100644 --- a/examples/playground.sb +++ b/examples/playground.sb @@ -1,6 +1,8 @@ fn main() { - let x: string + let x = ["Foo", "Bar"] - x = "Hello World" - _printf(x) + for i in x { + _printf(x[i]) + _printf("\n") + } } \ No newline at end of file diff --git a/src/generator/c.rs b/src/generator/c.rs index e5417d2..e3b2e85 100644 --- a/src/generator/c.rs +++ b/src/generator/c.rs @@ -114,6 +114,7 @@ fn generate_statement(statement: Statement) -> String { Statement::Assign(name, state) => generate_assign(*name, *state), Statement::Block(_) => generate_block(statement), Statement::While(expr, body) => generate_while_loop(expr, *body), + Statement::For(ident, expr, body) => todo!(), }; format!("{}\n", state) diff --git a/src/generator/js.rs b/src/generator/js.rs index 5fead96..8e44f7d 100644 --- a/src/generator/js.rs +++ b/src/generator/js.rs @@ -49,14 +49,19 @@ fn generate_function(func: Function) -> String { .join(", "); let mut raw = format!("function {N}({A})", N = func.name, A = arguments); - raw += &generate_block(func.body); + raw += &generate_block(func.body, None); raw } -fn generate_block(block: Statement) -> String { +/// prepend is used to pass optional statements, that will be put in front of the regular block +/// Currently used in for statements, to declare local variables +fn generate_block(block: Statement, prepend: Option) -> String { let mut generated = String::from("{\n"); + + // TODO: Prepend statements + let statements = match block { Statement::Block(blk) => blk, _ => panic!("Block body should be of type Statement::Block"), @@ -80,8 +85,9 @@ fn generate_statement(statement: Statement) -> String { generate_conditional(expr, *if_state, else_state.map(|x| *x)) } Statement::Assign(name, state) => generate_assign(*name, *state), - Statement::Block(_) => generate_block(statement), + Statement::Block(_) => generate_block(statement, None), Statement::While(expr, body) => generate_while_loop(expr, *body), + Statement::For(ident, expr, body) => generate_for_loop(ident, expr, *body), }; format!("{};\n", state) @@ -104,7 +110,17 @@ fn generate_while_loop(expr: Expression, body: Statement) -> String { out_str += &generate_expression(expr); out_str += ") "; - out_str += &generate_block(body); + out_str += &generate_block(body, None); + out_str +} + +fn generate_for_loop(ident: Variable, expr: Expression, body: Statement) -> String { + let expr_string = generate_expression(expr); + let mut out_str = format!("for (let {I} = 0; {I} < {E}.length; {I}++)", I = ident.name, E = expr_string); + + // Currently, the index of the current iteration is used, instead of the value itself. + // TODO: generate local variable + out_str += &generate_block(body, None); out_str } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 5a65d5e..f280650 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -127,6 +127,8 @@ pub enum Keyword { Else, Return, While, + For, + In, Break, Continue, Function, @@ -331,6 +333,8 @@ impl Cursor<'_> { c if c == "let" => Keyword::Let, c if c == "return" => Keyword::Return, c if c == "while" => Keyword::While, + c if c == "for" => Keyword::For, + c if c == "in" => Keyword::In, _ => Keyword::Unknown, } } diff --git a/src/parser/node_type.rs b/src/parser/node_type.rs index ac0855e..c90bce4 100644 --- a/src/parser/node_type.rs +++ b/src/parser/node_type.rs @@ -71,6 +71,7 @@ pub enum Statement { Return(Option), If(Expression, Box, Option>), While(Expression, Box), + For(Variable, Expression, Box), Exp(Expression), } diff --git a/src/parser/rules.rs b/src/parser/rules.rs index bd7ec6a..c85e2de 100644 --- a/src/parser/rules.rs +++ b/src/parser/rules.rs @@ -122,6 +122,7 @@ impl Parser { TokenKind::Keyword(Keyword::Return) => self.parse_return(), TokenKind::Keyword(Keyword::If) => self.parse_conditional_statement(), TokenKind::Keyword(Keyword::While) => self.parse_while_loop(), + TokenKind::Keyword(Keyword::For) => self.parse_for_loop(), TokenKind::Identifier(_) => { let ident = self.match_identifier()?; @@ -299,6 +300,17 @@ impl Parser { Ok(Statement::While(expr, Box::new(body))) } + fn parse_for_loop(&mut self) -> Result { + self.match_keyword(Keyword::For)?; + + let ident = self.match_identifier()?; + self.match_keyword(Keyword::In)?; + let expr = self.parse_expression()?; + let body = self.parse_block()?; + + Ok(Statement::For(Variable {name: ident, ty: None}, expr, Box::new(body))) + } + fn parse_conditional_statement(&mut self) -> Result { self.match_keyword(Keyword::If)?; let condition = self.parse_expression()?; diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 6a6c40a..b6e4241 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -641,3 +641,37 @@ fn test_late_initializing_variable() { let tree = parse(tokens, Some(raw.to_string())); assert!(tree.is_ok()); } + +#[test] +fn test_simple_for_loop() { + let raw = " + fn main() { + let x = [1, 2, 3] + + for i in x { + _printf(i) + } + } + "; + let tokens = tokenize(raw); + let tree = parse(tokens, Some(raw.to_string())); + assert!(tree.is_ok()); +} + +#[test] +fn test_nested_for_loop() { + let raw = " + fn main() { + let x = [1, 2, 3] + + for i in x { + for j in x { + _printf(j) + } + } + } + "; + let tokens = tokenize(raw); + let tree = parse(tokens, Some(raw.to_string())); + assert!(tree.is_ok()); +} \ No newline at end of file