From 2f669b77fd5d4c0f3c3fe3e234a42f3e75903116 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 19 Oct 2021 23:13:36 -0700 Subject: [PATCH] Move reduced_ast.rs into multiple files --- schala-lang/language/src/reduced_ast.rs | 535 ------------------ schala-lang/language/src/reduced_ast/mod.rs | 513 +++++++++++++++++ schala-lang/language/src/reduced_ast/types.rs | 92 +++ 3 files changed, 605 insertions(+), 535 deletions(-) delete mode 100644 schala-lang/language/src/reduced_ast.rs create mode 100644 schala-lang/language/src/reduced_ast/mod.rs create mode 100644 schala-lang/language/src/reduced_ast/types.rs diff --git a/schala-lang/language/src/reduced_ast.rs b/schala-lang/language/src/reduced_ast.rs deleted file mode 100644 index 2329488..0000000 --- a/schala-lang/language/src/reduced_ast.rs +++ /dev/null @@ -1,535 +0,0 @@ -//! # Reduced AST -//! The reduced AST is a minimal AST designed to be built from the full AST after all possible -//! static checks have been done. Consequently, the AST reduction phase does very little error -//! checking itself - any errors should ideally be caught either by an earlier phase, or are -//! runtime errors that the evaluator should handle. That said, becuase it does do table lookups -//! that can in principle fail [especially at the moment with most static analysis not yet complete], -//! there is an Expr variant `ReductionError` to handle these cases. -//! -//! A design decision to make - should the ReducedAST types contain all information about -//! type/layout necessary for the evaluator to work? If so, then the evaluator should not -//! have access to the symbol table at all and ReducedAST should carry that information. If not, -//! then ReducedAST shouldn't be duplicating information that can be queried at runtime from the -//! symbol table. But I think the former might make sense since ultimately the bytecode will be -//! built from the ReducedAST. - -#![allow(clippy::enum_variant_names)] - -use std::rc::Rc; -use std::str::FromStr; -use std::convert::TryFrom; - -use crate::ast::*; -use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable}; -use crate::builtin::Builtin; -use crate::util::deref_optional_box; - -#[derive(Debug)] -pub struct ReducedAST(pub Vec); - -#[derive(Debug, Clone)] -pub enum Stmt { - PreBinding { - name: Rc, - func: Func, - }, - Binding { - name: Rc, - constant: bool, - expr: Expr, - }, - Expr(Expr), - Noop, -} - -#[derive(Debug, Clone)] -pub enum Expr { - Unit, - Lit(Lit), - Sym(Rc), //a Sym is anything that can be looked up by name at runtime - i.e. a function or variable address - Tuple(Vec), - Func(Func), - Constructor { - type_name: Rc, - name: Rc, - tag: usize, - arity: usize, // n.b. arity here is always the value from the symbol table - if it doesn't match what it's being called with, that's an eval error, eval will handle it - }, - Call { - f: Box, - args: Vec, - }, - Assign { - val: Box, //TODO this probably can't be a val - expr: Box, - }, - Conditional { - cond: Box, - then_clause: Vec, - else_clause: Vec, - }, - ConditionalTargetSigilValue, - CaseMatch { - cond: Box, - alternatives: Vec - }, - UnimplementedSigilValue, - ReductionError(String), -} - -pub type BoundVars = Vec>>; //remember that order matters here - -#[derive(Debug, Clone)] -pub struct Alternative { - pub matchable: Subpattern, - pub item: Vec, -} - -#[derive(Debug, Clone)] -pub struct Subpattern { - pub tag: Option, - pub subpatterns: Vec>, - pub bound_vars: BoundVars, - pub guard: Option, -} - -#[derive(Debug, Clone)] -pub enum Lit { - Nat(u64), - Int(i64), - Float(f64), - Bool(bool), - StringLit(Rc), -} - -#[derive(Debug, Clone)] -pub enum Func { - BuiltIn(Builtin), - UserDefined { - name: Option>, - params: Vec>, - body: Vec, - } -} - -pub fn reduce(ast: &AST, symbol_table: &SymbolTable) -> ReducedAST { - let mut reducer = Reducer { symbol_table }; - reducer.ast(ast) -} - -struct Reducer<'a> { - symbol_table: &'a SymbolTable -} - -impl<'a> Reducer<'a> { - fn ast(&mut self, ast: &AST) -> ReducedAST { - let mut output = vec![]; - for statement in ast.statements.iter() { - output.push(self.statement(statement)); - } - ReducedAST(output) - } - - fn statement(&mut self, stmt: &Statement) -> Stmt { - match &stmt.kind { - StatementKind::Expression(expr) => Stmt::Expr(self.expression(expr)), - StatementKind::Declaration(decl) => self.declaration(decl), - StatementKind::Import(_) => Stmt::Noop, - StatementKind::Module(modspec) => { - for statement in modspec.contents.iter() { - self.statement(statement); - } - Stmt::Noop - } - } - } - - #[allow(clippy::ptr_arg)] - fn block(&mut self, block: &Block) -> Vec { - block.iter().map(|stmt| self.statement(stmt)).collect() - } - - fn invocation_argument(&mut self, invoc: &InvocationArgument) -> Expr { - use crate::ast::InvocationArgument::*; - match invoc { - Positional(ex) => self.expression(ex), - Keyword { .. } => Expr::UnimplementedSigilValue, - Ignored => Expr::UnimplementedSigilValue, - } - } - - fn expression(&mut self, expr: &Expression) -> Expr { - use crate::ast::ExpressionKind::*; - let input = &expr.kind; - match input { - NatLiteral(n) => Expr::Lit(Lit::Nat(*n)), - FloatLiteral(f) => Expr::Lit(Lit::Float(*f)), - StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())), - BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)), - BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs), - PrefixExp(op, arg) => self.prefix(op, arg), - Value(qualified_name) => self.value(qualified_name), - Call { f, arguments } => self.reduce_call_expression(f, arguments), - TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| self.expression(e)).collect()), - IfExpression { discriminator, body } => self.reduce_if_expression(deref_optional_box(discriminator), body), - Lambda { params, body, .. } => self.reduce_lambda(params, body), - NamedStruct { name, fields } => self.reduce_named_struct(name, fields), - Index { .. } => Expr::UnimplementedSigilValue, - WhileExpression { .. } => Expr::UnimplementedSigilValue, - ForExpression { .. } => Expr::UnimplementedSigilValue, - ListLiteral { .. } => Expr::UnimplementedSigilValue, - } - } - - fn value(&mut self, qualified_name: &QualifiedName) -> Expr { - let Symbol { local_name, spec, .. } = match self.symbol_table.lookup_symbol(&qualified_name.id) { - Some(s) => s, - //TODO this causes several evaluation tests to fail, figure out what's going on here - //None => return Expr::ReductionError(format!("Symbol {:?} not found", sym_name)), - None => { - let name = qualified_name.components.last().unwrap().clone(); - return Expr::Sym(name) - } - }; - - match spec { - SymbolSpec::RecordConstructor { .. } => Expr::ReductionError("AST reducer doesn't expect a RecordConstructor here".to_string()), - SymbolSpec::DataConstructor { index, arity, type_name } => Expr::Constructor { - type_name: type_name.clone(), - name: local_name.clone(), - tag: *index, - arity: *arity, - }, - SymbolSpec::Func(_) => Expr::Sym(local_name.clone()), - SymbolSpec::Binding => Expr::Sym(local_name.clone()), //TODO not sure if this is right, probably needs to eventually be fqsn - } - } - - #[allow(clippy::ptr_arg)] - fn reduce_lambda(&mut self, params: &[FormalParam], body: &Block) -> Expr { - Expr::Func(Func::UserDefined { - name: None, - params: params.iter().map(|param| param.name.clone()).collect(), - body: self.block(body), - }) - } - - fn reduce_named_struct(&mut self, name: &QualifiedName, fields: &[(Rc, Expression)]) -> Expr { - let symbol = match self.symbol_table.lookup_symbol(&name.id) { - Some(fqsn) => fqsn, - None => return Expr::ReductionError(format!("FQSN lookup for name {:?} failed", name)), - }; - - let (type_name, index, members_from_table) = match &symbol.spec { - SymbolSpec::RecordConstructor { members, type_name, index } => (type_name.clone(), index, members), - _ => return Expr::ReductionError("Not a record constructor".to_string()), - }; - - let arity = members_from_table.len(); - - let mut args: Vec<(Rc, Expr)> = fields.iter() - .map(|(name, expr)| (name.clone(), self.expression(expr))) - .collect(); - - args.as_mut_slice() - .sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); //arbitrary - sorting by alphabetical order - - let args = args.into_iter().map(|(_, expr)| expr).collect(); - - //TODO make sure this sorting actually works - let f = box Expr::Constructor { type_name, name: symbol.local_name.clone(), tag: *index, arity, }; - Expr::Call { f, args } - } - - fn reduce_call_expression(&mut self, func: &Expression, arguments: &[ InvocationArgument ]) -> Expr { - Expr::Call { - f: Box::new(self.expression(func)), - args: arguments.iter().map(|arg| self.invocation_argument(arg)).collect(), - } - } - - fn reduce_if_expression(&mut self, discriminator: Option<&Expression>, body: &IfExpressionBody) -> Expr { - let cond = Box::new(match discriminator { - Some(expr) => self.expression(expr), - None => return Expr::ReductionError("blank cond if-expr not supported".to_string()), - }); - - match body { - IfExpressionBody::SimpleConditional { then_case, else_case } => { - let then_clause = self.block(then_case); - let else_clause = match else_case.as_ref() { - None => vec![], - Some(stmts) => self.block(stmts), - }; - Expr::Conditional { cond, then_clause, else_clause } - }, - IfExpressionBody::SimplePatternMatch { pattern, then_case, else_case } => { - let then_clause = self.block(then_case); - let else_clause = match else_case.as_ref() { - None => vec![], - Some(stmts) => self.block(stmts), - }; - - let alternatives = vec![ - pattern.to_alternative(then_clause, self.symbol_table), - Alternative { - matchable: Subpattern { - tag: None, - subpatterns: vec![], - bound_vars: vec![], - guard: None, - }, - item: else_clause - }, - ]; - - Expr::CaseMatch { - cond, - alternatives, - } - }, - IfExpressionBody::CondList(ref condition_arms) => { - let mut alternatives = vec![]; - for arm in condition_arms { - match arm.condition { - Condition::Expression(ref _expr) => { - return Expr::UnimplementedSigilValue - }, - Condition::Pattern(ref p) => { - let item = self.block(&arm.body); - let alt = p.to_alternative(item, self.symbol_table); - alternatives.push(alt); - }, - Condition::TruncatedOp(_, _) => { - return Expr::UnimplementedSigilValue - }, - Condition::Else => { - return Expr::UnimplementedSigilValue - } - } - } - Expr::CaseMatch { cond, alternatives } - } - } - } - - fn binop(&mut self, binop: &BinOp, lhs: &Expression, rhs: &Expression) -> Expr { - let operation = Builtin::from_str(binop.sigil()).ok(); - match operation { - Some(Builtin::Assignment) => Expr::Assign { - val: Box::new(self.expression(&*lhs)), - expr: Box::new(self.expression(&*rhs)), - }, - Some(op) => { - let f = Box::new(Expr::Func(Func::BuiltIn(op))); - Expr::Call { f, args: vec![self.expression(&*lhs), self.expression(&*rhs)] } - }, - None => { - //TODO handle a user-defined operation - Expr::UnimplementedSigilValue - } - } - } - - fn prefix(&mut self, prefix: &PrefixOp, arg: &Expression) -> Expr { - let builtin: Option = TryFrom::try_from(prefix).ok(); - match builtin { - Some(op) => { - let f = Box::new(Expr::Func(Func::BuiltIn(op))); - Expr::Call { f, args: vec![self.expression(arg)] } - }, - None => { //TODO need this for custom prefix ops - Expr::UnimplementedSigilValue - } - } - } - - fn declaration(&mut self, declaration: &Declaration) -> Stmt { - use self::Declaration::*; - match declaration { - Binding {name, constant, expr, .. } => Stmt::Binding { name: name.clone(), constant: *constant, expr: self.expression(expr) }, - FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding { - name: name.clone(), - func: Func::UserDefined { - name: Some(name.clone()), - params: params.iter().map(|param| param.name.clone()).collect(), - body: self.block(statements), - } - }, - TypeDecl { .. } => Stmt::Noop, - TypeAlias{ .. } => Stmt::Noop, - Interface { .. } => Stmt::Noop, - Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue), - _ => Stmt::Expr(Expr::UnimplementedSigilValue) - } - } -} - - - - - -/* ig var pat - * x is SomeBigOldEnum(_, x, Some(t)) - */ - -fn handle_symbol(symbol: Option<&Symbol>, inner_patterns: &[Pattern], symbol_table: &SymbolTable) -> Subpattern { - use self::Pattern::*; - let tag = symbol.map(|symbol| match symbol.spec { - SymbolSpec::DataConstructor { index, .. } => index, - _ => panic!("Symbol is not a data constructor - this should've been caught in type-checking"), - }); - let bound_vars = inner_patterns.iter().map(|p| match p { - VarOrName(qualified_name) => { - let symbol_exists = symbol_table.lookup_symbol(&qualified_name.id).is_some(); - if symbol_exists { - None - } else { - let QualifiedName { components, .. } = qualified_name; - if components.len() == 1 { - Some(components[0].clone()) - } else { - panic!("Bad variable name in pattern"); - } - } - }, - _ => None, - }).collect(); - - let subpatterns = inner_patterns.iter().map(|p| match p { - Ignored => None, - VarOrName(_) => None, - Literal(other) => Some(other.to_subpattern(symbol_table)), - tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)), - ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)), - Record(..) => unimplemented!(), - }).collect(); - - let guard = None; - /* - let guard_equality_exprs: Vec = subpatterns.iter().map(|p| match p { - Literal(lit) => match lit { - _ => unimplemented!() - }, - _ => unimplemented!() - }).collect(); - */ - - Subpattern { - tag, - subpatterns, - guard, - bound_vars, - } -} - -impl Pattern { - fn to_alternative(&self, item: Vec, symbol_table: &SymbolTable) -> Alternative { - let s = self.to_subpattern(symbol_table); - Alternative { - matchable: Subpattern { - tag: s.tag, - subpatterns: s.subpatterns, - bound_vars: s.bound_vars, - guard: s.guard, - }, - item - } - } - - fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern { - use self::Pattern::*; - match self { - TupleStruct(QualifiedName{ components, id }, inner_patterns) => { - match symbol_table.lookup_symbol(id) { - Some(symbol) => handle_symbol(Some(symbol), inner_patterns, symbol_table), - None => panic!("Symbol {:?} not found", components) - } - }, - TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table), - Record(_name, _pairs) => { - unimplemented!() - }, - Ignored => Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![] }, - Literal(lit) => lit.to_subpattern(symbol_table), - VarOrName(QualifiedName { components, id }) => { - // if symbol is Some, treat this as a symbol pattern. If it's None, treat it - // as a variable. - match symbol_table.lookup_symbol(id) { - Some(symbol) => handle_symbol(Some(symbol), &[], symbol_table), - None => { - println!("Components: {:?}", components); - let name = if components.len() == 1 { - components[0].clone() - } else { - panic!("check this line of code yo"); - }; - Subpattern { - tag: None, - subpatterns: vec![], - guard: None, - bound_vars: vec![Some(name)], - } - } - } - }, - } - } -} - -impl PatternLiteral { - fn to_subpattern(&self, _symbol_table: &SymbolTable) -> Subpattern { - use self::PatternLiteral::*; - match self { - NumPattern { neg, num } => { - let comparison = Expr::Lit(match (neg, num) { - (false, ExpressionKind::NatLiteral(n)) => Lit::Nat(*n), - (false, ExpressionKind::FloatLiteral(f)) => Lit::Float(*f), - (true, ExpressionKind::NatLiteral(n)) => Lit::Int(-(*n as i64)), - (true, ExpressionKind::FloatLiteral(f)) => Lit::Float(-f), - _ => panic!("This should never happen") - }); - let guard = Some(Expr::Call { - f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), - args: vec![comparison, Expr::ConditionalTargetSigilValue], - }); - Subpattern { - tag: None, - subpatterns: vec![], - guard, - bound_vars: vec![], - } - }, - StringPattern(s) => { - let guard = Some(Expr::Call { - f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), - args: vec![Expr::Lit(Lit::StringLit(s.clone())), Expr::ConditionalTargetSigilValue] - }); - - Subpattern { - tag: None, - subpatterns: vec![], - guard, - bound_vars: vec![], - } - }, - BoolPattern(b) => { - let guard = Some(if *b { - Expr::ConditionalTargetSigilValue - } else { - Expr::Call { - f: Box::new(Expr::Func(Func::BuiltIn(Builtin::BooleanNot))), - args: vec![Expr::ConditionalTargetSigilValue] - } - }); - Subpattern { - tag: None, - subpatterns: vec![], - guard, - bound_vars: vec![], - } - }, - } - } - -} diff --git a/schala-lang/language/src/reduced_ast/mod.rs b/schala-lang/language/src/reduced_ast/mod.rs new file mode 100644 index 0000000..633ed0f --- /dev/null +++ b/schala-lang/language/src/reduced_ast/mod.rs @@ -0,0 +1,513 @@ +//! # Reduced AST +//! The reduced AST is a minimal AST designed to be built from the full AST after all possible +//! static checks have been done. Consequently, the AST reduction phase does very little error +//! checking itself - any errors should ideally be caught either by an earlier phase, or are +//! runtime errors that the evaluator should handle. That said, becuase it does do table lookups +//! that can in principle fail [especially at the moment with most static analysis not yet complete], +//! there is an Expr variant `ReductionError` to handle these cases. +//! +//! A design decision to make - should the ReducedAST types contain all information about +//! type/layout necessary for the evaluator to work? If so, then the evaluator should not +//! have access to the symbol table at all and ReducedAST should carry that information. If not, +//! then ReducedAST shouldn't be duplicating information that can be queried at runtime from the +//! symbol table. But I think the former might make sense since ultimately the bytecode will be +//! built from the ReducedAST. + +use std::convert::TryFrom; +use std::rc::Rc; +use std::str::FromStr; + +use crate::ast::*; +use crate::builtin::Builtin; +use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable}; +use crate::util::deref_optional_box; + +mod types; +pub use types::*; + +pub fn reduce(ast: &AST, symbol_table: &SymbolTable) -> ReducedAST { + let mut reducer = Reducer { symbol_table }; + reducer.ast(ast) +} + +struct Reducer<'a> { + symbol_table: &'a SymbolTable, +} + +impl<'a> Reducer<'a> { + fn ast(&mut self, ast: &AST) -> ReducedAST { + let mut output = vec![]; + for statement in ast.statements.iter() { + output.push(self.statement(statement)); + } + ReducedAST(output) + } + + fn statement(&mut self, stmt: &Statement) -> Stmt { + match &stmt.kind { + StatementKind::Expression(expr) => Stmt::Expr(self.expression(expr)), + StatementKind::Declaration(decl) => self.declaration(decl), + StatementKind::Import(_) => Stmt::Noop, + StatementKind::Module(modspec) => { + for statement in modspec.contents.iter() { + self.statement(statement); + } + Stmt::Noop + } + } + } + + #[allow(clippy::ptr_arg)] + fn block(&mut self, block: &Block) -> Vec { + block.iter().map(|stmt| self.statement(stmt)).collect() + } + + fn invocation_argument(&mut self, invoc: &InvocationArgument) -> Expr { + use crate::ast::InvocationArgument::*; + match invoc { + Positional(ex) => self.expression(ex), + Keyword { .. } => Expr::UnimplementedSigilValue, + Ignored => Expr::UnimplementedSigilValue, + } + } + + fn expression(&mut self, expr: &Expression) -> Expr { + use crate::ast::ExpressionKind::*; + let input = &expr.kind; + match input { + NatLiteral(n) => Expr::Lit(Lit::Nat(*n)), + FloatLiteral(f) => Expr::Lit(Lit::Float(*f)), + StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())), + BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)), + BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs), + PrefixExp(op, arg) => self.prefix(op, arg), + Value(qualified_name) => self.value(qualified_name), + Call { f, arguments } => self.reduce_call_expression(f, arguments), + TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| self.expression(e)).collect()), + IfExpression { + discriminator, + body, + } => self.reduce_if_expression(deref_optional_box(discriminator), body), + Lambda { params, body, .. } => self.reduce_lambda(params, body), + NamedStruct { name, fields } => self.reduce_named_struct(name, fields), + Index { .. } => Expr::UnimplementedSigilValue, + WhileExpression { .. } => Expr::UnimplementedSigilValue, + ForExpression { .. } => Expr::UnimplementedSigilValue, + ListLiteral { .. } => Expr::UnimplementedSigilValue, + } + } + + fn value(&mut self, qualified_name: &QualifiedName) -> Expr { + let Symbol { + local_name, spec, .. + } = match self.symbol_table.lookup_symbol(&qualified_name.id) { + Some(s) => s, + //TODO this causes several evaluation tests to fail, figure out what's going on here + //None => return Expr::ReductionError(format!("Symbol {:?} not found", sym_name)), + None => { + let name = qualified_name.components.last().unwrap().clone(); + return Expr::Sym(name); + } + }; + + match spec { + SymbolSpec::RecordConstructor { .. } => Expr::ReductionError( + "AST reducer doesn't expect a RecordConstructor here".to_string(), + ), + SymbolSpec::DataConstructor { + index, + arity, + type_name, + } => Expr::Constructor { + type_name: type_name.clone(), + name: local_name.clone(), + tag: *index, + arity: *arity, + }, + SymbolSpec::Func(_) => Expr::Sym(local_name.clone()), + SymbolSpec::Binding => Expr::Sym(local_name.clone()), //TODO not sure if this is right, probably needs to eventually be fqsn + } + } + + #[allow(clippy::ptr_arg)] + fn reduce_lambda(&mut self, params: &[FormalParam], body: &Block) -> Expr { + Expr::Func(Func::UserDefined { + name: None, + params: params.iter().map(|param| param.name.clone()).collect(), + body: self.block(body), + }) + } + + fn reduce_named_struct( + &mut self, + name: &QualifiedName, + fields: &[(Rc, Expression)], + ) -> Expr { + let symbol = match self.symbol_table.lookup_symbol(&name.id) { + Some(fqsn) => fqsn, + None => return Expr::ReductionError(format!("FQSN lookup for name {:?} failed", name)), + }; + + let (type_name, index, members_from_table) = match &symbol.spec { + SymbolSpec::RecordConstructor { + members, + type_name, + index, + } => (type_name.clone(), index, members), + _ => return Expr::ReductionError("Not a record constructor".to_string()), + }; + + let arity = members_from_table.len(); + + let mut args: Vec<(Rc, Expr)> = fields + .iter() + .map(|(name, expr)| (name.clone(), self.expression(expr))) + .collect(); + + args.as_mut_slice() + .sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); //arbitrary - sorting by alphabetical order + + let args = args.into_iter().map(|(_, expr)| expr).collect(); + + //TODO make sure this sorting actually works + let f = box Expr::Constructor { + type_name, + name: symbol.local_name.clone(), + tag: *index, + arity, + }; + Expr::Call { f, args } + } + + fn reduce_call_expression( + &mut self, + func: &Expression, + arguments: &[InvocationArgument], + ) -> Expr { + Expr::Call { + f: Box::new(self.expression(func)), + args: arguments + .iter() + .map(|arg| self.invocation_argument(arg)) + .collect(), + } + } + + fn reduce_if_expression( + &mut self, + discriminator: Option<&Expression>, + body: &IfExpressionBody, + ) -> Expr { + let cond = Box::new(match discriminator { + Some(expr) => self.expression(expr), + None => return Expr::ReductionError("blank cond if-expr not supported".to_string()), + }); + + match body { + IfExpressionBody::SimpleConditional { + then_case, + else_case, + } => { + let then_clause = self.block(then_case); + let else_clause = match else_case.as_ref() { + None => vec![], + Some(stmts) => self.block(stmts), + }; + Expr::Conditional { + cond, + then_clause, + else_clause, + } + } + IfExpressionBody::SimplePatternMatch { + pattern, + then_case, + else_case, + } => { + let then_clause = self.block(then_case); + let else_clause = match else_case.as_ref() { + None => vec![], + Some(stmts) => self.block(stmts), + }; + + let alternatives = vec![ + pattern.to_alternative(then_clause, self.symbol_table), + Alternative { + matchable: Subpattern { + tag: None, + subpatterns: vec![], + bound_vars: vec![], + guard: None, + }, + item: else_clause, + }, + ]; + + Expr::CaseMatch { cond, alternatives } + } + IfExpressionBody::CondList(ref condition_arms) => { + let mut alternatives = vec![]; + for arm in condition_arms { + match arm.condition { + Condition::Expression(ref _expr) => return Expr::UnimplementedSigilValue, + Condition::Pattern(ref p) => { + let item = self.block(&arm.body); + let alt = p.to_alternative(item, self.symbol_table); + alternatives.push(alt); + } + Condition::TruncatedOp(_, _) => return Expr::UnimplementedSigilValue, + Condition::Else => return Expr::UnimplementedSigilValue, + } + } + Expr::CaseMatch { cond, alternatives } + } + } + } + + fn binop(&mut self, binop: &BinOp, lhs: &Expression, rhs: &Expression) -> Expr { + let operation = Builtin::from_str(binop.sigil()).ok(); + match operation { + Some(Builtin::Assignment) => Expr::Assign { + val: Box::new(self.expression(&*lhs)), + expr: Box::new(self.expression(&*rhs)), + }, + Some(op) => { + let f = Box::new(Expr::Func(Func::BuiltIn(op))); + Expr::Call { + f, + args: vec![self.expression(&*lhs), self.expression(&*rhs)], + } + } + None => { + //TODO handle a user-defined operation + Expr::UnimplementedSigilValue + } + } + } + + fn prefix(&mut self, prefix: &PrefixOp, arg: &Expression) -> Expr { + let builtin: Option = TryFrom::try_from(prefix).ok(); + match builtin { + Some(op) => { + let f = Box::new(Expr::Func(Func::BuiltIn(op))); + Expr::Call { + f, + args: vec![self.expression(arg)], + } + } + None => { + //TODO need this for custom prefix ops + Expr::UnimplementedSigilValue + } + } + } + + fn declaration(&mut self, declaration: &Declaration) -> Stmt { + use self::Declaration::*; + match declaration { + Binding { + name, + constant, + expr, + .. + } => Stmt::Binding { + name: name.clone(), + constant: *constant, + expr: self.expression(expr), + }, + FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding { + name: name.clone(), + func: Func::UserDefined { + name: Some(name.clone()), + params: params.iter().map(|param| param.name.clone()).collect(), + body: self.block(statements), + }, + }, + TypeDecl { .. } => Stmt::Noop, + TypeAlias { .. } => Stmt::Noop, + Interface { .. } => Stmt::Noop, + Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue), + _ => Stmt::Expr(Expr::UnimplementedSigilValue), + } + } +} + +fn handle_symbol( + symbol: Option<&Symbol>, + inner_patterns: &[Pattern], + symbol_table: &SymbolTable, +) -> Subpattern { + use self::Pattern::*; + let tag = symbol.map(|symbol| match symbol.spec { + SymbolSpec::DataConstructor { index, .. } => index, + _ => { + panic!("Symbol is not a data constructor - this should've been caught in type-checking") + } + }); + let bound_vars = inner_patterns + .iter() + .map(|p| match p { + VarOrName(qualified_name) => { + let symbol_exists = symbol_table.lookup_symbol(&qualified_name.id).is_some(); + if symbol_exists { + None + } else { + let QualifiedName { components, .. } = qualified_name; + if components.len() == 1 { + Some(components[0].clone()) + } else { + panic!("Bad variable name in pattern"); + } + } + } + _ => None, + }) + .collect(); + + let subpatterns = inner_patterns + .iter() + .map(|p| match p { + Ignored => None, + VarOrName(_) => None, + Literal(other) => Some(other.to_subpattern(symbol_table)), + tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)), + ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)), + Record(..) => unimplemented!(), + }) + .collect(); + + let guard = None; + /* + let guard_equality_exprs: Vec = subpatterns.iter().map(|p| match p { + Literal(lit) => match lit { + _ => unimplemented!() + }, + _ => unimplemented!() + }).collect(); + */ + + Subpattern { + tag, + subpatterns, + guard, + bound_vars, + } +} + +impl Pattern { + fn to_alternative(&self, item: Vec, symbol_table: &SymbolTable) -> Alternative { + let s = self.to_subpattern(symbol_table); + Alternative { + matchable: Subpattern { + tag: s.tag, + subpatterns: s.subpatterns, + bound_vars: s.bound_vars, + guard: s.guard, + }, + item, + } + } + + fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern { + use self::Pattern::*; + match self { + TupleStruct(QualifiedName { components, id }, inner_patterns) => { + match symbol_table.lookup_symbol(id) { + Some(symbol) => handle_symbol(Some(symbol), inner_patterns, symbol_table), + None => panic!("Symbol {:?} not found", components), + } + } + TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table), + Record(_name, _pairs) => { + unimplemented!() + } + Ignored => Subpattern { + tag: None, + subpatterns: vec![], + guard: None, + bound_vars: vec![], + }, + Literal(lit) => lit.to_subpattern(symbol_table), + VarOrName(QualifiedName { components, id }) => { + // if symbol is Some, treat this as a symbol pattern. If it's None, treat it + // as a variable. + match symbol_table.lookup_symbol(id) { + Some(symbol) => handle_symbol(Some(symbol), &[], symbol_table), + None => { + println!("Components: {:?}", components); + let name = if components.len() == 1 { + components[0].clone() + } else { + panic!("check this line of code yo"); + }; + Subpattern { + tag: None, + subpatterns: vec![], + guard: None, + bound_vars: vec![Some(name)], + } + } + } + } + } + } +} + +impl PatternLiteral { + fn to_subpattern(&self, _symbol_table: &SymbolTable) -> Subpattern { + use self::PatternLiteral::*; + match self { + NumPattern { neg, num } => { + let comparison = Expr::Lit(match (neg, num) { + (false, ExpressionKind::NatLiteral(n)) => Lit::Nat(*n), + (false, ExpressionKind::FloatLiteral(f)) => Lit::Float(*f), + (true, ExpressionKind::NatLiteral(n)) => Lit::Int(-(*n as i64)), + (true, ExpressionKind::FloatLiteral(f)) => Lit::Float(-f), + _ => panic!("This should never happen"), + }); + let guard = Some(Expr::Call { + f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), + args: vec![comparison, Expr::ConditionalTargetSigilValue], + }); + Subpattern { + tag: None, + subpatterns: vec![], + guard, + bound_vars: vec![], + } + } + StringPattern(s) => { + let guard = Some(Expr::Call { + f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), + args: vec![ + Expr::Lit(Lit::StringLit(s.clone())), + Expr::ConditionalTargetSigilValue, + ], + }); + + Subpattern { + tag: None, + subpatterns: vec![], + guard, + bound_vars: vec![], + } + } + BoolPattern(b) => { + let guard = Some(if *b { + Expr::ConditionalTargetSigilValue + } else { + Expr::Call { + f: Box::new(Expr::Func(Func::BuiltIn(Builtin::BooleanNot))), + args: vec![Expr::ConditionalTargetSigilValue], + } + }); + Subpattern { + tag: None, + subpatterns: vec![], + guard, + bound_vars: vec![], + } + } + } + } +} diff --git a/schala-lang/language/src/reduced_ast/types.rs b/schala-lang/language/src/reduced_ast/types.rs new file mode 100644 index 0000000..3743afd --- /dev/null +++ b/schala-lang/language/src/reduced_ast/types.rs @@ -0,0 +1,92 @@ +#![allow(clippy::enum_variant_names)] + +use crate::builtin::Builtin; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ReducedAST(pub Vec); + +#[derive(Debug, Clone)] +pub enum Stmt { + PreBinding { + name: Rc, + func: Func, + }, + Binding { + name: Rc, + constant: bool, + expr: Expr, + }, + Expr(Expr), + Noop, +} + +#[derive(Debug, Clone)] +pub enum Expr { + Unit, + Lit(Lit), + Sym(Rc), //a Sym is anything that can be looked up by name at runtime - i.e. a function or variable address + Tuple(Vec), + Func(Func), + Constructor { + type_name: Rc, + name: Rc, + tag: usize, + arity: usize, // n.b. arity here is always the value from the symbol table - if it doesn't match what it's being called with, that's an eval error, eval will handle it + }, + Call { + f: Box, + args: Vec, + }, + Assign { + val: Box, //TODO this probably can't be a val + expr: Box, + }, + Conditional { + cond: Box, + then_clause: Vec, + else_clause: Vec, + }, + ConditionalTargetSigilValue, + CaseMatch { + cond: Box, + alternatives: Vec, + }, + UnimplementedSigilValue, + ReductionError(String), +} + +pub type BoundVars = Vec>>; //remember that order matters here + +#[derive(Debug, Clone)] +pub struct Alternative { + pub matchable: Subpattern, + pub item: Vec, +} + +#[derive(Debug, Clone)] +pub struct Subpattern { + pub tag: Option, + pub subpatterns: Vec>, + pub bound_vars: BoundVars, + pub guard: Option, +} + +#[derive(Debug, Clone)] +pub enum Lit { + Nat(u64), + Int(i64), + Float(f64), + Bool(bool), + StringLit(Rc), +} + +#[derive(Debug, Clone)] +pub enum Func { + BuiltIn(Builtin), + UserDefined { + name: Option>, + params: Vec>, + body: Vec, + }, +}