diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 8ce576bcfa720aef0e2e4d9701150fb216f14b7a..e9f1d96120d8954e41f43ac58d3050c0efdc57e8 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -279,12 +279,13 @@ impl PassDefinitions { module_scope, false, None )?; - // Consume block and the definition's scope - let body_id = self.consume_block_statement(module, iter, ctx)?; + // Consume body + let (body_id, source) = self.consume_procedure_body(module, iter, ctx, definition_id, ProcedureKind::Function)?; let scope_id = ctx.heap.alloc_scope(|this| Scope::new(this, ScopeAssociation::Definition(definition_id))); // Assign everything in the preallocated AST node let function = ctx.heap[definition_id].as_procedure_mut(); + function.source = source; function.return_type = Some(parser_type); function.parameters = parameters; function.scope = scope_id; @@ -316,13 +317,15 @@ impl PassDefinitions { )?; let parameters = parameter_section.into_vec(); - // Consume block - let body_id = self.consume_block_statement(module, iter, ctx)?; + // Consume body + let procedure_kind = ctx.heap[definition_id].as_procedure().kind; + let (body_id, source) = self.consume_procedure_body(module, iter, ctx, definition_id, procedure_kind)?; let scope_id = ctx.heap.alloc_scope(|this| Scope::new(this, ScopeAssociation::Definition(definition_id))); // Assign everything in the AST node let component = ctx.heap[definition_id].as_procedure_mut(); debug_assert!(component.return_type.is_none()); + component.source = source; component.parameters = parameters; component.scope = scope_id; component.body = body_id; @@ -330,6 +333,60 @@ impl PassDefinitions { Ok(()) } + /// Consumes a procedure's body: either a user-defined procedure, which we + /// parse as normal, or a builtin function, where we'll make sure we expect + /// the particular builtin. + /// + /// We expect that the procedure's name is already stored in the + /// preallocated AST node. + fn consume_procedure_body( + &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx, definition_id: DefinitionId, kind: ProcedureKind + ) -> Result<(BlockStatementId, ProcedureSource), ParseError> { + if iter.peek() == Some(TokenKind::Pragma) { + let (pragma, pragma_start, pragma_end) = consume_pragma(&module.source, iter)?; + if pragma != b"#builtin" { + return Err(ParseError::new_error_str_at_span( + &module.source, InputSpan::from_positions(pragma_start, pragma_end), + "expected a '#builtin' pragma, or a function body" + )); + } + + // Retrieve module and procedure name + assert!(module.name.is_some(), "compiler error: builtin procedure body in unnamed module"); + let (_, module_name) = module.name.as_ref().unwrap(); + let module_name = module_name.as_str(); + + let definition = ctx.heap[definition_id].as_procedure(); + let procedure_name = definition.identifier.value.as_str(); + + let source = match (module_name, procedure_name) { + ("std.global", "get") => ProcedureSource::FuncGet, + ("std.global", "put") => ProcedureSource::FuncPut, + ("std.global", "fires") => ProcedureSource::FuncFires, + ("std.global", "create") => ProcedureSource::FuncCreate, + ("std.global", "length") => ProcedureSource::FuncLength, + ("std.global", "assert") => ProcedureSource::FuncAssert, + ("std.global", "print") => ProcedureSource::FuncPrint, + _ => panic!( + "compiler error: unknown builtin procedure '{}' in module '{}'", + procedure_name, module_name + ), + }; + + return Ok((BlockStatementId::new_invalid(), source)); + } else { + let body_id = self.consume_block_statement(module, iter, ctx)?; + let source = match kind { + ProcedureKind::Function => + ProcedureSource::FuncUserDefined, + ProcedureKind::Primitive | ProcedureKind::Composite => + ProcedureSource::CompUserDefined, + }; + + return Ok((body_id, source)) + } + } + /// Consumes a statement and returns a boolean indicating whether it was a /// block or not. fn consume_statement(&mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx) -> Result { @@ -1579,22 +1636,19 @@ impl PassDefinitions { }, Definition::Procedure(proc_def) => { // Check whether it is a builtin function + // TODO: Once we start generating bytecode this is unnecessary let procedure_id = proc_def.this; - let method = if proc_def.builtin { - match proc_def.identifier.value.as_bytes() { - KW_FUNC_GET => Method::Get, - KW_FUNC_PUT => Method::Put, - KW_FUNC_FIRES => Method::Fires, - KW_FUNC_CREATE => Method::Create, - KW_FUNC_LENGTH => Method::Length, - KW_FUNC_ASSERT => Method::Assert, - KW_FUNC_PRINT => Method::Print, - _ => unreachable!(), - } - } else if proc_def.kind == ProcedureKind::Function { - Method::UserFunction - } else { - Method::UserComponent + let method = match proc_def.source { + ProcedureSource::FuncUserDefined => Method::UserFunction, + ProcedureSource::CompUserDefined => Method::UserComponent, + ProcedureSource::FuncGet => Method::Get, + ProcedureSource::FuncPut => Method::Put, + ProcedureSource::FuncFires => Method::Fires, + ProcedureSource::FuncCreate => Method::Create, + ProcedureSource::FuncLength => Method::Length, + ProcedureSource::FuncAssert => Method::Assert, + ProcedureSource::FuncPrint => Method::Print, + _ => todo!("other proc sources") }; // Function call: consume the arguments