Add 'Run' statement to grammar and syntax tree

This commit is contained in:
Moritz Hölting 2024-04-06 15:12:20 +02:00
parent c770a54517
commit 9f8b31e2aa
6 changed files with 115 additions and 21 deletions

View File

@ -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:

View File

@ -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",
}
}
}

View File

@ -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();

View File

@ -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)?;

View File

@ -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.

View File

@ -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<TranspileError>,
) -> TranspileResult<Command> {
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<TranspileError>,
) -> TranspileResult<Command> {
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}")))
}
}
}
}