Files
core/crates/dever-core/src/parser.rs
T

1186 lines
42 KiB
Rust

use crate::diagnostic::Diagnostic;
use crate::source::{SourceFile, SourceMap, Span};
use crate::syntax::*;
use crate::token::{Kind, Token};
mod expression;
type ParseResult<T> = Result<T, Diagnostic>;
pub(crate) fn parse(tokens: Vec<Token>, source: &SourceFile) -> Result<Package, Vec<Diagnostic>> {
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<Token>,
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<Span> {
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<Token> {
if self.at(kind) {
Some(self.advance())
} else {
None
}
}
fn expect(&mut self, kind: &Kind, description: &str) -> ParseResult<Token> {
self.take(kind)
.ok_or_else(|| self.error(format!("expected {description}")))
}
fn error(&self, message: impl Into<String>) -> 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<Name> {
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<Path> {
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<T>(&mut self, parse: impl FnOnce(&mut Self) -> ParseResult<T>) -> ParseResult<T> {
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<T>(
&mut self,
closing: Kind,
parse: impl Fn(&mut Self) -> ParseResult<T>,
) -> ParseResult<Vec<T>> {
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<Comment>,
) -> Result<Package, Vec<Diagnostic>> {
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<ExternalAdapter> {
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 <ecosystem>:<name>@<version>"));
};
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 <spec>' or 'allow <capability>' 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<Declaration> {
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<Schedule> {
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<DatabaseBinding> {
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<ModelIndex> {
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<Relation> {
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<Seed> {
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<Vec<RecordField>> {
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<Migration> {
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<MigrationSql> {
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<SqlText> {
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<ModelSql> {
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<Span>) -> ParseResult<TypeDeclaration> {
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<TypeDeclaration> {
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<Field> {
let name = self.name()?;
self.field_after_name(name, None, None)
}
fn field_after_name(
&mut self,
name: Name,
private: Option<Span>,
owner: Option<Span>,
) -> ParseResult<Field> {
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<FieldStorage> {
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<Variant> {
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<TypeRef> {
self.nested(Self::type_ref_inner)
}
fn type_ref_inner(&mut self) -> ParseResult<TypeRef> {
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<NumberParameter> {
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<Span>,
) -> ParseResult<FunctionClause> {
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<u32> {
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::<u32>()
.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<String>)> {
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<Input> {
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<Pattern> {
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<Vec<Bound>> {
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<BinaryOperator> {
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<Bound> {
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<String> {
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<Literal> {
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<Statement> {
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()),
})
}
}