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

View File

@ -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<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> {
pub fn parse_annotation(&mut self, handler: &impl Handler<Error>) -> Option<Annotation> {
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<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::{
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<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

View File

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

View File

@ -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<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)]
struct FunctionData {
namespace: String,
statements: Vec<Statement>,
public: bool,
annotations: HashMap<String, Option<String>>,
}
@ -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<str>,
{
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<TranspileError>,
) -> 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<TranspileError>,
) {
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<TranspileError>,
) -> TranspileResult<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 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(

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())
}
}