From 6810fd00a57006447234f627c7dff14cd238593e 2021-05-07 22:49:04 From: MH Date: 2021-05-07 22:49:04 Subject: [PATCH] initial (unused) ast executor --- diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 0d11efdc96bfcbe2dbcff96cba791079112126ea..23d65f22633525765bdb019900c8b6db9f9177a3 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -115,10 +115,7 @@ define_aliased_ast_id!(RootId, Id, index(Root, protocol_descriptions), all define_aliased_ast_id!(PragmaId, Id, index(Pragma, pragmas), alloc(alloc_pragma)); define_aliased_ast_id!(ImportId, Id, index(Import, imports), alloc(alloc_import)); define_aliased_ast_id!(ParserTypeId, Id, index(ParserType, parser_types), alloc(alloc_parser_type)); - -define_aliased_ast_id!(VariableId, Id, index(Variable, variables)); -define_new_ast_id!(ParameterId, VariableId, index(Parameter, Variable::Parameter, variables), alloc(alloc_parameter)); -define_new_ast_id!(LocalId, VariableId, index(Local, Variable::Local, variables), alloc(alloc_local)); +define_aliased_ast_id!(VariableId, Id, index(Variable, variables), alloc(alloc_variable)); define_aliased_ast_id!(DefinitionId, Id, index(Definition, definitions)); define_new_ast_id!(StructDefinitionId, DefinitionId, index(StructDefinition, Definition::Struct, definitions), alloc(alloc_struct_definition)); @@ -661,63 +658,21 @@ impl Scope { } #[derive(Debug, Clone)] -pub enum Variable { - Parameter(Parameter), - Local(Local), +pub enum VariableKind { + Parameter, + Local, } -impl Variable { - pub fn identifier(&self) -> &Identifier { - match self { - Variable::Parameter(var) => &var.identifier, - Variable::Local(var) => &var.identifier, - } - } - pub fn is_parameter(&self) -> bool { - match self { - Variable::Parameter(_) => true, - _ => false, - } - } - pub fn as_parameter(&self) -> &Parameter { - match self { - Variable::Parameter(result) => result, - _ => panic!("Unable to cast `Variable` to `Parameter`"), - } - } - pub fn as_local(&self) -> &Local { - match self { - Variable::Local(result) => result, - _ => panic!("Unable to cast `Variable` to `Local`"), - } - } - pub fn as_local_mut(&mut self) -> &mut Local { - match self { - Variable::Local(result) => result, - _ => panic!("Unable to cast 'Variable' to 'Local'"), - } - } -} - -/// TODO: Remove distinction between parameter/local and add an enum to indicate -/// the distinction between the two #[derive(Debug, Clone)] -pub struct Parameter { - pub this: ParameterId, - // Phase 2: parser - pub span: InputSpan, +pub struct Variable { + pub this: VariableId, + // Parsing + pub kind: VariableKind, pub parser_type: ParserType, pub identifier: Identifier, -} - -#[derive(Debug, Clone)] -pub struct Local { - pub this: LocalId, - // Phase 1: parser - pub identifier: Identifier, - pub parser_type: ParserType, - // Phase 2: linker + // Validator/linker pub relative_pos_in_block: u32, + pub unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated } #[derive(Debug, Clone)] @@ -820,6 +775,13 @@ impl Definition { _ => panic!("Unable to cast `Definition` to `Function`"), } } + pub fn parameters(&self) -> &Vec { + match self { + Definition::Component(def) => &def.parameters, + Definition::Function(def) => &def.parameters, + _ => panic!("Called parameters() on {:?}", self) + } + } pub fn defined_in(&self) -> RootId { match self { Definition::Struct(def) => def.defined_in, @@ -838,20 +800,6 @@ impl Definition { Definition::Function(def) => &def.identifier, } } - pub fn parameters(&self) -> &Vec { - match self { - Definition::Component(com) => &com.parameters, - Definition::Function(fun) => &fun.parameters, - _ => panic!("cannot retrieve parameters for {:?}", self), - } - } - pub fn body(&self) -> BlockStatementId { - match self { - Definition::Component(com) => com.body, - Definition::Function(fun) => fun.body, - _ => panic!("cannot retrieve body for {:?}", self), - } - } pub fn poly_vars(&self) -> &Vec { match self { Definition::Struct(def) => &def.poly_vars, @@ -974,7 +922,7 @@ pub struct ComponentDefinition { pub identifier: Identifier, pub poly_vars: Vec, // Phase 2: parsing - pub parameters: Vec, + pub parameters: Vec, pub body: BlockStatementId, } @@ -1004,7 +952,7 @@ pub struct FunctionDefinition { pub poly_vars: Vec, // Phase 2: parsing pub return_types: Vec, - pub parameters: Vec, + pub parameters: Vec, pub body: BlockStatementId, } @@ -1221,14 +1169,14 @@ impl Statement { match self { Statement::Block(_) => todo!(), Statement::Local(stmt) => match stmt { - LocalStatement::Channel(stmt) => stmt.next = Some(next), - LocalStatement::Memory(stmt) => stmt.next = Some(next), + LocalStatement::Channel(stmt) => stmt.next = next, + LocalStatement::Memory(stmt) => stmt.next = next, }, - Statement::EndIf(stmt) => stmt.next = Some(next), - Statement::EndWhile(stmt) => stmt.next = Some(next), - Statement::EndSynchronous(stmt) => stmt.next = Some(next), - Statement::New(stmt) => stmt.next = Some(next), - Statement::Expression(stmt) => stmt.next = Some(next), + Statement::EndIf(stmt) => stmt.next = next, + Statement::EndWhile(stmt) => stmt.next = next, + Statement::EndSynchronous(stmt) => stmt.next = next, + Statement::New(stmt) => stmt.next = next, + Statement::Expression(stmt) => stmt.next = next, Statement::Return(_) | Statement::Break(_) | Statement::Continue(_) @@ -1249,7 +1197,9 @@ pub struct BlockStatement { pub span: InputSpan, // of the complete block pub statements: Vec, // Phase 2: linker - pub parent_scope: Option, + pub parent_scope: Scope, + pub first_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated + pub next_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated pub relative_pos_in_parent: u32, pub locals: Vec, pub labels: Vec, @@ -1315,12 +1265,6 @@ impl LocalStatement { LocalStatement::Memory(v) => v.span, } } - pub fn next(&self) -> Option { - match self { - LocalStatement::Memory(stmt) => stmt.next, - LocalStatement::Channel(stmt) => stmt.next, - } - } } #[derive(Debug, Clone)] @@ -1328,9 +1272,9 @@ pub struct MemoryStatement { pub this: MemoryStatementId, // Phase 1: parser pub span: InputSpan, - pub variable: LocalId, + pub variable: VariableId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } /// ChannelStatement is the declaration of an input and output port associated @@ -1343,11 +1287,11 @@ pub struct ChannelStatement { pub this: ChannelStatementId, // Phase 1: parser pub span: InputSpan, // of the "channel" keyword - pub from: LocalId, // output - pub to: LocalId, // input + pub from: VariableId, // output + pub to: VariableId, // input // Phase 2: linker pub relative_pos_in_block: u32, - pub next: Option, + pub next: StatementId, } #[derive(Debug, Clone)] @@ -1378,7 +1322,7 @@ pub struct EndIfStatement { pub this: EndIfStatementId, pub start_if: IfStatementId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } #[derive(Debug, Clone)] @@ -1398,7 +1342,7 @@ pub struct EndWhileStatement { pub this: EndWhileStatementId, pub start_while: WhileStatementId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } #[derive(Debug, Clone)] @@ -1437,7 +1381,7 @@ pub struct EndSynchronousStatement { pub this: EndSynchronousStatementId, pub start_sync: SynchronousStatementId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } #[derive(Debug, Clone)] @@ -1465,7 +1409,7 @@ pub struct NewStatement { pub span: InputSpan, // of the "new" keyword pub expression: CallExpressionId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } #[derive(Debug, Clone)] @@ -1475,7 +1419,7 @@ pub struct ExpressionStatement { pub span: InputSpan, pub expression: ExpressionId, // Phase 2: linker - pub next: Option, + pub next: StatementId, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -1669,7 +1613,7 @@ impl Expression { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum AssignmentOperator { Set, Multiplied, @@ -1687,45 +1631,45 @@ pub enum AssignmentOperator { #[derive(Debug, Clone)] pub struct AssignmentExpression { pub this: AssignmentExpressionId, - // Phase 2: parser + // Parsing pub span: InputSpan, // of the operator pub left: ExpressionId, pub operation: AssignmentOperator, pub right: ExpressionId, - // Phase 3: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 4: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct BindingExpression { pub this: BindingExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, pub left: LiteralExpressionId, pub right: ExpressionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct ConditionalExpression { pub this: ConditionalExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, // of question mark operator pub test: ExpressionId, pub true_expression: ExpressionId, pub false_expression: ExpressionId, - // Phase 2: linker + // Validator/Linking pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinaryOperator { Concatenate, LogicalOr, @@ -1751,19 +1695,19 @@ pub enum BinaryOperator { #[derive(Debug, Clone)] pub struct BinaryExpression { pub this: BinaryExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, // of the operator pub left: ExpressionId, pub operation: BinaryOperator, pub right: ExpressionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum UnaryOperation { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UnaryOperator { Positive, Negative, BitwiseNot, @@ -1777,68 +1721,68 @@ pub enum UnaryOperation { #[derive(Debug, Clone)] pub struct UnaryExpression { pub this: UnaryExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, // of the operator - pub operation: UnaryOperation, + pub operation: UnaryOperator, pub expression: ExpressionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct IndexingExpression { pub this: IndexingExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, pub subject: ExpressionId, pub index: ExpressionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct SlicingExpression { pub this: SlicingExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, // from '[' to ']'; pub subject: ExpressionId, pub from_index: ExpressionId, pub to_index: ExpressionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct SelectExpression { pub this: SelectExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, // of the '.' pub subject: ExpressionId, pub field: Field, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } #[derive(Debug, Clone)] pub struct CallExpression { pub this: CallExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, pub parser_type: ParserType, // of the function call, not the return type pub method: Method, pub arguments: Vec, pub definition: DefinitionId, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, // of the return type } @@ -1864,12 +1808,12 @@ pub struct MethodSymbolic { #[derive(Debug, Clone)] pub struct LiteralExpression { pub this: LiteralExpressionId, - // Phase 1: parser + // Parsing pub span: InputSpan, pub value: Literal, - // Phase 2: linker + // Validator/Linker pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } @@ -1968,11 +1912,11 @@ pub struct LiteralUnion { #[derive(Debug, Clone)] pub struct VariableExpression { pub this: VariableExpressionId, - // Phase 1: parser + // Parsing pub identifier: Identifier, - // Phase 2: linker + // Validator/Linker pub declaration: Option, pub parent: ExpressionParent, - // Phase 3: type checking + // Typing pub concrete_type: ConcreteType, } \ No newline at end of file diff --git a/src/protocol/ast_printer.rs b/src/protocol/ast_printer.rs index 5b4ff24bb9d0d1c0af587cd7ff5a937d47b39533..1111946971c5c0ba6e8dc61b9b691b103a35910e 100644 --- a/src/protocol/ast_printer.rs +++ b/src/protocol/ast_printer.rs @@ -14,8 +14,6 @@ const PREFIX_PRAGMA_ID: &'static str = "Prag"; const PREFIX_IMPORT_ID: &'static str = "Imp "; const PREFIX_TYPE_ANNOT_ID: &'static str = "TyAn"; const PREFIX_VARIABLE_ID: &'static str = "Var "; -const PREFIX_PARAMETER_ID: &'static str = "Par "; -const PREFIX_LOCAL_ID: &'static str = "Loc "; const PREFIX_DEFINITION_ID: &'static str = "Def "; const PREFIX_STRUCT_ID: &'static str = "DefS"; const PREFIX_ENUM_ID: &'static str = "DefE"; @@ -409,9 +407,13 @@ impl ASTWriter { Statement::Block(stmt) => { self.kv(indent).with_id(PREFIX_BLOCK_STMT_ID, stmt.this.0.index) .with_s_key("Block"); + self.kv(indent2).with_s_key("FirstUniqueScopeID").with_disp_val(&stmt.first_unique_id_in_scope); + self.kv(indent2).with_s_key("NextUniqueScopeID").with_disp_val(&stmt.next_unique_id_in_scope); + self.kv(indent2).with_s_key("RelativePos").with_disp_val(&stmt.relative_pos_in_parent); + self.kv(indent2).with_s_key("Statements"); for stmt_id in &stmt.statements { - self.write_stmt(heap, *stmt_id, indent2); + self.write_stmt(heap, *stmt_id, indent3); } }, Statement::Local(stmt) => { @@ -421,9 +423,9 @@ impl ASTWriter { .with_s_key("LocalChannel"); self.kv(indent2).with_s_key("From"); - self.write_local(heap, stmt.from, indent3); + self.write_variable(heap, stmt.from, indent3); self.kv(indent2).with_s_key("To"); - self.write_local(heap, stmt.to, indent3); + self.write_variable(heap, stmt.to, indent3); self.kv(indent2).with_s_key("Next") .with_opt_disp_val(stmt.next.as_ref().map(|v| &v.index)); }, @@ -432,7 +434,7 @@ impl ASTWriter { .with_s_key("LocalMemory"); self.kv(indent2).with_s_key("Variable"); - self.write_local(heap, stmt.variable, indent3); + self.write_variable(heap, stmt.variable, indent3); self.kv(indent2).with_s_key("Next") .with_opt_disp_val(stmt.next.as_ref().map(|v| &v.index)); } @@ -788,16 +790,19 @@ impl ASTWriter { } } - fn write_local(&mut self, heap: &Heap, local_id: LocalId, indent: usize) { - let local = &heap[local_id]; + fn write_variable(&mut self, heap: &Heap, variable_id: VariableId, indent: usize) { + let var = &heap[variable_id]; let indent2 = indent + 1; - self.kv(indent).with_id(PREFIX_LOCAL_ID, local_id.0.index) - .with_s_key("Local"); + self.kv(indent).with_id(PREFIX_VARIABLE_ID, variable_id.0.index) + .with_s_key("Variable"); - self.kv(indent2).with_s_key("Name").with_identifier_val(&local.identifier); + self.kv(indent2).with_s_key("Name").with_identifier_val(&var.identifier); + self.kv(indent2).with_s_key("Kind").with_debug_val(&var.kind); self.kv(indent2).with_s_key("ParserType") - .with_custom_val(|w| write_parser_type(w, heap, &local.parser_type)); + .with_custom_val(|w| write_parser_type(w, heap, &var.parser_type)); + self.kv(indent2).with_s_key("RelativePos").with_disp_val(&var.relative_pos_in_block); + self.kv(indent2).with_s_key("UniqueScopeID").with_disp_val(&var.unique_id_in_scope); } //-------------------------------------------------------------------------- diff --git a/src/protocol/eval/mod.rs b/src/protocol/eval/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..83d73efd843af124e3a0f27f213407d3e5809ff8 --- /dev/null +++ b/src/protocol/eval/mod.rs @@ -0,0 +1,732 @@ +/// eval +/// +/// Evaluator of the generated AST. Note that we use some misappropriated terms +/// to describe where values live and what they do. This is a temporary +/// implementation of an evaluator until some kind of appropriate bytecode or +/// machine code is generated. +/// +/// Code is always executed within a "frame". For Reowolf the first frame is +/// usually an executed component. All subsequent frames are function calls. +/// Simple values live on the "stack". Each variable/parameter has a place on +/// the stack where its values are stored. If the value is not a primitive, then +/// its value will be stored in the "heap". Expressions are treated differently +/// and use a separate "stack" for their evaluation. +/// +/// Since this is a value-based language, most values are copied. One has to be +/// careful with values that reside in the "heap" and make sure that copies are +/// properly removed from the heap.. +/// +/// Just to reiterate: this is a temporary implementation. A proper +/// implementation would fully fill out the type table with alignment/size/ +/// offset information and lay out bytecode. + +mod value; + +use std::collections::VecDeque; + +use super::value::*; +use crate::PortId; +use crate::protocol::ast::*; +use crate::protocol::*; + +struct HeapAllocation { + values: Vec, +} + +struct Store { + // The stack where variables/parameters are stored. Note that this is a + // non-shrinking stack. So it may be filled with garbage. + stack: Vec, + // Represents the place in the stack where we find the `PrevStackBoundary` + // value containing the previous stack boundary. This is so we can pop from + // the stack after function calls. + cur_stack_boundary: usize, + // A rather ridiculous simulated heap, but this allows us to "allocate" + // things that occupy more then one stack slot. + heap_regions: Vec, + free_regions: VecDeque, +} + +impl Store { + fn new() -> Self { + let mut store = Self{ + stack: Vec::with_capacity(64), + cur_stack_boundary: 0, + heap_regions: Vec::new(), + free_regions: VecDeque::new(), + }; + + store.stack.push(Value::PrevStackBoundary(-1)); + store + } + + /// Resizes(!) the stack to fit the required number of values. Any + /// unallocated slots are initialized to `Unassigned`. The specified stack + /// index is exclusive. + fn reserve_stack(&mut self, unique_stack_idx: usize) { + let new_size = self.cur_stack_boundary + unique_stack_idx + 1; + if new_size > self.stack.len() { + self.stack.resize(new_size, Value::Unassigned); + } + } + + /// Clears values on the stack and removes their heap allocations when + /// applicable. The specified index itself will also be cleared (so if you + /// specify 0 all values in the frame will be destroyed) + fn clear_stack(&mut self, unique_stack_idx: usize) { + let new_size = self.cur_stack_boundary + unique_stack_idx + 1; + for idx in new_size..self.stack.len() { + self.drop_value(&self.stack[idx]); + self.stack[idx] = Value::Unassigned; + } + } + + /// Reads a value from a specific address. The value is always copied, hence + /// if the value ends up not being written, one should call `drop_value` on + /// it. + fn read_copy(&mut self, address: ValueId) -> Value { + match value { + ValueId::Stack(pos) => { + let cur_pos = self.cur_stack_boundary + 1 + pos as usize; + return self.clone_value(&self.stack[cur_pos]); + }, + ValueId::Heap(heap_pos, region_idx) => { + return self.clone_value(&self.heap_regions[heap_pos as usize].values[region_idx as usize]) + } + } + } + + /// Returns an immutable reference to the value pointed to by an address + fn read_ref(&mut self, address: ValueId) -> &Value { + match value { + ValueId::Stack(pos) => { + let cur_pos = self.cur_stack_boundary + 1 + pos as usize; + return &self.stack[cur_pos]; + }, + ValueId::Heap(heap_pos, region_idx) => { + return &self.heap_regions[heap_pos as usize].values[region_idx as usize]; + } + } + } + + /// Returns a mutable reference to the value pointed to by an address + fn read_mut_ref(&mut self, address: ValueId) -> &mut Value { + match value { + ValueId::Stack(pos) => { + let cur_pos = self.cur_stack_boundary + 1 + pos as usize; + return &mut self.stack[cur_pos]; + }, + ValueId::Heap(heap_pos, region_idx) => { + return &mut self.heap_regions[heap_pos as usize].values[region_idx as usize]; + } + } + } + + /// Writes a value + fn write(&mut self, address: ValueId, value: Value) { + match address { + ValueId::Stack(pos) => { + let cur_pos = self.cur_stack_boundary + 1 + pos as usize; + self.drop_value(&self.stack[cur_pos]); + self.stack[cur_pos] = value; + }, + ValueId::Heap(heap_pos, region_idx) => { + let heap_pos = heap_pos as usize; + let region_idx = region_idx as usize; + self.drop_value(&self.heap_regions[heap_pos].values[region_idx]); + self.heap_regions[heap_pos].values[region_idx] = value + } + } + } + + fn clone_value(&mut self, value: &Value) -> Value { + // Quickly check if the value is not on the heap + let source_heap_pos = value.get_heap_pos(); + if source_heap_pos.is_none() { + // We can do a trivial copy + return value.clone(); + } + + // Value does live on heap, copy it + let source_heap_pos = source_heap_pos.unwrap() as usize; + let target_heap_pos = self.alloc_heap(); + let target_heap_pos_usize = target_heap_pos as usize; + + let num_values = self.heap_regions[source_heap_pos].values.len(); + for value_idx in 0..num_values { + let cloned = self.clone_value(&self.heap_regions[source_heap_pos].values[value_idx]); + self.heap_regions[target_heap_pos_usize].values.push(cloned); + } + + match value { + Value::Message(_) => Value::Message(target_heap_pos), + Value::Array(_) => Value::Array(target_heap_pos), + Value::Union(tag, _) => Value::Union(*tag, target_heap_pos), + Value::Struct(_) => Value::Struct(target_heap_pos), + _ => unreachable!("performed clone_value on heap, but {:?} is not a heap value", value), + } + } + + fn drop_value(&mut self, value: &Value) { + if let Some(heap_pos) = value.get_heap_pos() { + self.drop_heap_pos(heap_pos); + } + } + + fn drop_heap_pos(&mut self, heap_pos: HeapPos) { + let num_values = self.heap_regions[heap_pos as usize].values.len(); + for value_idx in 0..num_values { + if let Some(other_heap_pos) = self.heap_regions[heap_pos as usize].values[value_idx].get_heap_pos() { + self.drop_heap_pos(other_heap_pos); + } + } + + self.heap_regions[heap_pos as usize].values.clear(); + self.free_regions.push_back(heap_pos); + } + + fn alloc_heap(&mut self) -> HeapPos { + if self.free_regions.is_empty() { + let idx = self.heap_regions.len() as HeapPos; + self.heap_regions.push(HeapAllocation{ values: Vec::new() }); + return idx; + } else { + let idx = self.free_regions.pop_back().unwrap(); + return idx; + } + } +} + +enum ExprInstruction { + EvalExpr(ExpressionId), + PushValToFront, +} + +struct Frame { + definition: DefinitionId, + position: StatementId, + expr_stack: VecDeque, // hack for expression evaluation, evaluated by popping from back + expr_values: VecDeque, // hack for expression results, evaluated by popping from front/back +} + +impl Frame { + /// Creates a new execution frame. Does not modify the stack in any way. + pub fn new(heap: &Heap, definition_id: DefinitionId) -> Self { + let definition = &heap[definition_id]; + let first_statement = match definition { + Definition::Component(definition) => definition.body, + Definition::Function(definition) => definition.body, + _ => unreachable!("initializing frame with {:?} instead of a function/component", definition), + }; + + Frame{ + definition: definition_id, + position: first_statement.upcast(), + expr_stack: VecDeque::with_capacity(128), + expr_values: VecDeque::with_capacity(128), + } + } + + /// Prepares a single expression for execution. This involves walking the + /// expression tree and putting them in the `expr_stack` such that + /// continuously popping from its back will evaluate the expression. The + /// results of each expression will be stored by pushing onto `expr_values`. + pub fn prepare_single_expression(&mut self, heap: &Heap, expr_id: ExpressionId) { + debug_assert!(self.expr_stack.is_empty()); + self.expr_values.clear(); // May not be empty if last expression result(s) were discarded + + self.serialize_expression(heap, expr_id); + } + + /// Prepares multiple expressions for execution (i.e. evaluating all + /// function arguments or all elements of an array/union literal). Per + /// expression this works the same as `prepare_single_expression`. However + /// after each expression is evaluated we insert a `PushValToFront` + /// instruction + pub fn prepare_multiple_expressions(&mut self, heap: &Heap, expr_ids: &[ExpressionId]) { + debug_assert!(self.expr_stack.is_empty()); + self.expr_values.clear(); + + for expr_id in expr_ids { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, *expr_id); + } + } + + /// Performs depth-first serialization of expression tree. Let's not care + /// about performance for a temporary runtime implementation + fn serialize_expression(&mut self, heap: &Heap, id: ExpressionId) { + self.expr_stack.push_back(ExprInstruction::EvalExpr(id)); + + match &heap[id] { + Expression::Assignment(expr) => { + self.serialize_expression(heap, expr.left); + self.serialize_expression(heap, expr.right); + }, + Expression::Binding(expr) => { + todo!("implement binding expression"); + }, + Expression::Conditional(expr) => { + self.serialize_expression(heap, expr.test); + }, + Expression::Binary(expr) => { + self.serialize_expression(heap, expr.left); + self.serialize_expression(heap, expr.right); + }, + Expression::Unary(expr) => { + self.serialize_expression(heap, expr.expression); + }, + Expression::Indexing(expr) => { + self.serialize_expression(heap, expr.index); + self.serialize_expression(heap, expr.subject); + }, + Expression::Slicing(expr) => { + self.serialize_expression(heap, expr.from_index); + self.serialize_expression(heap, expr.to_index); + self.serialize_expression(heap, expr.subject); + }, + Expression::Select(expr) => { + self.serialize_expression(heap, expr.subject); + }, + Expression::Literal(expr) => { + // Here we only care about literals that have subexpressions + match &expr.value { + Literal::Null | Literal::True | Literal::False | + Literal::Character(_) | Literal::String(_) | + Literal::Integer(_) | Literal::Enum(_) => { + // No subexpressions + }, + Literal::Struct(literal) => { + for field in &literal.fields { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, field.value); + } + }, + Literal::Union(literal) => { + for value_expr_id in &literal.values { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, *value_expr_id); + } + }, + Literal::Array(value_expr_ids) => { + for value_expr_id in value_expr_ids { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, *value_expr_id); + } + } + } + }, + Expression::Call(expr) => { + for arg_expr_id in &expr.arguments { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, *arg_expr_id); + } + }, + Expression::Variable(expr) => { + // No subexpressions + } + } + } +} + +type EvalResult = Result<(), EvalContinuation>; +pub enum EvalContinuation { + Stepping, + Inconsistent, + Terminal, + SyncBlockStart, + SyncBlockEnd, + NewComponent(DefinitionId, Vec), + BlockFires(Value), + BlockGet(Value), + Put(Value, Value), +} + +pub struct Prompt { + frames: Vec, + store: Store, +} + +impl Prompt { + pub fn new(heap: &Heap, def: DefinitionId) -> Self { + let mut prompt = Self{ + frames: Vec::new(), + store: Store::new(), + }; + + prompt.frames.push(Frame::new(heap, def)); + prompt + } + + pub fn step(&mut self, heap: &Heap, ctx: &mut EvalContext) -> EvalResult { + let cur_frame = self.frames.last_mut().unwrap(); + if cur_frame.position.is_invalid() { + if heap[cur_frame.definition].is_function() { + todo!("End of function without return, return an evaluation error"); + } + return Err(EvalContinuation::Terminal); + } + + while !cur_frame.expr_stack.is_empty() { + let next = cur_frame.expr_stack.pop_back().unwrap(); + match next { + ExprInstruction::PushValToFront => { + cur_frame.expr_values.rotate_right(1); + }, + ExprInstruction::EvalExpr(expr_id) => { + let expr = &heap[expr_id]; + match expr { + Expression::Assignment(expr) => { + let to = cur_frame.expr_values.pop_back().unwrap().as_ref(); + let rhs = cur_frame.expr_values.pop_back().unwrap(); + apply_assignment_operator(&mut self.store, to, expr.operation, rhs); + cur_frame.expr_values.push_back(self.store.read_copy(to)); + self.store.drop_value(&rhs); + }, + Expression::Binding(_expr) => { + todo!("Binding expression"); + }, + Expression::Conditional(expr) => { + // Evaluate testing expression, then extend the + // expression stack with the appropriate expression + let test_result = cur_frame.expr_values.pop_back().unwrap().as_bool(); + if test_result { + cur_frame.serialize_expression(heap, expr.true_expression); + } else { + cur_frame.serialize_expression(heap, expr.false_expression); + } + }, + Expression::Binary(expr) => { + let lhs = cur_frame.expr_values.pop_back().unwrap(); + let rhs = cur_frame.expr_values.pop_back().unwrap(); + let result = apply_binary_operator(&mut self.store, &lhs, expr.operation, &rhs); + cur_frame.expr_values.push_back(result); + self.store.drop_value(&lhs); + self.store.drop_value(&rhs); + }, + Expression::Unary(expr) => { + let val = cur_frame.expr_values.pop_back().unwrap(); + let result = apply_unary_operator(&mut self.store, expr.operation, &val); + cur_frame.expr_values.push_back(result); + self.store.drop_value(&val); + }, + Expression::Indexing(expr) => { + // TODO: Out of bounds checking + // Evaluate index. Never heap allocated so we do + // not have to drop it. + let index = cur_frame.expr_values.pop_back().unwrap(); + let index = match &index { + Value::Ref(value_ref) => self.store.read_ref(*value_ref), + index => index, + }; + debug_assert!(deref.is_integer()); + let index = if index.is_signed_integer() { + index.as_signed_integer() as u32 + } else { + index.as_unsigned_integer() as u32 + }; + + let subject = cur_frame.expr_values.pop_back().unwrap(); + let heap_pos = match val { + Value::Ref(value_ref) => self.store.read_ref(value_ref).as_array(), + val => val.as_array(), + }; + + cur_frame.expr_values.push_back(Value::Ref(ValueId::Heap(heap_pos, index))); + self.store.drop_value(&subject); + }, + Expression::Slicing(expr) => { + // TODO: Out of bounds checking + todo!("implement slicing") + }, + Expression::Select(expr) => { + let subject= cur_frame.expr_values.pop_back().unwrap(); + let heap_pos = match &subject { + Value::Ref(value_ref) => self.store.read_ref(*value_ref).as_struct(), + subject => subject.as_struct(), + }; + + cur_frame.expr_values.push_back(Value::Ref(ValueId::Heap(heap_pos, expr.field.as_symbolic().field_idx as u32))); + self.store.drop_value(&subject); + }, + Expression::Literal(expr) => { + let value = match &expr.value { + Literal::Null => Value::Null, + Literal::True => Value::Bool(true), + Literal::False => Value::Bool(false), + Literal::Character(lit_value) => Value::Char(*lit_value), + Literal::String(lit_value) => { + let heap_pos = self.store.alloc_heap(); + let values = &mut self.store.heap_regions[heap_pos as usize].values; + let value = lit_value.as_str(); + debug_assert!(values.is_empty()); + values.reserve(value.len()); + for character in value.as_bytes() { + debug_assert!(character.is_ascii()); + values.push(Value::Char(*character as char)); + } + Value::String(heap_pos) + } + Literal::Integer(lit_value) => { + use ConcreteTypePart as CTP; + debug_assert_eq!(expr.concrete_type.parts.len(), 1); + match expr.concrete_type.parts[0] { + CTP::UInt8 => Value::UInt8(lit_value.unsigned_value as u8), + CTP::UInt16 => Value::UInt16(lit_value.unsigned_value as u16), + CTP::UInt32 => Value::UInt32(lit_value.unsigned_value as u32), + CTP::UInt64 => Value::UInt64(lit_value.unsigned_value as u64), + CTP::SInt8 => Value::SInt8(lit_value.unsigned_value as i8), + CTP::SInt16 => Value::SInt16(lit_value.unsigned_value as i16), + CTP::SInt32 => Value::SInt32(lit_value.unsigned_value as i32), + CTP::SInt64 => Value::SInt64(lit_value.unsigned_value as i64), + _ => unreachable!(), + } + } + Literal::Struct(lit_value) => { + let heap_pos = self.store.alloc_heap(); + let num_fields = lit_value.fields.len(); + let values = &mut self.store.heap_regions[heap_pos as usize].values; + debug_assert!(values.is_empty()); + values.reserve(num_fields); + for _ in 0..num_fields { + values.push(cur_frame.expr_values.pop_front().unwrap()); + } + Value::Struct(heap_pos) + } + Literal::Enum(lit_value) => { + Value::Enum(lit_value.variant_idx as i64); + } + Literal::Union(lit_value) => { + let heap_pos = self.store.alloc_heap(); + let num_values = lit_value.values.len(); + let values = &mut self.store.heap_regions[heap_pos as usize].values; + debug_assert!(values.is_empty()); + values.reserve(num_values); + for _ in 0..num_values { + values.push(cur_frame.expr_values.pop_front().unwrap()); + } + Value::Union(lit_value.variant_idx as i64, heap_pos) + } + Literal::Array(lit_value) => { + let heap_pos = self.store.alloc_heap(); + let num_values = lit_value.len(); + let values = &mut self.store.heap_regions[heap_pos as usize].values; + debug_assert!(values.is_empty()); + values.reserve(num_values); + for _ in 0..num_values { + values.push(cur_frame.expr_values.pop_front().unwrap()) + } + } + }; + + cur_frame.expr_values.push_back(value); + }, + Expression::Call(expr) => { + // Push a new frame. Note that all expressions have + // been pushed to the front, so they're in the order + // of the definition. + let num_args = expr.arguments.len(); + + // Prepare stack for a new frame + let cur_stack_boundary = self.store.cur_stack_boundary; + self.store.cur_stack_boundary = self.store.stack.len(); + self.store.stack.push(Value::PrevStackBoundary(cur_stack_boundary as isize)); + for _ in 0..num_args { + self.store.stack.push(cur_frame.expr_values.pop_front().unwrap()); + } + + // Push the new frame + self.frames.push(Frame::new(heap, expr.definition)); + + // To simplify the logic a little bit we will now + // return and ask our caller to call us again + return Err(EvalContinuation::Stepping); + }, + Expression::Variable(expr) => { + let variable = &heap[expr.declaration.unwrap()]; + cur_frame.expr_values.push_back(Value::Ref(ValueId::Stack(variable.unique_id_in_scope as StackPos))); + } + } + } + } + } + + // No (more) expressions to evaluate. So evaluate statement (that may + // depend on the result on the last evaluated expression(s)) + let stmt = &heap[cur_frame.position]; + let return_value = match stmt { + Statement::Block(stmt) => { + // Reserve space on stack, but also make sure excess stack space + // is cleared + self.store.clear_stack(stmt.first_unique_id_in_scope as usize); + self.store.reserve_stack(stmt.next_unique_id_in_scope as usize); + cur_frame.position = stmt.statements[0]; + + Ok(()) + }, + Statement::Local(stmt) => { + match stmt { + LocalStatement::Memory(stmt) => { + let variable = &heap[stmt.variable]; + self.store.write(ValueId::Stack(variable.unique_id_in_scope as u32), Value::Unassigned); + + cur_frame.position = stmt.next; + }, + LocalStatement::Channel(stmt) => { + let [from_value, to_value] = ctx.new_channel(); + self.store.write(ValueId::Stack(heap[stmt.from].unique_id_in_scope as u32), from_value); + self.store.write(ValueId::Stack(heap[stmt.to].unique_id_in_scope as u32), to_value); + + cur_frame.position = stmt.next; + } + } + + Ok(()) + }, + Statement::Labeled(stmt) => { + cur_frame.position = stmt.body; + + Ok(()) + }, + Statement::If(stmt) => { + debug_assert_eq!(cur_frame.expr_values.len(), 1, "expected one expr value for if statement"); + let test_value = cur_frame.expr_values.pop_back().unwrap().as_bool(); + if test_value { + cur_frame.position = stmt.true_body.upcast(); + } else if let Some(false_body) = stmt.false_body { + cur_frame.position = false_body.upcast(); + } else { + // Not true, and no false body + cur_frame.position = stmt.end_if.unwrap().upcast(); + } + + Ok(()) + }, + Statement::EndIf(stmt) => { + cur_frame.position = stmt.next; + Ok(()) + }, + Statement::While(stmt) => { + debug_assert_eq!(cur_frame.expr_values.len(), 1, "expected one expr value for while statement"); + let test_value = cur_frame.expr_values.pop_back().unwrap().as_bool(); + if test_value { + cur_frame.position = stmt.body.upcast(); + } else { + cur_frame.position = stmt.end_while.unwrap().upcast(); + } + + Ok(()) + }, + Statement::EndWhile(stmt) => { + cur_frame.position = stmt.next; + + Ok(()) + }, + Statement::Break(stmt) => { + cur_frame.position = stmt.target.unwrap().upcast(); + + Ok(()) + }, + Statement::Continue(stmt) => { + cur_frame.position = stmt.target.unwrap().upcast(); + + Ok(()) + }, + Statement::Synchronous(stmt) => { + cur_frame.position = stmt.body.upcast(); + + Err(EvalContinuation::SyncBlockStart) + }, + Statement::EndSynchronous(stmt) => { + cur_frame.position = stmt.next; + + Err(EvalContinuation::SyncBlockEnd) + }, + Statement::Return(stmt) => { + debug_assert!(heap[cur_frame.definition].is_function()); + debug_assert_eq!(cur_frame.expr_values.len(), 1, "expected one expr value for return statement"); + + // Clear any values in the current stack frame + self.store.clear_stack(0); + + // The preceding frame has executed a call, so is expecting the + // return expression on its expression value stack. + let return_value = cur_frame.expr_values.pop_back().unwrap(); + let prev_stack_idx = self.store.stack[self.store.cur_stack_boundary].as_stack_boundary(); + debug_assert!(prev_stack_idx >= 0); + self.store.cur_stack_boundary = prev_stack_idx as usize; + self.frames.pop(); + let cur_frame = self.frames.last_mut().unwrap(); + cur_frame.expr_values.push_back(return_value); + + // Immediately return, we don't care about the current frame + // anymore and there is nothing left to evaluate + return Ok(()); + }, + Statement::Goto(stmt) => { + cur_frame.position = stmt.target.unwrap().upcast(); + + Ok(()) + }, + Statement::New(stmt) => { + let call_expr = &heap[stmt.expression]; + debug_assert!(heap[call_expr.definition].is_component()); + debug_assert_eq!( + cur_frame.expr_values.len(), heap[call_expr.definition].parameters().len(), + "mismatch in expr stack size and number of arguments for new statement" + ); + + // Note that due to expression value evaluation they exist in + // reverse order on the stack. + // TODO: Revise this code, keep it as is to be compatible with current runtime + let mut args = Vec::new(); + while let Some(value) = cur_frame.expr_values.pop_front() { + args.push(value); + } + + cur_frame.position = stmt.next; + + todo!("Make sure this is handled correctly, transfer 'heap' values to another Prompt"); + Err(EvalContinuation::NewComponent(call_expr.definition, args)) + }, + Statement::Expression(stmt) => { + // The expression has just been completely evaluated. Some + // values might have remained on the expression value stack. + cur_frame.expr_values.clear(); + cur_frame.position = stmt.next; + + Ok(()) + }, + }; + + // If the next statement requires evaluating expressions then we push + // these onto the expression stack. This way we will evaluate this + // stack in the next loop, then evaluate the statement using the result + // from the expression evaluation. + if !cur_frame.position.is_invalid() { + let stmt = &heap[cur_frame.position]; + + match stmt { + Statement::If(stmt) => cur_frame.prepare_single_expression(heap, stmt.test), + Statement::While(stmt) => cur_frame.prepare_single_expression(heap, stmt.test), + Statement::Return(stmt) => { + debug_assert_eq!(stmt.expressions.len(), 1); // TODO: @ReturnValues + cur_frame.prepare_single_expression(heap, stmt.expressions[0]); + }, + Statement::New(stmt) => { + // Note that we will end up not evaluating the call itself. + // Rather we will evaluate its expressions and then + // instantiate the component upon reaching the "new" stmt. + let call_expr = &heap[stmt.expression]; + cur_frame.prepare_multiple_expressions(heap, &call_expr.arguments); + }, + Statement::Expression(stmt) => { + cur_frame.prepare_single_expression(heap, stmt.expression); + } + _ => {}, + } + } + + return_value + } +} \ No newline at end of file diff --git a/src/protocol/eval/value.rs b/src/protocol/eval/value.rs new file mode 100644 index 0000000000000000000000000000000000000000..8dc006f2f8ced17594cc867729b5e92486996f27 --- /dev/null +++ b/src/protocol/eval/value.rs @@ -0,0 +1,318 @@ + +use crate::PortId; +use crate::protocol::ast::{ + AssignmentOperator, + BinaryOperator, + UnaryOperator, +}; +use crate::protocol::eval::Store; + +pub type StackPos = u32; +pub type HeapPos = u32; + +#[derive(Copy, Clone)] +pub enum ValueId { + Stack(StackPos), // place on stack + Heap(HeapPos, u32), // allocated region + values within that region +} + +#[derive(Debug, Clone)] +pub enum Value { + // Special types, never encountered during evaluation if the compiler works correctly + Unassigned, // Marker when variables are first declared, immediately followed by assignment + PrevStackBoundary(isize), // Marker for stack frame beginning, so we can pop stack values + Ref(ValueId), // Reference to a value, used by expressions producing references + // Builtin types + Input(PortId), + Output(PortId), + Message(HeapPos), + Null, + Bool(bool), + Char(char), + String(HeapPos), + UInt8(u8), + UInt16(u16), + UInt32(u32), + UInt64(u64), + SInt8(i8), + SInt16(i16), + SInt32(i32), + SInt64(i64), + Array(HeapPos), + // Instances of user-defined types + Enum(i64), + Union(i64, HeapPos), + Struct(HeapPos), +} + +macro_rules! impl_union_unpack_as_value { + ($func_name:ident, $variant_name:path, $return_type:ty) => { + impl Value { + pub(crate) fn $func_name(&self) -> $return_type { + match self { + $variant_name(v) => *v, + _ => panic!(concat!("called ", stringify!($func_name()), " on {:?}"), self), + } + } + } + } +} + +impl_union_unpack_as_value!(as_stack_boundary, Value::PrevStackBoundary, isize); +impl_union_unpack_as_value!(as_ref, Value::Ref, ValueId); +impl_union_unpack_as_value!(as_input, Value::Input, PortId); +impl_union_unpack_as_value!(as_output, Value::Output, PortId); +impl_union_unpack_as_value!(as_message, Value::Message, HeapPos); +impl_union_unpack_as_value!(as_bool, Value::Bool, bool); +impl_union_unpack_as_value!(as_char, Value::Char, char); +impl_union_unpack_as_value!(as_string, Value::String, HeapPos); +impl_union_unpack_as_value!(as_uint8, Value::UInt8, u8); +impl_union_unpack_as_value!(as_uint16, Value::UInt16, u16); +impl_union_unpack_as_value!(as_uint32, Value::UInt32, u32); +impl_union_unpack_as_value!(as_uint64, Value::UInt64, u64); +impl_union_unpack_as_value!(as_sint8, Value::SInt8, i8); +impl_union_unpack_as_value!(as_sint16, Value::SInt16, i16); +impl_union_unpack_as_value!(as_sint32, Value::SInt32, i32); +impl_union_unpack_as_value!(as_sint64, Value::SInt64, i64); +impl_union_unpack_as_value!(as_array, Value::Array, HeapPos); +impl_union_unpack_as_value!(as_enum, Value::Enum, i64); +impl_union_unpack_as_value!(as_struct, Value::Struct, HeapPos); + +impl Value { + pub(crate) fn as_union(&self) -> (i64, HeapPos) { + match self { + Value::Union(tag, v) => (*tag, *v), + _ => panic!("called as_union on {:?}", self), + } + } + + pub(crate) fn is_integer(&self) -> bool { + match self { + Value::UInt8(_) | Value::UInt16(_) | Value::UInt32(_) | Value::UInt64(_) | + Value::SInt8(_) | Value::SInt16(_) | Value::SInt32(_) | Value::SInt64(_) => true, + _ => false + } + } + + pub(crate) fn is_unsigned_integer(&self) -> bool { + match self { + Value::UInt8(_) | Value::UInt16(_) | Value::UInt32(_) | Value::UInt64(_) => true, + _ => false + } + } + + pub(crate) fn is_signed_integer(&self) -> bool { + match self { + Value::SInt8(_) | Value::SInt16(_) | Value::SInt32(_) | Value::SInt64(_) => true, + _ => false + } + } + + pub(crate) fn as_unsigned_integer(&self) -> u64 { + match self { + Value::UInt8(v) => *v as u64, + Value::UInt16(v) => *v as u64, + Value::UInt32(v) => *v as u64, + Value::UInt64(v) => *v as u64, + _ => unreachable!("called as_unsigned_integer on {:?}", self), + } + } + + pub(crate) fn as_signed_integer(&self) -> i64 { + match self { + Value::SInt8(v) => *v as i64, + Value::SInt16(v) => *v as i64, + Value::SInt32(v) => *v as i64, + Value::SInt64(v) => *v as i64, + _ => unreachable!("called as_signed_integer on {:?}", self) + } + } + + /// Returns the heap position associated with the value. If the value + /// doesn't store anything in the heap then we return `None`. + pub(crate) fn get_heap_pos(&self) -> Option { + match self { + Value::Message(v) => Some(*v), + Value::Array(v) => Some(*v), + Value::Union(_, v) => Some(*v), + Value::Struct(v) => Some(*v), + _ => None + } + } +} + +/// Applies the assignment operator. If a heap position is returned then that +/// heap position should be cleared +pub(crate) fn apply_assignment_operator(store: &mut Store, lhs: ValueRef, op: AssignmentOperator, rhs: Value) { + use AssignmentOperator as AO; + + macro_rules! apply_int_op { + ($lhs:ident, $assignment_tokens:tt, $operator:ident, $rhs:ident) => { + match $lhs { + Value::UInt8(v) => { *v $assignment_tokens $rhs.as_uint8(); }, + Value::UInt16(v) => { *v $assignment_tokens $rhs.as_uint16(); }, + Value::UInt32(v) => { *v $assignment_tokens $rhs.as_uint32(); }, + Value::UInt64(v) => { *v $assignment_tokens $rhs.as_uint64(); }, + Value::SInt8(v) => { *v $assignment_tokens $rhs.as_sint8(); }, + Value::SInt16(v) => { *v $assignment_tokens $rhs.as_sint16(); }, + Value::SInt32(v) => { *v $assignment_tokens $rhs.as_sint32(); }, + Value::SInt64(v) => { *v $assignment_tokens $rhs.as_sint64(); }, + _ => unreachable!("apply_assignment_operator {:?} on lhs {:?} and rhs {:?}", $operator, $lhs, $rhs), + } + } + } + + let lhs = store.read_mut_ref(lhs); + + let mut to_dealloc = None; + match AO { + AO::Set => { + match lhs { + Value::Unassigned => { *lhs = rhs; }, + Value::Input(v) => { *v = rhs.as_input(); }, + Value::Output(v) => { *v = rhs.as_output(); }, + Value::Message(v) => { to_dealloc = Some(*v); *v = rhs.as_message(); }, + Value::Bool(v) => { *v = rhs.as_bool(); }, + Value::Char(v) => { *v = rhs.as_char(); }, + Value::String(v) => { *v = rhs.as_string().clone(); }, + Value::UInt8(v) => { *v = rhs.as_uint8(); }, + Value::UInt16(v) => { *v = rhs.as_uint16(); }, + Value::UInt32(v) => { *v = rhs.as_uint32(); }, + Value::UInt64(v) => { *v = rhs.as_uint64(); }, + Value::SInt8(v) => { *v = rhs.as_sint8(); }, + Value::SInt16(v) => { *v = rhs.as_sint16(); }, + Value::SInt32(v) => { *v = rhs.as_sint32(); }, + Value::SInt64(v) => { *v = rhs.as_sint64(); }, + Value::Array(v) => { to_dealloc = Some(*v); *v = rhs.as_array(); }, + Value::Enum(v) => { *v = rhs.as_enum(); }, + Value::Union(lhs_tag, lhs_heap_pos) => { + to_dealloc = Some(*lhs_heap_pos); + let (rhs_tag, rhs_heap_pos) = rhs.as_union(); + *lhs_tag = rhs_tag; + *lhs_heap_pos = rhs_heap_pos; + } + Value::Struct(v) => { to_dealloc = Some(*v); *v = rhs.as_struct(); }, + _ => unreachable!("apply_assignment_operator {:?} on lhs {:?} and rhs {:?}", op, lhs, rhs), + } + }, + AO::Multiplied => { apply_int_op!(lhs, *=, op, rhs) }, + AO::Divided => { apply_int_op!(lhs, /=, op, rhs) }, + AO::Remained => { apply_int_op!(lhs, %=, op, rhs) }, + AO::Added => { apply_int_op!(lhs, +=, op, rhs) }, + AO::Subtracted => { apply_int_op!(lhs, -=, op, rhs) }, + AO::ShiftedLeft => { apply_int_op!(lhs, <<=, op, rhs) }, + AO::ShiftedRight => { apply_int_op!(lhs, >>=, op, rhs) }, + AO::BitwiseAnded => { apply_int_op!(lhs, &=, op, rhs) }, + AO::BitwiseXored => { apply_int_op!(lhs, ^=, op, rhs) }, + AO::BitwiseOred => { apply_int_op!(lhs, |=, op, rhs) }, + } + + if let Some(heap_pos) = to_dealloc { + store.drop_heap_pos(heap_pos); + } +} + +pub(crate) fn apply_binary_operator(store: &mut Store, lhs: &Value, op: BinaryOperator, rhs: &Value) -> Value { + use BinaryOperator as BO; + + macro_rules! apply_int_op_and_return { + ($lhs:ident, $operator_tokens:tt, $operator:ident, $rhs:ident) => { + return match $lhs { + Value::UInt8(v) => { Value::UInt8( *v $operator_tokens $rhs.as_uint8() ); }, + Value::UInt16(v) => { Value::UInt16(*v $operator_tokens $rhs.as_uint16()); }, + Value::UInt32(v) => { Value::UInt32(*v $operator_tokens $rhs.as_uint32()); }, + Value::UInt64(v) => { Value::UInt64(*v $operator_tokens $rhs.as_uint64()); }, + Value::SInt8(v) => { Value::SInt8( *v $operator_tokens $rhs.as_sint8() ); }, + Value::SInt16(v) => { Value::SInt16(*v $operator_tokens $rhs.as_sint16()); }, + Value::SInt32(v) => { Value::SInt32(*v $operator_tokens $rhs.as_sint32()); }, + Value::SInt64(v) => { Value::SInt64(*v $operator_tokens $rhs.as_sint64()); }, + _ => unreachable!("apply_binary_operator {:?} on lhs {:?} and rhs {:?}", $operator, $lhs, $rhs) + }; + } + } + + match op { + BO::Concatenate => { + let lhs_heap_pos; + let rhs_heap_pos; + let construct_fn; + match lhs { + Value::Message(lhs_pos) => { + lhs_heap_pos = *lhs_pos; + rhs_heap_pos = rhs.as_message(); + construct_fn = |pos: HeapPos| Value::Message(pos); + }, + Value::String(lhs_pos) => { + lhs_heap_pos = *lhs_pos; + rhs_heap_pos = rhs.as_string(); + construct_fn = |pos: HeapPos| Value::String(pos); + }, + Value::Array(lhs_pos) => { + lhs_heap_pos = *lhs_pos; + rhs_heap_pos = *rhs.as_array(); + construct_fn = |pos: HeapPos| Value::Array(pos); + }, + _ => unreachable!("apply_binary_operator {:?} on lhs {:?} and rhs {:?}", op, lhs, rhs) + } + + let target_heap_pos = store.alloc_heap(); + let target = &mut store.heap_regions[target_heap_pos as usize].values; + target.extend(&store.heap_regions[lhs_heap_pos as usize].values); + target.extend(&store.heap_regions[rhs_heap_pos as usize].values); + return construct_fn(target_heap_pos); + }, + BO::LogicalOr => { + return Value::Bool(lhs.as_bool() || rhs.as_bool()); + }, + BO::LogicalAnd => { + return Value::Bool(lhs.as_bool() && rhs.as_bool()); + }, + BO::BitwiseOr => { apply_int_op_and_return!(lhs, |, op, rhs); }, + BO::BitwiseXor => { apply_int_op_and_return!(lhs, ^, op, rhs); }, + BO::BitwiseAnd => { apply_int_op_and_return!(lhs, &, op, rhs); }, + BO::Equality => { todo!("implement") }, + BO::Inequality => { todo!("implement") }, + BO::LessThan => { apply_int_op_and_return!(lhs, <, op, rhs); }, + BO::GreaterThan => { apply_int_op_and_return!(lhs, >, op, rhs); }, + BO::LessThanEqual => { apply_int_op_and_return!(lhs, <=, op, rhs); }, + BO::GreaterThanEqual => { apply_int_op_and_return!(lhs, >=, op, rhs); }, + BO::ShiftLeft => { apply_int_op_and_return!(lhs, <<, op, rhs); }, + BO::ShiftRight => { apply_int_op_and_return!(lhs, >>, op, rhs); }, + BO::Add => { apply_int_op_and_return!(lhs, +, op, rhs); }, + BO::Subtract => { apply_int_op_and_return!(lhs, -, op, rhs); }, + BO::Multiply => { apply_int_op_and_return!(lhs, *, op, rhs); }, + BO::Divide => { apply_int_op_and_return!(lhs, /, op, rhs); }, + BO::Remainder => { apply_int_op_and_return!(lhs, %, op, rhs); } + } +} + +pub(crate) fn apply_unary_operator(store: &mut Store, op: UnaryOperator, value: &Value) -> Value { + use UnaryOperator as UO; + + macro_rules! apply_int_expr_and_return { + ($value:ident, $apply:expr, $op:ident) => { + return match $value { + Value::UInt8(v) => Value::UInt8($apply), + Value::UInt16(v) => Value::UInt16($apply), + Value::UInt32(v) => Value::UInt32($apply), + Value::UInt64(v) => Value::UInt64($apply), + Value::SInt8(v) => Value::SInt8($apply), + Value::SInt16(v) => Value::SInt16($apply), + Value::SInt32(v) => Value::SInt32($apply), + Value::SInt64(v) => Value::SInt64($apply), + _ => unreachable!("apply_unary_operator {:?} on value {:?}", $op, $value), + }; + } + } + + match op { + UO::Positive => { apply_int_expr_and_return!(value, *v, op) }, + UO::Negative => { apply_int_expr_and_return!(value, *v, op) }, + UO::BitwiseNot => { apply_int_expr_and_return!(value, *v, op)}, + UO::LogicalNot => { return Value::Bool(!value.as_bool()); }, + UO::PreIncrement => { todo!("implement") }, + UO::PreDecrement => { todo!("implement") }, + UO::PostIncrement => { todo!("implement") }, + UO::PostDecrement => { todo!("implement") }, + } +} \ No newline at end of file diff --git a/src/protocol/eval.rs b/src/protocol/eval_old.rs similarity index 99% rename from src/protocol/eval.rs rename to src/protocol/eval_old.rs index 31c38fae23bf18e62912c32f05e01aa0f4d9c89f..5d5ffb3faa1ec7f0e1685dda67a376e1a7f0ce28 100644 --- a/src/protocol/eval.rs +++ b/src/protocol/eval_old.rs @@ -1507,17 +1507,17 @@ impl Store { Expression::Unary(expr) => { let mut value = self.eval(h, ctx, expr.expression)?; match expr.operation { - UnaryOperation::PostIncrement => { + UnaryOperator::PostIncrement => { self.update(h, ctx, expr.expression, value.plus(&ONE))?; } - UnaryOperation::PreIncrement => { + UnaryOperator::PreIncrement => { value = value.plus(&ONE); self.update(h, ctx, expr.expression, value.clone())?; } - UnaryOperation::PostDecrement => { + UnaryOperator::PostDecrement => { self.update(h, ctx, expr.expression, value.minus(&ONE))?; } - UnaryOperation::PreDecrement => { + UnaryOperator::PreDecrement => { value = value.minus(&ONE); self.update(h, ctx, expr.expression, value.clone())?; } diff --git a/src/protocol/parser/depth_visitor.rs b/src/protocol/parser/depth_visitor.rs index fc3ef06d03bc0b4d36a52a78aadcac5724b5d4e5..49edf55dfe94ff8a2b2c843f8e930c5d6c9b5a80 100644 --- a/src/protocol/parser/depth_visitor.rs +++ b/src/protocol/parser/depth_visitor.rs @@ -719,10 +719,10 @@ impl Visitor for AssignableExpressions { self.error(h[expr].span.begin) } else { match h[expr].operation { - UnaryOperation::PostDecrement - | UnaryOperation::PreDecrement - | UnaryOperation::PostIncrement - | UnaryOperation::PreIncrement => { + UnaryOperator::PostDecrement + | UnaryOperator::PreDecrement + | UnaryOperator::PostIncrement + | UnaryOperator::PreIncrement => { self.assignable = true; recursive_unary_expression(self, h, expr)?; self.assignable = false; diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index b3a5379de5a7b858e7ec8e23f784d7648686b1b0..46795e3056b332203d54831f5318578969422a35 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -355,7 +355,9 @@ impl PassDefinitions { is_implicit: true, span: InputSpan::from_positions(wrap_begin_pos, wrap_end_pos), // TODO: @Span statements, - parent_scope: None, + parent_scope: Scope::Definition(DefinitionId::new_invalid()), + first_unique_id_in_scope: -1, + next_unique_id_in_scope: -1, relative_pos_in_parent: 0, locals: Vec::new(), labels: Vec::new() @@ -382,7 +384,7 @@ impl PassDefinitions { section.push(id.upcast()); let end_if = ctx.heap.alloc_end_if_statement(|this| EndIfStatement{ - this, start_if: id, next: None + this, start_if: id, next: StatementId::new_invalid() }); section.push(id.upcast()); @@ -393,7 +395,7 @@ impl PassDefinitions { section.push(id.upcast()); let end_while = ctx.heap.alloc_end_while_statement(|this| EndWhileStatement{ - this, start_while: id, next: None + this, start_while: id, next: StatementId::new_invalid() }); section.push(id.upcast()); @@ -410,7 +412,7 @@ impl PassDefinitions { section.push(id.upcast()); let end_sync = ctx.heap.alloc_end_synchronous_statement(|this| EndSynchronousStatement{ - this, start_sync: id, next: None + this, start_sync: id, next: StatementId::new_invalid() }); let sync_stmt = &mut ctx.heap[id]; @@ -477,7 +479,9 @@ impl PassDefinitions { is_implicit: false, span: block_span, statements, - parent_scope: None, + parent_scope: Scope::Definition(DefinitionId::new_invalid()), + first_unique_id_in_scope: -1, + next_unique_id_in_scope: -1, relative_pos_in_parent: 0, locals: Vec::new(), labels: Vec::new(), @@ -656,7 +660,7 @@ impl PassDefinitions { this, span: new_span, expression: call_id, - next: None + next: StatementId::new_invalid(), })) } @@ -691,17 +695,21 @@ impl PassDefinitions { consume_token(&module.source, iter, TokenKind::SemiColon)?; // Construct ports - let from = ctx.heap.alloc_local(|this| Local{ + let from = ctx.heap.alloc_variable(|this| Variable{ this, + kind: VariableKind::Local, identifier: from_identifier, parser_type: channel_type.clone(), relative_pos_in_block: 0, + unique_id_in_scope: -1, }); - let to = ctx.heap.alloc_local(|this| Local{ + let to = ctx.heap.alloc_variable(|this|Variable{ this, + kind: VariableKind::Local, identifier: to_identifier, parser_type: channel_type, relative_pos_in_block: 0, + unique_id_in_scope: -1, }); // Construct the channel @@ -710,7 +718,7 @@ impl PassDefinitions { span: channel_span, from, to, relative_pos_in_block: 0, - next: None, + next: StatementId::new_invalid(), })) } @@ -782,17 +790,19 @@ impl PassDefinitions { consume_token(&module.source, iter, TokenKind::SemiColon)?; // Allocate the memory statement with the variable - let local_id = ctx.heap.alloc_local(|this| Local{ + let local_id = ctx.heap.alloc_variable(|this| Variable{ this, + kind: VariableKind::Local, identifier: identifier.clone(), parser_type, relative_pos_in_block: 0, + unique_id_in_scope: -1, }); let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{ this, span: memory_span, variable: local_id, - next: None + next: StatementId::new_invalid() }); // Allocate the initial assignment @@ -816,7 +826,7 @@ impl PassDefinitions { this, span: InputSpan::from_positions(initial_expr_begin_pos, initial_expr_end_pos), expression: assignment_expr_id.upcast(), - next: None, + next: StatementId::new_invalid(), }); return Ok(Some((memory_stmt_id, assignment_stmt_id))) @@ -841,7 +851,7 @@ impl PassDefinitions { this, span: InputSpan::from_positions(start_pos, end_pos), expression, - next: None, + next: StatementId::new_invalid(), })) } @@ -849,6 +859,8 @@ impl PassDefinitions { // Expression Parsing //-------------------------------------------------------------------------- + // TODO: @Cleanup This is fine for now. But I prefer my stacktraces not to + // look like enterprise Java code... fn consume_expression( &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx ) -> Result { @@ -1077,9 +1089,9 @@ impl PassDefinitions { fn consume_prefix_expression( &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx ) -> Result { - fn parse_prefix_token(token: Option) -> Option { + fn parse_prefix_token(token: Option) -> Option { use TokenKind as TK; - use UnaryOperation as UO; + use UnaryOperator as UO; match token { Some(TK::Plus) => Some(UO::Positive), Some(TK::Minus) => Some(UO::Negative), @@ -1129,7 +1141,7 @@ impl PassDefinitions { if token == TokenKind::PlusPlus { result = ctx.heap.alloc_unary_expression(|this| UnaryExpression{ this, span, - operation: UnaryOperation::PostIncrement, + operation: UnaryOperator::PostIncrement, expression: result, parent: ExpressionParent::None, concrete_type: ConcreteType::default() @@ -1137,7 +1149,7 @@ impl PassDefinitions { } else if token == TokenKind::MinusMinus { result = ctx.heap.alloc_unary_expression(|this| UnaryExpression{ this, span, - operation: UnaryOperation::PostDecrement, + operation: UnaryOperator::PostDecrement, expression: result, parent: ExpressionParent::None, concrete_type: ConcreteType::default() @@ -1913,17 +1925,18 @@ fn consume_parameter_list( TokenKind::OpenParen, TokenKind::CloseParen, source, iter, ctx, |source, iter, ctx| { let poly_vars = ctx.heap[definition_id].poly_vars(); // TODO: @Cleanup, this is really ugly. But rust... - let (start_pos, _) = iter.next_positions(); let parser_type = consume_parser_type( source, iter, &ctx.symbols, &ctx.heap, poly_vars, scope, definition_id, false, 0 )?; let identifier = consume_ident_interned(source, iter, ctx)?; - let parameter_id = ctx.heap.alloc_parameter(|this| Parameter{ + let parameter_id = ctx.heap.alloc_variable(|this| Variable{ this, - span: InputSpan::from_positions(start_pos, identifier.span.end), + kind: VariableKind::Parameter, parser_type, - identifier + identifier, + relative_pos_in_block: 0, + unique_id_in_scope: -1, }); Ok(parameter_id) }, diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index 218055c7ea6e3f6565d05a933dcf4b5da6312cbf..3d36bb225909edee013eedec754192c53d04f00e 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -1670,7 +1670,7 @@ impl PassTyping { } fn progress_unary_expr(&mut self, ctx: &mut Ctx, id: UnaryExpressionId) -> Result<(), ParseError> { - use UnaryOperation as UO; + use UnaryOperator as UO; let upcast_id = id.upcast(); let expr = &ctx.heap[id]; diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index 3d77f3d5fe7b04ff9f8221ff4de2b3472e04b587..4df38c1545ada05e0bc5e8c198792ed2f6dedc9a 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -12,6 +12,7 @@ use super::visitor::{ VisitorResult }; use crate::protocol::parser::ModuleCompilationPhase; +use crossbeam_utils::thread::scope; #[derive(PartialEq, Eq)] enum DefinitionType { @@ -48,30 +49,32 @@ impl DefinitionType { /// Because of this scheme expressions will not be visited in the breadth-first /// pass. pub(crate) struct PassValidationLinking { - /// `in_sync` is `Some(id)` if the visitor is visiting the children of a - /// synchronous statement. A single value is sufficient as nested - /// synchronous statements are not allowed + // `in_sync` is `Some(id)` if the visitor is visiting the children of a + // synchronous statement. A single value is sufficient as nested + // synchronous statements are not allowed in_sync: Option, - /// `in_while` contains the last encountered `While` statement. This is used - /// to resolve unlabeled `Continue`/`Break` statements. + // `in_while` contains the last encountered `While` statement. This is used + // to resolve unlabeled `Continue`/`Break` statements. in_while: Option, // Traversal state: current scope (which can be used to find the parent - // scope), the definition variant we are considering, and whether the - // visitor is performing breadthwise block statement traversal. - cur_scope: Option, + // scope) and the definition variant we are considering. + cur_scope: Scope, def_type: DefinitionType, // Parent expression (the previous stmt/expression we visited that could be // used as an expression parent) expr_parent: ExpressionParent, + // Set by parent to indicate that child expression must be assignable. The + // child will throw an error if it is not assignable. The stored span is + // used for the error's position + must_be_assignable: Option, // Keeping track of relative position in block in the breadth-first pass. relative_pos_in_block: u32, + // Various temporary buffers for traversal. Essentially working around + // Rust's borrowing rules since it cannot understand we're modifying AST + // members but not the AST container. + variable_buffer: ScopedBuffer, definition_buffer: ScopedBuffer, - // Single buffer of statement IDs that we want to traverse in a block. - // Required to work around Rust borrowing rules and to prevent constant - // cloning of vectors. statement_buffer: ScopedBuffer, - // Another buffer, now with expression IDs, to prevent constant cloning of - // vectors while working around rust's borrowing rules expression_buffer: ScopedBuffer, } @@ -80,10 +83,12 @@ impl PassValidationLinking { Self{ in_sync: None, in_while: None, - cur_scope: None, + cur_scope: Scope::Definition(DefinitionId::new_invalid()), expr_parent: ExpressionParent::None, def_type: DefinitionType::Function(FunctionDefinitionId::new_invalid()), + must_be_assignable: None, relative_pos_in_block: 0, + variable_buffer: ScopedBuffer::new_reserved(128), definition_buffer: ScopedBuffer::new_reserved(128), statement_buffer: ScopedBuffer::new_reserved(STMT_BUFFER_INIT_CAPACITY), expression_buffer: ScopedBuffer::new_reserved(EXPR_BUFFER_INIT_CAPACITY), @@ -93,7 +98,7 @@ impl PassValidationLinking { fn reset_state(&mut self) { self.in_sync = None; self.in_while = None; - self.cur_scope = None; + self.cur_scope = Scope::Definition(DefinitionId::new_invalid()); self.expr_parent = ExpressionParent::None; self.def_type = DefinitionType::Function(FunctionDefinitionId::new_invalid()); self.relative_pos_in_block = 0; @@ -126,11 +131,21 @@ impl Visitor2 for PassValidationLinking { ComponentVariant::Primitive => DefinitionType::Primitive(id), ComponentVariant::Composite => DefinitionType::Composite(id), }; - self.cur_scope = Some(Scope::Definition(id.upcast())); + self.cur_scope = Scope::Definition(id.upcast()); self.expr_parent = ExpressionParent::None; + // Visit parameters and assign a unique scope ID + let definition = &ctx.heap[id]; + let body_id = definition.body; + let section = self.variable_buffer.start_section_initialized(&definition.parameters); + for variable_idx in 0..section.len() { + let variable_id = section[variable_idx]; + let variable = &mut ctx.heap[variable_id]; + variable.unique_id_in_scope = variable_idx as i32; + } + section.forget(); + // Visit statements in component body - let body_id = ctx.heap[id].body; self.visit_block_stmt(ctx, body_id)?; Ok(()) } @@ -140,11 +155,21 @@ impl Visitor2 for PassValidationLinking { // Set internal statement indices self.def_type = DefinitionType::Function(id); - self.cur_scope = Some(Scope::Definition(id.upcast())); + self.cur_scope = Scope::Definition(id.upcast()); self.expr_parent = ExpressionParent::None; + // Visit parameters and assign a unique scope ID + let definition = &ctx.heap[id]; + let body_id = definition.body; + let section = self.variable_buffer.start_section_initialized(&definition.parameters); + for variable_idx in 0..section.len() { + let variable_id = section[variable_idx]; + let variable = &mut ctx.heap[variable_id]; + variable.unique_id_in_scope = variable_idx as i32; + } + section.forget(); + // Visit statements in function body - let body_id = ctx.heap[id].body; self.visit_block_stmt(ctx, body_id)?; Ok(()) } @@ -356,8 +381,10 @@ impl Visitor2 for PassValidationLinking { assignment_expr.parent = old_expr_parent; self.expr_parent = ExpressionParent::Expression(upcast_id, 0); + self.must_be_assignable = Some(assignment_expr.span); self.visit_expr(ctx, left_expr_id)?; self.expr_parent = ExpressionParent::Expression(upcast_id, 1); + self.must_be_assignable = None; self.visit_expr(ctx, right_expr_id)?; self.expr_parent = old_expr_parent; Ok(()) @@ -367,6 +394,12 @@ impl Visitor2 for PassValidationLinking { let upcast_id = id.upcast(); let conditional_expr = &mut ctx.heap[id]; + if let Some(span) = self.must_be_assignable { + return Err(ParseError::new_error_str_at_span( + &ctx.module.source, span, "cannot assign to the result from a conditional expression" + )) + } + let test_expr_id = conditional_expr.test; let true_expr_id = conditional_expr.true_expression; let false_expr_id = conditional_expr.false_expression; @@ -388,6 +421,13 @@ impl Visitor2 for PassValidationLinking { fn visit_binary_expr(&mut self, ctx: &mut Ctx, id: BinaryExpressionId) -> VisitorResult { let upcast_id = id.upcast(); let binary_expr = &mut ctx.heap[id]; + + if let Some(span) = self.must_be_assignable { + return Err(ParseError::new_error_str_at_span( + &ctx.module.source, span, "cannot assign to the result from a binary expression" + )) + } + let left_expr_id = binary_expr.left; let right_expr_id = binary_expr.right; @@ -407,6 +447,12 @@ impl Visitor2 for PassValidationLinking { let unary_expr = &mut ctx.heap[id]; let expr_id = unary_expr.expression; + if let Some(span) = self.must_be_assignable { + return Err(ParseError::new_error_str_at_span( + &ctx.module.source, span, "cannot assign to the result from a unary expression" + )) + } + let old_expr_parent = self.expr_parent; unary_expr.parent = old_expr_parent; @@ -477,6 +523,12 @@ impl Visitor2 for PassValidationLinking { let old_expr_parent = self.expr_parent; literal_expr.parent = old_expr_parent; + if let Some(span) = self.must_be_assignable { + return Err(ParseError::new_error_str_at_span( + &ctx.module.source, span, "cannot assign to a literal expression" + )) + } + match &mut literal_expr.value { Literal::Null | Literal::True | Literal::False | Literal::Character(_) | Literal::String(_) | Literal::Integer(_) => { @@ -655,6 +707,12 @@ impl Visitor2 for PassValidationLinking { fn visit_call_expr(&mut self, ctx: &mut Ctx, id: CallExpressionId) -> VisitorResult { let call_expr = &mut ctx.heap[id]; + if let Some(span) = self.must_be_assignable { + return Err(ParseError::new_error_str_at_span( + &ctx.module.source, span, "cannot assign to the result from a call expression" + )) + } + // Check whether the method is allowed to be called within the code's // context (in sync, definition type, etc.) let mut expected_wrapping_new_stmt = false; @@ -797,14 +855,19 @@ impl PassValidationLinking { // Set parent scope and relative position in the parent scope. Remember // these values to set them back to the old values when we're done with // the traversal of the block's statements. + let scope_next_unique_id = get_scope_next_unique_id(ctx, &self.cur_scope); + let body = &mut ctx.heap[id]; body.parent_scope = self.cur_scope.clone(); body.relative_pos_in_parent = self.relative_pos_in_block; + body.first_unique_id_in_scope = scope_next_unique_id; + body.next_unique_id_in_scope = scope_next_unique_id; - let old_scope = self.cur_scope.replace(match hint { + let old_scope = self.cur_scope.clone(); + self.cur_scope = match hint { Some(sync_id) => Scope::Synchronous((sync_id, id)), None => Scope::Regular(id), - }); + }; let old_relative_pos = self.relative_pos_in_block; // Copy statement IDs into buffer @@ -818,6 +881,7 @@ impl PassValidationLinking { self.visit_statement_for_locals_labels_and_in_sync(ctx, self.relative_pos_in_block, statement_section[stmt_idx])?; } + // Perform the depth-first traversal for stmt_idx in 0..statement_section.len() { self.relative_pos_in_block = stmt_idx as u32; self.visit_stmt(ctx, statement_section[stmt_idx])?; @@ -868,8 +932,8 @@ impl PassValidationLinking { /// Adds a local variable to the current scope. It will also annotate the /// `Local` in the AST with its relative position in the block. - fn checked_local_add(&mut self, ctx: &mut Ctx, relative_pos: u32, id: LocalId) -> Result<(), ParseError> { - debug_assert!(self.cur_scope.is_some()); + fn checked_local_add(&mut self, ctx: &mut Ctx, relative_pos: u32, id: VariableId) -> Result<(), ParseError> { + debug_assert!(self.cur_scope.is_block()); // Make sure we do not conflict with any global symbols let cur_scope = SymbolScope::Definition(self.def_type.definition_id()); @@ -891,7 +955,7 @@ impl PassValidationLinking { // Make sure we do not shadow any variables in any of the scopes. Note // that variables in parent scopes may be declared later let local = &ctx.heap[id]; - let mut scope = self.cur_scope.as_ref().unwrap(); + let mut scope = &self.cur_scope; let mut local_relative_pos = self.relative_pos_in_block; loop { @@ -928,7 +992,7 @@ impl PassValidationLinking { ParseError::new_error_str_at_span( &ctx.module.source, local.identifier.span, "Local variable name conflicts with parameter" ).with_info_str_at_span( - &ctx.module.source, parameter.span, "Parameter definition is found here" + &ctx.module.source, parameter.identifier.span, "Parameter definition is found here" ) ); } @@ -942,8 +1006,13 @@ impl PassValidationLinking { } // No collisions at all - let block = &mut ctx.heap[self.cur_scope.as_ref().unwrap().to_block()]; + let block = &mut ctx.heap[self.cur_scope.to_block()]; block.locals.push(id); + let unique_id_in_scope = block.next_unique_id_in_scope; + block.next_unique_id_in_scope += 1; + + let variable = &mut ctx.heap[id]; + variable.unique_id_in_scope = unique_id_in_scope; Ok(()) } @@ -951,12 +1020,12 @@ impl PassValidationLinking { /// Finds a variable in the visitor's scope that must appear before the /// specified relative position within that block. fn find_variable(&self, ctx: &Ctx, mut relative_pos: u32, identifier: &Identifier) -> Result { - debug_assert!(self.cur_scope.is_some()); + debug_assert!(self.cur_scope.is_block()); // TODO: May still refer to an alias of a global symbol using a single // identifier in the namespace. // No need to use iterator over namespaces if here - let mut scope = self.cur_scope.as_ref().unwrap(); + let mut scope = &self.cur_scope; loop { debug_assert!(scope.is_block()); @@ -1000,7 +1069,7 @@ impl PassValidationLinking { /// Adds a particular label to the current scope. Will return an error if /// there is another label with the same name visible in the current scope. fn checked_label_add(&mut self, ctx: &mut Ctx, relative_pos: u32, in_sync: Option, id: LabeledStatementId) -> Result<(), ParseError> { - debug_assert!(self.cur_scope.is_some()); + debug_assert!(self.cur_scope.is_block()); // Make sure label is not defined within the current scope or any of the // parent scope. @@ -1009,7 +1078,7 @@ impl PassValidationLinking { label.in_sync = in_sync; let label = &ctx.heap[id]; - let mut scope = self.cur_scope.as_ref().unwrap(); + let mut scope = &self.cur_scope; loop { debug_assert!(scope.is_block(), "scope is not a block"); @@ -1044,9 +1113,9 @@ impl PassValidationLinking { /// will make sure that the target label does not skip over any variable /// declarations within the scope in which the label was found. fn find_label(&self, ctx: &Ctx, identifier: &Identifier) -> Result { - debug_assert!(self.cur_scope.is_some()); + debug_assert!(self.cur_scope.is_block()); - let mut scope = self.cur_scope.as_ref().unwrap(); + let mut scope = &self.cur_scope; loop { debug_assert!(scope.is_block(), "scope is not a block"); let relative_scope_pos = ctx.heap[scope.to_block()].relative_pos_in_parent; @@ -1088,7 +1157,7 @@ impl PassValidationLinking { /// statement that is one of our current parents. fn has_parent_while_scope(&self, ctx: &Ctx, id: WhileStatementId) -> bool { debug_assert!(self.cur_scope.is_some()); - let mut scope = self.cur_scope.as_ref().unwrap(); + let mut scope = &self.cur_scope; let while_stmt = &ctx.heap[id]; loop { debug_assert!(scope.is_block()); @@ -1099,7 +1168,7 @@ impl PassValidationLinking { let block = &ctx.heap[block]; debug_assert!(block.parent_scope.is_some(), "block scope does not have a parent"); - scope = block.parent_scope.as_ref().unwrap(); + scope = &block.parent_scope; if !scope.is_block() { return false; } @@ -1171,4 +1240,21 @@ impl PassValidationLinking { Ok(target) } +} + +fn get_scope_next_unique_id(ctx: &Ctx, scope: &Scope) -> i32 { + match scope { + Scope::Definition(definition_id) => { + let definition = &ctx.heap[*definition_id]; + match definition { + Definition::Component(definition) => definition.parameters.len() as i32, + Definition::Function(definition) => definition.parameters.len() as i32, + _ => unreachable!("Scope::Definition points to non-procedure type") + } + }, + Scope::Synchronous((_, block_id)) | Scope::Regular(block_id) => { + let block = &ctx.heap[*block_id]; + block.next_unique_id_in_scope + } + } } \ No newline at end of file