From b105a4515464a5fd8596b4dcf4535db0a52375e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:49:25 +0200 Subject: [PATCH] Introduce namespace configuration syntax --- grammar.md | 6 +- src/lexical/token.rs | 2 + src/lib.rs | 4 +- src/syntax/syntax_tree/declaration.rs | 7 ++ src/syntax/syntax_tree/program.rs | 118 ++++++++++++++++++++++++-- src/transpile/transpiler.rs | 11 ++- 6 files changed, 132 insertions(+), 16 deletions(-) diff --git a/grammar.md b/grammar.md index 0bd3974..f356aa6 100644 --- a/grammar.md +++ b/grammar.md @@ -1,4 +1,4 @@ -# Grammar of the shulkerscript language +# Grammar of the ShulkerScript language ## Table of contents @@ -126,14 +126,14 @@ Expression: ``` ### Primary -``` ebnf +```ebnf Primary: FunctionCall ; ``` ### FunctionCall -``` ebnf +```ebnf FunctionCall: Identifier '(' (Expression (',' Expression)*)? ')' ; diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 95164c0..50e15f3 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -24,6 +24,7 @@ pub enum KeywordKind { Group, Run, Lua, + Namespace, } impl ToString for KeywordKind { @@ -68,6 +69,7 @@ impl KeywordKind { Self::Group => "group", Self::Run => "run", Self::Lua => "lua", + Self::Namespace => "namespace", } } } diff --git a/src/lib.rs b/src/lib.rs index 4b58baa..9179772 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ pub mod transpile; use std::{cell::Cell, fmt::Display, path::Path}; use base::{source_file::SourceFile, Handler, Result}; -use syntax::syntax_tree::program::Program; +use syntax::syntax_tree::program::ProgramFile; #[cfg(feature = "shulkerbox")] use transpile::transpiler::Transpiler; @@ -49,7 +49,7 @@ pub fn tokenize(path: &Path) -> Result { /// # Errors /// - If an error occurs while reading the file. /// - If an error occurs while parsing the source code. -pub fn parse(path: &Path) -> Result { +pub fn parse(path: &Path) -> Result { let source_file = SourceFile::load(path)?; let printer = Printer::new(); diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index fc48aa7..d368ab6 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -21,6 +21,13 @@ use crate::{ use super::{statement::Block, ConnectedList}; +/// Syntax Synopsis: +/// +/// ``` ebnf +/// Declaration: +/// Function +/// ; +/// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Declaration { diff --git a/src/syntax/syntax_tree/program.rs b/src/syntax/syntax_tree/program.rs index 34082ec..7e4ff6a 100644 --- a/src/syntax/syntax_tree/program.rs +++ b/src/syntax/syntax_tree/program.rs @@ -3,27 +3,128 @@ use getset::Getters; use crate::{ - base::Handler, + base::{ + source_file::{SourceElement, Span}, + Handler, + }, + lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token}, syntax::{ - error::Error, + error::{Error, SyntaxKind, UnexpectedSyntax}, parser::{Parser, Reading}, }, }; use super::declaration::Declaration; -/// Program is a collection of declarations. +/// Program is a collection of declarations preceeded by a namespace selector. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] -pub struct Program { +pub struct ProgramFile { + /// The namespace selector. + #[get = "pub"] + namespace: Namespace, /// The declarations within the program. #[get = "pub"] declarations: Vec, } +/// Namespace is a namespace selector. +/// +/// Syntax Synopsis: +/// +/// ```ebnf +/// Namespace: +/// 'namespace' StringLiteral ';' ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +#[allow(missing_docs)] +pub struct Namespace { + /// The `namespace` keyword. + #[get = "pub"] + namespace_keyword: Keyword, + /// The name of the namespace. + #[get = "pub"] + namespace_name: StringLiteral, + /// The semicolon. + #[get = "pub"] + semicolon: Punctuation, +} + +impl SourceElement for Namespace { + fn span(&self) -> Span { + self.namespace_keyword + .span() + .join(&self.semicolon.span()) + .expect("Invalid span") + } +} + +impl Namespace { + /// Dissolves the namespace into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, StringLiteral, Punctuation) { + (self.namespace_keyword, self.namespace_name, self.semicolon) + } + + /// Validates a namespace string. + #[must_use] + pub fn validate_str(namespace: &str) -> bool { + const VALID_CHARS: &str = "0123456789abcdefghijklmnopqrstuvwxyz_-."; + + namespace.chars().all(|c| VALID_CHARS.contains(c)) + } +} + impl<'a> Parser<'a> { - /// Parses a [`Program`]. - pub fn parse_program(&mut self, handler: &impl Handler) -> Option { + /// Parses a [`ProgramFile`]. + pub fn parse_program(&mut self, handler: &impl Handler) -> Option { + let namespace = match self.stop_at_significant() { + Reading::Atomic(Token::Keyword(namespace_keyword)) + if namespace_keyword.keyword == KeywordKind::Namespace => + { + // eat the keyword + self.forward(); + + let namespace_name = match self.stop_at_significant() { + Reading::Atomic(Token::StringLiteral(name)) => { + self.forward(); + Some(name) + } + unexpected => { + self.forward(); + handler.receive( + UnexpectedSyntax { + expected: SyntaxKind::StringLiteral, + found: unexpected.into_token(), + } + .into(), + ); + None + } + } + .and_then(|name| Namespace::validate_str(name.str_content()).then_some(name))?; + + let semicolon = self.parse_punctuation(';', true, handler)?; + + Some(Namespace { + namespace_keyword, + namespace_name, + semicolon, + }) + } + unexpected => { + handler.receive( + UnexpectedSyntax { + expected: SyntaxKind::Keyword(KeywordKind::Namespace), + found: unexpected.into_token(), + } + .into(), + ); + None + } + }?; + let mut declarations = Vec::new(); while !self.is_exhausted() { @@ -44,6 +145,9 @@ impl<'a> Parser<'a> { } } - Some(Program { declarations }) + Some(ProgramFile { + namespace, + declarations, + }) } } diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 9c62700..5a57041 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -10,7 +10,7 @@ use crate::{ syntax::syntax_tree::{ declaration::Declaration, expression::{Expression, FunctionCall, Primary}, - program::Program, + program::{Namespace, ProgramFile}, statement::{Conditional, Statement}, }, }; @@ -55,11 +55,13 @@ impl Transpiler { /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing pub fn transpile( &mut self, - program: &Program, + program: &ProgramFile, handler: &impl Handler, ) -> Result<(), TranspileError> { + let namespace = program.namespace(); + for declaration in program.declarations() { - self.transpile_declaration(declaration, handler); + self.transpile_declaration(declaration, namespace, handler); } let mut always_transpile_functions = Vec::new(); @@ -88,6 +90,7 @@ impl Transpiler { fn transpile_declaration( &mut self, declaration: &Declaration, + namespace: &Namespace, _handler: &impl Handler, ) { match declaration { @@ -109,7 +112,7 @@ impl Transpiler { self.functions.write().unwrap().insert( name, FunctionData { - namespace: "shulkerscript".to_string(), + namespace: namespace.namespace_name().str_content().to_string(), statements, annotations, },