From def8adfcb9a9572f4e030990626a0d08377118bd Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 24 Jun 2023 21:11:44 -0500 Subject: hehehaha --- examples/guessing.sloth | 47 +++++++------- examples/hello.sloth | 2 + graph.png | Bin 0 -> 350220 bytes sloth/src/analysis/mod.rs | 25 +------- sloth/src/analysis/setup.rs | 77 +++++++++++++++++++++++ sloth/src/lexer.rs | 3 + sloth/src/main.rs | 20 +++--- sloth/src/parser/ast.rs | 32 +++++++--- sloth/src/parser/graph.rs | 96 +++++++++++++++++++---------- sloth/src/parser/stmt.rs | 145 ++++++++++++++++++++++++++++---------------- sloth/src/symtable.rs | 45 ++++++++++---- 11 files changed, 337 insertions(+), 155 deletions(-) create mode 100644 graph.png create mode 100644 sloth/src/analysis/setup.rs diff --git a/examples/guessing.sloth b/examples/guessing.sloth index 05c25db..5a759e5 100644 --- a/examples/guessing.sloth +++ b/examples/guessing.sloth @@ -1,26 +1,29 @@ -val computer: int = random(1, 10); +foreign fn print(); +foreign fn println(); +foreign fn readln() String; +foreign fn random(min: Int, max: Int) Int; +foreign fn parse_int(str: String) Int; -var tries: int = 0; -var correct: bool = false; +fn main() { + var computer: Int = random(1, 10); + var tries: Int = 0; + var correct: Bool = false; -while !correct { - print("\nPick a number between 1 and 10: "); - val human: int = parse_int(readln()); + while !correct { + print("Pick a number between 1 and 10: "); + var human: Int = parse_int(readln()); - if human == computer { - println("You guessed the same number as me!"); - correct = true; - } - - if human > computer { - println("Your guess was too high."); - } - - if human < computer { - println("Your guess was too low."); - } - - tries = tries + 1; -} + if human == computer { + println("You guessed the same number as me!\n"); + correct = true; + } else if human > computer { + println("Your guess was too high.\n"); + } else if human < computer { + println("Your guess was too low.\n"); + } + + tries = tries + 1; + } -println("\nIt took you ", tries, " tries to guess correctly!"); + println("It took you ", tries, " tries to guess correctly!"); +} diff --git a/examples/hello.sloth b/examples/hello.sloth index 8b395e8..a955e66 100644 --- a/examples/hello.sloth +++ b/examples/hello.sloth @@ -1,3 +1,5 @@ +foreign fn example(arg: Int) Void; + fn main() { var i: Int = 10; var j: Int = 1.0 - 1; diff --git a/graph.png b/graph.png new file mode 100644 index 0000000..f7c192d Binary files /dev/null and b/graph.png differ diff --git a/sloth/src/analysis/mod.rs b/sloth/src/analysis/mod.rs index 857fbb8..8232e0e 100644 --- a/sloth/src/analysis/mod.rs +++ b/sloth/src/analysis/mod.rs @@ -1,5 +1,6 @@ +pub mod setup; + use crate::parser::ast::{AstNode, ExprKind, Stmt, StmtKind}; -use crate::symtable::{Symbol, SymbolType}; #[derive(Debug, thiserror::Error)] pub enum AnalysisError { @@ -22,32 +23,12 @@ impl AnalysisError { } pub fn analyze(root: &mut Stmt) -> Result<(), AnalysisError> { - populate_symtable(&root.as_node()); + setup::populate_symtable(&root.as_node())?; check_usage(&root.as_node())?; Ok(()) } -fn populate_symtable(node: &AstNode) { - if let AstNode::Stmt(stmt) = node { - match &stmt.kind { - StmtKind::DefineVariable { identifier, .. } => { - let mut table = stmt.symtable.clone(); - table.insert(identifier.to_owned(), Symbol::new(SymbolType::Variable)); - } - StmtKind::DefineFunction { identifier, .. } => { - let mut table = stmt.symtable.clone(); - table.insert(identifier.to_owned(), Symbol::new(SymbolType::Function)); - } - _ => (), - } - } - - for child in node.children() { - populate_symtable(&child); - } -} - fn check_usage(node: &AstNode) -> Result<(), AnalysisError> { if let AstNode::Expr(expr) = node && let ExprKind::Identifier(identifier) = &expr.kind && !expr.symtable.clone().contains(identifier) { return Err(AnalysisError::UnknownIdentifier(expr.line, identifier.clone())); diff --git a/sloth/src/analysis/setup.rs b/sloth/src/analysis/setup.rs new file mode 100644 index 0000000..0c4f228 --- /dev/null +++ b/sloth/src/analysis/setup.rs @@ -0,0 +1,77 @@ +use super::AnalysisError; +use crate::parser::ast::{AstNode, Function, FunctionInput, FunctionKind, StmtKind}; +use crate::symtable::{Symbol, SymbolTable, Type}; + +pub(super) fn populate_symtable(node: &AstNode) -> Result<(), AnalysisError> { + if let AstNode::Stmt(stmt) = node { + let mut table = stmt.symtable.clone(); + + match &stmt.kind { + StmtKind::DefineVariable { + identifier, typ, .. + } => { + // When a variable is defined add it to the symbol table of the current scope. + let symbol = build_value_symbol(&table, typ)?; + table.insert(identifier.to_owned(), symbol); + } + StmtKind::DefineFunction(Function { + identifier, + inputs, + output, + kind, + }) => { + // When a function is defined add the function to the symbol + // table of the current scope, and add the inputs to the child + // (body) scope. + let function_symbol = build_function_symbol(&table, inputs, output.as_deref())?; + table.insert(identifier.to_owned(), function_symbol); + + if let FunctionKind::Normal { body } = kind { + let mut body_table = body.symtable.clone(); + + for input in inputs { + let symbol = build_value_symbol(&body_table, &input.typ)?; + body_table.insert(input.identifier.to_owned(), symbol); + } + } + } + _ => (), + } + } + + for child in node.children() { + populate_symtable(&child)?; + } + + Ok(()) +} + +fn build_value_symbol(table: &SymbolTable, typ: &str) -> Result { + let typ = table + .get_type(typ) + .ok_or(AnalysisError::UnknownIdentifier(0, typ.to_owned()))?; + + Ok(Symbol::Value(typ)) +} + +fn build_function_symbol( + table: &SymbolTable, + inputs: &[FunctionInput], + output: Option<&str>, +) -> Result { + let inputs = inputs + .iter() + .map(|it| table.get_type(&it.typ)) + .collect::>>() + .ok_or(AnalysisError::UnknownIdentifier(0, "0xOwO".to_owned()))?; + + let output = output + .map(|it| table.get_type(it)) + .unwrap_or(Some(Type::Void)) + .ok_or(AnalysisError::UnknownIdentifier(0, "0xUwU".to_owned()))?; + + Ok(Symbol::Value(Type::Function { + inputs, + output: output.into(), + })) +} diff --git a/sloth/src/lexer.rs b/sloth/src/lexer.rs index 3ad39a2..128b012 100644 --- a/sloth/src/lexer.rs +++ b/sloth/src/lexer.rs @@ -102,6 +102,8 @@ pub enum TokenType { As, + Foreign, + // Other Literal(Literal), Identifier(String), @@ -406,6 +408,7 @@ impl<'a> Iterator for Lexer<'a> { "break" => TokenType::Break, "continue" => TokenType::Continue, "as" => TokenType::As, + "foreign" => TokenType::Foreign, "true" => Literal::Boolean(true).into(), "false" => Literal::Boolean(false).into(), _ => TokenType::Identifier(value), diff --git a/sloth/src/main.rs b/sloth/src/main.rs index 1eb0ead..ec9eb7e 100644 --- a/sloth/src/main.rs +++ b/sloth/src/main.rs @@ -15,11 +15,14 @@ pub mod symtable; use std::{env, fs}; -use analysis::analyze; use itertools::Itertools; use lexer::Lexer; use parser::AstParser; -use symtable::{Symbol, SymbolTable, SymbolType}; +use symtable::{Symbol, SymbolTable}; + +use crate::analysis::analyze; +use crate::parser::graph::GraphBuilder; +use crate::symtable::Type; fn main() { let args = env::args().collect_vec(); @@ -38,9 +41,10 @@ fn main() { // Symbol table let mut global_symtable = SymbolTable::new(); - global_symtable.insert("print".to_owned(), Symbol::new(SymbolType::Function)); - global_symtable.insert("println".to_owned(), Symbol::new(SymbolType::Function)); - global_symtable.insert("readln".to_owned(), Symbol::new(SymbolType::Function)); + global_symtable.insert("Void".into(), Symbol::Type(Type::Void)); + global_symtable.insert("Int".into(), Symbol::Type(Type::Integer)); + global_symtable.insert("Float".into(), Symbol::Type(Type::Float)); + global_symtable.insert("Bool".into(), Symbol::Type(Type::Boolean)); // Parsing let tokens = Lexer::new(&source).collect_vec(); @@ -51,8 +55,10 @@ fn main() { return; } - println!("{ast:#?}"); + println!("Suces"); + + // println!("{ast:#?}"); - // let graph = GraphBuilder::generate(&ast).unwrap(); + // let graph = GraphBuilder::generate(Some(&source), &ast).unwrap(); // println!("{graph}"); } diff --git a/sloth/src/parser/ast.rs b/sloth/src/parser/ast.rs index a542847..8b58fcd 100644 --- a/sloth/src/parser/ast.rs +++ b/sloth/src/parser/ast.rs @@ -172,7 +172,11 @@ impl Stmt { } StmtKind::DefineVariable { value, .. } => children.push(value.as_node()), StmtKind::AssignVariable { value, .. } => children.push(value.as_node()), - StmtKind::DefineFunction { body, .. } => children.push(body.as_node()), + StmtKind::DefineFunction(Function { kind, .. }) => { + if let FunctionKind::Normal { body } = kind { + children.push(body.as_node()) + } + } StmtKind::Return(value) => children.push(value.as_node()), } @@ -207,15 +211,24 @@ pub enum StmtKind { /// A function definition. Output is None when the function returns nothing /// meaning void, otherwise it is the name of the type the function /// returns. - DefineFunction { - identifier: String, - inputs: Vec, - output: Option, - body: Box, - }, + DefineFunction(Function), Return(Expr), } +#[derive(PartialEq, Clone, Debug)] +pub struct Function { + pub identifier: String, + pub inputs: Vec, + pub output: Option, + pub kind: FunctionKind, +} + +#[derive(PartialEq, Clone, Debug)] +pub enum FunctionKind { + Normal { body: Box }, + Foreign, +} + #[derive(PartialEq, Clone, Debug)] pub struct FunctionInput { pub identifier: String, @@ -259,7 +272,10 @@ impl Display for Literal { Literal::Float(f) => format!("{f}"), Literal::Boolean(b) => format!("{b}"), Literal::Character(c) => format!("'{c}'"), - Literal::String(s) => format!("\"{s}\""), + Literal::String(s) => format!( + "\\\"{}\\\"", + s.replace('\"', "\\\"").replace("\\n", "\\\\n") + ), Literal::Array(..) => "".to_string(), }; diff --git a/sloth/src/parser/graph.rs b/sloth/src/parser/graph.rs index da4bd71..23f46fe 100644 --- a/sloth/src/parser/graph.rs +++ b/sloth/src/parser/graph.rs @@ -1,24 +1,31 @@ use std::fmt::{Error, Write}; -use super::ast::{Expr, ExprKind, Stmt, StmtKind}; +use super::ast::{Expr, ExprKind, Function, FunctionKind, Stmt, StmtKind}; pub struct GraphBuilder { graph: String, } impl GraphBuilder { - pub fn generate(ast: &[Stmt]) -> Result { + pub fn generate(source: Option<&str>, ast: &Stmt) -> Result { let mut this = Self { graph: String::new(), }; this.graph.push_str("digraph {\n"); - for stmt in ast.iter() { - this.traverse_stmt0(stmt)?; - } - for stmt in ast.iter() { - this.traverse_stmt(stmt)?; + + if let Some(source) = source { + let source = source + .replace('\"', "\\\"") + .replace("\\n", "\\\\n") + .replace('\n', "\\l"); + + this.graph.push_str(&format!("label = \"{source}\";")); + this.graph.push_str("labeljust = l; labelloc = t;"); } + + this.traverse_stmt0(ast)?; + this.traverse_stmt(ast)?; this.graph.push('}'); Ok(this.graph) @@ -72,7 +79,7 @@ impl GraphBuilder { } => { writeln!( &mut self.graph, - "N{} [shape=box label=\"DefineVariable\\n\\nIdentifier={}\\nType={}\"];", + "N{} [shape=box label=\"DefineVariable\\n\\nIdentifier={}\\lType={}\\l\"];", stmt.id, identifier, typ )?; self.traverse_expr0(value)?; @@ -85,22 +92,30 @@ impl GraphBuilder { )?; self.traverse_expr0(value)?; } - StmtKind::DefineFunction { + StmtKind::DefineFunction(Function { identifier, inputs, output, - body, - } => { + kind, + }) => { writeln!( &mut self.graph, "N{} [shape=box \ - label=\"DefineFunction\\n\\nIdentifier={}\\nInputs={}\\nOutput={}\"];", + label=\"DefineFunction\\n\\nIdentifier={}\\lInputs={}\\lOutput={}\\lKind={}\\\ + l\"];", stmt.id, identifier, inputs.len(), - output.is_some() + output.is_some(), + match kind { + FunctionKind::Normal { .. } => "Normal", + FunctionKind::Foreign => "Foreign", + } )?; - self.traverse_stmt0(body)?; + + if let FunctionKind::Normal { body } = kind { + self.traverse_stmt0(body)?; + } } StmtKind::Return(expr) => { writeln!( @@ -120,7 +135,7 @@ impl GraphBuilder { ExprKind::Grouping(child) => { writeln!( &mut self.graph, - "N{} [shape=circle label=\"Grouping\"];", + "N{} [shape=box style=rounded label=\"Grouping\"];", expr.id )?; self.traverse_expr0(child)?; @@ -128,21 +143,21 @@ impl GraphBuilder { ExprKind::Literal(literal) => { writeln!( &mut self.graph, - "N{} [shape=diamond label=\"Literal\\n\\nValue={}\"];", + "N{} [shape=box style=\"filled,rounded\" label=\"{}\"];", expr.id, literal )?; } ExprKind::Identifier(identifier) => { writeln!( &mut self.graph, - "N{} [shape=diamond label=\"Identifier\\n\\nIdentifier={}\"];", + "N{} [shape=box style=\"filled,rounded\" label=\"{}\"];", expr.id, identifier )?; } ExprKind::BinaryOp { op, lhs, rhs } => { writeln!( &mut self.graph, - "N{} [shape=circle label=\"{}\"];", + "N{} [shape=box style=rounded label=\"{}\"];", expr.id, op )?; self.traverse_expr0(lhs)?; @@ -151,7 +166,7 @@ impl GraphBuilder { ExprKind::UnaryOp { op, value } => { writeln!( &mut self.graph, - "N{} [shape=circle label=\"Unary {}\"];", + "N{} [shape=box style=rounded label=\"{}\"];", expr.id, op )?; self.traverse_expr0(value)?; @@ -159,7 +174,7 @@ impl GraphBuilder { ExprKind::Call { callee, args } => { writeln!( &mut self.graph, - "N{} [shape=circle label=\"Function Call\"];", + "N{} [shape=box style=rounded label=\"Function Call\"];", expr.id )?; self.traverse_expr0(callee)?; @@ -185,8 +200,17 @@ impl GraphBuilder { self.traverse_expr(expr)?; } StmtKind::IfStmt { - if_then, else_then, .. + condition, + if_then, + else_then, + .. } => { + writeln!( + &mut self.graph, + "N{} -> N{} [label = \"Condition\"];", + stmt.id, condition.id + )?; + self.traverse_expr(condition)?; writeln!( &mut self.graph, "N{} -> N{} [label = \"If Then\"];", @@ -224,15 +248,21 @@ impl GraphBuilder { writeln!(&mut self.graph, "N{} -> N{};", stmt.id, value.id)?; self.traverse_expr(value)?; } - StmtKind::DefineFunction { body, .. } => { - writeln!( - &mut self.graph, - "N{} -> N{} [label = \"Body\"];", - stmt.id, body.id - )?; - self.traverse_stmt(body)?; + StmtKind::DefineFunction(Function { kind, .. }) => { + if let FunctionKind::Normal { body } = kind { + writeln!( + &mut self.graph, + "N{} -> N{} [label = \"Body\"];", + stmt.id, body.id + )?; + + self.traverse_stmt(body)?; + } + } + StmtKind::Return(value) => { + writeln!(&mut self.graph, "N{} -> N{};", stmt.id, value.id)?; + self.traverse_expr(value)?; } - StmtKind::Return(_) => (), } Ok(()) @@ -255,10 +285,14 @@ impl GraphBuilder { self.traverse_expr(value)?; } ExprKind::Call { callee, args } => { - writeln!(&mut self.graph, "N{} -> N{};", expr.id, callee.id)?; + writeln!( + &mut self.graph, + "N{} -> N{} [label=callee];", + expr.id, callee.id + )?; self.traverse_expr(callee)?; for arg in args { - writeln!(&mut self.graph, "N{} -> N{};", expr.id, arg.id)?; + writeln!(&mut self.graph, "N{} -> N{} [label=arg];", expr.id, arg.id)?; self.traverse_expr(arg)?; } } diff --git a/sloth/src/parser/stmt.rs b/sloth/src/parser/stmt.rs index 7897e21..973b9c3 100644 --- a/sloth/src/parser/stmt.rs +++ b/sloth/src/parser/stmt.rs @@ -1,4 +1,4 @@ -use super::ast::{FunctionInput, Stmt, StmtKind}; +use super::ast::{Function, FunctionInput, FunctionKind, Stmt, StmtKind}; use super::{AstParser, ParsingError}; use crate::lexer::TokenType; @@ -7,10 +7,12 @@ impl<'a> AstParser<'a> { match self.peek().tt { TokenType::OpeningBrace => self.block(), + TokenType::Foreign => self.foreign(), + TokenType::If => self.if_stmt(), TokenType::While => self.while_stmt(), TokenType::Var => self.define_variable(), - TokenType::Fn => self.define_function(), + TokenType::Fn => self.define_function(false), TokenType::Return => self.return_stmt(), _ if self.peek2().tt == TokenType::Eq => self.assign_variable(), @@ -18,6 +20,17 @@ impl<'a> AstParser<'a> { } } + fn foreign(&mut self) -> Result { + // Consume the foreign token + self.consume(TokenType::Foreign, "Expected foreign")?; + + match self.peek().tt { + TokenType::Fn => self.define_function(true), + + _ => Err(ParsingError::UnexpectedToken), + } + } + fn if_stmt(&mut self) -> Result { // Consume the if token self.consume(TokenType::If, "Expected if")?; @@ -101,7 +114,7 @@ impl<'a> AstParser<'a> { } // TODO: Make argument types optional - fn define_function(&mut self) -> Result { + fn define_function(&mut self, is_foreign: bool) -> Result { // Consume the fn token self.consume(TokenType::Fn, "Expected fn")?; @@ -120,6 +133,12 @@ impl<'a> AstParser<'a> { identifier: input_identifier, typ: input_type, }); + + if self.peek().tt != TokenType::Comma { + break; + } + + self.consume(TokenType::Comma, "Expected ','")?; } self.consume(TokenType::ClosingParen, "Expected ')'")?; @@ -131,20 +150,27 @@ impl<'a> AstParser<'a> { None }; - // Get the function body - let body = self.block()?; + // Get the function kind + let kind = if is_foreign { + self.consume(TokenType::SemiColon, "Expected semicolon")?; + FunctionKind::Foreign + } else { + FunctionKind::Normal { + body: Box::new(self.block()?), + } + }; - let kind = StmtKind::DefineFunction { + let stmt = StmtKind::DefineFunction(Function { identifier, inputs, output, - body: body.into(), - }; + kind, + }); Ok(Stmt::new( self.reserve_id(), self.line, - kind, + stmt, self.top.clone(), )) } @@ -229,7 +255,9 @@ mod tests { use super::{AstParser, StmtKind}; use crate::lexer::Lexer; - use crate::parser::ast::{BinaryOp, Expr, ExprKind, FunctionInput, Literal, Stmt}; + use crate::parser::ast::{ + BinaryOp, Expr, ExprKind, Function, FunctionInput, FunctionKind, Literal, Stmt, + }; use crate::symtable::SymbolTable; #[test] @@ -301,49 +329,60 @@ mod tests { ) .collect_vec(); - let expected_ast = Ok(Stmt::without_table(11, StmtKind::DefineFunction { - identifier: "foo".to_owned(), - inputs: vec![FunctionInput { - identifier: "bar".to_owned(), - typ: "Int".to_owned(), - }], - output: Some("Int".to_owned()), - body: Box::new(Stmt::without_table( - 10, - StmtKind::Block(vec![ - Stmt::without_table(3, StmtKind::DefineVariable { - identifier: "baz".to_owned(), - value: Expr::without_table(2, ExprKind::BinaryOp { - op: BinaryOp::Add, - lhs: Box::new(Expr::without_table( - 0, - ExprKind::Identifier("bar".to_owned()), - )), - rhs: Box::new(Expr::without_table(1, Literal::Integer(1).into())), - }), - typ: "Int".to_owned(), - }), - Stmt::without_table(7, StmtKind::AssignVariable { - identifier: "baz".to_owned(), - value: Expr::without_table(6, ExprKind::BinaryOp { - op: BinaryOp::Add, - lhs: Box::new(Expr::without_table( - 4, - ExprKind::Identifier("baz".to_owned()), - )), - rhs: Box::new(Expr::without_table(5, Literal::Integer(1).into())), - }), - }), - Stmt::without_table( - 9, - StmtKind::Return(Expr::without_table( - 8, - ExprKind::Identifier("baz".to_owned()), - )), - ), - ]), - )), - })); + let expected_ast = Ok(Stmt::without_table( + 11, + StmtKind::DefineFunction(Function { + identifier: "foo".to_owned(), + inputs: vec![FunctionInput { + identifier: "bar".to_owned(), + typ: "Int".to_owned(), + }], + output: Some("Int".to_owned()), + kind: FunctionKind::Normal { + body: Box::new(Stmt::without_table( + 10, + StmtKind::Block(vec![ + Stmt::without_table(3, StmtKind::DefineVariable { + identifier: "baz".to_owned(), + value: Expr::without_table(2, ExprKind::BinaryOp { + op: BinaryOp::Add, + lhs: Box::new(Expr::without_table( + 0, + ExprKind::Identifier("bar".to_owned()), + )), + rhs: Box::new(Expr::without_table( + 1, + Literal::Integer(1).into(), + )), + }), + typ: "Int".to_owned(), + }), + Stmt::without_table(7, StmtKind::AssignVariable { + identifier: "baz".to_owned(), + value: Expr::without_table(6, ExprKind::BinaryOp { + op: BinaryOp::Add, + lhs: Box::new(Expr::without_table( + 4, + ExprKind::Identifier("baz".to_owned()), + )), + rhs: Box::new(Expr::without_table( + 5, + Literal::Integer(1).into(), + )), + }), + }), + Stmt::without_table( + 9, + StmtKind::Return(Expr::without_table( + 8, + ExprKind::Identifier("baz".to_owned()), + )), + ), + ]), + )), + }, + }), + )); let mut parser = AstParser::new(tokens, SymbolTable::new()); let generated_ast = parser.statement(); diff --git a/sloth/src/symtable.rs b/sloth/src/symtable.rs index 2cb4741..b2bb209 100644 --- a/sloth/src/symtable.rs +++ b/sloth/src/symtable.rs @@ -52,6 +52,24 @@ impl SymbolTable { None } + pub fn get_type(&self, identifier: &str) -> Option { + let symbol = self.get(identifier)?; + if let Symbol::Type(ref typ) = *symbol { + return Some(typ.clone()); + } + + None + } + + pub fn get_value(&self, identifier: &str) -> Option { + let symbol = self.get(identifier)?; + if let Symbol::Value(ref typ) = *symbol { + return Some(typ.clone()); + } + + None + } + pub fn get_mut(&self, identifier: &str) -> Option> { for scope in self.iter() { let reference = scope.symbols.borrow_mut(); @@ -108,18 +126,21 @@ impl<'a> Iterator for Iter<'a> { } #[derive(Debug)] -pub struct Symbol { - pub typ: SymbolType, +pub enum Symbol { + /// Symbol referencing a compile time type, such as the Int symbol + Type(Type), + /// Symbol referencing a runtime value, such as the println symbol + Value(Type), } -impl Symbol { - pub fn new(typ: SymbolType) -> Self { - Self { typ } - } -} - -#[derive(Debug)] -pub enum SymbolType { - Variable, - Function, +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum Type { + Void, + Integer, + Float, + Boolean, + Function { + inputs: Vec, + output: Box, + }, } -- cgit v1.2.3