Add Lua support to transpiler
This commit is contained in:
parent
9f8b31e2aa
commit
01040964af
|
@ -6,9 +6,10 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["shulkerbox"]
|
||||
default = ["lua", "shulkerbox"]
|
||||
shulkerbox = ["dep:shulkerbox"]
|
||||
serde = ["dep:serde"]
|
||||
lua = ["dep:mlua"]
|
||||
|
||||
[dependencies]
|
||||
chksum-md5 = "0.0.0"
|
||||
|
@ -16,6 +17,7 @@ colored = "2.1.0"
|
|||
derive_more = { version = "0.99.17", default-features = false, features = ["deref", "from", "deref_mut"] }
|
||||
enum-as-inner = "0.6.0"
|
||||
getset = "0.1.2"
|
||||
mlua = { version = "0.9.7", features = ["luau"], optional = true }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"], optional = true }
|
||||
shulkerbox = { path = "../shulkerbox", optional = true}
|
||||
strum = { version = "0.26.2", features = ["derive"] }
|
||||
|
|
62
grammar.md
62
grammar.md
|
@ -59,36 +59,77 @@ Conditional:
|
|||
;
|
||||
```
|
||||
|
||||
### ParenthizedCondition
|
||||
### Condition
|
||||
```ebnf
|
||||
Condition:
|
||||
PrimaryCondition
|
||||
BinaryCondition
|
||||
;
|
||||
```
|
||||
|
||||
#### PrimaryCondition
|
||||
```ebnf
|
||||
PrimaryCondition:
|
||||
ConditionalPrefix
|
||||
| ParenthesizedCondition
|
||||
| StringLiteral
|
||||
;
|
||||
```
|
||||
|
||||
#### ConditionalPrefix
|
||||
```ebnf
|
||||
ConditionalPrefix:
|
||||
ConditionalPrefixOperator PrimaryCondition
|
||||
;
|
||||
```
|
||||
|
||||
#### ConditionalPrefixOperator
|
||||
``` ebnf
|
||||
ConditionalPrefixOperator: '!';
|
||||
```
|
||||
|
||||
#### BinaryCondition
|
||||
``` ebnf
|
||||
BinaryCondition:
|
||||
Condition ConditionalBinaryOperator Condition
|
||||
;
|
||||
```
|
||||
|
||||
#### ConditionalBinaryOperator
|
||||
``` ebnf
|
||||
ConditionalBinaryOperator:
|
||||
'&&'
|
||||
| '||'
|
||||
;
|
||||
```
|
||||
|
||||
#### ParenthizedCondition
|
||||
```ebnf
|
||||
ParenthizedCondition:
|
||||
'(' Condition ')'
|
||||
;
|
||||
```
|
||||
|
||||
### Condition
|
||||
```ebnf
|
||||
Condition:
|
||||
StringLiteral
|
||||
```
|
||||
|
||||
### Grouping
|
||||
``` ebnf
|
||||
Grouping:
|
||||
'group' Block
|
||||
;
|
||||
;
|
||||
```
|
||||
|
||||
### Expression
|
||||
```ebnf
|
||||
Expression:
|
||||
Primary
|
||||
;
|
||||
```
|
||||
|
||||
### Primary
|
||||
``` ebnf
|
||||
Primary:
|
||||
FunctionCall
|
||||
;
|
||||
```
|
||||
|
||||
### FunctionCall
|
||||
|
@ -97,3 +138,10 @@ FunctionCall:
|
|||
Identifier '(' (Expression (',' Expression)*)? ')'
|
||||
;
|
||||
```
|
||||
|
||||
### LuaCode
|
||||
```ebnf
|
||||
LuaCode:
|
||||
'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}'
|
||||
;
|
||||
```
|
|
@ -51,6 +51,12 @@ impl SourceFile {
|
|||
&self.content
|
||||
}
|
||||
|
||||
/// Get the path of the source file.
|
||||
#[must_use]
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Get the line of the source file at the given line number.
|
||||
///
|
||||
/// Numbering starts at 1.
|
||||
|
@ -95,8 +101,6 @@ impl SourceFile {
|
|||
#[must_use]
|
||||
pub fn get_location(&self, byte_index: usize) -> Option<Location> {
|
||||
if self.content.is_char_boundary(byte_index) {
|
||||
None
|
||||
} else {
|
||||
// get the line number by binary searching the line ranges
|
||||
let line = self
|
||||
.lines
|
||||
|
@ -125,6 +129,8 @@ impl SourceFile {
|
|||
line: line + 1,
|
||||
column,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ pub enum KeywordKind {
|
|||
Else,
|
||||
Group,
|
||||
Run,
|
||||
Lua,
|
||||
}
|
||||
|
||||
impl ToString for KeywordKind {
|
||||
|
@ -66,6 +67,7 @@ impl KeywordKind {
|
|||
Self::Else => "else",
|
||||
Self::Group => "group",
|
||||
Self::Run => "run",
|
||||
Self::Lua => "lua",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use derive_more::{Deref, From};
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
||||
use crate::base::{source_file::SourceFile, Handler};
|
||||
use crate::base::{
|
||||
source_file::{SourceElement, SourceFile, Span},
|
||||
Handler,
|
||||
};
|
||||
|
||||
use super::{
|
||||
error::{self, UndelimitedDelimiter},
|
||||
|
@ -164,13 +168,26 @@ impl TokenStream {
|
|||
|
||||
/// Is an enumeration of either a [`Token`] or a [`Delimited`].
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, EnumAsInner)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum TokenTree {
|
||||
Token(Token),
|
||||
Delimited(Delimited),
|
||||
}
|
||||
|
||||
impl SourceElement for TokenTree {
|
||||
fn span(&self) -> Span {
|
||||
match self {
|
||||
Self::Token(token) => token.span().to_owned(),
|
||||
Self::Delimited(delimited) => delimited
|
||||
.open
|
||||
.span()
|
||||
.join(&delimited.close.span)
|
||||
.expect("Invalid delimited span"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Is an enumeration of the different types of delimiters in the [`Delimited`].
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
|
|
@ -247,6 +247,12 @@ impl<'a> Frame<'a> {
|
|||
self.get_reading(self.token_provider.token_stream().get(self.current_index))
|
||||
}
|
||||
|
||||
/// Returns a raw [`Token`] pointing by the `current_index` of the [`Frame`].
|
||||
#[must_use]
|
||||
pub fn peek_raw(&self) -> Option<&TokenTree> {
|
||||
self.token_provider.token_stream().get(self.current_index)
|
||||
}
|
||||
|
||||
/// Returns the next significant [`Token`] after the `current_index` of the [`Frame`].
|
||||
#[must_use]
|
||||
pub fn peek_significant(&self) -> Reading {
|
||||
|
|
|
@ -23,9 +23,9 @@ use crate::{
|
|||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ``` ebnf
|
||||
/// Expression:
|
||||
/// Prefix
|
||||
/// | Parenthesized
|
||||
/// PrimaryCondition:
|
||||
/// ConditionalPrefix
|
||||
/// | ParenthesizedCondition
|
||||
/// | StringLiteral
|
||||
/// ```
|
||||
#[allow(missing_docs)]
|
||||
|
@ -88,7 +88,7 @@ impl BinaryCondition {
|
|||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ``` ebnf
|
||||
/// BinaryOperator:
|
||||
/// ConditionalBinaryOperator:
|
||||
/// '&&'
|
||||
/// | '||'
|
||||
/// ;
|
||||
|
@ -165,7 +165,7 @@ impl SourceElement for ParenthesizedCondition {
|
|||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ``` ebnf
|
||||
/// PrefixOperator: '!';
|
||||
/// ConditionalPrefixOperator: '!';
|
||||
/// ```
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -185,8 +185,8 @@ impl SourceElement for ConditionalPrefixOperator {
|
|||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ```ebnf
|
||||
/// Prefix:
|
||||
/// ConditionalPrefixOperator StringLiteral
|
||||
/// ConditionalPrefix:
|
||||
/// ConditionalPrefixOperator PrimaryCondition
|
||||
/// ;
|
||||
/// ```
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -216,7 +216,10 @@ impl ConditionalPrefix {
|
|||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ``` ebnf
|
||||
/// Condition: PrimaryCondition;
|
||||
/// Condition:
|
||||
/// PrimaryCondition
|
||||
/// | BinaryCondition
|
||||
/// ;
|
||||
/// ```
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
Handler,
|
||||
},
|
||||
lexical::{
|
||||
token::{Identifier, Punctuation, StringLiteral, Token},
|
||||
token::{Identifier, Keyword, KeywordKind, Punctuation, StringLiteral, Token},
|
||||
token_stream::Delimiter,
|
||||
},
|
||||
syntax::{
|
||||
|
@ -47,12 +47,13 @@ impl SourceElement for Expression {
|
|||
/// Primary:
|
||||
/// FunctionCall
|
||||
/// ```
|
||||
#[allow(missing_docs)]
|
||||
#[allow(missing_docs, clippy::large_enum_variant)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)]
|
||||
pub enum Primary {
|
||||
FunctionCall(FunctionCall),
|
||||
StringLiteral(StringLiteral),
|
||||
Lua(LuaCode),
|
||||
}
|
||||
|
||||
impl SourceElement for Primary {
|
||||
|
@ -60,6 +61,7 @@ impl SourceElement for Primary {
|
|||
match self {
|
||||
Self::FunctionCall(function_call) => function_call.span(),
|
||||
Self::StringLiteral(string_literal) => string_literal.span(),
|
||||
Self::Lua(lua_code) => lua_code.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +99,60 @@ impl SourceElement for FunctionCall {
|
|||
}
|
||||
}
|
||||
|
||||
/// Syntax Synopsis:
|
||||
///
|
||||
/// ```ebnf
|
||||
/// LuaCode:
|
||||
/// 'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}'
|
||||
/// ```
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
||||
pub struct LuaCode {
|
||||
/// The `lua` keyword.
|
||||
#[get = "pub"]
|
||||
lua_keyword: Keyword,
|
||||
/// The left parenthesis of the lua code.
|
||||
#[get = "pub"]
|
||||
left_parenthesis: Punctuation,
|
||||
/// The arguments of the lua code.
|
||||
#[get = "pub"]
|
||||
variables: Option<ConnectedList<Identifier, Punctuation>>,
|
||||
/// The right parenthesis of the lua code.
|
||||
#[get = "pub"]
|
||||
right_parenthesis: Punctuation,
|
||||
/// The left brace of the lua code.
|
||||
#[get = "pub"]
|
||||
left_brace: Punctuation,
|
||||
/// The lua code contents.
|
||||
#[get = "pub"]
|
||||
code: String,
|
||||
/// The right brace of the lua code.
|
||||
#[get = "pub"]
|
||||
right_brace: Punctuation,
|
||||
}
|
||||
|
||||
impl SourceElement for LuaCode {
|
||||
fn span(&self) -> Span {
|
||||
self.lua_keyword
|
||||
.span()
|
||||
.join(&self.right_brace.span)
|
||||
.expect("Invalid lua code span")
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaCode {
|
||||
/// Dissolves the [`LuaCode`] into its components.
|
||||
#[must_use]
|
||||
pub fn dissolve(self) -> (Keyword, Punctuation, String, Punctuation) {
|
||||
(
|
||||
self.lua_keyword,
|
||||
self.left_brace,
|
||||
self.code,
|
||||
self.right_brace,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
/// Parses an [`Expression`]
|
||||
pub fn parse_expression(&mut self, handler: &impl Handler<Error>) -> Option<Expression> {
|
||||
|
@ -141,6 +197,65 @@ impl<'a> Parser<'a> {
|
|||
Some(Primary::StringLiteral(literal))
|
||||
}
|
||||
|
||||
// lua code expression
|
||||
Reading::Atomic(Token::Keyword(lua_keyword))
|
||||
if lua_keyword.keyword == KeywordKind::Lua =>
|
||||
{
|
||||
// eat the lua keyword
|
||||
self.forward();
|
||||
|
||||
// parse the variable list
|
||||
let variables = self.parse_enclosed_list(
|
||||
Delimiter::Parenthesis,
|
||||
',',
|
||||
|parser| match parser.next_significant_token() {
|
||||
Reading::Atomic(Token::Identifier(identifier)) => {
|
||||
parser.forward();
|
||||
Some(identifier)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
handler,
|
||||
)?;
|
||||
|
||||
self.stop_at_significant();
|
||||
|
||||
let tree = self.step_into(
|
||||
Delimiter::Brace,
|
||||
|parser| {
|
||||
let first = parser.next_token();
|
||||
let mut last = parser.next_token();
|
||||
|
||||
while !parser.is_end() {
|
||||
last = parser.next_token();
|
||||
}
|
||||
|
||||
let combined = first
|
||||
.into_token()
|
||||
.and_then(|first| {
|
||||
first.span().join(&last.into_token().map_or_else(
|
||||
|| first.span().to_owned(),
|
||||
|last| last.span().to_owned(),
|
||||
))
|
||||
})
|
||||
.expect("Invalid lua code span");
|
||||
|
||||
Some(combined.str().trim().to_owned())
|
||||
},
|
||||
handler,
|
||||
)?;
|
||||
|
||||
Some(Primary::Lua(LuaCode {
|
||||
lua_keyword,
|
||||
left_parenthesis: variables.open,
|
||||
variables: variables.list,
|
||||
right_parenthesis: variables.close,
|
||||
left_brace: tree.open,
|
||||
code: tree.tree?,
|
||||
right_brace: tree.close,
|
||||
}))
|
||||
}
|
||||
|
||||
unexpected => {
|
||||
// make progress
|
||||
self.forward();
|
||||
|
|
|
@ -10,6 +10,10 @@ pub enum TranspileError {
|
|||
MissingFunctionDeclaration(String),
|
||||
#[error("Unexpected expression: {}", .0.span().str())]
|
||||
UnexpectedExpression(Expression),
|
||||
#[error("Lua code evaluation is disabled.")]
|
||||
LuaDisabled,
|
||||
#[error("Lua runtime error: {}", .0)]
|
||||
LuaRuntimeError(String),
|
||||
}
|
||||
|
||||
/// The result of a transpilation operation.
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
//! Executes the Lua code and returns the resulting command.
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
mod enabled {
|
||||
use mlua::Lua;
|
||||
|
||||
use crate::{
|
||||
base::{source_file::SourceElement, Handler},
|
||||
syntax::syntax_tree::expression::LuaCode,
|
||||
transpile::error::{TranspileError, TranspileResult},
|
||||
};
|
||||
|
||||
impl LuaCode {
|
||||
/// Evaluates the Lua code and returns the resulting command.
|
||||
///
|
||||
/// # Errors
|
||||
/// - If Lua code evaluation is disabled.
|
||||
pub fn eval_string(
|
||||
&self,
|
||||
handler: &impl Handler<TranspileError>,
|
||||
) -> TranspileResult<String> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let name = {
|
||||
let span = self.span();
|
||||
let file = span.source_file();
|
||||
let path = file.path();
|
||||
|
||||
let start = span.start_location();
|
||||
let end = span.end_location().unwrap_or_else(|| {
|
||||
let content_size = file.content().len();
|
||||
file.get_location(content_size - 1)
|
||||
.expect("Failed to get location")
|
||||
});
|
||||
|
||||
format!(
|
||||
"{}:{}:{}-{}:{}",
|
||||
path.display(),
|
||||
start.line,
|
||||
start.column,
|
||||
end.line,
|
||||
end.column
|
||||
)
|
||||
};
|
||||
|
||||
let lua_result = lua
|
||||
.load(self.code())
|
||||
.set_name(name)
|
||||
.eval::<String>()
|
||||
.map_err(|err| {
|
||||
let err = TranspileError::from(err);
|
||||
handler.receive(err.clone());
|
||||
err
|
||||
})?;
|
||||
|
||||
Ok(lua_result)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mlua::Error> for TranspileError {
|
||||
fn from(value: mlua::Error) -> Self {
|
||||
let string = value.to_string();
|
||||
Self::LuaRuntimeError(
|
||||
string
|
||||
.strip_prefix("runtime error: ")
|
||||
.unwrap_or(&string)
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "lua"))]
|
||||
mod disabled {
|
||||
use crate::{
|
||||
base::Handler,
|
||||
syntax::syntax_tree::expression::LuaCode,
|
||||
transpile::error::{TranspileError, TranspileResult},
|
||||
};
|
||||
|
||||
impl LuaCode {
|
||||
/// Will always return an error because Lua code evaluation is disabled.
|
||||
/// Enable the feature `lua` to enable Lua code evaluation.
|
||||
///
|
||||
/// # Errors
|
||||
/// - If Lua code evaluation is disabled.
|
||||
pub fn eval_string(
|
||||
&self,
|
||||
handler: &impl Handler<TranspileError>,
|
||||
) -> TranspileResult<String> {
|
||||
handler.receive(TranspileError::LuaDisabled);
|
||||
Err(TranspileError::LuaDisabled)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,5 +4,7 @@
|
|||
#[cfg(feature = "shulkerbox")]
|
||||
pub mod conversions;
|
||||
pub mod error;
|
||||
#[doc(hidden)]
|
||||
pub mod lua;
|
||||
#[cfg(feature = "shulkerbox")]
|
||||
pub mod transpiler;
|
||||
|
|
|
@ -233,6 +233,9 @@ impl Transpiler {
|
|||
Expression::Primary(Primary::StringLiteral(string)) => {
|
||||
Ok(Some(Command::Raw(string.str_content().to_string())))
|
||||
}
|
||||
Expression::Primary(Primary::Lua(code)) => {
|
||||
Ok(Some(Command::Raw(code.eval_string(handler)?)))
|
||||
}
|
||||
},
|
||||
Statement::Block(_) => {
|
||||
unreachable!("Only literal commands are allowed in functions at this time.")
|
||||
|
|
Loading…
Reference in New Issue