diff --git a/grammar.md b/grammar.md index d444ee9..ad442f5 100644 --- a/grammar.md +++ b/grammar.md @@ -35,6 +35,8 @@ Statement: | Conditional | Grouping | DocComment + | Semicolon + | Run ; ``` @@ -43,6 +45,13 @@ Statement: Block: '{' Statement* '}'; ``` +### Run +```ebnf +Run: + 'run' Expression ';' + ; +``` + ### Conditional ```ebnf Conditional: diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 35ba5ad..8279a7e 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -22,6 +22,7 @@ pub enum KeywordKind { If, Else, Group, + Run, } impl ToString for KeywordKind { @@ -64,6 +65,7 @@ impl KeywordKind { Self::If => "if", Self::Else => "else", Self::Group => "group", + Self::Run => "run", } } } diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index 59c8738..350ca54 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -9,7 +9,7 @@ use crate::{ Handler, }, lexical::{ - token::{Identifier, Punctuation, Token}, + token::{Identifier, Punctuation, StringLiteral, Token}, token_stream::Delimiter, }, syntax::{ @@ -52,12 +52,14 @@ impl SourceElement for Expression { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] pub enum Primary { FunctionCall(FunctionCall), + StringLiteral(StringLiteral), } impl SourceElement for Primary { fn span(&self) -> Span { match self { Self::FunctionCall(function_call) => function_call.span(), + Self::StringLiteral(string_literal) => string_literal.span(), } } } @@ -131,6 +133,14 @@ impl<'a> Parser<'a> { } } + // string literal expression + Reading::Atomic(Token::StringLiteral(literal)) => { + // eat the string literal + self.forward(); + + Some(Primary::StringLiteral(literal)) + } + unexpected => { // make progress self.forward(); diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index 21ac523..685fc08 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -28,6 +28,8 @@ use super::{condition::ParenthesizedCondition, expression::Expression}; /// | Conditional /// | Grouping /// | DocComment +/// | Semicolon +/// | Run /// ; /// ``` #[allow(missing_docs)] @@ -40,6 +42,7 @@ pub enum Statement { Grouping(Grouping), DocComment(DocComment), Semicolon(Semicolon), + Run(Run), } impl SourceElement for Statement { @@ -51,6 +54,7 @@ impl SourceElement for Statement { Self::Grouping(grouping) => grouping.span(), Self::DocComment(doc_comment) => doc_comment.span(), Self::Semicolon(semi) => semi.span(), + Self::Run(run) => run.span(), } } } @@ -93,13 +97,51 @@ impl SourceElement for Block { } } +/// Syntax Synopsis: +/// +/// ``` ebnf +/// Run: +/// 'run' Expression ';' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Run { + /// The `run` keyword. + #[get = "pub"] + run_keyword: Keyword, + /// The expression of the run statement. + #[get = "pub"] + expression: Expression, + /// The semicolon of the run statement. + #[get = "pub"] + semicolon: Punctuation, +} + +impl SourceElement for Run { + fn span(&self) -> Span { + self.run_keyword + .span() + .join(&self.semicolon.span()) + .expect("The span of the run statement is invalid.") + } +} + +impl Run { + /// Dissolves the [`Run`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Expression, Punctuation) { + (self.run_keyword, self.expression, self.semicolon) + } +} + /// Syntax Synopsis: /// /// ``` ebnf /// Conditional: /// 'if' ParenthizedCondition Block ('else' Block)? /// ; -/// ```` +/// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] pub struct Conditional { @@ -361,6 +403,23 @@ impl<'a> Parser<'a> { })) } + // run statement + Reading::Atomic(Token::Keyword(run_keyword)) + if run_keyword.keyword == KeywordKind::Run => + { + // eat the run keyword + self.forward(); + + let expression = self.parse_expression(handler)?; + let semicolon = self.parse_punctuation(';', true, handler)?; + + Some(Statement::Run(Run { + run_keyword, + expression, + semicolon, + })) + } + // semicolon statement _ => { let expression = self.parse_expression(handler)?; diff --git a/src/transpile/error.rs b/src/transpile/error.rs index 06c8d8e..6ccaa45 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -1,11 +1,15 @@ //! Errors that can occur during transpilation. +use crate::{base::source_file::SourceElement, syntax::syntax_tree::expression::Expression}; + /// Errors that can occur during transpilation. #[allow(clippy::module_name_repetitions, missing_docs)] #[derive(Debug, thiserror::Error, Clone)] pub enum TranspileError { #[error("Function {} was called but never declared.", .0)] MissingFunctionDeclaration(String), + #[error("Unexpected expression: {}", .0.span().str())] + UnexpectedExpression(Expression), } /// The result of a transpilation operation. diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 8552879..8331c40 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -9,7 +9,7 @@ use crate::{ base::{source_file::SourceElement, Handler}, syntax::syntax_tree::{ declaration::Declaration, - expression::{Expression, Primary}, + expression::{Expression, FunctionCall, Primary}, program::Program, statement::{Conditional, Statement}, }, @@ -226,6 +226,14 @@ impl Transpiler { Statement::LiteralCommand(literal_command) => { Ok(Some(literal_command.clean_command().into())) } + Statement::Run(run) => match run.expression() { + Expression::Primary(Primary::FunctionCall(func)) => { + self.transpile_function_call(func, handler).map(Some) + } + Expression::Primary(Primary::StringLiteral(string)) => { + Ok(Some(Command::Raw(string.str_content().to_string()))) + } + }, Statement::Block(_) => { unreachable!("Only literal commands are allowed in functions at this time.") } @@ -256,14 +264,31 @@ impl Transpiler { Ok(Some(Command::Group(commands))) } } + #[allow(clippy::match_wildcard_for_single_variants)] Statement::Semicolon(semi) => match semi.expression() { - Expression::Primary(primary) => self - .transpile_primary_expression(primary, handler) - .map(Some), + Expression::Primary(Primary::FunctionCall(func)) => { + self.transpile_function_call(func, handler).map(Some) + } + unexpected => { + let error = TranspileError::UnexpectedExpression(unexpected.clone()); + handler.receive(error.clone()); + Err(error) + } }, } } + fn transpile_function_call( + &mut self, + func: &FunctionCall, + handler: &impl Handler, + ) -> TranspileResult { + let identifier = func.identifier().span(); + let identifier_name = identifier.str(); + let location = self.get_or_transpile_function(identifier_name, handler)?; + Ok(Command::Raw(format!("function {location}"))) + } + fn transpile_conditional( &mut self, cond: &Conditional, @@ -344,19 +369,4 @@ impl Transpiler { })) } } - - fn transpile_primary_expression( - &mut self, - primary: &Primary, - handler: &impl Handler, - ) -> TranspileResult { - match primary { - Primary::FunctionCall(func) => { - let identifier = func.identifier().span(); - let identifier_name = identifier.str(); - let location = self.get_or_transpile_function(identifier_name, handler)?; - Ok(Command::Raw(format!("function {location}"))) - } - } - } }