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
97 changes: 31 additions & 66 deletions devenv.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1760162706,
"lastModified": 1773192994,
"narHash": "sha256-4Ftfp6FbRtJT/IcjO0k0XKwpwF85mJmY0Uh4srGPq0I=",
"owner": "cachix",
"repo": "devenv",
"rev": "0d5ad578728fe4bce66eb4398b8b1e66deceb4e4",
"rev": "0a7c4902fe1c9d19d9228e36f36bc26daaa1e39c",
"type": "github"
},
"original": {
Expand All @@ -24,10 +25,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1760250892,
"lastModified": 1773126504,
"narHash": "sha256-/iXlg2V5UMlgCmyRHkPHjlD6NdMfFOnwFMvH7REigD4=",
"owner": "nix-community",
"repo": "fenix",
"rev": "b0b86e20829d1766bffb9f654d9fad47e099dc1b",
"rev": "64407ddb1932af06ed5cd711f6a2ed946b2548b9",
"type": "github"
},
"original": {
Expand All @@ -36,96 +38,58 @@
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"nixpkgs": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1759523803,
"lastModified": 1772749504,
"narHash": "sha256-eqtQIz0alxkQPym+Zh/33gdDjkkch9o6eHnMPnXFXN0=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "cfc9f7bb163ad8542029d303e599c0f7eee09835",
"repo": "devenv-nixpkgs",
"rev": "08543693199362c1fddb8f52126030d0d374ba2e",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs": {
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1758532697,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f",
"lastModified": 1772173633,
"narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"fenix": "fenix",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
],
"rust-overlay": "rust-overlay"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1760201021,
"lastModified": 1773053271,
"narHash": "sha256-5xc4Bk4/AgIOpf8NSNJxD+vNJ9KVyDailQ1EmfMmwjE=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "6fcd20b1acd355d2d253bd6747386ed8f629b4d0",
"rev": "16e4436162aae5dbfe5b5ebb9ed0ded2305cff25",
"type": "github"
},
"original": {
Expand All @@ -142,10 +106,11 @@
]
},
"locked": {
"lastModified": 1760236527,
"lastModified": 1773198218,
"narHash": "sha256-sxQV16GQrBEfrwuhYT9WvmFBnN8HakhRfR+JR+3qaTo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "a38dd7f462825c75ce8567816ae38c2e7d826bfa",
"rev": "e552b1d2850f5f0a739bba27c6463af1a29e2f4e",
"type": "github"
},
"original": {
Expand All @@ -157,4 +122,4 @@
},
"root": "root",
"version": 7
}
}
7 changes: 7 additions & 0 deletions examples/for_loop.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let numbers = [1, 2, 3, 4, 5];
let sum = 0;
for num in numbers {
let p = term.println;
p(num);
}
term.println("Done");
36 changes: 36 additions & 0 deletions examples/for_loops.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// examples/for_loops.lf
// Demonstration of for loops and nesting

fn main() {
let numbers = [1, 2, 3, 4, 5];

print("Iterating over numbers:");
for n in numbers {
print(n);
}

let nested = [[1, 2], [3, 4], [5, 6]];
print("Nested loops:");
for pair in nested {
for n in pair {
print(n);
}
}

// Testing the field access in loop body (parser fix)
def Person {
name: String,
age: Number
}

let people = [
Person { name: "Alice", age: 30 },
Person { name: "Bob", age: 25 }
];

print("Iterating over people (testing field access):");
for p in people {
print(p.name);
print(p.age);
}
}
83 changes: 60 additions & 23 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ impl<'a> Parser<'a> {
}

