diff --git a/.gitignore b/.gitignore index f44aed3..f6e7bed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target notes.txt .gitignore -pkg/ \ No newline at end of file +pkg/ +web/node_modules +web/.next \ No newline at end of file diff --git a/gg.ai b/gg.ai new file mode 100644 index 0000000..49229d5 --- /dev/null +++ b/gg.ai @@ -0,0 +1,14 @@ + +{ + var b = 5; + print b; + + { + var c = 10; + print c; + } + + print c; +} + +print b; diff --git a/src/ast.rs b/src/ast.rs index ed27198..22cbd3d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,7 +2,7 @@ use crate::{ scanner::{Lexer, TokenType}, - vm::{self}, + vm, }; use std::fmt; @@ -16,7 +16,10 @@ pub enum ASTNode { Callee(String, Vec), // function call with arguments Let(String, Vec), Assign(String, Vec), + If(Vec, Vec, Option>), // condition, then, else + While(Vec, Vec), // condition, body Print(Vec), + Block(Vec), // depth, statements } #[derive(Debug, Clone, Copy, PartialEq)] @@ -77,7 +80,13 @@ impl<'a> Parser<'a> { let statement = match self.lexer.peek().token_type { TokenType::PRINT => self.parse_print(), TokenType::LET => self.parse_let(), - + TokenType::LeftBrace => { + self.lexer.next(); + let statements = self.parse(); + assert_eq!(self.lexer.next().token_type, TokenType::RightBrace); + ASTNode::Block(statements) + } + TokenType::RightBrace => break, // contains equal pr +=, -=, *=, /= TokenType::Identifier if self.lexer.peek_n_type(2).contains(&TokenType::EQUAL) @@ -96,6 +105,28 @@ impl<'a> Parser<'a> { } } } + TokenType::IF => { + self.lexer.next(); + assert_eq!(self.lexer.next().token_type, TokenType::LeftParen); + let condition = self.parse_expression(); + assert_eq!(self.lexer.next().token_type, TokenType::RightParen); + let then_branch = self.parse_block(); + let else_branch = if self.lexer.peek().token_type == TokenType::ELSE { + self.lexer.next(); + Some(self.parse_block()) + } else { + None + }; + ASTNode::If(vec![condition], then_branch, else_branch) + } + TokenType::WHILE => { + self.lexer.next(); + assert_eq!(self.lexer.next().token_type, TokenType::LeftParen); + let condition = self.parse_expression(); + assert_eq!(self.lexer.next().token_type, TokenType::RightParen); + let body = self.parse_block(); + ASTNode::While(vec![condition], body) + } TokenType::SEMICOLON => { self.lexer.next(); continue; @@ -149,6 +180,13 @@ impl<'a> Parser<'a> { Ok(ASTNode::Assign(identifier, vec![expr])) } + fn parse_block(&mut self) -> Vec { + assert_eq!(self.lexer.next().token_type, TokenType::LeftBrace); + let statements = self.parse(); + assert_eq!(self.lexer.next().token_type, TokenType::RightBrace); + statements + } + fn parse_expression(&mut self) -> ASTNode { expr_bp(self.lexer, 0) } @@ -344,9 +382,7 @@ fn infix_binding_power(op: Ops) -> Option<(u8, u8)> { use colored::*; impl fmt::Display for Ops { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { Ops::BinaryOp(BinaryOp::Add) => write!(f, "{}", "+".green()), Ops::BinaryOp(BinaryOp::Sub) => write!(f, "{}", "-".green()), @@ -395,9 +431,37 @@ impl fmt::Display for ASTNode { ASTNode::Let(identifier, expr) => { write!(f, "let {} = {}", identifier, expr[0]) } + ASTNode::Block(statements) => { + for stmt in statements { + write!(f, "{}", stmt)?; + } + write!(f, "") + } ASTNode::Assign(identifier, expr) => { write!(f, "{} = {}", identifier, expr[0]) } + ASTNode::If(condition, then_branch, else_branch) => { + write!(f, "if {} {{", condition[0])?; + for stmt in then_branch { + write!(f, "{}", stmt)?; + } + write!(f, "}}")?; + if !else_branch.is_none() { + write!(f, " else {{")?; + for stmt in else_branch { + write!(f, "{:?}", stmt)?; + } + write!(f, "}}")?; + } + write!(f, "") + } + ASTNode::While(condition, body) => { + write!(f, "while {} {{", condition[0])?; + for stmt in body { + write!(f, "{}", stmt)?; + } + write!(f, "}}") + } ASTNode::Op(head, rest) => { write!(f, "({}", head)?; for s in rest { diff --git a/src/chunk.rs b/src/chunk.rs index 0ce194b..8ccfe19 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,4 +1,4 @@ -use crate::value::ValueType; +use crate::{tensor::Tensor, value::ValueType}; #[derive(Debug, Clone, Copy)] #[repr(u8)] @@ -12,6 +12,8 @@ pub enum OpCode { OpSubtract, OpMultiply, OpDivide, + OpPower, + OpNot, OpEqualEqual, OpGreater, @@ -22,7 +24,14 @@ pub enum OpCode { OpDefineGlobal, OpGetGlobal, OpSetGlobal, - OpPower, + + OpDefineLocal, + OpGetLocal, + OpSetLocal, + + OpJumpIfFalse, + OpJump, + OpLoop, OpCall, } @@ -71,11 +80,13 @@ impl std::fmt::Display for OpCode { OpCode::OpSubtract => write!(f, "OP_SUBTRACT"), OpCode::OpMultiply => write!(f, "OP_MULTIPLY"), OpCode::OpDivide => write!(f, "OP_DIVIDE"), + OpCode::OpPower => write!(f, "OP_POWER"), + OpCode::OpNil => write!(f, "OP_NIL"), OpCode::OpTrue => write!(f, "OP_TRUE"), OpCode::OpFalse => write!(f, "OP_FALSE"), OpCode::OpNot => write!(f, "OP_NOT"), - OpCode::OpEqualEqual => write!(f, "OP_EQUAL"), + OpCode::OpEqualEqual => write!(f, "OP_EQUAL_EQUAL"), OpCode::OpGreater => write!(f, "OP_GREATER"), OpCode::OpLess => write!(f, "OP_LESS"), OpCode::OpPrint => write!(f, "OP_PRINT"), @@ -83,7 +94,15 @@ impl std::fmt::Display for OpCode { OpCode::OpDefineGlobal => write!(f, "OP_DEFINE_GLOBAL"), OpCode::OpGetGlobal => write!(f, "OP_GET_GLOBAL"), OpCode::OpSetGlobal => write!(f, "OP_SET_GLOBAL"), - OpCode::OpPower => write!(f, "OP_POWER"), + + OpCode::OpDefineLocal => write!(f, "OP_DEFINE_LOCAL"), + OpCode::OpGetLocal => write!(f, "OP_GET_LOCAL"), + OpCode::OpSetLocal => write!(f, "OP_SET_LOCAL"), + + OpCode::OpJumpIfFalse => write!(f, "OP_JUMP_IF_FALSE"), + OpCode::OpJump => write!(f, "OP_JUMP"), + OpCode::OpLoop => write!(f, "OP_LOOP"), + OpCode::OpCall => write!(f, "OP_CALL"), } } diff --git a/src/compiler.rs b/src/compiler.rs index 8a284a9..cbc9631 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,3 +1,5 @@ +// https://huyenchip.com/2021/09/07/a-friendly-introduction-to-machine-learning-compilers-and-optimizers.html + use crate::{ ast::{ASTNode, BinaryOp, Ops, PostfixOp, UnaryOp}, chunk::{Chunk, OpCode, VectorType}, @@ -6,12 +8,30 @@ use crate::{ value::ValueType, }; +#[derive(Debug, Clone, Default)] +struct Local { + name: String, + depth: u8, +} + +// impl display for Local +impl std::fmt::Display for Local { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}, {}", self.name, self.depth) + } +} + pub struct Compiler { chunk: Chunk, interner: Interner, + + // local variables + locals: Vec, + local_count: usize, + scope_depth: u8, } -// write a macro that can take single of multiple opcode and write it to the chunk, ( without mentioning self.chunk ) +// write a macro that can take single or multiple opcodes and write them to the chunk, (without mentioning self.chunk) macro_rules! write_op { ($chunk:expr, $($op:expr),*) => { $( $chunk.write(VectorType::Code($op)))* @@ -35,6 +55,9 @@ impl Compiler { Self { chunk: Chunk::new(), interner: Interner::default(), + locals: Vec::new(), + local_count: 0, + scope_depth: 0, } } @@ -67,11 +90,16 @@ impl Compiler { write_cons!(self.chunk, self.chunk.constants.len() - 1); } ASTNode::Identifier(iden) => { - write_op!(self.chunk, OpCode::OpGetGlobal); - let global = self - .chunk - .add_constant(ValueType::Identifier(self.interner.intern_string(iden))); - write_cons!(self.chunk, global); + if let Some(local) = self.resolve_local(&iden) { + write_op!(self.chunk, OpCode::OpGetLocal); + write_cons!(self.chunk, local); + } else { + write_op!(self.chunk, OpCode::OpGetGlobal); + let global = self + .chunk + .add_constant(ValueType::Identifier(self.interner.intern_string(iden))); + write_cons!(self.chunk, global); + } } ASTNode::Op(op, vec) => { for node in vec { @@ -88,23 +116,28 @@ impl Compiler { Ops::BinaryOp(BinaryOp::Eq) => write_op!(self.chunk, OpCode::OpEqualEqual), Ops::BinaryOp(BinaryOp::Ne) => { write_op!(self.chunk, OpCode::OpEqualEqual); - write_op!(self.chunk, OpCode::OpNot) + write_op!(self.chunk, OpCode::OpNot); } Ops::BinaryOp(BinaryOp::Lt) => write_op!(self.chunk, OpCode::OpLess), Ops::BinaryOp(BinaryOp::Le) => { write_op!(self.chunk, OpCode::OpGreater); - write_op!(self.chunk, OpCode::OpNot) + write_op!(self.chunk, OpCode::OpNot); + } + Ops::BinaryOp(BinaryOp::Gt) => { + write_op!(self.chunk, OpCode::OpGreater); } - Ops::BinaryOp(BinaryOp::Gt) => write_op!(self.chunk, OpCode::OpGreater), Ops::BinaryOp(BinaryOp::Ge) => { write_op!(self.chunk, OpCode::OpLess); - write_op!(self.chunk, OpCode::OpNot) + write_op!(self.chunk, OpCode::OpNot); + } + Ops::UnaryOp(UnaryOp::Negate) => { + write_op!(self.chunk, OpCode::OpNegate); } - Ops::UnaryOp(UnaryOp::Negate) => write_op!(self.chunk, OpCode::OpNegate), - Ops::PostfixOp(PostfixOp::StarStar) => write_op!(self.chunk, OpCode::OpPower), + Ops::PostfixOp(PostfixOp::StarStar) => { + write_op!(self.chunk, OpCode::OpPower); + } Ops::PostfixOp(PostfixOp::Call) => { - // self.chunk.write(VectorType::Code(OpCode::OpCall)); write_op!(self.chunk, OpCode::OpCall); self.chunk .write(VectorType::Constant(self.chunk.constants.len() - 1)); @@ -121,6 +154,19 @@ impl Compiler { ASTNode::Let(iden, expr) => { assert!(expr.len() == 1); + if self.scope_depth > 0 { + if self.local_count == 256 { + panic!("Too many local variables."); + } + self.locals.push(Local { + name: iden, + depth: self.scope_depth, + }); + self.local_count += 1; + self.visit(expr[0].clone()); + return; + } + let global = add_con!( self.chunk, ValueType::Identifier(self.interner.intern_string(iden)) @@ -131,14 +177,33 @@ impl Compiler { } ASTNode::Assign(iden, expr) => { assert!(expr.len() == 1); - - let global = add_con!( - self.chunk, - ValueType::Identifier(self.interner.intern_string(iden)) - ); self.visit(expr[0].clone()); - write_op!(self.chunk, OpCode::OpSetGlobal); - write_cons!(self.chunk, global); + + if let Some(local) = self.resolve_local(&iden) { + write_op!(self.chunk, OpCode::OpSetLocal); + write_cons!(self.chunk, local); + } else { + let global = add_con!( + self.chunk, + ValueType::Identifier(self.interner.intern_string(iden)) + ); + write_op!(self.chunk, OpCode::OpSetGlobal); + write_cons!(self.chunk, global); + } + } + ASTNode::Block(stmts) => { + self.scope_depth += 1; + for stmt in stmts { + self.visit(stmt); + } + self.scope_depth -= 1; + + while self.local_count > 0 + && self.locals[self.local_count - 1].depth > self.scope_depth + { + self.local_count -= 1; + write_op!(self.chunk, OpCode::OpPop); + } } ASTNode::Callee(iden, _) => { let global = add_con!( @@ -147,6 +212,78 @@ impl Compiler { ); write_cons!(self.chunk, global); } + ASTNode::If(cond, then, els) => { + assert_eq!(cond.len(), 1); + self.visit(cond[0].clone()); + + let else_jump_offset = self.chunk.code.len(); + write_op!(self.chunk, OpCode::OpJumpIfFalse); + add_con!(self.chunk, ValueType::JumpOffset(else_jump_offset)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + let else_jump_const_idx = add_con!(self.chunk, ValueType::JumpOffset(0)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + write_op!(self.chunk, OpCode::OpPop); + + then.iter().for_each(|stmt| { + println!("stmt: {:?}", stmt); + self.visit(stmt.clone()) + }); + + let jump_to_end = self.chunk.code.len(); + write_op!(self.chunk, OpCode::OpJump); + add_con!(self.chunk, ValueType::JumpOffset(jump_to_end)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + let jump_const_idx = add_con!(self.chunk, ValueType::JumpOffset(0)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + write_op!(self.chunk, OpCode::OpPop); + + let else_offset = self.chunk.code.len(); + self.chunk.constants[else_jump_const_idx] = ValueType::JumpOffset(else_offset - 1); + + // Compile the "else" block if it exists + if let Some(els) = els { + els.iter().for_each(|stmt| self.visit(stmt.clone())); + } + + let end_offset = self.chunk.code.len(); + self.chunk.constants[jump_const_idx] = ValueType::JumpOffset(end_offset); + } + ASTNode::While(cond, body) => { + let loop_start = self.chunk.code.len(); + + assert_eq!(cond.len(), 1); + self.visit(cond[0].clone()); + + let exit_jump_offset = self.chunk.code.len(); + write_op!(self.chunk, OpCode::OpJumpIfFalse); + add_con!(self.chunk, ValueType::JumpOffset(exit_jump_offset)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + let exit_jump_const_idx = add_con!(self.chunk, ValueType::JumpOffset(0)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + write_op!(self.chunk, OpCode::OpPop); + + body.iter().for_each(|stmt| self.visit(stmt.clone())); + + let loop_jump_offset = self.chunk.code.len(); + write_op!(self.chunk, OpCode::OpLoop); + add_con!(self.chunk, ValueType::JumpOffset(loop_jump_offset)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + add_con!(self.chunk, ValueType::JumpOffset(loop_start)); + write_cons!(self.chunk, self.chunk.constants.len() - 1); + write_op!(self.chunk, OpCode::OpPop); + + let exit_offset = self.chunk.code.len(); + self.chunk.constants[exit_jump_const_idx] = ValueType::JumpOffset(exit_offset - 1); + } + } + } + + fn resolve_local(&self, name: &String) -> Option { + for i in (0..self.local_count).rev() { + if self.locals[i].name == *name { + return Some(i); + } } + None } } diff --git a/src/debug.rs b/src/debug.rs index d8d5038..26d4da1 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,16 +1,18 @@ -use crate::chunk; +use crate::{chunk, interner::Interner, value::ValueType}; use colored::*; pub struct Debug { name: String, + interner: Interner, chunk: chunk::Chunk, } impl Debug { - pub fn new(name: &str, chunk: chunk::Chunk) -> Self { + pub fn new(name: &str, chunk: chunk::Chunk, interner: Interner) -> Self { Self { name: name.to_string(), chunk, + interner, } } @@ -63,26 +65,66 @@ impl Debug { | chunk::OpCode::OpDefineGlobal | chunk::OpCode::OpGetGlobal | chunk::OpCode::OpSetGlobal + | chunk::OpCode::OpDefineLocal + | chunk::OpCode::OpGetLocal + | chunk::OpCode::OpSetLocal | chunk::OpCode::OpCall, ) => { let constant = self.chunk.code[offset + 1]; match constant { - chunk::VectorType::Constant(idx) => { + chunk::VectorType::Constant(idx) => match self.chunk.constants[idx] { + ValueType::String(s) | ValueType::Identifier(s) => { + println!( + "{:04} {} {:20} | {}{}", + offset.to_string().yellow(), + instruction.to_string().red(), + constant.to_string().green().italic(), + "intr->".purple().magenta().italic(), + self.interner.lookup(s).purple().magenta().italic() + ); + } + _ => { + println!( + "{:04} {} {:20} | {}", + offset.to_string().yellow(), + instruction.to_string().red(), + constant.to_string().green().italic(), + self.chunk.constants[idx] + .to_string() + .purple() + .magenta() + .italic() + ); + } + }, + _ => { + unreachable!(); + } + } + return offset + 2; + } + chunk::VectorType::Code(chunk::OpCode::OpJump | chunk::OpCode::OpJumpIfFalse | chunk::OpCode::OpLoop) => { + let current_location = self.chunk.code[offset + 1]; + let jump_offset = self.chunk.code[offset + 2]; + + if let chunk::VectorType::Constant(loc) = current_location { + if let chunk::VectorType::Constant(jump) = jump_offset { println!( - "{:04} {} {:04} | {}", + "{:04} {} | {}->{}", offset.to_string().yellow(), instruction.to_string().red(), - constant.to_string().green().italic(), - self.chunk.constants[idx] + self.chunk.constants[loc] .to_string() .purple() .magenta() - .italic() + .italic(), + self.chunk.constants[jump] + .to_string() + .purple() + .magenta() + .italic(), ); } - _ => { - println!("{:04} {} {:04}", offset, instruction, constant); - } } return offset + 2; } diff --git a/src/lib.rs b/src/lib.rs index fc10910..6fb7bb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub fn run_source(src: &str) -> String { let (bytecode, interner) = compiler.compile(out); println!("{:?}", bytecode); - let debug = debug::Debug::new("test", bytecode.clone()); + let debug = debug::Debug::new("test", bytecode.clone(), interner.clone()); debug.disassemble(); let mut vm = vm::VM::init(bytecode, interner); @@ -33,3 +33,10 @@ pub fn run_source(src: &str) -> String { format!("{:?}", result) } + +#[test] +fn test_run_source() { + let src = "let a = 1 + 2 * 3;print(a);"; + let result = run_source(src); + assert_eq!(result, "Ok([Tensor(7)])"); +} diff --git a/src/main.rs b/src/main.rs index 747e105..c3b5f22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,21 +88,21 @@ pub fn run_source(src: &str) -> Result { let out = Parser::new(&mut lexer).parse(); for stmt in out.iter() { - println!("{}", stmt); + println!("{:?}", stmt); } println!("-------------"); let mut compiler = compiler::Compiler::new(); let (bytecode, interner) = compiler.compile(out); - // println!("{:?}", bytecode); + println!("{:?}", bytecode); - let debug = debug::Debug::new("test", bytecode.clone()); + let debug = debug::Debug::new("test", bytecode.clone(), interner.clone()); debug.disassemble(); let mut vm = vm::VM::init(bytecode, interner); let result = vm.run(); - // println!("{:?}", result); + println!("{:?}", result); return result; } @@ -136,4 +136,40 @@ mod tests { Result::Ok(vec![ValueType::Tensor(Tensor::from(24.70408163265306))]) ); } + + #[test] + fn test_scopes() { + let src = r#" + let a = 4; + { + let b = 5; + print(b); + { + let c = 10; + print(c); + let b = 353; + print(b); + } + print(b); + b = 11; + print(b); + a = 12; + } + print(a); + "#; + + let out = run_source(&src); + + assert_eq!( + out, + Result::Ok(vec![ + ValueType::Tensor(Tensor::from(5.0)), + ValueType::Tensor(Tensor::from(10.0)), + ValueType::Tensor(Tensor::from(353.0)), + ValueType::Tensor(Tensor::from(5.0)), + ValueType::Tensor(Tensor::from(11.0)), + ValueType::Tensor(Tensor::from(12.0)) + ]) + ); + } } diff --git a/src/tensor.rs b/src/tensor.rs index de300a2..bd92f8c 100644 --- a/src/tensor.rs +++ b/src/tensor.rs @@ -9,7 +9,7 @@ use std::{ rc::Rc, }; -#[derive(Clone, Eq, Debug, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct Tensor(Rc>); impl std::fmt::Display for Tensor { @@ -18,6 +18,13 @@ impl std::fmt::Display for Tensor { } } +// debug print +impl std::fmt::Debug for Tensor { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.borrow().data) + } +} + impl Tensor { pub fn from(t: T) -> Tensor where diff --git a/src/value.rs b/src/value.rs index b678ed8..8cc359c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -8,16 +8,19 @@ pub enum ValueType { Boolean(bool), Nil, // Lists, Dicts, Tensors, etc. + + JumpOffset(usize), } impl std::fmt::Display for ValueType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - ValueType::Tensor(n) => write!(f, "tensor->{}", n), + ValueType::Tensor(n) => write!(f, "tnsr->{}", n), ValueType::String(s) => write!(f, "str->{}", s), ValueType::Identifier(s) => write!(f, "iden->{}", s), ValueType::Boolean(b) => write!(f, "bool->{}", b), ValueType::Nil => write!(f, "nil"), + ValueType::JumpOffset(j) => write!(f, "jmp->{}", j), } } } diff --git a/src/vm.rs b/src/vm.rs index 0cd90a8..9fa1dc2 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,4 +1,4 @@ -use std::{any::Any, clone, collections::HashMap}; +use std::collections::HashMap; use thiserror::Error; use crate::{ @@ -38,15 +38,12 @@ pub enum Result { RuntimeErr(String), } -// write code to shrink chunk::VectorType::Code(chunk::OpCode::OpReturn) to Code(OpReturn) - impl VM { - // pub(crate) fn init(chunk: Chunk) -> VM { pub(crate) fn init(chunk: Chunk, interner: Interner) -> VM { VM { chunk, ip: 0, - stack: core::array::from_fn(|i| ValueType::Nil), + stack: core::array::from_fn(|_| ValueType::Nil), stack_top: 0, interner, globals: HashMap::new(), @@ -68,20 +65,18 @@ impl VM { }; } - /// Macro to generate the opcode enum `opcode!(OpReturn)` to `chunk::VectorType::Code(chunk::OpCode::OpReturn)` macro_rules! opcode { ($op:ident) => { chunk::VectorType::Code(chunk::OpCode::$op) }; } - /// Macro to get the constant from the chunk macro_rules! get_constant { ($index:expr) => { match $index { chunk::VectorType::Constant(idx) => self.read_constant(idx as usize), _ => { - return Result::RuntimeErr("Invalid constant index".to_string()); + return Result::RuntimeErr(format!("Invalid constant '{}'", $index)); } } }; @@ -139,9 +134,11 @@ impl VM { let a = pop!(); push!(ValueType::Boolean(a == b)); } + // TODO: Not working for now opcode!(OpGreater) => { let b = pop!(); let a = pop!(); + println!("a: {:?}", a > b); push!(ValueType::Boolean(a > b)); } opcode!(OpLess) => { @@ -161,6 +158,38 @@ impl VM { let constant = get_constant!(self.read_byte()); push!(constant); } + opcode!(OpJumpIfFalse) => { + self.read_byte(); + let offset = self.read_byte(); + let value = self.peek(0); + + if let ValueType::Boolean(false) = value { + if let VectorType::Constant(idx) = offset { + if let ValueType::JumpOffset(offset) = self.read_constant(idx as usize) + { + self.ip = offset; + } + } + } + } + opcode!(OpJump) => { + self.read_byte(); + let offset = self.read_byte(); + if let VectorType::Constant(idx) = offset { + if let ValueType::JumpOffset(offset) = self.read_constant(idx as usize) { + self.ip = offset + } + } + } + opcode!(OpLoop) => { + self.read_byte(); + let offset = self.read_byte(); + if let VectorType::Constant(idx) = offset { + if let ValueType::JumpOffset(offset) = self.read_constant(idx as usize) { + self.ip = offset + } + } + } opcode!(OpDefineGlobal) => { let constant = get_constant!(self.read_byte()); let value = self.peek(0); @@ -179,11 +208,17 @@ impl VM { if let Some(value) = value { push!(value.clone()); } else { - return Result::RuntimeErr("Undefined global variable".to_string()); + return Result::RuntimeErr(format!( + "Undefined variable '{}'", + self.interner.lookup(idx) + )); } } _ => { - return Result::RuntimeErr("Invalid global variable".to_string()); + return Result::RuntimeErr(format!( + "Invalid global variable '{}'", + constant + )); } } } @@ -198,7 +233,36 @@ impl VM { // TODO - only set the value if it exists } _ => { - return Result::RuntimeErr("Invalid global variable".to_string()); + return Result::RuntimeErr(format!( + "Invalid global variable '{}'", + constant + )); + } + } + } + opcode!(OpGetLocal) => { + let slot = self.read_byte(); + + match slot { + VectorType::Constant(idx) => { + let value = self.stack[idx as usize].clone(); + push!(value); + } + _ => { + return Result::RuntimeErr(format!("Invalid slot '{}'", slot)); + } + } + } + opcode!(OpSetLocal) => { + let slot = self.read_byte(); + + match slot { + VectorType::Constant(idx) => { + let value = self.peek(0); + self.stack[idx as usize] = value; + } + _ => { + return Result::RuntimeErr(format!("Invalid slot '{}'", slot)); } } } @@ -231,14 +295,22 @@ impl VM { } } } - VectorType::Constant(_) => {} + _ => { + return { + if let chunk::VectorType::Constant(idx) = instruction { + let value = self.read_constant(idx as usize); + println!("Constant: {:?}", value); + } + + Result::RuntimeErr(format!("Invalid opcode '{}'", instruction)) + }; + } } } } - // Reads the byte currently pointed at by ip and then advances the instruction pointer - book fn read_byte(&mut self) -> VectorType { - let byte = self.chunk.code[self.ip]; + let byte = self.chunk.code[self.ip].clone(); self.ip += 1; return byte; } diff --git a/test.ai b/test.ai index dae7944..815d4d7 100644 --- a/test.ai +++ b/test.ai @@ -1,42 +1,10 @@ -// x[0+2][1] - -// x.relu(a.b(0+2), 2-1).max(0)/2 -// x.relu(0, 1, "sdfsd", false) -// x.f(1 > 2, 3 != 3) - - -let a = -4.0; -let b = 2.0; -let c = a + b; -let d = a * b + b**3; -c += c + 1; -c += 1 + c + (-a); -d += d * 2 + (b + a).relu(); -d += 3 * d + (b - a).relu(); -let e = c - d; -let f = e**2; -let g = f / 2.0; -g += 10.0 / f; - - -print(g); // prints 24.7041, the outcome of this forward pass -g.backward(); -print(a.grad()); // prints 138.8338, i.e. the numerical value of dg/da -print(b.grad()); // prints 138.8338, i.e. the numerical value of dg/da - -// f@g - -// 1 or 2 - -// Op(PostfixOp(Call), [Identifier("a"), Callee([Calle("relu")], [])]) - -// print(a); -// print(a <= 4); - -// let breakfast = "beignets"; -//let beverage = "cafe au lait"; -//breakfast = "beignets with " + beverage; -//print(breakfast); - -// Ops(PostfixOp(Call), [Identifier("x"), Ops(PostfixOp(Args), [Ops(PostfixOp(Call), [Identifier("a"), Ops(PostfixOp(Args), [Number(0.0)])]), Number(2.0)])]) -// Op(PostfixOp(Call), [Identifier("x"), Callee("f", [Op(BinaryOp(Gt), [Number(1.0), Number(2.0)])])]) \ No newline at end of file +//if (2 == 2) { +// print("gg"); +//} else { +// print(1); +// print("ggg"); +//} + +//while (3 > 2) { +// print("gg"); +//} \ No newline at end of file diff --git a/tests/loc.rs b/tests/loc.rs index 98266f8..57e22fc 100644 --- a/tests/loc.rs +++ b/tests/loc.rs @@ -1,3 +1,7 @@ +// https://brenocon.com/dean_perf.html +// https://vercel.com/blog/latency-numbers-every-web-developer-should-know + + // Lines of Code use tokei::{Config, LanguageType, Languages}; diff --git a/web/app/Editor.tsx b/web/app/Editor.tsx new file mode 100644 index 0000000..f05b20a --- /dev/null +++ b/web/app/Editor.tsx @@ -0,0 +1,51 @@ +"use client"; + +import Editor from "@monaco-editor/react"; +export default async function IDE() { + const exports = await import("../../pkg/ai"); + const { run_source: run_source } = exports; + + + const handleSubmit = async ( + event: React.FormEvent + ) => { + event.preventDefault(); + + let code = event.target[0].value; + let output = run_source(code); + + console.log(output); + } + + return ( +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/web/app/page.tsx b/web/app/page.tsx index 5705d4e..e2b31cf 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,113 +1,9 @@ -import Image from "next/image"; +import IDE from "./Editor"; export default function Home() { return (
-
-

- Get started by editing  - app/page.tsx -

- -
- -
- Next.js Logo -
- - +
); } diff --git a/web/bun.lockb b/web/bun.lockb old mode 100644 new mode 100755 index 8896b56..37d078b Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/next-env.d.ts b/web/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/web/next.config.mjs b/web/next.config.mjs index 4678774..b12cc4d 100644 --- a/web/next.config.mjs +++ b/web/next.config.mjs @@ -1,4 +1,18 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + webpack(config, { isServer, dev }) { + // Use the client static directory in the server bundle and prod mode + // Fixes `Error occurred prerendering page "/"` + config.output.webassemblyModuleFilename = + isServer && !dev + ? "../static/wasm/[modulehash].wasm" + : "static/wasm/[modulehash].wasm"; + + // Since Webpack 5 doesn't enable WebAssembly by default, we should do it manually + config.experiments = { ...config.experiments, asyncWebAssembly: true }; + + return config; + }, +}; export default nextConfig; diff --git a/web/package.json b/web/package.json index 51b1687..9059dc4 100644 --- a/web/package.json +++ b/web/package.json @@ -9,24 +9,26 @@ "lint": "next lint" }, "dependencies": { + "@monaco-editor/react": "^4.6.0", "@radix-ui/react-icons": "^1.3.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "monaco-editor": "^0.49.0", "next": "14.2.2", - "react": "^18", - "react-dom": "^18", + "react": "^18.3.1", + "react-dom": "^18.3.1", "shadcn-ui": "^0.8.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "typescript": "^5", - "@types/node": "^20", - "@types/react": "^18", - "@types/react-dom": "^18", - "postcss": "^8", - "tailwindcss": "^3.4.1", - "eslint": "^8", + "typescript": "^5.4.5", + "@types/node": "^20.14.2", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.4", + "eslint": "^8.57.0", "eslint-config-next": "14.2.2" } }