├── .gitignore ├── src ├── ast │ ├── mod.rs │ ├── stmt.rs │ └── expr.rs ├── sample_program.lux ├── scanner │ ├── mod.rs │ ├── types.rs │ ├── helpers.rs │ └── scan.rs ├── resolver │ ├── mod.rs │ ├── helpers.rs │ ├── statements.rs │ ├── resolve.rs │ ├── env.rs │ └── expressions.rs ├── parser │ ├── mod.rs │ ├── types.rs │ ├── parse.rs │ ├── helpers.rs │ ├── statements.rs │ └── expressions.rs ├── runner │ ├── mod.rs │ ├── types.rs │ ├── helpers.rs │ ├── errors.rs │ └── run.rs ├── interpreter │ ├── mod.rs │ ├── pn.rs │ ├── env.rs │ ├── interpret.rs │ ├── types.rs │ ├── helpers.rs │ ├── statements.rs │ ├── native_functions.rs │ └── expressions.rs ├── rustfmt.toml ├── lib.rs ├── main.rs ├── env.rs └── token.rs ├── rust-toolchain.toml ├── assets └── luxya_logo.png ├── .editorconfig ├── Cargo.toml ├── justfile ├── LICENSE ├── Cargo.lock ├── .github └── workflows │ └── rust.yml ├── doc ├── additions.md └── native_functions.md ├── README.md └── tools └── generate_ast.py /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .mypy_cache 3 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod expr; 2 | pub mod stmt; 3 | -------------------------------------------------------------------------------- /src/sample_program.lux: -------------------------------------------------------------------------------- 1 | 2 | print "hiya world ✨"; 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/scanner/mod.rs: -------------------------------------------------------------------------------- 1 | mod helpers; 2 | mod scan; 3 | mod types; 4 | 5 | pub use scan::scan; 6 | -------------------------------------------------------------------------------- /assets/luxya_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franeklubi/luxya/HEAD/assets/luxya_logo.png -------------------------------------------------------------------------------- /src/resolver/mod.rs: -------------------------------------------------------------------------------- 1 | mod env; 2 | mod expressions; 3 | mod helpers; 4 | mod resolve; 5 | mod statements; 6 | 7 | pub use resolve::resolve; 8 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | mod expressions; 2 | mod helpers; 3 | mod parse; 4 | mod statements; 5 | 6 | pub mod types; 7 | 8 | pub use parse::parse; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.rs] 4 | charset = utf-8 5 | end_of_line = lf 6 | 7 | max_line_length = 80 8 | 9 | indent_style = tab 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /src/runner/mod.rs: -------------------------------------------------------------------------------- 1 | mod errors; 2 | mod helpers; 3 | mod run; 4 | mod types; 5 | 6 | pub use errors::DescribableError; 7 | pub use run::{file as run_file, repl as run_repl, source as run_source}; 8 | pub use types::RunError; 9 | -------------------------------------------------------------------------------- /src/interpreter/mod.rs: -------------------------------------------------------------------------------- 1 | mod interpret; 2 | mod pn; 3 | 4 | pub mod env; 5 | pub mod expressions; 6 | pub mod helpers; 7 | pub mod native_functions; 8 | pub mod statements; 9 | pub mod types; 10 | 11 | pub use interpret::interpret; 12 | -------------------------------------------------------------------------------- /src/rustfmt.toml: -------------------------------------------------------------------------------- 1 | binop_separator = "Front" 2 | blank_lines_upper_bound = 2 3 | brace_style = "SameLineWhere" 4 | max_width = 80 5 | hard_tabs = true 6 | imports_layout = "HorizontalVertical" 7 | newline_style = "Unix" 8 | normalize_comments = true 9 | condense_wildcard_suffixes = true 10 | format_strings = true 11 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | clippy::all, 3 | clippy::pedantic, 4 | clippy::nursery, 5 | clippy::unnecessary_wraps, 6 | clippy::semicolon_if_nothing_returned 7 | )] 8 | 9 | mod ast; 10 | mod env; 11 | mod interpreter; 12 | mod parser; 13 | mod resolver; 14 | mod runner; 15 | mod scanner; 16 | mod token; 17 | 18 | pub use runner::*; 19 | pub use runner::{run_file, run_repl}; 20 | -------------------------------------------------------------------------------- /src/resolver/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::expr::Expr; 2 | 3 | use std::cell::Cell; 4 | 5 | 6 | // A shorthand way to extract identifier expr 7 | pub fn assume_resolvable_expr(expr: &Expr) -> &Cell { 8 | match expr { 9 | Expr::Identifier(i) => &i.env_distance, 10 | Expr::Assignment(a) => &a.env_distance, 11 | Expr::This(t) => &t.env_distance, 12 | Expr::Super(s) => &s.env_distance, 13 | _ => unreachable!( 14 | "Couldn't extract resolvable expr. This shouldn't happen" 15 | ), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/scanner/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{runner::DescribableError, token::Location}; 2 | 3 | use std::{iter, str}; 4 | 5 | 6 | pub struct ScanError { 7 | pub offset: usize, 8 | pub message: String, 9 | } 10 | 11 | impl DescribableError for ScanError { 12 | fn location(&self) -> Location { 13 | Location { 14 | byte_offset: self.offset, 15 | byte_length: 1, 16 | } 17 | } 18 | 19 | fn description(&self) -> &str { 20 | &self.message 21 | } 22 | } 23 | 24 | pub type ScannerIter<'a, 'b> = &'a mut iter::Peekable>; 25 | 26 | pub struct ConsumptionResult { 27 | pub last_offset: usize, 28 | pub hit_eof: bool, 29 | } 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "luxya" 3 | version = "1.1.2" 4 | authors = ["franek "] 5 | edition = "2018" 6 | description = "Programming language with a tree-walking interpreter" 7 | license-file = "LICENSE" 8 | repository = "https://github.com/franeklubi/luxya/" 9 | keywords = [ 10 | "programming-language", 11 | "interpreter", 12 | "programming-languages", 13 | "interpreted-programming-language", 14 | "tree-walk-interpreter", 15 | ] 16 | categories = ["compilers"] 17 | 18 | [dependencies] 19 | exitcode = "1.1.2" 20 | termcolor = "1.1.2" 21 | 22 | [profile.release] 23 | lto = "fat" 24 | opt-level = 3 25 | panic = "abort" 26 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{env, process}; 2 | 3 | 4 | fn main() { 5 | let args: Vec = env::args().collect(); 6 | 7 | if args.len() > 1 { 8 | for arg in args.iter().skip(1) { 9 | match luxya::run_file(arg) { 10 | Err(luxya::RunError::Io(err)) => { 11 | println!("{}", err); 12 | process::exit(exitcode::IOERR); 13 | } 14 | Err(luxya::RunError::Exec) => { 15 | println!("Errors while executing {}", arg); 16 | process::exit(exitcode::DATAERR); 17 | } 18 | _ => (), 19 | } 20 | } 21 | } else if let Err(err) = luxya::run_repl() { 22 | println!("{}", err); 23 | process::exit(exitcode::OSERR); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/runner/types.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | io::{self}, 4 | }; 5 | 6 | 7 | pub enum RunError { 8 | Io(io::Error), 9 | Exec, 10 | } 11 | 12 | impl From for RunError { 13 | fn from(e: io::Error) -> Self { 14 | Self::Io(e) 15 | } 16 | } 17 | 18 | pub struct Line { 19 | pub number: u32, 20 | pub offset: usize, 21 | pub content: String, 22 | } 23 | 24 | impl Line { 25 | pub fn prefix(&self) -> String { 26 | format!("[{}:{}]", self.number, self.offset) 27 | } 28 | } 29 | 30 | impl fmt::Display for Line { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | write!(f, "{}: {}", self.prefix(), self.content) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/runner/helpers.rs: -------------------------------------------------------------------------------- 1 | use super::types::Line; 2 | 3 | 4 | pub fn get_line(source: &str, byte_offset: usize) -> Line { 5 | let mut line_start_offset = 0; 6 | let mut line_end_offset = source.len(); 7 | let mut lines = 1; 8 | 9 | // getting the start and end of the line 10 | for (i, c) in source.as_bytes().iter().enumerate() { 11 | if *c == b'\n' { 12 | if i < byte_offset { 13 | line_start_offset = i + 1; 14 | } else { 15 | line_end_offset = i; 16 | break; 17 | } 18 | 19 | lines += 1; 20 | } 21 | } 22 | 23 | Line { 24 | content: source[line_start_offset..line_end_offset].to_string(), 25 | number: lines, 26 | offset: (byte_offset - line_start_offset + 1) 27 | .min(line_end_offset - line_start_offset), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | 2 | sample_program_path := './src/sample_program.lux' 3 | generate_ast_path := './tools/generate_ast.py' 4 | 5 | main: 6 | just clippy 7 | clear 8 | cargo build --release --verbose 9 | 10 | release: 11 | cargo build --release --verbose 12 | 13 | clippy: 14 | clear 15 | cargo clippy --all-features -- -D warnings 16 | 17 | run: 18 | just clippy 19 | clear 20 | cargo run 21 | 22 | sample: 23 | echo siema 24 | just clippy 25 | clear 26 | cargo run -- {{sample_program_path}} 27 | 28 | watch: 29 | cargo watch -x "fmt; just run" 30 | 31 | watch_sample: 32 | cargo watch -x "fmt; just sample" 33 | 34 | generate_ast: 35 | python3 {{generate_ast_path}} 36 | 37 | generate_ast_check: 38 | mypy --check-untyped-defs {{generate_ast_path}} && just generate_ast 39 | 40 | watch_generate_ast: 41 | echo {{generate_ast_path}} | entr -c just generate_ast_check 42 | -------------------------------------------------------------------------------- /src/ast/stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::expr::Expr; 2 | use crate::token::Token; 3 | 4 | pub struct ForValue { 5 | pub condition: Option, 6 | pub body: Box, 7 | pub closer: Option>, 8 | } 9 | 10 | pub struct IfValue { 11 | pub condition: Expr, 12 | pub then: Option>, 13 | pub otherwise: Option>, 14 | } 15 | 16 | pub struct DeclarationValue { 17 | pub name: Token, 18 | pub initializer: Option, 19 | pub mutable: bool, 20 | } 21 | 22 | pub struct ClassValue { 23 | pub name: Token, 24 | pub methods: Vec, 25 | pub superclass: Option, 26 | } 27 | 28 | pub struct ReturnValue { 29 | pub keyword: Token, 30 | pub expression: Option, 31 | } 32 | 33 | pub struct ExpressionValue { 34 | pub expression: Expr, 35 | } 36 | 37 | pub struct BlockValue { 38 | pub statements: Vec, 39 | } 40 | 41 | pub struct ContinueValue { 42 | pub keyword: Token, 43 | } 44 | 45 | pub struct PrintValue { 46 | pub expression: Expr, 47 | } 48 | 49 | pub struct BreakValue { 50 | pub keyword: Token, 51 | } 52 | 53 | pub enum Stmt { 54 | For(ForValue), 55 | If(IfValue), 56 | Declaration(DeclarationValue), 57 | Class(ClassValue), 58 | Return(ReturnValue), 59 | Expression(ExpressionValue), 60 | Block(BlockValue), 61 | Continue(ContinueValue), 62 | Print(PrintValue), 63 | Break(BreakValue), 64 | } 65 | -------------------------------------------------------------------------------- /src/env.rs: -------------------------------------------------------------------------------- 1 | use crate::{interpreter::types::RuntimeError, token::Token}; 2 | 3 | use std::collections::HashMap; 4 | 5 | 6 | #[derive(Clone)] 7 | pub struct DeclaredValue { 8 | pub mutable: bool, 9 | pub value: V, 10 | } 11 | 12 | pub struct EnvironmentBase { 13 | pub enclosing: Option, 14 | pub scope: HashMap, 15 | } 16 | 17 | impl EnvironmentBase { 18 | pub fn new(enclosing: Option) -> Self { 19 | Self { 20 | enclosing, 21 | scope: HashMap::new(), 22 | } 23 | } 24 | } 25 | 26 | pub trait EnvironmentWrapper { 27 | fn new() -> Self; 28 | 29 | fn fork(&self) -> Self; 30 | 31 | fn read( 32 | &self, 33 | steps: u32, 34 | identifier: &Token, 35 | ) -> Result, RuntimeError>; 36 | 37 | fn declare( 38 | &self, 39 | name: String, 40 | value: DeclaredValue, 41 | ) -> Option>; 42 | 43 | fn assign( 44 | &self, 45 | steps: u32, 46 | identifier: &Token, 47 | value: V, 48 | ) -> Result; 49 | } 50 | 51 | #[macro_export] 52 | macro_rules! unwrap_scope { 53 | ($wie:expr) => {{ 54 | &$wie.0.borrow().scope 55 | }}; 56 | } 57 | 58 | #[macro_export] 59 | macro_rules! unwrap_scope_mut { 60 | ($wie:expr) => {{ 61 | &mut $wie.0.borrow_mut().scope 62 | }}; 63 | } 64 | 65 | #[macro_export] 66 | macro_rules! unwrap_enclosing { 67 | ($wie:expr) => {{ 68 | &$wie.0.borrow().enclosing 69 | }}; 70 | } 71 | -------------------------------------------------------------------------------- /src/parser/types.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::expr::Expr, 3 | runner::DescribableError, 4 | token::{Location, Token}, 5 | }; 6 | 7 | use std::{iter, rc::Rc, vec}; 8 | 9 | pub type ParserIter<'a> = &'a mut iter::Peekable>; 10 | 11 | pub struct ParseError { 12 | pub token: Option, 13 | pub message: String, 14 | } 15 | 16 | impl DescribableError for ParseError { 17 | fn location(&self) -> Location { 18 | self.token.as_ref().map_or( 19 | Location { 20 | byte_offset: usize::MAX, 21 | byte_length: 1, 22 | }, 23 | |token| token.location, 24 | ) 25 | } 26 | 27 | fn description(&self) -> &str { 28 | &self.message 29 | } 30 | } 31 | 32 | impl Expr { 33 | pub const fn human_type(&self) -> &str { 34 | match self { 35 | Expr::Assignment(_) => "an assignment", 36 | Expr::Binary(_) => "a binary expression", 37 | Expr::Grouping(_) => "a grouping", 38 | Expr::Literal(_) => "a literal", 39 | Expr::Unary(_) => "a unary expression", 40 | Expr::Identifier(_) => "an identifier", 41 | Expr::Call(_) => "a function/method call", 42 | Expr::Function(_) => "a function/method declaration", 43 | Expr::Get(_) => "property getter", 44 | Expr::Set(_) => "property setter", 45 | Expr::This(_) => "a this expression", 46 | Expr::Super(_) => "a super expression", 47 | Expr::Object(_) => "an object definition", 48 | } 49 | } 50 | } 51 | 52 | pub struct Property { 53 | pub key: Rc, 54 | pub value: Expr, 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, franeklubi 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/interpreter/pn.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::expr::{Expr, LiteralValue}; 2 | 3 | 4 | #[allow(dead_code)] 5 | pub fn stringify_tree(expr: &Expr) -> String { 6 | match expr { 7 | Expr::Binary(v) => { 8 | pn_gen(&v.operator.token_type.to_string(), &[&v.left, &v.right]) 9 | } 10 | Expr::Unary(v) => { 11 | pn_gen(&v.operator.token_type.to_string(), &[&v.right]) 12 | } 13 | Expr::Grouping(v) => pn_gen("group", &[&v.expression]), 14 | Expr::Literal(v) => match v { 15 | LiteralValue::String(s) => format!("{:?}", s), 16 | LiteralValue::Number(n) => format!("{}", n), 17 | LiteralValue::True => "true".into(), 18 | LiteralValue::False => "false".into(), 19 | LiteralValue::Nil => "nil".into(), 20 | LiteralValue::List(_) => "[ ... ]".into(), 21 | LiteralValue::Char(c) => format!("{:?}", c), 22 | }, 23 | Expr::Identifier(v) => v.name.token_type.to_string(), 24 | Expr::Assignment(v) => pn_gen(&format!("= {}", v.name), &[&v.value]), 25 | Expr::Call(v) => pn_gen( 26 | &format!("call {}", stringify_tree(&v.calee)), 27 | v.arguments.iter().collect::>().as_slice(), 28 | ), 29 | // TODO: implement these XD 30 | Expr::Function(_v) => unimplemented!(), 31 | Expr::Get(_v) => unimplemented!(), 32 | Expr::Set(_v) => unimplemented!(), 33 | Expr::This(_v) => unimplemented!(), 34 | Expr::Super(_v) => unimplemented!(), 35 | Expr::Object(_v) => unimplemented!(), 36 | } 37 | } 38 | 39 | fn pn_gen(name: &str, exprs: &[&Expr]) -> String { 40 | let mut res = format!("({}", name); 41 | 42 | for expr in exprs.iter() { 43 | res += " "; 44 | res += &stringify_tree(expr); 45 | } 46 | 47 | res + ")" 48 | } 49 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "exitcode" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" 10 | 11 | [[package]] 12 | name = "luxya" 13 | version = "1.1.2" 14 | dependencies = [ 15 | "exitcode", 16 | "termcolor", 17 | ] 18 | 19 | [[package]] 20 | name = "termcolor" 21 | version = "1.1.2" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 24 | dependencies = [ 25 | "winapi-util", 26 | ] 27 | 28 | [[package]] 29 | name = "winapi" 30 | version = "0.3.9" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 33 | dependencies = [ 34 | "winapi-i686-pc-windows-gnu", 35 | "winapi-x86_64-pc-windows-gnu", 36 | ] 37 | 38 | [[package]] 39 | name = "winapi-i686-pc-windows-gnu" 40 | version = "0.4.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 43 | 44 | [[package]] 45 | name = "winapi-util" 46 | version = "0.1.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 49 | dependencies = [ 50 | "winapi", 51 | ] 52 | 53 | [[package]] 54 | name = "winapi-x86_64-pc-windows-gnu" 55 | version = "0.4.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 58 | -------------------------------------------------------------------------------- /src/runner/errors.rs: -------------------------------------------------------------------------------- 1 | use super::helpers::get_line; 2 | use crate::token::Location; 3 | 4 | use std::{fmt, io::Write}; 5 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 6 | 7 | 8 | pub trait DescribableError { 9 | fn location(&self) -> Location; 10 | fn description(&self) -> &str; 11 | } 12 | 13 | 14 | pub fn report(source: &str, category: &str, errors: &[T]) 15 | where 16 | T: DescribableError, 17 | { 18 | print!( 19 | "\n{} error{}:", 20 | category, 21 | if errors.len() > 1 { "s" } else { "" } 22 | ); 23 | 24 | errors.iter().for_each(|error| report_error(source, error)); 25 | 26 | println!(); 27 | } 28 | 29 | fn report_error(source: &str, error: &T) 30 | where 31 | T: DescribableError, 32 | { 33 | let location = error.location(); 34 | 35 | let line = get_line(source, location.byte_offset); 36 | let line_prefix = line.prefix(); 37 | 38 | let trimmed_content = line.content.trim_start(); 39 | 40 | let trimmed_offset = (line.offset + 1) 41 | - (line.content.as_bytes().len() - trimmed_content.as_bytes().len()); 42 | 43 | let trimmed_content = trimmed_content.trim_end(); 44 | 45 | // Line output 46 | println!( 47 | "\n\t{line_prefix}: {trimmed_content}", 48 | line_prefix = line_prefix, 49 | trimmed_content = trimmed_content 50 | ); 51 | 52 | // Setting terminal colours 53 | let mut stdout = StandardStream::stdout(ColorChoice::Always); 54 | 55 | let set_err = stdout.set_color( 56 | ColorSpec::new() 57 | .set_fg(Some(Color::Rgb(239, 41, 41))) 58 | .set_bold(true), 59 | ); 60 | handle_result(set_err); 61 | 62 | // Error output 63 | let write_err = writeln!( 64 | &mut stdout, 65 | "\t{offset}{marker} {description}", 66 | offset = " ".repeat(line_prefix.len() + trimmed_offset), 67 | marker = "^".repeat(location.byte_length), 68 | description = error.description() 69 | ); 70 | handle_result(write_err); 71 | 72 | let set_err = 73 | stdout.set_color(ColorSpec::new().set_fg(None).set_bold(false)); 74 | handle_result(set_err); 75 | } 76 | 77 | fn handle_result(res: Result) 78 | where 79 | ERR: fmt::Display, 80 | { 81 | if let Err(err) = res { 82 | println!("{}", err); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/scanner/helpers.rs: -------------------------------------------------------------------------------- 1 | use super::types::{ConsumptionResult, ScanError, ScannerIter}; 2 | use crate::token::TokenType; 3 | 4 | 5 | /// will consume chars while peek matches the predicate 6 | /// 7 | /// returns a struct with the last offset (in bytes) of where 8 | /// the next char would be 9 | /// (regardless of it being there or the iterator ending) 10 | /// 11 | /// sets `hit_eof` when the scanning has reached eof 12 | pub fn consume_while_peek( 13 | chars: ScannerIter, 14 | predicate: impl Fn(&char) -> bool, 15 | ) -> ConsumptionResult { 16 | let mut last_offset = 0; 17 | 18 | loop { 19 | break match chars.peek() { 20 | // peek matches the predicate, so we continue on 21 | Some((i, c)) if predicate(c) => { 22 | last_offset = i + c.len_utf8(); 23 | chars.next(); 24 | 25 | continue; 26 | } 27 | // char doesn't match the predicate, so we return the result 28 | Some((i, _)) => ConsumptionResult { 29 | last_offset: *i, 30 | hit_eof: false, 31 | }, 32 | // we hit the eof 33 | None => ConsumptionResult { 34 | last_offset, 35 | hit_eof: true, 36 | }, 37 | }; 38 | } 39 | } 40 | 41 | pub fn expect_char( 42 | chars: ScannerIter, 43 | after: char, 44 | offset: usize, 45 | override_message: Option<&str>, 46 | ) -> Result { 47 | if let Some(c) = chars.next() { 48 | Ok(c.1) 49 | } else if let Some(msg) = override_message { 50 | Err(ScanError { 51 | message: msg.to_owned(), 52 | offset, 53 | }) 54 | } else { 55 | Err(ScanError { 56 | message: format!("Expected char after `{}`", after), 57 | offset, 58 | }) 59 | } 60 | } 61 | 62 | pub fn tokenize_identifier(identifier: &str) -> TokenType { 63 | match identifier { 64 | "and" => TokenType::And, 65 | "class" => TokenType::Class, 66 | "else" => TokenType::Else, 67 | "false" => TokenType::False, 68 | "for" => TokenType::For, 69 | "fun" => TokenType::Fun, 70 | "if" => TokenType::If, 71 | "nil" => TokenType::Nil, 72 | "or" => TokenType::Or, 73 | "print" => TokenType::Print, 74 | "return" => TokenType::Return, 75 | "super" => TokenType::Super, 76 | "this" => TokenType::This, 77 | "true" => TokenType::True, 78 | "let" => TokenType::Let, 79 | "const" => TokenType::Const, 80 | "break" => TokenType::Break, 81 | "continue" => TokenType::Continue, 82 | "extends" => TokenType::Extends, 83 | _ => TokenType::Identifier(identifier.into()), 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ast/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::{ast::stmt::Stmt, parser::types::Property, token::Token}; 2 | use std::{cell::Cell, rc::Rc}; 3 | 4 | #[derive(Clone)] 5 | pub enum LiteralValue { 6 | List(Rc>), 7 | String(Rc), 8 | Number(f64), 9 | Char(char), 10 | True, 11 | False, 12 | Nil, 13 | } 14 | 15 | pub struct FunctionValue { 16 | pub keyword: Token, 17 | pub name: Option, 18 | pub params: Option>>, 19 | pub body: Option>>, 20 | } 21 | 22 | pub struct SetValue { 23 | pub setee: Box, 24 | pub key: GetAccessor, 25 | pub value: Box, 26 | pub blame: Token, 27 | } 28 | 29 | pub struct SuperValue { 30 | pub blame: Token, 31 | pub accessor: SuperAccessor, 32 | pub env_distance: Cell, 33 | } 34 | 35 | pub struct CallValue { 36 | pub calee: Box, 37 | pub closing_paren: Token, 38 | pub arguments: Vec, 39 | } 40 | 41 | pub struct AssignmentValue { 42 | pub name: Token, 43 | pub value: Box, 44 | pub env_distance: Cell, 45 | } 46 | 47 | pub struct BinaryValue { 48 | pub left: Box, 49 | pub operator: Token, 50 | pub right: Box, 51 | } 52 | 53 | pub struct GetValue { 54 | pub getee: Box, 55 | pub key: GetAccessor, 56 | pub blame: Token, 57 | } 58 | 59 | pub struct IdentifierValue { 60 | pub name: Token, 61 | pub env_distance: Cell, 62 | } 63 | 64 | pub struct ObjectValue { 65 | pub blame: Token, 66 | pub properties: Vec, 67 | } 68 | 69 | pub struct ThisValue { 70 | pub blame: Token, 71 | pub env_distance: Cell, 72 | } 73 | 74 | pub struct UnaryValue { 75 | pub operator: Token, 76 | pub right: Box, 77 | } 78 | 79 | pub struct GroupingValue { 80 | pub expression: Box, 81 | } 82 | 83 | pub enum Expr { 84 | Function(FunctionValue), 85 | Set(SetValue), 86 | Super(SuperValue), 87 | Call(CallValue), 88 | Assignment(AssignmentValue), 89 | Binary(BinaryValue), 90 | Get(GetValue), 91 | Identifier(IdentifierValue), 92 | Object(ObjectValue), 93 | This(ThisValue), 94 | Unary(UnaryValue), 95 | Grouping(GroupingValue), 96 | Literal(LiteralValue), 97 | } 98 | 99 | pub enum GetAccessor { 100 | DotName(Rc), 101 | DotEval(Box), 102 | SubscriptionNumber(f64), 103 | SubscriptionEval(Box), 104 | } 105 | 106 | pub enum SuperAccessor { 107 | Method(Token), 108 | Call(Vec), 109 | } 110 | -------------------------------------------------------------------------------- /src/runner/run.rs: -------------------------------------------------------------------------------- 1 | use super::{errors, types::RunError}; 2 | use crate::{interpreter, parser, resolver, scanner}; 3 | 4 | use std::{ 5 | fs, 6 | io::{self, Read, Write}, 7 | }; 8 | 9 | 10 | /// # Errors 11 | /// 12 | /// Will return `RunError::Io` if `path` 13 | /// does not exist or the user does not have permission to read it. 14 | // 15 | /// Will return `RunError::Exec` if any execution errors occur. 16 | pub fn file(path: &str) -> Result<(), RunError> { 17 | let mut f = fs::File::open(path)?; 18 | 19 | let mut buffer = String::new(); 20 | f.read_to_string(&mut buffer)?; 21 | 22 | if source(&buffer) { 23 | return Err(RunError::Exec); 24 | }; 25 | 26 | Ok(()) 27 | } 28 | 29 | /// # Errors 30 | /// 31 | /// Will return `Err` if there are any errors during reading from command line. 32 | pub fn repl() -> Result<(), io::Error> { 33 | loop { 34 | print!(">>> "); 35 | io::stdout().flush()?; 36 | 37 | let mut buffer = String::new(); 38 | io::stdin().read_line(&mut buffer)?; 39 | 40 | if buffer.is_empty() { 41 | break; 42 | } 43 | 44 | // In REPL mode, we always add `;` at the end so that 45 | // the user doesn't have to 😇 46 | buffer += ";"; 47 | 48 | // TODO: merge envs when doing REPL 49 | if source(&buffer) { 50 | eprintln!("Errors occurred"); 51 | } 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | // TODO: maybe change the signature to return parsed tree of vars and functions 58 | // so that we can merge that with the last tree in the REPL mode - we want 59 | // things to be persistent dont we? (COMBAK: when implementing a better repl) 60 | /// Runs the provided source 61 | /// 62 | /// returned bool indicates if any error(s) occurred 63 | #[must_use] 64 | pub fn source(source: &str) -> bool { 65 | // Scanning 66 | let (tokens, errors) = scanner::scan(source); 67 | 68 | if !errors.is_empty() { 69 | errors::report(source, "Scan", &errors); 70 | 71 | return true; 72 | } 73 | 74 | // Parsing 75 | let (statements, errors) = parser::parse(tokens); 76 | 77 | if !errors.is_empty() { 78 | errors::report(source, "Parse", &errors); 79 | 80 | return true; 81 | } 82 | 83 | // Resolving 84 | if let Err(error) = resolver::resolve(&statements) { 85 | errors::report(source, "Resolve", &[error]); 86 | 87 | true 88 | 89 | // Interpreting 😇 90 | } else if let Err(error) = interpreter::interpret(&statements) { 91 | errors::report(source, "Runtime", &[error]); 92 | 93 | true 94 | } else { 95 | false 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/interpreter/env.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | helpers::assume_identifier, 3 | types::{InterpreterValue, RuntimeError}, 4 | }; 5 | use crate::{ 6 | env::{DeclaredValue, EnvironmentBase, EnvironmentWrapper}, 7 | token::Token, 8 | unwrap_scope_mut, 9 | }; 10 | 11 | use std::{cell::RefCell, rc::Rc}; 12 | 13 | 14 | #[derive(Clone)] 15 | pub struct InterpreterEnvironment( 16 | Rc< 17 | RefCell< 18 | EnvironmentBase< 19 | InterpreterEnvironment, 20 | DeclaredValue, 21 | >, 22 | >, 23 | >, 24 | ); 25 | 26 | impl PartialEq for InterpreterEnvironment { 27 | fn eq(&self, other: &Self) -> bool { 28 | Rc::ptr_eq(&self.0, &other.0) 29 | } 30 | } 31 | 32 | impl EnvironmentWrapper for InterpreterEnvironment { 33 | fn new() -> Self { 34 | Self(Rc::new(RefCell::new(EnvironmentBase::new(None)))) 35 | } 36 | 37 | fn fork(&self) -> Self { 38 | Self(Rc::new(RefCell::new(EnvironmentBase::new(Some( 39 | self.clone(), 40 | ))))) 41 | } 42 | 43 | fn read( 44 | &self, 45 | steps: u32, 46 | identifier: &Token, 47 | ) -> Result, RuntimeError> { 48 | let mut scope: Rc>> = self.0.clone(); 49 | 50 | for _ in 0..steps { 51 | let new_scope = { 52 | let borrowed = scope.borrow(); 53 | let enclosing = 54 | borrowed.enclosing.as_ref().expect("Enclosing environment"); 55 | 56 | enclosing.0.clone() 57 | }; 58 | 59 | scope = new_scope; 60 | } 61 | 62 | let name = assume_identifier(identifier); 63 | 64 | let borrowed = scope.borrow(); 65 | 66 | Ok(borrowed.scope.get(name).expect("Identifier").clone()) 67 | } 68 | 69 | fn declare( 70 | &self, 71 | name: String, 72 | value: DeclaredValue, 73 | ) -> Option> { 74 | unwrap_scope_mut!(self).insert(name, value) 75 | } 76 | 77 | fn assign( 78 | &self, 79 | steps: u32, 80 | identifier: &Token, 81 | value: InterpreterValue, 82 | ) -> Result { 83 | let mut scope: Rc>> = self.0.clone(); 84 | 85 | for _ in 0..steps { 86 | let new_scope = { 87 | let borrowed = scope.borrow_mut(); 88 | let enclosing = borrowed 89 | .enclosing 90 | .as_ref() 91 | .expect("The enclosing environment to exist"); 92 | 93 | enclosing.0.clone() 94 | }; 95 | 96 | scope = new_scope; 97 | } 98 | 99 | let name = assume_identifier(identifier); 100 | 101 | let mut borrowed = scope.borrow_mut(); 102 | 103 | let entry = borrowed 104 | .scope 105 | .get_mut(name) 106 | .expect("The identifier to be there"); 107 | 108 | if entry.mutable { 109 | *entry = DeclaredValue { 110 | mutable: entry.mutable, 111 | value: value.clone(), 112 | }; 113 | 114 | Ok(value) 115 | } else { 116 | Err(RuntimeError { 117 | message: format!( 118 | "Cannot reassign a const {} `{}`", 119 | entry.value.human_type(), 120 | name 121 | ), 122 | token: identifier.clone(), 123 | }) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | clippy: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Cache 20 | uses: actions/cache@v2.1.5 21 | with: 22 | path: | 23 | ~/.cargo/bin/ 24 | ~/.cargo/registry/index/ 25 | ~/.cargo/registry/cache/ 26 | ~/.cargo/git/db/ 27 | target/ 28 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 29 | 30 | - uses: actions-rs/toolchain@v1.0.6 31 | with: 32 | toolchain: nightly 33 | 34 | - name: Run clippy 35 | env: 36 | TERM: "xterm" 37 | run: | 38 | rustup component add clippy --toolchain nightly-x86_64-unknown-linux-gnu 39 | cargo clippy --all-features -- -D warnings 40 | 41 | 42 | GNU_Linux_build: 43 | needs: clippy 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v2 48 | 49 | - name: Cache 50 | uses: actions/cache@v2.1.5 51 | with: 52 | path: | 53 | ~/.cargo/bin/ 54 | ~/.cargo/registry/index/ 55 | ~/.cargo/registry/cache/ 56 | ~/.cargo/git/db/ 57 | target/ 58 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 59 | 60 | - uses: actions-rs/toolchain@v1 61 | with: 62 | toolchain: nightly 63 | 64 | - name: Release build 65 | run: cargo build --release --verbose 66 | 67 | - name: Strip binary 68 | run: strip --strip-all target/release/luxya 69 | 70 | - name: Upload a Build Artifact 71 | uses: actions/upload-artifact@v2.2.3 72 | with: 73 | # Artifact name 74 | name: luxya_GNU_Linux 75 | # A file, directory or wildcard pattern that describes what to upload 76 | path: target/release/luxya 77 | 78 | Windows_build: 79 | needs: clippy 80 | runs-on: windows-latest 81 | 82 | steps: 83 | - uses: actions/checkout@v2 84 | 85 | - name: Cache 86 | uses: actions/cache@v2.1.5 87 | with: 88 | path: | 89 | ~/.cargo/bin/ 90 | ~/.cargo/registry/index/ 91 | ~/.cargo/registry/cache/ 92 | ~/.cargo/git/db/ 93 | target/ 94 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 95 | 96 | - uses: actions-rs/toolchain@v1 97 | with: 98 | toolchain: nightly 99 | 100 | - name: Release build 101 | run: cargo build --release --verbose 102 | 103 | - name: Upload a Build Artifact 104 | uses: actions/upload-artifact@v2.2.3 105 | with: 106 | # Artifact name 107 | name: luxya_Windows 108 | # A file, directory or wildcard pattern that describes what to upload 109 | path: target/release/luxya.exe 110 | -------------------------------------------------------------------------------- /src/parser/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | expressions::expression, 3 | helpers::{expect_semicolon, synchronize}, 4 | statements::{ 5 | block_statement, 6 | break_statement, 7 | class_statement, 8 | continue_statement, 9 | expression_statement, 10 | for_statement, 11 | if_statement, 12 | print_statement, 13 | return_statement, 14 | }, 15 | types::{ParseError, ParserIter}, 16 | }; 17 | use crate::{ 18 | ast::stmt::{DeclarationValue, Stmt}, 19 | expect, 20 | match_then_consume, 21 | token::{Token, TokenType}, 22 | }; 23 | 24 | 25 | pub fn parse(tokens: Vec) -> (Vec, Vec) { 26 | let tokens: ParserIter = &mut tokens.into_iter().peekable(); 27 | 28 | let mut statements = Vec::new(); 29 | let mut errors = Vec::new(); 30 | 31 | while tokens.peek().is_some() { 32 | match declaration(tokens) { 33 | Ok(Some(s)) => statements.push(s), 34 | 35 | Err(s) => { 36 | synchronize(tokens); 37 | 38 | errors.push(s); 39 | } 40 | _ => (), 41 | } 42 | } 43 | 44 | (statements, errors) 45 | } 46 | 47 | pub fn declaration(tokens: ParserIter) -> Result, ParseError> { 48 | if let Some(token) = 49 | match_then_consume!(tokens, TokenType::Let | TokenType::Const) 50 | { 51 | let name = 52 | expect!(tokens, TokenType::Identifier(_), "Expected identifier",)?; 53 | 54 | let initializer = 55 | if match_then_consume!(tokens, TokenType::Equal).is_some() { 56 | Some(expression(tokens)?) 57 | } else { 58 | None 59 | }; 60 | 61 | expect_semicolon(tokens)?; 62 | 63 | Ok(Some(Stmt::Declaration(DeclarationValue { 64 | name, 65 | initializer, 66 | mutable: TokenType::Let == token.token_type, 67 | }))) 68 | } else { 69 | statement(tokens) 70 | } 71 | } 72 | 73 | /// Statement can not fail and produce None for a statement, because it wouldn't 74 | /// be significant (e.g. lone `;`) 75 | pub fn statement(tokens: ParserIter) -> Result, ParseError> { 76 | let consumed_token = match_then_consume!( 77 | tokens, 78 | TokenType::If 79 | | TokenType::For 80 | | TokenType::Print 81 | | TokenType::Break 82 | | TokenType::Class 83 | | TokenType::Return 84 | | TokenType::Continue 85 | | TokenType::LeftBrace 86 | | TokenType::Semicolon 87 | ); 88 | 89 | let token_type = consumed_token.as_ref().map(|ct| &ct.token_type); 90 | 91 | match token_type { 92 | Some(TokenType::If) => if_statement(tokens), 93 | Some(TokenType::For) => for_statement(tokens), 94 | Some(TokenType::Print) => print_statement(tokens), 95 | Some(TokenType::Class) => class_statement(tokens), 96 | Some(TokenType::LeftBrace) => block_statement(tokens), 97 | Some(TokenType::Break) => unsafe { 98 | break_statement(tokens, consumed_token.unwrap_unchecked()) 99 | }, 100 | Some(TokenType::Return) => unsafe { 101 | return_statement(tokens, consumed_token.unwrap_unchecked()) 102 | }, 103 | Some(TokenType::Continue) => unsafe { 104 | continue_statement(tokens, consumed_token.unwrap_unchecked()) 105 | }, 106 | 107 | // We allow trails of semicolons and treat them as empty statements 108 | Some(TokenType::Semicolon) => Ok(None), 109 | 110 | _ => expression_statement(tokens), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/parser/helpers.rs: -------------------------------------------------------------------------------- 1 | use super::types::{ParseError, ParserIter}; 2 | use crate::{ 3 | expect_one, 4 | token::{Token, TokenType}, 5 | }; 6 | 7 | 8 | #[inline] 9 | pub fn expect_semicolon(tokens: ParserIter) -> Result { 10 | expect_one!(tokens, TokenType::Semicolon) 11 | } 12 | 13 | // call only if the token that the parser choked on is not ';' 14 | // TODO: add sync context (like parsing methods or global sync etc.) 15 | pub fn synchronize(tokens: ParserIter) { 16 | while let Some(token) = tokens.peek() { 17 | match token.token_type { 18 | TokenType::Class 19 | | TokenType::Fun 20 | | TokenType::Let 21 | | TokenType::Const 22 | | TokenType::For 23 | | TokenType::If 24 | | TokenType::Print 25 | | TokenType::Return => { 26 | break; 27 | } 28 | 29 | _ => { 30 | if TokenType::Semicolon == tokens.next().unwrap().token_type { 31 | break; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | #[macro_export] 39 | macro_rules! peek_matches { 40 | ($tokens:expr, $( $expected:pat )|+ $(,)?) => { 41 | matches!( 42 | $tokens.peek(), 43 | Some(Token { token_type: $( $expected )|+, .. }), 44 | ) 45 | }; 46 | } 47 | 48 | #[macro_export] 49 | macro_rules! match_then_consume { 50 | ($tokens:expr, $( $expected:pat )|+ $(,)?) => { 51 | match $tokens.peek().map(|t| &t.token_type) { 52 | Some($( $expected )|+) => $tokens.next(), 53 | _ => None, 54 | } 55 | }; 56 | } 57 | 58 | #[macro_export] 59 | macro_rules! expect { 60 | ($tokens:ident, $( $expected:pat )|+, $message:expr $(,)?) => { 61 | match_then_consume!( 62 | $tokens, 63 | $( $expected )|+, 64 | ).ok_or_else(|| ParseError { 65 | message: $message.into(), 66 | token: $tokens.peek().cloned(), 67 | }) 68 | }; 69 | } 70 | 71 | #[macro_export] 72 | macro_rules! expect_one { 73 | ($tokens:ident, $expected:expr $(,)?) => { 74 | // we can't use peek_matches, because we need to generate an error 75 | // based on the expected token type 76 | match $tokens.peek() { 77 | Some(Token { token_type, .. }) if token_type == &$expected => { 78 | // i mean we just matched that, unsafe is pretty justified 79 | Ok(unsafe { $tokens.next().unwrap_unchecked() }) 80 | } 81 | _ => Err(ParseError { 82 | message: format!("Expected `{}`", $expected.human_type()), 83 | token: $tokens.peek().cloned(), 84 | }), 85 | } 86 | }; 87 | } 88 | 89 | #[macro_export] 90 | macro_rules! build_binary_expr { 91 | ($tokens:ident, $lower_precedence:expr, $( $expected:pat )|+ $(,)?) => {{ 92 | let mut expr = $lower_precedence($tokens)?; 93 | 94 | while let Some(operator) = match_then_consume!( 95 | $tokens, 96 | $( $expected )|+, 97 | ) { 98 | let right = $lower_precedence($tokens)?; 99 | 100 | expr = Expr::Binary(BinaryValue { 101 | left: Box::new(expr), 102 | operator, 103 | right: Box::new(right), 104 | }); 105 | } 106 | 107 | Ok(expr) 108 | }}; 109 | } 110 | 111 | #[macro_export] 112 | macro_rules! match_then_consume_stmt { 113 | ($tokens:ident, $( $starts_with:pat )|+, $message:expr $(,)?) => { 114 | if peek_matches!($tokens, $( $starts_with )|+) { 115 | statement($tokens) 116 | } else { 117 | Err(ParseError { 118 | message: $message.into(), 119 | token: $tokens.peek().cloned(), 120 | }) 121 | } 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /doc/additions.md: -------------------------------------------------------------------------------- 1 | # Additions 2 | 3 | 4 | --- 5 | * [Lists](#lists) 6 | * [Chars](#chars) 7 | * [Square bracket accessor](#square-bracket-accessor) 8 | * [Object notation](#object-notation) 9 | * [Grouping accessor](#grouping-accessor) 10 | --- 11 | 12 | 13 | ## Lists 14 | List declaration: 15 | ```lux 16 | const list = [1, 2, 3, "hi!"]; 17 | 18 | print list; // [ 1, 2, 3, hi! ] 19 | ``` 20 | 21 | 22 | ## Chars 23 | Basically, a char type. 24 | 25 | Syntax: 26 | ```lux 27 | const a_char = 'a'; 28 | ``` 29 | 30 | It's sole purpose is to aid in string modification: 31 | ```lux 32 | const letters = expand("luxya 🤢"); 33 | 34 | letters[6] = '✨'; 35 | 36 | print from_chars(letters); // luxya ✨ 37 | ``` 38 | Read more about [`expand` here](./native_functions.md#expand), [here](#square-bracket-accessor), and about [`from_chars` here](./native_functions.md#from_chars). 39 | 40 | 41 | ## Square bracket accessor 42 | Accessing lists: 43 | ```lux 44 | const list = ['a', 'b', 'c', 'd']; 45 | 46 | print list[2]; // c 47 | ``` 48 | 49 | Accessing strings: 50 | ```lux 51 | const name = "Ola"; 52 | 53 | print name[0]; // O 54 | ``` 55 | 56 | You may think that accessing strings is not reliable, coz what if we have a multibyte `char`? Let's try it. 57 | ```lux 58 | const emoji = "🥺"; 59 | 60 | print emoji[0]; // â 61 | ``` 62 | `â` is not what we expected, but that's the desired behaviour. Luxya deals with accessing chars by using their byte representation, so that you can expect an O(1) operation on every access. 63 | 64 | But what if we want to access the emoji as a char? We use the [`expand`](./native_functions.md#expand) function! 65 | ```lux 66 | const name = "luxya ✨"; 67 | 68 | const expanded = expand(name); 69 | 70 | print expanded; // [ l, u, x, y, a, , ✨ ] 71 | 72 | print expanded[6]; // ✨ 73 | ``` 74 | Neat! What we get is a list of chars that we can now safely and reliably access! 75 | 76 | 77 | ## Object notation 78 | Luxya supports a full object notation! 79 | 80 | Declare an empty object: 81 | ```lux 82 | const object = {}; 83 | ``` 84 | 85 | Declare objects with key value pairs: 86 | ```lux 87 | const some_value = 1; 88 | 89 | const object = { 90 | name: "luxya ✨", 91 | "arbitrary key": "value!", // you can use strings as keys! 92 | some_value, // shorthand key-value notation 93 | }; 94 | 95 | print object; // { name: luxya ✨, arbitrary key: value!, some_value: 1 } 96 | ``` 97 | As you can see, the last key doesn't have a value. That's because luxya supports a shorthand key-value notation - it will automatically bind the value if the value of the same name is declared in scope! 98 | 99 | 100 | ## Grouping accessor 101 | By using the grouping accessor, you can get and set properties on objects with any arbitrary key, that you couldn't do with just a dot notation (`object.key`). 102 | ```lux 103 | const object = {}; 104 | 105 | const key = "1 2 3 4 key with spaces"; 106 | 107 | object.(key) = "value!"; 108 | 109 | print object.(key); // value! 110 | ``` 111 | 112 | By combining this accessor with [`expand`](./native_functions.md#expand), [`has`](./native_functions.md#has) and [`unset`](./native_functions.md#unset) functions, you can use objects as hashmaps. 113 | 114 | ```lux 115 | const map = {}; 116 | 117 | map.("arbitrary key") = "value!"; 118 | 119 | print has(map, "arbitrary key"); // true 120 | 121 | unset(map, "arbitrary key"); 122 | print has(map, "arbitrary key"); // false 123 | ``` 124 | -------------------------------------------------------------------------------- /src/resolver/statements.rs: -------------------------------------------------------------------------------- 1 | use super::{env::ResolverEnvironment, resolve}; 2 | use crate::{ 3 | ast::{ 4 | expr::Expr, 5 | stmt::{ClassValue, DeclarationValue, ForValue, IfValue, PrintValue}, 6 | }, 7 | env::{DeclaredValue, EnvironmentWrapper}, 8 | interpreter::{ 9 | helpers::assume_identifier, 10 | types::{InterpreterValue, RuntimeError, StmtResult}, 11 | }, 12 | }; 13 | 14 | 15 | #[inline] 16 | pub fn print_statement( 17 | v: &PrintValue, 18 | env: &ResolverEnvironment, 19 | ) -> Result, RuntimeError> { 20 | resolve::expression(&v.expression, env)?; 21 | 22 | Ok(StmtResult::Noop) 23 | } 24 | 25 | pub fn declaration_statement( 26 | v: &DeclarationValue, 27 | env: &ResolverEnvironment, 28 | ) -> Result, RuntimeError> { 29 | if let Some(i) = &v.initializer { 30 | resolve::expression(i, env)?; 31 | } 32 | 33 | env.declare( 34 | assume_identifier(&v.name).to_owned(), 35 | DeclaredValue { 36 | mutable: v.mutable, 37 | value: InterpreterValue::Nil, 38 | }, 39 | ); 40 | 41 | Ok(StmtResult::Noop) 42 | } 43 | 44 | pub fn if_statement( 45 | v: &IfValue, 46 | env: &ResolverEnvironment, 47 | ) -> Result, RuntimeError> { 48 | resolve::expression(&v.condition, env)?; 49 | 50 | if let Some(then) = &v.then { 51 | resolve::statement(then, env)?; 52 | } 53 | 54 | if let Some(otherwise) = &v.otherwise { 55 | resolve::statement(otherwise, env)?; 56 | } 57 | 58 | Ok(StmtResult::Noop) 59 | } 60 | 61 | pub fn for_statement( 62 | v: &ForValue, 63 | env: &ResolverEnvironment, 64 | ) -> Result, RuntimeError> { 65 | resolve::statement(&v.body, env)?; 66 | 67 | if let Some(condition) = &v.condition { 68 | resolve::expression(condition, env)?; 69 | } 70 | 71 | if let Some(closer) = &v.closer { 72 | resolve::statement(closer, env)?; 73 | } 74 | 75 | Ok(StmtResult::Noop) 76 | } 77 | 78 | pub fn class_statement( 79 | v: &ClassValue, 80 | env: &ResolverEnvironment, 81 | ) -> Result, RuntimeError> { 82 | let iden = assume_identifier(&v.name); 83 | 84 | env.declare( 85 | iden.to_owned(), 86 | DeclaredValue { 87 | mutable: false, 88 | value: InterpreterValue::Nil, 89 | }, 90 | ); 91 | 92 | let superclass_env = if let Some(expr) = &v.superclass { 93 | let superclass = if let Expr::Identifier(s) = expr { 94 | s 95 | } else { 96 | unreachable!("Superclass should be an identifier expression") 97 | }; 98 | 99 | let super_iden = assume_identifier(&superclass.name); 100 | 101 | if super_iden == iden { 102 | return Err(RuntimeError { 103 | message: "Class cannot inherit from itself".into(), 104 | token: superclass.name.clone(), 105 | }); 106 | } 107 | 108 | resolve::expression(expr, env)?; 109 | 110 | let superclass_env = env.fork(); 111 | 112 | superclass_env.declare( 113 | "super".to_owned(), 114 | DeclaredValue { 115 | mutable: false, 116 | value: InterpreterValue::Nil, 117 | }, 118 | ); 119 | 120 | Some(superclass_env) 121 | } else { 122 | None 123 | }; 124 | 125 | let class_env = superclass_env.map_or_else(|| env.fork(), |dce| dce.fork()); 126 | 127 | class_env.declare( 128 | "this".to_owned(), 129 | DeclaredValue { 130 | mutable: false, 131 | value: InterpreterValue::Nil, 132 | }, 133 | ); 134 | 135 | for method in &v.methods { 136 | // expression wires the method to function_expression 137 | resolve::expression(method, &class_env)?; 138 | } 139 | 140 | Ok(StmtResult::Noop) 141 | } 142 | -------------------------------------------------------------------------------- /src/interpreter/interpret.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::InterpreterEnvironment, 3 | expressions::{ 4 | assignment_expression, 5 | binary_experssion, 6 | call_expression, 7 | function_expression, 8 | get_expression, 9 | identifier_expression, 10 | literal_expression, 11 | object_expression, 12 | set_expression, 13 | super_expression, 14 | this_expression, 15 | unary_expression, 16 | }, 17 | native_functions, 18 | statements::{ 19 | block_statement, 20 | break_statement, 21 | class_statement, 22 | continue_statement, 23 | declaration_statement, 24 | expression_statement, 25 | for_statement, 26 | if_statement, 27 | print_statement, 28 | return_statement, 29 | }, 30 | types::{InterpreterValue, RuntimeError, StmtResult}, 31 | }; 32 | use crate::{ 33 | ast::{expr::Expr, stmt::Stmt}, 34 | env::EnvironmentWrapper, 35 | }; 36 | 37 | 38 | pub fn interpret(statements: &[Stmt]) -> Result<(), RuntimeError> { 39 | let env = InterpreterEnvironment::new(); 40 | 41 | native_functions::declare(&env); 42 | 43 | match eval_statements(statements, &env)? { 44 | StmtResult::Noop => Ok(()), 45 | StmtResult::Break(token) => Err(RuntimeError { 46 | message: "Cannot use `break` outside of a loop".into(), 47 | token, 48 | }), 49 | StmtResult::Continue(token) => Err(RuntimeError { 50 | message: "Cannot use `continue` outside of a loop".into(), 51 | token, 52 | }), 53 | StmtResult::Return { keyword, .. } => Err(RuntimeError { 54 | message: "Cannot use `return` outside of a function".into(), 55 | token: keyword, 56 | }), 57 | } 58 | } 59 | 60 | pub fn eval_statements( 61 | statements: &[Stmt], 62 | env: &InterpreterEnvironment, 63 | ) -> Result, RuntimeError> { 64 | for stmt in statements { 65 | let res = eval_statement(stmt, env)?; 66 | 67 | if !matches!(res, StmtResult::Noop) { 68 | return Ok(res); 69 | } 70 | } 71 | 72 | Ok(StmtResult::Noop) 73 | } 74 | 75 | fn eval_statement( 76 | stmt: &Stmt, 77 | env: &InterpreterEnvironment, 78 | ) -> Result, RuntimeError> { 79 | match stmt { 80 | Stmt::Expression(v) => expression_statement(eval_expression, v, env), 81 | Stmt::Print(v) => print_statement(eval_expression, v, env), 82 | Stmt::Declaration(v) => declaration_statement(eval_expression, v, env), 83 | Stmt::Block(v) => block_statement(eval_statements, v, env), 84 | Stmt::If(v) => if_statement(eval_expression, eval_statement, v, env), 85 | Stmt::For(v) => for_statement(eval_expression, eval_statement, v, env), 86 | Stmt::Return(v) => return_statement(eval_expression, v, env), 87 | Stmt::Break(v) => Ok(break_statement(v)), 88 | Stmt::Continue(v) => Ok(continue_statement(v)), 89 | Stmt::Class(v) => class_statement(v, env), 90 | } 91 | } 92 | 93 | pub fn eval_expression( 94 | expr: &Expr, 95 | env: &InterpreterEnvironment, 96 | ) -> Result { 97 | match expr { 98 | Expr::Literal(v) => literal_expression(v, env), 99 | Expr::Grouping(v) => eval_expression(&v.expression, env), 100 | Expr::Unary(v) => unary_expression(v, env), 101 | Expr::Binary(v) => binary_experssion(v, env), 102 | Expr::Identifier(v) => identifier_expression(v, env), 103 | Expr::Assignment(v) => assignment_expression(eval_expression, v, env), 104 | Expr::Call(v) => call_expression(v, env), 105 | Expr::Function(v) => Ok(function_expression(v, env)), 106 | Expr::Get(v) => get_expression(v, env), 107 | Expr::Set(v) => set_expression(v, env), 108 | Expr::This(v) => this_expression(v, env), 109 | Expr::Super(v) => super_expression(v, env), 110 | Expr::Object(v) => object_expression(v, env), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/resolver/resolve.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::ResolverEnvironment, 3 | expressions::{ 4 | assignment_expression, 5 | binary_expression, 6 | call_expression, 7 | function_expression, 8 | get_expression, 9 | identifier_expression, 10 | object_expression, 11 | set_expression, 12 | super_expression, 13 | this_expression, 14 | }, 15 | statements::{ 16 | class_statement, 17 | declaration_statement, 18 | for_statement, 19 | if_statement, 20 | print_statement, 21 | }, 22 | }; 23 | use crate::{ 24 | ast::{expr::Expr, stmt::Stmt}, 25 | env::EnvironmentWrapper, 26 | interpreter::{ 27 | native_functions::NATIVE_FUNCTION_NAMES, 28 | statements as interpreter_stmts, 29 | types::{InterpreterValue, RuntimeError, StmtResult}, 30 | }, 31 | unwrap_scope_mut, 32 | }; 33 | 34 | 35 | pub fn resolve(stmts: &[Stmt]) -> Result<(), RuntimeError> { 36 | let scope = ResolverEnvironment::new(); 37 | 38 | // Declaring native functions 39 | { 40 | let scope_map = unwrap_scope_mut!(scope); 41 | 42 | for k in &NATIVE_FUNCTION_NAMES { 43 | scope_map.insert((*k).to_string(), true); 44 | } 45 | } 46 | 47 | match statements(stmts, &scope)? { 48 | StmtResult::Noop => Ok(()), 49 | StmtResult::Break(token) => Err(RuntimeError { 50 | message: "Cannot use `break` outside of a loop".into(), 51 | token, 52 | }), 53 | StmtResult::Continue(token) => Err(RuntimeError { 54 | message: "Cannot use `continue` outside of a loop".into(), 55 | token, 56 | }), 57 | StmtResult::Return { keyword, .. } => Err(RuntimeError { 58 | message: "Cannot use `return` outside of a function".into(), 59 | token: keyword, 60 | }), 61 | } 62 | } 63 | 64 | pub fn statements( 65 | statements: &[Stmt], 66 | env: &ResolverEnvironment, 67 | ) -> Result, RuntimeError> { 68 | for stmt in statements { 69 | let res = statement(stmt, env)?; 70 | 71 | if !matches!(res, StmtResult::Noop) { 72 | return Ok(res); 73 | } 74 | } 75 | 76 | Ok(StmtResult::Noop) 77 | } 78 | 79 | pub fn statement( 80 | stmt: &Stmt, 81 | env: &ResolverEnvironment, 82 | ) -> Result, RuntimeError> { 83 | match stmt { 84 | Stmt::Block(v) => { 85 | interpreter_stmts::block_statement(statements, v, env) 86 | } 87 | Stmt::Expression(v) => { 88 | interpreter_stmts::expression_statement(expression, v, env) 89 | } 90 | Stmt::Break(v) => Ok(interpreter_stmts::break_statement(v)), 91 | Stmt::Continue(v) => Ok(interpreter_stmts::continue_statement(v)), 92 | Stmt::Return(v) => { 93 | interpreter_stmts::return_statement(expression, v, env) 94 | } 95 | 96 | // custom resolver statement handlers 97 | Stmt::Print(v) => print_statement(v, env), 98 | Stmt::Declaration(v) => declaration_statement(v, env), 99 | Stmt::If(v) => if_statement(v, env), 100 | Stmt::For(v) => for_statement(v, env), 101 | Stmt::Class(v) => class_statement(v, env), 102 | } 103 | } 104 | 105 | pub fn expression( 106 | expr: &Expr, 107 | env: &ResolverEnvironment, 108 | ) -> Result { 109 | match expr { 110 | Expr::Grouping(v) => expression(&v.expression, env), 111 | Expr::Literal(_v) => Ok(InterpreterValue::Nil), 112 | 113 | // custom resolver expression handlers 114 | Expr::Identifier(v) => identifier_expression(expr, v, env), 115 | Expr::Assignment(v) => assignment_expression(expr, v, env), 116 | Expr::Unary(v) => expression(&v.right, env), 117 | Expr::Function(v) => function_expression(v, env), 118 | Expr::Super(v) => super_expression(expr, v, env), 119 | Expr::This(v) => this_expression(expr, v, env), 120 | Expr::Binary(v) => binary_expression(v, env), 121 | Expr::Object(v) => object_expression(v, env), 122 | Expr::Call(v) => call_expression(v, env), 123 | Expr::Get(v) => get_expression(v, env), 124 | Expr::Set(v) => set_expression(v, env), 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, mem, rc::Rc}; 2 | 3 | 4 | #[derive(Clone, Debug)] 5 | #[allow(clippy::module_name_repetitions)] 6 | pub enum TokenType { 7 | // Single-character tokens 8 | LeftParen, 9 | RightParen, 10 | LeftBrace, 11 | RightBrace, 12 | Comma, 13 | Dot, 14 | Minus, 15 | Plus, 16 | Semicolon, 17 | Colon, 18 | Slash, 19 | Star, 20 | LeftSquareBracket, 21 | RightSquareBracket, 22 | Modulo, 23 | 24 | // One Or Two Character Tokens 25 | Bang, 26 | BangEqual, 27 | Equal, 28 | EqualEqual, 29 | Greater, 30 | GreaterEqual, 31 | Less, 32 | LessEqual, 33 | 34 | // Literals 35 | Identifier(Rc), 36 | String(Rc), 37 | Number(f64), 38 | Char(char), 39 | 40 | // Keywords 41 | And, 42 | Class, 43 | Else, 44 | False, 45 | Fun, 46 | For, 47 | If, 48 | Nil, 49 | Or, 50 | Print, 51 | Return, 52 | Super, 53 | This, 54 | True, 55 | Let, 56 | Const, 57 | Break, 58 | Continue, 59 | Extends, 60 | } 61 | 62 | impl TokenType { 63 | pub fn repr(&self) -> String { 64 | match self { 65 | TokenType::String(s) => format!("{:?}", s), 66 | TokenType::Identifier(i) => format!("{}", i), 67 | TokenType::Number(n) => format!("{:?}", n), 68 | TokenType::Char(c) => format!("{:?}", c), 69 | _ => self.human_type().to_owned(), 70 | } 71 | } 72 | 73 | pub const fn human_type(&self) -> &str { 74 | match self { 75 | TokenType::String(_) => "string", 76 | TokenType::Identifier(_) => "identifier", 77 | TokenType::Number(_) => "number", 78 | TokenType::Char(_) => "char", 79 | TokenType::LeftParen => "(", 80 | TokenType::RightParen => ")", 81 | TokenType::LeftBrace => "{", 82 | TokenType::RightBrace => "}", 83 | TokenType::LeftSquareBracket => "[", 84 | TokenType::RightSquareBracket => "]", 85 | TokenType::Comma => ",", 86 | TokenType::Dot => ".", 87 | TokenType::Minus => "-", 88 | TokenType::Plus => "+", 89 | TokenType::Colon => ":", 90 | TokenType::Semicolon => ";", 91 | TokenType::Slash => "/", 92 | TokenType::Star => "*", 93 | TokenType::Bang => "!", 94 | TokenType::BangEqual => "!=", 95 | TokenType::Equal => "=", 96 | TokenType::EqualEqual => "==", 97 | TokenType::Greater => ">", 98 | TokenType::GreaterEqual => ">=", 99 | TokenType::Less => "<", 100 | TokenType::LessEqual => "<=", 101 | TokenType::And => "and", 102 | TokenType::Class => "class", 103 | TokenType::Else => "else", 104 | TokenType::False => "false", 105 | TokenType::Fun => "fun", 106 | TokenType::For => "for", 107 | TokenType::If => "if", 108 | TokenType::Nil => "nil", 109 | TokenType::Or => "or", 110 | TokenType::Print => "print", 111 | TokenType::Return => "return", 112 | TokenType::Super => "super", 113 | TokenType::This => "this", 114 | TokenType::True => "true", 115 | TokenType::Let => "let", 116 | TokenType::Const => "const", 117 | TokenType::Break => "break", 118 | TokenType::Continue => "continue", 119 | TokenType::Extends => "extends", 120 | TokenType::Modulo => "%", 121 | } 122 | } 123 | } 124 | 125 | #[derive(Clone)] 126 | pub struct Token { 127 | pub location: Location, 128 | pub token_type: TokenType, 129 | } 130 | 131 | #[derive(Clone, Copy)] 132 | pub struct Location { 133 | pub byte_offset: usize, 134 | pub byte_length: usize, 135 | } 136 | 137 | impl fmt::Display for Token { 138 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 139 | write!( 140 | f, 141 | "{}\tfrom: {};\tto: {};", 142 | self.token_type, 143 | self.location.byte_offset, 144 | self.location.byte_offset + self.location.byte_length, 145 | ) 146 | } 147 | } 148 | 149 | impl fmt::Display for TokenType { 150 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 151 | write!(f, "{}", self.repr()) 152 | } 153 | } 154 | 155 | impl PartialEq for TokenType { 156 | fn eq(&self, other: &Self) -> bool { 157 | mem::discriminant(self) == mem::discriminant(other) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /doc/native_functions.md: -------------------------------------------------------------------------------- 1 | # Native functions 2 | 3 | 4 | Explanation of syntax used in this section: 5 | - a function which accepts argument of any type and returns a string: `name(any) -> string` 6 | - a function which accepts an object and a string, and returns a boolean: `name(object, string) -> boolean` 7 | - a function which accepts number or string for an argument and doesn't return anything: `name(number | string)` 8 | - a function which returns a list of chars: `name() -> list[char]` 9 | 10 | 11 | --- 12 | * [str](#str) 13 | * [typeof](#typeof) 14 | * [number](#number) 15 | * [len](#len) 16 | * [expand](#expand) 17 | * [push](#push) 18 | * [extend](#extend) 19 | * [from_chars](#from_chars) 20 | * [deep_copy](#deep_copy) 21 | * [is_nan](#is_nan) 22 | * [floor](#floor) 23 | * [ceil](#ceil) 24 | * [has](#has) 25 | * [unset](#unset) 26 | * [read](#read) 27 | --- 28 | 29 | 30 | ## str 31 | Signature: `str(any) -> string` 32 | 33 | You can use `str` to convert any value to a string. 34 | 35 | 36 | ## number 37 | Signature: `number(number | string | char) -> number` 38 | 39 | `number` converts a value to a number. The resulting number can be `NaN`. To test if a number is `NaN`, use [`is_nan`](#is-nan) 40 | 41 | ```lux 42 | print number("1234"); // 1234 43 | print number('a'); // NaN 44 | ``` 45 | 46 | 47 | ## typeof 48 | Signature: `typeof(any) -> string` 49 | 50 | `typeof` returns a string representation of the first argument 51 | 52 | ```lux 53 | print typeof(true); // boolean 54 | print typeof("luxya ✨"); // string 55 | print typeof(2136); // number 56 | ``` 57 | 58 | 59 | ## len 60 | Signature: `len(string | list[any]) -> number` 61 | 62 | `len` returns the length of a string (in bytes) or a list 63 | 64 | 65 | ## expand 66 | Signature: `expand(string | object) -> list[char | string]` 67 | 68 | `expand` returns a list of chars in a string, or keys in an object 69 | 70 | ```lux 71 | const name = "luxya ✨"; 72 | 73 | print expand(name); // [ l, u, x, y, a, , ✨ ]; 74 | 75 | const object = { 76 | name, 77 | key: "value", 78 | }; 79 | 80 | print expand(object); // [ name, key ]; 81 | ``` 82 | 83 | You can find example usages of `expand` [here](./additions.md#chars) and [here](./additions.md#square-bracket-accessor). 84 | 85 | 86 | ## from_chars 87 | Signature: `from_chars(list[char]) -> string` 88 | 89 | `from_chars` accepts a list of chars and returns a string 90 | 91 | 92 | ## push 93 | Signature: `push(list[any], any) -> list[any]` 94 | 95 | `push` can be used to push any value to the end of a list 96 | 97 | 98 | ## extend 99 | Signature: `extend(list[any], list[any]) -> list[any]` 100 | 101 | `extend` concatenates two lists 102 | 103 | 104 | ## deep_copy 105 | Signature: `deep_copy(any) -> any` 106 | 107 | `deep_copy` performs a deep copy of an object passed 108 | 109 | 110 | ## is_nan 111 | Signature: `is_nan(number) -> any` 112 | 113 | `is_nan` tells you if a number is `NaN` 114 | 115 | 116 | ## floor 117 | Signature: `floor(number) -> number` 118 | 119 | `floor` floors a number 120 | 121 | 122 | ## ceil 123 | Signature: `ceil(number) -> number` 124 | 125 | `ceil` ceils a number, lol 126 | 127 | 128 | ## has 129 | Signature: `has(object | list | string, any) -> number` 130 | 131 | `has` tests if the first argument includes the second one 132 | 133 | In case of an object, it tests if the first argument contains a string representation of the second argument as a key. 134 | 135 | In case of a list, it tests if the list contains the second argument. 136 | 137 | In case of a string it tests if the first argument contains a string representation of the second argument as a substring. 138 | 139 | 140 | ## unset 141 | Signature: `unset(object, string) -> any` 142 | 143 | `unset` removes an entry from an object under a provided key and returns the removed value. If there wasn't any value under the provided key, it returns `nil`. 144 | 145 | 146 | ## read 147 | Signature: `read(string | nil) -> string` 148 | 149 | `read` prints the provided string (or prints nothing if `nil` is provided) and waits for the user input, returning it 150 | -------------------------------------------------------------------------------- /src/resolver/env.rs: -------------------------------------------------------------------------------- 1 | use super::helpers::assume_resolvable_expr; 2 | use crate::{ 3 | ast::expr::Expr, 4 | env::{DeclaredValue, EnvironmentBase, EnvironmentWrapper}, 5 | interpreter::{ 6 | helpers::assume_identifier, 7 | types::{InterpreterValue, RuntimeError}, 8 | }, 9 | token::Token, 10 | unwrap_enclosing, 11 | unwrap_scope, 12 | unwrap_scope_mut, 13 | }; 14 | 15 | use std::{cell::RefCell, rc::Rc}; 16 | 17 | 18 | // Everything we need to create resolved map will have to be inside this env 19 | #[derive(Clone)] 20 | pub struct ResolverEnvironment( 21 | // true if variable, false if const 22 | pub Rc>>, 23 | ); 24 | 25 | // The InterpreterValue in this implementation tells us basically nothing, as 26 | // we won't be resolving the true values of our nodes. 27 | // 28 | // It's just there to satisfy EnvironmentWrapper and a couple of statement 29 | // functions in interpreter/statements. 30 | // 31 | // I'll always supply Nil here 32 | impl EnvironmentWrapper for ResolverEnvironment { 33 | fn new() -> Self { 34 | Self(Rc::new(RefCell::new(EnvironmentBase::new(None)))) 35 | } 36 | 37 | fn fork(&self) -> Self { 38 | Self(Rc::new(RefCell::new(EnvironmentBase::new(Some( 39 | self.clone(), 40 | ))))) 41 | } 42 | 43 | fn read( 44 | &self, 45 | steps: u32, 46 | identifier: &Token, 47 | ) -> Result, RuntimeError> { 48 | let mut scope: Rc>> = self.0.clone(); 49 | 50 | for _ in 0..steps { 51 | let new_scope = { 52 | let borrowed = scope.borrow(); 53 | let enclosing = borrowed 54 | .enclosing 55 | .as_ref() 56 | .expect("The enclosing environment to exist"); 57 | 58 | enclosing.0.clone() 59 | }; 60 | 61 | scope = new_scope; 62 | } 63 | 64 | let name = assume_identifier(identifier); 65 | 66 | let borrowed = scope.borrow(); 67 | 68 | Ok(DeclaredValue { 69 | mutable: *borrowed 70 | .scope 71 | .get(name) 72 | .expect("The identifier to be there"), 73 | value: InterpreterValue::Nil, 74 | }) 75 | } 76 | 77 | fn declare( 78 | &self, 79 | name: String, 80 | value: DeclaredValue, 81 | ) -> Option> { 82 | unwrap_scope_mut!(self).insert(name, value.mutable); 83 | 84 | None 85 | } 86 | 87 | // checks if the target is mutable 88 | fn assign( 89 | &self, 90 | steps: u32, 91 | identifier: &Token, 92 | _value: InterpreterValue, 93 | ) -> Result { 94 | let entry = self.read(steps, identifier)?; 95 | 96 | if entry.mutable { 97 | Ok(InterpreterValue::Nil) 98 | } else { 99 | let name = assume_identifier(identifier); 100 | 101 | Err(RuntimeError { 102 | message: format!("Cannot reassign a const `{}`", name), 103 | token: identifier.clone(), 104 | }) 105 | } 106 | } 107 | } 108 | 109 | impl ResolverEnvironment { 110 | pub fn exists(&self, name: &str) -> bool { 111 | unwrap_scope!(self).get(name).is_some() 112 | || unwrap_enclosing!(self) 113 | .as_ref() 114 | .map_or(false, |enclosing| enclosing.exists(name)) 115 | } 116 | 117 | pub fn resolve_nest_level( 118 | &self, 119 | resolvable_node: &Expr, 120 | resolvable_token: &Token, 121 | ) -> Result<(), RuntimeError> { 122 | self.resolve_nest_level_worker(0, resolvable_node, resolvable_token) 123 | } 124 | 125 | fn resolve_nest_level_worker( 126 | &self, 127 | curr_distance: u32, 128 | resolvable_node: &Expr, 129 | resolvable_token: &Token, 130 | ) -> Result<(), RuntimeError> { 131 | let name = assume_identifier(resolvable_token); 132 | 133 | if let Some(_dv) = unwrap_scope!(self).get(name) { 134 | let env_distance = assume_resolvable_expr(resolvable_node); 135 | 136 | env_distance.set(curr_distance); 137 | 138 | Ok(()) 139 | } else if let Some(enclosing) = unwrap_enclosing!(self) { 140 | enclosing.resolve_nest_level_worker( 141 | curr_distance + 1, 142 | resolvable_node, 143 | resolvable_token, 144 | )?; 145 | 146 | Ok(()) 147 | } else { 148 | Err(RuntimeError { 149 | token: resolvable_token.clone(), 150 | message: format!("Identifier `{}` not defined", name), 151 | }) 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luxya ✨ 2 | 3 | Luxya is a [Lox](https://github.com/munificent/craftinginterpreters)-based programming language with a tree-walking interpreter *written in Rust©™*. 4 | 5 | ![luxya logo](./assets/luxya_logo.png) 6 | 7 | To download precompiled binaries for GNU/Linux and Windows visit the [releases](https://github.com/franeklubi/luxya/releases) page! 8 | 9 | 10 | --- 11 | * [Lox-luxya differences](#lox-luxya-differences) 12 | * [Additions](#additions) 13 | * [Syntax](#syntax-differences) 14 | * [Backend](#backend-differences) 15 | * [Native functions](#native-functions) 16 | * [Usage](#usage) 17 | * [Examples](#examples) 18 | * [Compilation and development](#compilation-and-development) 19 | --- 20 | 21 | 22 | ## Lox-luxya differences 23 | ### [Additions](./doc/additions.md): 24 | - **lists!**; [read more](./doc/additions.md#lists) 25 | - square bracket accessor (`[expression]`); [read more](./doc/additions.md#square-bracket-accessor) 26 | - grouping accessor (`.(expression)`); [read more](./doc/additions.md#grouping-accessor) 27 | - full object notation; [read more](./doc/additions.md#objects) 28 | - chars; [read more](./doc/additions.md#chars) 29 | - the modulo (`%`) operator 30 | 31 | ### Syntax differences: 32 | - function declarations are expressions, rather than statements, so you can create anonymous (but not strictly) functions you want to use in-place: `function_name(10, a, fun () { print "callback" })` 33 | - introduced `let` instead of `var`, and `const` for immutable declarations 34 | - `if`'s, `else`'s, and `for`'s body has to be a block 35 | - `if`s condition doesn't need to be a grouping (basically you can do: `if true { ... }`) 36 | - although there is no type coercion, any value that's not strictly `true` will be treated as `not true` when placed in `if`'s or `for`'s condition 37 | - `for`'s initialization consists of three, **not** grouped fields (e.g.: `for let i = 0; i < 10; i = i + 1 { ... }`) 38 | - there are no `while` loops, but you can achieve the same behaviour with `for` (`while true { ... }` is the same as `for ; true; { ... }`) 39 | - you can state an infinite loop by omitting every field: `for ;; {}` 40 | - `init` (Lox's constructor method) is named `constructor` 41 | - you cannot call an instance's `constructor` directly (`constructor`s are only callable by using `Classname()` or `super()`) 42 | - to call a superclass's constructor you need to call the `super` keyword, as you would a function 43 | - inheritance is done with the `extends` keyword, replacing the `<` syntax 44 | - chars, which you can read more about [here](./doc/additions.md#chars) 45 | 46 | ### Backend differences: 47 | - numbers are `IEEE 754-2008` compliant (rust's f64 underneath) 48 | - no type coercion, no truthy nor falsy values 49 | - no visitor pattern 50 | - reference counting because there's no garbage collector to leverage 51 | - shadowing of named values is permitted 52 | 53 | ### Native functions: 54 | You can find full list of native functions [here](./doc/native_functions.md) 55 | 56 | 57 | ## Usage 58 | To run any script source run: 59 | ```sh 60 | $ luxya 61 | ``` 62 | 63 | To run in REPL mode (which is not yet finished): 64 | ```sh 65 | $ luxya 66 | ``` 67 | 68 | 69 | ## Examples 70 | ```lux 71 | for let i = 0; i < 10; i = i + 1 { 72 | print i; 73 | } 74 | ``` 75 | ```lux 76 | fun shout(text) { 77 | print text + "!"; 78 | } 79 | 80 | shout("hi"); 81 | ``` 82 | ```lux 83 | class Language { 84 | constructor(name) { 85 | this.name = name; 86 | } 87 | 88 | say_language() { 89 | print this.name; 90 | } 91 | } 92 | 93 | class Luxya extends Language { 94 | constructor() { 95 | super("luxya"); 96 | } 97 | 98 | say_language() { 99 | print "✨✨✨"; 100 | super.say_language(); 101 | print "✨✨✨"; 102 | } 103 | } 104 | 105 | const luxya = Luxya(); 106 | 107 | luxya.say_language(); 108 | ``` 109 | 110 | 111 | ## Compilation and development 112 | The source comprises two parts: 113 | - the `src/ast/*` generated by `tools/generate_ast.py` 114 | - the rest 115 | 116 | To get a release build you can use `just`: 117 | ```sh 118 | $ just 119 | ``` 120 | Or use cargo: 121 | ```sh 122 | $ cargo build --release --verbose 123 | ``` 124 | 125 | There are also a couple of useful dev commands like: 126 | ```sh 127 | # run REPL in watch mode 128 | $ just watch 129 | 130 | # run the `sample_program.lux` in watch mode 131 | # (script source overridable with sample_program_path in justfile) 132 | $ just watch_sample 133 | 134 | # run the `generate_ast.py` script with mypy checks in watch mode 135 | # (script source overridable with generate_ast_path in justfile) 136 | $ just watch_generate_ast 137 | ``` 138 | -------------------------------------------------------------------------------- /src/scanner/scan.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | helpers::{consume_while_peek, expect_char, tokenize_identifier}, 3 | types::{ScanError, ScannerIter}, 4 | }; 5 | use crate::token::{self, Location, TokenType}; 6 | 7 | 8 | pub fn scan(source: &str) -> (Vec, Vec) { 9 | let mut tokens = vec![]; 10 | let mut errors = vec![]; 11 | 12 | let mut chars = source.char_indices().peekable(); 13 | 14 | while let Some(_peek) = chars.peek() { 15 | // We should be at the beginning of the next lexeme 16 | match scan_token(&mut chars, source) { 17 | Ok(Some(token)) => tokens.push(token), 18 | Ok(None) => break, // iterator is exhausted 19 | Err(err) => errors.push(err), 20 | } 21 | } 22 | 23 | (tokens, errors) 24 | } 25 | 26 | /// Consumes the next token's chars 27 | #[allow(clippy::too_many_lines)] 28 | fn scan_token( 29 | chars: ScannerIter, 30 | source: &str, 31 | ) -> Result, ScanError> { 32 | // using while, because we want to skip unimportant chars, like whitespace 33 | while let Some((i, c)) = chars.next() { 34 | // We should be at the beginning of the next lexeme 35 | let mut token_len = c.len_utf8(); 36 | 37 | let token_type = match c { 38 | '(' => TokenType::LeftParen, 39 | ')' => TokenType::RightParen, 40 | '{' => TokenType::LeftBrace, 41 | '}' => TokenType::RightBrace, 42 | '[' => TokenType::LeftSquareBracket, 43 | ']' => TokenType::RightSquareBracket, 44 | ',' => TokenType::Comma, 45 | '.' => TokenType::Dot, 46 | '-' => TokenType::Minus, 47 | '+' => TokenType::Plus, 48 | '%' => TokenType::Modulo, 49 | ';' => TokenType::Semicolon, 50 | ':' => TokenType::Colon, 51 | '*' => TokenType::Star, 52 | '!' => { 53 | if let Some((_, '=')) = chars.peek() { 54 | chars.next(); 55 | 56 | token_len += 1; 57 | 58 | TokenType::BangEqual 59 | } else { 60 | TokenType::Bang 61 | } 62 | } 63 | '=' => { 64 | if let Some((_, '=')) = chars.peek() { 65 | chars.next(); 66 | 67 | token_len += 1; 68 | 69 | TokenType::EqualEqual 70 | } else { 71 | TokenType::Equal 72 | } 73 | } 74 | '<' => { 75 | if let Some((_, '=')) = chars.peek() { 76 | chars.next(); 77 | 78 | token_len += 1; 79 | 80 | TokenType::LessEqual 81 | } else { 82 | TokenType::Less 83 | } 84 | } 85 | '>' => { 86 | if let Some((_, '=')) = chars.peek() { 87 | chars.next(); 88 | 89 | token_len += 1; 90 | 91 | TokenType::GreaterEqual 92 | } else { 93 | TokenType::Greater 94 | } 95 | } 96 | '/' => { 97 | if let Some((_, '/')) = chars.peek() { 98 | // comment goes until the end of the line 99 | chars.take_while(|(_, c)| *c != '\n').for_each(drop); 100 | 101 | continue; 102 | } 103 | 104 | TokenType::Slash 105 | } 106 | '"' => { 107 | let res = consume_while_peek(chars, |c| *c != '"'); 108 | 109 | if res.hit_eof { 110 | return Err(ScanError { 111 | offset: i, 112 | message: "Unterminated string literal".to_owned(), 113 | }); 114 | } 115 | 116 | chars.next(); 117 | 118 | TokenType::String(source[i + 1..res.last_offset].into()) 119 | } 120 | '\'' => { 121 | let c = expect_char(chars, '\'', i, None)?; 122 | 123 | let closer = expect_char(chars, c, i, None)?; 124 | 125 | if '\'' == closer { 126 | TokenType::Char(c) 127 | } else { 128 | // get rid of remaining chars 129 | let _ = consume_while_peek(chars, |c| *c != '\''); 130 | chars.next(); 131 | 132 | return Err(ScanError { 133 | offset: i, 134 | message: "Expected closing ' after char".to_owned(), 135 | }); 136 | } 137 | } 138 | c if c.is_ascii_digit() => { 139 | let number_end = consume_while_peek(chars, |c| { 140 | *c == '.' || c.is_ascii_digit() 141 | }) 142 | .last_offset; 143 | 144 | let to_parse = &source[i..number_end]; 145 | 146 | token_len = number_end - i; 147 | 148 | TokenType::Number(to_parse.parse().expect("Parsed number")) 149 | } 150 | c if c.is_alphabetic() || c == '_' => { 151 | let identifier_end = consume_while_peek(chars, |peek| { 152 | peek.is_alphanumeric() || *peek == '_' 153 | }) 154 | .last_offset; 155 | 156 | token_len = identifier_end - i; 157 | 158 | tokenize_identifier(&source[i..identifier_end]) 159 | } 160 | c if c.is_whitespace() => { 161 | continue; 162 | } 163 | _ => { 164 | return Err(ScanError { 165 | offset: i, 166 | message: format!("Unexpected character {:?}", c), 167 | }); 168 | } 169 | }; 170 | 171 | return Ok(Some(token::Token { 172 | token_type, 173 | location: Location { 174 | byte_offset: i, 175 | byte_length: token_len, 176 | }, 177 | })); 178 | } 179 | 180 | Ok(None) 181 | } 182 | -------------------------------------------------------------------------------- /src/interpreter/types.rs: -------------------------------------------------------------------------------- 1 | use super::env::InterpreterEnvironment; 2 | use crate::{ 3 | ast::expr::FunctionValue, 4 | runner::DescribableError, 5 | token::{Location, Token}, 6 | }; 7 | 8 | use std::{cell::RefCell, collections::HashMap, fmt, rc::Rc}; 9 | 10 | 11 | const MAX_LIST_VALUES_PRINT: usize = 100; 12 | 13 | pub struct RuntimeError { 14 | pub message: String, 15 | pub token: Token, 16 | } 17 | 18 | impl DescribableError for RuntimeError { 19 | fn location(&self) -> Location { 20 | self.token.location 21 | } 22 | 23 | fn description(&self) -> &str { 24 | &self.message 25 | } 26 | } 27 | 28 | #[derive(Clone, PartialEq)] 29 | pub enum InterpreterValue { 30 | Function { 31 | fun: Rc, 32 | enclosing_env: InterpreterEnvironment, 33 | }, 34 | Instance { 35 | class: Option>, 36 | properties: Rc>>, 37 | }, 38 | Class { 39 | superclass: Option>, 40 | constructor: Option>, 41 | name: Rc, 42 | methods: Rc>, 43 | }, 44 | List(Rc>>), 45 | String(Rc), 46 | Number(f64), 47 | Char(char), 48 | True, 49 | False, 50 | Nil, 51 | } 52 | 53 | impl InterpreterValue { 54 | pub const fn human_type(&self) -> &str { 55 | match self { 56 | InterpreterValue::True | InterpreterValue::False => "boolean", 57 | InterpreterValue::Instance { .. } => "class instance", 58 | InterpreterValue::Function { .. } => "function", 59 | InterpreterValue::Class { .. } => "class", 60 | InterpreterValue::String(_) => "string", 61 | InterpreterValue::Number(_) => "number", 62 | InterpreterValue::List(_) => "list", 63 | InterpreterValue::Char(_) => "char", 64 | InterpreterValue::Nil => "nil", 65 | } 66 | } 67 | 68 | pub fn repr(&self, nested: bool) -> String { 69 | match self { 70 | InterpreterValue::List(l) => { 71 | let take_amount = 72 | if nested { 0 } else { MAX_LIST_VALUES_PRINT }; 73 | 74 | let l_borrow = l.borrow(); 75 | 76 | let mut list_repr = String::from("[ "); 77 | 78 | list_repr += &l_borrow 79 | .iter() 80 | .take(take_amount) 81 | .map(|v| v.repr(true)) 82 | .collect::>() 83 | .join(", "); 84 | 85 | let list_len = l_borrow.len(); 86 | 87 | if list_len > take_amount { 88 | list_repr += &format!( 89 | "{}...{} hidden ]", 90 | if nested { "" } else { ", " }, 91 | list_len - take_amount 92 | ); 93 | } else { 94 | list_repr += " ]"; 95 | } 96 | 97 | list_repr 98 | } 99 | InterpreterValue::Instance { class, properties } => { 100 | if let Some(class) = class { 101 | return format!("instance of {}", class); 102 | } 103 | 104 | let take_amount = 105 | if nested { 0 } else { MAX_LIST_VALUES_PRINT }; 106 | 107 | let p_borrow = properties.borrow(); 108 | 109 | let mut obj_repr = String::from("{ "); 110 | 111 | obj_repr += &p_borrow 112 | .iter() 113 | .take(take_amount) 114 | .map(|(k, v)| format!("\n\t{}: {},", k, v.repr(true))) 115 | .collect::(); 116 | 117 | let key_num = p_borrow.len(); 118 | 119 | if key_num > take_amount { 120 | obj_repr += &format!( 121 | "{}...{} hidden{}}}", 122 | if nested { "" } else { "\n\t" }, 123 | key_num - take_amount, 124 | if nested { " " } else { ",\n" }, 125 | ); 126 | } else { 127 | obj_repr += "\n}"; 128 | } 129 | 130 | obj_repr 131 | } 132 | InterpreterValue::Class { name, .. } => format!("class {}", name), 133 | InterpreterValue::Function { .. } => String::from("function"), 134 | InterpreterValue::String(s) => format!("{}", s), 135 | InterpreterValue::Number(n) => format!("{}", n), 136 | InterpreterValue::Char(c) => format!("{}", c), 137 | InterpreterValue::False => String::from("false"), 138 | InterpreterValue::True => String::from("true"), 139 | InterpreterValue::Nil => String::from("nil"), 140 | } 141 | } 142 | } 143 | 144 | pub enum StmtResult { 145 | Return { keyword: Token, value: T }, 146 | Break(Token), 147 | Continue(Token), 148 | Noop, 149 | } 150 | 151 | pub type NativeFunctionSignature = fn( 152 | &Token, 153 | &InterpreterEnvironment, 154 | &[InterpreterValue], 155 | ) 156 | -> Result; 157 | 158 | pub enum InterpreterFunction { 159 | Native { 160 | arity: usize, 161 | fun: NativeFunctionSignature, 162 | }, 163 | LoxDefined(FunctionValue), 164 | } 165 | 166 | impl PartialEq for InterpreterFunction { 167 | fn eq(&self, other: &Self) -> bool { 168 | match (&self, &other) { 169 | ( 170 | InterpreterFunction::LoxDefined(FunctionValue { 171 | body: Some(body1), 172 | .. 173 | }), 174 | InterpreterFunction::LoxDefined(FunctionValue { 175 | body: Some(body2), 176 | .. 177 | }), 178 | ) => Rc::ptr_eq(body1, body2), 179 | _ => false, 180 | } 181 | } 182 | } 183 | 184 | impl From for InterpreterValue { 185 | fn from(v: bool) -> Self { 186 | if v { 187 | Self::True 188 | } else { 189 | Self::False 190 | } 191 | } 192 | } 193 | 194 | impl fmt::Display for InterpreterValue { 195 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 196 | write!(f, "{}", self.repr(false)) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/interpreter/helpers.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::InterpreterEnvironment, 3 | interpret::eval_expression, 4 | types::{InterpreterFunction, InterpreterValue, RuntimeError, StmtResult}, 5 | }; 6 | use crate::{ 7 | ast::expr::{FunctionValue, GetAccessor}, 8 | env::{DeclaredValue, EnvironmentWrapper}, 9 | token::{Token, TokenType}, 10 | }; 11 | 12 | use std::{cell::RefMut, rc::Rc}; 13 | 14 | 15 | #[macro_export] 16 | macro_rules! try_exact_convert { 17 | ($from:expr, $from_t:ty, $to_t:ty) => {{ 18 | #[allow(clippy::as_conversions)] 19 | let converted = $from as $to_t; 20 | 21 | #[allow(clippy::float_cmp, clippy::as_conversions)] 22 | if converted as $from_t == $from { 23 | Ok(converted) 24 | } else { 25 | Err("Cannot convert") 26 | } 27 | }}; 28 | } 29 | 30 | 31 | // A shorthand way to extract identifier's name 32 | pub fn assume_identifier(t: &Token) -> &str { 33 | match &t.token_type { 34 | TokenType::Identifier(i) => i, 35 | TokenType::Super => "super", 36 | TokenType::This => "this", 37 | _ => unreachable!("Couldn't extract identifier."), 38 | } 39 | } 40 | 41 | pub fn guard_function( 42 | ibv: StmtResult, 43 | ) -> Result { 44 | match ibv { 45 | StmtResult::Break(token) => Err(RuntimeError { 46 | message: "Cannot use `break` outside of a loop".into(), 47 | token, 48 | }), 49 | StmtResult::Continue(token) => Err(RuntimeError { 50 | message: "Cannot use `continue` outside of a loop".into(), 51 | token, 52 | }), 53 | StmtResult::Return { value, .. } => Ok(value), 54 | StmtResult::Noop => Ok(InterpreterValue::Nil), 55 | } 56 | } 57 | 58 | #[inline] 59 | pub fn confirm_arity( 60 | target: usize, 61 | value: usize, 62 | blame: &Token, 63 | ) -> Result<(), RuntimeError> { 64 | if target == value { 65 | Ok(()) 66 | } else { 67 | Err(RuntimeError { 68 | message: format!( 69 | "{} arguments", 70 | if value > target { 71 | "Too many" 72 | } else { 73 | "Not enough" 74 | } 75 | ), 76 | token: blame.clone(), 77 | }) 78 | } 79 | } 80 | 81 | #[inline] 82 | pub fn map_arguments( 83 | parameters: &[Token], 84 | arguments: &[InterpreterValue], 85 | fun_env: &InterpreterEnvironment, 86 | ) { 87 | parameters.iter().zip(arguments).for_each(|(param, arg)| { 88 | let name = assume_identifier(param); 89 | 90 | fun_env.declare( 91 | name.to_string(), 92 | DeclaredValue { 93 | mutable: true, 94 | value: arg.clone(), 95 | }, 96 | ); 97 | }); 98 | } 99 | 100 | #[inline] 101 | pub fn construct_lox_defined_function( 102 | fv: &FunctionValue, 103 | env: &InterpreterEnvironment, 104 | ) -> InterpreterValue { 105 | InterpreterValue::Function { 106 | enclosing_env: env.clone(), 107 | fun: Rc::new(InterpreterFunction::LoxDefined(FunctionValue { 108 | body: fv.body.as_ref().map(Rc::clone), 109 | keyword: fv.keyword.clone(), 110 | name: fv.name.clone(), 111 | params: fv.params.as_ref().map(Rc::clone), 112 | })), 113 | } 114 | } 115 | 116 | pub fn bind_function( 117 | fun: &InterpreterValue, 118 | instance: InterpreterValue, 119 | ) -> InterpreterValue { 120 | let (fun, new_env) = 121 | if let InterpreterValue::Function { fun, enclosing_env } = fun { 122 | (fun.clone(), enclosing_env.fork()) 123 | } else { 124 | unreachable!("CHuju kurwa panie") 125 | }; 126 | 127 | new_env.declare( 128 | "this".into(), 129 | DeclaredValue { 130 | mutable: false, 131 | value: instance, 132 | }, 133 | ); 134 | 135 | InterpreterValue::Function { 136 | fun, 137 | enclosing_env: new_env, 138 | } 139 | } 140 | 141 | #[inline] 142 | pub fn unwrap_list<'a>( 143 | value: &'a InterpreterValue, 144 | blame: &Token, 145 | arg_index: usize, 146 | override_msg: Option, 147 | ) -> Result>, RuntimeError> { 148 | if let InterpreterValue::List(l) = &value { 149 | Ok(l.borrow_mut()) 150 | } else { 151 | Err(RuntimeError { 152 | message: override_msg.unwrap_or_else(|| { 153 | format!("Argument {} must be of type list", arg_index) 154 | }), 155 | token: blame.clone(), 156 | }) 157 | } 158 | } 159 | 160 | pub fn extract_subscription_index( 161 | accessor: &GetAccessor, 162 | blame: &Token, 163 | max_len: usize, 164 | env: &InterpreterEnvironment, 165 | ) -> Result { 166 | let extracted_n = match &accessor { 167 | GetAccessor::SubscriptionNumber(n) => Ok(*n), 168 | GetAccessor::SubscriptionEval(expr) => { 169 | let eval = eval_expression(expr, env)?; 170 | 171 | if let InterpreterValue::Number(n) = eval { 172 | Ok(n) 173 | } else { 174 | Err(RuntimeError { 175 | message: format!( 176 | "Cannot use {} for indexing", 177 | eval.human_type() 178 | ), 179 | token: blame.clone(), 180 | }) 181 | } 182 | } 183 | _ => unreachable!("Wrong accessor in subscription"), 184 | }?; 185 | 186 | let index = try_exact_convert!(extracted_n, f64, usize).map_err(|_| { 187 | RuntimeError { 188 | message: format!( 189 | "Cannot access element on erroneous index {}", 190 | extracted_n 191 | ), 192 | token: blame.clone(), 193 | } 194 | })?; 195 | 196 | if index >= max_len { 197 | Err(RuntimeError { 198 | message: format!("Index {} out of bounds", extracted_n), 199 | token: blame.clone(), 200 | }) 201 | } else { 202 | Ok(index) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/resolver/expressions.rs: -------------------------------------------------------------------------------- 1 | use super::{env::ResolverEnvironment, resolve}; 2 | use crate::{ 3 | ast::expr::{ 4 | AssignmentValue, 5 | BinaryValue, 6 | CallValue, 7 | Expr, 8 | FunctionValue, 9 | GetAccessor, 10 | GetValue, 11 | IdentifierValue, 12 | ObjectValue, 13 | SetValue, 14 | SuperAccessor, 15 | SuperValue, 16 | ThisValue, 17 | }, 18 | env::{DeclaredValue, EnvironmentWrapper}, 19 | interpreter::{ 20 | helpers::{assume_identifier, guard_function}, 21 | types::{InterpreterValue, RuntimeError}, 22 | }, 23 | }; 24 | 25 | 26 | #[inline] 27 | pub fn identifier_expression( 28 | expr: &Expr, 29 | v: &IdentifierValue, 30 | env: &ResolverEnvironment, 31 | ) -> Result { 32 | env.resolve_nest_level(expr, &v.name)?; 33 | 34 | Ok(InterpreterValue::Nil) 35 | } 36 | 37 | pub fn assignment_expression( 38 | expr: &Expr, 39 | v: &AssignmentValue, 40 | env: &ResolverEnvironment, 41 | ) -> Result { 42 | // that takes care on the variables on the right 43 | resolve::expression(&v.value, env)?; 44 | 45 | // and this one manages the ones on the left 😎 46 | env.resolve_nest_level(expr, &v.name)?; 47 | 48 | env.assign(v.env_distance.get(), &v.name, InterpreterValue::Nil)?; 49 | 50 | Ok(InterpreterValue::Nil) 51 | } 52 | 53 | pub fn function_expression( 54 | v: &FunctionValue, 55 | env: &ResolverEnvironment, 56 | ) -> Result { 57 | // if the function has a name attached, declare it in scope 58 | if let Some(name) = &v.name { 59 | let iden = assume_identifier(name); 60 | 61 | if env.exists(iden) { 62 | return Err(RuntimeError { 63 | message: format!( 64 | "A value with name `{}` is already in the scope", 65 | iden, 66 | ), 67 | token: name.clone(), 68 | }); 69 | } 70 | 71 | env.declare( 72 | iden.to_owned(), 73 | DeclaredValue { 74 | mutable: false, 75 | value: InterpreterValue::Nil, 76 | }, 77 | ); 78 | } 79 | 80 | let new_scope = env.fork(); 81 | 82 | // declaring dummy for each parameter 83 | if let Some(params) = &v.params { 84 | params.iter().for_each(|param| { 85 | let name = assume_identifier(param); 86 | 87 | new_scope.declare( 88 | name.to_owned(), 89 | DeclaredValue { 90 | mutable: true, 91 | value: InterpreterValue::Nil, 92 | }, 93 | ); 94 | }); 95 | } 96 | 97 | // evaluating function body 98 | if let Some(statements) = &v.body { 99 | let e = resolve::statements(statements, &new_scope)?; 100 | Ok(guard_function(e)?) 101 | } else { 102 | Ok(InterpreterValue::Nil) 103 | } 104 | } 105 | 106 | #[inline] 107 | pub fn binary_expression( 108 | v: &BinaryValue, 109 | env: &ResolverEnvironment, 110 | ) -> Result { 111 | resolve::expression(&v.left, env)?; 112 | resolve::expression(&v.right, env)?; 113 | 114 | Ok(InterpreterValue::Nil) 115 | } 116 | 117 | pub fn call_expression( 118 | v: &CallValue, 119 | env: &ResolverEnvironment, 120 | ) -> Result { 121 | resolve::expression(&v.calee, env)?; 122 | 123 | for arg in &v.arguments { 124 | resolve::expression(arg, env)?; 125 | } 126 | 127 | Ok(InterpreterValue::Nil) 128 | } 129 | 130 | #[inline] 131 | pub fn get_expression( 132 | v: &GetValue, 133 | env: &ResolverEnvironment, 134 | ) -> Result { 135 | resolve::expression(&v.getee, env)?; 136 | 137 | match &v.key { 138 | GetAccessor::DotEval(key) => { 139 | resolve::expression(key, env)?; 140 | } 141 | GetAccessor::SubscriptionEval(expr) => { 142 | resolve::expression(expr, env)?; 143 | } 144 | _ => (), 145 | } 146 | 147 | Ok(InterpreterValue::Nil) 148 | } 149 | 150 | pub fn set_expression( 151 | v: &SetValue, 152 | env: &ResolverEnvironment, 153 | ) -> Result { 154 | resolve::expression(&v.setee, env)?; 155 | resolve::expression(&v.value, env)?; 156 | 157 | if let GetAccessor::DotEval(key) = &v.key { 158 | resolve::expression(key, env)?; 159 | } 160 | 161 | Ok(InterpreterValue::Nil) 162 | } 163 | 164 | #[inline] 165 | pub fn this_expression( 166 | expr: &Expr, 167 | v: &ThisValue, 168 | env: &ResolverEnvironment, 169 | ) -> Result { 170 | env.resolve_nest_level(expr, &v.blame) 171 | .map_err(|err| RuntimeError { 172 | token: err.token, 173 | message: "Cannot call `this` outside of a method".into(), 174 | })?; 175 | 176 | Ok(InterpreterValue::Nil) 177 | } 178 | 179 | pub fn super_expression( 180 | expr: &Expr, 181 | v: &SuperValue, 182 | env: &ResolverEnvironment, 183 | ) -> Result { 184 | env.resolve_nest_level(expr, &v.blame) 185 | .map_err(|err| RuntimeError { 186 | token: err.token, 187 | message: "Cannot call `super` outside of a child class method" 188 | .into(), 189 | })?; 190 | 191 | if let SuperAccessor::Call(args) = &v.accessor { 192 | for arg in args.iter() { 193 | resolve::expression(arg, env)?; 194 | } 195 | } 196 | 197 | Ok(InterpreterValue::Nil) 198 | } 199 | 200 | #[inline] 201 | pub fn object_expression( 202 | v: &ObjectValue, 203 | env: &ResolverEnvironment, 204 | ) -> Result { 205 | for value in v.properties.iter().map(|p| &p.value) { 206 | resolve::expression(value, env)?; 207 | } 208 | 209 | Ok(InterpreterValue::Nil) 210 | } 211 | -------------------------------------------------------------------------------- /tools/generate_ast.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple, Optional 2 | 3 | 4 | ArrowExpr = Tuple[str, Optional[str]] 5 | 6 | def generate_ast( 7 | base_name: str, 8 | types: List[str], 9 | imports: List[str], 10 | literal_types: List[str], 11 | literal_types_name: Optional[str], 12 | additional_code: Optional[str], 13 | ) -> str: 14 | generated_file: str = '' 15 | 16 | # import everything 17 | for i in imports: 18 | generated_file += 'use {};\n'.format(i) 19 | 20 | generated_file += '\n' 21 | 22 | # generate literal values 23 | if literal_types_name != None: 24 | generated_file += '#[derive(Clone)]pub enum {} {{\n' \ 25 | .format(literal_types_name) 26 | 27 | for l in literal_types: 28 | lv = parse_arrow_expr(l) 29 | 30 | if lv is None: 31 | continue 32 | 33 | generated_file += '\t' + lv[0] 34 | 35 | if lv[1] != None: 36 | generated_file += '({})'.format(lv[1]) 37 | 38 | generated_file += ',\n' 39 | 40 | generated_file += '}\n\n' 41 | 42 | enum_members: List[ArrowExpr] = [] 43 | 44 | # generate value structs 45 | for t in types: 46 | member = parse_arrow_expr(t) 47 | 48 | if member is None: 49 | continue 50 | 51 | enum_members.append(member) 52 | 53 | if member[1] is None: 54 | continue 55 | 56 | # making fields public 57 | publicized: str = ', '.join( 58 | ['pub {}'.format(field.strip()) for field in member[1].split(',')] 59 | ) 60 | 61 | generated_file += 'pub struct {}Value {{{}}}\n\n'.format(member[0], publicized) 62 | 63 | # generate base enum 64 | generated_file += 'pub enum {} {{\n'.format(base_name) 65 | 66 | for member in enum_members: 67 | generated_file += '\t' + member[0] 68 | 69 | if member[1] != None: 70 | generated_file += '({}Value)'.format(member[0]) 71 | 72 | generated_file += ',\n' 73 | 74 | generated_file += '}\n' 75 | 76 | if additional_code is not None: 77 | generated_file += additional_code 78 | 79 | return generated_file 80 | 81 | 82 | # arrow expression is defined as such: 83 | # ' -> ' | '' 84 | # the second parameter in the arrow expresssion is optionalk 85 | def parse_arrow_expr(expr: str) -> Optional[ArrowExpr]: 86 | splout = expr.split('->') 87 | len_of_splout = len(splout) 88 | if len_of_splout < 1 or len_of_splout > 2: 89 | print('Invalid arrow expression: "{}"'.format(expr)) 90 | return None 91 | 92 | p1: str = splout[0].strip() 93 | 94 | p2: Optional[str] = None 95 | 96 | if len_of_splout > 1: 97 | p2 = splout[1].strip() 98 | 99 | return (p1, p2) 100 | 101 | 102 | def gen_expr() -> str: 103 | to_generate = [ 104 | """ 105 | Function -> 106 | keyword: Token, name: Option, 107 | params: Option>>, body: Option>> 108 | """, 109 | """ 110 | Set -> 111 | setee: Box, key: GetAccessor, 112 | value: Box, blame: Token 113 | """, 114 | """ 115 | Super -> 116 | blame: Token, accessor: SuperAccessor, 117 | env_distance: Cell 118 | """, 119 | 'Call -> calee: Box, closing_paren: Token, arguments: Vec', 120 | 'Assignment -> name: Token, value: Box, env_distance: Cell', 121 | 'Binary -> left: Box, operator: Token, right: Box', 122 | 'Get -> getee: Box, key: GetAccessor, blame: Token', 123 | 'Identifier -> name: Token, env_distance: Cell', 124 | 'Object -> blame: Token, properties: Vec', 125 | 'This -> blame: Token, env_distance: Cell', 126 | 'Unary -> operator: Token, right: Box', 127 | 'Grouping -> expression: Box', 128 | 'Literal(LiteralValue)', 129 | ] 130 | 131 | imports = [ 132 | 'crate::{ast::stmt::*, token::Token, parser::types::Property}', 133 | 'std::{rc::Rc, cell::Cell}', 134 | ] 135 | 136 | literal_types = [ 137 | 'List -> Rc>', 138 | 'String -> Rc', 139 | 'Number -> f64', 140 | 'Char -> char', 141 | 'True', 142 | 'False', 143 | 'Nil', 144 | ] 145 | 146 | additional_code = """ 147 | pub enum GetAccessor { 148 | DotName(Rc), 149 | DotEval(Box), 150 | SubscriptionNumber(f64), 151 | SubscriptionEval(Box), 152 | } 153 | 154 | pub enum SuperAccessor { 155 | Method(Token), 156 | Call(Vec), 157 | } 158 | """ 159 | 160 | return generate_ast( 161 | 'Expr', 162 | to_generate, 163 | imports, 164 | literal_types, 165 | 'LiteralValue', 166 | additional_code, 167 | ) 168 | 169 | 170 | def gen_stmt() -> str: 171 | to_generate = [ 172 | """ 173 | For -> 174 | condition: Option, body: Box, 175 | closer: Option> 176 | """, 177 | """ 178 | If -> 179 | condition: Expr, then: Option>, 180 | otherwise: Option> 181 | """, 182 | """ 183 | Declaration -> 184 | name: Token, initializer: Option, 185 | mutable: bool 186 | """, 187 | """ 188 | Class -> 189 | name: Token, methods: Vec, 190 | superclass: Option 191 | """, 192 | 'Return -> keyword: Token, expression: Option', 193 | 'Expression -> expression: Expr', 194 | 'Block -> statements: Vec', 195 | 'Continue -> keyword: Token', 196 | 'Print -> expression: Expr', 197 | 'Break -> keyword: Token', 198 | ] 199 | 200 | imports = [ 201 | 'crate::token::Token', 202 | 'crate::ast::expr::Expr', 203 | ] 204 | 205 | literal_types: List[str] = [] 206 | 207 | return generate_ast( 208 | 'Stmt', 209 | to_generate, 210 | imports, 211 | literal_types, 212 | None, 213 | None, 214 | ) 215 | 216 | def write_to_file(text: str, path: str) -> None: 217 | with open(path, 'w') as f: 218 | f.write(text) 219 | 220 | 221 | def main() -> None: 222 | write_to_file(gen_expr(), './src/ast/expr.rs') 223 | write_to_file(gen_stmt(), './src/ast/stmt.rs') 224 | 225 | 226 | if __name__ == '__main__': 227 | main() 228 | -------------------------------------------------------------------------------- /src/parser/statements.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | expressions::{expression, function_declaration}, 3 | helpers::expect_semicolon, 4 | parse::{declaration, statement}, 5 | types::{ParseError, ParserIter}, 6 | }; 7 | use crate::{ 8 | ast::{ 9 | expr::{Expr, IdentifierValue}, 10 | stmt::{ 11 | BlockValue, 12 | BreakValue, 13 | ClassValue, 14 | ContinueValue, 15 | ExpressionValue, 16 | ForValue, 17 | IfValue, 18 | PrintValue, 19 | ReturnValue, 20 | Stmt, 21 | }, 22 | }, 23 | expect, 24 | expect_one, 25 | match_then_consume, 26 | match_then_consume_stmt, 27 | peek_matches, 28 | token::{Token, TokenType}, 29 | }; 30 | 31 | use std::{cell::Cell, vec}; 32 | 33 | 34 | #[inline] 35 | pub fn print_statement(tokens: ParserIter) -> Result, ParseError> { 36 | let stmt = Stmt::Print(PrintValue { 37 | expression: expression(tokens)?, 38 | }); 39 | 40 | expect_semicolon(tokens)?; 41 | 42 | Ok(Some(stmt)) 43 | } 44 | 45 | pub fn expression_statement( 46 | tokens: ParserIter, 47 | ) -> Result, ParseError> { 48 | let expr = expression(tokens)?; 49 | 50 | // expect semicolon only if the expression is not a function 51 | let semicolon_expected = !matches!(expr, Expr::Function(_)); 52 | 53 | let stmt = Stmt::Expression(ExpressionValue { expression: expr }); 54 | 55 | if semicolon_expected { 56 | expect_semicolon(tokens)?; 57 | } 58 | 59 | Ok(Some(stmt)) 60 | } 61 | 62 | pub fn if_statement(tokens: ParserIter) -> Result, ParseError> { 63 | let condition = expression(tokens)?; 64 | 65 | let then = match_then_consume_stmt!( 66 | tokens, 67 | TokenType::LeftBrace, 68 | "Expected a block expression for `then` branch" 69 | )? 70 | .map(Box::new); 71 | 72 | let otherwise = if match_then_consume!(tokens, TokenType::Else).is_some() { 73 | match_then_consume_stmt!( 74 | tokens, 75 | TokenType::LeftBrace | TokenType::If, 76 | "Expected" 77 | )? 78 | .map(Box::new) 79 | } else { 80 | None 81 | }; 82 | 83 | if then.is_none() && otherwise.is_none() { 84 | Ok(None) 85 | } else { 86 | Ok(Some(Stmt::If(IfValue { 87 | condition, 88 | then, 89 | otherwise, 90 | }))) 91 | } 92 | } 93 | 94 | pub fn block_statement(tokens: ParserIter) -> Result, ParseError> { 95 | let mut statements = Vec::new(); 96 | 97 | while !peek_matches!(tokens, TokenType::RightBrace) { 98 | if let Some(d) = declaration(tokens)? { 99 | statements.push(d); 100 | } 101 | } 102 | 103 | expect_one!(tokens, TokenType::RightBrace)?; 104 | 105 | // as though it may not seem as an optimization, it really is a useful 106 | // heuristic to return an empty statement rather than block 107 | // with 0 statements 108 | // 109 | // for example: I use this in `if` statements to determine if I need to 110 | // even return them or not 111 | if statements.is_empty() { 112 | Ok(None) 113 | } else { 114 | Ok(Some(Stmt::Block(BlockValue { statements }))) 115 | } 116 | } 117 | 118 | pub fn for_statement(tokens: ParserIter) -> Result, ParseError> { 119 | // parse declaration 120 | if !peek_matches!( 121 | tokens, 122 | TokenType::Semicolon | TokenType::Let | TokenType::Const 123 | ) { 124 | return Err(ParseError { 125 | message: "Expected `let`, `const`, or `;` to omit declaration" 126 | .to_owned(), 127 | token: tokens.peek().cloned(), 128 | }); 129 | } 130 | 131 | // parse initializer 132 | let initializer = 133 | if peek_matches!(tokens, TokenType::Let | TokenType::Const) { 134 | declaration(tokens)? 135 | } else { 136 | tokens.next(); 137 | 138 | None 139 | }; 140 | 141 | // parse condition 142 | let condition = 143 | if match_then_consume!(tokens, TokenType::Semicolon).is_some() { 144 | None 145 | } else { 146 | let expr = expression(tokens)?; 147 | 148 | expect_one!(tokens, TokenType::Semicolon)?; 149 | 150 | Some(expr) 151 | }; 152 | 153 | // parse closer (the increment or whatever) 154 | let closer = if peek_matches!(tokens, TokenType::LeftBrace) { 155 | None 156 | } else { 157 | Some(expression(tokens)?) 158 | }; 159 | 160 | 161 | // parse for's body. If the body is None, then we may as well 162 | // short-circuit it there, and return Ok(None) 163 | let body_stmt = match_then_consume_stmt!( 164 | tokens, 165 | TokenType::LeftBrace, 166 | "Expected for's body" 167 | )?; 168 | 169 | let body = if let Some(body) = body_stmt { 170 | body 171 | } else { 172 | return Ok(None); 173 | }; 174 | 175 | 176 | let for_stmt = Stmt::For(ForValue { 177 | condition, 178 | body: Box::new(body), 179 | closer: closer.map(|c| { 180 | Box::new(Stmt::Expression(ExpressionValue { expression: c })) 181 | }), 182 | }); 183 | 184 | // determine if for body requires to be in a separate block 185 | // because of the initializer 186 | let for_body = if let Some(initializer) = initializer { 187 | Stmt::Block(BlockValue { 188 | statements: vec![initializer, for_stmt], 189 | }) 190 | } else { 191 | for_stmt 192 | }; 193 | 194 | Ok(Some(for_body)) 195 | } 196 | 197 | pub fn return_statement( 198 | tokens: ParserIter, 199 | keyword: Token, 200 | ) -> Result, ParseError> { 201 | let expression = if peek_matches!(tokens, TokenType::Semicolon) { 202 | None 203 | } else { 204 | Some(expression(tokens)?) 205 | }; 206 | 207 | expect_semicolon(tokens)?; 208 | 209 | Ok(Some(Stmt::Return(ReturnValue { 210 | keyword, 211 | expression, 212 | }))) 213 | } 214 | 215 | #[inline] 216 | pub fn break_statement( 217 | tokens: ParserIter, 218 | keyword: Token, 219 | ) -> Result, ParseError> { 220 | expect_semicolon(tokens)?; 221 | 222 | Ok(Some(Stmt::Break(BreakValue { keyword }))) 223 | } 224 | 225 | #[inline] 226 | pub fn continue_statement( 227 | tokens: ParserIter, 228 | keyword: Token, 229 | ) -> Result, ParseError> { 230 | expect_semicolon(tokens)?; 231 | 232 | Ok(Some(Stmt::Continue(ContinueValue { keyword }))) 233 | } 234 | 235 | pub fn class_statement(tokens: ParserIter) -> Result, ParseError> { 236 | let name = 237 | expect!(tokens, TokenType::Identifier(_), "Expected class name")?; 238 | 239 | let superclass = 240 | if match_then_consume!(tokens, TokenType::Extends).is_some() { 241 | let superclass_name = expect!( 242 | tokens, 243 | TokenType::Identifier(_), 244 | "Expected superclass name", 245 | )?; 246 | 247 | Some(Expr::Identifier(IdentifierValue { 248 | name: superclass_name, 249 | env_distance: Cell::default(), 250 | })) 251 | } else { 252 | None 253 | }; 254 | 255 | expect_one!(tokens, TokenType::LeftBrace)?; 256 | 257 | let mut methods = Vec::new(); 258 | 259 | while !peek_matches!(tokens, TokenType::RightBrace) { 260 | methods.push(function_declaration(tokens, true)?); 261 | } 262 | 263 | expect_one!(tokens, TokenType::RightBrace)?; 264 | 265 | Ok(Some(Stmt::Class(ClassValue { 266 | name, 267 | methods, 268 | superclass, 269 | }))) 270 | } 271 | -------------------------------------------------------------------------------- /src/interpreter/statements.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::InterpreterEnvironment, 3 | helpers::{assume_identifier, construct_lox_defined_function}, 4 | interpret::eval_expression, 5 | types::{InterpreterValue, RuntimeError, StmtResult}, 6 | }; 7 | use crate::{ 8 | ast::{ 9 | expr::Expr, 10 | stmt::{ 11 | BlockValue, 12 | BreakValue, 13 | ClassValue, 14 | ContinueValue, 15 | DeclarationValue, 16 | ExpressionValue, 17 | ForValue, 18 | IfValue, 19 | PrintValue, 20 | ReturnValue, 21 | Stmt, 22 | }, 23 | }, 24 | env::{DeclaredValue, EnvironmentWrapper}, 25 | }; 26 | 27 | use std::{collections::HashMap, rc::Rc}; 28 | 29 | 30 | #[inline] 31 | pub fn expression_statement( 32 | expr_evaluator: fn(&Expr, &E) -> Result, 33 | v: &ExpressionValue, 34 | env: &E, 35 | ) -> Result, RuntimeError> 36 | where 37 | E: EnvironmentWrapper, 38 | { 39 | expr_evaluator(&v.expression, env)?; 40 | 41 | Ok(StmtResult::Noop) 42 | } 43 | 44 | #[inline] 45 | pub fn print_statement( 46 | expr_evaluator: fn(&Expr, &E) -> Result, 47 | v: &PrintValue, 48 | env: &E, 49 | ) -> Result, RuntimeError> 50 | where 51 | T: std::fmt::Display, 52 | E: EnvironmentWrapper, 53 | { 54 | let evaluated = expr_evaluator(&v.expression, env)?; 55 | 56 | println!("{}", evaluated); 57 | 58 | Ok(StmtResult::Noop) 59 | } 60 | 61 | pub fn declaration_statement( 62 | expr_evaluator: fn(&Expr, &E) -> Result, 63 | v: &DeclarationValue, 64 | env: &E, 65 | ) -> Result, RuntimeError> 66 | where 67 | E: EnvironmentWrapper, 68 | { 69 | let value = v 70 | .initializer 71 | .as_ref() 72 | .map_or(Ok(InterpreterValue::Nil), |initializer| { 73 | expr_evaluator(initializer, env) 74 | })?; 75 | 76 | env.declare( 77 | assume_identifier(&v.name).to_owned(), 78 | DeclaredValue { 79 | mutable: v.mutable, 80 | value, 81 | }, 82 | ); 83 | 84 | Ok(StmtResult::Noop) 85 | } 86 | 87 | #[inline] 88 | pub fn block_statement( 89 | stmts_evaluator: fn(&[Stmt], &E) -> Result, RuntimeError>, 90 | v: &BlockValue, 91 | env: &E, 92 | ) -> Result, RuntimeError> 93 | where 94 | E: EnvironmentWrapper, 95 | { 96 | let new_scope = env.fork(); 97 | 98 | stmts_evaluator(&v.statements, &new_scope) 99 | } 100 | 101 | #[inline] 102 | pub fn if_statement( 103 | expr_evaluator: fn(&Expr, &E) -> Result, 104 | stmt_evaluator: fn( 105 | &Stmt, 106 | &E, 107 | ) -> Result, RuntimeError>, 108 | v: &IfValue, 109 | env: &E, 110 | ) -> Result, RuntimeError> 111 | where 112 | E: EnvironmentWrapper, 113 | { 114 | if expr_evaluator(&v.condition, env)? == InterpreterValue::True { 115 | v.then 116 | .as_ref() 117 | .map_or(Ok(StmtResult::Noop), |then| stmt_evaluator(then, env)) 118 | } else if let Some(otherwise) = &v.otherwise { 119 | stmt_evaluator(otherwise, env) 120 | } else { 121 | Ok(StmtResult::Noop) 122 | } 123 | } 124 | 125 | pub fn for_statement( 126 | expr_evaluator: fn(&Expr, &E) -> Result, 127 | stmt_evaluator: fn(&Stmt, &E) -> Result, RuntimeError>, 128 | v: &ForValue, 129 | env: &E, 130 | ) -> Result, RuntimeError> 131 | where 132 | E: EnvironmentWrapper, 133 | { 134 | // these branches look sooo sketchy, but it's an optimization for 135 | // condition-less loops 136 | if let Some(condition) = &v.condition { 137 | while expr_evaluator(condition, env)? == InterpreterValue::True { 138 | let e = stmt_evaluator(&v.body, env)?; 139 | 140 | match e { 141 | StmtResult::Break(_) => break, 142 | StmtResult::Continue(_) => { 143 | if let Some(c) = &v.closer { 144 | stmt_evaluator(c, env)?; 145 | } 146 | 147 | continue; 148 | } 149 | StmtResult::Noop => (), 150 | StmtResult::Return { .. } => { 151 | return Ok(e); 152 | } 153 | } 154 | 155 | if let Some(c) = &v.closer { 156 | stmt_evaluator(c, env)?; 157 | } 158 | } 159 | } else { 160 | loop { 161 | let e = stmt_evaluator(&v.body, env)?; 162 | 163 | match e { 164 | StmtResult::Break(_) => break, 165 | StmtResult::Continue(_) => { 166 | if let Some(c) = &v.closer { 167 | stmt_evaluator(c, env)?; 168 | } 169 | 170 | continue; 171 | } 172 | StmtResult::Noop => (), 173 | StmtResult::Return { .. } => { 174 | return Ok(e); 175 | } 176 | } 177 | 178 | if let Some(c) = &v.closer { 179 | stmt_evaluator(c, env)?; 180 | } 181 | } 182 | } 183 | 184 | Ok(StmtResult::Noop) 185 | } 186 | 187 | #[inline] 188 | pub fn return_statement( 189 | expr_evaluator: fn(&Expr, &E) -> Result, 190 | v: &ReturnValue, 191 | env: &E, 192 | ) -> Result, RuntimeError> 193 | where 194 | E: EnvironmentWrapper, 195 | { 196 | Ok(StmtResult::Return { 197 | value: v 198 | .expression 199 | .as_ref() 200 | .map_or(Ok(InterpreterValue::Nil), |e| expr_evaluator(e, env))?, 201 | keyword: v.keyword.clone(), 202 | }) 203 | } 204 | 205 | #[inline] 206 | pub fn break_statement(v: &BreakValue) -> StmtResult { 207 | StmtResult::Break(v.keyword.clone()) 208 | } 209 | 210 | #[inline] 211 | pub fn continue_statement(v: &ContinueValue) -> StmtResult { 212 | StmtResult::Continue(v.keyword.clone()) 213 | } 214 | 215 | pub fn class_statement( 216 | v: &ClassValue, 217 | env: &InterpreterEnvironment, 218 | ) -> Result, RuntimeError> { 219 | let name = assume_identifier(&v.name); 220 | 221 | let (superclass, super_env) = if let Some(expr) = &v.superclass { 222 | let evaluated = eval_expression(expr, env)?; 223 | 224 | if !matches!(evaluated, InterpreterValue::Class { .. }) { 225 | return Err(RuntimeError { 226 | message: format!( 227 | "Cannot inherit from {}", 228 | evaluated.human_type() 229 | ), 230 | token: v.name.clone(), 231 | }); 232 | } 233 | 234 | let superclass = eval_expression(expr, env)?; 235 | 236 | let super_env = env.fork(); 237 | 238 | super_env.declare( 239 | "super".into(), 240 | DeclaredValue { 241 | mutable: false, 242 | value: superclass.clone(), 243 | }, 244 | ); 245 | 246 | (Some(Rc::new(superclass)), Some(super_env)) 247 | } else { 248 | (None, None) 249 | }; 250 | 251 | let class_env = super_env.as_ref().unwrap_or(env); 252 | 253 | let mut methods = HashMap::new(); 254 | 255 | let mut constructor = None; 256 | 257 | for method in &v.methods { 258 | let fv = if let Expr::Function(v) = method { 259 | v 260 | } else { 261 | unreachable!( 262 | "Method should be a function expression. Parser fucked up" 263 | ) 264 | }; 265 | 266 | let fun = construct_lox_defined_function(fv, class_env); 267 | 268 | let name = assume_identifier(fv.name.as_ref().expect("Method name")); 269 | 270 | if name == "constructor" { 271 | constructor = Some(Rc::new(fun)); 272 | } else { 273 | methods.insert(name.to_owned(), fun); 274 | } 275 | } 276 | 277 | env.declare( 278 | name.to_owned(), 279 | DeclaredValue { 280 | mutable: false, 281 | value: InterpreterValue::Class { 282 | superclass, 283 | constructor, 284 | name: Rc::from(name), 285 | methods: Rc::new(methods), 286 | }, 287 | }, 288 | ); 289 | 290 | Ok(StmtResult::Noop) 291 | } 292 | -------------------------------------------------------------------------------- /src/interpreter/native_functions.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::InterpreterEnvironment, 3 | helpers::unwrap_list, 4 | types::{ 5 | InterpreterFunction, 6 | InterpreterValue, 7 | NativeFunctionSignature, 8 | RuntimeError, 9 | }, 10 | }; 11 | use crate::{ 12 | env::{DeclaredValue, EnvironmentWrapper}, 13 | token::Token, 14 | try_exact_convert, 15 | }; 16 | 17 | use std::{ 18 | cell::RefCell, 19 | io::{self, Write}, 20 | rc::Rc, 21 | }; 22 | 23 | 24 | pub const NATIVE_FUNCTION_NAMES: [&str; 15] = [ 25 | "str", 26 | "typeof", 27 | "number", 28 | "len", 29 | "expand", 30 | "push", 31 | "extend", 32 | "from_chars", 33 | "deep_copy", 34 | "is_nan", 35 | "floor", 36 | "ceil", 37 | "has", 38 | "unset", 39 | "read", 40 | ]; 41 | 42 | struct FunctionDefinition<'a> { 43 | name: &'a str, 44 | arity: usize, 45 | fun: NativeFunctionSignature, 46 | } 47 | 48 | 49 | fn native_str( 50 | _keyword: &Token, 51 | _env: &InterpreterEnvironment, 52 | args: &[InterpreterValue], 53 | ) -> InterpreterValue { 54 | let input = &args[0]; 55 | 56 | if let InterpreterValue::String(_) = input { 57 | input.clone() 58 | } else { 59 | InterpreterValue::String(Rc::from(input.to_string())) 60 | } 61 | } 62 | 63 | fn native_typeof( 64 | _keyword: &Token, 65 | _env: &InterpreterEnvironment, 66 | args: &[InterpreterValue], 67 | ) -> InterpreterValue { 68 | InterpreterValue::String(Rc::from(args[0].human_type())) 69 | } 70 | 71 | fn native_number( 72 | keyword: &Token, 73 | _env: &InterpreterEnvironment, 74 | args: &[InterpreterValue], 75 | ) -> Result { 76 | let input = &args[0]; 77 | 78 | match input { 79 | InterpreterValue::Number(_) => Ok(input.clone()), 80 | InterpreterValue::String(s) => { 81 | Ok(InterpreterValue::Number(s.parse().or(Ok(f64::NAN))?)) 82 | } 83 | InterpreterValue::Char(c) => Ok(InterpreterValue::Number( 84 | c.to_digit(10).map_or(f64::NAN, std::convert::Into::into), 85 | )), 86 | _ => Err(RuntimeError { 87 | message: format!("Can't parse {} to number", input.human_type()), 88 | token: keyword.clone(), 89 | }), 90 | } 91 | } 92 | 93 | fn native_len( 94 | keyword: &Token, 95 | _env: &InterpreterEnvironment, 96 | args: &[InterpreterValue], 97 | ) -> Result { 98 | match &args[0] { 99 | InterpreterValue::String(s) => try_exact_convert!(s.len(), usize, f64) 100 | .map_err(|_| RuntimeError { 101 | message: format!("Cannot conver from {}_usize to f64", s.len(),), 102 | token: keyword.clone(), 103 | }) 104 | .map(InterpreterValue::Number), 105 | InterpreterValue::List(l) => { 106 | let l_borrow = l.borrow(); 107 | 108 | try_exact_convert!(l_borrow.len(), usize, f64) 109 | .map_err(|_| RuntimeError { 110 | message: format!( 111 | "Cannot conver from {}_usize to f64", 112 | l_borrow.len(), 113 | ), 114 | token: keyword.clone(), 115 | }) 116 | .map(InterpreterValue::Number) 117 | } 118 | _ => Err(RuntimeError { 119 | message: format!("Can't get length of {}", &args[0].human_type()), 120 | token: keyword.clone(), 121 | }), 122 | } 123 | } 124 | 125 | fn native_expand( 126 | keyword: &Token, 127 | _env: &InterpreterEnvironment, 128 | args: &[InterpreterValue], 129 | ) -> Result { 130 | let val = &args[0]; 131 | 132 | match val { 133 | InterpreterValue::String(s) => Ok(InterpreterValue::List(Rc::new( 134 | RefCell::new(s.chars().map(InterpreterValue::Char).collect()), 135 | ))), 136 | InterpreterValue::Instance { properties, .. } => { 137 | let keys = properties 138 | .borrow() 139 | .keys() 140 | .cloned() 141 | .map(|k| InterpreterValue::String(k.into())) 142 | .collect(); 143 | 144 | Ok(InterpreterValue::List(Rc::new(RefCell::new(keys)))) 145 | } 146 | _ => Err(RuntimeError { 147 | message: format!("Can't use expand on {}", val.human_type()), 148 | token: keyword.clone(), 149 | }), 150 | } 151 | } 152 | 153 | fn native_push( 154 | keyword: &Token, 155 | _env: &InterpreterEnvironment, 156 | args: &[InterpreterValue], 157 | ) -> Result { 158 | let mut l_borrow = unwrap_list(&args[0], keyword, 0, None)?; 159 | 160 | l_borrow.push(args[1].clone()); 161 | 162 | drop(l_borrow); 163 | 164 | Ok(args[0].clone()) 165 | } 166 | 167 | fn native_extend( 168 | keyword: &Token, 169 | _env: &InterpreterEnvironment, 170 | args: &[InterpreterValue], 171 | ) -> Result { 172 | let second_items = unwrap_list(&args[1], keyword, 1, None)? 173 | .iter() 174 | .cloned() 175 | .collect::>(); 176 | 177 | unwrap_list(&args[0], keyword, 0, None)?.extend(second_items); 178 | 179 | Ok(args[0].clone()) 180 | } 181 | 182 | fn native_from_chars( 183 | keyword: &Token, 184 | _env: &InterpreterEnvironment, 185 | args: &[InterpreterValue], 186 | ) -> Result { 187 | let l_borrow = unwrap_list(&args[0], keyword, 0, None)?; 188 | 189 | let string = l_borrow 190 | .iter() 191 | .map(|v| { 192 | if let InterpreterValue::Char(c) = v { 193 | Ok(*c) 194 | } else { 195 | Err(RuntimeError { 196 | message: format!( 197 | "Cannot convert from {} to char", 198 | v.human_type() 199 | ), 200 | token: keyword.clone(), 201 | }) 202 | } 203 | }) 204 | .collect::>()?; 205 | 206 | Ok(InterpreterValue::String(Rc::from(string))) 207 | } 208 | 209 | fn native_deep_copy( 210 | keyword: &Token, 211 | env: &InterpreterEnvironment, 212 | args: &[InterpreterValue], 213 | ) -> Result { 214 | let value = &args[0]; 215 | 216 | match value { 217 | InterpreterValue::Instance { class, properties } => { 218 | let cloned_properties = properties 219 | .borrow() 220 | .iter() 221 | .map(|p| { 222 | let key = p.0.clone(); 223 | let value = native_deep_copy(keyword, env, &[p.1.clone()])?; 224 | 225 | Ok((key, value)) 226 | }) 227 | .collect::>()?; 228 | 229 | Ok(InterpreterValue::Instance { 230 | class: class.clone(), 231 | properties: Rc::new(RefCell::new(cloned_properties)), 232 | }) 233 | } 234 | InterpreterValue::List(l) => { 235 | let cloned_list = l 236 | .borrow() 237 | .iter() 238 | .map(|v| native_deep_copy(keyword, env, &[v.clone()])) 239 | .collect::>()?; 240 | 241 | Ok(InterpreterValue::List(Rc::new(RefCell::new(cloned_list)))) 242 | } 243 | _ => Ok(value.clone()), 244 | } 245 | } 246 | 247 | fn native_is_nan( 248 | keyword: &Token, 249 | _env: &InterpreterEnvironment, 250 | args: &[InterpreterValue], 251 | ) -> Result { 252 | let value = &args[0]; 253 | 254 | if let InterpreterValue::Number(n) = value { 255 | Ok(n.is_nan().into()) 256 | } else { 257 | Err(RuntimeError { 258 | message: format!("Cannot use is_nan on {}", value.human_type()), 259 | token: keyword.clone(), 260 | }) 261 | } 262 | } 263 | 264 | fn native_floor( 265 | keyword: &Token, 266 | _env: &InterpreterEnvironment, 267 | args: &[InterpreterValue], 268 | ) -> Result { 269 | let value = &args[0]; 270 | 271 | if let InterpreterValue::Number(n) = value { 272 | Ok(InterpreterValue::Number(n.floor())) 273 | } else { 274 | Err(RuntimeError { 275 | message: format!("Cannot use floor on {}", value.human_type()), 276 | token: keyword.clone(), 277 | }) 278 | } 279 | } 280 | 281 | fn native_ceil( 282 | keyword: &Token, 283 | _env: &InterpreterEnvironment, 284 | args: &[InterpreterValue], 285 | ) -> Result { 286 | let value = &args[0]; 287 | 288 | if let InterpreterValue::Number(n) = value { 289 | Ok(InterpreterValue::Number(n.ceil())) 290 | } else { 291 | Err(RuntimeError { 292 | message: format!("Cannot use ceil on {}", value.human_type()), 293 | token: keyword.clone(), 294 | }) 295 | } 296 | } 297 | 298 | fn native_has( 299 | keyword: &Token, 300 | _env: &InterpreterEnvironment, 301 | args: &[InterpreterValue], 302 | ) -> Result { 303 | let searchee = &args[0]; 304 | let value = &args[1]; 305 | 306 | match (searchee, value) { 307 | (InterpreterValue::Instance { properties, .. }, _) => { 308 | let borrowed_props = properties.borrow(); 309 | 310 | Ok(borrowed_props.contains_key(&value.to_string()).into()) 311 | } 312 | (InterpreterValue::List(l), _) => { 313 | let l_borrow = l.borrow(); 314 | 315 | Ok(l_borrow.iter().any(|v| v == value).into()) 316 | } 317 | (InterpreterValue::String(s1), InterpreterValue::String(s2)) => { 318 | Ok(s1.contains(&**s2).into()) 319 | } 320 | (InterpreterValue::String(s), InterpreterValue::Char(c)) => { 321 | Ok(s.contains(*c).into()) 322 | } 323 | _ => Err(RuntimeError { 324 | message: format!( 325 | "Cannot use has with {} and {}", 326 | searchee.human_type(), 327 | value.human_type() 328 | ), 329 | token: keyword.clone(), 330 | }), 331 | } 332 | } 333 | 334 | fn native_unset( 335 | keyword: &Token, 336 | _env: &InterpreterEnvironment, 337 | args: &[InterpreterValue], 338 | ) -> Result { 339 | let map = &args[0]; 340 | let key = &args[1]; 341 | 342 | match (map, key) { 343 | ( 344 | InterpreterValue::Instance { properties, .. }, 345 | InterpreterValue::String(s), 346 | ) => { 347 | let mut borrowed_props = properties.borrow_mut(); 348 | 349 | Ok(borrowed_props.remove(&**s).unwrap_or(InterpreterValue::Nil)) 350 | } 351 | _ => Err(RuntimeError { 352 | message: format!( 353 | "Cannot use unset with {} and {}", 354 | map.human_type(), 355 | key.human_type() 356 | ), 357 | token: keyword.clone(), 358 | }), 359 | } 360 | } 361 | 362 | fn native_read( 363 | keyword: &Token, 364 | _env: &InterpreterEnvironment, 365 | args: &[InterpreterValue], 366 | ) -> Result { 367 | let to_print = if args[0] == InterpreterValue::Nil { 368 | "".to_owned() 369 | } else { 370 | args[0].to_string() 371 | }; 372 | 373 | print!("{}", to_print); 374 | 375 | io::stdout().flush().map_err(|e| RuntimeError { 376 | message: e.to_string(), 377 | token: keyword.clone(), 378 | })?; 379 | 380 | let mut buffer = String::new(); 381 | io::stdin() 382 | .read_line(&mut buffer) 383 | .map_err(|e| RuntimeError { 384 | message: e.to_string(), 385 | token: keyword.clone(), 386 | })?; 387 | 388 | Ok(InterpreterValue::String(buffer.into())) 389 | } 390 | 391 | fn declarator(env: &InterpreterEnvironment, funs: &[FunctionDefinition]) { 392 | for definition in funs { 393 | env.declare( 394 | definition.name.to_owned(), 395 | DeclaredValue { 396 | mutable: true, 397 | value: InterpreterValue::Function { 398 | fun: Rc::new(InterpreterFunction::Native { 399 | arity: definition.arity, 400 | fun: definition.fun, 401 | }), 402 | enclosing_env: env.clone(), 403 | }, 404 | }, 405 | ); 406 | } 407 | } 408 | 409 | pub fn declare(env: &InterpreterEnvironment) { 410 | declarator( 411 | env, 412 | &[ 413 | FunctionDefinition { 414 | name: NATIVE_FUNCTION_NAMES[0], 415 | arity: 1, 416 | fun: |keyword, env, args| Ok(native_str(keyword, env, args)), 417 | }, 418 | FunctionDefinition { 419 | name: NATIVE_FUNCTION_NAMES[1], 420 | arity: 1, 421 | fun: |keyword, env, args| Ok(native_typeof(keyword, env, args)), 422 | }, 423 | FunctionDefinition { 424 | name: NATIVE_FUNCTION_NAMES[2], 425 | arity: 1, 426 | fun: native_number, 427 | }, 428 | FunctionDefinition { 429 | name: NATIVE_FUNCTION_NAMES[3], 430 | arity: 1, 431 | fun: native_len, 432 | }, 433 | FunctionDefinition { 434 | name: NATIVE_FUNCTION_NAMES[4], 435 | arity: 1, 436 | fun: native_expand, 437 | }, 438 | FunctionDefinition { 439 | name: NATIVE_FUNCTION_NAMES[5], 440 | arity: 2, 441 | fun: native_push, 442 | }, 443 | FunctionDefinition { 444 | name: NATIVE_FUNCTION_NAMES[6], 445 | arity: 2, 446 | fun: native_extend, 447 | }, 448 | FunctionDefinition { 449 | name: NATIVE_FUNCTION_NAMES[7], 450 | arity: 1, 451 | fun: native_from_chars, 452 | }, 453 | FunctionDefinition { 454 | name: NATIVE_FUNCTION_NAMES[8], 455 | arity: 1, 456 | fun: native_deep_copy, 457 | }, 458 | FunctionDefinition { 459 | name: NATIVE_FUNCTION_NAMES[9], 460 | arity: 1, 461 | fun: native_is_nan, 462 | }, 463 | FunctionDefinition { 464 | name: NATIVE_FUNCTION_NAMES[10], 465 | arity: 1, 466 | fun: native_floor, 467 | }, 468 | FunctionDefinition { 469 | name: NATIVE_FUNCTION_NAMES[11], 470 | arity: 1, 471 | fun: native_ceil, 472 | }, 473 | FunctionDefinition { 474 | name: NATIVE_FUNCTION_NAMES[12], 475 | arity: 2, 476 | fun: native_has, 477 | }, 478 | FunctionDefinition { 479 | name: NATIVE_FUNCTION_NAMES[13], 480 | arity: 2, 481 | fun: native_unset, 482 | }, 483 | FunctionDefinition { 484 | name: NATIVE_FUNCTION_NAMES[14], 485 | arity: 1, 486 | fun: native_read, 487 | }, 488 | ], 489 | ); 490 | } 491 | -------------------------------------------------------------------------------- /src/parser/expressions.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | statements::block_statement, 3 | types::{ParseError, ParserIter, Property}, 4 | }; 5 | use crate::{ 6 | ast::{ 7 | expr::{ 8 | AssignmentValue, 9 | BinaryValue, 10 | CallValue, 11 | Expr, 12 | FunctionValue, 13 | GetAccessor, 14 | GetValue, 15 | GroupingValue, 16 | IdentifierValue, 17 | LiteralValue, 18 | ObjectValue, 19 | SetValue, 20 | SuperAccessor, 21 | SuperValue, 22 | ThisValue, 23 | UnaryValue, 24 | }, 25 | stmt::Stmt, 26 | }, 27 | build_binary_expr, 28 | expect, 29 | expect_one, 30 | match_then_consume, 31 | peek_matches, 32 | token::{Token, TokenType}, 33 | }; 34 | 35 | use std::{cell::Cell, rc::Rc}; 36 | 37 | 38 | pub fn expression(tokens: ParserIter) -> Result { 39 | assignment(tokens) 40 | } 41 | 42 | fn assignment(tokens: ParserIter) -> Result { 43 | let expr = logic_or(tokens)?; 44 | 45 | if let Some(equals) = match_then_consume!(tokens, TokenType::Equal) { 46 | match expr { 47 | Expr::Identifier(v) => Ok(Expr::Assignment(AssignmentValue { 48 | name: v.name, 49 | value: Box::new(assignment(tokens)?), 50 | env_distance: Cell::new(0), 51 | })), 52 | Expr::Get(v) => Ok(Expr::Set(SetValue { 53 | setee: v.getee, 54 | key: v.key, 55 | blame: v.blame, 56 | value: Box::new(assignment(tokens)?), 57 | })), 58 | _ => Err(ParseError { 59 | token: Some(equals), 60 | message: format!( 61 | "Invalid l-value. Cannot assign to {}", 62 | expr.human_type() 63 | ), 64 | }), 65 | } 66 | } else { 67 | Ok(expr) 68 | } 69 | } 70 | 71 | fn logic_or(tokens: ParserIter) -> Result { 72 | build_binary_expr!(tokens, logic_and, TokenType::Or) 73 | } 74 | 75 | fn logic_and(tokens: ParserIter) -> Result { 76 | build_binary_expr!(tokens, equality, TokenType::And) 77 | } 78 | 79 | fn equality(tokens: ParserIter) -> Result { 80 | build_binary_expr!( 81 | tokens, 82 | comparison, 83 | TokenType::BangEqual | TokenType::EqualEqual, 84 | ) 85 | } 86 | 87 | fn comparison(tokens: ParserIter) -> Result { 88 | build_binary_expr!( 89 | tokens, 90 | term, 91 | TokenType::Greater 92 | | TokenType::GreaterEqual 93 | | TokenType::Less 94 | | TokenType::LessEqual 95 | ) 96 | } 97 | 98 | fn term(tokens: ParserIter) -> Result { 99 | build_binary_expr!( 100 | tokens, 101 | factor, 102 | TokenType::Minus | TokenType::Plus | TokenType::Modulo, 103 | ) 104 | } 105 | 106 | fn factor(tokens: ParserIter) -> Result { 107 | build_binary_expr!(tokens, unary, TokenType::Slash | TokenType::Star) 108 | } 109 | 110 | fn unary(tokens: ParserIter) -> Result { 111 | if matches!( 112 | tokens.peek().map(|t| &t.token_type), 113 | Some(TokenType::Bang | TokenType::Minus) 114 | ) { 115 | let operator = tokens.next().unwrap(); 116 | 117 | let right = unary(tokens)?; 118 | 119 | Ok(Expr::Unary(UnaryValue { 120 | operator, 121 | right: Box::new(right), 122 | })) 123 | } else { 124 | function_declaration(tokens, false) 125 | } 126 | } 127 | 128 | pub fn function_declaration( 129 | tokens: ParserIter, 130 | method: bool, 131 | ) -> Result { 132 | // methods don't have the `fun` keyword 133 | if method || peek_matches!(tokens, TokenType::Fun) { 134 | // expect the `fun` keyword if normal function, an identifier otherwise 135 | let keyword = if method { 136 | expect!(tokens, TokenType::Identifier(_), "Expected method name",)? 137 | } else { 138 | expect_one!(tokens, TokenType::Fun)? 139 | }; 140 | 141 | // if we're parsing a method, we actually already have 142 | // parse it's name in the keyword 143 | let name = if method { 144 | Some(keyword.clone()) 145 | } else { 146 | match_then_consume!(tokens, TokenType::Identifier(_)) 147 | }; 148 | 149 | // intro to parameter parsing 150 | expect_one!(tokens, TokenType::LeftParen)?; 151 | 152 | let mut params = Vec::new(); 153 | 154 | // parse parameters 155 | while !peek_matches!(tokens, TokenType::RightParen) { 156 | params.push(expect!( 157 | tokens, 158 | TokenType::Identifier(_), 159 | "Expected parameter name" 160 | )?); 161 | 162 | if match_then_consume!(tokens, TokenType::Comma).is_none() { 163 | break; 164 | } 165 | } 166 | 167 | expect_one!(tokens, TokenType::RightParen)?; 168 | // outro of parameter parsing 169 | 170 | // parse the body 171 | expect_one!(tokens, TokenType::LeftBrace)?; 172 | 173 | let body = block_statement(tokens)?; 174 | 175 | let statements = if let Some(Stmt::Block(bv)) = body { 176 | Some(Rc::new(bv.statements)) 177 | } else { 178 | None 179 | }; 180 | 181 | Ok(Expr::Function(FunctionValue { 182 | body: statements, 183 | keyword, 184 | name, 185 | params: if params.is_empty() { 186 | None 187 | } else { 188 | Some(Rc::new(params)) 189 | }, 190 | })) 191 | } else { 192 | call(tokens) 193 | } 194 | } 195 | 196 | fn finish_call(tokens: ParserIter, calee: Expr) -> Result { 197 | let mut arguments = Vec::new(); 198 | 199 | while !peek_matches!(tokens, TokenType::RightParen) { 200 | arguments.push(expression(tokens)?); 201 | 202 | if match_then_consume!(tokens, TokenType::Comma).is_none() { 203 | break; 204 | } 205 | } 206 | 207 | Ok(Expr::Call(CallValue { 208 | arguments, 209 | calee: Box::new(calee), 210 | closing_paren: expect_one!(tokens, TokenType::RightParen)?, 211 | })) 212 | } 213 | 214 | fn finish_get(tokens: ParserIter, getee: Expr) -> Result { 215 | let peek = tokens.peek(); 216 | let peek_token_type = peek.as_ref().map(|c| c.token_type.clone()); 217 | 218 | // Consuming the next token in following match arms 219 | // (except for the error one), because I want to advance the iterator 220 | match peek_token_type { 221 | Some(TokenType::Identifier(i)) => { 222 | // unwrap_unchecked because we just matched the type of peek 😇 223 | let blame = unsafe { tokens.next().unwrap_unchecked() }; 224 | 225 | Ok(Expr::Get(GetValue { 226 | getee: Box::new(getee), 227 | key: GetAccessor::DotName(i), 228 | blame, 229 | })) 230 | } 231 | Some(TokenType::LeftParen) => { 232 | // same here, unwrapping what we already matched 233 | let blame = unsafe { tokens.next().unwrap_unchecked() }; 234 | 235 | let eval = expression(tokens)?; 236 | 237 | expect_one!(tokens, TokenType::RightParen)?; 238 | 239 | Ok(Expr::Get(GetValue { 240 | getee: Box::new(getee), 241 | key: GetAccessor::DotEval(Box::new(eval)), 242 | blame, 243 | })) 244 | } 245 | _ => Err(ParseError { 246 | token: peek.cloned(), 247 | message: "Expected identifier or a parenthesized expression to \ 248 | evaluate" 249 | .into(), 250 | }), 251 | } 252 | } 253 | 254 | fn finish_sub(tokens: ParserIter, getee: Expr) -> Result { 255 | let peek = tokens.peek(); 256 | let peek_type = peek.as_ref().map(|p| p.token_type.clone()); 257 | 258 | let accessor = if let Some(TokenType::Number(n)) = peek_type { 259 | // unwrap_unchecked because we just matched peek 😇 260 | // 261 | // Consuming the next token here, because I want to advance the 262 | // iterator 263 | let blame = unsafe { tokens.next().unwrap_unchecked() }; 264 | 265 | Expr::Get(GetValue { 266 | getee: Box::new(getee), 267 | key: GetAccessor::SubscriptionNumber(n), 268 | blame, 269 | }) 270 | } else { 271 | // unwrapping what we already matched 272 | let blame = unsafe { peek.unwrap_unchecked().clone() }; 273 | 274 | let eval = expression(tokens)?; 275 | 276 | Expr::Get(GetValue { 277 | getee: Box::new(getee), 278 | key: GetAccessor::SubscriptionEval(Box::new(eval)), 279 | blame, 280 | }) 281 | }; 282 | 283 | 284 | expect_one!(tokens, TokenType::RightSquareBracket)?; 285 | 286 | Ok(accessor) 287 | } 288 | 289 | fn call(tokens: ParserIter) -> Result { 290 | let mut expr = primary(tokens)?; 291 | 292 | while let Some(consumed) = match_then_consume!( 293 | tokens, 294 | TokenType::LeftParen | TokenType::Dot | TokenType::LeftSquareBracket 295 | ) { 296 | match consumed.token_type { 297 | TokenType::LeftParen => { 298 | expr = finish_call(tokens, expr)?; 299 | } 300 | TokenType::LeftSquareBracket => { 301 | expr = finish_sub(tokens, expr)?; 302 | } 303 | _ => { 304 | expr = finish_get(tokens, expr)?; 305 | } 306 | } 307 | } 308 | 309 | Ok(expr) 310 | } 311 | 312 | fn primary(tokens: ParserIter) -> Result { 313 | let token = if let Some(token) = tokens.next() { 314 | token 315 | } else { 316 | return Err(ParseError { 317 | token: None, 318 | message: "Unexpected EOF".into(), 319 | }); 320 | }; 321 | 322 | match token.token_type { 323 | TokenType::False => Ok(Expr::Literal(LiteralValue::False)), 324 | TokenType::True => Ok(Expr::Literal(LiteralValue::True)), 325 | TokenType::Nil => Ok(Expr::Literal(LiteralValue::Nil)), 326 | TokenType::String(s) => Ok(Expr::Literal(LiteralValue::String(s))), 327 | TokenType::Char(c) => Ok(Expr::Literal(LiteralValue::Char(c))), 328 | TokenType::Number(n) => Ok(Expr::Literal(LiteralValue::Number(n))), 329 | 330 | TokenType::Identifier(_) => Ok(Expr::Identifier(IdentifierValue { 331 | name: token, 332 | env_distance: Cell::new(0), 333 | })), 334 | 335 | // Grouping 336 | TokenType::LeftParen => { 337 | let expr = expression(tokens)?; 338 | 339 | expect_one!(tokens, TokenType::RightParen)?; 340 | 341 | Ok(Expr::Grouping(GroupingValue { 342 | expression: Box::new(expr), 343 | })) 344 | } 345 | 346 | // This 347 | TokenType::This => Ok(Expr::This(ThisValue { 348 | blame: token, 349 | env_distance: Cell::new(0), 350 | })), 351 | 352 | // Lists 353 | TokenType::LeftSquareBracket => parse_list(tokens), 354 | 355 | // Objects 356 | TokenType::LeftBrace => parse_object(tokens, token), 357 | 358 | // Super 359 | TokenType::Super => parse_super(tokens, token), 360 | 361 | _ => Err(ParseError { 362 | token: Some(token), 363 | message: "Expected expression".into(), 364 | }), 365 | } 366 | } 367 | 368 | // Singular-primary parsing functions down there 👇 369 | 370 | pub fn parse_object( 371 | tokens: ParserIter, 372 | blame: Token, 373 | ) -> Result { 374 | let mut properties: Vec = Vec::new(); 375 | 376 | while !peek_matches!(tokens, TokenType::RightBrace) { 377 | let key_token = expect!( 378 | tokens, 379 | TokenType::Identifier(_) | TokenType::String(_), 380 | "Expected property name", 381 | )?; 382 | 383 | let key = match &key_token.token_type { 384 | TokenType::Identifier(s) | TokenType::String(s) => s, 385 | _ => unreachable!("Hi!! Welcome to my kitchen"), 386 | }; 387 | 388 | let value = if match_then_consume!(tokens, TokenType::Colon).is_some() { 389 | expression(tokens)? 390 | } else if let TokenType::Identifier(_) = key_token.token_type { 391 | Expr::Identifier(IdentifierValue { 392 | name: key_token.clone(), 393 | env_distance: Cell::default(), 394 | }) 395 | } else { 396 | return Err(ParseError { 397 | token: Some(key_token), 398 | message: "Cannot use short property declaration with string" 399 | .into(), 400 | }); 401 | }; 402 | 403 | properties.push(Property { 404 | key: key.clone(), 405 | value, 406 | }); 407 | 408 | if match_then_consume!(tokens, TokenType::Comma).is_none() { 409 | break; 410 | } 411 | } 412 | 413 | expect_one!(tokens, TokenType::RightBrace)?; 414 | 415 | Ok(Expr::Object(ObjectValue { blame, properties })) 416 | } 417 | 418 | pub fn parse_super( 419 | tokens: ParserIter, 420 | blame: Token, 421 | ) -> Result { 422 | let dummy_expr = Expr::Literal(LiteralValue::Nil); 423 | 424 | let accessor = match tokens.next().map(|next| next.token_type) { 425 | Some(TokenType::LeftParen) => { 426 | let call_expr = finish_call(tokens, dummy_expr)?; 427 | 428 | let arguments = if let Expr::Call(cv) = call_expr { 429 | cv.arguments 430 | } else { 431 | unreachable!("Call is not a call? Weird \u{1f633}") 432 | }; 433 | 434 | SuperAccessor::Call(arguments) 435 | } 436 | Some(TokenType::Dot) => { 437 | let name = expect!( 438 | tokens, 439 | TokenType::Identifier(_), 440 | "Expected a superclass method name", 441 | )?; 442 | 443 | SuperAccessor::Method(name) 444 | } 445 | _ => { 446 | return Err(ParseError { 447 | token: Some(blame), 448 | message: "Expected `.` or `(...args)` (constructor call) \ 449 | after `super`" 450 | .into(), 451 | }) 452 | } 453 | }; 454 | 455 | Ok(Expr::Super(SuperValue { 456 | blame, 457 | accessor, 458 | env_distance: Cell::new(0), 459 | })) 460 | } 461 | 462 | pub fn parse_list(tokens: ParserIter) -> Result { 463 | let mut values = Vec::new(); 464 | 465 | while !peek_matches!(tokens, TokenType::RightSquareBracket) { 466 | values.push(expression(tokens)?); 467 | 468 | if match_then_consume!(tokens, TokenType::Comma).is_none() { 469 | break; 470 | } 471 | } 472 | 473 | expect_one!(tokens, TokenType::RightSquareBracket)?; 474 | 475 | Ok(Expr::Literal(LiteralValue::List(Rc::new(values)))) 476 | } 477 | -------------------------------------------------------------------------------- /src/interpreter/expressions.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | env::InterpreterEnvironment, 3 | helpers::{ 4 | assume_identifier, 5 | bind_function, 6 | confirm_arity, 7 | construct_lox_defined_function, 8 | extract_subscription_index, 9 | guard_function, 10 | map_arguments, 11 | unwrap_list, 12 | }, 13 | interpret::{eval_expression, eval_statements}, 14 | types::{InterpreterFunction, InterpreterValue, RuntimeError}, 15 | }; 16 | use crate::{ 17 | ast::expr::{ 18 | AssignmentValue, 19 | BinaryValue, 20 | CallValue, 21 | Expr, 22 | FunctionValue, 23 | GetAccessor, 24 | GetValue, 25 | IdentifierValue, 26 | LiteralValue, 27 | ObjectValue, 28 | SetValue, 29 | SuperAccessor, 30 | SuperValue, 31 | ThisValue, 32 | UnaryValue, 33 | }, 34 | env::{DeclaredValue, EnvironmentWrapper}, 35 | token::{Location, Token, TokenType}, 36 | }; 37 | 38 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 39 | 40 | 41 | // inlining because it's used only once, but i wanted to take it 42 | // out of the context, to make it less cluttery 43 | #[inline] 44 | pub fn literal_expression( 45 | v: &LiteralValue, 46 | env: &InterpreterEnvironment, 47 | ) -> Result { 48 | match v { 49 | LiteralValue::String(s) => Ok(InterpreterValue::String(Rc::clone(s))), 50 | LiteralValue::Number(n) => Ok(InterpreterValue::Number(*n)), 51 | LiteralValue::True => Ok(InterpreterValue::True), 52 | LiteralValue::False => Ok(InterpreterValue::False), 53 | LiteralValue::Nil => Ok(InterpreterValue::Nil), 54 | LiteralValue::Char(c) => Ok(InterpreterValue::Char(*c)), 55 | LiteralValue::List(l) => { 56 | let values = 57 | l.iter() 58 | .map(|expr| eval_expression(expr, env)) 59 | .collect::, RuntimeError>>()?; 60 | 61 | Ok(InterpreterValue::List(Rc::new(RefCell::new(values)))) 62 | } 63 | } 64 | } 65 | 66 | #[inline] 67 | pub fn identifier_expression( 68 | v: &IdentifierValue, 69 | env: &E, 70 | ) -> Result 71 | where 72 | E: EnvironmentWrapper, 73 | { 74 | Ok(env.read(v.env_distance.get(), &v.name)?.value) 75 | } 76 | 77 | #[inline] 78 | pub fn assignment_expression( 79 | expr_evaluator: fn(&Expr, &E) -> Result, 80 | v: &AssignmentValue, 81 | env: &E, 82 | ) -> Result 83 | where 84 | E: EnvironmentWrapper, 85 | { 86 | env.assign( 87 | v.env_distance.get(), 88 | &v.name, 89 | expr_evaluator(&v.value, env)?, 90 | ) 91 | } 92 | 93 | #[inline] 94 | pub fn call_expression( 95 | v: &CallValue, 96 | env: &InterpreterEnvironment, 97 | ) -> Result { 98 | let callee = eval_expression(&v.calee, env)?; 99 | 100 | execute_call(&callee, &v.arguments, &v.closing_paren, env) 101 | } 102 | 103 | pub fn execute_call( 104 | callee: &InterpreterValue, 105 | arguments: &[Expr], 106 | blame: &Token, 107 | env: &InterpreterEnvironment, 108 | ) -> Result { 109 | match callee { 110 | InterpreterValue::Function { fun, enclosing_env } => { 111 | let arguments = arguments 112 | .iter() 113 | .map(|arg| eval_expression(arg, env)) 114 | .collect::, RuntimeError>>()?; 115 | 116 | match &**fun { 117 | InterpreterFunction::LoxDefined(fv) => { 118 | confirm_arity( 119 | fv.params.as_ref().map_or(0, |p| p.len()), 120 | arguments.len(), 121 | blame, 122 | )?; 123 | 124 | let fun_env = &enclosing_env.fork(); 125 | 126 | if let Some(params) = &fv.params { 127 | map_arguments(params, &arguments, fun_env); 128 | } 129 | 130 | if let Some(statements) = &fv.body { 131 | let e = eval_statements(&*statements, fun_env)?; 132 | Ok(guard_function(e)?) 133 | } else { 134 | Ok(InterpreterValue::Nil) 135 | } 136 | } 137 | InterpreterFunction::Native { arity, fun } => { 138 | confirm_arity(*arity, arguments.len(), blame)?; 139 | 140 | Ok(fun(blame, &enclosing_env.fork(), &arguments)?) 141 | } 142 | } 143 | } 144 | InterpreterValue::Class { constructor, .. } => { 145 | let instance = InterpreterValue::Instance { 146 | class: Some(Rc::new(callee.clone())), 147 | properties: Rc::new(RefCell::new(HashMap::new())), 148 | }; 149 | 150 | if let Some(constructor) = &constructor { 151 | let constructor = bind_function(constructor, instance.clone()); 152 | 153 | execute_call(&constructor, arguments, blame, env)?; 154 | } 155 | 156 | Ok(instance) 157 | } 158 | _ => Err(RuntimeError { 159 | message: format!("Cannot call {}", callee.human_type()), 160 | token: blame.clone(), 161 | }), 162 | } 163 | } 164 | 165 | #[inline] 166 | pub fn function_expression( 167 | v: &FunctionValue, 168 | env: &InterpreterEnvironment, 169 | ) -> InterpreterValue { 170 | let fun = construct_lox_defined_function(v, env); 171 | 172 | if let Some(t) = &v.name { 173 | let iden = assume_identifier(t); 174 | 175 | env.declare( 176 | iden.to_string(), 177 | DeclaredValue { 178 | mutable: false, 179 | value: fun.clone(), 180 | }, 181 | ); 182 | } 183 | 184 | fun 185 | } 186 | 187 | pub fn unary_expression( 188 | v: &UnaryValue, 189 | env: &InterpreterEnvironment, 190 | ) -> Result { 191 | let right_value = eval_expression(&v.right, env)?; 192 | 193 | match (&v.operator.token_type, &right_value) { 194 | (TokenType::Minus, InterpreterValue::Number(n)) => { 195 | Ok(InterpreterValue::Number(-n)) 196 | } 197 | (TokenType::Bang, InterpreterValue::True) => { 198 | Ok(InterpreterValue::False) 199 | } 200 | (TokenType::Bang, InterpreterValue::False) => { 201 | Ok(InterpreterValue::True) 202 | } 203 | 204 | _ => Err(RuntimeError { 205 | message: format!( 206 | "Cannot use `{}` on `{}`", 207 | v.operator.token_type, right_value 208 | ), 209 | token: v.operator.clone(), 210 | }), 211 | } 212 | } 213 | 214 | pub fn binary_experssion( 215 | v: &BinaryValue, 216 | env: &InterpreterEnvironment, 217 | ) -> Result { 218 | // first, match the logical operators, so that we can have short-circuiting 219 | match v.operator.token_type { 220 | TokenType::Or => { 221 | return Ok( 222 | if eval_expression(&v.left, env)? == InterpreterValue::True { 223 | InterpreterValue::True 224 | } else { 225 | eval_expression(&v.right, env)? 226 | }, 227 | ) 228 | } 229 | TokenType::And => { 230 | let left_value = eval_expression(&v.left, env)?; 231 | 232 | return Ok(if left_value == InterpreterValue::True { 233 | eval_expression(&v.right, env)? 234 | } else { 235 | left_value 236 | }); 237 | } 238 | _ => (), 239 | } 240 | 241 | // then eval_statement both sides normally 242 | let left_value = eval_expression(&v.left, env)?; 243 | let right_value = eval_expression(&v.right, env)?; 244 | 245 | // im sorry for this, but i found that the nested matches require 246 | // much simpler patterns, 247 | // and with this, i can achieve less comparisons overall 248 | match v.operator.token_type { 249 | TokenType::BangEqual => Ok((left_value != right_value).into()), 250 | TokenType::EqualEqual => Ok((left_value == right_value).into()), 251 | 252 | _ => match (&left_value, &right_value) { 253 | (InterpreterValue::Number(n1), InterpreterValue::Number(n2)) => { 254 | match v.operator.token_type { 255 | TokenType::Minus => Ok(InterpreterValue::Number(n1 - n2)), 256 | TokenType::Slash => Ok(InterpreterValue::Number(n1 / n2)), 257 | TokenType::Star => Ok(InterpreterValue::Number(n1 * n2)), 258 | TokenType::Plus => Ok(InterpreterValue::Number(n1 + n2)), 259 | TokenType::Greater => Ok((n1 > n2).into()), 260 | TokenType::GreaterEqual => Ok((n1 >= n2).into()), 261 | TokenType::Less => Ok((n1 < n2).into()), 262 | TokenType::LessEqual => Ok((n1 <= n2).into()), 263 | TokenType::Modulo => Ok(InterpreterValue::Number(n1 % n2)), 264 | 265 | _ => unreachable!("Scanner did a bad job \u{1f60e}."), 266 | } 267 | } 268 | (InterpreterValue::String(s1), InterpreterValue::String(s2)) => { 269 | if v.operator.token_type == TokenType::Plus { 270 | Ok(InterpreterValue::String(Rc::from(s1.to_string() + s2))) 271 | } else { 272 | Err(RuntimeError { 273 | message: format!( 274 | "You cannot use `{}` on two strings. Did you mean \ 275 | `+`?", 276 | v.operator.token_type 277 | ), 278 | token: v.operator.clone(), 279 | }) 280 | } 281 | } 282 | // error bby 283 | _ => Err(RuntimeError { 284 | message: format!( 285 | "Cannot use `{}` on {} and {}", 286 | v.operator.token_type, 287 | left_value.human_type(), 288 | right_value.human_type() 289 | ), 290 | token: v.operator.clone(), 291 | }), 292 | }, 293 | } 294 | } 295 | 296 | fn find_method( 297 | key: &str, 298 | class: &InterpreterValue, 299 | instance: &InterpreterValue, 300 | blame: &Token, 301 | ) -> Result { 302 | let (methods, superclass) = if let InterpreterValue::Class { 303 | methods, 304 | superclass, 305 | .. 306 | } = &class 307 | { 308 | (methods, superclass) 309 | } else { 310 | unreachable!("Class is not a class? \u{1f914}") 311 | }; 312 | 313 | if let Some(method) = methods.get(key) { 314 | Ok(bind_function(method, instance.clone())) 315 | } else if let Some(superclass) = &superclass { 316 | find_method(key, superclass, instance, blame) 317 | } else { 318 | Err(RuntimeError { 319 | message: format!( 320 | "Couldnt find property nor method with key {}", 321 | key 322 | ), 323 | token: blame.clone(), 324 | }) 325 | } 326 | } 327 | 328 | fn get_dot( 329 | v: &GetValue, 330 | env: &InterpreterEnvironment, 331 | ) -> Result { 332 | // auxiliary function used only once down below, that's why inlining is 333 | // completely justified 🥺 334 | #[inline] 335 | fn get_property( 336 | key: &str, 337 | properties: &HashMap, 338 | class: &Option>, 339 | instance: &InterpreterValue, 340 | blame: &Token, 341 | ) -> Result { 342 | if let Some(p) = properties.get(key) { 343 | Ok(p.clone()) 344 | } else if let Some(class) = class { 345 | find_method(key, class, instance, blame) 346 | } else { 347 | Err(RuntimeError { 348 | message: format!("Property {} not defined", key), 349 | token: blame.clone(), 350 | }) 351 | } 352 | } 353 | 354 | let getee = eval_expression(&v.getee, env)?; 355 | 356 | let (properties, class) = 357 | if let InterpreterValue::Instance { properties, class } = &getee { 358 | (properties, class) 359 | } else { 360 | return Err(RuntimeError { 361 | message: format!( 362 | "Can't access properties on {}", 363 | getee.human_type() 364 | ), 365 | token: v.blame.clone(), 366 | }); 367 | }; 368 | 369 | let borrowed_props = properties.borrow(); 370 | 371 | match &v.key { 372 | GetAccessor::DotName(iden) => { 373 | get_property(iden, &borrowed_props, class, &getee, &v.blame) 374 | } 375 | GetAccessor::DotEval(expr) => { 376 | let key = eval_expression(expr, env)?.to_string(); 377 | 378 | get_property(key.as_str(), &borrowed_props, class, &getee, &v.blame) 379 | } 380 | _ => unreachable!("Wrong accessor in dot"), 381 | } 382 | } 383 | 384 | fn get_subscription( 385 | v: &GetValue, 386 | env: &InterpreterEnvironment, 387 | ) -> Result { 388 | let getee_val = eval_expression(&v.getee, env)?; 389 | 390 | match getee_val { 391 | InterpreterValue::String(s) => { 392 | let index = 393 | extract_subscription_index(&v.key, &v.blame, s.len(), env)?; 394 | 395 | unsafe { 396 | Ok(InterpreterValue::Char(char::from( 397 | *s.as_bytes().get_unchecked(index), 398 | ))) 399 | } 400 | } 401 | InterpreterValue::List(l) => { 402 | let l_borrow = l.borrow(); 403 | 404 | let index = extract_subscription_index( 405 | &v.key, 406 | &v.blame, 407 | l_borrow.len(), 408 | env, 409 | )?; 410 | 411 | unsafe { Ok(l_borrow.get_unchecked(index).clone()) } 412 | } 413 | _ => Err(RuntimeError { 414 | message: format!("Cannot index {}", getee_val.human_type()), 415 | token: v.blame.clone(), 416 | }), 417 | } 418 | } 419 | 420 | #[inline] 421 | pub fn get_expression( 422 | v: &GetValue, 423 | env: &InterpreterEnvironment, 424 | ) -> Result { 425 | if matches!(v.key, GetAccessor::DotName(_) | GetAccessor::DotEval(_)) { 426 | get_dot(v, env) 427 | } else { 428 | get_subscription(v, env) 429 | } 430 | } 431 | 432 | fn set_dot( 433 | v: &SetValue, 434 | env: &InterpreterEnvironment, 435 | ) -> Result { 436 | let setee = eval_expression(&v.setee, env)?; 437 | 438 | let properties = if let InterpreterValue::Instance { properties, .. } = 439 | setee 440 | { 441 | properties 442 | } else { 443 | return Err(RuntimeError { 444 | message: format!("Can't set properties on {}", setee.human_type()), 445 | token: v.blame.clone(), 446 | }); 447 | }; 448 | 449 | let value = eval_expression(&v.value, env)?; 450 | 451 | let mut borrowed_props = properties.borrow_mut(); 452 | 453 | match &v.key { 454 | GetAccessor::DotName(key) => { 455 | borrowed_props.insert(key.to_string(), value.clone()); 456 | } 457 | GetAccessor::DotEval(expr) => { 458 | let key = eval_expression(expr, env)?.to_string(); 459 | borrowed_props.insert(key, value.clone()); 460 | } 461 | _ => unreachable!("How"), 462 | }; 463 | 464 | Ok(value) 465 | } 466 | 467 | fn set_subscription( 468 | v: &SetValue, 469 | env: &InterpreterEnvironment, 470 | ) -> Result { 471 | let setee = eval_expression(&v.setee, env)?; 472 | 473 | let mut l_borrow = unwrap_list( 474 | &setee, 475 | &v.blame, 476 | 0, 477 | Some( 478 | "Setting values by using the `[]` operator is allowed only on \ 479 | lists" 480 | .to_owned(), 481 | ), 482 | )?; 483 | 484 | let index = 485 | extract_subscription_index(&v.key, &v.blame, l_borrow.len(), env)?; 486 | 487 | let value = eval_expression(&v.value, env)?; 488 | 489 | l_borrow[index] = value.clone(); 490 | 491 | Ok(value) 492 | } 493 | 494 | #[inline] 495 | pub fn set_expression( 496 | v: &SetValue, 497 | env: &InterpreterEnvironment, 498 | ) -> Result { 499 | if matches!(v.key, GetAccessor::DotName(_) | GetAccessor::DotEval(_)) { 500 | set_dot(v, env) 501 | } else { 502 | set_subscription(v, env) 503 | } 504 | } 505 | 506 | #[inline] 507 | pub fn this_expression(v: &ThisValue, env: &E) -> Result 508 | where 509 | E: EnvironmentWrapper, 510 | { 511 | Ok(env.read(v.env_distance.get(), &v.blame)?.value) 512 | } 513 | 514 | pub fn super_expression( 515 | v: &SuperValue, 516 | env: &InterpreterEnvironment, 517 | ) -> Result { 518 | let env_distance = v.env_distance.get(); 519 | 520 | let superclass = env.read(env_distance, &v.blame)?.value; 521 | 522 | // resolver got us this far, so we believe it that env with bound `this` is 523 | // 1 env-hop closer to us 524 | let instance = env 525 | .read( 526 | env_distance - 1, 527 | &Token { 528 | location: Location { 529 | byte_offset: 0, 530 | byte_length: 0, 531 | }, 532 | token_type: TokenType::This, 533 | }, 534 | )? 535 | .value; 536 | 537 | match &v.accessor { 538 | SuperAccessor::Method(m) => { 539 | let name = assume_identifier(m); 540 | 541 | find_method(name, &superclass, &instance, m) 542 | } 543 | SuperAccessor::Call(args) => { 544 | let constructor = 545 | if let InterpreterValue::Class { constructor, .. } = superclass 546 | { 547 | constructor 548 | } else { 549 | unreachable!( 550 | "Superclass should be a class like come on \u{1f926}" 551 | ) 552 | }; 553 | 554 | let constructor = constructor.ok_or_else(|| RuntimeError { 555 | message: "Superclass does not have a constructor".into(), 556 | token: v.blame.clone(), 557 | })?; 558 | 559 | let constructor = bind_function(&constructor, instance); 560 | 561 | execute_call(&constructor, args, &v.blame, env) 562 | } 563 | } 564 | } 565 | 566 | pub fn object_expression( 567 | v: &ObjectValue, 568 | env: &InterpreterEnvironment, 569 | ) -> Result { 570 | let properties = v 571 | .properties 572 | .iter() 573 | .map(|p| { 574 | let value = eval_expression(&p.value, env)?; 575 | 576 | Ok((p.key.to_string(), value)) 577 | }) 578 | .collect::, RuntimeError>>()?; 579 | 580 | Ok(InterpreterValue::Instance { 581 | class: None, 582 | properties: Rc::new(RefCell::new(properties)), 583 | }) 584 | } 585 | --------------------------------------------------------------------------------