fn next(&mut self) -> Result<Option<Token>> {
if !self.tokens.buffer.is_empty() {
return Ok(Some(self.tokens.buffer.remove(0)));
}

match self.tokens.read_next_token() {
Some(Ok(token)) => Ok(Some(token)),
Some(Err(e)) => Err(e),
Expand Down Expand Up @@ -319,10 +323,23 @@ impl<'a> Parser<'a> {
self.next()?; // consume #
self.parse_attribute_statement()
}
Token::Keyword(k) if k == "let" => self.parse_var_decl(false),
Token::Keyword(k) if k == "let" => {
// Check for mut: let mut x = ...
self.next()?; // consume 'let'
if let Some(Token::Keyword(k)) = self.peek()? {
if k == "mut" {
self.next()?; // consume 'mut'
self.parse_var_decl_after_keyword(true)
} else {
self.parse_var_decl_after_keyword(false)
}
} else {
self.parse_var_decl_after_keyword(false)
}
}
Token::Keyword(k) if k == "mut" => {
self.next()?; // consume mut
self.parse_var_decl(true)
self.parse_var_decl_after_keyword(true)
}
Token::Keyword(k) if k == "const" => self.parse_const_decl(),
Token::Keyword(k) if k == "fn" => self.parse_function_decl(false, false),
Expand Down Expand Up @@ -374,29 +391,55 @@ impl<'a> Parser<'a> {
}
Token::Punct(p) if p == "{" => self.parse_block_statement(),
_ => {
// Try to detect assignment: identifier = expression
// We need to look ahead to distinguish assignment from expression
// Try to parse identifier-based expressions that might be part of an assignment
// or just a field access/method call.
if let Token::Ident(name) = &token {
// Peek ahead to see if next token is '='
// Save the current token for later if needed
let name_clone = name.clone();
self.next()?; // consume the identifier

// Start building the expression, handle field access and postfixes
let mut left = Expr::Ident(name_clone);
left = self.parse_postfix(left)?;

// Now check if the next token is an assignment operator
if let Some(Token::Op(op)) = self.peek()? {
if op == "=" {
// This is an assignment statement
self.next()?; // consume '='
let value = self.parse_expression()?;
self.maybe_consume_semicolon();
return Ok(Stmt::Assign {
name: name_clone,
value,
});
// This is an assignment to a field or variable
// For now, we only support assignment to a simple variable in Stmt::Assign
// If it's a field access, we might need a different Stmt variant
if let Expr::Ident(var_name) = left {
self.next()?; // consume '='
let value = self.parse_expression()?;
self.maybe_consume_semicolon();
return Ok(Stmt::Assign {
name: var_name,
value,
});
} else {
// It's a field access assignment or similar,
// which should probably be handled as an expression
// but if your language treats it as a statement:
self.next()?; // consume '='
let value = self.parse_expression()?;
self.maybe_consume_semicolon();
// We need to return something, if we don't have Stmt::AssignField
// we can just treat it as Expr(BinOp(left, "=", value)) if we want
// but let's stick to what we have or add more if needed.
// For now, let's treat it as an expression statement.
return Ok(Stmt::Expr(Expr::BinOp {
op: "=".to_string(),
left: Box::new(left),
right: Box::new(value),
}));
}
}
}

// Not an assignment, put the identifier back and parse as expression
self.tokens.push_back(Token::Ident(name_clone));
// Not an assignment, we already have the full postfix expression
// We just need to handle it as an expression statement
let expr = self.parse_binary_expr_with_left(left, 0)?;
self.maybe_consume_semicolon();
return Ok(Stmt::Expr(expr));
}

// Parse as expression (token will be consumed by parse_expression)
Expand Down Expand Up @@ -471,13 +514,7 @@ impl<'a> Parser<'a> {
})
}

fn parse_var_decl(&mut self, mutable: bool) -> Result<Stmt> {
// Expect 'let'
let keyword_token = self.next()?;
if !matches!(keyword_token, Some(Token::Keyword(ref k)) if k == "let") {
return Err(self.tokens.croak("Expected 'let'".to_string(), None));
}

fn parse_var_decl_after_keyword(&mut self, mutable: bool) -> Result<Stmt> {
let name_token = self.next()?;
let name = match name_token {
Some(Token::Ident(name)) => name,
Expand Down
26 changes: 24 additions & 2 deletions src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,8 +1004,30 @@ impl Interpreter {
// No pattern matched
Err(self.error("Match expression did not match any pattern".to_string()))
}
Stmt::For { .. } | Stmt::Break | Stmt::Continue => {
// For loops not yet fully implemented
Stmt::For { var, iterable, body } => {
let iterable_val = self.eval_expr(iterable)?;
match iterable_val {
Value::Array(items) => {
for item in items {
self.env.push_scope();
self.env.set(var.clone(), item);
self.eval_stmt(*body.clone())?;
self.env.pop_scope();
if self.returning.is_some() {
break;
}
}
Ok(Value::Unit)
}
_ => Err(self.error(format!("Value is not iterable: {:?}", iterable_val))),
}
}
Stmt::Break => {
// Return a special value to indicate a break
// But this might require more changes in all loop evaluations
Ok(Value::Unit)
}
Stmt::Continue => {
Ok(Value::Unit)
}
}
Expand Down
Loading
Loading