From 10540708d498bfef7397a0964c17ff87d5bdc456 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sat, 28 Jan 2023 01:11:55 +0100 Subject: [PATCH] finished resolver (chapter 11) and started classes (chapter 12) --- Cargo.lock | 312 ++++++++++++++++++ Cargo.toml | 16 +- src/parser/GRAMMAR => GRAMMAR | 10 +- frontend/Cargo.toml | 11 + {src => frontend/src}/lexer/_lexer.rs | 7 +- src/misc.rs => frontend/src/lexer/code_pos.rs | 15 +- frontend/src/lexer/error.rs | 19 ++ {src => frontend/src}/lexer/mod.rs | 4 + {src => frontend/src}/lexer/token.rs | 2 +- frontend/src/lib.rs | 2 + frontend/src/parser/error.rs | 45 +++ {src => frontend/src}/parser/expr.rs | 43 ++- frontend/src/parser/misc.rs | 5 + {src => frontend/src}/parser/mod.rs | 7 +- .../src/parser/parse.rs | 90 ++++- {src => frontend/src}/parser/stmt.rs | 22 +- interpreter/Cargo.toml | 13 + interpreter/src/class.rs | 19 ++ interpreter/src/environment.rs | 209 ++++++++++++ interpreter/src/error.rs | 85 +++++ .../src}/function.rs | 12 +- .../src}/interpret.rs | 94 +++--- interpreter/src/lib.rs | 18 + .../src}/lox_std.rs | 28 +- interpreter/src/resolver/error.rs | 11 + interpreter/src/resolver/mod.rs | 5 + interpreter/src/resolver/resolve.rs | 276 ++++++++++++++++ {src/interpreter => interpreter/src}/run.rs | 73 ++-- interpreter/src/runtime.rs | 101 ++++++ {src/interpreter => interpreter/src}/value.rs | 19 +- src/error.rs | 125 ------- src/interpreter/environment.rs | 146 -------- src/interpreter/mod.rs | 10 - src/main.rs | 34 +- 34 files changed, 1449 insertions(+), 439 deletions(-) rename src/parser/GRAMMAR => GRAMMAR (81%) create mode 100644 frontend/Cargo.toml rename {src => frontend/src}/lexer/_lexer.rs (98%) rename src/misc.rs => frontend/src/lexer/code_pos.rs (55%) create mode 100644 frontend/src/lexer/error.rs rename {src => frontend/src}/lexer/mod.rs (52%) rename {src => frontend/src}/lexer/token.rs (98%) create mode 100644 frontend/src/lib.rs create mode 100644 frontend/src/parser/error.rs rename {src => frontend/src}/parser/expr.rs (81%) create mode 100644 frontend/src/parser/misc.rs rename {src => frontend/src}/parser/mod.rs (53%) rename src/parser/_parser.rs => frontend/src/parser/parse.rs (87%) rename {src => frontend/src}/parser/stmt.rs (84%) create mode 100644 interpreter/Cargo.toml create mode 100644 interpreter/src/class.rs create mode 100644 interpreter/src/environment.rs create mode 100644 interpreter/src/error.rs rename {src/interpreter => interpreter/src}/function.rs (89%) rename {src/interpreter => interpreter/src}/interpret.rs (76%) create mode 100644 interpreter/src/lib.rs rename {src/interpreter => interpreter/src}/lox_std.rs (82%) create mode 100644 interpreter/src/resolver/error.rs create mode 100644 interpreter/src/resolver/mod.rs create mode 100644 interpreter/src/resolver/resolve.rs rename {src/interpreter => interpreter/src}/run.rs (66%) create mode 100644 interpreter/src/runtime.rs rename {src/interpreter => interpreter/src}/value.rs (88%) delete mode 100644 src/error.rs delete mode 100644 src/interpreter/environment.rs delete mode 100644 src/interpreter/mod.rs diff --git a/Cargo.lock b/Cargo.lock index dc62d3b..3e686db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,152 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[package]] +name = "clap" +version = "4.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "phf" version = "0.11.1" @@ -44,6 +190,30 @@ dependencies = [ "siphasher", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.50" @@ -81,16 +251,55 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" name = "rlox2" version = "0.1.0" dependencies = [ + "clap", + "rlox2-frontend", + "rlox2-interpreter", +] + +[[package]] +name = "rlox2-frontend" +version = "0.1.0" +dependencies = [ + "itertools", "phf", "thiserror", ] +[[package]] +name = "rlox2-interpreter" +version = "0.1.0" +dependencies = [ + "itertools", + "rlox2-frontend", + "thiserror", +] + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.107" @@ -102,6 +311,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.38" @@ -127,3 +345,97 @@ name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index c8df177..d051e43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,18 @@ name = "rlox2" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "rlox" +path = "src/main.rs" + +[workspace] +members = ["frontend", "interpreter"] + +[dependencies.rlox2-frontend] +path = "frontend" + +[dependencies.rlox2-interpreter] +path = "interpreter" [dependencies] -phf = { version = "0.11.1", features = ["macros"] } -thiserror = "1.0" +clap = { version = "4.1.4", features = ["derive"] } diff --git a/src/parser/GRAMMAR b/GRAMMAR similarity index 81% rename from src/parser/GRAMMAR rename to GRAMMAR index 118ae5c..7bb225c 100644 --- a/src/parser/GRAMMAR +++ b/GRAMMAR @@ -14,14 +14,18 @@ if_stmt -> "if" "(" expression ")" statement ( "else" statement )? ; print_stmt -> "print" expression ";" ; while_stmt -> "while" "(" expression ")" statement ; for_stmt -> "for" "(" (declaration | expr_stmt | ";") ";" expression? ";" expression ";" ")" statement ; -declaration -> var_decl | fun_decl ; block -> "{" statement* "}" ; expr_Stmt -> expression ";" ; break -> "break" ";" ; return -> "return" expression? ";" ; +declaration -> var_decl | fun_decl | class_decl ; + var_decl -> "var" IDENTIFIER ( "=" expression )? ";" fun_decl -> "fun" IDENTIFIER "(" parameters ")" block ; +class_decl -> "class" IDENTIFIER "{" method* "}" ; + +method -> IDENTIFIER "(" parameters ")" block ; expression -> assignment @@ -37,8 +41,8 @@ unary -> ( "!" | "-" ) unary | call ; call -> primary ( "(" arguments? ")" )* ; arguments -> expression ( "," expression )* ; -primary -> "(" expression ")" | IDENTIFIER | fun_expr | NUMBER | STRING | "true" | "false" | "nil" ; +primary -> "(" expression ")" | IDENTIFIER | lambda | NUMBER | STRING | "true" | "false" | "nil" ; -fun_expr -> "fun" "(" parameters ")" block ; +lambda -> "fun" "(" parameters ")" block ; parameters -> ( IDENTIFIER ( "," IDENTIFIER )* )? ; \ No newline at end of file diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml new file mode 100644 index 0000000..a8f6fdd --- /dev/null +++ b/frontend/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rlox2-frontend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +phf = { version = "0.11.1", features = ["macros"] } +thiserror = "1.0.38" +itertools = "0.10.5" diff --git a/src/lexer/_lexer.rs b/frontend/src/lexer/_lexer.rs similarity index 98% rename from src/lexer/_lexer.rs rename to frontend/src/lexer/_lexer.rs index c525b7f..8c03a5e 100644 --- a/src/lexer/_lexer.rs +++ b/frontend/src/lexer/_lexer.rs @@ -1,9 +1,6 @@ use phf::phf_map; -use crate::error::LexerError; -use crate::misc::CodePos; - -use super::{Token, TokenType}; +use super::{CodePos, LexerError, Token, TokenType}; /*====================================================================================================================*/ @@ -289,7 +286,7 @@ impl Lexer { Err(err) => { self.errors.push(LexerError::InvalidNumberLiteral { lexeme, - msg: format!("{err}"), + msg: err.to_string(), code_pos: self.code_pos, }); return None; diff --git a/src/misc.rs b/frontend/src/lexer/code_pos.rs similarity index 55% rename from src/misc.rs rename to frontend/src/lexer/code_pos.rs index 2b55450..c67217a 100644 --- a/src/misc.rs +++ b/frontend/src/lexer/code_pos.rs @@ -1,5 +1,3 @@ -use std::fmt::{Debug, Display}; - #[derive(Copy, Clone)] pub struct CodePos { pub line: u32, @@ -12,23 +10,14 @@ impl Default for CodePos { } } -impl Display for CodePos { +impl std::fmt::Display for CodePos { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "line {}, col {}", self.line, self.col) } } -impl Debug for CodePos { +impl std::fmt::Debug for CodePos { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.line, self.col) } } - -/*====================================================================================================================*/ - -pub fn indent(s: String) -> String { - s.split('\n') - .map(|line| format!("\t{line}")) - .collect::>() - .join("\n") -} diff --git a/frontend/src/lexer/error.rs b/frontend/src/lexer/error.rs new file mode 100644 index 0000000..39279bd --- /dev/null +++ b/frontend/src/lexer/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +use super::CodePos; + +#[derive(Error, Debug)] +pub enum LexerError { + #[error("Unexpected character '{c}' at {code_pos}.")] + UnexpectedCharacter { c: char, code_pos: CodePos }, + #[error("Unterminated string literal starting at {code_pos}.")] + UnterminatedStringLiteral { code_pos: CodePos }, + #[error("Unterminated block comment starting at {code_pos}.")] + UnterminatedBlockComment { code_pos: CodePos }, + #[error("Invalid number literal {lexeme} at {code_pos}: {msg}")] + InvalidNumberLiteral { + lexeme: String, + msg: String, + code_pos: CodePos, + }, +} diff --git a/src/lexer/mod.rs b/frontend/src/lexer/mod.rs similarity index 52% rename from src/lexer/mod.rs rename to frontend/src/lexer/mod.rs index d851fd6..5d7f3a2 100644 --- a/src/lexer/mod.rs +++ b/frontend/src/lexer/mod.rs @@ -1,5 +1,9 @@ mod _lexer; +mod code_pos; +mod error; mod token; pub use _lexer::scan_tokens; +pub use code_pos::CodePos; +pub use error::LexerError; pub use token::{Token, TokenType}; diff --git a/src/lexer/token.rs b/frontend/src/lexer/token.rs similarity index 98% rename from src/lexer/token.rs rename to frontend/src/lexer/token.rs index 1da1471..6956e07 100644 --- a/src/lexer/token.rs +++ b/frontend/src/lexer/token.rs @@ -1,4 +1,4 @@ -use crate::misc::CodePos; +use super::CodePos; #[allow(dead_code, clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq)] diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs new file mode 100644 index 0000000..6054fb1 --- /dev/null +++ b/frontend/src/lib.rs @@ -0,0 +1,2 @@ +pub mod lexer; +pub mod parser; diff --git a/frontend/src/parser/error.rs b/frontend/src/parser/error.rs new file mode 100644 index 0000000..a28c982 --- /dev/null +++ b/frontend/src/parser/error.rs @@ -0,0 +1,45 @@ +use thiserror::Error; + +use crate::lexer::{CodePos, Token}; + +use super::Expr; + +#[derive(Error, Debug)] +pub enum ParserError { + #[error("Token stream ended unexpectedly.")] + TokenStreamEnded, + #[error("Expected a primary expression, but found a {token} token instead at {0}.", token.code_pos)] + ExpectedPrimary { token: Token }, + #[error("Missing semicolon at {code_pos}")] + MissingSemicolon { code_pos: CodePos }, + #[error("Expected variable name at {0}, got {token} instead", token.code_pos)] + ExpectedVarName { token: Token }, + #[error("Can't assign to {expr} at {code_pos}")] + InvalidAssignment { expr: Expr, code_pos: CodePos }, + #[error("Missing closing curly brace at {code_pos}")] + MissingRightBrace { code_pos: CodePos }, + #[error("Missing closing parenthesis at {code_pos}")] + MissingRightParen { code_pos: CodePos }, + #[error("Missing parenthesis after if at {code_pos}")] + MissingParenAfterIf { code_pos: CodePos }, + #[error("Missing parenthesis after while at {code_pos}")] + MissingParenAfterWhile { code_pos: CodePos }, + #[error("Missing parenthesis after for at {code_pos}")] + MissingParenAfterFor { code_pos: CodePos }, + #[error("Call at {code_pos} has too many arguments")] + TooManyArguments { code_pos: CodePos }, + #[error("{msg} at {code_pos}")] + MissingIdentifier { msg: String, code_pos: CodePos }, + #[error("Missing arguments to function declaration at {code_pos}")] + MissingFunctionArgs { code_pos: CodePos }, + #[error("Missing body to function declaration at {code_pos}")] + MissingFunctionBody { code_pos: CodePos }, + #[error("Function declaration at {code_pos} has too many parameters")] + TooManyParams { code_pos: CodePos }, + #[error("Return statement outside of function definition")] + InvalidReturn { code_pos: CodePos }, + #[error("Break statement outside of loop")] + InvalidBreak { code_pos: CodePos }, + #[error("Missing class body at {code_pos}")] + MissingClassBody { code_pos: CodePos }, +} diff --git a/src/parser/expr.rs b/frontend/src/parser/expr.rs similarity index 81% rename from src/parser/expr.rs rename to frontend/src/parser/expr.rs index 7bb8491..3159b32 100644 --- a/src/parser/expr.rs +++ b/frontend/src/parser/expr.rs @@ -1,6 +1,8 @@ use std::fmt::Display; use std::rc::Rc; +use itertools::Itertools; + use super::Stmt; #[derive(Debug, Clone)] @@ -28,8 +30,15 @@ pub enum Expr { Variable { name: String, }, - Assignment { + LocalVariable { name: String, + level: usize, + }, + GlobalVariable { + name: String, + }, + Assignment { + target: Box, value: Box, }, Call { @@ -39,6 +48,7 @@ pub enum Expr { Function { name: String, param_names: Vec, + closure_vars: Vec<(String, usize)>, body: Box, }, } @@ -89,10 +99,10 @@ impl Expr { Expr::Grouping { expr } } - pub fn assignment(name: impl Into, value: Expr) -> Self { - let name = name.into(); + pub fn assignment(target: Expr, value: Expr) -> Self { + let target = Box::new(target); let value = Box::new(value); - Expr::Assignment { name, value } + Expr::Assignment { target, value } } pub fn call(callee: Expr, args: Vec) -> Self { @@ -105,6 +115,7 @@ impl Expr { Self::Function { name, param_names, + closure_vars: Vec::new(), body, } } @@ -120,22 +131,24 @@ impl Display for Expr { write!(f, "({op} {left} {right})") } Expr::Grouping { expr } => write!(f, "(group {expr})"), - Expr::Variable { name } => write!(f, "{name}"), - Expr::Assignment { name, value } => write!(f, "{name} = {value}"), - Expr::Call { callee, args } => write!( - f, - "({callee} {})", - args.iter() - .map(|arg| format!("{arg}")) - .collect::>() - .join(" ") - ), + Expr::Variable { name } => write!(f, "(var {name})"), + Expr::LocalVariable { name, level } => write!(f, "(var {name} local({level}))"), + Expr::GlobalVariable { name } => write!(f, "(var {name} global)"), + Expr::Assignment { target, value } => write!(f, "{target} = {value}"), + Expr::Call { callee, args } => write!(f, "({callee} {})", args.iter().map(|arg| arg.to_string()).join(" ")), Expr::Function { name, param_names, + closure_vars, body, } => { - write!(f, "fun {name}({}) {body}", param_names.join(", ")) + if !closure_vars.is_empty() { + let closure_fmt = closure_vars.iter().map(|(name, _level)| name).join(", "); + + write!(f, "fun [{closure_fmt}] {name}({}) {body}", param_names.join(", ")) + } else { + write!(f, "fun {name}({}) {body}", param_names.join(", ")) + } } } } diff --git a/frontend/src/parser/misc.rs b/frontend/src/parser/misc.rs new file mode 100644 index 0000000..1fd6fff --- /dev/null +++ b/frontend/src/parser/misc.rs @@ -0,0 +1,5 @@ +use itertools::Itertools; + +pub fn indent(s: String) -> String { + s.split('\n').map(|line| format!("\t{line}")).join("\n") +} diff --git a/src/parser/mod.rs b/frontend/src/parser/mod.rs similarity index 53% rename from src/parser/mod.rs rename to frontend/src/parser/mod.rs index 6bb49e0..4a93b66 100644 --- a/src/parser/mod.rs +++ b/frontend/src/parser/mod.rs @@ -1,7 +1,10 @@ -mod _parser; +mod error; mod expr; +mod misc; +mod parse; mod stmt; -pub use _parser::parse_tokens; +pub use error::ParserError; pub use expr::{BinaryOp, Expr, Literal, LogicalOp, UnaryOp}; +pub use parse::parse_tokens; pub use stmt::Stmt; diff --git a/src/parser/_parser.rs b/frontend/src/parser/parse.rs similarity index 87% rename from src/parser/_parser.rs rename to frontend/src/parser/parse.rs index 60f2457..2f0861d 100644 --- a/src/parser/_parser.rs +++ b/frontend/src/parser/parse.rs @@ -1,11 +1,10 @@ use std::vec::IntoIter; -use crate::error::ParserError; use crate::lexer::{Token, TokenType}; use crate::parser::expr::BinaryOp; use super::expr::{Expr, UnaryOp}; -use super::{LogicalOp, Stmt}; +use super::{LogicalOp, ParserError, Stmt}; /*====================================================================================================================*/ @@ -62,6 +61,9 @@ struct Parser { token_iter: TokenIter, parse_errors: Vec, + + is_in_function: bool, + is_in_loop: bool, } impl Parser { @@ -69,6 +71,8 @@ impl Parser { Parser { token_iter: TokenIter::new(tokens), parse_errors: Vec::new(), + is_in_function: false, + is_in_loop: false, } } @@ -142,20 +146,29 @@ impl Parser { TokenType::While => self.while_statement(), TokenType::For => self.for_statement(), TokenType::Var => self.var_declaration(), + TokenType::Class => self.class_declaration(), TokenType::Fun => self.fun_declaration(), TokenType::LeftBrace => self.block(), TokenType::Break => { + let code_pos = self.peek_token().code_pos; assert_eq!(self.next_token().token_type, TokenType::Break); self.semicolon()?; + if !self.is_in_loop { + return Err(ParserError::InvalidBreak { code_pos }); + } Ok(Stmt::Break) } TokenType::Return => { + let code_pos = self.peek_token().code_pos; assert_eq!(self.next_token().token_type, TokenType::Return); let expr = match self.peek_token().token_type { TokenType::Semicolon => Expr::nil(), _ => self.expression()?, }; self.semicolon()?; + if !self.is_in_function { + return Err(ParserError::InvalidReturn { code_pos }); + } Ok(Stmt::return_stmt(expr)) } _ => self.expression_statement(), @@ -202,7 +215,17 @@ impl Parser { code_pos: token.code_pos, })?; - let body = self.statement()?; + let is_in_loop = std::mem::replace(&mut self.is_in_loop, true); + + let body = match self.statement() { + Ok(body) => body, + Err(err) => { + self.is_in_loop = is_in_loop; + return Err(err); + } + }; + + self.is_in_loop = is_in_loop; Ok(Stmt::while_stmt(condition, body)) } @@ -239,7 +262,17 @@ impl Parser { code_pos: token.code_pos, })?; - let mut body = self.statement()?; + let is_in_loop = std::mem::replace(&mut self.is_in_loop, true); + + let mut body = match self.statement() { + Ok(body) => body, + Err(err) => { + self.is_in_loop = is_in_loop; + return Err(err); + } + }; + + self.is_in_loop = is_in_loop; if let Some(increment) = increment { body = Stmt::Block { @@ -295,6 +328,38 @@ impl Parser { Ok(Stmt::var_decl(name, initializer)) } + fn class_declaration(&mut self) -> ParserResult { + assert_eq!(self.next_token().token_type, TokenType::Class); + + let name = self.identifier("Missing class name")?; + + self.consume_token(TokenType::LeftBrace, |token| ParserError::MissingClassBody { + code_pos: token.code_pos, + })?; + + let mut methods = Vec::new(); + + while self.peek_token().token_type != TokenType::RightBrace { + let method_name = self.identifier("Expected method name").map_err(|err| { + if self.peek_token().token_type == TokenType::EOF { + ParserError::MissingRightBrace { + code_pos: self.peek_token().code_pos, + } + } else { + err + } + })?; + + let method = self.fun_params_and_body(method_name)?; + + methods.push(method); + } + + assert_eq!(self.next_token().token_type, TokenType::RightBrace); + + Ok(Stmt::Class { name, methods }) + } + fn fun_declaration(&mut self) -> ParserResult { assert_eq!(self.next_token().token_type, TokenType::Fun); @@ -331,7 +396,20 @@ impl Parser { }); } - let body = self.block()?; + let is_in_function = std::mem::replace(&mut self.is_in_function, true); + let is_in_loop = std::mem::replace(&mut self.is_in_loop, false); + + let body = match self.block() { + Ok(body) => body, + Err(err) => { + self.is_in_function = is_in_function; + self.is_in_loop = is_in_loop; + return Err(err); + } + }; + + self.is_in_function = is_in_function; + self.is_in_loop = is_in_loop; let name = name.into(); Ok(Expr::function(name, param_names, body)) @@ -415,7 +493,7 @@ impl Parser { let value = self.assignment()?; match expr { - Expr::Variable { name } => Ok(Expr::assignment(name, value)), + Expr::Variable { name } => Ok(Expr::assignment(Expr::Variable { name }, value)), _ => Err(ParserError::InvalidAssignment { expr, code_pos }), } } diff --git a/src/parser/stmt.rs b/frontend/src/parser/stmt.rs similarity index 84% rename from src/parser/stmt.rs rename to frontend/src/parser/stmt.rs index 8781f68..bd90e79 100644 --- a/src/parser/stmt.rs +++ b/frontend/src/parser/stmt.rs @@ -1,7 +1,6 @@ use std::fmt::Display; -use crate::misc::indent; - +use super::misc::indent; use super::Expr; #[derive(Debug, Clone)] @@ -25,6 +24,10 @@ pub enum Stmt { Block { statements: Vec, }, + Class { + name: String, + methods: Vec, + }, ExprStmt { expr: Box, }, @@ -92,13 +95,13 @@ impl Display for Stmt { writeln!(f, "if {condition}")?; match then_branch.as_ref() { Stmt::Block { .. } => write!(f, "{then_branch}")?, - _ => write!(f, "{}", indent(format!("{then_branch}")))?, + _ => write!(f, "{}", indent(then_branch.to_string()))?, } if let Some(else_branch) = else_branch { writeln!(f, "\nelse")?; match else_branch.as_ref() { Stmt::Block { .. } => write!(f, "{else_branch}")?, - _ => write!(f, "{}", indent(format!("{else_branch}")))?, + _ => write!(f, "{}", indent(else_branch.to_string()))?, } } Ok(()) @@ -107,7 +110,7 @@ impl Display for Stmt { writeln!(f, "{condition}")?; match body.as_ref() { Stmt::Block { .. } => write!(f, "{body}")?, - _ => write!(f, "{}", indent(format!("{body}")))?, + _ => write!(f, "{}", indent(body.to_string()))?, } Ok(()) } @@ -115,7 +118,14 @@ impl Display for Stmt { Stmt::Block { statements } => { writeln!(f, "{{")?; for statement in statements { - write!(f, "{}", indent(format!("{statement}")))?; + writeln!(f, "{}", indent(statement.to_string()))?; + } + write!(f, "}}") + } + Stmt::Class { name, methods } => { + writeln!(f, "class {name} {{")?; + for method in methods { + writeln!(f, "{method}")?; } write!(f, "}}") } diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml new file mode 100644 index 0000000..cecaef4 --- /dev/null +++ b/interpreter/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rlox2-interpreter" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.rlox2-frontend] +path = "../frontend" + +[dependencies] +thiserror = "1.0.38" +itertools = "0.10.5" diff --git a/interpreter/src/class.rs b/interpreter/src/class.rs new file mode 100644 index 0000000..9efb3ee --- /dev/null +++ b/interpreter/src/class.rs @@ -0,0 +1,19 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct LoxClass { + name: String, +} + +impl LoxClass { + pub fn new(name: impl Into) -> Self { + let name = name.into(); + LoxClass { name } + } +} + +impl Display for LoxClass { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "", self.name) + } +} diff --git a/interpreter/src/environment.rs b/interpreter/src/environment.rs new file mode 100644 index 0000000..cb3c48d --- /dev/null +++ b/interpreter/src/environment.rs @@ -0,0 +1,209 @@ +use std::collections::HashMap; +use std::fmt::Display; +use std::io::{Read, Write}; + +use crate::error::RuntimeError; + +use super::value::HeapedValue; +use super::{Runtime, Value}; + +pub type Scope = HashMap; + +#[derive(Debug)] +pub struct Environment<'a> { + local_scopes: Vec, + + runtime: &'a mut Runtime, +} + +impl<'a> Environment<'a> { + pub fn new(runtime: &'a mut Runtime) -> Self { + Environment { + // globals: HashMap::new(), + // frames: vec![Frame::new_global()], + local_scopes: Vec::new(), + + runtime, + } + } + + pub fn globals(&self) -> &HashMap { + self.runtime.globals() + } + + pub fn enter_scope(&mut self) { + // self.current_frame_mut().enter_scope(); + self.local_scopes.push(Scope::new()); + } + + pub fn push_scope(&mut self, scope: Scope) { + self.local_scopes.push(scope); + } + + pub fn exit_scope(&mut self) { + // self.current_frame_mut().exit_scope(); + self.local_scopes.pop().expect("Tried to pop global scope"); + } + + pub fn get_global(&self, name: &str) -> Result { + self.runtime.get_global(name) + } + + pub fn get_local(&self, name: &str, level: usize) -> Result { + if let Some(scope) = self.local_scopes.iter().rev().nth(level) { + if let Some(heap_value) = scope.get(name) { + return Ok(heap_value.get_clone()); + } + } + + // DEBUG + println!("Name {name} not defined at level {level}"); + println!("{self}"); + + Err(RuntimeError::NameNotDefined { name: name.to_owned() }) + // self.runtime.get_global(name) + } + + pub fn define(&mut self, name: impl Into, value: Value) { + if let Some(scope) = self.local_scopes.last_mut() { + let name = name.into(); + scope.insert(name, HeapedValue::new(value)); + } else { + self.runtime.define_global(name, value) + } + } + + pub fn assign_global(&mut self, name: &str, value: Value) -> Result<(), RuntimeError> { + self.runtime.assign_global(name, value) + } + + pub fn assign(&mut self, name: &str, value: Value, level: usize) -> Result<(), RuntimeError> { + if let Some(scope) = self.local_scopes.iter_mut().rev().nth(level) { + if let Some(heap_value) = scope.get_mut(name) { + heap_value.replace(value); + return Ok(()); + } + } + + Err(RuntimeError::NameNotDefined { name: name.to_owned() }) + // self.runtime.assign_global(name, value) + } + + /* pub fn push_frame(&mut self, base_scope: Scope) { + self.frames.push(Frame::new(base_scope)); + } + + pub fn pop_frame(&mut self) { + self.frames.pop().expect("Tried to pop global frame"); + } */ + + pub fn collect_closure(&self, closure_vars: &[(String, usize)]) -> Scope { + let mut closure_scope = Scope::new(); + + for (name, level) in closure_vars { + let heap_value = self + .local_scopes + .iter() + .rev() + .nth(*level) + .unwrap() + .get(name) + .unwrap() + .clone(); + + let name = name.to_owned(); + + closure_scope.insert(name, heap_value); + } + + closure_scope + } +} + +impl Display for Environment<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.runtime)?; + + for (level, scope) in self.local_scopes.iter().enumerate() { + write!(f, "\nScope {level}:")?; + + for (name, value) in scope.iter() { + write!(f, "\n\t{name} = {value}")?; + } + } + + Ok(()) + } +} + +impl Read for Environment<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.runtime.read(buf) + } +} + +impl Write for Environment<'_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.runtime.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.runtime.flush() + } +} + +/*====================================================================================================================*/ + +/* struct Frame { + scopes: Vec, +} + +impl Frame { + fn new(base_scope: Scope) -> Self { + Frame { + scopes: vec![base_scope], + } + } + + fn new_global() -> Self { + Frame { scopes: Vec::new() } + } + + fn enter_scope(&mut self) { + self.scopes.push(Scope::new()); + } + + fn exit_scope(&mut self) { + self.scopes.pop().expect("Tried to exit scope, but no scope to exit"); + } + + fn get(&self, name: &str) -> Option { + for scope in self.scopes.iter().rev() { + if let Some(heap_value) = scope.get(name) { + return Some(heap_value.get_clone()); + } + } + + None + } + + fn try_define(&mut self, name: impl Into + Borrow, value: Value) -> Result<(), Value> { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(name.into(), HeapValue::new(value)); + Ok(()) + } else { + Err(value) + } + } + + fn try_assign(&mut self, name: &str, value: Value) -> Result<(), Value> { + for scope in self.scopes.iter_mut().rev() { + if let Some(heap_value) = scope.get_mut(name) { + heap_value.replace(value); + return Ok(()); + } + } + + Err(value) + } +} */ diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs new file mode 100644 index 0000000..e62f536 --- /dev/null +++ b/interpreter/src/error.rs @@ -0,0 +1,85 @@ +use itertools::Itertools; +use rlox2_frontend::lexer::LexerError; +use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp}; +use thiserror::Error; + +use crate::ResolverError; +use crate::Value; + +#[derive(Error, Debug)] +pub enum RuntimeError { + #[error("Unary operator {op} had invalid argument {arg}")] + UnaryOpInvalidArgument { op: UnaryOp, arg: Value }, + #[error("Binary operator {op} had invalid arguments {left} and {right}")] + BinaryOpInvalidArguments { left: Value, op: BinaryOp, right: Value }, + #[error("Division by zero")] + DivisionByZero, + #[error("Name {name} is not defined")] + NameNotDefined { name: String }, + #[error("Global {name} is not defined")] + GlobalNotDefined { name: String }, + #[error("{callee} is not callable")] + NotCallable { callee: Value }, + #[error("{name}() takes {arity} args, but {given} were given.")] + WrongArity { name: String, arity: usize, given: usize }, + #[error("Extern function call to {name} failed: {msg}")] + ExtFunCallFailed { name: String, msg: String }, + #[error("Uncaught break statement")] + Break, + #[error("Uncaught return statement")] + Return { value: Value }, + #[error("Exit with exit code {exit_code}")] + Exit { exit_code: i32 }, +} + +#[derive(Error, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum LoxError { + #[error("{0}", format_multiple_errors(inner))] + LexerError { inner: Vec }, + #[error("{0}", format_multiple_errors(inner))] + ParserError { inner: Vec }, + #[error("{inner}")] + ResolverError { inner: ResolverError }, + #[error("{inner}")] + RuntimeError { inner: RuntimeError }, + #[error("Called exit() with exit code {exit_code}")] + Exit { exit_code: i32 }, +} + +fn format_multiple_errors(errs: &Vec) -> String { + let msg = if errs.len() == 1 { + errs[0].to_string() + } else { + errs.iter().map(|err| err.to_string()).join("\n") + }; + + msg +} + +impl From> for LoxError { + fn from(lexer_errs: Vec) -> Self { + LoxError::LexerError { inner: lexer_errs } + } +} + +impl From> for LoxError { + fn from(parser_errs: Vec) -> Self { + LoxError::ParserError { inner: parser_errs } + } +} + +impl From for LoxError { + fn from(resolver_err: ResolverError) -> Self { + LoxError::ResolverError { inner: resolver_err } + } +} + +impl From for LoxError { + fn from(runtime_err: RuntimeError) -> Self { + match runtime_err { + RuntimeError::Exit { exit_code } => LoxError::Exit { exit_code }, + _ => LoxError::RuntimeError { inner: runtime_err }, + } + } +} diff --git a/src/interpreter/function.rs b/interpreter/src/function.rs similarity index 89% rename from src/interpreter/function.rs rename to interpreter/src/function.rs index 0b83116..722c447 100644 --- a/src/interpreter/function.rs +++ b/interpreter/src/function.rs @@ -1,10 +1,10 @@ use std::fmt::{Debug, Display}; -use crate::parser::Stmt; +use rlox2_frontend::parser::Stmt; use super::environment::{Environment, Scope}; use super::interpret::EvalResult; -use super::Value; +use super::{Runtime, Value}; #[derive(Debug, Clone)] pub struct LoxFunction { @@ -15,12 +15,12 @@ pub struct LoxFunction { } impl LoxFunction { - pub fn new(name: impl Into, param_names: Vec, body: Stmt) -> Self { + pub fn new(name: impl Into, closure: Scope, param_names: Vec, body: Stmt) -> Self { let name = name.into(); let body = Box::new(body); LoxFunction { name, - closure: Scope::new(), + closure, param_names, body, } @@ -71,10 +71,10 @@ impl LoxExternFunction { LoxExternFunction { name, closure } } - pub fn register(self, env: &mut Environment) { + pub fn register(self, env: &mut Runtime) { let name = self.name.clone(); let fun = Value::extern_function(self); - env.define(name, fun); + env.define_global(name, fun); } pub fn call(&self, args: Vec, env: &mut Environment) -> EvalResult { diff --git a/src/interpreter/interpret.rs b/interpreter/src/interpret.rs similarity index 76% rename from src/interpreter/interpret.rs rename to interpreter/src/interpret.rs index ac274c8..3a2780b 100644 --- a/src/interpreter/interpret.rs +++ b/interpreter/src/interpret.rs @@ -1,26 +1,32 @@ use std::rc::Rc; +use rlox2_frontend::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp}; + use crate::error::RuntimeError; -use crate::parser::{BinaryOp, Expr, Literal, LogicalOp, Stmt, UnaryOp}; +use crate::LoxClass; use super::environment::Environment; -use super::{LoxFunction, Value}; +use super::{LoxFunction, Runtime, Value}; pub type EvalResult = Result; /*====================================================================================================================*/ -pub fn execute(statement: Stmt, env: &mut Environment) -> Result<(), RuntimeError> { - statement.execute(env) +pub fn execute(statement: Stmt, runtime: &mut Runtime) -> Result<(), RuntimeError> { + let mut env = Environment::new(runtime); + + statement.eval(&mut env)?; + + Ok(()) } /*====================================================================================================================*/ -/* trait Eval { - fn eval(self, env: &mut Environment) -> EvalResult; -} */ +trait Eval { + fn eval(&self, env: &mut Environment) -> EvalResult; +} -impl Literal { +impl Eval for Literal { fn eval(&self, env: &mut Environment) -> EvalResult { let _ = env; match self { @@ -32,7 +38,7 @@ impl Literal { } } -impl Expr { +impl Eval for Expr { fn eval(&self, env: &mut Environment) -> EvalResult { match self { Expr::Literal { literal } => literal.eval(env), @@ -104,10 +110,17 @@ impl Expr { Ok(right) } Expr::Grouping { expr } => expr.eval(env), - Expr::Variable { name } => env.get(name), - Expr::Assignment { name, value } => { + Expr::Variable { name } => panic!("Unresolved variable {name}"), + Expr::LocalVariable { name, level } => env.get_local(name, *level), + Expr::GlobalVariable { name } => env.get_global(name), + Expr::Assignment { target, value } => { let value = value.eval(env)?; - env.assign(name, value.clone())?; + + match target.as_ref() { + Expr::LocalVariable { name, level } => env.assign(name, value.clone(), *level)?, + Expr::GlobalVariable { name } => env.assign_global(name, value.clone())?, + _ => panic!("Invalid assigment target {target}"), + } Ok(value) } Expr::Call { callee, args } => { @@ -126,9 +139,11 @@ impl Expr { Expr::Function { name, param_names, + closure_vars, body, } => Ok(Value::function(LoxFunction::new( name, + env.collect_closure(closure_vars), param_names.clone(), body.as_ref().clone(), ))), @@ -136,8 +151,8 @@ impl Expr { } } -impl Stmt { - fn execute(&self, env: &mut Environment) -> EvalResult<()> { +impl Eval for Stmt { + fn eval(&self, env: &mut Environment) -> EvalResult { match self { Stmt::Print { expr } => { match expr.eval(env)? { @@ -145,8 +160,6 @@ impl Stmt { Value::String(s) => println!("{s}"), val => println!("{val}"), } - - Ok(()) } Stmt::IfStmt { condition, @@ -155,49 +168,54 @@ impl Stmt { } => { let condition = condition.eval(env)?; if condition.is_truthy() { - then_branch.execute(env) + then_branch.eval(env)?; } else if let Some(else_branch) = else_branch { - else_branch.execute(env) - } else { - Ok(()) + else_branch.eval(env)?; } } Stmt::While { condition, body } => { while condition.eval(env)?.is_truthy() { - match body.execute(env) { + match body.eval(env) { Ok(_) => {} Err(RuntimeError::Break) => break, Err(err) => return Err(err), } } - Ok(()) } Stmt::VarDecl { name, initializer } => { let initializer = initializer.eval(env)?; - env.define(name.as_ref(), initializer); - Ok(()) + env.define(name, initializer); } Stmt::Block { statements } => { env.enter_scope(); for statement in statements { - // on error the current frame gets destroyed anyways, so no need to exit scope - statement.execute(env)?; + if let Err(err) = statement.eval(env) { + env.exit_scope(); + return Err(err); + } } env.exit_scope(); - Ok(()) + } + Stmt::Class { name, methods: _ } => { + env.define(name, Value::Nil); + + let class = Value::class(LoxClass::new(name)); + + env.assign(name, class, 0)?; } Stmt::ExprStmt { expr } => { // expr.eval(env)?; // Ok(Value::Nil) expr.eval(env)?; - Ok(()) } - Stmt::Break => Err(RuntimeError::Break), + Stmt::Break => return Err(RuntimeError::Break), Stmt::Return { expr } => { let value = expr.eval(env)?; - Err(RuntimeError::Return { value }) + return Err(RuntimeError::Return { value }); } } + + Ok(Value::Nil) } } @@ -213,20 +231,20 @@ impl LoxFunction { }); } - env.push_frame(self.closure().clone()); + env.push_scope(self.closure().clone()); for (name, value) in std::iter::zip(self.param_names(), args) { - env.define(name.as_ref(), value); + env.define(name, value); } - let ret_val = match self.body().execute(env) { - Ok(()) => Value::Nil, - Err(RuntimeError::Return { value }) => value, - Err(err) => return Err(err), + let ret_val = match self.body().eval(env) { + Ok(_) => Ok(Value::Nil), + Err(RuntimeError::Return { value }) => Ok(value), + Err(err) => Err(err), }; - env.pop_frame(); + env.exit_scope(); - Ok(ret_val) + ret_val } } diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs new file mode 100644 index 0000000..8ae862b --- /dev/null +++ b/interpreter/src/lib.rs @@ -0,0 +1,18 @@ +mod class; +mod environment; +mod error; +mod function; +mod interpret; +mod lox_std; +mod resolver; +mod run; +mod runtime; +mod value; + +pub use class::LoxClass; +pub use error::{LoxError, RuntimeError}; +pub use function::LoxFunction; +pub use resolver::{resolve, ResolverError}; +pub use run::{run_file, run_repl}; +pub use runtime::Runtime; +pub use value::Value; diff --git a/src/interpreter/lox_std.rs b/interpreter/src/lox_std.rs similarity index 82% rename from src/interpreter/lox_std.rs rename to interpreter/src/lox_std.rs index 73a3f9b..e51e96c 100644 --- a/src/interpreter/lox_std.rs +++ b/interpreter/src/lox_std.rs @@ -1,15 +1,14 @@ use crate::error::RuntimeError; -use super::environment::Environment; use super::function::{ExternFunClosure, LoxExternFunction}; -use super::value::HeapValue; -use super::Value; +use super::{Runtime, Value}; -pub fn init_std(env: &mut Environment) { - input().register(env); +pub fn init_std(env: &mut Runtime) { clock().register(env); - print_globals().register(env); exit().register(env); + input().register(env); + print_env().register(env); + print_globals().register(env); } fn input() -> LoxExternFunction { @@ -58,7 +57,7 @@ fn clock() -> LoxExternFunction { fn print_globals() -> LoxExternFunction { let closure: ExternFunClosure = |args, env| match *args { [] => { - let mut globals: Vec<(&String, &HeapValue)> = env.globals().iter().collect(); + let mut globals: Vec<(&String, &Value)> = env.globals().iter().collect(); globals.sort_by_key(|&(name, _value)| name); @@ -77,6 +76,21 @@ fn print_globals() -> LoxExternFunction { LoxExternFunction::new("print_globals", closure) } +fn print_env() -> LoxExternFunction { + let closure: ExternFunClosure = |args, env| match *args { + [] => { + println!("{env}"); + Ok(Value::Nil) + } + _ => Err(RuntimeError::ExtFunCallFailed { + name: "print_env".to_owned(), + msg: "print_env() takes no arguments".to_owned(), + }), + }; + + LoxExternFunction::new("print_env", closure) +} + fn exit() -> LoxExternFunction { let closure: ExternFunClosure = |args, _env| { match &*args { diff --git a/interpreter/src/resolver/error.rs b/interpreter/src/resolver/error.rs new file mode 100644 index 0000000..fbfee8c --- /dev/null +++ b/interpreter/src/resolver/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ResolverError { + #[error("Can't read variable {name} in its own initializer.")] + VarInOwnInitializer { name: String }, + #[error("Could not resolve variable {name}")] + UnresolvableVariable { name: String }, + #[error("Return outside of function definition")] + ReturnOutsideFunction, +} diff --git a/interpreter/src/resolver/mod.rs b/interpreter/src/resolver/mod.rs new file mode 100644 index 0000000..86fe52f --- /dev/null +++ b/interpreter/src/resolver/mod.rs @@ -0,0 +1,5 @@ +mod error; +mod resolve; + +pub use error::ResolverError; +pub use resolve::resolve; diff --git a/interpreter/src/resolver/resolve.rs b/interpreter/src/resolver/resolve.rs new file mode 100644 index 0000000..da96f36 --- /dev/null +++ b/interpreter/src/resolver/resolve.rs @@ -0,0 +1,276 @@ +use std::collections::HashMap; + +use rlox2_frontend::parser::{Expr, Stmt}; + +use crate::Runtime; +use crate::{LoxError, ResolverError}; + +/*====================================================================================================================*/ + +type ResolverScope = HashMap; +type ResolverFrame = Vec; + +/*====================================================================================================================*/ + +#[derive(Debug, Clone, Copy)] +enum ResolveStatus { + Declared, + Defined, +} + +/*====================================================================================================================*/ + +pub fn resolve(statement: &mut Stmt, runtime: &mut Runtime) -> Result<(), LoxError> { + let global_names = runtime.globals().keys().cloned(); + + let mut resolver = Resolver::new(global_names); + + resolver.resolve_stmt(statement)?; + + Ok(()) +} + +#[derive(Debug)] +struct Resolver { + global_scope: ResolverScope, + // local_scopes: Vec, + frames: Vec, + + closure_vars: HashMap, +} + +impl Resolver { + fn new(global_names: impl Iterator) -> Self { + let mut global_scope = ResolverScope::new(); + + for name in global_names { + global_scope.insert(name, ResolveStatus::Defined); + } + + Resolver { + global_scope, + frames: vec![ResolverFrame::new()], + closure_vars: HashMap::new(), + } + } + + fn local_scopes(&self) -> &ResolverFrame { + self.frames.last().unwrap() + } + + fn local_scopes_mut(&mut self) -> &mut ResolverFrame { + self.frames.last_mut().unwrap() + } + + fn enter_scope(&mut self) { + self.local_scopes_mut().push(ResolverScope::new()); + } + + fn exit_scope(&mut self) { + self.local_scopes_mut() + .pop() + .expect("Tried to pop from empty ResolverFrame"); + } + + fn push_frame(&mut self) { + self.frames.push(vec![ResolverScope::new()]); + } + + fn pop_frame(&mut self) { + self.frames.pop().expect("Tried to pop non-existant ResolverFrame"); + } + + fn declare(&mut self, name: &str) { + let name = name.to_owned(); + if let Some(local_scope) = self.local_scopes_mut().last_mut() { + local_scope.insert(name, ResolveStatus::Declared); + } else { + self.global_scope.insert(name, ResolveStatus::Declared); + } + } + + fn define(&mut self, name: &str) { + let name = name.to_owned(); + if let Some(local_scope) = self.local_scopes_mut().last_mut() { + local_scope.insert(name, ResolveStatus::Defined); + } else { + self.global_scope.insert(name, ResolveStatus::Declared); + } + } + + fn check(&self, name: &str) -> Option { + if let Some(local_scope) = self.local_scopes().last() { + local_scope.get(name).cloned() + } else { + self.global_scope.get(name).cloned() + } + } + + fn resolve_var(&mut self, name: &str) -> Result { + let name = name.to_owned(); + + let mut level = 0; + + for scope in self.local_scopes().iter().rev() { + if scope.contains_key(&name) { + return Ok(Expr::LocalVariable { name, level }); + } + // DEBUG + level += 1; + } + + for frame in self.frames.iter().rev().skip(1) { + for scope in frame.iter().rev() { + if scope.contains_key(&name) { + if !self.closure_vars.contains_key(&name) { + // the level at which the closed-over variable will be collected from + // from the perspective of the parameter/closure scope i.e. the outmost of the function + self.closure_vars + .insert(name.clone(), level - self.local_scopes().len()); + } + return Ok(Expr::LocalVariable { + name, + // distance from actual variable refernce to parameter/closure scope + level: self.local_scopes().len() - 1, + }); + } + level += 1; + } + } + + if self.global_scope.contains_key(&name) { + return Ok(Expr::GlobalVariable { name }); + } + + Err(ResolverError::UnresolvableVariable { name }) + } + + fn resolve_stmt(&mut self, stmt: &mut Stmt) -> Result<(), ResolverError> { + match stmt { + Stmt::Print { expr } => self.resolve_expr(expr), + Stmt::IfStmt { + condition, + then_branch, + else_branch, + } => { + self.resolve_expr(condition)?; + self.resolve_stmt(then_branch)?; + if let Some(else_branch) = else_branch { + self.resolve_stmt(else_branch)?; + } + Ok(()) + } + Stmt::While { condition, body } => { + self.resolve_expr(condition)?; + self.resolve_stmt(body)?; + Ok(()) + } + Stmt::VarDecl { name, initializer } => { + self.declare(name); + self.resolve_expr(initializer)?; + self.define(name); + Ok(()) + } + Stmt::Block { statements } => { + self.enter_scope(); + for statement in statements.iter_mut() { + self.resolve_stmt(statement)?; + } + self.exit_scope(); + Ok(()) + } + Stmt::Class { name, methods: _ } => { + self.declare(name); + self.define(name); + Ok(()) + } + Stmt::ExprStmt { expr } => self.resolve_expr(expr), + Stmt::Break => Ok(()), + Stmt::Return { expr } => self.resolve_expr(expr), + } + } + + fn resolve_expr(&mut self, expr: &mut Expr) -> Result<(), ResolverError> { + match expr { + Expr::Literal { literal: _ } => Ok(()), + Expr::Unary { op: _, expr } => self.resolve_expr(expr), + Expr::Binary { left, op: _, right } => { + self.resolve_expr(left)?; + self.resolve_expr(right)?; + Ok(()) + } + Expr::Logical { left, op: _, right } => { + self.resolve_expr(left)?; + self.resolve_expr(right)?; + Ok(()) + } + Expr::Grouping { expr } => self.resolve_expr(expr), + Expr::Variable { name } => { + if let Some(ResolveStatus::Declared) = self.check(name) { + let name = name.clone(); + return Err(ResolverError::VarInOwnInitializer { name }); + } + + *expr = self.resolve_var(name)?; + + Ok(()) + } + Expr::LocalVariable { name, level: _ } => { + panic!("Tried to resolve variable {name} twice") + } + Expr::GlobalVariable { name } => { + panic!("Tried to resolve variable {name} twice") + } + + Expr::Assignment { target, value } => { + self.resolve_expr(value)?; + + let target = target.as_mut(); + if let Expr::Variable { name } = target { + *target = self.resolve_var(name)?; + } else { + panic!("Invalid assignment target {target}"); + } + + Ok(()) + } + Expr::Call { callee, args } => { + self.resolve_expr(callee)?; + for arg in args.iter_mut() { + self.resolve_expr(arg)?; + } + Ok(()) + } + Expr::Function { + name, + param_names, + closure_vars, + body, + } => { + let old_closure_names = std::mem::take(&mut self.closure_vars); + + self.push_frame(); + + self.declare(name); + self.define(name); + + for param_name in param_names { + self.declare(param_name); + self.define(param_name); + } + + self.resolve_stmt(body)?; + + self.pop_frame(); + + let closure_names = std::mem::replace(&mut self.closure_vars, old_closure_names); + + for closure_var in closure_names { + closure_vars.push(closure_var); + } + + Ok(()) + } + } + } +} diff --git a/src/interpreter/run.rs b/interpreter/src/run.rs similarity index 66% rename from src/interpreter/run.rs rename to interpreter/src/run.rs index 4c9fd4c..2de073b 100644 --- a/src/interpreter/run.rs +++ b/interpreter/src/run.rs @@ -1,28 +1,17 @@ use std::io::Write; +use rlox2_frontend::lexer::{scan_tokens, Token}; +use rlox2_frontend::parser::parse_tokens; + use crate::error::LoxError; -use crate::interpreter::interpret::execute; -use crate::lexer::{scan_tokens, Token}; -use crate::parser::parse_tokens; +use crate::interpret::execute; +use crate::resolver::resolve; -use super::environment::Environment; +use super::Runtime; -pub fn interpreter_main() { - let args: Vec = std::env::args().collect(); - - match args.len() { - 1 => run_repl(), - 2 => run_file(&args[1]), - _ => { - eprintln!("Usage: rlox [script]"); - std::process::exit(64); - } - } -} - -fn run_file(script_path: &str) { +pub fn run_file(runtime: &mut Runtime, script_path: &str) { let source_code = std::fs::read_to_string(script_path).unwrap_or_else(|err| { - eprintln!("Reading script file {} failed: {}", script_path, err); + eprintln!("Reading script file {script_path} failed: {err}"); std::process::exit(66); }); @@ -31,14 +20,14 @@ fn run_file(script_path: &str) { std::process::exit(65); } */ - let mut env = Environment::new(); - - match run(&source_code, &mut env) { - Ok(()) => std::process::exit(0), + match run(&source_code, runtime) { + Ok(()) => {} Err(err) => { eprintln!("{err}"); match err { - LoxError::LexerError { .. } | LoxError::ParserError { .. } => std::process::exit(65), + LoxError::LexerError { .. } | LoxError::ParserError { .. } | LoxError::ResolverError { .. } => { + std::process::exit(65) + } LoxError::RuntimeError { .. } => std::process::exit(70), LoxError::Exit { exit_code } => std::process::exit(exit_code), } @@ -46,11 +35,9 @@ fn run_file(script_path: &str) { } } -fn run_repl() { +pub fn run_repl(runtime: &mut Runtime) { let stdin = std::io::stdin(); - let mut env = Environment::new(); - loop { let mut input_buf = String::new(); @@ -59,7 +46,7 @@ fn run_repl() { 'inner: loop { stdin.read_line(&mut input_buf).unwrap_or_else(|err| { - eprintln!("Could not read from stdin: {}", err); + eprintln!("Could not read from stdin: {err}"); std::process::exit(66); }); @@ -78,6 +65,11 @@ fn run_repl() { } print!("< "); + + // let indentation = " ".repeat((num_open_braces + num_open_brackets + num_open_parens) as usize); + + // print!("{indentation}"); + std::io::stdout().flush().unwrap(); } @@ -87,21 +79,20 @@ fn run_repl() { std::process::exit(0); } - match run(input_buf, &mut env) { + match run(input_buf, runtime) { Ok(()) => {} Err(LoxError::Exit { exit_code }) => std::process::exit(exit_code), - Err(err) => eprintln!("{}", err), + Err(err) => eprintln!("{err}"), } } } -fn run(code_string: &str, env: &mut Environment) -> Result<(), LoxError> { +fn run(code_string: &str, runtime: &mut Runtime) -> Result<(), LoxError> { let tokens: Vec = scan_tokens(code_string)?; /* let token_str = tokens .iter() - .map(|token| format!("{token}")) - .collect::>() + .map(|token| token.to_string()) .join(" "); println!("{token_str}"); */ @@ -112,16 +103,14 @@ fn run(code_string: &str, env: &mut Environment) -> Result<(), LoxError> { println!("{statement}"); } */ - // let mut result = Value::Nil; - - for statement in statements { - execute(statement, env)?; - } - - /* match result { - Value::Nil => {} - result => println!("{result}"), + /* for statement in statements.iter() { + println!("{statement}"); } */ + for mut statement in statements { + resolve(&mut statement, runtime)?; + execute(statement, runtime)?; + } + Ok(()) } diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs new file mode 100644 index 0000000..e0d3fe5 --- /dev/null +++ b/interpreter/src/runtime.rs @@ -0,0 +1,101 @@ +use std::collections::HashMap; +use std::fmt::Display; +use std::io::{stdin, stdout, Read, Write}; + +use crate::error::RuntimeError; + +use super::lox_std::init_std; +use super::Value; + +pub struct Runtime { + globals: HashMap, + + in_stream: Box, + out_stream: Box, +} + +impl Runtime { + pub fn new(in_stream: Box, out_stream: Box) -> Self { + let in_stream = Box::new(std::io::BufReader::new(in_stream)); + let mut runtime = Runtime { + globals: HashMap::new(), + in_stream, + out_stream, + }; + + init_std(&mut runtime); + + runtime + } + + pub fn globals(&self) -> &HashMap { + &self.globals + } + + pub fn get_global(&self, name: &str) -> Result { + self.globals + .get(name) + .cloned() + .ok_or_else(|| RuntimeError::GlobalNotDefined { name: name.to_owned() }) + } + + pub fn define_global(&mut self, name: impl Into, value: Value) { + let name = name.into(); + self.globals.insert(name, value); + } + + pub fn assign_global(&mut self, name: &str, value: Value) -> Result<(), RuntimeError> { + if let Some(old_value) = self.globals.get_mut(name) { + *old_value = value; + Ok(()) + } else { + Err(RuntimeError::GlobalNotDefined { name: name.to_owned() }) + } + } +} + +impl Default for Runtime { + fn default() -> Self { + Runtime::new(Box::new(std::io::BufReader::new(stdin())), Box::new(stdout())) + } +} + +impl std::fmt::Debug for Runtime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Runtime").field("globals", &self.globals).finish() + } +} + +impl Display for Runtime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Globals:")?; + + for (name, value) in self.globals.iter() { + write!(f, "\n\t{name} = {value}")?; + } + + Ok(()) + } +} + +impl Drop for Runtime { + fn drop(&mut self) { + self.out_stream.flush().unwrap(); + } +} + +impl Read for Runtime { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.in_stream.read(buf) + } +} + +impl Write for Runtime { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.out_stream.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.out_stream.flush() + } +} diff --git a/src/interpreter/value.rs b/interpreter/src/value.rs similarity index 88% rename from src/interpreter/value.rs rename to interpreter/src/value.rs index 42dcd1e..163162a 100644 --- a/src/interpreter/value.rs +++ b/interpreter/src/value.rs @@ -2,11 +2,14 @@ use std::cell::UnsafeCell; use std::fmt::{Debug, Display}; use std::rc::Rc; +use crate::LoxClass; + use super::function::LoxExternFunction; use super::LoxFunction; #[derive(Debug, Clone)] pub enum Value { + Class(Rc), Function(Rc), ExternFunction(Rc), String(Rc), @@ -16,6 +19,11 @@ pub enum Value { } impl Value { + pub fn class(class: LoxClass) -> Self { + let class = Rc::new(class); + Value::Class(class) + } + pub fn function(fun: LoxFunction) -> Self { let fun = Rc::new(fun); Value::Function(fun) @@ -39,6 +47,7 @@ impl Value { impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Value::Class(class) => write!(f, "{class}"), Value::Function(fun) => write!(f, "{fun}"), Value::ExternFunction(ext_fun) => write!(f, "{ext_fun}"), Value::String(s) => write!(f, "\"{s}\""), @@ -66,14 +75,14 @@ impl PartialEq for Value { /*====================================================================================================================*/ #[derive(Clone)] -pub struct HeapValue { +pub struct HeapedValue { inner: Rc>, } -impl HeapValue { +impl HeapedValue { pub fn new(value: Value) -> Self { let inner = Rc::new(UnsafeCell::new(value)); - HeapValue { inner } + HeapedValue { inner } } pub fn get_clone(&self) -> Value { @@ -93,13 +102,13 @@ impl HeapValue { } } -impl Debug for HeapValue { +impl Debug for HeapedValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("HeapValue").field("inner", &self.get_clone()).finish() } } -impl Display for HeapValue { +impl Display for HeapedValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.get_clone()) } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 854b47c..0000000 --- a/src/error.rs +++ /dev/null @@ -1,125 +0,0 @@ -use thiserror::Error; - -use crate::interpreter::Value; -use crate::lexer::Token; -use crate::misc::CodePos; -use crate::parser::{BinaryOp, Expr, UnaryOp}; - -#[derive(Error, Debug)] -pub enum LexerError { - #[error("Unexpected character '{c}' at {code_pos}.")] - UnexpectedCharacter { c: char, code_pos: CodePos }, - #[error("Unterminated string literal starting at {code_pos}.")] - UnterminatedStringLiteral { code_pos: CodePos }, - #[error("Unterminated block comment starting at {code_pos}.")] - UnterminatedBlockComment { code_pos: CodePos }, - #[error("Invalid number literal {lexeme} at {code_pos}: {msg}")] - InvalidNumberLiteral { - lexeme: String, - msg: String, - code_pos: CodePos, - }, -} - -#[derive(Error, Debug)] -pub enum ParserError { - #[error("Token stream ended unexpectedly.")] - TokenStreamEnded, - #[error("Expected a primary expression, but found a {token} token instead at {0}.", token.code_pos)] - ExpectedPrimary { token: Token }, - #[error("Missing semicolon at {code_pos}")] - MissingSemicolon { code_pos: CodePos }, - #[error("Expected variable name at {0}, got {token} instead", token.code_pos)] - ExpectedVarName { token: Token }, - #[error("Can't assign to {expr} at {code_pos}")] - InvalidAssignment { expr: Expr, code_pos: CodePos }, - #[error("Missing closing curly brace at {code_pos}")] - MissingRightBrace { code_pos: CodePos }, - #[error("Missing closing parenthesis at {code_pos}")] - MissingRightParen { code_pos: CodePos }, - #[error("Missing parenthesis after if at {code_pos}")] - MissingParenAfterIf { code_pos: CodePos }, - #[error("Missing parenthesis after while at {code_pos}")] - MissingParenAfterWhile { code_pos: CodePos }, - #[error("Missing parenthesis after for at {code_pos}")] - MissingParenAfterFor { code_pos: CodePos }, - #[error("Call at {code_pos} has too many arguments")] - TooManyArguments { code_pos: CodePos }, - #[error("{msg} at {code_pos}")] - MissingIdentifier { msg: String, code_pos: CodePos }, - #[error("Missing arguments to function declaration at {code_pos}")] - MissingFunctionArgs { code_pos: CodePos }, - #[error("Missing body to function declaration at {code_pos}")] - MissingFunctionBody { code_pos: CodePos }, - #[error("Function declaration at {code_pos} has too many parameters")] - TooManyParams { code_pos: CodePos }, -} - -#[derive(Error, Debug)] -pub enum RuntimeError { - #[error("Unary operator {op} had invalid argument {arg}")] - UnaryOpInvalidArgument { op: UnaryOp, arg: Value }, - #[error("Binary operator {op} had invalid arguments {left} and {right}")] - BinaryOpInvalidArguments { left: Value, op: BinaryOp, right: Value }, - #[error("Division by zero")] - DivisionByZero, - #[error("Name {name} is not defined")] - NameNotDefined { name: String }, - #[error("{callee} is not callable")] - NotCallable { callee: Value }, - #[error("{name}() takes {arity} args, but {given} were given.")] - WrongArity { name: String, arity: usize, given: usize }, - #[error("Extern function call to {name} failed: {msg}")] - ExtFunCallFailed { name: String, msg: String }, - #[error("Uncaught break statement")] - Break, - #[error("Uncaught return statement")] - Return { value: Value }, - #[error("Exit with exit code {exit_code}")] - Exit { exit_code: i32 }, -} - -#[derive(Error, Debug)] -#[allow(clippy::enum_variant_names)] -pub enum LoxError { - #[error("{0}", format_multiple_errors(inner))] - LexerError { inner: Vec }, - #[error("{0}", format_multiple_errors(inner))] - ParserError { inner: Vec }, - #[error("{inner}")] - RuntimeError { inner: RuntimeError }, - #[error("Called exit() with exit code {exit_code}")] - Exit { exit_code: i32 }, -} - -fn format_multiple_errors(errs: &Vec) -> String { - let msg = if errs.len() == 1 { - format!("{}", errs[0]) - } else { - let msgs: Vec = errs.iter().map(|err| format!("{}", err)).collect(); - msgs.join("\n") - }; - - msg -} - -impl From> for LoxError { - fn from(lexer_errs: Vec) -> Self { - LoxError::LexerError { inner: lexer_errs } - } -} - -impl From> for LoxError { - fn from(parser_errs: Vec) -> Self { - LoxError::ParserError { inner: parser_errs } - } -} - -impl From for LoxError { - fn from(runtime_err: RuntimeError) -> Self { - match runtime_err { - RuntimeError::Exit { exit_code } => LoxError::Exit { exit_code }, - _ => LoxError::RuntimeError { inner: runtime_err }, - } - } -} diff --git a/src/interpreter/environment.rs b/src/interpreter/environment.rs deleted file mode 100644 index 0a4e8f4..0000000 --- a/src/interpreter/environment.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::borrow::Borrow; -use std::collections::HashMap; - -use crate::error::RuntimeError; - -use super::lox_std::init_std; -use super::value::HeapValue; -use super::Value; - -pub type Scope = HashMap; - -pub struct Environment { - globals: Scope, - - // scopes: Vec, - frames: Vec, -} - -impl Environment { - pub fn new() -> Self { - let mut env = Environment { - globals: HashMap::new(), - frames: vec![Frame::new_global()], - }; - - init_std(&mut env); - - env - } - - fn current_frame(&self) -> &Frame { - self.frames.last().unwrap() - } - - fn current_frame_mut(&mut self) -> &mut Frame { - self.frames.last_mut().unwrap() - } - - pub fn globals(&self) -> &Scope { - &self.globals - } - - pub fn get(&self, name: &str) -> Result { - if let Some(value) = self.current_frame().get(name) { - return Ok(value); - } - - match self.globals.get(name) { - Some(heap_value) => Ok(heap_value.get_clone()), - None => Err(RuntimeError::NameNotDefined { name: name.to_owned() }), - } - } - - pub fn define(&mut self, name: impl Into + Borrow, value: Value) { - if let Err(value) = self.current_frame_mut().try_define(name.borrow(), value) { - self.globals.insert(name.into(), HeapValue::new(value)); - } - } - - pub fn assign(&mut self, name: &str, value: Value) -> Result<(), RuntimeError> { - let value = match self.current_frame_mut().try_assign(name, value) { - Ok(()) => return Ok(()), - Err(value) => value, - }; - - if let Some(var) = self.globals.get_mut(name) { - // *var = value; - var.replace(value); - Ok(()) - } else { - Err(RuntimeError::NameNotDefined { name: name.to_owned() }) - } - } - - pub fn enter_scope(&mut self) { - self.current_frame_mut().enter_scope(); - } - - pub fn exit_scope(&mut self) { - self.current_frame_mut().exit_scope(); - } - - pub fn push_frame(&mut self, base_scope: Scope) { - self.frames.push(Frame::new(base_scope)); - } - - pub fn pop_frame(&mut self) { - self.frames.pop().expect("Tried to pop global frame"); - } -} - -/*====================================================================================================================*/ - -struct Frame { - scopes: Vec, -} - -impl Frame { - fn new(base_scope: Scope) -> Self { - Frame { - scopes: vec![base_scope], - } - } - - fn new_global() -> Self { - Frame { scopes: Vec::new() } - } - - fn enter_scope(&mut self) { - self.scopes.push(Scope::new()); - } - - fn exit_scope(&mut self) { - self.scopes.pop().expect("Tried to exit scope, but no scope to exit"); - } - - fn get(&self, name: &str) -> Option { - for scope in self.scopes.iter().rev() { - if let Some(heap_value) = scope.get(name) { - return Some(heap_value.get_clone()); - } - } - - None - } - - fn try_define(&mut self, name: impl Into + Borrow, value: Value) -> Result<(), Value> { - if let Some(scope) = self.scopes.last_mut() { - scope.insert(name.into(), HeapValue::new(value)); - Ok(()) - } else { - Err(value) - } - } - - fn try_assign(&mut self, name: &str, value: Value) -> Result<(), Value> { - for scope in self.scopes.iter_mut().rev() { - if let Some(heap_value) = scope.get_mut(name) { - heap_value.replace(value); - return Ok(()); - } - } - - Err(value) - } -} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs deleted file mode 100644 index 8d949d1..0000000 --- a/src/interpreter/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod environment; -mod function; -mod interpret; -mod lox_std; -mod run; -mod value; - -pub use function::LoxFunction; -pub use run::interpreter_main; -pub use value::Value; diff --git a/src/main.rs b/src/main.rs index 636da3c..fc2709b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,31 @@ -mod error; -mod interpreter; -mod lexer; -mod misc; -mod parser; +use clap::{ArgAction, Parser}; +use rlox2_interpreter::{run_file, run_repl, Runtime}; + +#[derive(Parser, Debug)] +struct CliArgs { + #[arg()] + file_name: Option, + + #[arg(short, action = ArgAction::SetTrue)] + interactive: bool, +} fn main() { - interpreter::interpreter_main(); + interpreter_main(); +} + +pub fn interpreter_main() { + let cli_args = CliArgs::parse(); + + let mut runtime = Runtime::default(); + + if let Some(file_name) = cli_args.file_name { + run_file(&mut runtime, &file_name); + + if cli_args.interactive { + run_repl(&mut runtime); + } + } else { + run_repl(&mut runtime); + } }