diff --git a/.gitignore b/.gitignore index ff2439cfcf9428eb0857b6eb6b3c416f552f1353..e28572ce52e6b4869fbec1bd3d544798bba8b7db 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,4 @@ target/ # Generated Parser -forge-script-lang/src/parser/forge_script.rs \ No newline at end of file +forge-script-lang/src/parser/forge_grammar.rs \ No newline at end of file diff --git a/forge-script-lang/src/build.rs b/forge-script-lang/src/build.rs index 30948686dbcff96db3c8d664474117a783423706..194123dc6bdccfbcce539cbb9f25f3f7778fed72 100644 --- a/forge-script-lang/src/build.rs +++ b/forge-script-lang/src/build.rs @@ -1,5 +1,6 @@ pub fn main() { lalrpop::Configuration::new() - .process_file("src/parser/forge_script.lalrpop") + .generate_in_source_tree() + .process_file("src/parser/forge_grammar.lalrpop") .expect("Failed to parse language grammar"); } diff --git a/forge-script-lang/src/error/mod.rs b/forge-script-lang/src/error/mod.rs index b3986af0e3b1e7cba39ae28595bf960cc61e8bc3..f74116195a3c6555dc114ffe88fab73b799e9496 100644 --- a/forge-script-lang/src/error/mod.rs +++ b/forge-script-lang/src/error/mod.rs @@ -1,8 +1,12 @@ -use crate::lexer::Span; +use crate::lexer::{ScriptTokenType, Span}; use crate::parse::ScriptToken; -use peg::error::ExpectedSet; +use crate::utilities::offset_to_line_column; +use lalrpop_util::ParseError as BaseLalrError; use std::error::Error; use std::fmt::{Display, Formatter}; +use std::process::{ExitCode, Termination}; + +pub type LalrError<'a> = BaseLalrError<usize, ScriptTokenType<'a>, TokenError<'a>>; #[derive(Debug)] pub enum TokenErrorKind<'a> { @@ -61,13 +65,53 @@ pub enum ForgeErrorKind<'a> { found: ScriptToken<'a>, expected: peg::error::ExpectedSet, }, + InvalidToken { + location: TokenIndex, + }, + UnexpectedEof { + expected: Vec<String>, + }, + UnrecognizedToken { + token: ScriptTokenType<'a>, + span: ErrorSpan, + expected: Vec<String>, + }, + ExpectedEof { + token: ScriptTokenType<'a>, + span: ErrorSpan, + }, + Custom(String), } +pub type TokenIndex = usize; +pub type ErrorSpan = (TokenIndex, TokenIndex); + #[derive(Debug, PartialEq)] pub struct ForgeError<'a> { pub kind: ForgeErrorKind<'a>, } +impl<'a> Termination for ForgeError<'a> { + fn report(self) -> ExitCode { + match self.kind { + ForgeErrorKind::IncompleteInput => ExitCode::from(101), + ForgeErrorKind::LexerError(_) => ExitCode::from(102), + ForgeErrorKind::UnexpectedToken { .. } => ExitCode::from(103), + ForgeErrorKind::InvalidToken { .. } => ExitCode::from(104), + ForgeErrorKind::UnexpectedEof { .. } => ExitCode::from(105), + ForgeErrorKind::UnrecognizedToken { .. } => ExitCode::from(106), + ForgeErrorKind::ExpectedEof { .. } => ExitCode::from(107), + ForgeErrorKind::Custom(_) => ExitCode::from(1), + } + } +} + +impl<'a> From<ForgeErrorKind<'a>> for ForgeError<'a> { + fn from(value: ForgeErrorKind<'a>) -> Self { + Self { kind: value } + } +} + impl<'a> From<ParseError<'a>> for ForgeError<'a> { fn from(value: ParseError<'a>) -> Self { match value.kind { @@ -91,6 +135,31 @@ impl<'a> From<TokenError<'a>> for ForgeError<'a> { } } +impl<'a> From<LalrError<'a>> for ForgeError<'a> { + fn from(value: LalrError<'a>) -> Self { + match value { + LalrError::InvalidToken { location } => { + ForgeErrorKind::InvalidToken { location }.into() + } + LalrError::UnrecognizedEof { expected, .. } => { + ForgeErrorKind::UnexpectedEof { expected }.into() + } + LalrError::UnrecognizedToken { token, expected } => ForgeErrorKind::UnrecognizedToken { + expected, + token: token.1, + span: (token.0, token.2), + } + .into(), + LalrError::ExtraToken { token } => ForgeErrorKind::ExpectedEof { + token: token.1, + span: (token.0, token.2), + } + .into(), + LalrError::User { error } => ForgeErrorKind::Custom(format!("{}", error)).into(), + } + } +} + pub type ForgeResult<'a, T> = Result<T, ForgeError<'a>>; pub fn print_unexpected_token<'a>( @@ -134,79 +203,178 @@ pub fn print_unexpected_token<'a>( eprintln!("|\n| Failed To Parse: expected {}", expected); } -pub struct ErrorFormat<'a>(pub &'a str, pub &'a ScriptToken<'a>, pub &'a ExpectedSet); +pub fn print_forge_error<'a>(source: &'a str, fe: &'a ForgeError) { + eprintln!("{}", format_forge_error(source, fe)); +} + +pub fn format_forge_error<'a>(source: &'a str, fe: &'a ForgeError) -> String { + match &fe.kind { + ForgeErrorKind::IncompleteInput => String::from("| Unexpected end of file"), + ForgeErrorKind::LexerError(err) => format!("| {}", err), + ForgeErrorKind::UnexpectedToken { found, expected } => { + format!( + "{}", + SourcePrinter( + source, + format!("{}", found.token_type), + HighlightConfig { + line: found.position.location_line() as usize, + column: found.position.get_column(), + highlight_len: found.token_type.len(), + }, + Some(format!( + "| Found {}, expected one of {}", + found.token_type, + expected + .tokens() + .collect::<Vec<&str>>() + .as_slice() + .join(", ") + )) + ), + ) + } + ForgeErrorKind::InvalidToken { location } => { + let (line, column) = offset_to_line_column(source, *location); + format!( + "{}", + SourcePrinter( + source, + String::from("invalid token"), + HighlightConfig { + line, + column, + highlight_len: 1, + }, + None, + ) + ) + } + ForgeErrorKind::UnexpectedEof { expected } => { + format!( + "| Unexpected end of file, expected one of {}", + expected.as_slice().join(", ") + ) + } + ForgeErrorKind::UnrecognizedToken { + token, + span, + expected, + } => { + let (line, column) = offset_to_line_column(source, span.0); + format!( + "{}", + SourcePrinter( + source, + format!("{}", token), + HighlightConfig { + line, + column, + highlight_len: token.len(), + }, + Some(format!( + "| Found {}, expected one of {}", + token, + expected.as_slice().join(", ") + )) + ), + ) + } + ForgeErrorKind::ExpectedEof { token, span } => { + let (line, column) = offset_to_line_column(source, span.0); + format!( + "{}", + SourcePrinter( + source, + format!("{}", token), + HighlightConfig { + line, + column, + highlight_len: token.len(), + }, + Some(format!("| Expected EOF, found {}", token)) + ), + ) + } + ForgeErrorKind::Custom(msg) => format!("| {}", msg), + } +} + +#[derive(Clone, Copy)] +pub struct HighlightConfig { + line: usize, + column: usize, + highlight_len: usize, +} -impl<'a> Display for ErrorFormat<'a> { +struct SourcePrinter<'a>(&'a str, String, HighlightConfig, Option<String>); +impl<'a> Display for SourcePrinter<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let ErrorFormat(source, token, expected) = self; - - let line = token.position.location_line() as usize; - let column = token.position.get_column(); - - let previous_line = if line > 1 { - source.lines().nth(line - 2) - } else { - None - }; - let source_line = source.lines().nth(line - 1).expect("Missing line"); - let next_line = source.lines().nth(line); - - let largest_line_num = line.max(line.saturating_sub(1)).max(line.saturating_add(1)); - let number_length = format!("{}", largest_line_num).len(); - - writeln!(f, "| Script error on line {} at \"{}\"\n|", line, token)?; - if let Some(prev) = previous_line { - writeln!( - f, - "| [{:>width$}] {}", - line - 1, - prev, - width = number_length - )?; - } + highlight_source_snippet(self.0, &self.1, self.2, self.3.clone(), f) + } +} + +pub fn highlight_source_snippet( + source: &str, + name: impl Display, + highlight: HighlightConfig, + additional: Option<String>, + f: &mut Formatter<'_>, +) -> std::fmt::Result { + let HighlightConfig { + line, + column, + highlight_len, + } = highlight; + + let previous_line = if line > 1 { + source.lines().nth(line - 2) + } else { + None + }; + let source_line = source.lines().nth(line - 1).expect("Missing line"); + let next_line = source.lines().nth(line); + + let largest_line_num = line.max(line.saturating_sub(1)).max(line.saturating_add(1)); + let number_length = format!("{}", largest_line_num).len(); + + writeln!(f, "| Script error on line {} at \"{}\"\n|", line, name)?; + if let Some(prev) = previous_line { writeln!( f, "| [{:>width$}] {}", - line, - source_line, + line - 1, + prev, width = number_length )?; + } + writeln!( + f, + "| [{:>width$}] {}", + line, + source_line, + width = number_length + )?; + writeln!( + f, + "| {} {}{}", + vec![" "; number_length + 2].join(""), + vec![" "; column - 1].join(""), + vec!["^"; highlight_len].join(""), + )?; + if let Some(next) = next_line { writeln!( f, - "| {} {}{}", - vec![" "; number_length + 2].join(""), - vec![" "; column - 1].join(""), - vec!["^"; token.token_type.len()].join(""), + "| [{:>width$}] {}", + line + 1, + next, + width = number_length )?; - if let Some(next) = next_line { - writeln!( - f, - "| [{:>width$}] {}", - line + 1, - next, - width = number_length - )?; - } - writeln!(f, "|\n| Failed To Parse: expected {}", expected) - } -} - -pub fn print_forge_error<'a>(source: &'a str, fe: &'a ForgeError) { - match &fe.kind { - ForgeErrorKind::IncompleteInput => eprintln!("| Unexpected end of file"), - ForgeErrorKind::LexerError(err) => eprintln!("| {}", err), - ForgeErrorKind::UnexpectedToken { found, expected } => { - print_unexpected_token(source, found, expected) - } } -} -pub fn format_forge_error<'a>(source: &'a str, fe: &'a ForgeError) -> String { - match &fe.kind { - ForgeErrorKind::IncompleteInput => String::from("| Unexpected end of file"), - ForgeErrorKind::LexerError(err) => format!("| {}", err), - ForgeErrorKind::UnexpectedToken { found, expected } => { - format!("{}", ErrorFormat(source, found, expected)) - } + if let Some(extra) = additional { + writeln!(f, "|\n| {}", extra) + } else { + writeln!(f, "|") } } diff --git a/forge-script-lang/src/lexer/keywords.rs b/forge-script-lang/src/lexer/keywords.rs index 892876ec38aae9f8e090805fc84b28b0cab85a29..62a92c9f5db923f0b57ad29850c4989fcad597a8 100644 --- a/forge-script-lang/src/lexer/keywords.rs +++ b/forge-script-lang/src/lexer/keywords.rs @@ -66,7 +66,7 @@ pub fn token_if(input: Span) -> IResult<Span, ScriptToken> { pub fn token_null(input: Span) -> IResult<Span, ScriptToken> { let (input, pos) = position(input)?; - let (input, _) = tag_ws("null", input)?; + let (input, _) = tag("null")(input)?; Ok(( input, ScriptToken { diff --git a/forge-script-lang/src/lexer/mod.rs b/forge-script-lang/src/lexer/mod.rs index 685a1bc28b12c38b2c2c69e8e657907f37dd21eb..f0b09374e008c12a59897ed41510b8caa759e957 100644 --- a/forge-script-lang/src/lexer/mod.rs +++ b/forge-script-lang/src/lexer/mod.rs @@ -108,6 +108,24 @@ mod _lex { Ok(tokens) } + + #[cfg(test)] + mod lex_test { + use super::*; + use test_case::test_case; + + #[test_case("123" => matches Ok(ScriptTokenType::Integer(123)))] + #[test_case("0.123" => matches Ok(ScriptTokenType::Float(_)))] + #[test_case("null" => matches Ok(ScriptTokenType::Null))] + #[test_case("foobar" => matches Ok(ScriptTokenType::Identifier(_)))] + #[test_case("true" => matches Ok(ScriptTokenType::Boolean(true)))] + #[test_case("false" => matches Ok(ScriptTokenType::Boolean(false)))] + fn correct_lexing(inp: &str) -> Result<ScriptTokenType, ()> { + any_token(Span::new(inp)) + .map_err(|_| ()) + .map(|tok| tok.1.token_type) + } + } } use crate::parser::TokenSlice; diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs index feab3203bacc7bc0b4a65da38d576fac9d0a87ee..24690c24670dfede9be8289f83412cf38dcc74f0 100644 --- a/forge-script-lang/src/parser/ast.rs +++ b/forge-script-lang/src/parser/ast.rs @@ -6,13 +6,13 @@ use std::ops::Deref; pub trait AstNode {} #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Program(pub ExpressionList); impl AstNode for Program {} #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ExpressionList { pub expressions: Vec<Expression>, @@ -51,7 +51,7 @@ impl Deref for ExpressionList { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -144,7 +144,7 @@ impl Display for BinaryOp { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -158,14 +158,14 @@ pub enum VoidExpression { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GroupedExpression { pub inner: Box<ValueExpression>, } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -194,12 +194,12 @@ pub enum ValueExpression { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TypeofValue(pub Box<ValueExpression>); #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ConditionalLoop { pub block: GuardedBlock, @@ -222,7 +222,7 @@ impl ConditionalLoop { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Conditional { pub blocks: Vec<GuardedBlock>, @@ -230,7 +230,7 @@ pub struct Conditional { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GuardedBlock { pub guard: Box<ValueExpression>, @@ -238,7 +238,7 @@ pub struct GuardedBlock { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Import { pub source: String, @@ -246,14 +246,14 @@ pub struct Import { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Export { pub items: IdentifierList, } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Print { pub expr: Box<ValueExpression>, @@ -270,7 +270,7 @@ pub type IdentifierList = Vec<IdentifierNode>; pub type ParameterList = Vec<ValueExpression>; #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Identifier(pub String); impl Display for Identifier { @@ -282,7 +282,7 @@ impl Display for Identifier { /// Alias an identifier, to create a new way of referring to it /// IdentifierAlias(original, alias) => identifier "as" alias #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct IdentifierAlias(pub String, pub String); impl Display for IdentifierAlias { @@ -292,7 +292,7 @@ impl Display for IdentifierAlias { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -328,7 +328,7 @@ impl IdentifierNode { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -353,7 +353,7 @@ impl Display for LiteralNode { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { pub ident: Identifier, @@ -361,7 +361,7 @@ pub struct Assignment { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -373,7 +373,7 @@ pub enum DeclareIdent { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DeclareFunction { pub ident: Identifier, @@ -382,14 +382,25 @@ pub struct DeclareFunction { } #[derive(Clone)] -#[cfg_attr(feature = "debug-ast", derive(Debug))] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FunctionCall { pub name: Identifier, pub params: ParameterList, } -macro_rules! impl_into_ast { +macro_rules! impl_into_void_expr { + ($($type: ty => $variant: expr),+) => { + $( + impl From<$type> for VoidExpression { + fn from(other: $type) -> Self { + $variant(other) + } + } + )+ + }; +} +macro_rules! impl_into_value_expr { ($($type: ty => $variant: expr),+) => { $( impl From<$type> for ValueExpression { @@ -401,7 +412,7 @@ macro_rules! impl_into_ast { }; } -impl_into_ast!( +impl_into_value_expr!( GroupedExpression => ValueExpression::Grouped, ExpressionList => ValueExpression::Block, LiteralNode => ValueExpression::Literal, @@ -413,3 +424,10 @@ impl_into_ast!( DeclareFunction => ValueExpression::DeclareFunction, TypeofValue => ValueExpression::Typeof ); + +impl_into_void_expr!( + Print => VoidExpression::Print, + ConditionalLoop => VoidExpression::ConditionLoop, + Import => VoidExpression::Import, + Export => VoidExpression::Export +); diff --git a/forge-script-lang/src/parser/forge_script.lalrpop b/forge-script-lang/src/parser/forge_grammar.lalrpop similarity index 51% rename from forge-script-lang/src/parser/forge_script.lalrpop rename to forge-script-lang/src/parser/forge_grammar.lalrpop index 06cb12775d0cb6adfd4b9cc26245d9b6e2af9cba..1066ca290abb411cfc9b5b48aa497f86a6dab1dd 100644 --- a/forge-script-lang/src/parser/forge_script.lalrpop +++ b/forge-script-lang/src/parser/forge_grammar.lalrpop @@ -4,17 +4,78 @@ use crate::runtime::numbers::Number; grammar<'a>; +pub Program: Program = { + ExpressionList => Program(<>) +}; + +Block: Option<ExpressionList> = { + "{" <ExpressionList?> "}" => <>, +}; + +ExpressionList: ExpressionList = { + <mut v:(<Expression> ";")+> <e:Expression?> => { + match e { + Some(val) => { + v.push(val); + ExpressionList { + expressions: v, + is_void: false, + } + }, + None => { + ExpressionList { + expressions: v, + is_void: true, + } + }, + } + } +}; + pub Expression: Expression = { ValueExpression => Expression::Value(<>), -} + VoidExpression => Expression::Void(<>), +}; + +VoidExpression: VoidExpression = { + "print" <expr:ValueExpression> => Print { expr: Box::new(expr) }.into(), + "import" "{" <items:IdentifierList> "}" "from" <source:StringValue> => Import { source, items }.into(), + "export" "{" <items:IdentifierList> "}" => Export { items }.into(), +}; ValueExpression: ValueExpression = { #[precedence(level="1")] Literal => ValueExpression::Literal(<>), #[precedence(level="1")] + "(" <expr:ValueExpression> ")" => GroupedExpression { inner: Box::new(expr) }.into(), + + #[precedence(level="2")] #[assoc(side="right")] "typeof" <expr:ValueExpression> => TypeofValue(Box::new(expr)).into(), + #[precedence(level="2")] #[assoc(side="right")] + "-" <expr:ValueExpression> => ValueExpression::Unary { operand: Box::new(expr), operator: UnaryOp::Negate }, + #[precedence(level="2")] #[assoc(side="right")] + "!" <expr:ValueExpression> => ValueExpression::Unary { operand: Box::new(expr), operator: UnaryOp::Not }, + #[precedence(level = "3")] + IfStatement => <>.into(), + + #[precedence(level="4")] #[assoc(side="left")] + <lhs:ValueExpression> "*" <rhs:ValueExpression> => { + ValueExpression::Binary { + lhs: Box::new(lhs), + rhs: Box::new(rhs), + operator: BinaryOp::Multiply, + } + }, #[precedence(level="4")] #[assoc(side="left")] + <lhs:ValueExpression> "/" <rhs:ValueExpression> => { + ValueExpression::Binary { + lhs: Box::new(lhs), + rhs: Box::new(rhs), + operator: BinaryOp::Divide, + } + }, + #[precedence(level="6")] #[assoc(side="left")] <lhs:ValueExpression> "+" <rhs:ValueExpression> => { ValueExpression::Binary { lhs: Box::new(lhs), @@ -22,7 +83,7 @@ ValueExpression: ValueExpression = { operator: BinaryOp::Add, } }, - #[precedence(level="4")] #[assoc(side="left")] + #[precedence(level="6")] #[assoc(side="left")] <lhs:ValueExpression> "-" <rhs:ValueExpression> => { ValueExpression::Binary { lhs: Box::new(lhs), @@ -30,7 +91,49 @@ ValueExpression: ValueExpression = { operator: BinaryOp::Subtract, } }, -} + + #[precedence(level="1")] + Identifier => <>.into() +}; + +IfStatement: Conditional = { + BareIfStatement => Conditional { blocks: vec![<>], fallback: None }, + <fi: BareIfStatement> "else" <bl:Block> => Conditional { blocks: vec![fi], fallback: bl }, + <fi:BareIfStatement> "else" <ls:IfStatement> => { + let mut ls = ls; + let mut new = vec![fi]; + new.append(&mut ls.blocks); + Conditional { + blocks: new, + fallback: ls.fallback, + } + } +}; + +BareIfStatement: GuardedBlock = { + "if" <guard:ValueExpression> <bl:Block> => { + GuardedBlock { + block: bl.unwrap_or_default(), + guard: Box::new(guard), + } + } +}; + +ParameterList = ListOf<ValueExpression>; +IdentifierList = ListOf<IdentifierNode>; + +IdentifierNode: IdentifierNode = { + IdentifierAlias => IdentifierNode::Alias(<>), + Identifier => IdentifierNode::Direct(<>), +}; + +IdentifierAlias: IdentifierAlias = { + <base:Identifier> "as" <alias:Identifier> => IdentifierAlias(base.0, alias.0), +}; + +Identifier: Identifier = { + "identifier" => Identifier(String::from(<>)) +}; Literal: LiteralNode = { "boolean" => LiteralNode::Boolean(<>), @@ -39,12 +142,22 @@ Literal: LiteralNode = { "integer" => LiteralNode::Number(Number::Integer(<>)), "string" => LiteralNode::String(String::from(<>)), "owned_string" => LiteralNode::String(<>), -} +}; StringValue: String = { "string" => String::from(<>), "owned_string" => <>, -} +}; + +ListOf<T>: Vec<T> = { + <mut v:(<T> ",")+> <e:T?> => match e { + None => v, + Some(e) => { + v.push(e); + v + } + } +}; extern { type Location = usize; diff --git a/forge-script-lang/src/parser/forge_script.rs b/forge-script-lang/src/parser/forge_script.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc9abccb9fac5ddd752065ba34e74f0c0dce216b --- /dev/null +++ b/forge-script-lang/src/parser/forge_script.rs @@ -0,0 +1,71 @@ +// use lalrpop_util::lalrpop_mod; +// lalrpop_mod!(pub forge_script); + +use crate::error::ForgeResult; +use crate::lexer::{script_to_tokens, ScriptTokenType}; +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>>; + +macro_rules! export_grammar_fn { + ($name:ident = $output:ty => $part: tt) => { + pub fn $name(source: &str) -> ForgeResult<$output> { + let tokens = script_to_tokens(source)? + .iter() + .map(|tok| { + Ok(( + tok.position.start(), + tok.token_type.clone(), + tok.position.start() + tok.position.len(), + )) + }) + .collect::<Vec<ExprSpan>>(); + + let value = + super::forge_grammar::$part::new().parse::<ExprSpan, Vec<ExprSpan>>(tokens)?; + + Ok(value) + } + }; +} + +export_grammar_fn!(parse_program = Program => ProgramParser); +export_grammar_fn!(parse_expression = Expression => ExpressionParser); + +// pub fn parse_expression(source: &str) -> ForgeResult<Expression> { +// let tokens = script_to_tokens(source)? +// .iter() +// .map(|tok| { +// Ok(( +// tok.position.start(), +// tok.token_type.clone(), +// tok.position.start() + tok.position.len(), +// )) +// }) +// .collect::<Vec<ExprSpan>>(); +// +// let value = +// super::forge_grammar::ExpressionParser::new().parse::<ExprSpan, Vec<ExprSpan>>(tokens)?; +// +// Ok(value) +// } + +#[cfg(test)] +mod grammar_test { + use super::parse_expression; + use crate::error::ForgeResult; + use crate::parse::ast::Expression; + use test_case::test_case; + + #[test_case("123" => matches Ok(_) ; "Parse literal number")] + #[test_case(r#""Basic String""# => matches Ok(_) ; "Parse literal string")] + #[test_case("false" => matches Ok(_) ; "Parse literal false")] + #[test_case("true" => matches Ok(_) ; "Parse literal true")] + #[test_case("null" => matches Ok(_) ; "Parse literal null")] + fn expression_parsing<'a>(prog: &'a str) -> ForgeResult<'a, Expression> { + parse_expression(prog) + } +} diff --git a/forge-script-lang/src/parser/lr_grammar.rs b/forge-script-lang/src/parser/lr_grammar.rs deleted file mode 100644 index c7d0e398ddab119d291257f7aafe89e0c5c7940b..0000000000000000000000000000000000000000 --- a/forge-script-lang/src/parser/lr_grammar.rs +++ /dev/null @@ -1,45 +0,0 @@ -// use lalrpop_util::lalrpop_mod; -// lalrpop_mod!(pub forge_script); - -use crate::lexer::{ScriptToken, ScriptTokenType}; -use crate::TokenError; -use peg::Parse; -use std::slice::Iter; - -pub struct TokenInput<'a>(std::slice::Iter<'a, ScriptToken<'a>>); - -impl<'a> From<Iter<'a, ScriptToken<'a>>> for TokenInput<'a> { - fn from(value: Iter<'a, ScriptToken<'a>>) -> Self { - TokenInput(value) - } -} - -pub type InputSpan<'a, Loc, Tok> = Result<(Loc, Tok, Loc), TokenError<'a>>; - -impl<'a> Iterator for TokenInput<'a> { - type Item = InputSpan<'a, usize, ScriptTokenType<'a>>; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next().map(|tok| { - Ok(( - tok.position.start(), - tok.token_type.clone(), - tok.position.start() + tok.position.len(), - )) - }) - } -} - -#[cfg(test)] -mod grammar_test { - use crate::lexer::{script_to_tokens, ScriptTokenType}; - use crate::parser::lr_grammar::{InputSpan, TokenInput}; - - #[test] - fn basic_parsing() { - let code = script_to_tokens("4 + 4").expect("Failed to parse"); - crate::parser::forge_script::ExpressionParser::new() - .parse::<InputSpan<usize, ScriptTokenType>, TokenInput>(code.iter().into()) - .expect("Failed to boop"); - } -} diff --git a/forge-script-lang/src/parser/mod.rs b/forge-script-lang/src/parser/mod.rs index 2745c9fb11a7f3b750763d3faa1b5b289f95f716..03f85bf9a20e607e84077659a247af2e8c5b6344 100644 --- a/forge-script-lang/src/parser/mod.rs +++ b/forge-script-lang/src/parser/mod.rs @@ -1,11 +1,11 @@ pub mod ast; mod atoms; +mod forge_script; mod grammar; -mod lr_grammar; #[cfg(test)] mod test_suite; -mod forge_script; +pub(crate) mod forge_grammar; use crate::error::{ForgeError, ForgeErrorKind, ForgeResult}; use crate::print_forge_error; @@ -15,44 +15,47 @@ pub fn slice<'a>(toks: &'a [crate::lexer::ScriptToken]) -> TokenSlice<'a> { TokenSlice(toks) } -pub fn parse_expression(expr: &str) -> ForgeResult<ast::Expression> { - let tokens = crate::lexer::script_to_tokens(expr)?; - let result = match grammar::expression(&TokenSlice(tokens.as_slice())) { - Ok(expr) => Ok(expr), - Err(parse_error) => { - let bad_token = &tokens[parse_error.location]; - Err(ForgeError { - kind: ForgeErrorKind::UnexpectedToken { - found: bad_token.clone(), - expected: parse_error.expected, - }, - }) - } - }; +pub use forge_script::parse_expression; +pub use forge_script::parse_program; - result.map_err(|e| { - print_forge_error(expr, &e); - e - }) -} +// pub fn parse_expression(expr: &str) -> ForgeResult<ast::Expression> { +// let tokens = crate::lexer::script_to_tokens(expr)?; +// let result = match grammar::expression(&TokenSlice(tokens.as_slice())) { +// Ok(expr) => Ok(expr), +// Err(parse_error) => { +// let bad_token = &tokens[parse_error.location]; +// Err(ForgeError { +// kind: ForgeErrorKind::UnexpectedToken { +// found: bad_token.clone(), +// expected: parse_error.expected, +// }, +// }) +// } +// }; +// +// result.map_err(|e| { +// print_forge_error(expr, &e); +// e +// }) +// } -pub fn parse_program(prog: &str) -> ForgeResult<ast::Program> { - let tokens = crate::lexer::script_to_tokens(prog)?; - let result = match grammar::program(&TokenSlice(tokens.as_slice())) { - Ok(prog) => Ok(prog.clone()), - Err(parse_error) => { - let bad_token = &tokens[parse_error.location]; - Err(ForgeError { - kind: ForgeErrorKind::UnexpectedToken { - found: bad_token.clone(), - expected: parse_error.expected, - }, - }) - } - }; - - result.map_err(|e| { - print_forge_error(prog, &e); - e - }) -} +// pub fn parse_program(prog: &str) -> ForgeResult<ast::Program> { +// let tokens = crate::lexer::script_to_tokens(prog)?; +// let result = match grammar::program(&TokenSlice(tokens.as_slice())) { +// Ok(prog) => Ok(prog.clone()), +// Err(parse_error) => { +// let bad_token = &tokens[parse_error.location]; +// Err(ForgeError { +// kind: ForgeErrorKind::UnexpectedToken { +// found: bad_token.clone(), +// expected: parse_error.expected, +// }, +// }) +// } +// }; +// +// result.map_err(|e| { +// print_forge_error(prog, &e); +// e +// }) +// } diff --git a/forge-script-lang/src/utilities.rs b/forge-script-lang/src/utilities.rs index 6c5c1c25a627fa1e7e62ff9723d5be3a658cf609..0233d1a2a7fbf823b4087f8050241944343ae25e 100644 --- a/forge-script-lang/src/utilities.rs +++ b/forge-script-lang/src/utilities.rs @@ -42,3 +42,23 @@ macro_rules! deref_as { pub trait Clown<Target> { fn clown(&self) -> Target; } + +type LineNum = usize; +type ColumnNum = usize; + +pub fn offset_to_line_column(source: &str, offset: usize) -> (LineNum, ColumnNum) { + let mut remaining = offset; + let mut current_line = 0; + for line in source.lines() { + current_line += 1; + if remaining < line.len() { + return (current_line, remaining + 1); + } + remaining -= line.len(); + } + + ( + current_line, + source.lines().last().map(|l| l.len()).unwrap_or(0), + ) +}