diff --git a/.gitignore b/.gitignore index b83d222..f471b72 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target/ +/src/assets/*.lua diff --git a/examples/basic.saturn b/examples/basic.saturn new file mode 100644 index 0000000..11274af --- /dev/null +++ b/examples/basic.saturn @@ -0,0 +1,47 @@ +// Basic examples + +// This is a comment +/* + this also is. +*/ + +// Declare variables +let some_var = "foo"; +let some_char = 'a'; // This is a number actually. +some_char += 1; +print(some_var, some_char); + +// Mutate them +some_var = 5; +some_var += 1; +print(some_var); + +// Declare functions +fn the_foo() { + return "bar"; +} + +// Use them +let answer = the_foo(); +print(answer); + +// Conditions +if true { + print("Of course"); +} else { + print("Never"); +} + +// Do-block +{ + let this_is = "isolated from the rest"; + print("of_the_code"); +} + +// Can be used as an expression too! +let my_var = "Foo"; +let result = { + let my_var = "another one"; + return my_var ++ "."; +}; +print(result, my_var); diff --git a/examples/collections.saturn b/examples/collections.saturn new file mode 100644 index 0000000..1ba7276 --- /dev/null +++ b/examples/collections.saturn @@ -0,0 +1,9 @@ +require("../src/assets/std"); + +let x = [1, 2, 3]; +let y = [4, 5, 6]; +let z = x ::: y; + +for (k, v) in entries(z) { + print(k ++ " => " ++ v); +} diff --git a/examples/extra_op.saturn b/examples/extra_op.saturn index 49764fe..448d1cb 100644 --- a/examples/extra_op.saturn +++ b/examples/extra_op.saturn @@ -1,12 +1,21 @@ -// Example usage of extra operators: +let { operator, entries } = require("../src/assets/std"); -fn __pipe_right(left, right) { +@operator("|>") +fn pipe_right(left, right) { return right(left); } +@operator("<|") +fn pipe_left(left, right) { + return left(right); +} + +// Example usage of extra operators: let r = 101 |> (a => a + 10) |> (a => a * 2) |> tostring; -print("Result = " ++ r ++ " (" ++ type(r) ++ ")"); +let kind = type <| r |> a => "(" ++ a ++ ")"; + +print("Result = " ++ r ++ " " ++ kind); diff --git a/examples/function_decorators.saturn b/examples/function_decorators.saturn index 9524284..5a28928 100644 --- a/examples/function_decorators.saturn +++ b/examples/function_decorators.saturn @@ -1,12 +1,21 @@ -fn Test() { +fn test() { return (target, name) => { print("Adding " ++ name ++ " to the test suite!"); }; } -@Test() +@test() fn my_func() { print("Some func"); } + +fn foo() { + return (_, n) => print("Adding foo to", n); +} +fn bar() { + return (_, n) => print("Adding bar to", n); +} + +@foo() @bar() fn testing() {} diff --git a/examples/loops.saturn b/examples/loops.saturn new file mode 100644 index 0000000..bd1f12e --- /dev/null +++ b/examples/loops.saturn @@ -0,0 +1,17 @@ +let { entries } = require("../src/assets/std"); + +let tbl = { + a: 1, + b: true, + c: "x" +}; + +// For-each +for (k, v) in entries(tbl) { + print(k, v); +} + +// Range based for +for i in 1..4 { + print(i); +} diff --git a/examples/oop.saturn b/examples/oop.saturn new file mode 100644 index 0000000..2dcd390 --- /dev/null +++ b/examples/oop.saturn @@ -0,0 +1,68 @@ +// Example of OOP possible uses with std +let { abstract, entries, mixin, trait, impl } = require("../src/assets/std"); + +// Simple abstract class example. +class SimpleAbstract { + @abstract() fn example(self) {} + fn inherited(self) { + print("This was inherited " ++ self.name); + } +} + +@mixin(SimpleAbstract) +class Simple { + fn example(self) { + print("Won't blow."); + } + fn run() { + let example = Simple { name: "Simple example" }; + example.example(); + example.inherited(); + } +} +Simple::run(); + +// Trait example +@trait() +class TraitLike { + fn foo(self) {} +} + +@impl(TraitLike) +class Implementor { + fn me(self) { + return "Implementor"; + } + fn foo(self) { + print("The foo be like: " ++ self.me()); + } +} + +// Mixin examples +@mixin(Implementor) +class Child { + fn me(self) { + return "The child"; + } +} + +class BeMe { + fn bar(self) { + return "Amogus"; + } +} + +@mixin(BeMe) +@mixin(Child) +class MonsterJack { + fn me(self) { + return "Sus town? " ++ self.bar(); + } +} + +let child = Child {}; +let imp = Implementor {}; +let monster = MonsterJack {}; +imp.foo(); +child.foo(); +monster.foo(); diff --git a/readme.md b/readme.md index 45eccf0..682b07a 100644 --- a/readme.md +++ b/readme.md @@ -314,6 +314,22 @@ print(consumer.consume(foo)); print(consumer.consume(bar)); ``` +Also you can decorate the code: +```rs +@bar() +class Foo { + @foo() + fn func(self) { + return "me"; + } +} + +@test() +fn suite() { + print("Yay!"); +} +``` + ## Why replace Lua? I like many aspects of Lua, specially how fast and lightweight the VM is. But @@ -323,11 +339,13 @@ make the scripts less verbose and more easy to write. Aside of the [Language Basics](#language-basics) section, there are other key aspects of the language: -- Decorators! -- A built-in prelude library for runtime type checks. -- ~~Nice string interpolation.~~ (Maybe not?) -- Terser loops. -- Built-in operator overloading. -- Custom operators. +- Decorators +- Classes +- A built-in prelude library for runtime type checks and other things. +- Char expansion +- Macros +- Cooler lambdas. +- Terser loops +- Custom operators - Some [RTTI](https://en.wikipedia.org/wiki/Run-time_type_information) (Which - enables reflection). + enables reflection) diff --git a/src/assets/std.saturn b/src/assets/std.saturn new file mode 100644 index 0000000..4b4f5c1 --- /dev/null +++ b/src/assets/std.saturn @@ -0,0 +1,221 @@ +let expand = table.unpack ?? unpack; + +// Collections and iterators + +fn @native entries(tbl) { + @Lua" + local iter = pairs(tbl); + return function(_, next) + local k, v = iter(tbl, next and next._0); + if k and v then + return { _0 = k, _1 = v }, v; + end + end; + "; +} + +// Runtime + +fn @native panic(message) { + @Lua" + error(message); + "; +} + +// OOP and inheritance + +let makeTrait = (name) => () => panic("Trait " ++ name ++ " is not constructible!"); + +fn trait() { + return (target, name) => { + target.__meta__.__call = makeTrait(name); + }; +} + +fn impl(trait) { + return (target, name) => { + let map = {}; + for (k, v) in entries(target.prototype) { + map[k] = v; + } + for (k, v) in entries(trait.prototype) { + if map[k] == () { + panic(name ++ " must implement trait method " ++ k ++ "!"); + } + } + }; +} + +fn mixin(parent) { + let index = parent.prototype.__meta__.__index; + if type(index) == "table" { + index = (self, key) => parent.prototype.__meta__.__index[key]; + } + return (target, name) => { + let prev = target.prototype.__meta__.__index; + if type(prev) == "table" { + let prev_tbl = prev; + prev = (self, key) => prev_tbl[key]; + } + target.prototype.__meta__.__index = (self, key) => { + return prev(self, key) or index(self, key); + }; + }; +} + +let makePure = (name) => () => panic("Attempting to call abstract method " ++ name ++ "!"); + +fn abstract() { + return (_, name, host, label, meta) => { + if meta.is_static { + host[name] = makePure(label ++ "::" ++ name); + } else { + host.prototype[name] = makePure(label ++ "." ++ name); + } + }; +} + +// Operators & meta + +let symbols = { + "+": "plus", + "-": "minus", + "*": "times", + "/": "slash", + ".": "dot", + "|": "pipe", + ">": "greater", + "<": "less", + "=": "equals", + "?": "interrogation", + "!": "exclamation", + "~": "tilde", + "%": "percent", + "&": "ampersand", + "#": "bang", + "$": "dollar", + "^": "power", + ":": "colon" +}; + +fn @native operator(symbol) { + @Lua" + local name = '_'; + for i=1, symbol:len() do + local s = symbol:sub(i, i); + local key = symbols[s]; + if key == nil then + error('You cannot declare an operator that contains the symbol ' .. tostring(s) .. '!'); + end + name = name .. '_' .. key; + end + return function(target) + _G[name] = target; + end; + "; +} + +// STD Custom operators + +@operator("|>") +fn pipe_right(left, right) { + return right(left); +} + +@operator("<|") +fn pipe_left(left, right) { + return left(right); +} + +@operator(":::") +fn @native merge(left, right) { + @Lua" + local u = table.unpack or unpack; + local tbl = { u(left) }; + for _, v in pairs(right) do + tbl[#tbl + 1] = v; + end + return tbl; + "; +} + +@operator("+?") +fn maybe_sum(left, right) { + return left and (left + right); +} +@operator("+??") +fn maybe_sum_or(left, right) { + return left and (left + right) or right; +} + +@operator("++?") +fn maybe_concat(left, right) { + return left and (left ++ right); +} +@operator("++??") +fn maybe_concat_or(left, right) { + return left and (left ++ right) or right; +} + +@operator("-?") +fn maybe_subtract(left, right) { + return left and (left - right); +} +@operator("-??") +fn maybe_subtract_or(left, right) { + return left and (left - right) or right; +} + +@operator("*?") +fn maybe_times(left, right) { + return left and (left * right); +} +@operator("*??") +fn maybe_times_or(left, right) { + return left and (left * right) or right; +} + +@operator("/?") +fn maybe_divide(left, right) { + return left and (left / right); +} +@operator("/??") +fn maybe_divide_or(left, right) { + return left and (left / right) or right; +} + +@operator("..") +fn range(start, _end) { + let i = start; + if start < _end { + return () => { + if i > _end { + return (); + } + let c = i; + i += 1; + return c; + }; + } else { + return () => { + if i < _end { + return (); + } + let c = i; + i -= 1; + return c; + }; + } +} + +return { + expand, + entries, + operator, + range, + abstract, + trait, + impl, + mixin, + panic +}; diff --git a/src/code/builder.rs b/src/code/builder.rs new file mode 100644 index 0000000..da5a735 --- /dev/null +++ b/src/code/builder.rs @@ -0,0 +1,102 @@ +pub struct UnevenIndentationError; +impl std::fmt::Debug for UnevenIndentationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Uneven Indentation: Attempting to pop furhter than 0!") + .finish() + } +} + +/// # String code builder +/// +/// This simple immutable builder accounts for raw strings (So it is agnostic +/// from the targetted output), but retains indentation aspects. +/// +/// This means that you have an indentation stack, with it's state retained +/// between calls, without having to store it in your code emitter. +/// +/// Each call, consumes the builder and returns an extended version of it. +/// If you want to preserve the state, clone the structure by calling `.clone()` +/// explicitly. +/// +/// Example: +/// ```rs +/// let out = Builder::new(" ") +/// .put("hello") +/// .push().line() +/// .put("my") +/// .pop().unwrap().line() +/// .put("world!") +/// .collect() +/// ``` +/// Yields: +/// ``` +/// hello +/// my +/// world +/// ``` +#[derive(Clone)] +pub struct Builder { + level: u16, + indent: String, + buffer: String, +} +impl Builder { + pub fn new(indent: T) -> Self + where + T: Into, + { + Builder { + level: 0, + indent: indent.into(), + buffer: Default::default(), + } + } + pub fn collect(self) -> String { + self.buffer + } + pub fn push(self) -> Self { + Builder { + level: self.level + 1, + ..self + } + } + pub fn pop(self) -> Result { + if self.level == 0 { + Err(UnevenIndentationError) + } else { + Ok(Builder { + level: self.level - 1, + ..self + }) + } + } + pub fn put(self, fragment: T) -> Self + where + T: Into, + { + Builder { + buffer: format!("{}{}", self.buffer, fragment.into()), + ..self + } + } + pub fn and(self, other: Self) -> Self { + Builder { + buffer: format!("{}{}", self.buffer, other.buffer), + ..self + } + } + pub fn line(self) -> Self { + Builder { + buffer: format!("{}\n{}", self.buffer, self.indent.repeat(self.level.into())), + ..self + } + } + /// Creates a new copy of this builder, but with empty buffer. + pub fn clone_like(&self) -> Self { + Builder { + level: self.level, + indent: self.indent.clone(), + buffer: "".to_string(), + } + } +} diff --git a/src/code/generator.rs b/src/code/generator.rs new file mode 100644 index 0000000..99489af --- /dev/null +++ b/src/code/generator.rs @@ -0,0 +1,6 @@ +/// TODO +pub trait Generator { + fn gen() { + todo!("Not ready yet.") + } +} diff --git a/src/code/macros.rs b/src/code/macros.rs new file mode 100644 index 0000000..ee46e20 --- /dev/null +++ b/src/code/macros.rs @@ -0,0 +1,9 @@ +use crate::parser::ast; + +struct MacroExpander; + +impl MacroExpander { + fn expand(call: &ast::CallExpression) -> ast::ScriptOrExpression { + todo!() + } +} diff --git a/src/code/mod.rs b/src/code/mod.rs new file mode 100644 index 0000000..cce9646 --- /dev/null +++ b/src/code/mod.rs @@ -0,0 +1,10 @@ +mod builder; +mod generator; +pub mod macros; +mod visitor; + +pub use builder::Builder; +pub use generator::Generator; +pub use visitor::{VisitError, Visitor}; + +pub trait BuilderVisitor: Visitor {} diff --git a/src/code.rs b/src/code/visitor.rs similarity index 62% rename from src/code.rs rename to src/code/visitor.rs index 0a2de27..21e3d8b 100644 --- a/src/code.rs +++ b/src/code/visitor.rs @@ -31,6 +31,7 @@ pub trait Visitor { fn visit_match(&self, ctx: T, expr: &Match) -> Result; fn visit_1tuple(&self, ctx: T, expr: &Expression) -> Result; fn visit_identifier(&self, ctx: T, expr: &Identifier) -> Result; + fn visit_do(&self, ctx: T, expr: &Do) -> Result; fn enter_script(&self, ctx: T, _script: &Script) -> Result { Ok(ctx) } @@ -54,6 +55,7 @@ pub trait Visitor { Expression::Vector(e) => self.visit_vector(ctx, e), Expression::Tuple1(e) => self.visit_1tuple(ctx, e), Expression::Identifier(e) => self.visit_identifier(ctx, e), + Expression::Do(e) => self.visit_do(ctx, e), } } fn visit_script(&self, ctx: T, script: &Script) -> Result { @@ -77,106 +79,3 @@ pub trait Visitor { self.exit_script(ctx, script) } } - -pub struct UnevenIndentationError; -impl std::fmt::Debug for UnevenIndentationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Uneven Indentation: Attempting to pop furhter than 0!") - .finish() - } -} - -/// # String code builder -/// -/// This simple immutable builder accounts for raw strings (So it is agnostic -/// from the targetted output), but retains indentation aspects. -/// -/// This means that you have an indentation stack, with it's state retained -/// between calls, without having to store it in your code emitter. -/// -/// Each call, consumes the builder and returns an extended version of it. -/// If you want to preserve the state, clone the structure by calling `.clone()` -/// explicitly. -/// -/// Example: -/// ```rs -/// let out = Builder::new(" ") -/// .put("hello") -/// .push().line() -/// .put("my") -/// .pop().unwrap().line() -/// .put("world!") -/// .collect() -/// ``` -/// Yields: -/// ``` -/// hello -/// my -/// world -/// ``` -#[derive(Clone)] -pub struct Builder { - level: u16, - indent: String, - buffer: String, -} -impl Builder { - pub fn new(indent: T) -> Self - where - T: Into, - { - Builder { - level: 0, - indent: indent.into(), - buffer: Default::default(), - } - } - pub fn collect(self) -> String { - self.buffer - } - pub fn push(self) -> Self { - Builder { - level: self.level + 1, - ..self - } - } - pub fn pop(self) -> Result { - if self.level == 0 { - Err(UnevenIndentationError) - } else { - Ok(Builder { - level: self.level - 1, - ..self - }) - } - } - pub fn put(self, fragment: T) -> Self - where - T: Into, - { - Builder { - buffer: format!("{}{}", self.buffer, fragment.into()), - ..self - } - } - pub fn and(self, other: Self) -> Self { - Builder { - buffer: format!("{}{}", self.buffer, other.buffer), - ..self - } - } - pub fn line(self) -> Self { - Builder { - buffer: format!("{}\n{}", self.buffer, self.indent.repeat(self.level.into())), - ..self - } - } - /// Creates a new copy of this builder, but with empty buffer. - pub fn clone_like(&self) -> Self { - Builder { - level: self.level, - indent: self.indent.clone(), - buffer: "".to_string(), - } - } -} diff --git a/src/lua.rs b/src/lua/mod.rs similarity index 75% rename from src/lua.rs rename to src/lua/mod.rs index 7c7069d..d21e3b9 100644 --- a/src/lua.rs +++ b/src/lua/mod.rs @@ -3,6 +3,47 @@ use crate::{ parser::ast::{self}, }; +fn escape_string(str: String) -> String { + return str.replace("\n", "\\n"); +} + +fn translate_operator(ctx: code::Builder, op: String) -> code::Builder { + let ctx = ctx.put("__"); + let name = op + .into_bytes() + .iter() + .map(|ch| { + match ch { + b'+' => "plus", + b'-' => "minus", + b'*' => "times", + b'/' => "slash", + b'.' => "dot", + b'|' => "pipe", + b'>' => "greater", + b'<' => "less", + b'=' => "equals", + b'?' => "interrogation", + b'!' => "exclamation", + b'~' => "tilde", + b'%' => "percent", + b'&' => "ampersand", + b'#' => "bang", + b'$' => "dollar", + b'^' => "power", + b':' => "colon", + _ => panic!( + "Error! Unexpected operator {} to be translated as a function!", + ch + ), + } + .to_owned() + }) + .collect::>() + .join("_"); + ctx.put(name) +} + pub struct LuaEmitter; impl LuaEmitter { @@ -87,6 +128,13 @@ impl code::Visitor for LuaEmitter { Ok(ctx.put(";")) } + fn visit_do(&self, ctx: code::Builder, expr: &ast::Do) -> Result { + let ctx = ctx.put("(function(...)").push(); + let ctx = self.visit_script(ctx, &expr.body)?; + let ctx = ctx.pop().unwrap().line().put("end)(...)"); + Ok(ctx) + } + fn visit_1tuple( &self, ctx: code::Builder, @@ -150,16 +198,12 @@ impl code::Visitor for LuaEmitter { let ctx = ctx?.line(); let ctx = match field { ast::ClassField::Method(f) => { - let level = if let Some(first) = f.arguments.first() { - if first.name.0 == "self" { - ".prototype." - } else { - "." - } + let is_self = if let Some(first) = f.arguments.first() { + first.name.0 == "self" } else { - "." - } - .to_string(); + false + }; + let level = if is_self { ".prototype." } else { "." }.to_string(); let ctx = ctx .put(stmt.name.0.clone()) .put(level) @@ -172,7 +216,26 @@ impl code::Visitor for LuaEmitter { body: ast::ScriptOrExpression::Script(f.body.clone()), }, )?; - ctx.put(";") + let ctx = ctx.put(";"); + let ctx = f.decorators.iter().fold(Ok(ctx), |ctx, dec| { + let ctx = ctx?.line(); + let ctx = self.visit_call(ctx, &dec.target)?; + let fn_ref = if is_self { + format!("{}.prototype.{}", stmt.name.0.clone(), f.name.0.clone()) + } else { + format!("{}.{}", stmt.name.0.clone(), f.name.0.clone()) + }; + let ctx = ctx.put(format!( + "({}, \"{}\", {}, \"{}\", {{ is_static = {} }});", + fn_ref, + f.name.0.clone(), + stmt.name.0.clone(), + stmt.name.0.clone(), + !is_self + )); + Ok(ctx) + })?; + ctx } ast::ClassField::Let(f) => { let ctx = match &f.target { @@ -192,49 +255,17 @@ impl code::Visitor for LuaEmitter { }; ctx.put(";") } - ast::ClassField::Operator(f) => { - let target = match f.operator { - ast::Operator::Plus => "__add", - ast::Operator::Minus => "__sub", - ast::Operator::Product => "__mul", - ast::Operator::Quotient => "__div", - ast::Operator::Remainder => "__mod", - ast::Operator::Power => "__pow", - ast::Operator::Equal => "__eq", - ast::Operator::Less => "__lt", - ast::Operator::LessEqual => "__le", - ast::Operator::Concat => "__concat", - ast::Operator::Count => "__len", - _ => todo!( - "Operator overload for {:?} operator not supported", - f.operator.clone() - ), - }; - let ctx = ctx.put(format!( - "{}.prototype.__meta__.{} = ", - stmt.name.0.clone(), - target - )); - let ctx = self.visit_lambda( - ctx, - &ast::Lambda { - arguments: f.arguments.clone(), - body: ast::ScriptOrExpression::Script(f.body.clone()), - }, - )?; - ctx.put(";") - } }; - let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { - let ctx = ctx?.line(); - let ctx = self.visit_call(ctx, &dec.target)?; - let ctx = ctx.put(format!( - "({}, \"{}\");", - stmt.name.0.clone(), - stmt.name.0.clone() - )); - Ok(ctx) - })?; + Ok(ctx) + })?; + let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { + let ctx = ctx?.line(); + let ctx = self.visit_call(ctx, &dec.target)?; + let ctx = ctx.put(format!( + "({}, \"{}\");", + stmt.name.0.clone(), + stmt.name.0.clone() + )); Ok(ctx) })?; Ok(ctx) @@ -266,7 +297,19 @@ impl code::Visitor for LuaEmitter { ctx.put("...") }; let ctx = ctx.put(")").push(); - let ctx = self.visit_script(ctx, &stmt.body)?; + let ctx = if let Some(native) = stmt.native.clone() { + let ctx = ctx.line().put("-- NATIVE CODE"); + match native.iter().find(|(ident, _)| ident.0 == "Lua") { + Some((_, src)) => match src { + ast::StringLiteral::Double(src) => ctx.put(src), + }, + None => ctx.put("error('Native function implementation not found')"), + } + .line() + .put("-- NATIVE CODE") + } else { + self.visit_script(ctx, &stmt.body)? + }; let ctx = ctx.pop().unwrap().line().put("end"); let ctx = stmt.decorators.iter().fold(Ok(ctx), |ctx, dec| { let ctx = ctx?.line(); @@ -448,6 +491,11 @@ impl code::Visitor for LuaEmitter { ctx }; let ctx = ctx.put("("); + if expr.head.is_macro { + let ctx = ctx.put(")"); + return Ok(ctx); + } + // If not macro let ctx = if let Some(first) = expr.head.arguments.first() { self.visit_expression(ctx, first)? } else { @@ -529,9 +577,7 @@ impl code::Visitor for LuaEmitter { expr: &ast::StringLiteral, ) -> Result { let ctx = match expr { - ast::StringLiteral::Double(s) => ctx.put("\"").put(s.clone()).put("\""), - ast::StringLiteral::Single(s) => ctx.put("'").put(s.clone()).put("'"), - ast::StringLiteral::Special(_) => todo!(), + ast::StringLiteral::Double(s) => ctx.put("\"").put(escape_string(s.clone())).put("\""), }; Ok(ctx) } @@ -545,93 +591,67 @@ impl code::Visitor for LuaEmitter { ctx: code::Builder, expr: &ast::BinaryExpression, ) -> Result { - // Match direct function translation first - let ctx = match expr.operator.clone() { - ast::Operator::Elastic - | ast::Operator::ElasticLeft - | ast::Operator::ElasticRight - | ast::Operator::PinguBoth - | ast::Operator::PinguLeft - | ast::Operator::PinguRight - | ast::Operator::ArrowStandBoth - | ast::Operator::ArrowStandLeft - | ast::Operator::ArrowStandRight - | ast::Operator::ArrowLeft - | ast::Operator::ArrowRight - | ast::Operator::Disjoin - | ast::Operator::PipeLeft - | ast::Operator::PipeRight - | ast::Operator::AskRight - | ast::Operator::AskLeft => { - let ctx = match expr.operator.clone() { - ast::Operator::Elastic => ctx.put("__elastic("), - ast::Operator::ElasticLeft => ctx.put("__elastic_left("), - ast::Operator::ElasticRight => ctx.put("__elastic_right("), - ast::Operator::PinguBoth => ctx.put("__pingu_both("), - ast::Operator::PinguLeft => ctx.put("__pingu_left("), - ast::Operator::PinguRight => ctx.put("__pingu_right("), - ast::Operator::ArrowStandBoth => ctx.put("__arrow_stand_both("), - ast::Operator::ArrowStandLeft => ctx.put("__arrow_stand_left("), - ast::Operator::ArrowStandRight => ctx.put("__arrow_stand_right("), - ast::Operator::BothWays => ctx.put("__both_ways("), - ast::Operator::ArrowLeft => ctx.put("__arrow_left("), - ast::Operator::ArrowRight => ctx.put("__arrow_right("), - ast::Operator::Disjoin => ctx.put("__disjoin("), - ast::Operator::PipeLeft => ctx.put("__pipe_left("), - ast::Operator::PipeRight => ctx.put("__pipe_right("), - ast::Operator::AskRight => ctx.put("__ask_right("), - ast::Operator::AskLeft => ctx.put("__ask_left("), - _ => panic!(), - }; - let ctx = self.visit_expression(ctx, &expr.left)?.put(", "); - self.visit_expression(ctx, &expr.right)?.put(")") + let op = expr.operator.0.as_str(); + // Extra logic (Indirect operation expansion) + if op == "??" { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" == nil and "); + let ctx = self.visit_expression(ctx, &expr.right)?.put(" or "); + return self.visit_expression(ctx, &expr.left); + } else if op == "?:" { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" or "); + return self.visit_expression(ctx, &expr.right); + } + // Translate native operators. + match op { + // Basic math + | "+" + | "-" + | "*" + | "/" + | "%" + | "**" + // Comparison + | ">" + | ">=" + | "<" + | "<=" + | "==" + // Logic + | "not" + | "and" + | "or" + // Logic + | "&" + | "|" + | "<<" + | "<<<" + | ">>" + | ">>>" + => { + let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); + let ctx = ctx.put(op.to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) } - _ => { - // Then if not, match extra logic (Indirect operation expansion) - if let ast::Operator::Coalesce = expr.operator.clone() { - let ctx = self.visit_expression(ctx, &expr.left)?.put(" == nil and "); - let ctx = self.visit_expression(ctx, &expr.right)?.put(" or "); - return self.visit_expression(ctx, &expr.left); - } else if let ast::Operator::Elvis = expr.operator.clone() { - let ctx = self.visit_expression(ctx, &expr.left)?.put(" or "); - return self.visit_expression(ctx, &expr.right); - } - // If all fails, translate native operators or panic. + "++" => { + // Native-to-native operator translation let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); - let ctx = match expr.operator.clone() { - // Basic math - ast::Operator::Plus => ctx.put("+"), - ast::Operator::Minus => ctx.put("-"), - ast::Operator::Product => ctx.put("*"), - ast::Operator::Quotient => ctx.put("/"), - ast::Operator::Remainder => ctx.put("%"), - ast::Operator::Power => ctx.put("**"), - ast::Operator::Concat => ctx.put(".."), - // Comparison - ast::Operator::Greater => ctx.put(">"), - ast::Operator::GreaterEqual => ctx.put(">="), - ast::Operator::Less => ctx.put("<"), - ast::Operator::LessEqual => ctx.put("<="), - ast::Operator::Equal => ctx.put("=="), - ast::Operator::NotEqual => ctx.put("~="), - // Logic - ast::Operator::LogicNot => ctx.put("not"), - ast::Operator::LogicAnd => ctx.put("and"), - ast::Operator::LogicOr => ctx.put("or"), - // Logic - ast::Operator::BWiseAnd => ctx.put("&"), - ast::Operator::BWiseOr => ctx.put("|"), - ast::Operator::BWiseLShift => ctx.put("<<"), - ast::Operator::BWiseLShiftRoundtrip => ctx.put("<<<"), - ast::Operator::BWiseRShift => ctx.put(">>"), - ast::Operator::BWiseRShiftRoundtrip => ctx.put(">>>"), - op => todo!("Binary operator {:?} not supported!", op), - }; - let ctx = self.visit_expression(ctx.put(" "), &expr.right)?; - return Ok(ctx); + let ctx = ctx.put("..".to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) } - }; - Ok(ctx) + "<>" => { + // Native-to-native operator translation + let ctx = self.visit_expression(ctx, &expr.left)?.put(" "); + let ctx = ctx.put("~=".to_owned()); + self.visit_expression(ctx.put(" "), &expr.right) + } + _ => { + // Direct function translation + let ctx = translate_operator(ctx, op.to_owned()).put("("); + let ctx = self.visit_expression(ctx, &expr.left)?.put(", "); + let ctx = self.visit_expression(ctx, &expr.right)?.put(")"); + Ok(ctx) + } + } } fn visit_unary( @@ -639,10 +659,10 @@ impl code::Visitor for LuaEmitter { ctx: code::Builder, expr: &ast::UnaryExpression, ) -> Result { - let ctx = match expr.operator.clone() { - ast::Operator::Minus => ctx.put("-"), - ast::Operator::LogicNot => ctx.put("not "), - ast::Operator::Count => ctx.put("#"), + let ctx = match expr.operator.clone().0.as_str() { + "-" => ctx.put("-"), + "not" => ctx.put("not "), + "#?" => ctx.put("#"), op => todo!("Unary operator {:?} not supported!", op), }; let ctx = self.visit_expression(ctx, &expr.expression)?; @@ -688,7 +708,8 @@ impl code::Visitor for LuaEmitter { self.visit_expression(ctx, &v.clone().unwrap()) } ast::TableKeyExpression::Expression(k) => { - let ctx = self.visit_expression(ctx, &k)?.put(" = "); + let ctx = ctx.put("["); + let ctx = self.visit_expression(ctx, &k)?.put("] = "); self.visit_expression(ctx, &v.clone().unwrap()) } ast::TableKeyExpression::Implicit(k) => { @@ -750,7 +771,7 @@ impl code::Visitor for LuaEmitter { ) -> Result { let ctx = match &expr.handler { ast::AssignmentTarget::Destructuring(e) => { - let ctx = ctx.line().put("for __destructuring__ in "); + let ctx = ctx.line().put("for __destructure__ in "); let ctx = self.visit_expression(ctx, &expr.target)?; let ctx = ctx.put(" do").push(); let ctx = self.generate_destructured_assignment(ctx, &e)?; diff --git a/src/main.rs b/src/main.rs index 6cc121e..2b286ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod code; mod errors; mod lua; mod parser; +pub mod runtime; #[cfg(test)] mod tests; @@ -72,6 +73,7 @@ fn main() { // Handle parsing errors match parser::Script::parse(input.clone()) { Ok(result) => { + // TODO: ABSTRACT! let output = lua::LuaEmitter .visit_script( code::Builder::new(indent).put( @@ -91,12 +93,7 @@ fn main() { out_file.write_all(output.as_bytes()).unwrap(); } } else { - let rt = rlua::Lua::new(); - rt.context(move |ctx| -> rlua::Result<()> { - ctx.load(&output).eval()?; - Ok(()) - }) - .unwrap(); + runtime::RuntimeHost.evaluate(&output).unwrap(); } } Err(err) => { diff --git a/src/parser/ast.rs b/src/parser/ast.rs index faf09c6..a5aca31 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -17,6 +17,7 @@ pub struct Function { pub arguments: Vec, pub decorators: Vec, pub body: Script, + pub native: Option>, } #[derive(Debug, Clone)] @@ -25,6 +26,11 @@ pub struct Lambda { pub body: ScriptOrExpression, } +#[derive(Debug, Clone)] +pub struct Do { + pub body: Script, +} + #[derive(Debug, Clone)] pub struct Tuple(pub Vec); @@ -180,83 +186,15 @@ pub enum Statement { Expression(Expression), } -#[derive(Debug, Clone)] -pub struct OperatorOverload { - pub operator: Operator, - pub arguments: Vec, - pub body: Script, -} - #[derive(Debug, Clone)] pub enum ClassField { Method(Function), Let(Let), - Operator(OperatorOverload), -} - -#[derive(Debug, Clone)] -pub enum Operator { - // Arithmetic - Plus, - Minus, - Quotient, - Product, - Power, - Remainder, - Concat, - Range, - // Comparison - Equal, - Less, - LessEqual, - Greater, - GreaterEqual, - NotEqual, - Starship, - Funnel, - // Logic - LogicOr, - LogicAnd, - LogicNor, - LogicNand, - LogicXOr, - LogicNot, - // Binary - BWiseAnd, - BWiseOr, - BWiseNot, - BWiseLShift, - BWiseRShift, - BWiseLShiftRoundtrip, - BWiseRShiftRoundtrip, - // Special operators (No native equivalent for these) - Count, // Except this, in Lua. - ArrowRight, - ArrowLeft, - BothWays, - ArrowStandRight, - ArrowStandLeft, - ArrowStandBoth, - // Exclamation, - Tilde, - Disjoin, - Elastic, - ElasticRight, - ElasticLeft, - Elvis, - Coalesce, - PinguRight, - PinguLeft, - PinguBoth, - PipeRight, - PipeLeft, - AskRight, - AskLeft, - // Bolted, - Dollar, - ExclamationQuestion, } +#[derive(Debug, Clone)] +pub struct Operator(pub String); + #[derive(Debug, Clone)] pub struct BinaryExpression { pub left: Expression, @@ -300,8 +238,6 @@ pub enum TableKeyExpression { #[derive(Debug, Clone)] pub enum StringLiteral { Double(String), - Single(String), - Special(String), } #[derive(Debug, Clone)] @@ -313,6 +249,7 @@ pub enum Expression { Tuple(Tuple), Tuple1(Box), Table(Table), + Do(Do), Vector(Vector), Number(Number), String(StringLiteral), diff --git a/src/parser/grammar.rs b/src/parser/grammar.rs new file mode 100644 index 0000000..db89273 --- /dev/null +++ b/src/parser/grammar.rs @@ -0,0 +1,394 @@ +use super::ast::*; + +peg::parser! { + grammar saturnus_script() for str { + pub rule script() -> Script + = _ statements:statement() ** __ _ + { Script { statements } } + + // Statements + rule statement() -> Statement + = e:class() { Statement::Class(e) } + / e:func() { Statement::Function(e) } + / e:for_each() { Statement::For(e) } + / e:while_loop() { Statement::While(e) } + / e:loop_loop() { Statement::Loop(e) } + / e:if_stmt() { Statement::If(e) } + / e:declare_var() { Statement::Let(e) } + / e:assignment() { Statement::Assignment(e) } + / e:return_stmt() { Statement::Return(e) } + / e:do_expression() { Statement::Expression(e) } + / e:expression() _ EOS() { Statement::Expression(e) } + + rule if_stmt() -> If + = "if" __ condition:expression() __ "{" body:script() + branches:("}" _ "else" __ "if" __ c:expression() __ "{" s:script() { (c, s) })* + else_branch:("}" _ "else" _ "{" e:script() {e})? + "}" + { If { condition, body, branches, else_branch } } + / expected!("If statement") + + rule for_each() -> For + = "for" __ handler:assignment_target() __ "in" __ target:expression() _ "{" + body:script() "}" + { For { handler, target, body } } + / expected!("For loop") + + rule while_loop() -> While + = "while" __ c:expression() _ "{" body:script() "}" + { While { condition: ExpressionOrLet::Expression(c), body } } + / "while" __ c:let_expression() _ "{" body:script() "}" + { While { condition: ExpressionOrLet::Let(c), body } } + / expected!("While loop") + + rule loop_loop() -> Loop + = "loop" _ "{" body:script() "}" + { Loop { body } } + / expected!("Loop") + + rule func() -> Function + = decorators:decorator_list() FN() __ NATIVE() __ name:identifier() _ arguments:argument_list() + _ "{" native:(__ "@" name:identifier() _ source:string_literal() _ EOS() { (name, source) })+ __ "}" + { Function { name, decorators, body: Script { statements: vec![] }, arguments, native: Some(native) } } + / + decorators:decorator_list() FN() __ name:identifier() _ arguments:argument_list() _ "{" body:script() "}" + { Function { name, decorators, body, arguments, native: None } } + / decorators:decorator_list() FN() __ name:identifier() _ arguments:argument_list() _ "{" _ "}" + { Function { name, decorators, body: Script { statements: vec![] }, arguments, native: None } } + / expected!("Function declaration") + + rule class() -> Class + = decorators:decorator_list() CLASS() + __ name:identifier() _ "{" + fields:(_ f:class_fields() _ {f})* + _ "}" + { Class { name, fields, decorators } } + / expected!("Class declaration") + + rule declare_var() -> Let + = e:let_expression() _ EOS() { e } + / expected!("Variable declaration") + + rule assignment() -> Assignment + = target:member_expression() _ extra:assign_extra()? "=" _ value:expression() _ EOS() + { Assignment { target, value, extra } } + + rule return_stmt() -> Return + = "return" __ value:expression() _ EOS() + { Return { value } } + + // Expressions + pub rule expression() -> Expression + = binary_expression() + + rule member_expression() -> MemberExpression + = head:primary() + tail:( + _ "[" _ e:expression() _ "]" { MemberSegment::Computed(e) } + / _ "." _ i:identifier() { MemberSegment::IdentifierDynamic(i) } + / _ "::" _ i:identifier() { MemberSegment::IdentifierStatic(i) } + )* + { MemberExpression { head, tail } } + + rule call_expression() -> CallExpression + = head:( + callee:member_expression() m:"!"? _ arguments:call_arguments() + { CallSubExpression { callee: Some(callee), is_macro: m.is_some(), arguments }.into() } + / callee:member_expression() _ arg:table_expression() + { CallSubExpression { callee: Some(callee), is_macro: false, arguments: vec![arg] } } + ) + tail:( + _ "[" _ prop:expression() _ "]" { MemberSegment::Computed(prop).into() } + / _ "." _ prop:identifier() { MemberSegment::IdentifierDynamic(prop).into() } + / _ "::" _ prop:identifier() { MemberSegment::IdentifierStatic(prop).into() } + / _ arguments:call_arguments() { CallSubExpression { callee: None, is_macro: false, arguments }.into() } + )* + { CallExpression { head, tail } } + + rule primary() -> Expression + = i:identifier() { Expression::Identifier(i) } + / string_expression() + / number_expression() + / table_expression() + / vector_expression() + / tuple_expression() + / lambda_expression() + / do_expression() + / enclosed_expression() + + rule call_arguments() -> Vec + = "(" _ args:(e:call_argument_list() _ { e })? ")" + { args.unwrap_or(vec![]) } + + rule call_argument_list() -> Vec + = args:expression() ** (_ "," _) + { args } + + rule binary_expression() -> Expression = precedence! { + value:$("-") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("+") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("#?") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("not") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("~^") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + // value:$("!" _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("~") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + // value:$("¬" _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("$") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + value:$("!?") _ expression:@ { UnaryExpression { expression, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$("++") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("..") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + + left:(@) _ value:$("+") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("-") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$("*") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("/") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:@ _ value:$("**") _ right:(@) { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$("%") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$(">=<") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$(">=") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$(">") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<=>") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<=") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<>") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("==") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$("and") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("or") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("xor") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("nand") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("nor") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + left:(@) _ value:$("&") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("|") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<<<") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$("<<") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$(">>>") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + left:(@) _ value:$(">>") _ right:@ { BinaryExpression { left, right, operator: Operator(value.into()) }.into() } + -- + // Extra logic: + left:(@) _ value:$(['^'|'+'|'-'|'*'|'/'|'.'|'|'|'>'|'<'|'='|'?'|'!'|'~'|'%'|'&'|'#'|'$'|':']+) _ right:@ { + BinaryExpression { left, right, operator: Operator(value.into()) }.into() + } + -- + e:atom() { e } + } + + rule atom() -> Expression + = e:call_expression() { Expression::Call(Box::new(e)) } + / lambda_expression() + / string_expression() + / number_expression() + / vector_expression() + / table_expression() + / tuple_expression() + / do_expression() + / e:member_expression() { Expression::Reference(Box::new(e)) } + / unit() { Expression::Unit } + / enclosed_expression() + + // Literal-to-expression + rule string_expression() -> Expression = e:string_literal() { Expression::String(e) } + rule number_expression() -> Expression = e:number_literal() { Expression::Number(e) } + rule lambda_expression() -> Expression = e:lambda_literal() { Expression::Lambda(Box::new(e)) } + rule vector_expression() -> Expression = e:vector_literal() { Expression::Vector(e) } + rule table_expression() -> Expression = e:table_literal() { Expression::Table(e) } + rule tuple_expression() -> Expression = e:tuple_literal() { Expression::Tuple(e) } + rule do_expression() -> Expression = e:do_literal() { Expression::Do(e) } + + rule enclosed_expression() -> Expression + = "(" _ e:expression() _ ")" { Expression::Tuple1(Box::new(e)) } + + rule lambda_literal() -> Lambda + = name:identifier() _ "=>" _ "{" body:script() "}" + { Lambda { arguments: vec![Argument { name, decorators: vec![] }], body: ScriptOrExpression::Script(body) } } + / name:identifier() _ "=>" _ body:expression() + { Lambda { arguments: vec![Argument { name, decorators: vec![] }], body: ScriptOrExpression::Expression(body) } } + / arguments:argument_list() _ "=>" _ "{" body:script() "}" + { Lambda { arguments, body: ScriptOrExpression::Script(body) } } + / arguments:argument_list() _ "=>" _ expr:expression() + { Lambda { arguments, body: ScriptOrExpression::Expression(expr) } } + / arguments:argument_list() _ "=>" _ "{" _ "}" + { Lambda { arguments, body: ScriptOrExpression::Script(Script { statements: vec![] }) } } + + // Literals + rule number_literal() -> Number + = value:$(DIGIT()+ "." DIGIT()+) { Number::Float(value.parse().unwrap()) } + / value:$(DIGIT()+) { Number::Integer(value.parse().unwrap()) } + / "'" value:$(!"'" ANY()) "'" { Number::Integer(value.chars().nth(0).unwrap() as i64) } + / expected!("Number literal") + + rule string_literal() -> StringLiteral + = "\"" value:$((!"\"" ANY())*) "\"" { StringLiteral::Double(value.into()) } + / expected!("String literal") + + rule vector_literal() -> Vector + = "[" _ expressions:comma_expr() _ "]" + { Vector { expressions } } + / expected!("Vector literal") + + rule table_literal() -> Table + = "{" _ key_values:table_kvs() _ "}" + { Table { key_values } } + / expected!("Table literal") + + rule tuple_literal() -> Tuple + = "(" _ e:expression() **<2,> (_ "," _) _ ")" + { Tuple(e) } + / expected!("Tuple literal") + + rule do_literal() -> Do + = "{" body:script() "}" + { Do { body } } + / expected!("Do block") + + // Auxiliaries and sub-expressions + rule let_expression() -> Let + = "let" __ target:assignment_target() value:(_ "=" _ e:expression(){e})? + { Let { target, value } } + + rule assignment_target() -> AssignmentTarget + = e:identifier() { AssignmentTarget::Identifier(e) } + / e:destructure_expression() { AssignmentTarget::Destructuring(e) } + + rule destructure_expression() -> Destructuring + = "{" _ targets:(name:identifier() ** (_ "," _) { name }) _ "}" + { Destructuring(targets, DestructureOrigin::Table) } + / "(" _ targets:(name:identifier() ** (_ "," _) { name }) _ ")" + { Destructuring(targets, DestructureOrigin::Tuple) } + / "[" _ targets:(name:identifier() ** (_ "," _) { name }) _ "]" + { Destructuring(targets, DestructureOrigin::Array) } + + rule class_fields() -> ClassField + = e:declare_var() { ClassField::Let(e) } + / e:func() { ClassField::Method(e) } + + rule assign_extra() -> Operator + = value:$("++") { Operator(value.into()) } + / value:$("+") { Operator(value.into()) } + / value:$("-") { Operator(value.into()) } + / value:$("*") { Operator(value.into()) } + / value:$("/") { Operator(value.into()) } + + rule argument_list() -> Vec + = "(" _ args:argument() ** (_ "," _) _ ")" { args } + + rule argument() -> Argument + = decorators:decorator_list() name:identifier() + { Argument { name, decorators } } + + rule decorator_list() -> Vec + = e:decorator() ++ _ _ { e } + / { vec![] } + + rule decorator() -> Decorator + = "@" _ target:call_expression() { Decorator { target } } + / expected!("Decorator") + + rule identifier() -> Identifier + = value:$(IDENT()) + { Identifier(value.into()) } + / expected!("Identifier") + + rule wrapped_comma_expr() -> Vec + = "(" _ e:comma_expr() _ ")" { e } + + rule comma_expr() -> Vec + = e:expression() ** (_ "," _) (_ "," _)? { e } + + rule table_kvs() -> Vec<(TableKeyExpression, Option)> + = kv:table_kv_pair() ** (_ "," _) (_ "," _)? + { kv } + + rule table_kv_pair() -> (TableKeyExpression, Option) + = k:identifier() _ ":" _ v:expression() + { (TableKeyExpression::Identifier(k), Some(v)) } + / k:string_expression() _ ":" _ v:expression() + { (TableKeyExpression::Expression(k), Some(v)) } + / "[" _ k:expression() _ "]" _ ":" _ v:expression() + { (TableKeyExpression::Expression(k), Some(v)) } + / k:identifier() + { ( + TableKeyExpression::Implicit(k.clone()), + None + ) } + + rule unit() -> Expression = "()" { Expression::Unit } + + // Tokens + rule IDENT() = ALPHA() (ALPHA() / DIGIT())* + rule LET() = "let" + rule MUT() = "mut" + rule CLASS() = "class" + rule END() = "end" + rule FN() = "fn" + rule NATIVE() = "@native" + rule ANY() = quiet!{ [_] } / expected!("Any character") + rule BLANK() = ['\t'|' '] / expected!("White space") + rule WS() = BLANK() / LINE_COMMENT() / BLOCK_COMMENT() / EOL() + rule LINE_COMMENT() = quiet!{ "//" (!EOL() ANY())* EOL() } / expected!("Line comment") + rule BLOCK_COMMENT() = quiet!{ "/*" (!"*/" ANY())* "*/" } / expected!("Block comment") + rule EOL() = quiet!{ ['\r'|'\n'] } / expected!("End of line") + rule EOS() = quiet!{ ";" } / expected!("End of statement") //quiet!{ EOL() / ";" } / expected!("End of statement") + rule ALPHA() = quiet!{ ['A'..='Z'|'a'..='z'|'_'] } / expected!("Alphanumeric") + rule DIGIT() = quiet!{ ['0'..='9'] } / expected!("Digit") + rule _ = WS()* + rule __ = WS()+ + + // Special matching rule: Any Binary Operator + rule any_operator() -> Operator + = value:$("++") { Operator(value.into()) } + / value:$("..") { Operator(value.into()) } + / value:$("+") { Operator(value.into()) } + / value:$("-") { Operator(value.into()) } + / value:$("*") { Operator(value.into()) } + / value:$("/") { Operator(value.into()) } + / value:$("**") { Operator(value.into()) } + / value:$("%") { Operator(value.into()) } + / value:$(">=<") { Operator(value.into()) } + / value:$(">=") { Operator(value.into()) } + / value:$("<=>") { Operator(value.into()) } + / value:$("<=") { Operator(value.into()) } + / value:$("<>") { Operator(value.into()) } + / value:$("==") { Operator(value.into()) } + / value:$("and") { Operator(value.into()) } + / value:$("or") { Operator(value.into()) } + / value:$("xor") { Operator(value.into()) } + / value:$("nand") { Operator(value.into()) } + / value:$("nor") { Operator(value.into()) } + / value:$(['^'|'+'|'-'|'*'|'/'|'.'|'|'|'>'|'<'|'='|'?'|'!'|'~'|'%'|'&'|'#'|'$'|':']+) { Operator(value.into()) } + } +} + +pub type ParseResult = Result>; + +#[derive(Debug, Clone)] +pub struct Script { + pub statements: Vec, +} +impl Script { + pub fn parse(input: I) -> ParseResult + where + I: Into, + { + let fragment: String = input.into(); + saturnus_script::script(&fragment) + } + + // This function is used only in tests for now. + #[cfg(test)] + pub fn parse_expression( + input: I, + ) -> Result> + where + I: Into, + { + let fragment: String = input.into(); + saturnus_script::expression(&fragment) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c7b6664..9c9ea43 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,422 +1,4 @@ pub mod ast; +mod grammar; -use ast::*; - -peg::parser! { - grammar saturnus_script() for str { - pub rule script() -> Script - = _ statements:statement() ** __ _ - { Script { statements } } - - // Statements - rule statement() -> Statement - = e:class() { Statement::Class(e) } - / e:func() { Statement::Function(e) } - / e:for_each() { Statement::For(e) } - / e:while_loop() { Statement::While(e) } - / e:loop_loop() { Statement::Loop(e) } - / e:if_stmt() { Statement::If(e) } - / e:declare_var() { Statement::Let(e) } - / e:assignment() { Statement::Assignment(e) } - / e:return_stmt() { Statement::Return(e) } - / e:expression() _ EOS() { Statement::Expression(e) } - - rule if_stmt() -> If - = "if" __ condition:expression() __ "{" body:script() - branches:("}" _ "else" __ "if" __ c:expression() __ "{" s:script() { (c, s) })* - else_branch:("}" _ "else" _ "{" e:script() {e})? - "}" - { If { condition, body, branches, else_branch } } - / expected!("If statement") - - rule for_each() -> For - = "for" __ handler:assignment_target() __ "in" __ target:expression() _ "{" - body:script() "}" - { For { handler, target, body } } - / expected!("For loop") - - rule while_loop() -> While - = "while" __ c:expression() _ "{" body:script() "}" - { While { condition: ExpressionOrLet::Expression(c), body } } - / "while" __ c:let_expression() _ "{" body:script() "}" - { While { condition: ExpressionOrLet::Let(c), body } } - / expected!("While loop") - - rule loop_loop() -> Loop - = "loop" _ "{" body:script() "}" - { Loop { body } } - / expected!("Loop") - - rule func() -> Function - = decorators:decorator_list() FN() __ name:identifier() _ arguments:argument_list() _ "{" body:script() "}" - { Function { name, decorators, body, arguments } } - / decorators:decorator_list() FN() __ name:identifier() _ arguments:argument_list() _ "{" _ "}" - { Function { name, decorators, body: Script { statements: vec![] }, arguments } } - / expected!("Function declaration") - - rule class() -> Class - = decorators:decorator_list() CLASS() - __ name:identifier() _ "{" - fields:(_ f:class_fields() _ {f})* - _ "}" - { Class { name, fields, decorators } } - / expected!("Class declaration") - - rule declare_var() -> Let - = e:let_expression() _ EOS() { e } - / expected!("Variable declaration") - - rule assignment() -> Assignment - = target:member_expression() _ extra:assign_extra()? "=" _ value:expression() _ EOS() - { Assignment { target, value, extra } } - - rule return_stmt() -> Return - = "return" __ value:expression() _ EOS() - { Return { value } } - - // Expressions - pub rule expression() -> Expression - = binary_expression() - - rule member_expression() -> MemberExpression - = head:primary() - tail:( - _ "[" _ e:expression() _ "]" { MemberSegment::Computed(e) } - / _ "." _ i:identifier() { MemberSegment::IdentifierDynamic(i) } - / _ "::" _ i:identifier() { MemberSegment::IdentifierStatic(i) } - )* - { MemberExpression { head, tail } } - - rule call_expression() -> CallExpression - = head:( - callee:member_expression() m:"!"? _ arguments:call_arguments() - { CallSubExpression { callee: Some(callee), is_macro: m.is_some(), arguments }.into() } - / callee:member_expression() _ arg:table_expression() - { CallSubExpression { callee: Some(callee), is_macro: false, arguments: vec![arg] } } - ) - tail:( - _ "[" _ prop:expression() _ "]" { MemberSegment::Computed(prop).into() } - / _ "." _ prop:identifier() { MemberSegment::IdentifierDynamic(prop).into() } - / _ "::" _ prop:identifier() { MemberSegment::IdentifierStatic(prop).into() } - / _ arguments:call_arguments() { CallSubExpression { callee: None, is_macro: false, arguments }.into() } - )* - { CallExpression { head, tail } } - - rule primary() -> Expression - = i:identifier() { Expression::Identifier(i) } - / string_expression() - / number_expression() - / table_expression() - / vector_expression() - / tuple_expression() - / lambda_expression() - / enclosed_expression() - - rule call_arguments() -> Vec - = "(" _ args:(e:call_argument_list() _ { e })? ")" - { args.unwrap_or(vec![]) } - - rule call_argument_list() -> Vec - = args:expression() ** (_ "," _) - { args } - - rule binary_expression() -> Expression = precedence! { - "-" _ expression:@ { UnaryExpression { expression, operator: Operator::Minus }.into() } - "+" _ expression:@ { UnaryExpression { expression, operator: Operator::Plus }.into() } - "#?" _ expression:@ { UnaryExpression { expression, operator: Operator::Count }.into() } - "not" _ expression:@ { UnaryExpression { expression, operator: Operator::LogicNot }.into() } - "~^" _ expression:@ { UnaryExpression { expression, operator: Operator::BWiseNot }.into() } - // "!" _ expression:@ { UnaryExpression { expression, operator: Operator::Exclamation }.into() } - "~" _ expression:@ { UnaryExpression { expression, operator: Operator::Tilde }.into() } - // "¬" _ expression:@ { UnaryExpression { expression, operator: Operator::Bolted }.into() } - "$" _ expression:@ { UnaryExpression { expression, operator: Operator::Dollar }.into() } - "!?" _ expression:@ { UnaryExpression { expression, operator: Operator::ExclamationQuestion }.into() } - -- - left:(@) _ "++" _ right:@ { BinaryExpression { left, right, operator: Operator::Concat }.into() } - left:(@) _ ".." _ right:@ { BinaryExpression { left, right, operator: Operator::Range }.into() } - - left:(@) _ "+" _ right:@ { BinaryExpression { left, right, operator: Operator::Plus }.into() } - left:(@) _ "-" _ right:@ { BinaryExpression { left, right, operator: Operator::Minus }.into() } - -- - left:(@) _ "*" _ right:@ { BinaryExpression { left, right, operator: Operator::Product }.into() } - left:(@) _ "/" _ right:@ { BinaryExpression { left, right, operator: Operator::Quotient }.into() } - -- - left:@ _ "**" _ right:(@) { BinaryExpression { left, right, operator: Operator::Power }.into() } - -- - left:(@) _ "%" _ right:@ { BinaryExpression { left, right, operator: Operator::Remainder }.into() } - -- - left:(@) _ ">=<" _ right:@ { BinaryExpression { left, right, operator: Operator::Funnel }.into() } - left:(@) _ ">=" _ right:@ { BinaryExpression { left, right, operator: Operator::GreaterEqual }.into() } - left:(@) _ ">" _ right:@ { BinaryExpression { left, right, operator: Operator::Greater }.into() } - left:(@) _ "<=>" _ right:@ { BinaryExpression { left, right, operator: Operator::Starship }.into() } - left:(@) _ "<=" _ right:@ { BinaryExpression { left, right, operator: Operator::LessEqual }.into() } - left:(@) _ "<>" _ right:@ { BinaryExpression { left, right, operator: Operator::NotEqual }.into() } - left:(@) _ "<" _ right:@ { BinaryExpression { left, right, operator: Operator::Less }.into() } - left:(@) _ "==" _ right:@ { BinaryExpression { left, right, operator: Operator::Equal }.into() } - -- - left:(@) _ "and" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicAnd }.into() } - left:(@) _ "or" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicOr }.into() } - left:(@) _ "xor" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicXOr }.into() } - left:(@) _ "nand" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicNand }.into() } - left:(@) _ "nor" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicNor }.into() } - -- - left:(@) _ "&" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseAnd }.into() } - left:(@) _ "|" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseOr }.into() } - left:(@) _ "<<<" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseLShiftRoundtrip }.into() } - left:(@) _ "<<" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseLShift }.into() } - left:(@) _ ">>>" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseRShiftRoundtrip }.into() } - left:(@) _ ">>" _ right:@ { BinaryExpression { left, right, operator: Operator::BWiseRShift }.into() } - // Extra logic: - left:(@) _ "^" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicXOr }.into() } - left:(@) _ "¬&" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicNand }.into() } - left:(@) _ "¬|" _ right:@ { BinaryExpression { left, right, operator: Operator::LogicNor }.into() } - -- - left:(@) _ "<~>" _ right:@ { BinaryExpression { left, right, operator: Operator::Elastic }.into() } - left:(@) _ "<~" _ right:@ { BinaryExpression { left, right, operator: Operator::ElasticLeft }.into() } - left:(@) _ "~>" _ right:@ { BinaryExpression { left, right, operator: Operator::ElasticRight }.into() } - left:(@) _ "<:>" _ right:@ { BinaryExpression { left, right, operator: Operator::PinguBoth }.into() } - left:(@) _ "<:" _ right:@ { BinaryExpression { left, right, operator: Operator::PinguLeft }.into() } - left:(@) _ ":>" _ right:@ { BinaryExpression { left, right, operator: Operator::PinguRight }.into() } - left:(@) _ "<-|->" _ right:@ { BinaryExpression { left, right, operator: Operator::ArrowStandBoth }.into() } - left:(@) _ "<-|" _ right:@ { BinaryExpression { left, right, operator: Operator::ArrowStandLeft }.into() } - left:(@) _ "|->" _ right:@ { BinaryExpression { left, right, operator: Operator::ArrowStandRight }.into() } - left:(@) _ "<->" _ right:@ { BinaryExpression { left, right, operator: Operator::BothWays }.into() } - left:(@) _ "<-" _ right:@ { BinaryExpression { left, right, operator: Operator::ArrowLeft }.into() } - left:(@) _ "->" _ right:@ { BinaryExpression { left, right, operator: Operator::ArrowRight }.into() } - left:(@) _ "<|>" _ right:@ { BinaryExpression { left, right, operator: Operator::Disjoin }.into() } - left:(@) _ "<|" _ right:@ { BinaryExpression { left, right, operator: Operator::PipeLeft }.into() } - left:(@) _ "|>" _ right:@ { BinaryExpression { left, right, operator: Operator::PipeRight }.into() } - left:(@) _ "" _ right:@ { BinaryExpression { left, right, operator: Operator::AskLeft }.into() } - -- - left:(@) _ "?:" _ right:@ { BinaryExpression { left, right, operator: Operator::Elvis }.into() } - left:(@) _ "??" _ right:@ { BinaryExpression { left, right, operator: Operator::Coalesce }.into() } - -- - e:atom() { e } - } - - rule atom() -> Expression - = e:call_expression() { Expression::Call(Box::new(e)) } - / lambda_expression() - / string_expression() - / number_expression() - / vector_expression() - / table_expression() - / tuple_expression() - / e:member_expression() { Expression::Reference(Box::new(e)) } - / unit() { Expression::Unit } - / enclosed_expression() - - // Literal-to-expression - rule string_expression() -> Expression = e:string_literal() { Expression::String(e) } - rule number_expression() -> Expression = e:number_literal() { Expression::Number(e) } - rule lambda_expression() -> Expression = e:lambda_literal() { Expression::Lambda(Box::new(e)) } - rule vector_expression() -> Expression = e:vector_literal() { Expression::Vector(e) } - rule table_expression() -> Expression = e:table_literal() { Expression::Table(e) } - rule tuple_expression() -> Expression = e:tuple_literal() { Expression::Tuple(e) } - - rule enclosed_expression() -> Expression - = "(" _ e:expression() _ ")" { Expression::Tuple1(Box::new(e)) } - - rule lambda_literal() -> Lambda - = name:identifier() _ "=>" _ "{" body:script() "}" - { Lambda { arguments: vec![Argument { name, decorators: vec![] }], body: ScriptOrExpression::Script(body) } } - / name:identifier() _ "=>" _ body:expression() - { Lambda { arguments: vec![Argument { name, decorators: vec![] }], body: ScriptOrExpression::Expression(body) } } - / arguments:argument_list() _ "=>" _ "{" body:script() "}" - { Lambda { arguments, body: ScriptOrExpression::Script(body) } } - / arguments:argument_list() _ "=>" _ expr:expression() - { Lambda { arguments, body: ScriptOrExpression::Expression(expr) } } - / arguments:argument_list() _ "=>" _ "{" _ "}" - { Lambda { arguments, body: ScriptOrExpression::Script(Script { statements: vec![] }) } } - - // Literals - rule number_literal() -> Number - = value:$(DIGIT()+ "." DIGIT()+) { Number::Float(value.parse().unwrap()) } - / value:$(DIGIT()+) { Number::Integer(value.parse().unwrap()) } - / expected!("Number literal") - - rule string_literal() -> StringLiteral - = "\"" value:$((!"\"" ANY())*) "\"" { StringLiteral::Double(value.into()) } - / "'" value:$((!"'" ANY())*) "'" { StringLiteral::Single(value.into()) } - / expected!("String literal") - - rule vector_literal() -> Vector - = "[" _ expressions:comma_expr() _ "]" - { Vector { expressions } } - / expected!("Vector literal") - - rule table_literal() -> Table - = "{" _ key_values:table_kvs() _ "}" - { Table { key_values } } - / expected!("Table literal") - - rule tuple_literal() -> Tuple - = "(" _ e:expression() **<2,> (_ "," _) _ ")" - { Tuple(e) } - / expected!("Tuple literal") - - // Auxiliaries and sub-expressions - rule let_expression() -> Let - = "let" __ target:assignment_target() value:(_ "=" _ e:expression(){e})? - { Let { target, value } } - - rule assignment_target() -> AssignmentTarget - = e:identifier() { AssignmentTarget::Identifier(e) } - / e:destructure_expression() { AssignmentTarget::Destructuring(e) } - - rule destructure_expression() -> Destructuring - = "{" _ targets:(name:identifier() ** (_ "," _) { name }) _ "}" - { Destructuring(targets, DestructureOrigin::Table) } - / "(" _ targets:(name:identifier() ** (_ "," _) { name }) _ ")" - { Destructuring(targets, DestructureOrigin::Tuple) } - / "[" _ targets:(name:identifier() ** (_ "," _) { name }) _ "]" - { Destructuring(targets, DestructureOrigin::Array) } - - rule class_fields() -> ClassField - = e:declare_var() { ClassField::Let(e) } - / e:func() { ClassField::Method(e) } - / "operator" _ operator:any_operator() _ arguments:argument_list() _ "{" body:script() "}" - { ClassField::Operator(OperatorOverload { operator, arguments, body }) } - - rule assign_extra() -> Operator - = "+" { Operator::Plus } - / "-" { Operator::Minus } - / "*" { Operator::Product } - / "/" { Operator::Quotient } - / "++" { Operator::Concat } - - rule argument_list() -> Vec - = "(" _ args:argument() ** (_ "," _) _ ")" { args } - - rule argument() -> Argument - = decorators:decorator_list() name:identifier() - { Argument { name, decorators } } - - rule decorator_list() -> Vec - = e:decorator() ++ __ __ { e } - / { vec![] } - - rule decorator() -> Decorator - = "@" _ target:call_expression() { Decorator { target } } - / expected!("Decorator") - - rule identifier() -> Identifier - = value:$(IDENT()) - { Identifier(value.into()) } - / expected!("Identifier") - - rule wrapped_comma_expr() -> Vec - = "(" _ e:comma_expr() _ ")" { e } - - rule comma_expr() -> Vec - = e:expression() ** (_ "," _) { e } - - rule table_kvs() -> Vec<(TableKeyExpression, Option)> - = kv:table_kv_pair() ** (_ "," _) - { kv } - - rule table_kv_pair() -> (TableKeyExpression, Option) - = k:identifier() _ ":" _ v:expression() - { (TableKeyExpression::Identifier(k), Some(v)) } - / "[" _ k:expression() _ "]" _ ":" _ v:expression() - { (TableKeyExpression::Expression(k), Some(v)) } - / k:identifier() - { ( - TableKeyExpression::Implicit(k.clone()), - None - ) } - - rule unit() -> Expression = "()" { Expression::Unit } - - // Tokens - rule IDENT() = ALPHA() (ALPHA() / DIGIT())* - rule LET() = "let" - rule MUT() = "mut" - rule CLASS() = "class" - rule END() = "end" - rule FN() = "fn" - rule ANY() = quiet!{ [_] } / expected!("Any character") - rule BLANK() = ['\t'|' '] / expected!("White space") - rule WS() = BLANK() / LINE_COMMENT() / BLOCK_COMMENT() / EOL() - rule LINE_COMMENT() = quiet!{ "//" (!EOL() ANY())* EOL() } / expected!("Line comment") - rule BLOCK_COMMENT() = quiet!{ "/*" (!"*/" ANY())* "*/" } / expected!("Block comment") - rule EOL() = quiet!{ ['\r'|'\n'] } / expected!("End of line") - rule EOS() = quiet!{ EOL() / ";" } / expected!("End of statement") - rule ALPHA() = quiet!{ ['A'..='Z'|'a'..='z'|'_'] } / expected!("Alphanumeric") - rule DIGIT() = quiet!{ ['0'..='9'] } / expected!("Digit") - rule _ = WS()* - rule __ = WS()+ - - // Special matching rule: Any Binary Operator - rule any_operator() -> Operator - = "++" { Operator::Concat } - / ".." { Operator::Range } - / "+" { Operator::Plus } - / "-" { Operator::Minus } - / "*" { Operator::Product } - / "/" { Operator::Quotient } - / "**" { Operator::Power } - / "%" { Operator::Remainder } - / ">=<" { Operator::Funnel } - / ">=" { Operator::GreaterEqual } - / "<=>" { Operator::Starship } - / "<=" { Operator::LessEqual } - / "<>" { Operator::NotEqual } - / "==" { Operator::Equal } - / "and" { Operator::LogicAnd } - / "or" { Operator::LogicOr } - / "xor" { Operator::LogicXOr } - / "nand" { Operator::LogicNand } - / "nor" { Operator::LogicNor } - / "<~>" { Operator::Elastic } - / "<~" { Operator::ElasticLeft } - / "~>" { Operator::ElasticRight } - / "<:>" { Operator::PinguBoth } - / "<:" { Operator::PinguLeft } - / ":>" { Operator::PinguRight } - / "<-|->" { Operator::ArrowStandBoth } - / "<-|" { Operator::ArrowStandLeft } - / "|->" { Operator::ArrowStandRight } - / "<->" { Operator::BothWays } - / "<-" { Operator::ArrowLeft } - / "->" { Operator::ArrowRight } - / "<|>" { Operator::Disjoin } - / "<|" { Operator::PipeLeft } - / "|>" { Operator::PipeRight } - / "?>" { Operator::AskRight } - / "" { Operator::Greater } - / "<" { Operator::Less } - } -} - -pub type ParseResult = Result>; - -#[derive(Debug, Clone)] -pub struct Script { - pub statements: Vec, -} -impl Script { - pub fn parse(input: I) -> ParseResult - where - I: Into, - { - let fragment: String = input.into(); - saturnus_script::script(&fragment) - } - - // This function is used only in tests for now. - #[cfg(test)] - pub fn parse_expression( - input: I, - ) -> Result> - where - I: Into, - { - let fragment: String = input.into(); - saturnus_script::expression(&fragment) - } -} +pub use grammar::{ParseResult, Script}; diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..d5f641f --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,15 @@ +pub struct RuntimeHost; + +#[derive(Debug, Clone)] +pub struct RuntimeError(rlua::Error); + +impl RuntimeHost { + pub fn evaluate(self, code: &String) -> Result<(), RuntimeError> { + let rt = rlua::Lua::new(); + rt.context(move |ctx| -> rlua::Result<()> { + ctx.load(&code).eval()?; + Ok(()) + }) + .map_err(|err| RuntimeError(err)) + } +}