implement from-import statement

This commit is contained in:
Moritz Hölting 2024-06-12 18:09:32 +02:00
parent deddf1d77e
commit 899a973315
6 changed files with 324 additions and 106 deletions

View File

@ -38,6 +38,8 @@ pub enum KeywordKind {
Run, Run,
Lua, Lua,
Namespace, Namespace,
From,
Import,
} }
impl ToString for KeywordKind { impl ToString for KeywordKind {
@ -96,6 +98,8 @@ impl KeywordKind {
Self::Run => "run", Self::Run => "run",
Self::Lua => "lua", Self::Lua => "lua",
Self::Namespace => "namespace", Self::Namespace => "namespace",
Self::From => "from",
Self::Import => "import",
} }
} }

View File

@ -7,7 +7,7 @@ use getset::Getters;
use crate::{ use crate::{
base::{ base::{
source_file::{SourceElement, Span}, source_file::{SourceElement, Span},
Handler, DummyHandler, Handler,
}, },
lexical::{ lexical::{
token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token}, token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
@ -32,12 +32,14 @@ use super::{statement::Block, ConnectedList};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Declaration { pub enum Declaration {
Function(Function), Function(Function),
Import(Import),
} }
impl SourceElement for Declaration { impl SourceElement for Declaration {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Self::Function(function) => function.span(), 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<Identifier, Punctuation>),
}
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> { impl<'a> Parser<'a> {
pub fn parse_annotation(&mut self, handler: &impl Handler<Error>) -> Option<Annotation> { pub fn parse_annotation(&mut self, handler: &impl Handler<Error>) -> Option<Annotation> {
match self.stop_at_significant() { match self.stop_at_significant() {
@ -229,31 +283,9 @@ impl<'a> Parser<'a> {
Reading::Atomic(Token::Keyword(function_keyword)) Reading::Atomic(Token::Keyword(function_keyword))
if function_keyword.keyword == KeywordKind::Function => if function_keyword.keyword == KeywordKind::Function =>
{ {
// eat the function keyword let function = self.parse_function(handler)?;
self.forward();
// parse the identifier Some(Declaration::Function(function))
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,
}))
} }
Reading::Atomic(Token::Keyword(pub_keyword)) Reading::Atomic(Token::Keyword(pub_keyword))
@ -263,47 +295,13 @@ impl<'a> Parser<'a> {
self.forward(); self.forward();
// parse the function keyword // 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 { Some(Declaration::Function(Function {
public_keyword: Some(pub_keyword), public_keyword: Some(pub_keyword),
annotations: Vec::new(), ..function
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
}
}
}
// parse annotations // parse annotations
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => { Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
@ -316,16 +314,70 @@ impl<'a> Parser<'a> {
annotations.push(annotation); annotations.push(annotation);
} }
// parse the function self.parse_declaration(handler).and_then(|declaration| {
self.parse_declaration(handler) if let Declaration::Function(mut function) = declaration {
.map(|declaration| match declaration {
Declaration::Function(mut function) => {
function.annotations.extend(annotations); function.annotations.extend(annotations);
Declaration::Function(function) 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 => { unexpected => {
// make progress // make progress
self.forward(); self.forward();
@ -339,4 +391,40 @@ impl<'a> Parser<'a> {
} }
} }
} }
pub fn parse_function(&mut self, handler: &impl Handler<Error>) -> Option<Function> {
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
}
}
} }

View File

@ -5,7 +5,7 @@ use getset::Getters;
use crate::{ use crate::{
base::{ base::{
source_file::{SourceElement, Span}, source_file::{SourceElement, Span},
Handler, DummyHandler, Handler,
}, },
lexical::{ lexical::{
token::{Punctuation, Token}, token::{Punctuation, Token},
@ -149,6 +149,45 @@ impl<'a> Parser<'a> {
close: delimited_tree.close, 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<T>(
&mut self,
seperator: char,
mut f: impl FnMut(&mut Self) -> Option<T>,
_handler: &impl Handler<Error>,
) -> Option<ConnectedList<T, Punctuation>> {
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<Element: SourceElement, Separator: SourceElement> SourceElement impl<Element: SourceElement, Separator: SourceElement> SourceElement

View File

@ -8,3 +8,5 @@ pub mod error;
pub mod lua; pub mod lua;
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
pub mod transpiler; pub mod transpiler;
mod util;

View File

@ -1,14 +1,14 @@
//! Transpiler for `ShulkerScript` //! Transpiler for `ShulkerScript`
use chksum_md5 as md5; 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 shulkerbox::datapack::{self, Command, Datapack, Execute};
use crate::{ use crate::{
base::{source_file::SourceElement, Handler}, base::{source_file::SourceElement, Handler},
syntax::syntax_tree::{ syntax::syntax_tree::{
declaration::Declaration, declaration::{Declaration, ImportItems},
expression::{Expression, FunctionCall, Primary}, expression::{Expression, FunctionCall, Primary},
program::{Namespace, ProgramFile}, program::{Namespace, ProgramFile},
statement::{ statement::{
@ -26,13 +26,15 @@ pub struct Transpiler {
datapack: shulkerbox::datapack::Datapack, datapack: shulkerbox::datapack::Datapack,
/// Key: (program identifier, function name) /// Key: (program identifier, function name)
functions: RwLock<HashMap<(String, String), FunctionData>>, functions: RwLock<HashMap<(String, String), FunctionData>>,
function_locations: RwLock<HashMap<(String, String), String>>, function_locations: RwLock<HashMap<(String, String), (String, bool)>>,
aliases: RwLock<HashMap<(String, String), (String, String)>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct FunctionData { struct FunctionData {
namespace: String, namespace: String,
statements: Vec<Statement>, statements: Vec<Statement>,
public: bool,
annotations: HashMap<String, Option<String>>, annotations: HashMap<String, Option<String>>,
} }
@ -44,6 +46,7 @@ impl Transpiler {
datapack: shulkerbox::datapack::Datapack::new(pack_format), datapack: shulkerbox::datapack::Datapack::new(pack_format),
functions: RwLock::new(HashMap::new()), functions: RwLock::new(HashMap::new()),
function_locations: RwLock::new(HashMap::new()), function_locations: RwLock::new(HashMap::new()),
aliases: RwLock::new(HashMap::new()),
} }
} }
@ -66,23 +69,7 @@ impl Transpiler {
Ident: AsRef<str>, Ident: AsRef<str>,
{ {
for (identifier, program) in programs { for (identifier, program) in programs {
self.transpile_program(program, identifier.as_ref(), handler)?; self.transpile_program_declarations(program, identifier.as_ref(), handler);
}
Ok(())
}
/// Transpiles the given program.
fn transpile_program(
&mut self,
program: &ProgramFile,
identifier: &str,
handler: &impl Handler<TranspileError>,
) -> TranspileResult<()> {
let namespace = program.namespace();
for declaration in program.declarations() {
self.transpile_declaration(declaration, namespace, identifier, handler);
} }
let mut always_transpile_functions = Vec::new(); let mut always_transpile_functions = Vec::new();
@ -107,6 +94,20 @@ impl Transpiler {
Ok(()) Ok(())
} }
/// Transpiles the given program.
fn transpile_program_declarations(
&mut self,
program: &ProgramFile,
identifier: &str,
handler: &impl Handler<TranspileError>,
) {
let namespace = program.namespace();
for declaration in program.declarations() {
self.transpile_declaration(declaration, namespace, identifier, handler);
}
}
/// Transpiles the given declaration. /// Transpiles the given declaration.
fn transpile_declaration( fn transpile_declaration(
&mut self, &mut self,
@ -136,10 +137,34 @@ impl Transpiler {
FunctionData { FunctionData {
namespace: namespace.namespace_name().str_content().to_string(), namespace: namespace.namespace_name().str_content().to_string(),
statements, statements,
public: function.is_public(),
annotations, 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,14 +178,32 @@ impl Transpiler {
handler: &impl Handler<TranspileError>, handler: &impl Handler<TranspileError>,
) -> TranspileResult<String> { ) -> TranspileResult<String> {
let program_query = (program_identifier.to_string(), name.to_string()); 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 already_transpiled = {
let locations = self.function_locations.read().unwrap(); 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 { if !already_transpiled {
let statements = { let statements = {
let functions = self.functions.read().unwrap(); let functions = self.functions.read().unwrap();
let function_data = functions.get(&program_query).ok_or_else(|| { 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()); let error = TranspileError::MissingFunctionDeclaration(name.to_string());
handler.receive(error.clone()); handler.receive(error.clone());
error error
@ -170,7 +213,14 @@ impl Transpiler {
let commands = self.transpile_function(&statements, program_identifier, handler)?; let commands = self.transpile_function(&statements, program_identifier, handler)?;
let functions = self.functions.read().unwrap(); let functions = self.functions.read().unwrap();
let function_data = functions.get(&program_query).ok_or_else(|| { 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()); let error = TranspileError::MissingFunctionDeclaration(name.to_string());
handler.receive(error.clone()); handler.receive(error.clone());
error error
@ -208,19 +258,20 @@ impl Transpiler {
self.function_locations.write().unwrap().insert( self.function_locations.write().unwrap().insert(
(program_identifier.to_string(), name.to_string()), (program_identifier.to_string(), name.to_string()),
function_location, (function_location, function_data.public),
); );
} }
let locations = self.function_locations.read().unwrap(); let locations = self.function_locations.read().unwrap();
locations locations
.get(&program_query) .get(&program_query)
.or_else(|| alias_query.and_then(|q| locations.get(&q).filter(|(_, p)| *p)))
.ok_or_else(|| { .ok_or_else(|| {
let error = TranspileError::MissingFunctionDeclaration(name.to_string()); let error = TranspileError::MissingFunctionDeclaration(name.to_string());
handler.receive(error.clone()); handler.receive(error.clone());
error error
}) })
.map(String::to_owned) .map(|(s, _)| s.to_owned())
} }
fn transpile_function( fn transpile_function(

34
src/transpile/util.rs Normal file
View File

@ -0,0 +1,34 @@
fn normalize_program_identifier<S>(identifier: S) -> String
where
S: AsRef<str>,
{
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<S, T>(current_identifier: S, import_path: T) -> String
where
S: AsRef<str>,
T: AsRef<str>,
{
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::<Vec<_>>();
identifier_elements.pop();
normalize_program_identifier(identifier_elements.join("/") + "/" + import_path.as_ref())
}
}