use crate::diagnostic::Diagnostic; use crate::source::{SourceFile, SourceMap, Span}; use crate::syntax::*; use crate::token::{Kind, Token}; mod expression; type ParseResult = Result; pub(crate) fn parse(tokens: Vec, source: &SourceFile) -> Result> { let layout = source.layout().map_err(|error| vec![error])?; let mut comments = Vec::new(); let tokens = tokens .into_iter() .filter(|token| { if let Kind::Comment(text) = &token.kind { comments.push(Comment { text: text.clone(), span: token.span, }); false } else { true } }) .collect(); let mut parser = Parser { tokens, cursor: 0, brace_depth: 0, nesting: 0, model: source.is_model(), application: !SourceMap::is_standard(source.id()) && match &layout { crate::source::SourceLayout::Main => source.strict_layout(), crate::source::SourceLayout::Test { .. } => true, crate::source::SourceLayout::Role { .. } => true, crate::source::SourceLayout::Loose => false, }, }; let name = source.package_path().map_err(|error| vec![error])?; parser.package(name, layout, comments) } struct Parser { tokens: Vec, cursor: usize, brace_depth: usize, nesting: usize, model: bool, application: bool, } impl Parser { fn current(&self) -> &Token { &self.tokens[self.cursor] } fn at(&self, kind: &Kind) -> bool { &self.current().kind == kind } fn at_name(&self, name: &str) -> bool { matches!(&self.current().kind, Kind::Name(text) if text == name) } fn modifier(&mut self, name: &str) -> Option { if self.at_name(name) && self .tokens .get(self.cursor + 1) .is_some_and(|token| matches!(token.kind, Kind::Name(_))) { Some(self.advance().span) } else { None } } fn advance(&mut self) -> Token { let token = self.current().clone(); match token.kind { Kind::LeftBrace => self.brace_depth += 1, Kind::RightBrace => self.brace_depth = self.brace_depth.saturating_sub(1), _ => {} } if !self.at(&Kind::End) { self.cursor += 1; } token } fn take(&mut self, kind: &Kind) -> Option { if self.at(kind) { Some(self.advance()) } else { None } } fn expect(&mut self, kind: &Kind, description: &str) -> ParseResult { self.take(kind) .ok_or_else(|| self.error(format!("expected {description}"))) } fn error(&self, message: impl Into) -> Diagnostic { Diagnostic::error("P001", message, self.current().span) } fn skip_lines(&mut self) { while self.take(&Kind::Newline).is_some() {} } fn previous_span(&self) -> Span { self.tokens[self.cursor - 1].span } fn name(&mut self) -> ParseResult { if let Kind::Name(text) = &self.current().kind { let name = Name { text: text.clone(), span: self.current().span, }; self.advance(); Ok(name) } else { Err(self.error("expected an identifier")) } } fn path(&mut self) -> ParseResult { let mut path = vec![self.name()?]; while self.take(&Kind::Dot).is_some() { path.push(self.name()?); } Ok(path) } // Cap structural recursion while parsing untrusted source, before using the host stack. fn nested(&mut self, parse: impl FnOnce(&mut Self) -> ParseResult) -> ParseResult { if self.nesting == MAX_SYNTAX_DEPTH { return Err(Diagnostic::error( "P009", "syntax nesting exceeds the compiler limit", self.current().span, )); } self.nesting += 1; let result = parse(self); self.nesting -= 1; result } fn line_end(&mut self, closing: &Kind) -> ParseResult<()> { if self.take(&Kind::Newline).is_some() { self.skip_lines(); return Ok(()); } if self.at(closing) { return Ok(()); } Err(self.error("expected a newline or the closing delimiter")) } fn separated( &mut self, closing: Kind, parse: impl Fn(&mut Self) -> ParseResult, ) -> ParseResult> { let mut members = Vec::new(); self.skip_lines(); if self.take(&closing).is_some() { return Ok(members); } loop { members.push(parse(self)?); self.skip_lines(); if self.take(&closing).is_some() { return Ok(members); } self.expect(&Kind::Comma, "',' between positional entries")?; self.skip_lines(); } } fn package( &mut self, name: Path, layout: crate::source::SourceLayout, comments: Vec, ) -> Result> { self.skip_lines(); let start = self.current().span; let mut declarations = Vec::new(); let mut errors = Vec::new(); while !self.at(&Kind::End) { let visibility = self.take(&Kind::Public).map(|token| token.span); let anonymous_http = layout.role() == Some(crate::source::SourceRole::Api) && ["get", "post", "put", "delete"] .iter() .any(|name| self.at_name(name)); if self.application && !anonymous_http && let Some(span) = visibility { errors.push(Diagnostic::error( "P001", "application visibility is defined by the source role; remove 'public'", span, )); } let declaration = if self.at(&Kind::Package) || self.at(&Kind::Exposes) { Err(self.error("package/exposes declarations were removed; the source path and role define identity and visibility")) } else if layout.role() == Some(crate::source::SourceRole::Api) && ["get", "post", "put", "delete", "cmd", "rest"] .iter() .any(|name| self.at_name(name)) { self.api_declaration() } else if layout.role() == Some(crate::source::SourceRole::Adapter) && self.at_name("external") { self.external_adapter().map(Declaration::External) } else if self.at(&Kind::Type) { self.type_declaration(None).map(Declaration::Type) } else if self.at_name("global") && self .tokens .get(self.cursor + 1) .is_some_and(|token| token.kind == Kind::Type) { let start = self.advance().span; self.type_declaration(Some(start)).map(Declaration::Type) } else if self.at_name("setting") && self .tokens .get(self.cursor + 1) .is_some_and(|token| token.kind == Kind::LeftBrace) { self.setting_declaration().map(Declaration::Type) } else if (self.model || layout.role() == Some(crate::source::SourceRole::Job)) && self.at_name("database") { self.database_binding().map(Declaration::Database) } else if self.at_name("schedule") && layout.role() == Some(crate::source::SourceRole::Job) { self.schedule().map(Declaration::Schedule) } else if self.model && self.at_name("index") { self.model_index(false).map(Declaration::ModelIndex) } else if self.model && self.at_name("unique") { self.model_index(true).map(Declaration::ModelIndex) } else if self.model && self.at_name("relation") { self.relation().map(Declaration::Relation) } else if self.model && self.at_name("seed") { self.seed().map(Declaration::Seed) } else if self.model && self.at_name("migrate") { self.migration().map(Declaration::Migration) } else if self.model && self.at_name("sql") { self.model_sql().map(Declaration::ModelSql) } else if self.at_name("job") { let start = self.advance().span; self.function( FunctionKind::Job { attempts: 0, timeout_ms: 0, }, Some(start), ) .map(Declaration::Function) } else if self.at_name("transaction") { let start = self.advance().span; self.function(FunctionKind::Transaction, Some(start)) .map(Declaration::Function) } else { self.function(FunctionKind::Ordinary, None) .map(Declaration::Function) }; match declaration { Ok(mut declaration) => { if let Declaration::Api(binding) = &mut declaration { binding.anonymous = visibility.is_some() && anonymous_http; if let Some(start) = visibility { binding.span = start.through(binding.span); } } if let Some(start) = visibility.filter(|_| !self.application) { match &mut declaration { Declaration::Type(ty) => { ty.public = true; ty.span = start.through(ty.span); } Declaration::Function(function) => { function.public = true; function.span = start.through(function.span); } _ => errors.push(Diagnostic::error( "P001", "'public' is allowed only on types and functions", start, )), } } declarations.push(declaration); } Err(error) => { errors.push(error); self.recover_declaration(); } } self.skip_lines(); } if errors.is_empty() { Ok(Package { name, layout, declarations, comments, span: start.through(self.current().span), }) } else { Err(errors) } } fn external_adapter(&mut self) -> ParseResult { let start = self.advance().span; let ecosystem = self.name()?; let ecosystem = match ecosystem.text.as_str() { "exec" => ExternalEcosystem::Exec, "command" => ExternalEcosystem::Command, "pip" => ExternalEcosystem::Pip, "npm" => ExternalEcosystem::Npm, "go" => ExternalEcosystem::Go, _ => { return Err(Diagnostic::error( "P001", "external Adapter ecosystem must be exec, command, pip, npm, or go", ecosystem.span, )); } }; let Kind::Text(entry) = &self.current().kind else { return Err(self.error("external requires a literal relative entry path")); }; let entry = entry.clone(); let entry_span = self.advance().span; self.expect(&Kind::LeftBrace, "'{' after the external entry")?; self.skip_lines(); let mut libs = Vec::new(); let mut capabilities = Vec::new(); while !self.at(&Kind::RightBrace) { if self.at_name("lib") { self.advance(); let Kind::Text(lib) = &self.current().kind else { return Err(self.error("external lib requires a literal name@version or :@")); }; libs.push(Name { text: lib.clone(), span: self.current().span, }); self.advance(); self.line_end(&Kind::RightBrace)?; continue; } if !self.at_name("allow") { return Err(self .error("external accepts only 'lib ' or 'allow ' entries")); } self.advance(); capabilities.push(self.name()?); self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; Ok(ExternalAdapter { ecosystem, entry, entry_span, libs, capabilities, span: start.through(end), }) } fn api_declaration(&mut self) -> ParseResult { let keyword = self.name()?; if keyword.text == "rest" { let model = if self.at(&Kind::Newline) || self.at(&Kind::End) { None } else { Some(self.path()?) }; let span = keyword.span.through(self.previous_span()); self.line_end(&Kind::End)?; return Ok(Declaration::Rest(RestDeclaration { model, span })); } let kind = match keyword.text.as_str() { "get" => ApiKind::Get, "post" => ApiKind::Post, "put" => ApiKind::Put, "delete" => ApiKind::Delete, "cmd" => ApiKind::Command, _ => unreachable!("checked API declaration keyword"), }; let action = self.name()?; self.expect(&Kind::Assign, "'=' before the App binding")?; let target = self.path()?; let span = keyword.span.through(self.previous_span()); self.line_end(&Kind::End)?; Ok(Declaration::Api(ApiBinding { anonymous: false, kind, action, target, span, })) } fn schedule(&mut self) -> ParseResult { let start = self.advance().span; let target = self.name()?; self.expect(&Kind::Assign, "'=' before UTC cron expression")?; let Kind::Text(cron) = &self.current().kind else { return Err(self.error("schedule requires a literal five-field UTC cron expression")); }; let cron = cron.clone(); let span = start.through(self.advance().span); Ok(Schedule { target, cron, span }) } fn database_binding(&mut self) -> ParseResult { let start = self.advance().span; let name = self.name()?; Ok(DatabaseBinding { span: start.through(name.span), name, }) } fn model_index(&mut self, unique: bool) -> ParseResult { let start = self.advance().span; self.expect(&Kind::LeftParen, "'(' after the index kind")?; let fields = self.separated(Kind::RightParen, Self::name)?; if fields.is_empty() { return Err(Diagnostic::error( "P001", "an index requires at least one field", start, )); } Ok(ModelIndex { unique, fields, span: start.through(self.previous_span()), }) } fn relation(&mut self) -> ParseResult { let start = self.advance().span; let name = self.name()?; self.expect(&Kind::Assign, "'=' after the relation name")?; let field = self.path()?; Ok(Relation { name, field, span: start.through(self.previous_span()), }) } fn seed(&mut self) -> ParseResult { let start = self.advance().span; self.expect(&Kind::LeftBrace, "'{' after 'seed'")?; self.skip_lines(); let mut rows = Vec::new(); while !self.at(&Kind::RightBrace) { rows.push(self.seed_row()?); self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; Ok(Seed { rows, span: start.through(end), }) } fn seed_row(&mut self) -> ParseResult> { self.expect(&Kind::LeftBrace, "'{' around a seed row")?; self.skip_lines(); let mut fields = Vec::new(); while !self.at(&Kind::RightBrace) { let name = self.name()?; let start = name.span; self.expect(&Kind::Assign, "'=' after the seed field name")?; let value = self.expression(0, false)?; fields.push(RecordField { name, value, span: start.through(self.previous_span()), }); self.line_end(&Kind::RightBrace)?; } self.advance(); Ok(fields) } fn migration(&mut self) -> ParseResult { let start = self.advance().span; let name = self.name()?; self.expect(&Kind::LeftBrace, "'{' after the migration name")?; self.skip_lines(); let mut operations = Vec::new(); while !self.at(&Kind::RightBrace) { if self.at_name("drop") { self.advance(); operations.push(MigrationOperation::Drop(self.name()?)); } else if self.at_name("before") || self.at_name("after") { operations.push(MigrationOperation::Sql(self.migration_sql()?)); } else { return Err(self.error("expected a migration operation")); } self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; Ok(Migration { name, operations, span: start.through(end), }) } fn migration_sql(&mut self) -> ParseResult { let phase = if self.at_name("before") { MigrationPhase::Before } else { MigrationPhase::After }; let start = self.advance().span; self.expect(&Kind::LeftBrace, "'{' after the migration phase")?; self.skip_lines(); let mut sqlite = None; let mut postgres = None; let mut parameters = None; while !self.at(&Kind::RightBrace) { let field = self.name()?; self.expect(&Kind::Assign, "'=' after the migration SQL field")?; if field.text == "parameters" { self.expect(&Kind::LeftBracket, "'[' for migration parameters")?; let values = self.separated(Kind::RightBracket, |this| this.expression(0, true))?; if parameters.replace(values).is_some() { return Err(Diagnostic::error( "P001", "duplicate migration parameters", field.span, )); } } else { let slot = match field.text.as_str() { "sqlite" => &mut sqlite, "postgres" => &mut postgres, _ => { return Err(Diagnostic::error( "P001", "migration SQL requires sqlite, postgres and parameters", field.span, )); } }; if slot.replace(self.sql_text()?).is_some() { return Err(Diagnostic::error( "P001", "duplicate migration SQL dialect", field.span, )); } } self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; Ok(MigrationSql { phase, sqlite: sqlite.ok_or_else(|| { Diagnostic::error("P001", "migration SQL requires sqlite text", start) })?, postgres: postgres.ok_or_else(|| { Diagnostic::error("P001", "migration SQL requires postgres text", start) })?, parameters: parameters.ok_or_else(|| { Diagnostic::error( "P001", "migration SQL requires an explicit parameters list", start, ) })?, span: start.through(end), }) } fn sql_text(&mut self) -> ParseResult { let Kind::Text(value) = &self.current().kind else { return Err(self.error("SQL text must be a static Text literal")); }; let value = SqlText { value: value.clone(), span: self.current().span, }; self.advance(); Ok(value) } fn model_sql(&mut self) -> ParseResult { let start = self.advance().span; let name = self.name()?; self.expect(&Kind::LeftParen, "'(' after a SQL operation name")?; let inputs = self.separated(Kind::RightParen, Self::field)?; self.expect(&Kind::LeftParen, "the SQL result list '('")?; let outputs = self.separated(Kind::RightParen, Self::field)?; self.expect(&Kind::LeftBrace, "the SQL body '{'")?; self.skip_lines(); let mut sqlite = None; let mut postgres = None; while !self.at(&Kind::RightBrace) { let dialect = self.name()?; if !matches!(dialect.text.as_str(), "sqlite" | "postgres") { return Err(Diagnostic::error( "P001", "a SQL body accepts only 'sqlite' and 'postgres'", dialect.span, )); } self.expect(&Kind::Assign, "'=' after the SQL dialect")?; let value = self.sql_text()?; let slot = if dialect.text == "sqlite" { &mut sqlite } else { &mut postgres }; if slot.replace(value).is_some() { return Err(Diagnostic::error( "P001", format!("duplicate '{}' SQL dialect", dialect.text), dialect.span, )); } self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; let sqlite = sqlite.ok_or_else(|| { Diagnostic::error("P001", "a SQL declaration requires sqlite text", name.span) })?; let postgres = postgres.ok_or_else(|| { Diagnostic::error( "P001", "a SQL declaration requires postgres text", name.span, ) })?; Ok(ModelSql { name, inputs, outputs, sqlite, postgres, span: start.through(end), }) } fn recover_declaration(&mut self) { // Resume only after a balanced body or a top-level line, never inside a record. while !self.at(&Kind::End) { let token = self.advance(); if self.brace_depth == 0 && matches!(token.kind, Kind::RightBrace | Kind::Newline) { return; } } } fn type_declaration(&mut self, global: Option) -> ParseResult { let type_start = self.expect(&Kind::Type, "'type' after 'global'")?.span; let start = global.unwrap_or(type_start); let name = self.name()?; self.expect(&Kind::LeftBrace, "'{' after the type name")?; self.skip_lines(); if let Some(end) = self.take(&Kind::RightBrace) { return Ok(TypeDeclaration { public: false, setting: false, global: global.is_some(), name, shape: TypeShape::Record(Vec::new()), span: start.through(end.span), }); } let private = self.modifier("private"); let owner = self.modifier("owner"); let first = self.name()?; let shape = if self.at(&Kind::Colon) { let mut fields = vec![self.field_after_name(first, private, owner)?]; self.line_end(&Kind::RightBrace)?; while !self.at(&Kind::RightBrace) { let private = self.modifier("private"); let owner = self.modifier("owner"); let name = self.name()?; fields.push(self.field_after_name(name, private, owner)?); self.line_end(&Kind::RightBrace)?; } TypeShape::Record(fields) } else { if private.is_some() { return Err(self.error("'private' is allowed only on record fields")); } let mut variants = vec![self.variant(first)?]; self.line_end(&Kind::RightBrace)?; while !self.at(&Kind::RightBrace) { let name = self.name()?; variants.push(self.variant(name)?); self.line_end(&Kind::RightBrace)?; } TypeShape::Choice(variants) }; let end = self.expect(&Kind::RightBrace, "'}'")?.span; Ok(TypeDeclaration { public: false, setting: false, global: global.is_some(), name, shape, span: start.through(end), }) } fn setting_declaration(&mut self) -> ParseResult { let start = self.advance().span; self.expect(&Kind::LeftBrace, "'{' after setting")?; self.skip_lines(); let mut fields = Vec::new(); while !self.at(&Kind::RightBrace) { fields.push(self.field()?); self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; Ok(TypeDeclaration { public: false, setting: true, global: false, name: Name { text: "Setting".into(), span: start, }, shape: TypeShape::Record(fields), span: start.through(end), }) } fn field(&mut self) -> ParseResult { let name = self.name()?; self.field_after_name(name, None, None) } fn field_after_name( &mut self, name: Name, private: Option, owner: Option, ) -> ParseResult { self.expect(&Kind::Colon, "':' followed by a type")?; let ty = self.type_ref()?; let bounds = self.bounds()?; let mut storage = self.field_storage()?; if owner.is_some() { self.expect( &Kind::Assign, "'=' and a trusted identity source after an owner field", )?; storage.owner = Some(self.expression(0, false)?); } let span = private .or(owner) .unwrap_or(name.span) .through(self.previous_span()); Ok(Field { name, ty, private: private.is_some(), bounds, storage, span, }) } fn field_storage(&mut self) -> ParseResult { let mut storage = FieldStorage::default(); while matches!( &self.current().kind, Kind::Name(name) if matches!(name.as_str(), "generated" | "default" | "index" | "unique" | "from" | "create" | "replace" | "search") ) { let modifier = self.name()?; match modifier.text.as_str() { "generated" if !storage.generated => storage.generated = true, "default" if storage.default.is_none() => { storage.default = Some(self.expression(0, false)?); } "index" if !storage.index => storage.index = true, "unique" if !storage.unique => storage.unique = true, "from" if storage.from.is_none() => storage.from = Some(self.name()?), "create" if storage.create.is_none() => { self.expect(&Kind::Assign, "'=' and a REST create binding expression")?; storage.create = Some(self.expression(0, false)?); } "replace" if storage.replace.is_none() => { self.expect(&Kind::Assign, "'=' and a REST replace binding expression")?; storage.replace = Some(self.expression(0, false)?); } "search" if storage.search.is_none() => { self.expect(&Kind::Assign, "'=' and a REST search binding expression")?; storage.search = Some(self.expression(0, false)?); } _ => { return Err(Diagnostic::error( "P001", format!("duplicate '{}' field modifier", modifier.text), modifier.span, )); } } } Ok(storage) } fn variant(&mut self, name: Name) -> ParseResult { let start = name.span; let error = name.text == "error" && matches!(self.current().kind, Kind::Name(_)); let name = if error { self.name()? } else { name }; let payload = if self.take(&Kind::LeftParen).is_some() { self.separated(Kind::RightParen, Self::field)? } else { Vec::new() }; let label = if self.take(&Kind::Assign).is_some() { let Kind::Text(label) = &self.current().kind else { return Err(self.error("expected a choice display label string")); }; let label = label.clone(); self.advance(); Some(label) } else { None }; let span = start.through(self.previous_span()); Ok(Variant { name, error, payload, label, span, }) } fn type_ref(&mut self) -> ParseResult { self.nested(Self::type_ref_inner) } fn type_ref_inner(&mut self) -> ParseResult { let name = self.path()?; let generic = name.len() == 1 && matches!( name[0].text.as_str(), "List" | "Map" | "MapEntry" | "Stream" | "AsyncStream" | "Channel" | "Related" ) || name.len() == 2 && name[0].text == "Related" && matches!(name[1].text.as_str(), "Loaded" | "Unloaded"); let arguments = if generic && self.take(&Kind::Less).is_some() { self.separated(Kind::Greater, Self::type_ref)? } else { Vec::new() }; let storage_parameters = self.at(&Kind::LeftParen) && self .tokens .get(self.cursor + 1) .is_some_and(|token| matches!(token.kind, Kind::Number(_))); let parameters = if storage_parameters { self.advance(); self.separated(Kind::RightParen, Self::number_parameter)? } else { Vec::new() }; let nullable = self.take(&Kind::Question).is_some(); if self.at(&Kind::Question) { return Err(self.error("a type may have only one '?' suffix")); } let span = name[0].span.through(self.previous_span()); Ok(TypeRef { name, arguments, parameters, nullable, span, }) } fn number_parameter(&mut self) -> ParseResult { let Kind::Number(value) = &self.current().kind else { return Err(self.error("expected a numeric type parameter")); }; let value = value.clone(); let span = self.advance().span; Ok(NumberParameter { value, span }) } fn function( &mut self, mut kind: FunctionKind, declaration_start: Option, ) -> ParseResult { let mut name = self.name()?; while self.take(&Kind::Dot).is_some() { let segment = self.name()?; name.text.push('.'); name.text.push_str(&segment.text); name.span = name.span.through(segment.span); } self.expect(&Kind::LeftParen, "'(' after a function name")?; let inputs = self.separated(Kind::RightParen, Self::input)?; self.expect(&Kind::LeftParen, "the named output list '('")?; let outputs = self.separated(Kind::RightParen, Self::field)?; if matches!(kind, FunctionKind::Job { .. }) { let attempts = self.job_policy("retry", 100)?; let timeout_ms = self.job_policy("timeout", dever_runtime::config::MAX_JOB_TIMEOUT_MS)?; kind = FunctionKind::Job { attempts, timeout_ms, }; } let (pure, recovery) = self.function_contracts()?; let fails = if self.at_name("fails") { self.advance(); Some(self.type_ref()?) } else { None }; if let Some(failure) = &fails && !self.at(&Kind::LeftBrace) { let span = declaration_start.unwrap_or(name.span).through(failure.span); return Ok(FunctionClause { public: false, name, kind, inputs, outputs, pure, recovery, fails, bodyless: true, body: Vec::new(), span, }); } self.expect(&Kind::LeftBrace, "the function body '{'")?; self.skip_lines(); let mut body = Vec::new(); while !self.at(&Kind::RightBrace) { body.push(self.statement()?); self.line_end(&Kind::RightBrace)?; } let end = self.advance().span; let span = declaration_start.unwrap_or(name.span).through(end); Ok(FunctionClause { public: false, name, kind, inputs, outputs, pure, recovery, fails, bodyless: false, body, span, }) } fn job_policy(&mut self, name: &str, maximum: u32) -> ParseResult { if !self.at_name(name) { return Err(self.error(format!("job requires {name}(positive integer)"))); } self.advance(); self.expect(&Kind::LeftParen, "'(' before job policy")?; let number = self.signed_number()?; let value = number .parse::() .ok() .filter(|value| *value > 0 && *value <= maximum) .ok_or_else(|| self.error(format!("{name} must be an integer in 1..={maximum}")))?; self.expect(&Kind::RightParen, "')' after job policy")?; Ok(value) } fn function_contracts(&mut self) -> ParseResult<(bool, Option)> { let mut pure = false; let mut recovery = None; self.skip_lines(); while self.at_name("pure") || self.at_name("recover") { if self.at_name("pure") { if pure { return Err(self.error("duplicate 'pure' contract")); } self.advance(); pure = true; } else { if recovery.is_some() { return Err(self.error("duplicate 'recover' contract")); } self.advance(); self.skip_lines(); self.expect(&Kind::LeftParen, "'(' after 'recover'")?; self.skip_lines(); let Kind::Text(reason) = &self.current().kind else { return Err(self.error("expected a nonempty recovery reason string")); }; if reason.trim().is_empty() { return Err(self.error("recovery reason must not be empty")); } recovery = Some(reason.clone()); self.advance(); self.skip_lines(); self.expect(&Kind::RightParen, "')' after the recovery reason")?; } self.skip_lines(); } Ok((pure, recovery)) } fn input(&mut self) -> ParseResult { let name = self.name()?; self.expect(&Kind::Colon, "':' followed by an input type or pattern")?; let kind = if matches!(&self.current().kind, Kind::Name(value) if value == "handler") { let handler_span = self.advance().span; self.expect(&Kind::LeftParen, "'(' after 'handler'")?; let inputs = self.separated(Kind::RightParen, Self::field)?; self.expect(&Kind::LeftParen, "the handler output list '('")?; let outputs = self.separated(Kind::RightParen, Self::field)?; InputKind::Handler(HandlerSignature { inputs, outputs, span: handler_span.through(self.previous_span()), }) } else { InputKind::Value(self.pattern()?) }; let span = name.span.through(self.previous_span()); Ok(Input { name, kind, span }) } fn pattern(&mut self) -> ParseResult { let start = self.current().span; if let Some(token) = self.take(&Kind::Other) { return Ok(Pattern::Other(token.span)); } if let Some(value) = self.literal() { return Ok(Pattern::Literal { value, span: start.through(self.previous_span()), }); } if self.at(&Kind::Minus) { let number = self.signed_number()?; return Ok(Pattern::Literal { value: Literal::Number(number), span: start.through(self.previous_span()), }); } let ty = self.type_ref()?; if self.take(&Kind::LeftParen).is_some() { if ty.nullable { return Err(self.error("expected a qualified choice variant")); } let bindings = self.separated(Kind::RightParen, Self::name)?; return Ok(Pattern::Variant { name: ty.name, arguments: ty.arguments, bindings, span: start.through(self.previous_span()), }); } let bounds = self.bounds()?; Ok(Pattern::Typed { ty, bounds }) } fn bounds(&mut self) -> ParseResult> { let mut bounds = Vec::new(); if self.range_operator().is_some() { bounds.push(self.bound()?); if self.take(&Kind::And).is_some() { bounds.push(self.bound()?); } } Ok(bounds) } fn range_operator(&self) -> Option { match self.current().kind { Kind::Less => Some(BinaryOperator::Less), Kind::LessEqual => Some(BinaryOperator::LessEqual), Kind::Greater => Some(BinaryOperator::Greater), Kind::GreaterEqual => Some(BinaryOperator::GreaterEqual), _ => None, } } fn bound(&mut self) -> ParseResult { let comparison = self .range_operator() .ok_or_else(|| self.error("expected '<', '<=', '>' or '>='"))?; let start = self.advance().span; let number = self.signed_number()?; Ok(Bound { comparison, number, span: start.through(self.previous_span()), }) } fn signed_number(&mut self) -> ParseResult { let negative = self.take(&Kind::Minus).is_some(); if let Kind::Number(number) = &self.current().kind { let spelling = if negative { format!("-{number}") } else { number.clone() }; self.advance(); Ok(spelling) } else { Err(self.error("expected a numeric literal")) } } fn literal(&mut self) -> Option { let value = match &self.current().kind { Kind::True => Literal::Bool(true), Kind::False => Literal::Bool(false), Kind::Null => Literal::Null, Kind::Text(text) => Literal::Text(text.clone()), Kind::Number(number) => Literal::Number(number.clone()), _ => return None, }; self.advance(); Some(value) } fn statement(&mut self) -> ParseResult { let expression = self.expression(0, false)?; let start = expression.span; let kind = if self.take(&Kind::Assign).is_some() { let ExpressionKind::Name(target) = expression.kind else { return Err(Diagnostic::error( "P002", "assignment requires a local name or field path", start, )); }; let value = self.expression(0, false)?; StatementKind::Assign { target, value } } else if matches!( expression.kind, ExpressionKind::Call { .. } | ExpressionKind::Fail(_) | ExpressionKind::Contextual(_) ) { StatementKind::Call(expression) } else { return Err(Diagnostic::error( "P002", "a body statement must be an assignment or function call", start, )); }; Ok(Statement { kind, span: start.through(self.previous_span()), }) } }