diff --git a/devenv.lock b/devenv.lock index f972da2..af30336 100644 --- a/devenv.lock +++ b/devenv.lock @@ -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": { @@ -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": { @@ -36,74 +38,39 @@ "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" } }, @@ -111,21 +78,18 @@ "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": { @@ -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": { @@ -157,4 +122,4 @@ }, "root": "root", "version": 7 -} +} \ No newline at end of file diff --git a/examples/for_loop.lf b/examples/for_loop.lf new file mode 100644 index 0000000..0012757 --- /dev/null +++ b/examples/for_loop.lf @@ -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"); diff --git a/examples/for_loops.lf b/examples/for_loops.lf new file mode 100644 index 0000000..81f1317 --- /dev/null +++ b/examples/for_loops.lf @@ -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); + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 292b799..16f1edb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -205,6 +205,10 @@ impl<'a> Parser<'a> { } fn next(&mut self) -> Result> { + 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), @@ -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), @@ -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) @@ -471,13 +514,7 @@ impl<'a> Parser<'a> { }) } - fn parse_var_decl(&mut self, mutable: bool) -> Result { - // 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 { let name_token = self.next()?; let name = match name_token { Some(Token::Ident(name)) => name, diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 8e7c5f5..3d493b6 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -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) } } diff --git a/www/package-lock.json b/www/package-lock.json index 1803f23..e287f5e 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -61,6 +61,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2173,6 +2174,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2269,6 +2271,7 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", + "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2342,8 +2345,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", @@ -4219,6 +4221,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -4380,6 +4383,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4407,6 +4411,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4438,6 +4443,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4457,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4650,6 +4657,7 @@ "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -5154,6 +5162,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0",