feat: Implement purity inference for optimization
This commit introduces purity inference to the compiler's optimizer. This allows for more aggressive constant folding and dead code elimination by tracking which functions and global variables are free of side effects. Key changes include: - Added `global_purity` field to `Optimizer` and `Environment`. - Modified `Optimizer::is_pure` to recursively determine if an AST node represents a pure computation. - Introduced `Optimizer::try_fold_pure` to replace the old `try_fold_intrinsic`, enabling folding of pure function calls with constant arguments. - Updated `Environment::register_native` and `Environment::register_constant` to optionally record purity. - Added purity flags to several built-in functions in `core.rs` and `datetime.rs`. - A new example `optimizer_purity.myc` demonstrates the new feature.
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
(do
|
||||||
|
(def PI 3.1415)
|
||||||
|
(def area (fn [x] (* PI x)))
|
||||||
|
(area 10)
|
||||||
|
)
|
||||||
+145
-52
@@ -12,11 +12,12 @@ pub struct Optimizer {
|
|||||||
pub level: u32,
|
pub level: u32,
|
||||||
max_passes: usize,
|
max_passes: usize,
|
||||||
pub globals: Option<Rc<RefCell<Vec<Value>>>>,
|
pub globals: Option<Rc<RefCell<Vec<Value>>>>,
|
||||||
|
pub global_purity: Option<Rc<RefCell<HashMap<u32, bool>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Optimizer {
|
impl Optimizer {
|
||||||
pub fn new(level: u32) -> Self {
|
pub fn new(level: u32) -> Self {
|
||||||
Self { level, max_passes: 5, globals: None }
|
Self { level, max_passes: 5, globals: None, global_purity: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
pub fn with_globals(mut self, globals: Rc<RefCell<Vec<Value>>>) -> Self {
|
||||||
@@ -24,6 +25,11 @@ impl Optimizer {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_purity(mut self, purity: Rc<RefCell<HashMap<u32, bool>>>) -> Self {
|
||||||
|
self.global_purity = Some(purity);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn optimize(&self, node: TypedNode) -> TypedNode {
|
pub fn optimize(&self, node: TypedNode) -> TypedNode {
|
||||||
if self.level == 0 {
|
if self.level == 0 {
|
||||||
return node;
|
return node;
|
||||||
@@ -119,7 +125,7 @@ impl Optimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(folded) = self.try_fold_intrinsic(&callee, &args) {
|
if let Some(folded) = self.try_fold_pure(&callee, &args) {
|
||||||
return folded;
|
return folded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,14 +199,14 @@ impl Optimizer {
|
|||||||
|
|
||||||
let removable = match &e.kind {
|
let removable = match &e.kind {
|
||||||
BoundKind::DefLocal { slot, .. } | BoundKind::Set { addr: Address::Local(slot), .. } => {
|
BoundKind::DefLocal { slot, .. } | BoundKind::Set { addr: Address::Local(slot), .. } => {
|
||||||
!sub.used_slots.contains(slot) && !sub.captured_slots.contains(slot) && self.is_side_effect_free(&e)
|
!sub.used_slots.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(&e)
|
||||||
}
|
}
|
||||||
BoundKind::DefGlobal { global_index, .. } => {
|
BoundKind::DefGlobal { global_index, .. } => {
|
||||||
// Phase 4: DCE for Globals
|
// Phase 4: DCE for Globals
|
||||||
// A global can be removed if it was only used for inlining in this script.
|
// A global can be removed if it was only used for inlining in this script.
|
||||||
!sub.used_globals.contains(global_index) && self.is_side_effect_free(&e)
|
!sub.used_globals.contains(global_index) && self.is_pure(&e)
|
||||||
}
|
}
|
||||||
_ => self.is_side_effect_free(&e),
|
_ => self.is_pure(&e),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !removable {
|
if !removable {
|
||||||
@@ -283,6 +289,14 @@ impl Optimizer {
|
|||||||
if let BoundKind::Constant(val) = &value.kind {
|
if let BoundKind::Constant(val) = &value.kind {
|
||||||
sub.add_global(global_index, val.clone());
|
sub.add_global(global_index, val.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purity Inference: Globals are pure if their value is pure
|
||||||
|
if self.is_pure(&value) {
|
||||||
|
if let Some(purity_rc) = &self.global_purity {
|
||||||
|
purity_rc.borrow_mut().insert(global_index, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(BoundKind::DefGlobal { name, global_index, value }, node.ty)
|
(BoundKind::DefGlobal { name, global_index, value }, node.ty)
|
||||||
},
|
},
|
||||||
BoundKind::Set { addr, value } => {
|
BoundKind::Set { addr, value } => {
|
||||||
@@ -339,21 +353,118 @@ impl Optimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_side_effect_free(&self, node: &TypedNode) -> bool {
|
fn is_pure(&self, node: &TypedNode) -> bool {
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
BoundKind::Constant(_) | BoundKind::Get { .. } | BoundKind::Parameter { .. } | BoundKind::Nop | BoundKind::Lambda { .. } => true,
|
BoundKind::Constant(_) | BoundKind::Parameter { .. } | BoundKind::Nop | BoundKind::Lambda { .. } => true,
|
||||||
BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_side_effect_free(e)),
|
BoundKind::Get { addr, .. } => {
|
||||||
BoundKind::Record { fields } => fields.iter().all(|(k, v)| self.is_side_effect_free(k) && self.is_side_effect_free(v)),
|
match addr {
|
||||||
BoundKind::If { cond, then_br, else_br } => {
|
Address::Global(idx) => {
|
||||||
self.is_side_effect_free(cond) && self.is_side_effect_free(then_br) && else_br.as_ref().is_none_or(|e| self.is_side_effect_free(e))
|
if let Some(purity_rc) = &self.global_purity {
|
||||||
|
*purity_rc.borrow().get(idx).unwrap_or(&false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => true, // Locals and Upvalues are pure in terms of side-effects
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_side_effect_free(e)),
|
BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_pure(e)),
|
||||||
BoundKind::Expansion { bound_expanded, .. } => self.is_side_effect_free(bound_expanded),
|
BoundKind::Record { fields } => fields.iter().all(|(k, v)| self.is_pure(k) && self.is_pure(v)),
|
||||||
BoundKind::DefLocal { value, .. } | BoundKind::Set { value, .. } => self.is_side_effect_free(value),
|
BoundKind::If { cond, then_br, else_br } => {
|
||||||
_ => false, // Call and DefGlobal are considered impure
|
self.is_pure(cond) && self.is_pure(then_br) && else_br.as_ref().is_none_or(|e| self.is_pure(e))
|
||||||
|
}
|
||||||
|
BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_pure(e)),
|
||||||
|
BoundKind::Expansion { bound_expanded, .. } => self.is_pure(bound_expanded),
|
||||||
|
BoundKind::DefLocal { value, .. } | BoundKind::Set { addr: Address::Local(_), value } => self.is_pure(value),
|
||||||
|
|
||||||
|
BoundKind::Call { callee, args } => {
|
||||||
|
// A call is pure if the callee is a known pure function and args are pure
|
||||||
|
let callee_pure = match &callee.kind {
|
||||||
|
BoundKind::Get { addr: Address::Global(idx), .. } => {
|
||||||
|
if let Some(purity_rc) = &self.global_purity {
|
||||||
|
*purity_rc.borrow().get(idx).unwrap_or(&false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BoundKind::Constant(Value::Function(_)) => true, // Native fns are checked at registry
|
||||||
|
BoundKind::Constant(Value::Object(obj)) => {
|
||||||
|
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
||||||
|
self.is_pure(&closure.function_node)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
callee_pure && self.is_pure(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => false, // DefGlobal and Set(Global) are impure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option<TypedNode> {
|
||||||
|
// 1. Check if the whole call is pure
|
||||||
|
// Create a temporary call node to check purity
|
||||||
|
let temp_call = Node {
|
||||||
|
identity: callee.identity.clone(),
|
||||||
|
ty: StaticType::Any,
|
||||||
|
kind: BoundKind::Call { callee: Box::new(callee.clone()), args: Box::new(args.clone()) }
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.is_pure(&temp_call) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Extract constant arguments
|
||||||
|
let mut arg_nodes = Vec::new();
|
||||||
|
self.flatten_typed_tuple(args, &mut arg_nodes);
|
||||||
|
|
||||||
|
let mut arg_values = Vec::with_capacity(arg_nodes.len());
|
||||||
|
for node in arg_nodes {
|
||||||
|
if let BoundKind::Constant(val) = node.kind {
|
||||||
|
arg_values.push(val);
|
||||||
|
} else {
|
||||||
|
return None; // All arguments must be constants for folding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Resolve the function to execute
|
||||||
|
let func_val = match &callee.kind {
|
||||||
|
BoundKind::Get { addr: Address::Global(idx), .. } => {
|
||||||
|
self.globals.as_ref()?.borrow().get(*idx as usize)?.clone()
|
||||||
|
}
|
||||||
|
BoundKind::Constant(val) => val.clone(),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Execute
|
||||||
|
let result = match func_val {
|
||||||
|
Value::Function(f) => f(arg_values),
|
||||||
|
Value::Object(obj) => {
|
||||||
|
if let Some(_closure) = obj.as_any().downcast_ref::<Closure>() {
|
||||||
|
// For closures, we'd need a VM to execute.
|
||||||
|
// However, if it's pure and we are in the Optimizer,
|
||||||
|
// we might want to avoid full VM spin-up here if possible,
|
||||||
|
// or use a shared VM instance.
|
||||||
|
// For now, we only fold Native Functions.
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. Return new constant node
|
||||||
|
Some(Node {
|
||||||
|
identity: callee.identity.clone(),
|
||||||
|
ty: result.static_type(),
|
||||||
|
kind: BoundKind::Constant(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option<TypedNode> {
|
fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option<TypedNode> {
|
||||||
if self.contains_def_local(&body) {
|
if self.contains_def_local(&body) {
|
||||||
return None;
|
return None;
|
||||||
@@ -405,42 +516,6 @@ impl Optimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_intrinsic(&self, callee: &TypedNode, args: &TypedNode) -> Option<TypedNode> {
|
|
||||||
if let BoundKind::Get { name, .. } = &callee.kind
|
|
||||||
&& let BoundKind::Tuple { elements } = &args.kind
|
|
||||||
&& elements.len() == 2
|
|
||||||
{
|
|
||||||
let val_a = self.get_const_int(&elements[0]);
|
|
||||||
let val_b = self.get_const_int(&elements[1]);
|
|
||||||
|
|
||||||
if let (Some(a), Some(b)) = (val_a, val_b) {
|
|
||||||
let res = match &*name.name {
|
|
||||||
"+" => Some(Value::Int(a + b)),
|
|
||||||
"-" => Some(Value::Int(a - b)),
|
|
||||||
"*" => Some(Value::Int(a * b)),
|
|
||||||
"/" if b != 0 => Some(Value::Int(a / b)),
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
if let Some(val) = res {
|
|
||||||
return Some(Node {
|
|
||||||
identity: callee.identity.clone(),
|
|
||||||
ty: StaticType::Int,
|
|
||||||
kind: BoundKind::Constant(val),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_const_int(&self, node: &TypedNode) -> Option<i64> {
|
|
||||||
match &node.kind {
|
|
||||||
BoundKind::Constant(Value::Int(i)) => Some(*i),
|
|
||||||
BoundKind::Tuple { elements } if elements.len() == 1 => self.get_const_int(&elements[0]),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_def_local(&self, node: &TypedNode) -> bool {
|
fn contains_def_local(&self, node: &TypedNode) -> bool {
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
BoundKind::DefLocal { .. } => true,
|
BoundKind::DefLocal { .. } => true,
|
||||||
@@ -571,7 +646,6 @@ impl SubstitutionMap {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use crate::ast::environment::Environment;
|
use crate::ast::environment::Environment;
|
||||||
|
|
||||||
fn get_optimized_dump(source: &str, level: u32) -> String {
|
fn get_optimized_dump(source: &str, level: u32) -> String {
|
||||||
@@ -586,6 +660,25 @@ mod tests {
|
|||||||
insta::assert_snapshot!(dump);
|
insta::assert_snapshot!(dump);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_opt_folding_complex() {
|
||||||
|
// Test float folding
|
||||||
|
let dump_float = get_optimized_dump("(+ 1.5 2.5)", 2);
|
||||||
|
assert!(dump_float.contains("Constant: 4"));
|
||||||
|
|
||||||
|
// Test text folding
|
||||||
|
let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", 2);
|
||||||
|
assert!(dump_text.contains("Constant: \"hello world\""));
|
||||||
|
|
||||||
|
// Test date folding
|
||||||
|
let dump_date = get_optimized_dump("(date \"2023-01-01\")", 2);
|
||||||
|
assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#"));
|
||||||
|
|
||||||
|
// Test comparison folding
|
||||||
|
let dump_cmp = get_optimized_dump("(> 10 5)", 2);
|
||||||
|
assert!(dump_cmp.contains("Constant: true"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_opt_local_inlining() {
|
fn test_opt_local_inlining() {
|
||||||
let source = "(fn [] (do (def x 10) (+ x 5)))";
|
let source = "(fn [] (do (def x 10) (+ x 5)))";
|
||||||
|
|||||||
+14
-2
@@ -21,6 +21,7 @@ use crate::ast::rtl::intrinsics;
|
|||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
|
pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
|
||||||
pub global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
|
pub global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
|
||||||
|
pub global_purity: Rc<RefCell<HashMap<u32, bool>>>,
|
||||||
pub global_values: Rc<RefCell<Vec<Value>>>,
|
pub global_values: Rc<RefCell<Vec<Value>>>,
|
||||||
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
|
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
|
||||||
pub monomorph_cache: Rc<RefCell<MonoCache>>,
|
pub monomorph_cache: Rc<RefCell<MonoCache>>,
|
||||||
@@ -85,6 +86,7 @@ impl Environment {
|
|||||||
let env = Self {
|
let env = Self {
|
||||||
global_names: Rc::new(RefCell::new(HashMap::new())),
|
global_names: Rc::new(RefCell::new(HashMap::new())),
|
||||||
global_types: Rc::new(RefCell::new(HashMap::new())),
|
global_types: Rc::new(RefCell::new(HashMap::new())),
|
||||||
|
global_purity: Rc::new(RefCell::new(HashMap::new())),
|
||||||
global_values: Rc::new(RefCell::new(Vec::new())),
|
global_values: Rc::new(RefCell::new(Vec::new())),
|
||||||
function_registry: Rc::new(RefCell::new(HashMap::new())),
|
function_registry: Rc::new(RefCell::new(HashMap::new())),
|
||||||
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
|
monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
|
||||||
@@ -112,15 +114,18 @@ impl Environment {
|
|||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
ty: StaticType,
|
ty: StaticType,
|
||||||
|
is_pure: bool,
|
||||||
func: impl Fn(Vec<Value>) -> Value + 'static,
|
func: impl Fn(Vec<Value>) -> Value + 'static,
|
||||||
) {
|
) {
|
||||||
let mut names = self.global_names.borrow_mut();
|
let mut names = self.global_names.borrow_mut();
|
||||||
let mut types = self.global_types.borrow_mut();
|
let mut types = self.global_types.borrow_mut();
|
||||||
let mut values = self.global_values.borrow_mut();
|
let mut values = self.global_values.borrow_mut();
|
||||||
|
let mut purity = self.global_purity.borrow_mut();
|
||||||
|
|
||||||
let idx = values.len() as u32;
|
let idx = values.len() as u32;
|
||||||
names.insert(Symbol::from(name), idx);
|
names.insert(Symbol::from(name), idx);
|
||||||
types.insert(idx, ty);
|
types.insert(idx, ty);
|
||||||
|
purity.insert(idx, is_pure);
|
||||||
values.push(Value::Function(Rc::new(func)));
|
values.push(Value::Function(Rc::new(func)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,10 +133,12 @@ impl Environment {
|
|||||||
let mut names = self.global_names.borrow_mut();
|
let mut names = self.global_names.borrow_mut();
|
||||||
let mut types = self.global_types.borrow_mut();
|
let mut types = self.global_types.borrow_mut();
|
||||||
let mut values = self.global_values.borrow_mut();
|
let mut values = self.global_values.borrow_mut();
|
||||||
|
let mut purity = self.global_purity.borrow_mut();
|
||||||
|
|
||||||
let idx = values.len() as u32;
|
let idx = values.len() as u32;
|
||||||
names.insert(Symbol::from(name), idx);
|
names.insert(Symbol::from(name), idx);
|
||||||
types.insert(idx, ty);
|
types.insert(idx, ty);
|
||||||
|
purity.insert(idx, true); // Constants are always pure
|
||||||
values.push(val);
|
values.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +189,9 @@ impl Environment {
|
|||||||
let specialized = self.specialize_node(node);
|
let specialized = self.specialize_node(node);
|
||||||
|
|
||||||
// 2. Optimize (Level 1: Cracking, Level 2: Collapsing)
|
// 2. Optimize (Level 1: Cracking, Level 2: Collapsing)
|
||||||
let optimizer = Optimizer::new(self.optimization_level).with_globals(self.global_values.clone());
|
let optimizer = Optimizer::new(self.optimization_level)
|
||||||
|
.with_globals(self.global_values.clone())
|
||||||
|
.with_purity(self.global_purity.clone());
|
||||||
let optimized = optimizer.optimize(specialized);
|
let optimized = optimizer.optimize(specialized);
|
||||||
|
|
||||||
// 3. TCO (Always performed, converts to ExecNode)
|
// 3. TCO (Always performed, converts to ExecNode)
|
||||||
@@ -200,6 +209,7 @@ impl Environment {
|
|||||||
let mono_cache = self.monomorph_cache.clone();
|
let mono_cache = self.monomorph_cache.clone();
|
||||||
let global_values = self.global_values.clone();
|
let global_values = self.global_values.clone();
|
||||||
let global_types = self.global_types.clone();
|
let global_types = self.global_types.clone();
|
||||||
|
let global_purity = self.global_purity.clone();
|
||||||
let opt_level = self.optimization_level;
|
let opt_level = self.optimization_level;
|
||||||
|
|
||||||
let compiler = Rc::new(
|
let compiler = Rc::new(
|
||||||
@@ -227,7 +237,9 @@ impl Environment {
|
|||||||
let specialized_ast = sub_specializer.specialize(retyped_ast);
|
let specialized_ast = sub_specializer.specialize(retyped_ast);
|
||||||
|
|
||||||
// 3. Optimize (Phase 2: Cracking & Folding)
|
// 3. Optimize (Phase 2: Cracking & Folding)
|
||||||
let optimizer = Optimizer::new(opt_level).with_globals(global_values.clone());
|
let optimizer = Optimizer::new(opt_level)
|
||||||
|
.with_globals(global_values.clone())
|
||||||
|
.with_purity(global_purity.clone());
|
||||||
let optimized_ast = optimizer.optimize(specialized_ast);
|
let optimized_ast = optimizer.optimize(specialized_ast);
|
||||||
|
|
||||||
// 4. TCO (converts to ExecNode)
|
// 4. TCO (converts to ExecNode)
|
||||||
|
|||||||
+18
-18
@@ -28,7 +28,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text },
|
Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
|
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
|
||||||
]);
|
]);
|
||||||
env.register_native("+", add_ty, |args| {
|
env.register_native("+", add_ty, true, |args| {
|
||||||
if args.len() == 2 {
|
if args.len() == 2 {
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
|
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
|
||||||
@@ -63,7 +63,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int },
|
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
|
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
|
||||||
]);
|
]);
|
||||||
env.register_native("-", sub_ty, |args| {
|
env.register_native("-", sub_ty, true, |args| {
|
||||||
if args.is_empty() { return Value::Void; }
|
if args.is_empty() { return Value::Void; }
|
||||||
if args.len() == 1 {
|
if args.len() == 1 {
|
||||||
return match args[0] {
|
return match args[0] {
|
||||||
@@ -101,7 +101,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
|
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
|
||||||
]);
|
]);
|
||||||
env.register_native("*", mul_ty, |args| {
|
env.register_native("*", mul_ty, true, |args| {
|
||||||
if args.len() == 2 {
|
if args.len() == 2 {
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
|
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
|
||||||
@@ -125,7 +125,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float },
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
|
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
|
||||||
]);
|
]);
|
||||||
env.register_native("/", div_ty, |args| {
|
env.register_native("/", div_ty, true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
|
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
|
||||||
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
|
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void };
|
||||||
@@ -136,7 +136,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
let int_div_ty = StaticType::Function(Box::new(
|
let int_div_ty = StaticType::Function(Box::new(
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
||||||
));
|
));
|
||||||
env.register_native("//", int_div_ty, |args| {
|
env.register_native("//", int_div_ty, true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => {
|
(Value::Int(a), Value::Int(b)) => {
|
||||||
@@ -152,7 +152,7 @@ fn register_arithmetic(env: &Environment) {
|
|||||||
let mod_ty = StaticType::Function(Box::new(
|
let mod_ty = StaticType::Function(Box::new(
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
||||||
));
|
));
|
||||||
env.register_native("%", mod_ty, |args| {
|
env.register_native("%", mod_ty, true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => {
|
(Value::Int(a), Value::Int(b)) => {
|
||||||
@@ -171,7 +171,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// --- Greater Than (>) ---
|
// --- Greater Than (>) ---
|
||||||
env.register_native(">", cmp_ty.clone(), |args| {
|
env.register_native(">", cmp_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
|
||||||
@@ -184,7 +184,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Less Than (<) ---
|
// --- Less Than (<) ---
|
||||||
env.register_native("<", cmp_ty.clone(), |args| {
|
env.register_native("<", cmp_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
|
||||||
@@ -197,7 +197,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Greater Or Equal (>=) ---
|
// --- Greater Or Equal (>=) ---
|
||||||
env.register_native(">=", cmp_ty.clone(), |args| {
|
env.register_native(">=", cmp_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a >= b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a >= b),
|
||||||
@@ -210,7 +210,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Less Or Equal (<=) ---
|
// --- Less Or Equal (<=) ---
|
||||||
env.register_native("<=", cmp_ty.clone(), |args| {
|
env.register_native("<=", cmp_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a <= b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a <= b),
|
||||||
@@ -226,7 +226,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
let eq_ty = StaticType::Function(Box::new(
|
let eq_ty = StaticType::Function(Box::new(
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool }
|
Signature { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool }
|
||||||
));
|
));
|
||||||
env.register_native("=", eq_ty.clone(), |args| {
|
env.register_native("=", eq_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
// Simple equality check.
|
// Simple equality check.
|
||||||
// Note: Floating point equality is tricky, but we follow standard behavior for now.
|
// Note: Floating point equality is tricky, but we follow standard behavior for now.
|
||||||
@@ -245,7 +245,7 @@ fn register_comparison(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Not Equal (<>) ---
|
// --- Not Equal (<>) ---
|
||||||
env.register_native("<>", eq_ty, |args| {
|
env.register_native("<>", eq_ty, true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Bool(a != b),
|
(Value::Int(a), Value::Int(b)) => Value::Bool(a != b),
|
||||||
@@ -268,7 +268,7 @@ fn register_logic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool },
|
Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int },
|
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int },
|
||||||
]);
|
]);
|
||||||
env.register_native("not", not_ty, |args| {
|
env.register_native("not", not_ty, true, |args| {
|
||||||
if args.len() != 1 { return Value::Void; }
|
if args.len() != 1 { return Value::Void; }
|
||||||
match &args[0] {
|
match &args[0] {
|
||||||
Value::Bool(b) => Value::Bool(!b),
|
Value::Bool(b) => Value::Bool(!b),
|
||||||
@@ -282,7 +282,7 @@ fn register_logic(env: &Environment) {
|
|||||||
Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool },
|
Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool },
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
|
||||||
]);
|
]);
|
||||||
env.register_native("and", logic_op_ty.clone(), |args| {
|
env.register_native("and", logic_op_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
|
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
|
||||||
@@ -292,7 +292,7 @@ fn register_logic(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Or (or) ---
|
// --- Or (or) ---
|
||||||
env.register_native("or", logic_op_ty.clone(), |args| {
|
env.register_native("or", logic_op_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
|
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
|
||||||
@@ -302,7 +302,7 @@ fn register_logic(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Xor (xor) ---
|
// --- Xor (xor) ---
|
||||||
env.register_native("xor", logic_op_ty.clone(), |args| {
|
env.register_native("xor", logic_op_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b),
|
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b),
|
||||||
@@ -315,7 +315,7 @@ fn register_logic(env: &Environment) {
|
|||||||
let shift_ty = StaticType::Function(Box::new(
|
let shift_ty = StaticType::Function(Box::new(
|
||||||
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }
|
||||||
));
|
));
|
||||||
env.register_native("<<", shift_ty.clone(), |args| {
|
env.register_native("<<", shift_ty.clone(), true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a << b),
|
(Value::Int(a), Value::Int(b)) => Value::Int(a << b),
|
||||||
@@ -324,7 +324,7 @@ fn register_logic(env: &Environment) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Shift Right (>>) ---
|
// --- Shift Right (>>) ---
|
||||||
env.register_native(">>", shift_ty, |args| {
|
env.register_native(">>", shift_ty, true, |args| {
|
||||||
if args.len() != 2 { return Value::Void; }
|
if args.len() != 2 { return Value::Void; }
|
||||||
match (&args[0], &args[1]) {
|
match (&args[0], &args[1]) {
|
||||||
(Value::Int(a), Value::Int(b)) => Value::Int(a >> b),
|
(Value::Int(a), Value::Int(b)) => Value::Int(a >> b),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub fn register(env: &Environment) {
|
|||||||
ret: StaticType::DateTime
|
ret: StaticType::DateTime
|
||||||
}));
|
}));
|
||||||
|
|
||||||
env.register_native("date", date_ty, |args| {
|
env.register_native("date", date_ty, true, |args| {
|
||||||
if let Value::Text(s) = &args[0] {
|
if let Value::Text(s) = &args[0] {
|
||||||
// Try parse YYYY-MM-DD
|
// Try parse YYYY-MM-DD
|
||||||
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ impl CompilerApp {
|
|||||||
|
|
||||||
fn dump_ast(&mut self) {
|
fn dump_ast(&mut self) {
|
||||||
self.env = Environment::new();
|
self.env = Environment::new();
|
||||||
|
self.env.optimization_level = 2; // Enable optimization for AST dump
|
||||||
match self.env.dump_ast(&self.source_code) {
|
match self.env.dump_ast(&self.source_code) {
|
||||||
Ok(dump) => {
|
Ok(dump) => {
|
||||||
self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump);
|
self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump);
|
||||||
|
|||||||
Reference in New Issue
Block a user