diff --git a/forge-script-lang/src/error/mod.rs b/forge-script-lang/src/error/mod.rs index 9d9ccf425097e18c745ea86baebb95b27ed463a7..dc615a15f17651fc0455e0c50dd676ddf223cbbb 100644 --- a/forge-script-lang/src/error/mod.rs +++ b/forge-script-lang/src/error/mod.rs @@ -6,7 +6,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use std::process::{ExitCode, Termination}; -pub type LalrError<'a> = BaseLalrError<usize, ScriptTokenType<'a>, TokenError<'a>>; +pub type LalrError<'a> = BaseLalrError<usize, ScriptTokenType, TokenError<'a>>; #[derive(Debug)] pub enum TokenErrorKind<'a> { @@ -72,12 +72,12 @@ pub enum ForgeErrorKind<'a> { expected: Vec<String>, }, UnrecognizedToken { - token: ScriptTokenType<'a>, + token: ScriptTokenType, span: ErrorSpan, expected: Vec<String>, }, ExpectedEof { - token: ScriptTokenType<'a>, + token: ScriptTokenType, span: ErrorSpan, }, Custom(String), diff --git a/forge-script-lang/src/lexer/primitives.rs b/forge-script-lang/src/lexer/primitives.rs index fda23f51c49a6a5d5c02c6316caa1be962dec2a5..c44cddc4b8e85a9aabd94d44acc8280614358c53 100644 --- a/forge-script-lang/src/lexer/primitives.rs +++ b/forge-script-lang/src/lexer/primitives.rs @@ -19,7 +19,7 @@ pub fn token_ident(input: Span) -> IResult<Span, ScriptToken> { input, ScriptToken { position: pos, - token_type: ScriptTokenType::Identifier(value.fragment()), + token_type: ScriptTokenType::Identifier(value.fragment().to_string()), }, )) } @@ -147,7 +147,10 @@ mod parsing_tests { for expected in positive_cases { map_result!(token_ident(Span::new(expected)), |_, token: ScriptToken| { - assert_eq!(token.token_type, ScriptTokenType::Identifier(expected)) + assert_eq!( + token.token_type, + ScriptTokenType::Identifier(expected.to_string()) + ) }); } } diff --git a/forge-script-lang/src/lexer/tokens.rs b/forge-script-lang/src/lexer/tokens.rs index 971d270f4eb2697aa286edf1b2c52fd59f4bd4c4..9bb2c4186d52d35e1dbb70c3a61b6c488d0b9233 100644 --- a/forge-script-lang/src/lexer/tokens.rs +++ b/forge-script-lang/src/lexer/tokens.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::fmt::{format, Debug, Display, Formatter}; #[derive(PartialEq, Clone, Debug)] -pub enum ScriptTokenType<'a> { +pub enum ScriptTokenType { // Structural Tokens LeftParen, RightParen, @@ -34,8 +34,8 @@ pub enum ScriptTokenType<'a> { Caret, // Literals - Identifier(&'a str), - String(&'a str), + Identifier(String), + String(String), OwnedString(String), Integer(i64), Float(f64), @@ -65,7 +65,7 @@ pub enum ScriptTokenType<'a> { Eof, } -impl<'a> ScriptTokenType<'a> { +impl ScriptTokenType { pub fn len(&self) -> usize { match self { ScriptTokenType::LeftParen => 1, @@ -126,7 +126,7 @@ impl<'a> ScriptTokenType<'a> { } } -impl<'a> Display for ScriptTokenType<'a> { +impl Display for ScriptTokenType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ScriptTokenType::LeftParen => write!(f, "("), @@ -192,7 +192,7 @@ impl<'a> Display for TokenFromStringError<'a> { } impl<'a> Error for TokenFromStringError<'a> {} -impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> { +impl<'a> TryFrom<&'a str> for ScriptTokenType { type Error = TokenFromStringError<'a>; fn try_from(value: &'a str) -> Result<Self, Self::Error> { @@ -248,7 +248,7 @@ impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> { #[derive(Clone, PartialEq)] pub struct ScriptToken<'a> { pub position: Span<'a>, - pub token_type: ScriptTokenType<'a>, + pub token_type: ScriptTokenType, } impl<'a> Display for ScriptToken<'a> { diff --git a/forge-script-lang/src/parser/forge_grammar.lalrpop b/forge-script-lang/src/parser/forge_grammar.lalrpop index 7785f73ffdff9b69a4e3a288615f19601e9079f4..68a5d3d813cc29cda32d20015361276866abe19e 100644 --- a/forge-script-lang/src/parser/forge_grammar.lalrpop +++ b/forge-script-lang/src/parser/forge_grammar.lalrpop @@ -190,9 +190,9 @@ ListOf<T>: Vec<T> = { extern { type Location = usize; - type Error = crate::TokenError<'a>; + type Error = String; - enum ScriptTokenType<'a> { + enum ScriptTokenType { "(" => ScriptTokenType::LeftParen, ")" => ScriptTokenType::RightParen, "{" => ScriptTokenType::LeftBrace, @@ -238,7 +238,7 @@ extern { "float" => ScriptTokenType::Float(<f64>), "integer" => ScriptTokenType::Integer(<i64>), "owned_string" => ScriptTokenType::OwnedString(<String>), - "string" => ScriptTokenType::String(<&'a str>), - "identifier" => ScriptTokenType::Identifier(<&'a str>), + "string" => ScriptTokenType::String(<String>), + "identifier" => ScriptTokenType::Identifier(<String>), } } \ No newline at end of file diff --git a/forge-script-lang/src/parser/forge_script.rs b/forge-script-lang/src/parser/forge_script.rs index 897f48a2b72504483f8a08be339504f3b4061af5..78b7df73129e786c33449a3b2371fb7f9a95cdb6 100644 --- a/forge-script-lang/src/parser/forge_script.rs +++ b/forge-script-lang/src/parser/forge_script.rs @@ -4,13 +4,14 @@ use crate::parser::ast::{Expression, Program}; use crate::TokenError; use peg::Parse; -pub type InputSpan<'a, Loc, Tok> = Result<(Loc, Tok, Loc), TokenError<'a>>; -type ExprSpan<'a> = InputSpan<'a, usize, ScriptTokenType<'a>>; +pub type InputSpan<'a, Loc, Tok> = Result<(Loc, Tok, Loc), String>; +type ExprSpan<'a> = InputSpan<'a, usize, ScriptTokenType>; macro_rules! export_grammar_fn { ($name:ident = $output:ty => $part: tt) => { - pub fn $name(source: &str) -> ForgeResult<$output> { - let tokens = script_to_tokens(source)? + pub fn $name(source: &str) -> Result<$output, String> { + let tokens = script_to_tokens(source) + .map_err(|e| format!("{}", e))? .iter() .map(|tok| { Ok(( @@ -21,8 +22,9 @@ macro_rules! export_grammar_fn { }) .collect::<Vec<ExprSpan>>(); - let value = - super::forge_grammar::$part::new().parse::<ExprSpan, Vec<ExprSpan>>(tokens)?; + let value = super::forge_grammar::$part::new() + .parse::<ExprSpan, Vec<ExprSpan>>(tokens) + .map_err(|e| format!("{}", e))?; Ok(value) } @@ -46,7 +48,7 @@ mod grammar_test { #[test_case("null" => matches Ok(_) ; "Parse literal null")] #[test_case("if foo {}" => matches Ok(_) ; "Parse conditional")] #[test_case("2 * 4 - 3" => matches Ok(_) ; "Parse arithmetic")] - fn expression_parsing(prog: &str) -> ForgeResult<Expression> { + fn expression_parsing(prog: &str) -> Result<Expression, String> { parse_expression(prog) } } diff --git a/forge-script-lang/src/parser/grammar.rs b/forge-script-lang/src/parser/grammar.rs index 82bbd341019b717d13f41b9c0630720d61b14e8f..fa8d124f97ba513e792c1663eab138cfdd2d7c82 100644 --- a/forge-script-lang/src/parser/grammar.rs +++ b/forge-script-lang/src/parser/grammar.rs @@ -134,19 +134,19 @@ peg::parser! { = base:simple_identifier() "as" alias:simple_identifier() { IdentifierAlias(base.0, alias.0) } rule simple_identifier() -> Identifier - = [ScriptToken { token_type: ScriptTokenType::Identifier(vl), .. }] { Identifier(String::from(*vl)) } + = [ScriptToken { token_type: ScriptTokenType::Identifier(vl), .. }] { Identifier(String::from(vl)) } rule literal() -> LiteralNode = "true" { LiteralNode::Boolean(true) } / "false" { LiteralNode::Boolean(false) } / "null" { LiteralNode::Null } - / [ScriptToken { token_type: ScriptTokenType::String(vl), .. }] { LiteralNode::String(String::from(*vl)) } + / [ScriptToken { token_type: ScriptTokenType::String(vl), .. }] { LiteralNode::String(String::from(vl)) } / [ScriptToken { token_type: ScriptTokenType::OwnedString(vl), .. }] { LiteralNode::String(vl.clone()) } / [ScriptToken { token_type: ScriptTokenType::Integer(vl), .. }] { LiteralNode::Number(Number::Integer(*vl)) } / [ScriptToken { token_type: ScriptTokenType::Float(vl), .. }] { LiteralNode::Number(Number::Float(*vl)) } rule string_value() -> String - = [ScriptToken { token_type: ScriptTokenType::String(vl), .. }] { String::from(*vl) } + = [ScriptToken { token_type: ScriptTokenType::String(vl), .. }] { String::from(vl) } / [ScriptToken { token_type: ScriptTokenType::OwnedString(vl), .. }] { vl.clone() } rule eof() = ![_] diff --git a/forge-script-lang/src/pratt/parser.rs b/forge-script-lang/src/pratt/parser.rs index be775540554d13909220acc699aac2175e56b0c3..0ccdc4802883171f3fbee125537be6882bba26bc 100644 --- a/forge-script-lang/src/pratt/parser.rs +++ b/forge-script-lang/src/pratt/parser.rs @@ -61,6 +61,12 @@ impl ScanningState { } } + pub fn skip_first_n(&mut self, amount: usize) { + self.lexeme_start = self + .lexeme_current + .min(self.lexeme_start.saturating_add(amount)); + } + pub fn advance(&mut self, amount: usize) { self.lexeme_current += amount; } @@ -81,27 +87,28 @@ pub struct Scanner<'a> { } #[derive(Clone, Debug, PartialEq)] -pub enum ScannerErrorKind<'a> { +pub enum ScannerErrorKind { + BadIdentifier, UnexpectedEof, - UnexpectedCharacter { span: TokenSpan<'a> }, + UnexpectedToken { span: TokenSpan }, + InvalidLiteral { ltype: &'static str }, } #[derive(Clone, Debug, PartialEq)] -pub struct ScannerError<'a> { - pub kind: ScannerErrorKind<'a>, +pub struct ScannerError { + pub kind: ScannerErrorKind, + pub position: PositionState, } #[derive(Clone, Copy, Debug, PartialEq)] -pub struct TokenSpan<'a> { +pub struct TokenSpan { pub position: PositionState, pub length: usize, - pub source: &'a str, } -impl<'a> From<Scanner<'a>> for TokenSpan<'a> { - fn from(value: Scanner<'a>) -> TokenSpan<'a> { +impl<'a, 'b: 'a> From<&'b Scanner<'a>> for TokenSpan { + fn from(value: &Scanner) -> TokenSpan { TokenSpan { - source: value.source, position: value.position, length: value .scan_state @@ -111,16 +118,40 @@ impl<'a> From<Scanner<'a>> for TokenSpan<'a> { } } +fn gen_token_span(scanner: &Scanner) -> TokenSpan { + TokenSpan { + position: scanner.position, + length: scanner + .scan_state + .lexeme_current + .saturating_sub(scanner.scan_state.lexeme_start), + } +} + #[derive(Clone, Debug)] -pub struct ScannerToken<'a> { - pub location: TokenSpan<'a>, - pub token: ScriptTokenType<'a>, +pub struct ScannerToken { + pub location: TokenSpan, + pub token: ScriptTokenType, } -pub type ScannerResult<'a> = Result<ScannerToken<'a>, ScannerError<'a>>; +pub type ScannerResult = Result<ScannerToken, ScannerError>; + +macro_rules! next_match { + ($target: expr, $val: expr) => {{ + if $target.is_finished() { + false + } else if $target.source.chars().nth($target.position.offset) == Some($val) { + $target.position.advance(1); + $target.scan_state.advance(1); + true + } else { + false + } + }}; +} impl<'a> Scanner<'a> { - pub fn new(source: &str) -> Scanner { + pub fn new<'b>(source: &'b str) -> Scanner<'b> { Scanner { source, position: PositionState::default(), @@ -132,16 +163,330 @@ impl<'a> Scanner<'a> { self.position.offset == self.source.len() } - pub fn scan_token(&mut self) -> Result<ScannerToken<'a>, ScannerError> { + pub fn scan_token(&'a mut self) -> ScannerResult { + self.scan_state = ScanningState::new(self.position.offset); + if self.is_finished() { - Ok(ScannerToken { + return Ok(ScannerToken { token: ScriptTokenType::Eof, - location: TokenSpan::from(*self), - }) + location: gen_token_span(self), + }); + } + + let next_char = self.source.chars().nth(self.position.offset); + self.position.advance(1); + self.scan_state.advance(1); + + match next_char { + Some('=') => { + if next_match!(self, '=') { + Ok(self.tokenise(ScriptTokenType::EqualEqual)) + } else { + Ok(self.tokenise(ScriptTokenType::Equal)) + } + } + Some('%') => Ok(self.tokenise(ScriptTokenType::Modulo)), + Some('^') => Ok(self.tokenise(ScriptTokenType::Caret)), + Some('!') => { + if next_match!(self, '=') { + Ok(self.tokenise(ScriptTokenType::BangEqual)) + } else { + Ok(self.tokenise(ScriptTokenType::Bang)) + } + } + Some('(') => Ok(self.tokenise(ScriptTokenType::LeftParen)), + Some(')') => Ok(self.tokenise(ScriptTokenType::RightParen)), + Some('{') => Ok(self.tokenise(ScriptTokenType::LeftBrace)), + Some('}') => Ok(self.tokenise(ScriptTokenType::RightBrace)), + Some(',') => Ok(self.tokenise(ScriptTokenType::Comma)), + Some('.') => Ok(self.tokenise(ScriptTokenType::Dot)), + Some('-') => Ok(self.tokenise(ScriptTokenType::Minus)), + Some('+') => Ok(self.tokenise(ScriptTokenType::Plus)), + Some(';') => Ok(self.tokenise(ScriptTokenType::Semicolon)), + Some('/') => Ok(self.tokenise(ScriptTokenType::Slash)), + Some('*') => Ok(self.tokenise(ScriptTokenType::Asterisk)), + Some('<') => { + if next_match!(self, '=') { + Ok(self.tokenise(ScriptTokenType::LessEqual)) + } else { + Ok(self.tokenise(ScriptTokenType::Less)) + } + } + Some('>') => { + if next_match!(self, '=') { + Ok(self.tokenise(ScriptTokenType::GreaterEqual)) + } else { + Ok(self.tokenise(ScriptTokenType::Greater)) + } + } + Some('"') => { + let val = self.capture_string()?; + Ok(self.tokenise(val)) + } + Some(other) => { + if other.is_numeric() { + let val = self.capture_number()?; + Ok(self.tokenise(val)) + } else if other.is_alphabetic() { + let val = self.capture_keyword_or_ident()?; + Ok(self.tokenise(val)) + } else { + Err(ScannerError { + position: self.position, + + kind: ScannerErrorKind::UnexpectedToken { + span: gen_token_span(self), + }, + }) + } + } + None => Err(ScannerError { + position: self.position, + kind: ScannerErrorKind::UnexpectedEof, + }), + } + } + + pub fn consume_ws(&mut self) { + loop { + match self.peek() { + Some('\n') => self.increment_line(), + Some('/') => { + if self.peek_nth(1) == Some('/') { + while !self.is_finished() && self.peek() != Some('\n') { + self.increment_cursor(); + } + } + } + Some(other) => { + if other.is_whitespace() { + self.increment_cursor() + } else { + break; + } + } + None => break, + } + } + } + + fn capture_string(&mut self) -> Result<ScriptTokenType, ScannerError> { + loop { + if self.is_finished() { + return Err(ScannerError { + position: self.position, + + kind: ScannerErrorKind::UnexpectedEof, + }); + } + + match self.peek() { + Some('\"') => { + self.scan_state.skip_first_n(1); // Don't include first quote in capture + let tok = ScriptTokenType::String(String::from( + &self.source[self.scan_state.lexeme_start..self.scan_state.lexeme_current], + )); + self.increment_cursor(); + return Ok(tok); + } + Some('\n') => self.increment_line(), + Some(_) => self.increment_cursor(), + _ => {} + } + } + } + + fn capture_number(&mut self) -> Result<ScriptTokenType, ScannerError> { + let mut can_have_dot = true; + let mut can_have_underscore = true; + + loop { + if self.is_finished() { + break; + } + + let char = self.peek().ok_or(ScannerError { + position: self.position, + kind: ScannerErrorKind::UnexpectedEof, + })?; + + if char.is_numeric() { + self.increment_cursor(); + can_have_underscore = true; + } else if char == '_' && can_have_underscore { + self.increment_cursor(); + can_have_underscore = false; + } else if char == '.' && can_have_dot { + self.increment_cursor(); + can_have_dot = false; + can_have_underscore = false; + } else { + break; + } + } + + let result = if can_have_dot { + // We haven't consumed a dot, it's an int + (&self.source[self.scan_state.lexeme_start..self.scan_state.lexeme_current]) + .replace('_', "") + .parse::<i64>() + .map_err(|e| ScannerError { + position: self.position, + kind: ScannerErrorKind::InvalidLiteral { ltype: "integer" }, + }) + .map(ScriptTokenType::Integer) } else { + // We have consumed a dot, it's a float + (&self.source[self.scan_state.lexeme_start..self.scan_state.lexeme_current]) + .replace('_', "") + .parse::<f64>() + .map_err(|e| ScannerError { + position: self.position, + kind: ScannerErrorKind::InvalidLiteral { ltype: "float" }, + }) + .map(ScriptTokenType::Float) + }; + + self.increment_cursor(); + result + } + + fn capture_keyword_or_ident(&mut self) -> Result<ScriptTokenType, ScannerError> { + while self + .peek() + .map(|c| c.is_alphanumeric() || c == '_') + .unwrap_or(false) + { + self.increment_cursor(); + } + + let val = self.peek_current_slice().ok_or(ScannerError { + position: self.position, + kind: ScannerErrorKind::BadIdentifier, + })?; + let bad_kw = || { Err(ScannerError { - kind: ScannerErrorKind::UnexpectedEof, + position: self.position, + kind: ScannerErrorKind::BadIdentifier, }) + }; + let ident = || Ok(ScriptTokenType::Identifier(String::from(val))); + + eprintln!("Found ident {}", val); + + let mut val_chars = val.chars(); + match val_chars.next() { + Some('a') => self.check_ident(1, val, "s", ScriptTokenType::Alias), + Some('e') => match val_chars.next() { + Some('l') => self.check_ident(2, val, "se", ScriptTokenType::Else), + Some('x') => self.check_ident(2, val, "port", ScriptTokenType::Export), + _ => ident(), + }, + Some('f') => match val_chars.next() { + Some('n') => self.check_ident(2, val, "", ScriptTokenType::Function), + Some('r') => self.check_ident(2, val, "om", ScriptTokenType::From), + Some('o') => self.check_ident(2, val, "r", ScriptTokenType::For), + Some('i') => self.check_ident(2, val, "nally", ScriptTokenType::Finally), + _ => ident(), + }, + + Some('i') => match val_chars.next() { + Some('m') => self.check_ident(2, val, "port", ScriptTokenType::Import), + Some('f') => self.check_ident(2, val, "", ScriptTokenType::If), + _ => ident(), + }, + Some('l') => self.check_ident(1, val, "et", ScriptTokenType::Let), + Some('n') => self.check_ident(1, val, "ull", ScriptTokenType::Null), + Some('p') => self.check_ident(1, val, "rint", ScriptTokenType::Print), + Some('r') => self.check_ident(1, val, "eturn", ScriptTokenType::Return), + Some('s') => match val_chars.next() { + Some('t') => self.check_ident(2, val, "ruct", ScriptTokenType::Class), + Some('u') => self.check_ident(2, val, "per", ScriptTokenType::Super), + _ => ident(), + }, + Some('t') => match val_chars.next() { + Some('h') => self.check_ident(2, val, "is", ScriptTokenType::This), + Some('y') => self.check_ident(2, val, "peof", ScriptTokenType::Typeof), + _ => ident(), + }, + Some('w') => self.check_ident(1, val, "hile", ScriptTokenType::While), + Some(_) => ident(), + None => bad_kw(), + } + } + + fn check_ident( + &self, + start: usize, + val: &str, + expected: &str, + success: ScriptTokenType, + ) -> Result<ScriptTokenType, ScannerError> { + let sub = val.get(start..).ok_or(ScannerError { + position: self.position, + kind: ScannerErrorKind::BadIdentifier, + })?; + + if sub == expected { + Ok(success) + } else { + Ok(ScriptTokenType::Identifier(String::from(val))) + } + } + + #[inline(always)] + fn peek(&self) -> Option<char> { + self.peek_nth(0) + } + + fn peek_nth(&self, n: usize) -> Option<char> { + self.source.chars().nth(self.position.offset + n) + } + + fn peek_current_slice(&self) -> Option<&str> { + self.source + .get(self.scan_state.lexeme_start..self.scan_state.lexeme_current) + } + + fn increment_cursor(&mut self) { + self.position.advance(1); + self.scan_state.advance(1); + } + fn increment_line(&mut self) { + self.position.new_line(); + self.scan_state.advance(1); + } + + fn next_matches(&mut self, ch: char) -> bool { + if self.is_finished() { + false + } else if self.source.chars().nth(self.position.offset) == Some(ch) { + self.position.advance(1); + self.scan_state.advance(1); + true + } else { + false + } + } + + fn next(&mut self) -> Option<char> { + let ch = self.source.chars().nth(self.position.offset); + self.position.advance(1); + self.scan_state.advance(1); + ch + } + + fn next_checked(&mut self) -> Result<char, ScannerError> { + self.next().ok_or(ScannerError { + position: self.position, + kind: ScannerErrorKind::UnexpectedEof, + }) + } + + fn tokenise(&self, inner: ScriptTokenType) -> ScannerToken { + ScannerToken { + token: inner, + location: gen_token_span(self), } } } diff --git a/forge-script-lang/src/pratt/test_cases.rs b/forge-script-lang/src/pratt/test_cases.rs index 619c04bd8cf8738a50e24b130538bdfe5d7c32d5..dd1533811c89bb8b453fb413ccab8570b764379c 100644 --- a/forge-script-lang/src/pratt/test_cases.rs +++ b/forge-script-lang/src/pratt/test_cases.rs @@ -2,11 +2,56 @@ use crate::lexer::ScriptTokenType; use crate::pratt::parser::{Scanner, ScannerError, ScannerResult}; use test_case::test_case; -#[test_case("1 + 2" => Ok(ScriptTokenType::Integer(1)) ; "expects integer")] -#[test_case("print 1 + 2" => Ok(ScriptTokenType::Print) ; "expects print")] -#[test_case("\"Foo\"" => matches Ok(ScriptTokenType::OwnedString(_)) ; "expects string")] -#[test_case("" => Ok(ScriptTokenType::Eof) ; "expects eof")] -fn next_token(source: &'static str) -> Result<ScriptTokenType<'static>, ScannerError> { - let mut scanner = Scanner::new(source); - scanner.scan_token().map(|t| t.token) +#[test_case("", Ok(ScriptTokenType::Eof) ; "expects eof")] +#[test_case("%", Ok(ScriptTokenType::Modulo) ; "Expects ScriptTokenType::Modulo")] +#[test_case("^", Ok(ScriptTokenType::Caret) ; "Expects ScriptTokenType::Caret")] +#[test_case("!", Ok(ScriptTokenType::Bang) ; "Expects ScriptTokenType::Bang")] +#[test_case("(", Ok(ScriptTokenType::LeftParen) ; "Expects ScriptTokenType::LeftParen")] +#[test_case(")", Ok(ScriptTokenType::RightParen) ; "Expects ScriptTokenType::RightParen")] +#[test_case("{", Ok(ScriptTokenType::LeftBrace) ; "Expects ScriptTokenType::LeftBrace")] +#[test_case("}", Ok(ScriptTokenType::RightBrace) ; "Expects ScriptTokenType::RightBrace")] +#[test_case(",", Ok(ScriptTokenType::Comma) ; "Expects ScriptTokenType::Comma")] +#[test_case(".", Ok(ScriptTokenType::Dot) ; "Expects ScriptTokenType::Dot")] +#[test_case("-", Ok(ScriptTokenType::Minus) ; "Expects ScriptTokenType::Minus")] +#[test_case("+", Ok(ScriptTokenType::Plus) ; "Expects ScriptTokenType::Plus")] +#[test_case(";", Ok(ScriptTokenType::Semicolon) ; "Expects ScriptTokenType::Semicolon")] +#[test_case("/", Ok(ScriptTokenType::Slash) ; "Expects ScriptTokenType::Slash")] +#[test_case("*", Ok(ScriptTokenType::Asterisk) ; "Expects ScriptTokenType::Asterisk")] +#[test_case("=", Ok(ScriptTokenType::Equal) ; "Expects ScriptTokenType::Equal")] +#[test_case("!=", Ok(ScriptTokenType::BangEqual) ; "Expects ScriptTokenType::BangEqual")] +#[test_case("==", Ok(ScriptTokenType::EqualEqual) ; "Expects ScriptTokenType::EqualEqual")] +#[test_case(">", Ok(ScriptTokenType::Greater) ; "Expects ScriptTokenType::Greater")] +#[test_case(">=", Ok(ScriptTokenType::GreaterEqual) ; "Expects ScriptTokenType::GreaterEqual")] +#[test_case("<", Ok(ScriptTokenType::Less) ; "Expects ScriptTokenType::Less")] +#[test_case("<=", Ok(ScriptTokenType::LessEqual) ; "Expects ScriptTokenType::LessEqual")] +#[test_case("\"Foo\"", Ok(ScriptTokenType::String(String::from("Foo"))) ; "Expects ScriptTokenType::String")] +#[test_case("123", Ok(ScriptTokenType::Integer(123)) ; "Expects ScriptTokenType::Integer")] +#[test_case("1_2_3", Ok(ScriptTokenType::Integer(123)) ; "Expects ScriptTokenType::Integer underscored")] +#[test_case("123.123", Ok(ScriptTokenType::Float(123.123)) ; "Expects ScriptTokenType::Float")] +#[test_case("12_3.1_23", Ok(ScriptTokenType::Float(123.123)) ; "Expects ScriptTokenType::Float underscored")] +#[test_case("struct", Ok(ScriptTokenType::Class) ; "Expects ScriptTokenType::Class")] +#[test_case("else", Ok(ScriptTokenType::Else) ; "Expects ScriptTokenType::Else")] +#[test_case("fn", Ok(ScriptTokenType::Function) ; "Expects ScriptTokenType::Function")] +#[test_case("for", Ok(ScriptTokenType::For) ; "Expects ScriptTokenType::For")] +#[test_case("if", Ok(ScriptTokenType::If) ; "Expects ScriptTokenType::If")] +#[test_case("null", Ok(ScriptTokenType::Null) ; "Expects ScriptTokenType::Null")] +#[test_case("print", Ok(ScriptTokenType::Print) ; "Expects ScriptTokenType::Print")] +#[test_case("return", Ok(ScriptTokenType::Return) ; "Expects ScriptTokenType::Return")] +#[test_case("super", Ok(ScriptTokenType::Super) ; "Expects ScriptTokenType::Super")] +#[test_case("this", Ok(ScriptTokenType::This) ; "Expects ScriptTokenType::This")] +#[test_case("let", Ok(ScriptTokenType::Let) ; "Expects ScriptTokenType::Let")] +#[test_case("while", Ok(ScriptTokenType::While) ; "Expects ScriptTokenType::While")] +#[test_case("export", Ok(ScriptTokenType::Export) ; "Expects ScriptTokenType::Export")] +#[test_case("import", Ok(ScriptTokenType::Import) ; "Expects ScriptTokenType::Import")] +#[test_case("as", Ok(ScriptTokenType::Alias) ; "Expects ScriptTokenType::Alias")] +#[test_case("from", Ok(ScriptTokenType::From) ; "Expects ScriptTokenType::From")] +#[test_case("typeof", Ok(ScriptTokenType::Typeof) ; "Expects ScriptTokenType::Typeof")] +#[test_case("finally", Ok(ScriptTokenType::Finally) ; "Expects ScriptTokenType::Finally")] +#[test_case("foo", Ok(ScriptTokenType::Identifier(String::from("foo"))) ; "Expects ScriptTokenType::Identifier foo")] +#[test_case("foo_bar", Ok(ScriptTokenType::Identifier(String::from("foo_bar"))) ; "Expects ScriptTokenType::Identifier foo_bar")] +#[test_case("fo2ob4r", Ok(ScriptTokenType::Identifier(String::from("fo2ob4r"))) ; "Expects ScriptTokenType::Identifier fo2ob4r")] +fn next_token(source: &'static str, expected: Result<ScriptTokenType, ScannerError>) { + let mut scanner = Scanner::<'static>::new(source); + let token = scanner.scan_token().map(|t| t.token); + assert_eq!(token, expected); } diff --git a/forge-script-lang/src/runtime/vm/chunk_builder.rs b/forge-script-lang/src/runtime/vm/chunk_builder.rs index d67f6d1c723f2fcabca3ef88b081aee2fe565b3e..67841baea881d2fa25b9e2cec360a7b4c17991e1 100644 --- a/forge-script-lang/src/runtime/vm/chunk_builder.rs +++ b/forge-script-lang/src/runtime/vm/chunk_builder.rs @@ -19,8 +19,7 @@ impl ChunkBuilder { pub fn parse(code: &str) -> Result<Chunk, VmError> { let mut builder = ChunkBuilder::new(); - let program = - parse_program(code).map_err(|fe| VmError::ast_parser(format_forge_error(code, &fe)))?; + let program = parse_program(code)?; //.map_err(|fe| VmError::ast_parser(format_forge_error(code, &fe)))?; builder.evaluate_program(&program)?; Ok(builder.take_chunk()) } diff --git a/forge-script-lang/src/runtime/vm/machine.rs b/forge-script-lang/src/runtime/vm/machine.rs index 2be97ec3b72f21d8e517ff906cd58e9ae531f47f..5e7c724e0fbac2abf1233b981f368af5bc9ecdb9 100644 --- a/forge-script-lang/src/runtime/vm/machine.rs +++ b/forge-script-lang/src/runtime/vm/machine.rs @@ -60,6 +60,12 @@ impl From<UnsupportedOperation> for VmError { } } +impl From<String> for VmError { + fn from(value: String) -> Self { + VmError::CompilerError(CompilerErrorKind::Chunkbuilder(value)) + } +} + impl Display for VmError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self {