diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c3332fd --- /dev/null +++ b/TODO.md @@ -0,0 +1,12 @@ +# TODO ++ function calling ++ functions ++ mutable bindings ++ arrays ++ type syntax ++ error handling ++ pattern matching ++ closures ++ structs & tuples ++ loops ++ proper vm diff --git a/src/interpret.rs b/src/interpret.rs index dc42656..3534a46 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -1,12 +1,12 @@ + use crate::lex::{ TeaValue, Operator }; -use crate::parse::{ ASTNode, Value }; +use crate::parse::{ ASTNode, Value, KeywordFunction }; use std::collections::HashMap; #[derive(Debug)] -enum VarType { +pub enum VarType { Const(TeaValue), - //Mut, } enum BlockType { @@ -16,7 +16,7 @@ enum BlockType { #[derive(Debug)] pub struct InterpreterError { - description: String, + pub description: String, } impl InterpreterError { @@ -47,6 +47,7 @@ fn get_value(v: Value, scopes: &mut [Scope]) -> Result Err(InterpreterError::new("not yet implemented")) } } @@ -59,24 +60,49 @@ macro_rules! apply_op { (TeaValue::Int(aa), TeaValue::Int(bb)) => { TeaValue::Int(aa $op bb) }, - //(TeaValue::Uint(aa), TeaValue::Uint(bb)) => { - // TeaValue::Uint(aa $op bb) - //}, + (TeaValue::Uint(aa), TeaValue::Uint(bb)) => { + TeaValue::Uint(aa $op bb) + }, _ => panic!("Invalid types"), } } } -type Scope = HashMap; +pub type Scope = HashMap; fn interpret_block(ast: Vec, scopes: &mut Vec, block_type: BlockType) -> Result, InterpreterError> { + //println!("{ast:?}\n"); let mut ast = ast.clone(); ast.reverse(); let mut stack = vec![]; - //let mut scope = Scope::new(); loop { match ast.pop() { None => return Ok(stack), + Some(ASTNode::ArgColl(count)) => { + let start = stack.len() - count; + if stack.len() > count { + let call_stack: Vec = stack.drain(start..).collect(); + match stack.pop() { + None => return Err(InterpreterError::new("invalid function call")), + Some(Value::KwFn(KeywordFunction::Print)) => { + let mut iter = call_stack.into_iter(); + if let Some(i) = iter.next() { + let val = get_value(i, scopes)?; + print!("{val}"); + } + for i in iter { + let val = get_value(i, scopes)?; + print!(", {val}"); + } + println!(); + } + Some(Value::Lit(_)) => return Err(InterpreterError::new("cannot call literal")), + Some(Value::Id(_)) => return Err(InterpreterError::new("not yet implemented")), + } + } else { + return Err(InterpreterError::new("invalid call")); + } + }, Some(ASTNode::Block(tokens)) => { scopes.push(HashMap::new()); let mut result = interpret_block(tokens, scopes, BlockType::Block)?; @@ -125,7 +151,6 @@ fn interpret_block(ast: Vec, scopes: &mut Vec, block_type: Block let rhs = some_or_error!(stack.pop(), InterpreterError::new("rhs value needed")); let lhs = some_or_error!(stack.pop(), InterpreterError::new("lhs value needed")); let rhs: TeaValue = get_value(rhs, scopes)?; - //let current_scope = .unwrap(); match lhs { Value::Lit(_) => return Err(InterpreterError::new("invalid left hand side")), Value::Id(s) => { @@ -135,6 +160,9 @@ fn interpret_block(ast: Vec, scopes: &mut Vec, block_type: Block scopes.last_mut().unwrap().insert(s, VarType::Const(rhs)); } } + Value::KwFn(s) => { + return Err(InterpreterError::new(format!("unable to assign to keyword {s:?}").as_str())) + } } }, Some(ASTNode::StatementEnd) => { diff --git a/src/interpret/opchain.rs b/src/interpret/opchain.rs deleted file mode 100644 index 4ff9751..0000000 --- a/src/interpret/opchain.rs +++ /dev/null @@ -1,10 +0,0 @@ - -use crate::parse::ASTNode; - -fn get_domain(ast: &mut Vec) -> Vec { - ast.iter().take_while(|x| !matches!(x, ASTNode::StatementEnd)).cloned().collect() -} - -pub fn operator_chain(ast: &mut Vec) { - let tree = get_domain(ast); -} diff --git a/src/lex.rs b/src/lex.rs index 9b3c8c0..d8e1a31 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -26,6 +26,18 @@ pub enum TeaValue { String(String), } +use std::fmt; +impl fmt::Display for TeaValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TeaValue::Int(i) => write!(f, "{i}"), + TeaValue::Uint(u) => write!(f, "{u}u"), + TeaValue::Float(l) => write!(f, "{l}"), + TeaValue::String(s) => write!(f, "{s}"), + } + } +} + #[derive(Debug)] pub enum Token { Lit(TeaValue), @@ -52,6 +64,33 @@ fn skip_whitespace(input: &str) -> &str { &input[whitespace.len()..] } +fn skip_singleline_comment(input: &str) -> Option { + if input.get(0..2) != Some("//") { + return None; + } + let mut index = 2; + loop { + if matches!(input.chars().nth(index), None | Some('\n')) { + return Some(index); + } + index += 1; + } +} + +fn skip_multiline_comment(input: &str) -> Option { + if input.get(0..2) != Some("/*") { + return None; + } + let mut index = 2; + loop { + let s = input.get(index..(index + 2))?; + if s == "*/" { + return Some(index + 2); + } + index += 1; + } +} + macro_rules! try_parse { ($e:expr, $arr:ident, $str:ident) => { if let Some((v, s)) = $e { @@ -69,6 +108,14 @@ pub fn lex_tokens(s: &str) -> Result, (Vec, &str)> { if input.is_empty() { return Ok(tokens); } + if let Some(count) = skip_singleline_comment(input) { + input = &input[count..]; + continue; + } + if let Some(count) = skip_multiline_comment(input) { + input = &input[count..]; + continue; + } try_parse!(parse_literal(input), tokens, input); try_parse!(parse_operator(input), tokens, input); try_parse!(parse_symbol(input), tokens, input); diff --git a/src/lex/keyword.rs b/src/lex/keyword.rs index de96411..998406f 100644 --- a/src/lex/keyword.rs +++ b/src/lex/keyword.rs @@ -4,6 +4,7 @@ use super::{ whitespace_chars, Token }; pub enum Keyword { Let, Fn, + Print, // print is a keyword for... reasons } macro_rules! match_keyword { @@ -17,5 +18,6 @@ macro_rules! match_keyword { pub fn parse_keyword(input: &str) -> Option<(Token, &str)> { match_keyword!("let", Token::Kw(Keyword::Let), input); match_keyword!("fn", Token::Kw(Keyword::Fn), input); + match_keyword!("print", Token::Kw(Keyword::Print), input); None } diff --git a/src/lex/literal.rs b/src/lex/literal.rs index 84b5082..6750e13 100644 --- a/src/lex/literal.rs +++ b/src/lex/literal.rs @@ -86,16 +86,61 @@ fn parse_dec_int(input: &str) -> Option<(i32, &str)> { Some((sign * s.parse::().unwrap(), &input[s.len()..])) } +fn parse_uint(input: &str) -> Option<(u32, &str)> { + if input.starts_with("-") { + return None; + } + input.strip_prefix("0x").map(|input| -> String { + input.chars().take_while(|c| matches!(c, hex_chars!())).collect() + }).filter(|s| !s.is_empty() && input.chars().nth(s.len()) == Some('u')).map(|s| + (u32::from_str_radix(&s, 16).unwrap(), &input[(s.len() + 1)..]) + ).or({ + let s: String = input.chars().take_while(|c| matches!(c, dec_chars!())).collect(); + if s.is_empty() || input.chars().nth(s.len()) != Some('u') { + None + } else { + Some ((s.parse::().unwrap(), &input[(s.len() + 1)..])) + } + }) +} + +fn parse_str(input: &str) -> Option<(String, &str)> { + let x = input.chars().next()?; + if x != '"' { + return None; + } + let mut index = 1; + let mut terminated = false; + while index < input.len() { + // this is definitely stupid but idk how to do this properly + if Some('"') == input.chars().nth(index) && Some('\'') != input.chars().nth(index - 1) { + terminated = true; + break; + } + index += 1; + } + if !terminated { + return None; + } + Some((input.get(1..index).unwrap().to_owned().replace("\\n", "\n").replace("\\r", "\r").replace("\\0", "\0"), &input[(index + 1)..])) +} + pub fn parse_literal(input: &str) -> Option<(Token, &str)> { if let Some((val, s)) = parse_float(input) { return Some((Token::Lit(TeaValue::Float(val)), s)) } + if let Some((val, s)) = parse_uint(input) { + return Some((Token::Lit(TeaValue::Uint(val)), s)) + } if let Some((val, s)) = parse_hex_int(input) { return Some((Token::Lit(TeaValue::Int(val)), s)) } if let Some((val, s)) = parse_dec_int(input) { return Some((Token::Lit(TeaValue::Int(val)), s)) } + if let Some((val, s)) = parse_str(input) { + return Some((Token::Lit(TeaValue::String(val)), s)) + } // TODO: other values None } diff --git a/src/lex/symbol.rs b/src/lex/symbol.rs index 788cd61..fd015a6 100644 --- a/src/lex/symbol.rs +++ b/src/lex/symbol.rs @@ -8,17 +8,19 @@ pub enum Symbol { CloseBrace, OpenParen, CloseParen, + Comma, } pub fn parse_symbol(input: &str) -> Option<(Token, &str)> { let c: char = input.chars().next()?; let input = &input[1..]; match c { - '=' => Some((Token::Sym(Symbol::EqualSign), input)), - ';' => Some((Token::Sym(Symbol::Semicolon), input)), - '{' => Some((Token::Sym(Symbol::OpenBrace), input)), + '=' => Some((Token::Sym(Symbol::EqualSign), input)), + ';' => Some((Token::Sym(Symbol::Semicolon), input)), + ',' => Some((Token::Sym(Symbol::Comma), input)), + '{' => Some((Token::Sym(Symbol::OpenBrace), input)), '}' => Some((Token::Sym(Symbol::CloseBrace), input)), - '(' => Some((Token::Sym(Symbol::OpenParen), input)), + '(' => Some((Token::Sym(Symbol::OpenParen), input)), ')' => Some((Token::Sym(Symbol::CloseParen), input)), _ => None } diff --git a/src/main.rs b/src/main.rs index 2e35789..0e99de8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,19 @@ mod interpret; use interpret::interpret; fn main() { + println!("\n"); // makes output easier to visually parse when run with cargo run let args: String = args().skip(1).collect::>().join(" "); - let k = lex_tokens(&args).unwrap(); - let p = parse(k).unwrap(); - let i = interpret(p); - println!("{i:?}"); + let k = match lex_tokens(&args) { + Ok(a) => a, + Err((_, a)) => panic!("{a:?}"), + }; + let p = match parse(k) { + Ok(a) => a, + Err(a) => panic!("{:?}", a.description), + }; + let stack = match interpret(p) { + Ok(a) => a, + Err(a) => panic!("{:?}", a.description), + }; + println!("\nreturn: {stack:?}\n"); } diff --git a/src/parse.rs b/src/parse.rs index c4b08df..bae9cf1 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -11,6 +11,12 @@ use crate::lex::{ pub enum Value { Lit(TeaValue), Id(String), + KwFn(KeywordFunction), +} + +#[derive(Debug, Clone)] +pub enum KeywordFunction { + Print, } #[derive(Debug, Clone)] @@ -22,11 +28,12 @@ pub enum ASTNode { StatementEnd, Block(Vec), Parenthetical(Vec), + ArgColl(usize), } #[derive(Debug)] pub struct ParseError { - description: String, + pub description: String, } impl ParseError { @@ -37,6 +44,11 @@ impl ParseError { } } +pub enum ExprType { + Block, + Arg, +} + macro_rules! match_or_return { ($matchee:expr, $pattern:pat, $extract:expr, $ret:expr) => { match $matchee { @@ -46,15 +58,6 @@ macro_rules! match_or_return { } } -/*macro_rules! assert_match { - ($matchee:expr, $pattern:pat, $ret:expr) => { - match $matchee { - Some($pattern) => (), - _ => return Err($ret), - } - } -}*/ - fn next_expr_end(tokens: &[Token]) -> usize { let mut index = 0; let mut block_count = 0usize; @@ -100,7 +103,51 @@ fn next_paren_end(tokens: &[Token]) -> Result { } } -fn parse_expr(tokens: &[Token]) -> Result<(Vec, usize), ParseError> { +fn next_arg_end(tokens: &[Token]) -> Result<(bool, usize), ParseError> { + let mut index = 0; + let mut paren_count = 0usize; + let mut block_count = 0usize; + loop { + match tokens.get(index) { + Some(Token::Sym(Symbol::OpenParen)) => paren_count += 1, + Some(Token::Sym(Symbol::CloseParen)) => paren_count -= 1, + Some(Token::Sym(Symbol::OpenBrace)) => block_count += 1, + Some(Token::Sym(Symbol::CloseBrace)) => block_count -= 1, + Some(Token::Sym(Symbol::Comma)) if paren_count == 0 && block_count == 0 => return Ok((false, index)), + Some(Token::Sym(Symbol::Semicolon)) if paren_count == 0 && block_count == 0 => return Ok((true, index)), + None => { + if paren_count > 0 || block_count > 0 { + return Err(ParseError::new("mismatched ( or {")); + } + return Ok((true, index)) + } + _ => (), + } + index += 1; + } +} + +macro_rules! check_if_call { + ($tree:ident, $tkn:expr, $i:ident, $o:ident) => { + if matches!($tree.last(), Some(ASTNode::Val(_))) && $o.is_empty() { + let mut arg_count = 0; + loop { + let (finished, count) = next_arg_end(&$tkn[$i..])?; + let (mut stack, _) = parse_expr(&$tkn[$i..($i + count)], ExprType::Arg)?; + $tree.append(&mut stack); + $i += count + 1; + arg_count += 1; + if finished { + break; + } + } + $tree.push(ASTNode::ArgColl(arg_count)); + continue; + } + } +} + +fn parse_expr(tokens: &[Token], expr_type: ExprType) -> Result<(Vec, usize), ParseError> { let mut ast = vec![]; let mut index = 0; let mut op_stack: Vec = vec![]; @@ -108,43 +155,47 @@ fn parse_expr(tokens: &[Token]) -> Result<(Vec, usize), ParseError> { loop { match tokens.get(index) { None => { - while !op_stack.is_empty() { - ast.push(ASTNode::BinOp(op_stack.pop().unwrap())); - } if unary_negate && !matches!(tokens.get(index), Some(Token::Sym(Symbol::Semicolon))) { ast.push(ASTNode::UnaryMinus); } + while !op_stack.is_empty() { + ast.push(ASTNode::BinOp(op_stack.pop().unwrap())); + } return Ok((ast, index)); }, - Some(Token::Lit(v)) => ast.push(ASTNode::Val(Value::Lit(v.clone()))), - Some(Token::Id(v)) => ast.push(ASTNode::Val(Value::Id(v.clone()))), + Some(Token::Lit(v)) => { + check_if_call!(ast, tokens, index, op_stack); + ast.push(ASTNode::Val(Value::Lit(v.clone()))); + }, + Some(Token::Id(v)) => { + check_if_call!(ast, tokens, index, op_stack); + ast.push(ASTNode::Val(Value::Id(v.clone()))); + }, Some(Token::Kw(Keyword::Let)) => { + if matches!(expr_type, ExprType::Arg) { + return Err(ParseError::new("let not allowed in argument list")); + } let id = match_or_return!(tokens.get(index + 1), Token::Id(a), a, ParseError::new("identifier expected after 'let' keyword")); match_or_return!(tokens.get(index + 2), Token::Sym(Symbol::EqualSign), (), ParseError::new("= expected after identifier")); let stokens = &tokens[(3 + index)..]; let next_end = next_expr_end(stokens); - let (mut expr, count) = parse_expr(&stokens[..next_end])?; + let (mut expr, count) = parse_expr(&stokens[..next_end], ExprType::Block)?; ast.push(ASTNode::Val(Value::Id(id.to_owned()))); ast.append(&mut expr); ast.push(ASTNode::ConstDecl); - index += count + 2;// 4 + count - 1 + index += count + 2; }, Some(Token::Kw(Keyword::Fn)) => { - /*let id = match_or_return!(tokens.get(index + 1), Token::Id(a), a, ParseError::new("identifier expected after 'let' keyword")); - match_or_return!(tokens.get(index + 2), Token::Sym(Symbol::EqualSign), (), ParseError::new("= expected after identifier")); - let tokens = &tokens[(3 + index)..]; - let next_end = next_expr_end(tokens); - let (mut expr, count) = parse_expr(&tokens[..next_end])?; - ast.push(ASTNode::Val(Value::Id(id.to_owned()))); - ast.append(&mut expr); - ast.push(ASTNode::ConstDecl); - index += count + 3; // 4 + count - 1*/ todo!() }, + Some(Token::Kw(Keyword::Print)) => { + ast.push(ASTNode::Val(Value::KwFn(KeywordFunction::Print))); + }, Some(Token::Sym(Symbol::EqualSign)) => todo!(), Some(Token::Sym(Symbol::OpenBrace)) => { + check_if_call!(ast, tokens, index, op_stack); let end = next_block_end(&tokens[(index + 1)..])?; - let (expr, count) = parse_expr(&tokens[(index + 1)..(index + end + 1)])?; + let (expr, count) = parse_expr(&tokens[(index + 1)..(index + end + 1)], ExprType::Block)?; ast.push(ASTNode::Block(expr)); index += count + 1; }, @@ -152,8 +203,9 @@ fn parse_expr(tokens: &[Token]) -> Result<(Vec, usize), ParseError> { return Err(ParseError::new("unexpected }")); }, Some(Token::Sym(Symbol::OpenParen)) => { + check_if_call!(ast, tokens, index, op_stack); let end = next_paren_end(&tokens[(index + 1)..])?; - let (expr, count) = parse_expr(&tokens[(index + 1)..(index + end + 1)])?; + let (expr, count) = parse_expr(&tokens[(index + 1)..(index + end + 1)], ExprType::Block)?; ast.push(ASTNode::Parenthetical(expr)); index += count + 1; }, @@ -161,56 +213,42 @@ fn parse_expr(tokens: &[Token]) -> Result<(Vec, usize), ParseError> { return Err(ParseError::new("unexpected )")); }, Some(Token::Sym(Symbol::Semicolon)) => { + if matches!(expr_type, ExprType::Arg) { + return Err(ParseError::new("semicolon within argument")); + } + if unary_negate && !matches!(tokens.get(index), Some(Token::Sym(Symbol::Semicolon))) { + ast.push(ASTNode::UnaryMinus); + unary_negate = false; + } while !op_stack.is_empty() { ast.push(ASTNode::BinOp(op_stack.pop().unwrap())); } // I'll macro this later - if unary_negate && !matches!(tokens.get(index), Some(Token::Sym(Symbol::Semicolon))) { - ast.push(ASTNode::UnaryMinus); - unary_negate = false; - } ast.push(ASTNode::StatementEnd); }, + Some(Token::Sym(Symbol::Comma)) => { + if matches!(expr_type, ExprType::Arg) { + return Err(ParseError::new("comma within argument")); + } + }, Some(Token::Op(op)) => { - if (matches!(ast.last(), Some(ASTNode::StatementEnd) | None) || !op_stack.is_empty()) && matches!(op, Operator::Minus) { + if (ast.is_empty() || matches!(tokens.get(index - 1), Some(Token::Sym(Symbol::Semicolon) | Token::Op(_)))) && matches!(op, Operator::Minus) { unary_negate = true; index += 1; continue; } - if unary_negate && !matches!(tokens.get(index), Some(Token::Sym(Symbol::Semicolon))) { - ast.push(ASTNode::UnaryMinus); - unary_negate = false; - } match op_stack.last() { None => op_stack.push(op.clone()), Some(last_op) if last_op.get_precedence() < op.get_precedence() => { op_stack.push(op.clone()) }, Some(_) => { - while op_stack.last().is_some_and(|l| l.get_precedence() < op.get_precedence()) { + while op_stack.last().is_some_and(|l| l.get_precedence() > op.get_precedence()) { ast.push(ASTNode::BinOp(op_stack.pop().unwrap())); } op_stack.push(op.clone()) } } - /*if ast.is_empty() && matches!(op, Operator::Minus) { - ast.push(match tokens.get(index + 1) { - Some(Token::Id(v)) => ASTNode::Val(Value::Id(v.clone())), - Some(Token::Lit(v)) => ASTNode::Val(Value::Lit(v.clone())), - _ => return Err(ParseError::new("literal or identifier expected after operator")), - }); - ast.push(ASTNode::UnaryMinus); - index += 1; - } else { - assert_match!(ast.last(), ASTNode::Val(_), ParseError::new("literal or identifier expected before operator")); - ast.push(match tokens.get(index + 1) { - Some(Token::Id(v)) => ASTNode::Val(Value::Id(v.clone())), - Some(Token::Lit(v)) => ASTNode::Val(Value::Lit(v.clone())), - _ => return Err(ParseError::new("literal or identifier expected after operator")), - }); - ast.push(ASTNode::BinOp(op.clone())); - index += 1; - }*/ }, } @@ -223,7 +261,7 @@ pub fn parse(tokens: Vec) -> Result, ParseError> { let tokens: &[Token] = &tokens; let mut next = 0; loop { - let (mut nodes, count) = parse_expr(&tokens[next..])?; + let (mut nodes, count) = parse_expr(&tokens[next..], ExprType::Block)?; ast.append(&mut nodes); next += count; if tokens.get(next).is_none() { diff --git a/test.matcha b/test.matcha index 86ee907..9b117d9 100644 --- a/test.matcha +++ b/test.matcha @@ -1,2 +1,2 @@ -(5 + 6) * 4 +print "Hello, world!";