// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 clbbbb
///|
pub fn parse(text : String) -> ParseResult {
let tokens = tokenize(text)
if tokens.is_empty() {
return fail("empty expression")
}
let state = parse_or(tokens, 0)
match state.expr {
Some(expr) if state.index == tokens.length() => ok(expr)
Some(_) => fail("unexpected token: " + tokens[state.index].text)
None => fail(state.error)
}
}
///|
pub struct ParseState {
expr : Expr?
index : Int
error : String
} derive(Debug, Eq)
///|
pub fn state(expr : Expr, index : Int) -> ParseState {
{ expr: Some(expr), index, error: "" }
}
///|
pub fn state_error(message : String, index : Int) -> ParseState {
{ expr: None, index, error: message }
}
///|
pub fn parse_or(tokens : Array[Token], start : Int) -> ParseState {
let mut left = parse_and(tokens, start)
while left.expr is Some(_) && left.index < tokens.length() {
if tokens[left.index].kind is OrTok {
let right = parse_and(tokens, left.index + 1)
match (left.expr, right.expr) {
(Some(a), Some(b)) => left = state(Or(a, b), right.index)
_ => return right
}
} else {
break
}
}
left
}
///|
pub fn parse_and(tokens : Array[Token], start : Int) -> ParseState {
let mut left = parse_primary(tokens, start)
while left.expr is Some(_) && left.index < tokens.length() {
if tokens[left.index].kind is AndTok {
let right = parse_primary(tokens, left.index + 1)
match (left.expr, right.expr) {
(Some(a), Some(b)) => left = state(And(a, b), right.index)
_ => return right
}
} else {
break
}
}
left
}
///|
pub fn parse_primary(tokens : Array[Token], start : Int) -> ParseState {
if start >= tokens.length() {
return state_error("expected license id", start)
}
match tokens[start].kind {
Ident(id) => parse_license_tail(tokens, start + 1, canonical_license(id))
LParen => {
let inner = parse_or(tokens, start + 1)
match inner.expr {
Some(expr) if inner.index < tokens.length() &&
tokens[inner.index].kind is RParen => state(expr, inner.index + 1)
Some(_) => state_error("expected ')'", inner.index)
None => inner
}
}
_ => state_error("expected license id", start)
}
}
///|
pub fn parse_license_tail(
tokens : Array[Token],
index : Int,
id : String,
) -> ParseState {
if index + 1 < tokens.length() && tokens[index].kind is WithTok {
match tokens[index + 1].kind {
Ident(ex) =>
state(LicenseWithException(id, canonical_exception(ex)), index + 2)
_ => state_error("expected exception id after WITH", index + 1)
}
} else {
state(License(id), index)
}
}
///|
pub fn parse_or_panic(text : String) -> Expr {
match parse(text).expr {
Some(expr) => expr
None => License("INVALID")
}
}