Phase 2: parser and AST

Add the second pipeline stage: a hand-written recursive-descent parser with a
cascade expression layer, plus the AST it builds. Chosen over chumsky for precise
control of the specification's sync-point error recovery and plain-English errors;
the AST and later checkers are agnostic to this choice.

- legis-ast: full AST for declarations, statements, the precedence-encoded
  expression grammar, match/when in statement and expression forms, attempt,
  ranges, actions, tags, and conditional blocks. Spans on items, statements,
  expressions, and types.
- legis-parser: parses the whole grammar (minus the web route DSL, deferred to
  the web phase), with error recovery to synchronization points and a 20-error
  cap. Generic-call vs comparison is disambiguated by a restoring trial parse of
  `<Type,...>(`. Non-associative equality is rejected.
- legis-cli: new `parse` command dumping the syntax tree.
- Reference-program-over-grammar decisions, all exercised by the calculator:
  match arms may test a direct value (`is "+"`); arm bodies may be `{ }` blocks;
  a `when` expression arm may hold a bare `give back`; a `contract` may describe
  a constructor.
- legis-lexer: add the `while` keyword; make `value` a plain identifier (the
  reference program uses it as a name), with `has value` matched contextually.

27 tests pass (6 diag + 12 lexer + 9 parser); fmt and clippy clean. The reference
calculator parses to a correct AST with no problems.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:43:45 -05:00
parent 33bd4a78ff
commit ded6af2382
11 changed files with 3130 additions and 8 deletions

17
Cargo.lock generated
View File

@@ -128,6 +128,13 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "legis-ast"
version = "0.1.0"
dependencies = [
"legis-diag",
]
[[package]]
name = "legis-cli"
version = "0.1.0"
@@ -135,6 +142,7 @@ dependencies = [
"clap",
"legis-diag",
"legis-lexer",
"legis-parser",
]
[[package]]
@@ -149,6 +157,15 @@ dependencies = [
"logos",
]
[[package]]
name = "legis-parser"
version = "0.1.0"
dependencies = [
"legis-ast",
"legis-diag",
"legis-lexer",
]
[[package]]
name = "logos"
version = "0.14.4"

View File

@@ -3,6 +3,8 @@ resolver = "2"
members = [
"crates/legis-diag",
"crates/legis-lexer",
"crates/legis-ast",
"crates/legis-parser",
"crates/legis-cli",
]
@@ -19,3 +21,5 @@ clap = { version = "4", features = ["derive"] }
legis-diag = { path = "crates/legis-diag" }
legis-lexer = { path = "crates/legis-lexer" }
legis-ast = { path = "crates/legis-ast" }
legis-parser = { path = "crates/legis-parser" }

View File

@@ -37,7 +37,7 @@ legis/
├── crates/
│ ├── legis-lexer/ # logos-based lexer, token stream, | disambiguation
│ ├── legis-ast/ # AST node types, spans
│ ├── legis-parser/ # chumsky parser, error recovery (max 20/file)
│ ├── legis-parser/ # hand-written recursive-descent + Pratt, recovery (max 20/file)
│ ├── legis-resolve/ # name resolution, forbidden/shorthand names, imports
│ ├── legis-types/ # type checker, generics, behaviors, implicit success
│ ├── legis-check/ # purity checker, ownership checker, secret checker
@@ -66,11 +66,16 @@ legis/
- **Done when:** lexer round-trips the calculator + a token-soup fixture to golden.
### Phase 2 — Parser + AST
- Hand-written recursive-descent parser with a Pratt/cascade expression layer
(chosen over `chumsky` for precise control of the spec's sync-point recovery
and plain-English errors; AST + later checkers are agnostic to this choice).
- Full grammar: declarations (object, behavior, choices, schema, type, function,
pure function, method, test, benchmark), statements, the precedence-encoded
expression grammar, `match`/`when` statement *and* expression forms, `attempt`,
ranges, actions/short-actions, tags, conditional blocks.
- Error recovery to synchronization points, 20-error cap.
- Deferred to the web phase: the route DSL (`application.get(...){}`, `group`,
route patterns) and the `rate_limit(100 per minute)` protection argument form.
- **Done when:** calculator parses to AST; malformed fixtures produce recovered,
capped, plain-English parse errors.

