update
This commit is contained in:
parent
d19381964f
commit
3e7bb062aa
@ -8,12 +8,13 @@ use visitor::ConstantFolder;
|
||||
|
||||
fn main() {
|
||||
// let contents = fs::read_to_string("main_android_org.lua")
|
||||
let contents = fs::read_to_string("small.lua")
|
||||
// let contents = fs::read_to_string("small.lua")
|
||||
let contents = fs::read_to_string("very_small.lua")
|
||||
.expect("Something went wrong reading the file");
|
||||
|
||||
let tree = parse(&contents).expect("Parsing gone wrong");
|
||||
let tree = parse(&contents).expect("Parsing lua gone wrong");
|
||||
|
||||
let mut folder = ConstantFolder{};
|
||||
let mut folder = ConstantFolder::new();
|
||||
folder.visit(tree);
|
||||
|
||||
// let nodes = tree.nodes_mut();
|
||||
|
@ -1,69 +1,75 @@
|
||||
mod expression_reducer;
|
||||
|
||||
use full_moon::ast::*;
|
||||
|
||||
use crate::visitor::Visitor;
|
||||
|
||||
struct VariableValue {}
|
||||
|
||||
// value: Literal | String | Module | ReducedExpression
|
||||
struct Variable {
|
||||
name: String,
|
||||
value: VariableValue
|
||||
fn is_standard_library(name: String) -> bool {
|
||||
let standard_library_names: Vec<&str> = vec![
|
||||
"math", "string", "table", "bit"
|
||||
];
|
||||
return false
|
||||
}
|
||||
|
||||
pub struct ConstantFolder {
|
||||
locals: Vec<Variable>
|
||||
locals: Vec<expression_reducer::Variable>
|
||||
}
|
||||
|
||||
impl ConstantFolder {
|
||||
/// Return `Name` struct for a valid standard library name/function
|
||||
///
|
||||
/// Lua has standard library modules like `math.max`, `math.min`
|
||||
/// This function returns a Name if the given argument are valid
|
||||
/// This is to disambiguate between Var.Name
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `module` - Standard library model
|
||||
/// * `name` - The module
|
||||
///
|
||||
fn name_from_standard_library(name: &str) -> Option<&str> {
|
||||
let standard_library_names = vec![
|
||||
"math", "string", "table", "bit"
|
||||
];
|
||||
if standard_library_names.contains(&name) {
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
pub fn new() -> Self {
|
||||
ConstantFolder{
|
||||
locals: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expression<'a>(&mut self, expression: Expression<'a>) {
|
||||
match expression {
|
||||
Expression::BinaryOperator {
|
||||
lhs,
|
||||
rhs,
|
||||
binop
|
||||
} => {
|
||||
fn register_local(&mut self, name: Option<String>, value: Option<Expression>) -> bool {
|
||||
if name.is_none() {
|
||||
return false
|
||||
}
|
||||
Expression::Parentheses {
|
||||
contained,
|
||||
expression,
|
||||
} => {}
|
||||
Expression::UnaryOperator {
|
||||
unop,
|
||||
expression,
|
||||
} => {}
|
||||
Expression::Value {
|
||||
value
|
||||
} => {}
|
||||
_ => {}
|
||||
self.locals.push(
|
||||
expression_reducer::Variable::with_value(
|
||||
name.unwrap(), value, &self.locals
|
||||
));
|
||||
true
|
||||
}
|
||||
|
||||
fn assign(&mut self, name: Option<String>, value: Option<Expression>) -> bool {
|
||||
if name.is_none() {
|
||||
return false
|
||||
}
|
||||
let var = expression_reducer::Variable::with_value(
|
||||
name.unwrap(), value, &self.locals
|
||||
);
|
||||
{
|
||||
let mut iter = self.locals.iter_mut();
|
||||
let it = iter.find(|it| it.name() == var.name());
|
||||
it.map(|old| old.assign_value(var));
|
||||
};
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor for ConstantFolder {
|
||||
fn visit_assignment<'a>(&mut self, assignment: &Assignment<'a>) {
|
||||
println!("visit assignment {}", assignment);
|
||||
let mut names = assignment.variables().iter();
|
||||
let mut expressions = assignment.expressions().iter();
|
||||
|
||||
loop {
|
||||
let name = names.next().map(|x| match x {
|
||||
Var::Expression(var_expr) => {
|
||||
var_expr.to_string()
|
||||
}
|
||||
Var::Name(n) => n.token().to_string(),
|
||||
_ => {
|
||||
"".to_string()
|
||||
}
|
||||
});
|
||||
let expr = expressions.next().map(|x| x.clone() /* reduced */);
|
||||
if !self.assign(name, expr) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_do<'a>(&mut self, do_: &Do<'a>) {
|
||||
@ -87,7 +93,16 @@ impl Visitor for ConstantFolder {
|
||||
}
|
||||
|
||||
fn visit_local_assignment<'a>(&mut self, local_assignment: &LocalAssignment<'a>) {
|
||||
println!("visit local_assignment {}", local_assignment);
|
||||
let mut names = local_assignment.names().iter();
|
||||
let mut expressions = local_assignment.expressions().iter();
|
||||
|
||||
loop {
|
||||
let name = names.next().map(|x| x.token().to_string());
|
||||
let expr = expressions.next().map(|x| x.clone() /* reduced */);
|
||||
if !self.register_local(name, expr) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_local_function<'a>(&mut self, local_function: &LocalFunction<'a>) {
|
||||
|
365
src/visitor/constant_folder/expression_reducer.rs
Normal file
365
src/visitor/constant_folder/expression_reducer.rs
Normal file
@ -0,0 +1,365 @@
|
||||
//! A compile-time expression folder (constant propagation)
|
||||
//!
|
||||
//! To do constant folding, we must be able to reduce expression at
|
||||
//! the compile-time. We give each Variable an Expression and reduce it.
|
||||
//! If there is some expression, the variable value is the expression reduced.
|
||||
//! Else the variable has undefined value at compile-time.
|
||||
//!
|
||||
//! We try to reduce
|
||||
//! ```lua
|
||||
//! L41_1 = L37_1
|
||||
//! L42_1 = 20
|
||||
//! L43_1 = 76
|
||||
//! L44_1 = 201
|
||||
//! L45_1 = 132
|
||||
//! L46_1 = 98
|
||||
//! L47_1 = 93
|
||||
//! L41_1 = (L41_1(L42_1, L43_1, L44_1, L45_1, L46_1, L47_1))
|
||||
//! ```
|
||||
//! into
|
||||
//! ```lua
|
||||
//! L41_1 = L37_1
|
||||
//! L42_1 = 20
|
||||
//! L43_1 = 76
|
||||
//! L44_1 = 201
|
||||
//! L45_1 = 132
|
||||
//! L46_1 = 98
|
||||
//! L47_1 = 93
|
||||
//! L41_1 = L37_1(20, 76, 201, 132, 98, 93)
|
||||
//! ```
|
||||
//!
|
||||
//! We reduce expression with a list of local variables.
|
||||
//!
|
||||
//! There are many cases where reduction fail to complete, these expression
|
||||
//! won't reduced and kept the same.
|
||||
//!
|
||||
//! - Undefined or parameter
|
||||
//! - Nil initialized
|
||||
//! - Set {}
|
||||
//! - For counter
|
||||
//! - Function call
|
||||
//! - Lambda function
|
||||
//!
|
||||
//! Every expression containing these won't be reduced furthur. Thus:
|
||||
//! ```lua
|
||||
//! L1 = 0
|
||||
//! L2 = nil
|
||||
//! L3 = L1 + L2
|
||||
//! ```
|
||||
//! reduces into
|
||||
//! ```lua
|
||||
//! L1 = 0
|
||||
//! L2 = nil
|
||||
//! L3 = 0 + L2
|
||||
//! ```
|
||||
//!
|
||||
//! Keep in mind that we do expression folding only, dead code elimination
|
||||
//! is done through another pass
|
||||
|
||||
use full_moon::ast;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VariableType {
|
||||
Number,
|
||||
String,
|
||||
Boolean,
|
||||
// Function(Vec<VariableType>, Vec<VariableType>), // name, param, result
|
||||
// Array(Box<VariableType>),
|
||||
// Dict(Box<VariableType>),
|
||||
// Module(String, String),
|
||||
Function,
|
||||
Array,
|
||||
Dict,
|
||||
Module,
|
||||
Nil,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Variable {
|
||||
n: String, // name
|
||||
t: VariableType,
|
||||
v: Option<Expression>
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
pub fn with_value(name: String, expr: Option<ast::Expression>, locals: &Vec<Variable>) -> Self {
|
||||
let v = Expression::reduce(expr, locals);
|
||||
let t = v.as_ref().map_or(VariableType::Nil, |x| x.assume_type(locals));
|
||||
println!("variable_with {} = {:?}", name, v);
|
||||
Self {
|
||||
n: name,
|
||||
v: v,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.n.clone()
|
||||
}
|
||||
|
||||
pub fn assign_value(&mut self, var: Self) {
|
||||
println!("variable_assign {} = {:?}", self.n, var.v);
|
||||
self.v = var.v;
|
||||
self.t = var.t;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expression {
|
||||
Number(f64),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
Var(String),
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn reduce(expr: Option<ast::Expression>, locals: &Vec<Variable>) -> Option<Self> {
|
||||
if expr.is_none() {
|
||||
return None
|
||||
}
|
||||
let expr = expr.unwrap();
|
||||
Some(reduce_expression(expr, locals))
|
||||
}
|
||||
|
||||
pub fn assume_type(&self, locals: &Vec<Variable>) -> VariableType {
|
||||
match self {
|
||||
Expression::Number(_) => { VariableType::Number }
|
||||
Expression::Boolean(_) => { VariableType::Boolean }
|
||||
Expression::String(_) => { VariableType::String }
|
||||
Expression::Var(_) => {
|
||||
VariableType::Number
|
||||
}
|
||||
_ => {
|
||||
VariableType::Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_expression(expr: ast::Expression, locals: &Vec<Variable>) -> Expression {
|
||||
match expr {
|
||||
ast::Expression::BinaryOperator {
|
||||
lhs,
|
||||
rhs,
|
||||
binop
|
||||
} => {
|
||||
let lhs_ = reduce_expression(*lhs, locals);
|
||||
let rhs_ = reduce_expression(*rhs, locals);
|
||||
apply_binary_operator(lhs_, rhs_, binop)
|
||||
}
|
||||
ast::Expression::Parentheses {
|
||||
expression,
|
||||
..
|
||||
} => {
|
||||
reduce_expression(*expression, locals)
|
||||
}
|
||||
ast::Expression::UnaryOperator {
|
||||
unop,
|
||||
expression,
|
||||
} => {
|
||||
let expression_ = reduce_expression(*expression, locals);
|
||||
apply_unary_operator(expression_, unop)
|
||||
}
|
||||
ast::Expression::Value {
|
||||
value
|
||||
} => {
|
||||
reduce_expression_value(*value, locals)
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_expression_value(value: ast::Value, locals: &Vec<Variable>) -> Expression {
|
||||
match value {
|
||||
ast::Value::Function(function) => {
|
||||
panic!("reduce Expression::Function is unsupported");
|
||||
}
|
||||
ast::Value::FunctionCall(function_call) => {
|
||||
panic!("reduce Expression::FunctionCall is unsupported");
|
||||
}
|
||||
ast::Value::TableConstructor(table_constructor) => {
|
||||
panic!("reduce Expression::TableConstructor is unsupported");
|
||||
}
|
||||
ast::Value::Number(number) => {
|
||||
let num = number.token().to_string();
|
||||
Expression::Number(num.parse::<f64>().unwrap())
|
||||
}
|
||||
ast::Value::ParenthesesExpression(parentheses_expression) => {
|
||||
reduce_expression(parentheses_expression, locals)
|
||||
}
|
||||
ast::Value::String(string) => {
|
||||
Expression::String(string.token().to_string())
|
||||
}
|
||||
ast::Value::Symbol(symbol) => {
|
||||
panic!("reduce Expression::Symbol is unsupported");
|
||||
}
|
||||
ast::Value::Var(var) => {
|
||||
reduce_expression_value_var(var, locals)
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_expression_value_var(var: ast::Var, locals: &Vec<Variable>) -> Expression {
|
||||
match var {
|
||||
ast::Var::Expression(expr) => {
|
||||
panic!("reduce Var::Expression is unsupported")
|
||||
}
|
||||
ast::Var::Name(name) => {
|
||||
let local_var = locals.iter().find(|it| it.name() == name.token().to_string());
|
||||
match local_var {
|
||||
None => {
|
||||
panic!("local variable not found")
|
||||
}
|
||||
Some(v) => {
|
||||
v.v.as_ref().unwrap().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_binary_operator(lhs: Expression, rhs: Expression, binop: ast::BinOp) -> Expression {
|
||||
match binop {
|
||||
ast::BinOp::And(_) => {
|
||||
panic!("Binary And unimplemented")
|
||||
}
|
||||
ast::BinOp::Caret(_) => {
|
||||
panic!("Binary Caret unimplemented")
|
||||
}
|
||||
ast::BinOp::GreaterThan(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Boolean(lhs_ > rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary GreaterThan wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::GreaterThanEqual(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Boolean(lhs_ >= rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary GreaterThanEqual wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::LessThan(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Boolean(lhs_ < rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary LessThan wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::LessThanEqual(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Boolean(lhs_ <= rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary LessThanEqual wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::Minus(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Number(lhs_ - rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary Minus wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::Or(_) => {
|
||||
panic!("Binary Or unimplemented")
|
||||
}
|
||||
ast::BinOp::Percent(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Number(lhs_ % rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary Percent wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::Plus(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Number(lhs_ + rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary Plus wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::Slash(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Number(lhs_ / rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary Slash wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::Star(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Number(lhs_ * rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary Star wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::BinOp::TildeEqual(_) => {
|
||||
panic!("Binary TildeEqual unimplemented")
|
||||
}
|
||||
ast::BinOp::TwoDots(_) => {
|
||||
panic!("Binary TwoDots unimplemented")
|
||||
}
|
||||
ast::BinOp::TwoEqual(_) => {
|
||||
match (lhs, rhs) {
|
||||
(Expression::Number(lhs_), Expression::Number(rhs_)) => {
|
||||
Expression::Boolean(lhs_ == rhs_)
|
||||
}
|
||||
_ => {
|
||||
panic!("Binary TwoEqual wrong argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_unary_operator(expr: Expression, unop: ast::UnOp) -> Expression {
|
||||
match unop {
|
||||
ast::UnOp::Not(_) => {
|
||||
if let Expression::Boolean(e) = expr {
|
||||
Expression::Boolean(!e)
|
||||
} else {
|
||||
panic!("Unary Not applied to non Number")
|
||||
}
|
||||
}
|
||||
ast::UnOp::Minus(_) => {
|
||||
if let Expression::Number(e) = expr {
|
||||
Expression::Number(-e)
|
||||
} else {
|
||||
panic!("Unary Minus applied to non Number")
|
||||
}
|
||||
}
|
||||
ast::UnOp::Hash(_) => {
|
||||
panic!("Unary Hash unimplemented")
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
144
src/visitor/constant_folder/variable_.rs
Normal file
144
src/visitor/constant_folder/variable_.rs
Normal file
@ -0,0 +1,144 @@
|
||||
/// A compile-time expression folder (constant propagation)
|
||||
///
|
||||
/// To do constant folding, we must be able to reduce expression at
|
||||
/// the compile-time. We give each Variable an Expression and reduce it.
|
||||
/// If there is some expression, the variable value is the expression reduced.
|
||||
/// Else the variable has undefined value at compile-time.
|
||||
///
|
||||
/// We try to reduce
|
||||
/// ```lua
|
||||
/// L41_1 = L37_1
|
||||
/// L42_1 = 20
|
||||
/// L43_1 = 76
|
||||
/// L44_1 = 201
|
||||
/// L45_1 = 132
|
||||
/// L46_1 = 98
|
||||
/// L47_1 = 93
|
||||
/// L41_1 = (L41_1(L42_1, L43_1, L44_1, L45_1, L46_1, L47_1))
|
||||
/// ```
|
||||
/// into
|
||||
/// ```lua
|
||||
/// L41_1 = L37_1
|
||||
/// L42_1 = 20
|
||||
/// L43_1 = 76
|
||||
/// L44_1 = 201
|
||||
/// L45_1 = 132
|
||||
/// L46_1 = 98
|
||||
/// L47_1 = 93
|
||||
/// L41_1 = L37_1(20, 76, 201, 132, 98, 93)
|
||||
/// ```
|
||||
///
|
||||
/// We reduce expression with a list of local variables.
|
||||
///
|
||||
/// There are many cases where reduction fail to complete, these expression
|
||||
/// won't reduced and kept the same.
|
||||
///
|
||||
/// - Undefined or parameter
|
||||
/// - Nil initialized
|
||||
/// - Set {}
|
||||
/// - For counter
|
||||
///
|
||||
/// Every expression containing these won't be reduced furthur. Thus:
|
||||
/// ```lua
|
||||
/// L1 = 0
|
||||
/// L2 = nil
|
||||
/// L3 = L1 + L2
|
||||
/// ```
|
||||
/// reduces into
|
||||
/// ```lua
|
||||
/// L1 = 0
|
||||
/// L2 = nil
|
||||
/// L3 = 0 + L2
|
||||
/// ```
|
||||
///
|
||||
/// Keep in mind that we do expression folding only, dead code elimination
|
||||
/// is done through another pass
|
||||
use std::ops;
|
||||
|
||||
enum BinOp {
|
||||
Plus,
|
||||
Minus,
|
||||
Modulus,
|
||||
Times,
|
||||
Dividend,
|
||||
Concat,
|
||||
}
|
||||
|
||||
enum CompareOp {
|
||||
Lt,
|
||||
Eq,
|
||||
Gt,
|
||||
Lte,
|
||||
Gte,
|
||||
Ne,
|
||||
}
|
||||
|
||||
/// Types of expression
|
||||
enum Expression {
|
||||
/// a number (int)
|
||||
Literal(i64),
|
||||
/// a string literal
|
||||
String(String),
|
||||
/// special name of library module.function (math.min)
|
||||
Module(String, Vec<String>),
|
||||
/// variable referencing
|
||||
Var(Variable),
|
||||
/// Binary operation between expression
|
||||
BinOp(Box<Expression>, Box<Expression>, BinOp),
|
||||
/// Index into array
|
||||
Index(Box<Expression>, Box<Expression>),
|
||||
/// Comparision expression (1 < 2)
|
||||
Comparision(Box<Expression>, Box<Expression>, CompareOp),
|
||||
/// Function call
|
||||
FunctionCall(),
|
||||
}
|
||||
|
||||
/// Variable has a name and the expression attached to it
|
||||
pub struct Variable {
|
||||
name: String,
|
||||
value: Option<Box<Expression>>,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
}
|
||||
|
||||
impl ops::Add for Expression {
|
||||
type Output = Self;
|
||||
fn add(self, other: Self) -> Self {
|
||||
|
||||
}
|
||||
}
|
||||
impl ops::Sub for Expression {}
|
||||
impl ops::Mul for Expression {}
|
||||
impl ops::Div for Expression {}
|
||||
impl ops::Rem for Expression {}
|
||||
|
||||
pub fn reduce_expression(expr: Expression, locals: Vec<Variable>) -> Expression {
|
||||
match expr {
|
||||
Expression::BinOp(lhs_, rhs_, op) => {
|
||||
let lhs = reduce_expression(*lhs_, locals);
|
||||
let rhs = reduce_expression(*rhs_, locals);
|
||||
match op {
|
||||
Plus => {
|
||||
lhs + rhs
|
||||
}
|
||||
Minus => {
|
||||
lhs - rhs
|
||||
}
|
||||
Modulus => {
|
||||
lhs % rhs
|
||||
}
|
||||
Times => {
|
||||
lhs * rhs
|
||||
}
|
||||
Dividend => {
|
||||
lhs / rhs
|
||||
}
|
||||
Concat => {
|
||||
lhs.concat(rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => expr
|
||||
}
|
||||
}
|
3
very_small.lua
Normal file
3
very_small.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local L1_1, L1_2
|
||||
L1_2 = 2
|
||||
L1_1 = 1 + L1_2
|
Loading…
Reference in New Issue
Block a user