Introduce namespace configuration syntax

This commit is contained in:
Moritz Hölting 2024-04-06 21:49:25 +02:00
parent 01040964af
commit b105a45154
6 changed files with 132 additions and 16 deletions

View File

@ -1,4 +1,4 @@
# Grammar of the shulkerscript language # Grammar of the ShulkerScript language
## Table of contents ## Table of contents

View File

@ -24,6 +24,7 @@ pub enum KeywordKind {
Group, Group,
Run, Run,
Lua, Lua,
Namespace,
} }
impl ToString for KeywordKind { impl ToString for KeywordKind {
@ -68,6 +69,7 @@ impl KeywordKind {
Self::Group => "group", Self::Group => "group",
Self::Run => "run", Self::Run => "run",
Self::Lua => "lua", Self::Lua => "lua",
Self::Namespace => "namespace",
} }
} }
} }

View File

@ -22,7 +22,7 @@ pub mod transpile;
use std::{cell::Cell, fmt::Display, path::Path}; use std::{cell::Cell, fmt::Display, path::Path};
use base::{source_file::SourceFile, Handler, Result}; use base::{source_file::SourceFile, Handler, Result};
use syntax::syntax_tree::program::Program; use syntax::syntax_tree::program::ProgramFile;
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use transpile::transpiler::Transpiler; use transpile::transpiler::Transpiler;
@ -49,7 +49,7 @@ pub fn tokenize(path: &Path) -> Result<TokenStream> {
/// # Errors /// # Errors
/// - If an error occurs while reading the file. /// - If an error occurs while reading the file.
/// - If an error occurs while parsing the source code. /// - If an error occurs while parsing the source code.
pub fn parse(path: &Path) -> Result<Program> { pub fn parse(path: &Path) -> Result<ProgramFile> {
let source_file = SourceFile::load(path)?; let source_file = SourceFile::load(path)?;
let printer = Printer::new(); let printer = Printer::new();

View File

@ -21,6 +21,13 @@ use crate::{
use super::{statement::Block, ConnectedList}; use super::{statement::Block, ConnectedList};
/// Syntax Synopsis:
///
/// ``` ebnf
/// Declaration:
/// Function
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Declaration { pub enum Declaration {

View File

@ -3,27 +3,128 @@
use getset::Getters; use getset::Getters;
use crate::{ use crate::{
base::Handler, base::{
source_file::{SourceElement, Span},
Handler,
},
lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
syntax::{ syntax::{
error::Error, error::{Error, SyntaxKind, UnexpectedSyntax},
parser::{Parser, Reading}, parser::{Parser, Reading},
}, },
}; };
use super::declaration::Declaration; 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))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] #[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. /// The declarations within the program.
#[get = "pub"] #[get = "pub"]
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
} }
/// 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> { impl<'a> Parser<'a> {
/// Parses a [`Program`]. /// Parses a [`ProgramFile`].
pub fn parse_program(&mut self, handler: &impl Handler<Error>) -> Option<Program> { pub fn parse_program(&mut self, handler: &impl Handler<Error>) -> Option<ProgramFile> {
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(); let mut declarations = Vec::new();
while !self.is_exhausted() { while !self.is_exhausted() {
@ -44,6 +145,9 @@ impl<'a> Parser<'a> {
} }
} }
Some(Program { declarations }) Some(ProgramFile {
namespace,
declarations,
})
} }
} }

View File

@ -10,7 +10,7 @@ use crate::{
syntax::syntax_tree::{ syntax::syntax_tree::{
declaration::Declaration, declaration::Declaration,
expression::{Expression, FunctionCall, Primary}, expression::{Expression, FunctionCall, Primary},
program::Program, program::{Namespace, ProgramFile},
statement::{Conditional, Statement}, statement::{Conditional, Statement},
}, },
}; };
@ -55,11 +55,13 @@ impl Transpiler {
/// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing /// - [`TranspileError::MissingFunctionDeclaration`] If a called function is missing
pub fn transpile( pub fn transpile(
&mut self, &mut self,
program: &Program, program: &ProgramFile,
handler: &impl Handler<TranspileError>, handler: &impl Handler<TranspileError>,
) -> Result<(), TranspileError> { ) -> Result<(), TranspileError> {
let namespace = program.namespace();
for declaration in program.declarations() { for declaration in program.declarations() {
self.transpile_declaration(declaration, handler); self.transpile_declaration(declaration, namespace, handler);
} }
let mut always_transpile_functions = Vec::new(); let mut always_transpile_functions = Vec::new();
@ -88,6 +90,7 @@ impl Transpiler {
fn transpile_declaration( fn transpile_declaration(
&mut self, &mut self,
declaration: &Declaration, declaration: &Declaration,
namespace: &Namespace,
_handler: &impl Handler<TranspileError>, _handler: &impl Handler<TranspileError>,
) { ) {
match declaration { match declaration {
@ -109,7 +112,7 @@ impl Transpiler {
self.functions.write().unwrap().insert( self.functions.write().unwrap().insert(
name, name,
FunctionData { FunctionData {
namespace: "shulkerscript".to_string(), namespace: namespace.namespace_name().str_content().to_string(),
statements, statements,
annotations, annotations,
}, },