View File

@@ -18,8 +18,8 @@ running the reference calculator program (`examples/calculator.lgi`).
|-------|------|-------|
| 0 | Workspace + plain-English diagnostics guard | done |
| 1 | Lexer | done |
| 2 | Parser + AST | next |
| 3 | Name resolution | planned |
| 2 | Parser + AST | done |
| 3 | Name resolution | next |
| 4 | Type checker | planned |
| 5 | Purity / ownership / secret checkers | planned |
| 6 | LLVM codegen + minimal standard library (MVP) | planned |
@@ -32,6 +32,8 @@ running the reference calculator program (`examples/calculator.lgi`).
crates/
legis-diag shared diagnostics + the banned-jargon guard
legis-lexer logos-based lexer
legis-ast abstract syntax tree types
legis-parser hand-written recursive-descent parser
legis-cli the `legis` binary
examples/
calculator.lgi the specification's reference program
@@ -42,6 +44,7 @@ examples/
```
cargo test # run the suite
cargo run -p legis-cli -- tokens FILE # dump a file's token stream
cargo run -p legis-cli -- parse FILE # dump a file's syntax tree
```
## Toolchain

View File

@@ -0,0 +1,9 @@
[package]
name = "legis-ast"
version.workspace = true
edition.workspace = true
authors.workspace = true
publish.workspace = true
[dependencies]
legis-diag.workspace = true

674
crates/legis-ast/src/lib.rs Normal file
View File

