implement from-import statement
This commit is contained in:
parent
deddf1d77e
commit
899a973315
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,47 +295,13 @@ impl<'a> Parser<'a> {
|
|||
self.forward();
|
||||
|
||||
// parse the function keyword
|
||||
let function_keyword_reading = self.next_significant_token();
|
||||
|
||||
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)?;
|
||||
let function = self.parse_function(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,
|
||||
..function
|
||||
}))
|
||||
}
|
||||
unexpected => {
|
||||
handler.receive(Error::UnexpectedSyntax(UnexpectedSyntax {
|
||||
expected: SyntaxKind::Keyword(KeywordKind::Function),
|
||||
found: unexpected.into_token(),
|
||||
}));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse annotations
|
||||
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {
|
||||
|
@ -316,16 +314,70 @@ impl<'a> Parser<'a> {
|
|||
annotations.push(annotation);
|
||||
}
|
||||
|
||||
// parse the function
|
||||
self.parse_declaration(handler)
|
||||
.map(|declaration| match declaration {
|
||||
Declaration::Function(mut function) => {
|
||||
self.parse_declaration(handler).and_then(|declaration| {
|
||||
if let Declaration::Function(mut function) = declaration {
|
||||
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 => {
|
||||
// make progress
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,3 +8,5 @@ pub mod error;
|
|||
pub mod lua;
|
||||
#[cfg(feature = "shulkerbox")]
|
||||
pub mod transpiler;
|
||||
|
||||
mod util;
|
||||
|
|
|
@ -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,14 +178,32 @@ 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 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
|
||||
|
@ -170,7 +213,14 @@ impl Transpiler {
|
|||
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 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
|
||||
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue