From 82ac6744ca828764ea6c7e15d1c6a7623353caa8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 28 May 2025 16:37:25 -0400 Subject: [PATCH] `or` and `and` are now reserved words --- src/compiler.rs | 152 +++++++++++++++++++++++++++++++++++++++++++---- src/lexer.rs | 2 +- src/main.rs | 5 +- src/parser.rs | 11 +++- src/validator.rs | 2 +- src/value.rs | 2 +- src/vm.rs | 29 ++++++++- 7 files changed, 183 insertions(+), 20 deletions(-) diff --git a/src/compiler.rs b/src/compiler.rs index 73b2d5c..052d0b3 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -5,7 +5,6 @@ use chumsky::prelude::SimpleSpan; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::FromPrimitive; use std::cell::OnceCell; -use std::collections::HashMap; use std::rc::Rc; #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)] @@ -17,11 +16,13 @@ pub enum Op { Constant, Jump, JumpIfFalse, + JumpIfTrue, Pop, PopN, PushBinding, Store, StoreAt, + Stash, Load, ResetMatch, MatchNil, @@ -53,6 +54,8 @@ pub enum Op { Truncate, MatchDepth, + Call, + Eq, Add, Sub, @@ -63,6 +66,46 @@ pub enum Op { Assert, Get, At, + + Not, + // Inc, + // Dec, + // Gt, + // Gte, + // Lt, + // Lte, + // Mod, + // Round, + // Ceil, + // Floor, + // Random, + // Sqrt, + + // Assoc, + // Concat, + // Conj, + // Count, + // Disj, + // Dissoc, + // Range, + // Rest, + // Slice, + + // "atan_2" math/atan2 + // "chars" chars + // "cos" math/cos + // "doc" doc + // "downcase" string/ascii-lower + // "pi" math/pi + // "show" show + // "sin" math/sin + // "split" string/split + // "str_slice" string/slice + // "tan" math/tan + // "trim" string/trim + // "triml" string/triml + // "trimr" string/trimr + // "upcase" string/ascii-upper } impl std::fmt::Display for Op { @@ -76,11 +119,13 @@ impl std::fmt::Display for Op { Constant => "constant", Jump => "jump", JumpIfFalse => "jump_if_false", + JumpIfTrue => "jump_if_true", Pop => "pop", PopN => "pop_n", PushBinding => "push_binding", Store => "store", StoreAt => "store_at", + Stash => "stash", Load => "load", MatchNil => "match_nil", MatchTrue => "match_true", @@ -122,6 +167,10 @@ impl std::fmt::Display for Op { Assert => "assert", Get => "get", At => "at", + + Not => "not", + + Call => "call", }; write!(f, "{rep}") } @@ -147,10 +196,10 @@ impl Chunk { let op = Op::from_u8(self.bytecode[i]).unwrap(); use Op::*; match op { - Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse + Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse | PanicIfNoMatch | MatchWord | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | Duplicate | Decrement | Truncate | Noop | LoadTuple | LoadList | Eq - | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At => { + | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -159,8 +208,8 @@ impl Chunk { println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); } PushBinding | MatchTuple | MatchList | MatchDict | LoadDictValue | PushTuple - | PushDict | PushList | PushBox | Jump | JumpIfFalse | JumpIfNoMatch | JumpIfMatch - | JumpBack | JumpIfZero | MatchDepth | PopN | StoreAt => { + | PushDict | PushList | PushBox | Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch + | JumpIfMatch | JumpBack | JumpIfZero | MatchDepth | PopN | StoreAt | Call => { let next = self.bytecode[i + 1]; println!("{i:04}: {:16} {next:04}", op.to_string()); } @@ -201,6 +250,8 @@ fn get_builtin(name: &str, arity: usize) -> Option { ("assert!", 1) => Some(Op::Assert), ("get", 2) => Some(Op::Get), ("at", 2) => Some(Op::At), + ("not", 1) => Some(Op::Not), + _ => None, } } @@ -697,6 +748,46 @@ impl Compiler { self.emit_op(Op::GetKey); self.stack_depth -= 1; } + (Or, Arguments(args)) => { + let stack_depth = self.stack_depth; + let mut jump_idxes = vec![]; + if !args.is_empty() { + for arg in args { + self.visit(arg); + self.emit_op(Op::Stash); + self.emit_op(Op::JumpIfTrue); + jump_idxes.push(self.len()); + self.emit_byte(0xff); + } + for idx in jump_idxes { + self.chunk.bytecode[idx] = (self.len() - idx + 2) as u8; + } + self.emit_op(Op::Load); + } else { + self.emit_op(Op::False); + } + self.stack_depth = stack_depth + 1; + } + (And, Arguments(args)) => { + let stack_depth = self.stack_depth; + let mut jump_idxes = vec![]; + if !args.is_empty() { + for arg in args { + self.visit(arg); + self.emit_op(Op::Stash); + self.emit_op(Op::JumpIfFalse); + jump_idxes.push(self.len()); + self.emit_byte(0xff); + } + for idx in jump_idxes { + self.chunk.bytecode[idx] = (self.len() - idx + 2) as u8; + } + self.emit_op(Op::Load); + } else { + self.emit_op(Op::True); + } + self.stack_depth = stack_depth + 1; + } (Word(fn_name), Arguments(args)) => match get_builtin(fn_name, args.len()) { Some(code) => { for arg in args { @@ -705,7 +796,28 @@ impl Compiler { self.emit_op(code); self.stack_depth -= args.len() - 1; } - None => todo!(), + None => { + //algo + // visit all the args + // then store them + // then call the function + for (register_slot, arg) in args.iter().enumerate() { + self.visit(arg); + self.emit_op(Op::StoreAt); + self.emit_byte(register_slot); + } + + self.emit_op(Op::PushBinding); + self.stack_depth += 1; + let biter = self.bindings.iter().rev(); + for binding in biter { + if binding.name == *fn_name { + self.emit_byte(binding.stack_pos); + break; + } + } + self.emit_op(Op::Call); + } }, _ => unreachable!(), } @@ -817,6 +929,21 @@ impl Compiler { self.emit_constant(init_val); self.bind(name); + let FnBody(fn_body) = &body.as_ref().0 else { + unreachable!() + }; + + let mut chunks = vec![]; + + for clause in fn_body { + let MatchClause(pattern, guard, clause_body) = &clause.0 else { + unreachable!() + }; + let TuplePattern(pattern) = &pattern.0 else { + unreachable!() + }; + let arity = pattern.len(); + } // compile the function let mut compiler = Compiler::new(body, self.name, self.src); compiler.compile(); @@ -828,7 +955,7 @@ impl Compiler { let lfn = crate::value::LFn { name, doc: *doc, - chunk: compiler.chunk, + chunks, closed: vec![], }; @@ -976,6 +1103,7 @@ impl Compiler { | InterpolatedPattern(..) | AsPattern(..) | Splattern(..) => todo!(), + And | Or => unreachable!(), } } @@ -987,10 +1115,11 @@ impl Compiler { let op = Op::from_u8(*byte).unwrap(); use Op::*; match op { - Noop | Pop | Store | Load | Nil | True | False | MatchNil | MatchTrue + Noop | Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse | MatchWord | ResetMatch | PanicIfNoMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf | Duplicate | Truncate | Decrement | LoadTuple - | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At => { + | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At + | Not => { println!("{i:04}: {op}") } Constant | MatchConstant => { @@ -999,8 +1128,9 @@ impl Compiler { println!("{i:04}: {:16} {next:04}: {value}", op.to_string()); } PushBinding | MatchTuple | MatchList | PushTuple | PushDict | PushList - | MatchDict | LoadDictValue | PushBox | Jump | JumpIfFalse | JumpIfNoMatch - | JumpIfMatch | JumpBack | JumpIfZero | MatchDepth | PopN | StoreAt => { + | MatchDict | LoadDictValue | PushBox | Jump | JumpIfFalse | JumpIfTrue + | JumpIfNoMatch | JumpIfMatch | JumpBack | JumpIfZero | MatchDepth | PopN + | StoreAt | Call => { let (_, next) = codes.next().unwrap(); println!("{i:04}: {:16} {next:04}", op.to_string()); } diff --git a/src/lexer.rs b/src/lexer.rs index 5aecf26..50ca0ec 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -56,7 +56,7 @@ pub fn lexer( "nil" => Token::Nil, // todo: hard code these as type constructors "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" => Token::Reserved(word), + | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" => Token::Reserved(word), _ => Token::Word(word), }); diff --git a/src/main.rs b/src/main.rs index f9de812..833f275 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::env; const DEBUG_COMPILE: bool = true; const DEBUG_RUN: bool = true; -mod memory_sandbox; +// mod base; mod spans; use crate::spans::Spanned; @@ -74,8 +74,7 @@ pub fn run(src: &'static str) { pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = " -let foo = #{:a 1, :b 2} -:a (foo) +or (true, true, 42) "; run(src); } diff --git a/src/parser.rs b/src/parser.rs index 1255f5a..d698e1b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -63,6 +63,9 @@ pub enum Ast { // may come in handy? Error, + And, + Or, + // expression nodes Placeholder, Nil, @@ -121,6 +124,8 @@ impl fmt::Display for Ast { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Ast::*; match self { + And => write!(f, "And"), + Or => write!(f, "Or"), Error => write!(f, "Error"), Nil => write!(f, "nil"), String(s) => write!(f, "String: \"{}\"", s), @@ -727,7 +732,11 @@ where .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) .map_with(|args, e| (Arguments(args), e.span())); - let synth_root = word.or(keyword); + let or = just(Token::Reserved("or")).map_with(|_, e| (Or, e.span())); + + let and = just(Token::Reserved("and")).map_with(|_, e| (And, e.span())); + + let synth_root = or.or(and).or(word).or(keyword); let synth_term = keyword.or(args); diff --git a/src/validator.rs b/src/validator.rs index a788f90..24d672c 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -570,7 +570,7 @@ impl<'a> Validator<'a> { } PairPattern(_, patt) => self.visit(patt.as_ref()), // terminals can never be invalid - Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) => (), + Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (), // terminal patterns can never be invalid NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..) | KeywordPattern(..) | PlaceholderPattern => (), diff --git a/src/value.rs b/src/value.rs index 5cb5512..fd2ee3a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -13,7 +13,7 @@ pub struct LFn { // pub has_run: bool, // pub input: &'static str, // pub src: &'static str, - pub chunk: Chunk, + pub chunks: Vec<(u8, Chunk)>, pub closed: Vec, } diff --git a/src/vm.rs b/src/vm.rs index 419df12..5bb0804 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -34,7 +34,7 @@ pub struct Vm<'a> { pub stack: Vec, pub chunk: &'a Chunk, pub ip: usize, - pub return_register: [Value; 8], + pub return_register: [Value; 7], pub matches: bool, pub match_depth: u8, pub result: Option>, @@ -54,7 +54,6 @@ impl<'a> Vm<'a> { Value::Nothing, Value::Nothing, Value::Nothing, - Value::Nothing, ], matches: false, match_depth: 0, @@ -158,6 +157,18 @@ impl<'a> Vm<'a> { } } } + JumpIfTrue => { + let jump_len = self.chunk.bytecode[self.ip + 1]; + let cond = self.pop(); + match cond { + Value::Nil | Value::False => { + self.ip += 2; + } + _ => { + self.ip += jump_len as usize + 2; + } + } + } JumpIfZero => { let jump_len = self.chunk.bytecode[self.ip + 1]; let cond = self.pop(); @@ -198,6 +209,10 @@ impl<'a> Vm<'a> { self.return_register[i] = self.pop(); self.ip += 2; } + Stash => { + self.return_register[0] = self.peek().clone(); + self.ip += 1; + } Load => { let mut i = 0; while i < 8 && self.return_register[i] != Value::Nothing { @@ -530,6 +545,16 @@ impl<'a> Vm<'a> { self.stack.push(value); self.ip += 1; } + Not => { + let value = self.pop(); + let negated = match value { + Value::Nil | Value::False => Value::True, + _ => Value::False, + }; + self.push(negated); + self.ip += 1; + } + Call => todo!(), } } }