@@ -0,0 +1,674 @@
//! The Legis abstract syntax tree.
//!
//! These types mirror the specification's grammar. Spans are carried on the nodes
//! a later pass needs to point at — items, statements, expressions, and types —
//! so diagnostics can highlight the exact source. Smaller helper nodes borrow the
//! span of whatever contains them.
//!
//! A few shapes follow the *reference program* rather than the formal grammar
//! where the two disagree (the grammar is known to be incomplete in places):
//! `match` arms may match a direct value such as `is "+"`, and a `contract` may
//! describe a constructor with `contract build name(...)`.
use legis_diag::Span;
/// A whole source file.
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub items: Vec<Item>,
}
/// A lowercase name (variable, field, function, parameter, ...).
#[derive(Debug, Clone, PartialEq)]
pub struct Ident {
pub name: String,
pub span: Span,
}
/// An uppercase name (a type, behavior, choices, or variant).
#[derive(Debug, Clone, PartialEq)]
pub struct TypeName {
pub name: String,
pub span: Span,
}
// ---------------------------------------------------------------------------
// Tags
// ---------------------------------------------------------------------------
/// A compiler directive written with `#` before a declaration.
#[derive(Debug, Clone, PartialEq)]
pub enum Tag {
Stub,
RepeatsSafely,
NotSecret,
External(Ident),
Unsafe,
AllowWrap,
AllowSeed,
Deprecated(String),
Experimental,
ExpandInline,
NoExpand,
Cold,
KeepName,
Allow(Ident),
FileAllow(Ident),
NoLicense,
Test,
TestSetup,
TestTeardown,
Benchmark,
}
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
/// A type written in the source.
#[derive(Debug, Clone, PartialEq)]
pub struct TypeExpr {
pub kind: TypeKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeKind {
/// A primitive or plain named type, for example `Text` or `Vehicle`.
Named(String),
/// An angle-bracket type, for example `List<Whole>` or `Result<Whole, Text>`.
Apply { name: String, args: Vec<TypeExpr> },
/// A `BaseType of ItemType` type, for example `Stack of Whole`.
GenericOf { base: String, item: String },
/// A named tuple type, for example `(x: Decimal, y: Decimal)`.
Tuple(Vec<(Ident, TypeExpr)>),
/// A function type, for example `function(Whole) returns Bool`.
Function {
params: Vec<TypeExpr>,
returns: Box<TypeExpr>,
},
/// The `Self` type, valid only inside a behavior.
SelfType,
}
// ---------------------------------------------------------------------------
// Top-level items
// ---------------------------------------------------------------------------
/// One top-level declaration, with any doc comment and tags attached.
#[derive(Debug, Clone, PartialEq)]
pub struct Item {
pub doc: Option<String>,
pub tags: Vec<Tag>,
pub kind: ItemKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ItemKind {
Import(Import),
TypeAlias(TypeAlias),
Object(ObjectDecl),
Behavior(BehaviorDecl),
Choices(ChoicesDecl),
Schema(SchemaDecl),
Function(FunctionDecl),
Test(TestDecl),
Benchmark(BenchmarkDecl),
Conditional(ConditionalBlock),
}
/// `use library ...`.
#[derive(Debug, Clone, PartialEq)]
pub struct Import {
pub kind: ImportKind,
pub alias: Option<Ident>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ImportKind {
/// A standard or project library path such as `std.console`.
Standard(Vec<Ident>),
/// External code from outside Legis.
External {
language: Option<ExternalLang>,
name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExternalLang {
Rust,
C,
Csharp,
}
/// `type Name = ...;`.
#[derive(Debug, Clone, PartialEq)]
pub struct TypeAlias {
pub name: TypeName,
pub target: TypeExpr,
}
/// `object Name follows ... { ... }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectDecl {
pub name: TypeName,
/// The item type for a generic object, as in `object Stack of ItemType`.
pub generic_item: Option<TypeName>,
pub follows: Vec<TypeName>,
pub members: Vec<ObjectMember>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ObjectMember {
Field(FieldDecl),
Constructor(ConstructorDecl),
Method(MethodDecl),
}
/// A field on an object.
#[derive(Debug, Clone, PartialEq)]
pub struct FieldDecl {
pub doc: Option<String>,
pub name: Ident,
pub ty: TypeExpr,
pub default: Option<Expr>,
}
/// A `build` constructor.
#[derive(Debug, Clone, PartialEq)]
pub struct ConstructorDecl {
pub doc: Option<String>,
pub tags: Vec<Tag>,
/// The descriptive name of a named constructor, absent for the default one.
pub name: Option<Ident>,
pub params: Vec<Param>,
pub returns: TypeExpr,
pub body: Block,
}
/// How a method or constructor takes `self`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelfParam {
/// `self` — takes the value.
Value,
/// `peek self` — reads without taking.
Peek,
/// `peek vary self` — reads and changes without taking.
PeekVary,
}
/// A method on an object or choices type.
#[derive(Debug, Clone, PartialEq)]
pub struct MethodDecl {
pub doc: Option<String>,
pub tags: Vec<Tag>,
pub name: Ident,
pub self_param: Option<SelfParam>,
pub params: Vec<Param>,
pub returns: Option<TypeExpr>,
pub runs_in_background: bool,
pub protections: Vec<Protection>,
pub body: Block,
}
/// `behavior Name { ... }`.
#[derive(Debug, Clone, PartialEq)]
pub struct BehaviorDecl {
pub name: TypeName,
pub members: Vec<BehaviorMember>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum BehaviorMember {
Contract(ContractDecl),
Default(DefaultMethod),
}
/// A method an object must provide.
#[derive(Debug, Clone, PartialEq)]
pub struct ContractDecl {
pub doc: Option<String>,
/// True for a `contract build name(...)` constructor contract.
pub is_constructor: bool,
pub name: Ident,
pub self_param: Option<SelfParam>,
pub params: Vec<Param>,
pub returns: Option<TypeExpr>,
}
/// A method provided automatically by a behavior.
#[derive(Debug, Clone, PartialEq)]
pub struct DefaultMethod {
pub doc: Option<String>,
pub name: Ident,
pub self_param: Option<SelfParam>,
pub params: Vec<Param>,
pub returns: Option<TypeExpr>,
pub body: Block,
}
/// `choices Name follows ... { ... }`.
#[derive(Debug, Clone, PartialEq)]
pub struct ChoicesDecl {
pub name: TypeName,
pub follows: Vec<TypeName>,
pub members: Vec<ChoicesMember>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChoicesMember {
Variant(VariantDecl),
Method(MethodDecl),
}
/// One choice within a `choices` type.
#[derive(Debug, Clone, PartialEq)]
pub struct VariantDecl {
pub doc: Option<String>,
pub name: TypeName,
pub fields: Vec<VariantField>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VariantField {
pub name: Ident,
pub ty: TypeExpr,
}
/// `schema Name { ... }`.
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaDecl {
pub name: TypeName,
pub fields: Vec<SchemaField>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaField {
pub doc: Option<String>,
pub name: Ident,
pub ty: TypeExpr,
pub annotations: Vec<SchemaAnnotation>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SchemaAnnotation {
PrimaryKey,
AutoIncrement,
Required,
Unique,
Default(Expr),
ForeignKey { type_name: TypeName, field: Ident },
}
/// A standalone function or pure function.
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionDecl {
pub is_pure: bool,
pub name: Ident,
pub params: Vec<Param>,
pub returns: Option<TypeExpr>,
pub runs_in_background: bool,
pub protections: Vec<Protection>,
pub body: Block,
}
/// A function parameter.
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub kind: ParamKind,
pub name: Ident,
pub ty: TypeExpr,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParamKind {
/// Takes the value.
Value,
/// `peek` — reads without taking.
Peek,
/// `peek vary` — reads and changes without taking.
PeekVary,
}
/// A protection applied with `protect with`.
#[derive(Debug, Clone, PartialEq)]
pub struct Protection {
pub name: Ident,
pub args: Vec<Arg>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TestDecl {
pub name: Ident,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BenchmarkDecl {
pub name: Ident,
pub body: Block,
}
/// `#when condition { ... }` blocks.
#[derive(Debug, Clone, PartialEq)]
pub struct ConditionalBlock {
pub branches: Vec<(Condition, Vec<Item>)>,
pub otherwise: Option<Vec<Item>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Condition {
Platform(String),
Build(String),
Arch(String),
And(Box<Condition>, Box<Condition>),
Or(Box<Condition>, Box<Condition>),
}
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
/// A `{ ... }` block of statements.
#[derive(Debug, Clone, PartialEq)]
pub struct Block {
pub statements: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Stmt {
pub kind: StmtKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum StmtKind {
/// `let name: Type = value;` (`is_vary` true for `vary`).
Let {
is_vary: bool,
name: Ident,
ty: Option<TypeExpr>,
value: Expr,
},
/// `place = value;`.
Assign { target: Place, value: Expr },
/// An expression used for its effect, ending in `;`.
Expr(Expr),
/// `give back value;` or `give back;`.
GiveBack(Option<Expr>),
/// `if ... { } else if ... { } else { }`.
If {
branches: Vec<(Expr, Block)>,
otherwise: Option<Block>,
},
/// `repeat for name in iterable { }`.
RepeatFor {
pattern: ForPattern,
iterable: Expr,
body: Block,
},
/// `repeat while condition { }`.
RepeatWhile { condition: Expr, body: Block },
/// `match` used as a statement.
Match(MatchExpr),
/// `when` used as a statement.
When(WhenExpr),
/// `attempt { } if fails with ... { } always { }`.
Attempt(AttemptStmt),
}
/// The left-hand side of an assignment: a name and a chain of fields.
#[derive(Debug, Clone, PartialEq)]
pub struct Place {
pub root: Ident,
pub fields: Vec<Ident>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ForPattern {
Single(Ident),
Tuple(Vec<Ident>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct AttemptStmt {
pub body: Block,
/// `if fails with ErrorType with name { }` clauses.
pub typed_catches: Vec<TypedCatch>,
/// `if fails with name { }` catch-all clause.
pub catch_all: Option<(Ident, Block)>,
pub always: Option<Block>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedCatch {
pub error_type: TypeExpr,
pub binding: Ident,
pub body: Block,
}
// ---------------------------------------------------------------------------
// match / when
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq)]
pub struct MatchExpr {
pub subject: Box<Expr>,
pub arms: Vec<MatchArm>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MatchArm {
pub pattern: Pattern,
pub body: ArmBody,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern {
/// `is Type.Variant with binding`.
Variant {
path: Vec<String>,
binding: Option<Ident>,
span: Span,
},
/// `is "some value"` — a direct value pattern.
Value(Expr),
/// `otherwise`.
Otherwise,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhenExpr {
pub subject: Box<Expr>,
pub arms: Vec<WhenArm>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhenArm {
pub kind: WhenArmKind,
pub body: ArmBody,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WhenArmKind {
/// `succeeds with binding`.
Succeeds { binding: Option<Ident> },
/// `fails with ErrorType with binding`.
Fails {
error_type: Option<TypeExpr>,
binding: Option<Ident>,
},
/// `has value binding`.
HasValue { binding: Ident },
/// `is empty`.
IsEmpty,
}
/// The body of a `match` or `when` arm.
#[derive(Debug, Clone, PartialEq)]
pub enum ArmBody {
/// A list of statements (statement form, or an expression-form block).
Block(Vec<Stmt>),
/// A single value (expression form).
Value(Box<Expr>),
}
// ---------------------------------------------------------------------------
// Expressions
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq)]
pub struct Expr {
pub kind: ExprKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExprKind {
/// A whole-number value, kept as written.
Whole(String),
/// A decimal value, kept as written.
Decimal(String),
/// A text value, kept raw (interpolation resolved later).
Text {
raw: String,
multiline: bool,
},
Bool(bool),
Empty,
/// A reference to a name.
Name(String),
/// A reference to a type name, for example `Operation` in `Operation.Add`.
TypeRef(String),
/// A list value `[ ... ]`.
List(Vec<Expr>),
/// A map value `{ "key": value }`.
Map(Vec<(Expr, Expr)>),
/// A named tuple value `(x: 1, y: 2)`.
Tuple(Vec<(Ident, Expr)>),
/// An object value `Type { field: value }`.
ObjectInit {
type_name: TypeName,
fields: Vec<(Ident, Expr)>,
},
/// A constructor call `Type.build name(...)`.
Construct {
type_name: TypeName,
constructor: Option<Ident>,
args: Vec<Arg>,
},
/// Field access `base.field`.
Field {
base: Box<Expr>,
field: Ident,
},
/// Safe field access `base?.field`.
SafeField {
base: Box<Expr>,
field: Ident,
},
/// A call `callee<TypeArgs>(args)`.
Call {
callee: Box<Expr>,
type_args: Vec<TypeExpr>,
args: Vec<Arg>,
},
/// `!value` or `-value`.
Unary {
op: UnaryOp,
operand: Box<Expr>,
},
/// A binary operation.
Binary {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
},
/// `left |> right`.
Pipe {
value: Box<Expr>,
into: Box<Expr>,
},
/// `left then right`.
Compose {
first: Box<Expr>,
second: Box<Expr>,
},
/// `start..end` or `start..=end`.
Range {
start: Box<Expr>,
end: Box<Expr>,
inclusive: bool,
},
/// `pass up value`.
PassUp(Box<Expr>),
/// `wait value`.
Wait(Box<Expr>),
/// `success(value)`.
Success(Option<Box<Expr>>),
/// `fail(value)`.
Fail(Box<Expr>),
/// `action(params) returns Type { body }`.
Action {
params: Vec<Param>,
returns: Option<TypeExpr>,
body: Block,
runs_in_background: bool,
},
/// `|params| expression`.
ShortAction {
params: Vec<Ident>,
body: Box<Expr>,
},
/// A partial-application placeholder `...`.
Placeholder,
/// `match` used as an expression.
Match(MatchExpr),
/// `when` used as an expression.
When(WhenExpr),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Not,
Negate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Subtract,
Multiply,
Divide,
Remainder,
Equals,
NotEquals,
Less,
Greater,
LessEquals,
GreaterEquals,
And,
Or,
}
/// An argument in a call or constructor.
#[derive(Debug, Clone, PartialEq)]
pub enum Arg {
/// A plain positional argument.
Positional(Expr),
/// A named argument `name: value`.
Named { name: Ident, value: Expr },
/// `peek value`.
Peek(Expr),
/// `peek vary value`.
PeekVary(Expr),
/// `...` — a partial-application placeholder.
Placeholder,
}

View File

@@ -12,4 +12,5 @@ path = "src/main.rs"
[dependencies]
clap.workspace = true
legis-lexer.workspace = true
legis-parser.workspace = true
legis-diag.workspace = true

View File

@@ -1,8 +1,8 @@
//! The `legis` command-line tool.
//!
//! Phase 1 ships one inspection command, `tokens`, which dumps the token stream
//! for a source file. Build, run, test, and format commands arrive in later
//! phases as the compiler grows.
//! So far it ships two inspection commands: `tokens` (a look inside the lexer)
//! and `parse` (the abstract syntax tree). Build, run, test, and format commands
//! arrive in later phases as the compiler grows.
use std::path::PathBuf;
use std::process::ExitCode;
@@ -29,12 +29,45 @@ enum Command {
/// The .lgi file to read.
file: PathBuf,
},
/// Show the abstract syntax tree for a source file (a look inside the parser).
Parse {
/// The .lgi file to read.
file: PathBuf,
},
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.command {
Command::Tokens { file } => run_tokens(&file),
Command::Parse { file } => run_parse(&file),
}
}
fn run_parse(file: &PathBuf) -> ExitCode {
let source = match std::fs::read_to_string(file) {
Ok(text) => text,
Err(error) => {
eprintln!("error: could not read {}: {error}", file.display());
return ExitCode::FAILURE;
}
};
let (program, diagnostics) = legis_parser::parse(&source);
if diagnostics.is_empty() {
println!("{program:#?}");
println!(
"\nparsed {} top-level item(s) with no problems.",
program.items.len()
);
ExitCode::SUCCESS
} else {
for diagnostic in &diagnostics {
print_diagnostic(diagnostic);
}
eprintln!("\nfound {} problem(s).", diagnostics.len());
ExitCode::FAILURE
}
}

View File

@@ -166,6 +166,8 @@ pub enum Token {
Otherwise,
#[token("repeat")]
Repeat,
#[token("while")]
While,
#[token("for")]
For,
#[token("in")]
@@ -182,8 +184,10 @@ pub enum Token {
Always,
#[token("has")]
Has,
#[token("value")]
Value,
// Note: `value` is deliberately not a keyword. The word appears in the
// `has value` outcome, but the reference program also uses `value` as an
// ordinary name, so it is a plain identifier and `has value` is recognized
// by the parser checking for the name "value".
// --- Functional / misc keywords ---
#[token("then")]

View File

@@ -0,0 +1,11 @@
[package]
name = "legis-parser"
version.workspace = true
edition.workspace = true
authors.workspace = true
publish.workspace = true
[dependencies]
legis-diag.workspace = true
legis-lexer.workspace = true
legis-ast.workspace = true

File diff suppressed because it is too large Load Diff