diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 61afef0..29fef4e 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -38,6 +38,8 @@ pub enum KeywordKind { Run, Lua, Namespace, + From, + Import, } impl ToString for KeywordKind { @@ -96,6 +98,8 @@ impl KeywordKind { Self::Run => "run", Self::Lua => "lua", Self::Namespace => "namespace", + Self::From => "from", + Self::Import => "import", } } diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index 86b67a0..b36bcf5 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -7,7 +7,7 @@ use getset::Getters; use crate::{ base::{ source_file::{SourceElement, Span}, - Handler, + DummyHandler, Handler, }, lexical::{ token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token}, @@ -32,12 +32,14 @@ use super::{statement::Block, ConnectedList}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Declaration { Function(Function), + Import(Import), } impl SourceElement for Declaration { fn span(&self) -> Span { match self { Self::Function(function) => function.span(), + Self::Import(import) => import.span(), } } } @@ -170,6 +172,58 @@ impl SourceElement for Function { } } +/// Syntax Synopsis: +/// +/// ``` ebnf +/// Import: +/// 'from' StringLiteral 'import' ('*' | Identifier (',' Identifier)*) ';' +/// ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct Import { + #[get = "pub"] + from_keyword: Keyword, + #[get = "pub"] + module: StringLiteral, + #[get = "pub"] + import_keyword: Keyword, + #[get = "pub"] + items: ImportItems, + #[get = "pub"] + semicolon: Punctuation, +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ImportItems { + All(Punctuation), + Named(ConnectedList), +} + +impl Import { + /// Dissolves the [`Import`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, StringLiteral, Keyword, ImportItems, Punctuation) { + ( + self.from_keyword, + self.module, + self.import_keyword, + self.items, + self.semicolon, + ) + } +} + +impl SourceElement for Import { + fn span(&self) -> Span { + self.from_keyword + .span() + .join(&self.semicolon.span()) + .unwrap() + } +} + impl<'a> Parser<'a> { pub fn parse_annotation(&mut self, handler: &impl Handler) -> Option { match self.stop_at_significant() { @@ -229,31 +283,9 @@ impl<'a> Parser<'a> { Reading::Atomic(Token::Keyword(function_keyword)) if function_keyword.keyword == KeywordKind::Function => { - // eat the function keyword - self.forward(); + let function = self.parse_function(handler)?; - // parse the identifier - let identifier = self.parse_identifier(handler)?; - let delimited_tree = self.parse_enclosed_list( - Delimiter::Parenthesis, - ',', - |parser: &mut Parser<'_>| parser.parse_identifier(handler), - handler, - )?; - - // parse the block - let block = self.parse_block(handler)?; - - Some(Declaration::Function(Function { - public_keyword: None, - annotations: Vec::new(), - function_keyword, - identifier, - open_paren: delimited_tree.open, - parameters: delimited_tree.list, - close_paren: delimited_tree.close, - block, - })) + Some(Declaration::Function(function)) } Reading::Atomic(Token::Keyword(pub_keyword)) @@ -263,46 +295,12 @@ impl<'a> Parser<'a> { self.forward(); // parse the function keyword - let function_keyword_reading = self.next_significant_token(); + let function = self.parse_function(handler)?; - match function_keyword_reading { - Reading::Atomic(Token::Keyword(function_keyword)) - if function_keyword.keyword == KeywordKind::Function => - { - // eat the function keyword - self.forward(); - - // parse the identifier - let identifier = self.parse_identifier(handler)?; - let delimited_tree = self.parse_enclosed_list( - Delimiter::Parenthesis, - ',', - |parser: &mut Parser<'_>| parser.parse_identifier(handler), - handler, - )?; - - // parse the block - let block = self.parse_block(handler)?; - - Some(Declaration::Function(Function { - public_keyword: Some(pub_keyword), - annotations: Vec::new(), - function_keyword, - identifier, - open_paren: delimited_tree.open, - parameters: delimited_tree.list, - close_paren: delimited_tree.close, - block, - })) - } - unexpected => { - handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { - expected: SyntaxKind::Keyword(KeywordKind::Function), - found: unexpected.into_token(), - })); - None - } - } + Some(Declaration::Function(Function { + public_keyword: Some(pub_keyword), + ..function + })) } // parse annotations @@ -316,14 +314,68 @@ impl<'a> Parser<'a> { annotations.push(annotation); } - // parse the function - self.parse_declaration(handler) - .map(|declaration| match declaration { - Declaration::Function(mut function) => { - function.annotations.extend(annotations); - Declaration::Function(function) - } - }) + self.parse_declaration(handler).and_then(|declaration| { + if let Declaration::Function(mut function) = declaration { + function.annotations.extend(annotations); + Some(Declaration::Function(function)) + } else { + handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Keyword(KeywordKind::Function), + found: None, + })); + None + } + }) + } + + Reading::Atomic(Token::Keyword(from_keyword)) + if from_keyword.keyword == KeywordKind::From => + { + // eat the from keyword + self.forward(); + + // parse the module + let module = self.parse_string_literal(handler)?; + + let import_keyword = self.parse_keyword(KeywordKind::Import, handler)?; + + // TODO: re-enable when the asterisk is supported + let items = // match self.stop_at_significant() { + // Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '*' => { + // eat the asterisk + // self.forward(); + + // ImportItems::All(punc) + // } + // _ => + self.try_parse(|parser| parser + .parse_connected_list( + ',', + |parser| parser.parse_identifier(&DummyHandler), + handler, + ) + .map(ImportItems::Named)) // , + // } + ; + + if let Some(items) = items { + let semicolon = self.parse_punctuation(';', true, handler)?; + + Some(Declaration::Import(Import { + from_keyword, + module, + import_keyword, + items, + semicolon, + })) + } else { + handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Identifier, + found: self.stop_at_significant().into_token(), + })); + + None + } } unexpected => { @@ -339,4 +391,40 @@ impl<'a> Parser<'a> { } } } + + pub fn parse_function(&mut self, handler: &impl Handler) -> Option { + if let Reading::Atomic(Token::Keyword(function_keyword)) = self.stop_at_significant() { + // eat the function keyword + self.forward(); + + // parse the identifier + let identifier = self.parse_identifier(handler)?; + let delimited_tree = self.parse_enclosed_list( + Delimiter::Parenthesis, + ',', + |parser: &mut Parser<'_>| parser.parse_identifier(handler), + handler, + )?; + + // parse the block + let block = self.parse_block(handler)?; + + Some(Function { + public_keyword: None, + annotations: Vec::new(), + function_keyword, + identifier, + open_paren: delimited_tree.open, + parameters: delimited_tree.list, + close_paren: delimited_tree.close, + block, + }) + } else { + handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax { + expected: SyntaxKind::Keyword(KeywordKind::Function), + found: self.peek().into_token(), + })); + None + } + } } diff --git a/src/syntax/syntax_tree/mod.rs b/src/syntax/syntax_tree/mod.rs index 11f467d..8fe4489 100644 --- a/src/syntax/syntax_tree/mod.rs +++ b/src/syntax/syntax_tree/mod.rs @@ -5,7 +5,7 @@ use getset::Getters; use crate::{ base::{ source_file::{SourceElement, Span}, - Handler, + DummyHandler, Handler, }, lexical::{ token::{Punctuation, Token}, @@ -149,6 +149,45 @@ impl<'a> Parser<'a> { close: delimited_tree.close, }) } + + /// Parses a list of elements separated by a separator. + /// + /// The parser position must be at the connected list of the first element. It will + /// consume the whole connected list and move the next token after the list. + /// + /// # Errors + /// - if the parser position is not at the connected list of the given element. + /// - any error returned by the given parser function. + pub fn parse_connected_list( + &mut self, + seperator: char, + mut f: impl FnMut(&mut Self) -> Option, + _handler: &impl Handler, + ) -> Option> { + let first = f(self)?; + + let mut rest = Vec::new(); + + while let Some(sep) = + self.try_parse(|parser| parser.parse_punctuation(seperator, true, &DummyHandler)) + { + if let Some(element) = self.try_parse(&mut f) { + rest.push((sep, element)); + } else { + return Some(ConnectedList { + first, + rest, + trailing_separator: Some(sep), + }); + } + } + + Some(ConnectedList { + first, + rest, + trailing_separator: None, + }) + } } impl SourceElement diff --git a/src/transpile/mod.rs b/src/transpile/mod.rs index 478089d..151641c 100644 --- a/src/transpile/mod.rs +++ b/src/transpile/mod.rs @@ -8,3 +8,5 @@ pub mod error; pub mod lua; #[cfg(feature = "shulkerbox")] pub mod transpiler; + +mod util; diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 3011c22..7da6e2f 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -1,14 +1,14 @@ //! Transpiler for `ShulkerScript` use chksum_md5 as md5; -use std::{collections::HashMap, sync::RwLock}; +use std::{collections::HashMap, iter, sync::RwLock}; use shulkerbox::datapack::{self, Command, Datapack, Execute}; use crate::{ base::{source_file::SourceElement, Handler}, syntax::syntax_tree::{ - declaration::Declaration, + declaration::{Declaration, ImportItems}, expression::{Expression, FunctionCall, Primary}, program::{Namespace, ProgramFile}, statement::{ @@ -26,13 +26,15 @@ pub struct Transpiler { datapack: shulkerbox::datapack::Datapack, /// Key: (program identifier, function name) functions: RwLock>, - function_locations: RwLock>, + function_locations: RwLock>, + aliases: RwLock>, } #[derive(Debug, Clone)] struct FunctionData { namespace: String, statements: Vec, + public: bool, annotations: HashMap>, } @@ -44,6 +46,7 @@ impl Transpiler { datapack: shulkerbox::datapack::Datapack::new(pack_format), functions: RwLock::new(HashMap::new()), function_locations: RwLock::new(HashMap::new()), + aliases: RwLock::new(HashMap::new()), } } @@ -66,23 +69,7 @@ impl Transpiler { Ident: AsRef, { for (identifier, program) in programs { - self.transpile_program(program, identifier.as_ref(), handler)?; - } - - Ok(()) - } - - /// Transpiles the given program. - fn transpile_program( - &mut self, - program: &ProgramFile, - identifier: &str, - handler: &impl Handler, - ) -> TranspileResult<()> { - let namespace = program.namespace(); - - for declaration in program.declarations() { - self.transpile_declaration(declaration, namespace, identifier, handler); + self.transpile_program_declarations(program, identifier.as_ref(), handler); } let mut always_transpile_functions = Vec::new(); @@ -107,6 +94,20 @@ impl Transpiler { Ok(()) } + /// Transpiles the given program. + fn transpile_program_declarations( + &mut self, + program: &ProgramFile, + identifier: &str, + handler: &impl Handler, + ) { + let namespace = program.namespace(); + + for declaration in program.declarations() { + self.transpile_declaration(declaration, namespace, identifier, handler); + } + } + /// Transpiles the given declaration. fn transpile_declaration( &mut self, @@ -136,10 +137,34 @@ impl Transpiler { FunctionData { namespace: namespace.namespace_name().str_content().to_string(), statements, + public: function.is_public(), annotations, }, ); } + Declaration::Import(import) => { + let path = import.module().str_content(); + let import_identifier = + super::util::calculate_import_identifier(program_identifier, path); + + let mut aliases = self.aliases.write().unwrap(); + + match import.items() { + ImportItems::All(_) => todo!("Importing all items is not yet supported."), + ImportItems::Named(list) => { + let items = iter::once(list.first()) + .chain(list.rest().iter().map(|(_, ident)| ident)); + + for item in items { + let name = item.span.str(); + aliases.insert( + (program_identifier.to_string(), name.to_string()), + (import_identifier.clone(), name.to_string()), + ); + } + } + } + } }; } @@ -153,28 +178,53 @@ impl Transpiler { handler: &impl Handler, ) -> TranspileResult { let program_query = (program_identifier.to_string(), name.to_string()); + let alias_query = { + let aliases = self.aliases.read().unwrap(); + aliases.get(&program_query).cloned() + }; let already_transpiled = { let locations = self.function_locations.read().unwrap(); - locations.get(&program_query).is_some() + locations + .get(&program_query) + .or_else(|| { + alias_query + .clone() + .and_then(|q| locations.get(&q).filter(|(_, p)| *p)) + }) + .is_some() }; if !already_transpiled { let statements = { let functions = self.functions.read().unwrap(); - let function_data = functions.get(&program_query).ok_or_else(|| { - let error = TranspileError::MissingFunctionDeclaration(name.to_string()); - handler.receive(error.clone()); - error - })?; + let function_data = functions + .get(&program_query) + .or_else(|| { + alias_query + .clone() + .and_then(|q| functions.get(&q).filter(|f| f.public)) + }) + .ok_or_else(|| { + let error = TranspileError::MissingFunctionDeclaration(name.to_string()); + handler.receive(error.clone()); + error + })?; function_data.statements.clone() }; let commands = self.transpile_function(&statements, program_identifier, handler)?; let functions = self.functions.read().unwrap(); - let function_data = functions.get(&program_query).ok_or_else(|| { - let error = TranspileError::MissingFunctionDeclaration(name.to_string()); - handler.receive(error.clone()); - error - })?; + let function_data = functions + .get(&program_query) + .or_else(|| { + alias_query + .clone() + .and_then(|q| functions.get(&q).filter(|f| f.public)) + }) + .ok_or_else(|| { + let error = TranspileError::MissingFunctionDeclaration(name.to_string()); + handler.receive(error.clone()); + error + })?; let modified_name = function_data .annotations @@ -208,19 +258,20 @@ impl Transpiler { self.function_locations.write().unwrap().insert( (program_identifier.to_string(), name.to_string()), - function_location, + (function_location, function_data.public), ); } let locations = self.function_locations.read().unwrap(); locations .get(&program_query) + .or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p))) .ok_or_else(|| { let error = TranspileError::MissingFunctionDeclaration(name.to_string()); handler.receive(error.clone()); error }) - .map(String::to_owned) + .map(|(s, _)| s.to_owned()) } fn transpile_function( diff --git a/src/transpile/util.rs b/src/transpile/util.rs new file mode 100644 index 0000000..d170c91 --- /dev/null +++ b/src/transpile/util.rs @@ -0,0 +1,34 @@ +fn normalize_program_identifier(identifier: S) -> String +where + S: AsRef, +{ + identifier + .as_ref() + .split('/') + .fold(Vec::new(), |mut acc, el| match el { + "." | "" => acc, + ".." => { + acc.pop(); + acc + } + _ => { + acc.push(el); + acc + } + }) + .join("/") +} + +pub fn calculate_import_identifier(current_identifier: S, import_path: T) -> String +where + S: AsRef, + T: AsRef, +{ + if import_path.as_ref().starts_with('/') { + normalize_program_identifier(&import_path.as_ref()[1..]) + } else { + let mut identifier_elements = current_identifier.as_ref().split('/').collect::>(); + identifier_elements.pop(); + normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref()) + } +}