use crate::ast::*;
use crate::diag::Diagnostic;
use crate::lexer::{Token, TokenKind};
use crate::span::Span;
pub struct ParseOutput {
pub file: File,
pub diagnostics: Vec<Diagnostic>,
}
pub fn parse(src: &str, tokens: Vec<Token>) -> ParseOutput {
let mut p = Parser {
src,
tokens,
pos: 0,
diags: Vec::new(),
};
let file = p.parse_file();
ParseOutput {
file,
diagnostics: p.diags,
}
}
const STMT_KEYWORDS: &[&str] = &[
"canvas",
"def",
"group",
"class",
"constrain",
"pin",
"for",
"port",
];
struct Parser<'a> {
src: &'a str,
tokens: Vec<Token>,
pos: usize,
diags: Vec<Diagnostic>,
}
struct Bail;
type PResult<T> = Result<T, Bail>;
impl<'a> Parser<'a> {
fn peek(&self) -> &TokenKind {
&self.tokens[self.pos].kind
}
fn peek_at(&self, ahead: usize) -> &TokenKind {
let idx = (self.pos + ahead).min(self.tokens.len() - 1);
&self.tokens[idx].kind
}
fn span(&self) -> Span {
self.tokens[self.pos].span
}
fn prev_span(&self) -> Span {
self.tokens[self.pos.saturating_sub(1)].span
}
fn bump(&mut self) -> Token {
let t = self.tokens[self.pos].clone();
if self.pos < self.tokens.len() - 1 {
self.pos += 1;
}
t
}
fn at_eof(&self) -> bool {
matches!(self.peek(), TokenKind::Eof)
}
fn eat(&mut self, kind: &TokenKind) -> bool {
if self.peek() == kind {
self.bump();
true
} else {
false
}
}
fn skip_trivia(&mut self) {
while matches!(self.peek(), TokenKind::Newline | TokenKind::Comment(_)) {
self.bump();
}
}
fn expect(&mut self, kind: TokenKind, ctx: &str) -> PResult<Token> {
if self.peek() == &kind {
return Ok(self.bump());
}
let found = self.peek().describe();
self.diags.push(
Diagnostic::error(
"E0103",
format!("expected {} {ctx}, found {found}", kind.describe()),
)
.with_label(self.span(), format!("expected {} here", kind.describe())),
);
Err(Bail)
}
fn expect_ident(&mut self, ctx: &str) -> PResult<Ident> {
if let TokenKind::Ident(name) = self.peek() {
let name = name.clone();
let t = self.bump();
return Ok(Ident { name, span: t.span });
}
let found = self.peek().describe();
self.diags.push(
Diagnostic::error("E0103", format!("expected a name {ctx}, found {found}"))
.with_label(self.span(), "expected an identifier here"),
);
Err(Bail)
}
fn at_kw(&self, kw: &str) -> bool {
matches!(self.peek(), TokenKind::Ident(n) if n == kw)
}
fn recover(&mut self) {
let mut depth = 0usize;
loop {
match self.peek() {
TokenKind::Eof => return,
TokenKind::LBrace => {
depth += 1;
self.bump();
}
TokenKind::RBrace => {
if depth == 0 {
return; }
depth -= 1;
self.bump();
}
TokenKind::Newline | TokenKind::Semi if depth == 0 => {
self.bump();
return;
}
_ => {
self.bump();
}
}
}
}
fn parse_file(&mut self) -> File {
let header = self.parse_header();
let stmts = self.parse_stmt_list(true);
File { header, stmts }
}
fn parse_header(&mut self) -> Option<Header> {
self.skip_trivia();
if !self.at_kw("drawl") {
let span = self.span();
self.diags.push(
Diagnostic::warning("W0101", "missing `drawl` version header")
.with_label(
Span::new(span.start, span.start),
"file should start with a version header",
)
.with_help("add `drawl 0.1` as the first line"),
);
return None;
}
let kw = self.bump();
let (version, vspan) = match self.peek().clone() {
TokenKind::Float(v) => {
let t = self.bump();
(format!("{v}"), t.span)
}
TokenKind::Int(v) => {
let t = self.bump();
(format!("{v}"), t.span)
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected a version number after `drawl`, found {}",
other.describe()
),
)
.with_label(self.span(), "expected something like `0.1`"),
);
self.recover();
return Some(Header {
version: "0.1".into(),
span: kw.span,
});
}
};
if version != "0.1" {
self.diags.push(
Diagnostic::error("E0106", format!("unsupported drawl version `{version}`"))
.with_label(vspan, "this tool understands version `0.1`")
.with_help("change the header to `drawl 0.1`"),
);
}
Some(Header {
version,
span: kw.span.to(vspan),
})
}
fn parse_stmt_list(&mut self, top_level: bool) -> Vec<Stmt> {
let mut stmts = Vec::new();
loop {
let trivia = self.collect_leading_trivia();
match self.peek() {
TokenKind::Eof => {
self.flush_orphan_comments(trivia, &mut stmts);
return stmts;
}
TokenKind::RBrace => {
if top_level {
self.diags.push(
Diagnostic::error("E0110", "unmatched `}`")
.with_label(self.span(), "no open block to close here"),
);
self.bump();
continue;
}
self.flush_orphan_comments(trivia, &mut stmts);
return stmts;
}
_ => {}
}
let start = self.span();
match self.parse_stmt() {
Ok(mut stmt) => {
stmt.trivia = trivia;
self.attach_trailing_comment(&mut stmt);
stmts.push(stmt);
}
Err(Bail) => {
self.recover();
if self.span() == start && !self.at_eof() {
self.bump();
}
}
}
}
}
fn collect_leading_trivia(&mut self) -> Trivia {
let mut trivia = Trivia::default();
let mut newline_run = 0usize;
loop {
match self.peek() {
TokenKind::Semi => {
self.bump();
}
TokenKind::Newline => {
newline_run += 1;
if newline_run >= 2 && !trivia.leading.is_empty() {
}
if newline_run >= 2 {
trivia.blank_before = true;
}
self.bump();
}
TokenKind::Comment(text) => {
let text = text.clone();
trivia.leading.push(text);
newline_run = 0;
self.bump();
}
_ => return trivia,
}
}
}
fn flush_orphan_comments(&mut self, trivia: Trivia, stmts: &mut Vec<Stmt>) {
if !trivia.leading.is_empty() {
stmts.push(Stmt {
kind: StmtKind::Prop(Prop {
key: Vec::new(),
value: Value::Num(0.0, Span::DUMMY),
span: Span::DUMMY,
}),
span: Span::DUMMY,
trivia,
});
}
}
fn attach_trailing_comment(&mut self, stmt: &mut Stmt) {
if let TokenKind::Comment(text) = self.peek() {
stmt.trivia.trailing = Some(text.clone());
self.bump();
}
}
fn parse_stmt(&mut self) -> PResult<Stmt> {
let start = self.span();
let next_is_ident = matches!(self.peek_at(1), TokenKind::Ident(_));
let next_is_lbrace = matches!(self.peek_at(1), TokenKind::LBrace);
let kind = if self.at_kw("canvas") && next_is_lbrace {
self.bump();
StmtKind::Canvas(self.parse_block()?)
} else if self.at_kw("def") && next_is_ident {
self.bump();
StmtKind::Def(self.parse_def()?)
} else if self.at_kw("group")
&& (next_is_ident || matches!(self.peek_at(1), TokenKind::Str(_)))
{
self.bump();
StmtKind::Group(self.parse_group()?)
} else if self.at_kw("class") && next_is_ident {
self.bump();
let name = self.expect_ident("for the class")?;
let body = self.parse_block()?;
StmtKind::Class(Class { name, body })
} else if self.at_kw("constrain") && next_is_lbrace {
self.bump();
StmtKind::Constrain(self.parse_constrain_block()?)
} else if self.at_kw("pin") && next_is_ident {
self.bump();
StmtKind::Pin(self.parse_pin()?)
} else if self.at_kw("for") && next_is_ident {
self.bump();
StmtKind::For(self.parse_for()?)
} else if self.at_kw("port") && next_is_ident {
self.bump();
let name = self.expect_ident("for the port")?;
let body = if matches!(self.peek(), TokenKind::LBrace) {
self.parse_block()?
} else {
Block {
stmts: Vec::new(),
span: self.prev_span(),
}
};
StmtKind::Port(Port { name, body })
} else if matches!(self.peek(), TokenKind::Ident(_)) {
self.parse_ident_stmt()?
} else {
let found = self.peek().describe();
let mut d = Diagnostic::error("E0110", format!("expected a statement, found {found}"))
.with_label(self.span(), "not the start of any drawlang statement");
if let TokenKind::Ident(name) = self.peek() {
if let Some(s) = crate::diag::suggest(name, STMT_KEYWORDS.iter().copied()) {
d = d.with_help(format!("did you mean `{s}`?"));
}
}
d = d.with_help(
"statements are nodes (`id { ... }`), edges (`a -> b`), properties \
(`key: value`), or the keywords canvas/def/group/class/constrain/pin/for/port",
);
self.diags.push(d);
return Err(Bail);
};
let span = start.to(self.prev_span());
Ok(Stmt {
kind,
span,
trivia: Trivia::default(),
})
}
fn parse_ident_stmt(&mut self) -> PResult<StmtKind> {
if let (TokenKind::Ident(first), TokenKind::Ident(_)) = (self.peek(), self.peek_at(1)) {
if let Some(s) = crate::diag::suggest(first, STMT_KEYWORDS.iter().copied()) {
let first = first.clone();
self.diags.push(
Diagnostic::error("E0110", format!("unknown statement `{first}`"))
.with_label(self.span(), "not a drawlang keyword")
.with_help(format!("did you mean `{s}`?")),
);
return Err(Bail);
}
}
let path = self.parse_path(false)?;
match self.peek() {
TokenKind::Arrow | TokenKind::BidiArrow | TokenKind::BackArrow => {
self.parse_edge_rest(path)
}
TokenKind::LBrace => {
let name = self.path_as_single_name(path, "node")?;
let body = self.parse_block()?;
Ok(StmtKind::Node(Node {
name: Some(name),
kind: NodeKind::Plain { body },
}))
}
TokenKind::LParen => {
let callee = self.path_as_single_name(path, "component")?;
let args = self.parse_call_args()?;
let body = if matches!(self.peek(), TokenKind::LBrace) {
Some(self.parse_block()?)
} else {
None
};
Ok(StmtKind::Node(Node {
name: None,
kind: NodeKind::Call { callee, args, body },
}))
}
TokenKind::Colon => {
self.bump();
self.parse_after_colon(path)
}
TokenKind::Newline
| TokenKind::Semi
| TokenKind::Comment(_)
| TokenKind::RBrace
| TokenKind::Eof => {
let name = self.path_as_single_name(path, "node")?;
let span = name.span;
Ok(StmtKind::Node(Node {
name: Some(name),
kind: NodeKind::Plain {
body: Block {
stmts: Vec::new(),
span,
},
},
}))
}
other => {
let found = other.describe();
self.diags.push(
Diagnostic::error("E0103", format!(
"expected `{{`, `:`, `(`, or an edge arrow after `{}`, found {found}",
path.display()
))
.with_label(self.span(), "unexpected here")
.with_help("write `id { ... }` for a node, `id: value` for a property, or `a -> b` for an edge"),
);
Err(Bail)
}
}
}
fn parse_after_colon(&mut self, key_path: PathRef) -> PResult<StmtKind> {
if (self.at_kw("row") || self.at_kw("column"))
&& matches!(self.peek_at(1), TokenKind::LBrace)
{
let name = self.path_as_single_name(key_path, "container")?;
let kw = self.bump();
let ctype = if let TokenKind::Ident(k) = &kw.kind {
if k == "row" {
ContainerType::Row
} else {
ContainerType::Column
}
} else {
unreachable!()
};
let body = self.parse_block()?;
return Ok(StmtKind::Node(Node {
name: Some(name),
kind: NodeKind::Container {
ctype,
ctype_span: kw.span,
body,
},
}));
}
if self.at_kw("grid") {
let name = self.path_as_single_name(key_path, "container")?;
let kw = self.bump();
let (cols, rows, dspan) = match self.peek().clone() {
TokenKind::Dimension(c, r) => {
let t = self.bump();
(c, r, t.span)
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected grid dimensions after `grid`, found {}",
other.describe()
),
)
.with_label(
self.span(),
"expected something like `2x4` (columns x rows)",
),
);
return Err(Bail);
}
};
if cols == 0 || rows == 0 {
self.diags.push(
Diagnostic::error("E0105", "grid dimensions must be at least 1x1")
.with_label(dspan, "zero-sized grid"),
);
}
let body = self.parse_block()?;
return Ok(StmtKind::Node(Node {
name: Some(name),
kind: NodeKind::Container {
ctype: ContainerType::Grid { cols, rows },
ctype_span: kw.span.to(dspan),
body,
},
}));
}
if matches!(self.peek(), TokenKind::Ident(_))
&& matches!(self.peek_at(1), TokenKind::LParen)
{
let name = self.path_as_single_name(key_path, "node")?;
let callee = self.expect_ident("for the component")?;
let args = self.parse_call_args()?;
let body = if matches!(self.peek(), TokenKind::LBrace) {
Some(self.parse_block()?)
} else {
None
};
return Ok(StmtKind::Node(Node {
name: Some(name),
kind: NodeKind::Call { callee, args, body },
}));
}
let key = self.path_as_prop_key(key_path)?;
let value = self.parse_value()?;
let span = key
.first()
.map(|k| k.span)
.unwrap_or(Span::DUMMY)
.to(value.span());
Ok(StmtKind::Prop(Prop { key, value, span }))
}
fn parse_edge_rest(&mut self, from: PathRef) -> PResult<StmtKind> {
let op_tok = self.bump();
let to = self.parse_path(false)?;
let (from, op, to) = match op_tok.kind {
TokenKind::Arrow => (from, EdgeOp::Forward, to),
TokenKind::BidiArrow => (from, EdgeOp::Bidirectional, to),
TokenKind::BackArrow => (to, EdgeOp::Forward, from),
_ => unreachable!(),
};
let label = if self.eat(&TokenKind::Colon) {
match self.peek().clone() {
TokenKind::Str(_) => Some(self.parse_strlit()?),
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected a string label after `:`, found {}",
other.describe()
),
)
.with_label(
self.span(),
r#"edge labels are strings, like `: "PCIe 5.0 x16"`"#,
),
);
return Err(Bail);
}
}
} else {
None
};
let props = if matches!(self.peek(), TokenKind::LBrace) {
Some(self.parse_block()?)
} else {
None
};
Ok(StmtKind::Edge(Edge {
from,
op,
op_span: op_tok.span,
to,
label,
props,
}))
}
fn parse_def(&mut self) -> PResult<Def> {
let name = self.expect_ident("for the component")?;
self.expect(TokenKind::LParen, "to open the parameter list")?;
let mut params = Vec::new();
self.skip_trivia();
while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
params.push(self.expect_ident("for the parameter")?);
self.skip_trivia();
if !self.eat(&TokenKind::Comma) {
break;
}
self.skip_trivia();
}
self.expect(TokenKind::RParen, "to close the parameter list")?;
let body = self.parse_block()?;
Ok(Def { name, params, body })
}
fn parse_group(&mut self) -> PResult<Group> {
let name = self.expect_ident("for the group")?;
let label = if matches!(self.peek(), TokenKind::Str(_)) {
Some(self.parse_strlit()?)
} else {
None
};
let body = self.parse_block()?;
Ok(Group { name, label, body })
}
fn parse_pin(&mut self) -> PResult<Pin> {
let target = self.parse_path(false)?;
if !self.at_kw("at") {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected `at` after the pin target, found {}",
self.peek().describe()
),
)
.with_label(self.span(), "write `pin <element> at (x, y)`"),
);
return Err(Bail);
}
self.bump();
self.expect(TokenKind::LParen, "to open the position")?;
let x = self.parse_expr()?;
self.expect(TokenKind::Comma, "between the x and y coordinates")?;
let y = self.parse_expr()?;
self.expect(TokenKind::RParen, "to close the position")?;
Ok(Pin { target, x, y })
}
fn parse_for(&mut self) -> PResult<For> {
let var = self.expect_ident("for the loop variable")?;
if !self.at_kw("in") {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected `in` after the loop variable, found {}",
self.peek().describe()
),
)
.with_label(self.span(), "write `for i in 0..4 { ... }`"),
);
return Err(Bail);
}
self.bump();
let start = self.parse_expr()?;
self.expect(TokenKind::DotDot, "in the loop range")?;
let end = self.parse_expr()?;
let body = self.parse_block()?;
Ok(For {
var,
start,
end,
body,
})
}
fn parse_constrain_block(&mut self) -> PResult<Vec<Constraint>> {
self.expect(TokenKind::LBrace, "to open the constrain block")?;
let mut constraints = Vec::new();
loop {
let trivia = self.collect_leading_trivia();
match self.peek() {
TokenKind::RBrace => {
self.bump();
return Ok(constraints);
}
TokenKind::Eof => {
self.diags.push(
Diagnostic::error("E0103", "unclosed `constrain` block")
.with_label(self.span(), "expected `}` before end of file"),
);
return Ok(constraints);
}
_ => {}
}
let before = self.pos;
match self.parse_constraint(trivia) {
Ok(c) => constraints.push(c),
Err(Bail) => {
self.recover();
if self.pos == before && !self.at_eof() {
self.bump();
}
}
}
}
}
fn parse_constraint(&mut self, trivia: Trivia) -> PResult<Constraint> {
let start = self.span();
let mut name = self.expect_ident("for the constraint")?;
while matches!(self.peek(), TokenKind::Minus)
&& self.span().start == name.span.end
&& matches!(self.peek_at(1), TokenKind::Ident(_))
{
self.bump(); let part = self.expect_ident("after `-`")?;
name = Ident {
name: format!("{}-{}", name.name, part.name),
span: name.span.to(part.span),
};
}
self.expect(TokenKind::LParen, "to open the constraint arguments")?;
let mut args = Vec::new();
self.skip_trivia();
while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
args.push(self.parse_constraint_arg()?);
self.skip_trivia();
if !self.eat(&TokenKind::Comma) {
break;
}
self.skip_trivia();
}
self.expect(TokenKind::RParen, "to close the constraint arguments")?;
let mut c = Constraint {
name,
args,
span: start.to(self.prev_span()),
trivia: Trivia::default(),
};
c.trivia = trivia;
if let TokenKind::Comment(text) = self.peek() {
c.trivia.trailing = Some(text.clone());
self.bump();
}
Ok(c)
}
fn parse_constraint_arg(&mut self) -> PResult<ConstraintArg> {
match self.peek().clone() {
TokenKind::Int(v) => {
let t = self.bump();
Ok(ConstraintArg::Num(v as f64, t.span))
}
TokenKind::Float(v) => {
let t = self.bump();
Ok(ConstraintArg::Num(v, t.span))
}
TokenKind::Minus => {
let start = self.bump().span;
match self.peek().clone() {
TokenKind::Int(v) => {
let t = self.bump();
Ok(ConstraintArg::Num(-(v as f64), start.to(t.span)))
}
TokenKind::Float(v) => {
let t = self.bump();
Ok(ConstraintArg::Num(-v, start.to(t.span)))
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!("expected a number after `-`, found {}", other.describe()),
)
.with_label(self.span(), "expected a number"),
);
Err(Bail)
}
}
}
TokenKind::Ident(_) => Ok(ConstraintArg::Path(self.parse_path(true)?)),
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!(
"expected an element path or number, found {}",
other.describe()
),
)
.with_label(
self.span(),
"constraint arguments are element paths, keywords, or numbers",
),
);
Err(Bail)
}
}
}
fn parse_block(&mut self) -> PResult<Block> {
let open = self.expect(TokenKind::LBrace, "to open the block")?;
let stmts = self.parse_stmt_list(false);
let close = if matches!(self.peek(), TokenKind::RBrace) {
self.bump().span
} else {
self.diags.push(
Diagnostic::error("E0103", "unclosed block")
.with_label(open.span, "this `{` is never closed")
.with_label(self.span(), "expected `}` before this point"),
);
self.span()
};
Ok(Block {
stmts,
span: open.span.to(close),
})
}
fn parse_call_args(&mut self) -> PResult<Vec<Expr>> {
self.expect(TokenKind::LParen, "to open the arguments")?;
let mut args = Vec::new();
self.skip_trivia();
while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
args.push(self.parse_expr()?);
self.skip_trivia();
if !self.eat(&TokenKind::Comma) {
break;
}
self.skip_trivia();
}
self.expect(TokenKind::RParen, "to close the arguments")?;
Ok(args)
}
fn path_as_single_name(&mut self, path: PathRef, what: &str) -> PResult<Ident> {
match path.segments.as_slice() {
[PathSeg::Name(id)] => Ok(id.clone()),
_ => {
self.diags.push(
Diagnostic::error("E0107", format!("{what} names must be a single identifier"))
.with_label(
path.span,
format!("`{}` is a path, not a name", path.display()),
)
.with_help(
"dots and indices are for *referring* to elements, not declaring them",
),
);
Err(Bail)
}
}
}
fn path_as_prop_key(&mut self, path: PathRef) -> PResult<Vec<Ident>> {
let mut key = Vec::new();
for seg in &path.segments {
match seg {
PathSeg::Name(id) => key.push(id.clone()),
_ => {
self.diags.push(
Diagnostic::error("E0107", "property keys cannot contain indices")
.with_label(path.span, "expected a key like `label.wrap`"),
);
return Err(Bail);
}
}
}
Ok(key)
}
fn parse_path(&mut self, allow_wildcard: bool) -> PResult<PathRef> {
let first = self.expect_ident("to start an element path")?;
let start = first.span;
let mut segments = vec![PathSeg::Name(first)];
loop {
match self.peek() {
TokenKind::Dot => {
self.bump();
let id = self.expect_ident("after `.`")?;
segments.push(PathSeg::Name(id));
}
TokenKind::LBracket => {
self.bump();
if matches!(self.peek(), TokenKind::Star) {
let star = self.bump();
if !allow_wildcard {
self.diags.push(
Diagnostic::error(
"E0108",
"`[*]` is only allowed in constraint arguments",
)
.with_label(star.span, "wildcard not allowed here")
.with_help("name a specific index, like `[0]`"),
);
}
segments.push(PathSeg::Wildcard(star.span));
} else {
let expr = self.parse_expr()?;
segments.push(PathSeg::Index(expr));
}
self.expect(TokenKind::RBracket, "to close the index")?;
}
_ => break,
}
}
let span = start.to(self.prev_span());
Ok(PathRef { segments, span })
}
fn parse_value(&mut self) -> PResult<Value> {
match self.peek().clone() {
TokenKind::Str(_) => Ok(Value::Str(self.parse_strlit()?)),
TokenKind::Int(v) => {
let t = self.bump();
Ok(Value::Num(v as f64, t.span))
}
TokenKind::Float(v) => {
let t = self.bump();
Ok(Value::Num(v, t.span))
}
TokenKind::Minus => {
let start = self.bump().span;
match self.peek().clone() {
TokenKind::Int(v) => {
let t = self.bump();
Ok(Value::Num(-(v as f64), start.to(t.span)))
}
TokenKind::Float(v) => {
let t = self.bump();
Ok(Value::Num(-v, start.to(t.span)))
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!("expected a number after `-`, found {}", other.describe()),
)
.with_label(self.span(), "expected a number"),
);
Err(Bail)
}
}
}
TokenKind::Ident(name) => {
let t = self.bump();
Ok(Value::Word(Ident { name, span: t.span }))
}
TokenKind::AtIdent(name) => {
let t = self.bump();
Ok(Value::ThemeToken(Ident { name, span: t.span }))
}
TokenKind::HexColor(hex) => {
let t = self.bump();
Ok(Value::Color(hex, t.span))
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!("expected a property value, found {}", other.describe()),
)
.with_label(
self.span(),
"expected a string, number, word, `@token`, or `#color`",
),
);
Err(Bail)
}
}
}
fn parse_expr(&mut self) -> PResult<Expr> {
self.parse_additive()
}
fn parse_additive(&mut self) -> PResult<Expr> {
let mut lhs = self.parse_multiplicative()?;
loop {
let op = match self.peek() {
TokenKind::Plus => BinOp::Add,
TokenKind::Minus => BinOp::Sub,
_ => return Ok(lhs),
};
self.bump();
let rhs = self.parse_multiplicative()?;
let span = lhs.span.to(rhs.span);
lhs = Expr {
kind: ExprKind::Binary(op, Box::new(lhs), Box::new(rhs)),
span,
};
}
}
fn parse_multiplicative(&mut self) -> PResult<Expr> {
let mut lhs = self.parse_unary()?;
loop {
let op = match self.peek() {
TokenKind::Star => BinOp::Mul,
TokenKind::Slash => BinOp::Div,
TokenKind::Percent => BinOp::Mod,
_ => return Ok(lhs),
};
self.bump();
let rhs = self.parse_unary()?;
let span = lhs.span.to(rhs.span);
lhs = Expr {
kind: ExprKind::Binary(op, Box::new(lhs), Box::new(rhs)),
span,
};
}
}
fn parse_unary(&mut self) -> PResult<Expr> {
if matches!(self.peek(), TokenKind::Minus) {
let start = self.bump().span;
let inner = self.parse_unary()?;
let span = start.to(inner.span);
return Ok(Expr {
kind: ExprKind::Unary(UnOp::Neg, Box::new(inner)),
span,
});
}
self.parse_primary()
}
fn parse_primary(&mut self) -> PResult<Expr> {
match self.peek().clone() {
TokenKind::Int(v) => {
let t = self.bump();
Ok(Expr {
kind: ExprKind::Num(v as f64),
span: t.span,
})
}
TokenKind::Float(v) => {
let t = self.bump();
Ok(Expr {
kind: ExprKind::Num(v),
span: t.span,
})
}
TokenKind::Str(_) => {
let s = self.parse_strlit()?;
let span = s.span;
Ok(Expr {
kind: ExprKind::Str(Box::new(s)),
span,
})
}
TokenKind::Ident(name) => {
let t = self.bump();
Ok(Expr {
kind: ExprKind::Var(Ident { name, span: t.span }),
span: t.span,
})
}
TokenKind::LParen => {
self.bump();
let inner = self.parse_expr()?;
self.expect(TokenKind::RParen, "to close the parenthesized expression")?;
Ok(inner)
}
other => {
self.diags.push(
Diagnostic::error(
"E0103",
format!("expected an expression, found {}", other.describe()),
)
.with_label(self.span(), "expected a number, variable, or `(expr)`"),
);
Err(Bail)
}
}
}
fn parse_strlit(&mut self) -> PResult<StrLit> {
let tok = self.bump();
let TokenKind::Str(_) = &tok.kind else {
unreachable!("caller checked")
};
let span = tok.span;
let inner_start = span.start + 1;
let inner_end = span.end.saturating_sub(1).max(inner_start);
let raw = &self.src[inner_start.min(self.src.len())..inner_end.min(self.src.len())];
let mut parts = Vec::new();
let mut text = String::new();
let bytes = raw.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'\\' if i + 1 < bytes.len() => {
match bytes[i + 1] {
b'n' => text.push('\n'),
b't' => text.push('\t'),
b'"' => text.push('"'),
b'\\' => text.push('\\'),
b'{' => text.push('{'),
b'}' => text.push('}'),
other => text.push(other as char), }
i += 2;
}
b'{' => {
let expr_start = i + 1;
let mut depth = 1;
let mut j = expr_start;
while j < bytes.len() && depth > 0 {
match bytes[j] {
b'{' => depth += 1,
b'}' => depth -= 1,
_ => {}
}
j += 1;
}
let expr_end = j - 1; if depth > 0 {
text.push_str(&raw[i..]);
i = bytes.len();
continue;
}
if !text.is_empty() {
parts.push(StrPart::Text(std::mem::take(&mut text)));
}
let expr_src = &raw[expr_start..expr_end];
let abs_offset = inner_start + expr_start;
parts.push(StrPart::Expr(
self.parse_embedded_expr(expr_src, abs_offset)?,
));
i = j;
}
_ => {
let ch = raw[i..].chars().next().unwrap();
text.push(ch);
i += ch.len_utf8();
}
}
}
if !text.is_empty() {
parts.push(StrPart::Text(text));
}
Ok(StrLit { parts, span })
}
fn parse_embedded_expr(&mut self, expr_src: &str, abs_offset: usize) -> PResult<Expr> {
if expr_src.trim().is_empty() {
self.diags.push(
Diagnostic::error("E0104", "empty interpolation `{}` in string")
.with_label(
Span::new(abs_offset - 1, abs_offset + expr_src.len() + 1),
"nothing to interpolate",
)
.with_help(r#"put an expression inside the braces, like `"GPU {i}"`"#),
);
return Err(Bail);
}
let lexed = crate::lexer::lex(expr_src);
for mut d in lexed.diagnostics {
for l in &mut d.labels {
l.span = Span::new(l.span.start + abs_offset, l.span.end + abs_offset);
}
self.diags.push(d);
}
let tokens: Vec<Token> = lexed
.tokens
.into_iter()
.map(|t| Token {
kind: t.kind,
span: Span::new(t.span.start + abs_offset, t.span.end + abs_offset),
})
.collect();
let mut sub = Parser {
src: self.src,
tokens,
pos: 0,
diags: Vec::new(),
};
let result = sub.parse_expr();
let leftover = !matches!(sub.peek(), TokenKind::Newline | TokenKind::Eof);
self.diags.extend(sub.diags);
match result {
Ok(expr) => {
if leftover {
self.diags.push(
Diagnostic::error("E0104", "unexpected trailing tokens in interpolation")
.with_label(
Span::new(abs_offset, abs_offset + expr_src.len()),
"only a single expression is allowed inside `{...}`",
),
);
return Err(Bail);
}
Ok(expr)
}
Err(Bail) => Err(Bail),
}
}
}