This commit is contained in:
nganhkhoa 2021-05-18 17:40:26 +07:00
parent d19381964f
commit 3e7bb062aa
5 changed files with 578 additions and 50 deletions

View File

@ -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();

View File

@ -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
} => {
}
Expression::Parentheses {
contained,
expression,
} => {}
Expression::UnaryOperator {
unop,
expression,
} => {}
Expression::Value {
value
} => {}
_ => {}
fn register_local(&mut self, name: Option<String>, value: Option<Expression>) -> bool {
if name.is_none() {
return false
}
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>) {

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

View 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
View File

@ -0,0 +1,3 @@
local L1_1, L1_2
L1_2 = 2
L1_1 = 1 + L1_2