use super::*;
impl Parser {
pub(super) fn parse_statement(&mut self) -> Result<Stmt, Diagnostic> {
match self.peek() {
TokenKind::Such => self.parse_such(),
TokenKind::So => self.parse_so(),
TokenKind::Many => self.parse_many(),
TokenKind::Very => self.parse_very(),
TokenKind::Pls => self.parse_pls(),
TokenKind::If => self.parse_if(),
TokenKind::For => self.parse_for(),
TokenKind::While => self.parse_while(),
TokenKind::Bark => self.parse_bark(),
TokenKind::Bonk => self.parse_bonk(),
TokenKind::Return => self.parse_return(),
TokenKind::Bork => {
let span = self.current_span();
self.advance();
self.eat(TokenKind::Newline)?;
Ok(Stmt::Bork { span })
}
TokenKind::Continue => {
let span = self.current_span();
self.advance();
self.eat(TokenKind::Newline)?;
Ok(Stmt::Continue { span })
}
TokenKind::Def => {
Err(self.python_habit("no def here", "such greet much name: is the way"))
}
TokenKind::Class => Err(self.python_habit("no class here", "many Name: is the way")),
TokenKind::Amaze => self.parse_amaze(),
_ => self.parse_expr_or_assign(),
}
}
pub(super) fn parse_such(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Such)?;
let (name, _) = self.eat_ident("a name after such")?;
if self.is(&TokenKind::Comma) {
let (names, rest) = self.parse_target_names(name)?;
self.expect_destructure_eq()?;
let expr = self.parse_assign_rhs(true)?;
self.eat(TokenKind::Newline)?;
return Ok(Stmt::Decl {
names,
rest,
expr,
span,
});
}
match self.peek() {
TokenKind::Eq => {
self.advance();
let expr = self.parse_assign_rhs(false)?;
self.eat(TokenKind::Newline)?;
Ok(Stmt::Decl {
names: vec![name],
rest: None,
expr,
span,
})
}
TokenKind::Much | TokenKind::Colon => {
let mut params = Params::default();
if self.is(&TokenKind::Much) {
self.advance();
params = self.parse_params()?;
}
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
if !self.is(&TokenKind::Wow) {
return Err(self.missing_wow(self.current_span(), "function"));
}
self.advance(); self.eat(TokenKind::Newline)?;
Ok(Stmt::FuncDef {
name,
params,
body,
span,
})
}
_ => {
let bad = self.current_span();
Err(self
.diag(
bad,
format!("after such {name}, doge expects = (a variable) or : (a function)"),
)
.with_hint("such x = 1 — or — such greet much name:"))
}
}
}
pub(super) fn parse_so(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::So)?;
if let TokenKind::Str(_) | TokenKind::StrInterp(_) = self.peek() {
return self.parse_path_import(span);
}
let (name, _) = self.eat_ident("a name after so")?;
match self.peek() {
TokenKind::Eq => {
self.advance();
let expr = self.parse_expr()?;
self.eat(TokenKind::Newline)?;
Ok(Stmt::ConstDecl { name, expr, span })
}
TokenKind::Newline => {
self.advance();
Ok(Stmt::Import {
module: name,
path: None,
span,
})
}
_ => {
let bad = self.current_span();
Err(self
.diag(
bad,
format!("after so {name}, doge expects = (a constant) or a line end (an import)"),
)
.with_hint("so PI = 3.14 — so math — or — so \"lib/utils.doge\""))
}
}
}
fn parse_path_import(&mut self, span: Span) -> Result<Stmt, Diagnostic> {
let str_span = self.current_span();
if let TokenKind::StrInterp(_) = self.peek() {
self.advance();
return Err(self
.diag(
str_span,
"an import path is a plain string, not an interpolated one",
)
.with_headline("very import. much confuse.")
.with_hint("write a fixed path — so \"lib/utils.doge\""));
}
let TokenKind::Str(raw) = self.advance().kind else {
unreachable!("parse_path_import entered only on a string token");
};
self.eat(TokenKind::Newline)?;
let module = self.import_path_stem(&raw, str_span)?;
Ok(Stmt::Import {
module,
path: Some(raw),
span,
})
}
fn import_path_stem(&self, raw: &str, span: Span) -> Result<String, Diagnostic> {
let bad = |message: &str, hint: &str| {
self.diag(span, message.to_string())
.with_headline("very path. much confuse.")
.with_hint(hint.to_string())
};
if raw.contains('\\') {
return Err(bad(
"an import path uses / separators, not \\",
"write forward slashes — so \"lib/utils.doge\"",
));
}
if raw.starts_with('/') {
return Err(bad(
"an import path is relative to this file, so it cannot start with /",
"drop the leading / — so \"lib/utils.doge\"",
));
}
let Some(file) = raw.rsplit('/').next().filter(|f| !f.is_empty()) else {
return Err(bad(
"an import path needs a file name",
"point at a .doge file — so \"lib/utils.doge\"",
));
};
let Some(stem) = file.strip_suffix(".doge") else {
return Err(bad(
"an import path names a .doge file",
"add the extension — so \"lib/utils.doge\"",
));
};
if stem.is_empty() || !is_ident(stem) {
return Err(bad(
"the file name in an import path must be a plain name (so it can bind the module)",
"rename the file to a plain name — so \"lib/utils.doge\"",
));
}
Ok(stem.to_string())
}
pub(super) fn parse_many(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Many)?;
let (name, _) = self.eat_ident("an object name after many")?;
let parent = if self.is(&TokenKind::Much) {
self.advance();
let (parent, _) = self.eat_ident("a parent object name after much")?;
Some(parent)
} else {
None
};
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
let mut methods = Vec::new();
for stmt in body {
match stmt {
Stmt::FuncDef { .. } => methods.push(stmt),
other => {
let bad = other.span();
return Err(self
.diag(bad, "an object body may only hold methods (such …:)")
.with_headline("very object. much confuse.")
.with_hint("move this out of the object, or make it a method"));
}
}
}
if !self.is(&TokenKind::Wow) {
return Err(self.missing_wow(self.current_span(), "object"));
}
self.advance(); self.eat(TokenKind::Newline)?;
Ok(Stmt::ObjDef {
name,
parent,
methods,
span,
})
}
pub(super) fn parse_very(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Very)?;
let first = self.parse_expr()?;
if self.is(&TokenKind::Comma) {
return self.finish_multi_assign(first, true, span);
}
let op = match self.peek() {
TokenKind::Eq => None,
TokenKind::AugAssign(op) => Some(*op),
_ => {
return Err(self
.diag(
first.span(),
"very always reassigns — doge expects = after the target",
)
.with_hint("very age = 9"))
}
};
self.advance();
let expr = self.parse_assign_rhs(false)?;
self.eat(TokenKind::Newline)?;
self.require_target(&first)?;
Ok(Stmt::Assign {
targets: vec![first],
rest: None,
expr,
op,
flavored: true,
span,
})
}
pub(super) fn parse_pls(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Pls)?;
let body = self.parse_block()?;
self.eat(TokenKind::OhNo)?;
let (err_name, _) = self.eat_ident("the caught error's name after oh no")?;
self.eat(TokenKind::Bang)?;
let handler = self.parse_block()?;
Ok(Stmt::Try {
body,
err_name,
handler,
span,
})
}
pub(super) fn parse_if(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::If)?;
let mut branches = Vec::new();
let cond = self.parse_expr()?;
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
branches.push((cond, body));
while self.is(&TokenKind::Elif) {
self.advance();
let cond = self.parse_expr()?;
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
branches.push((cond, body));
}
let else_body = if self.is(&TokenKind::Else) {
self.advance();
self.eat(TokenKind::Colon)?;
Some(self.parse_block()?)
} else {
None
};
Ok(Stmt::If {
branches,
else_body,
span,
})
}
pub(super) fn parse_for(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::For)?;
let (first, _) = self.eat_ident("a loop variable after for")?;
let (vars, rest) = if self.is(&TokenKind::Comma) {
self.parse_target_names(first)?
} else {
(vec![first], None)
};
self.eat(TokenKind::In)?;
let iter = self.parse_expr()?;
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
Ok(Stmt::For {
vars,
rest,
iter,
body,
span,
})
}
pub(super) fn parse_while(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::While)?;
let cond = self.parse_expr()?;
self.eat(TokenKind::Colon)?;
let body = self.parse_block()?;
Ok(Stmt::While { cond, body, span })
}
pub(super) fn parse_bark(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Bark)?;
let expr = self.parse_expr()?;
self.eat(TokenKind::Newline)?;
Ok(Stmt::Bark { expr, span })
}
pub(super) fn parse_bonk(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Bonk)?;
let expr = self.parse_expr()?;
self.eat(TokenKind::Newline)?;
Ok(Stmt::Bonk { expr, span })
}
pub(super) fn parse_amaze(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Amaze)?;
let cond = self.parse_expr()?;
let message = if self.is(&TokenKind::Comma) {
self.advance();
Some(self.parse_expr()?)
} else {
None
};
self.eat(TokenKind::Newline)?;
Ok(Stmt::Amaze {
cond,
message,
span,
})
}
pub(super) fn parse_return(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
self.eat(TokenKind::Return)?;
let expr = if self.is(&TokenKind::Newline) {
None
} else {
Some(self.parse_expr()?)
};
self.eat(TokenKind::Newline)?;
Ok(Stmt::Return { expr, span })
}
pub(super) fn parse_expr_or_assign(&mut self) -> Result<Stmt, Diagnostic> {
let span = self.current_span();
let expr = self.parse_expr()?;
if self.is(&TokenKind::Comma) {
return self.finish_multi_assign(expr, false, span);
}
let assign = match self.peek() {
TokenKind::Eq => Some(None),
TokenKind::AugAssign(op) => Some(Some(*op)),
_ => None,
};
if let Some(op) = assign {
self.advance();
let value = self.parse_assign_rhs(false)?;
self.eat(TokenKind::Newline)?;
self.require_target(&expr)?;
Ok(Stmt::Assign {
targets: vec![expr],
rest: None,
expr: value,
op,
flavored: false,
span,
})
} else {
self.eat(TokenKind::Newline)?;
Ok(Stmt::ExprStmt { expr })
}
}
fn finish_multi_assign(
&mut self,
first: Expr,
flavored: bool,
span: Span,
) -> Result<Stmt, Diagnostic> {
let (targets, rest) = self.parse_target_exprs(first)?;
if let TokenKind::AugAssign(_) = self.peek() {
return Err(self
.diag(
self.current_span(),
"augmented assignment takes a single target",
)
.with_headline("very many. much augment.")
.with_hint("split it up, e.g. a = a + 1 on its own line"));
}
self.expect_destructure_eq()?;
let value = self.parse_assign_rhs(true)?;
self.eat(TokenKind::Newline)?;
for target in &targets {
self.require_target(target)?;
}
if let Some(rest) = &rest {
self.require_target(rest)?;
}
Ok(Stmt::Assign {
targets,
rest,
expr: value,
op: None,
flavored,
span,
})
}
pub(super) fn parse_block(&mut self) -> Result<Vec<Stmt>, Diagnostic> {
self.eat(TokenKind::Newline)?;
self.eat(TokenKind::Indent).map_err(|_| {
let span = self.current_span();
self.diag(span, "doge expected an indented block here")
.with_headline("very flat. much empty.")
.with_hint("indent the body with spaces")
})?;
let mut stmts = Vec::new();
while !self.is(&TokenKind::Dedent) {
if self.is(&TokenKind::Eof) {
return Err(self.missing_wow(self.current_span(), "block"));
}
stmts.push(self.parse_statement()?);
}
self.eat(TokenKind::Dedent)?;
Ok(stmts)
}
pub(super) fn parse_params(&mut self) -> Result<Params, Diagnostic> {
let mut params: Vec<Param> = Vec::new();
let mut vararg: Option<String> = None;
loop {
if self.is(&TokenKind::Many) {
self.advance();
let (name, _) = self.eat_ident("a name after many")?;
vararg = Some(name);
if self.is(&TokenKind::Comma) {
let comma = self.current_span();
return Err(self
.diag(comma, "the many parameter must be the last one")
.with_headline("very rest. much greedy.")
.with_hint("put many rest at the end of the parameter list"));
}
break;
}
let (name, span) = self.eat_ident("a parameter name")?;
let default = if self.is(&TokenKind::Eq) {
self.advance();
let expr = self.parse_expr()?;
self.require_literal(&expr)?;
Some(expr)
} else {
if params.last().is_some_and(|p| p.default.is_some()) {
return Err(self
.diag(
span,
"a parameter with no default cannot follow one with a default",
)
.with_headline("very order. much default.")
.with_hint("move defaulted parameters to the end of the list"));
}
None
};
params.push(Param {
name,
default,
span,
});
if self.is(&TokenKind::Comma) {
self.advance();
} else {
break;
}
}
Ok(Params { params, vararg })
}
pub(super) fn require_literal(&self, expr: &Expr) -> Result<(), Diagnostic> {
let ok = match expr {
Expr::Int { .. }
| Expr::Float { .. }
| Expr::Str { .. }
| Expr::Bool { .. }
| Expr::None { .. } => true,
Expr::Unary {
op: UnOp::Neg,
operand,
..
} => matches!(operand.as_ref(), Expr::Int { .. } | Expr::Float { .. }),
Expr::List { items, .. } => {
for item in items {
self.require_literal(item)?;
}
true
}
Expr::Dict { entries, .. } => {
for (key, value) in entries {
self.require_literal(key)?;
self.require_literal(value)?;
}
true
}
_ => false,
};
if ok {
Ok(())
} else {
Err(self
.diag(expr.span(), "a default parameter value must be a literal")
.with_headline("very default. much dynamic.")
.with_hint("use a fixed value like 0, \"hi\", true, none, or [ ]"))
}
}
pub(super) fn require_target(&self, expr: &Expr) -> Result<(), Diagnostic> {
match expr {
Expr::Ident { .. } | Expr::Index { .. } | Expr::Attr { .. } => Ok(()),
other => Err(self
.diag(other.span(), "doge cannot assign to this")
.with_hint("assign to a name, an item like xs[0], or a field like x.name")),
}
}
fn parse_target_names(
&mut self,
first: String,
) -> Result<(Vec<String>, Option<String>), Diagnostic> {
let mut names = vec![first];
let mut rest = None;
while self.is(&TokenKind::Comma) {
self.advance();
if self.is(&TokenKind::Many) {
self.advance();
let (name, _) = self.eat_ident("a name after many")?;
rest = Some(name);
self.reject_target_after_collector()?;
break;
}
let (name, _) = self.eat_ident("a name after the comma")?;
names.push(name);
}
Ok((names, rest))
}
fn parse_target_exprs(&mut self, first: Expr) -> Result<(Vec<Expr>, Option<Expr>), Diagnostic> {
let mut targets = vec![first];
let mut rest = None;
while self.is(&TokenKind::Comma) {
self.advance();
if self.is(&TokenKind::Many) {
self.advance();
rest = Some(self.parse_expr()?);
self.reject_target_after_collector()?;
break;
}
targets.push(self.parse_expr()?);
}
Ok((targets, rest))
}
fn reject_target_after_collector(&self) -> Result<(), Diagnostic> {
if self.is(&TokenKind::Comma) {
return Err(self
.diag(
self.current_span(),
"the many collector must be the last target",
)
.with_headline("very rest. much greedy.")
.with_hint("put many rest at the end of the target list"));
}
Ok(())
}
fn parse_assign_rhs(&mut self, multi: bool) -> Result<Expr, Diagnostic> {
let first = self.parse_expr()?;
if !self.is(&TokenKind::Comma) {
return Ok(first);
}
if !multi {
return Err(self
.diag(
self.current_span(),
"doge sees extra values but only one name to hold them",
)
.with_headline("very many. much value.")
.with_hint("give the left side matching names, or wrap the values in [ ]"));
}
let span = first.span();
let mut items = vec![first];
while self.is(&TokenKind::Comma) {
self.advance();
items.push(self.parse_expr()?);
}
Ok(Expr::List { items, span })
}
fn expect_destructure_eq(&mut self) -> Result<(), Diagnostic> {
if !self.is(&TokenKind::Eq) {
return Err(self
.diag(
self.current_span(),
"a destructuring assignment needs = and values",
)
.with_headline("very unpack. much incomplete.")
.with_hint("give matching values, e.g. a, b = [1, 2]"));
}
self.advance();
Ok(())
}
}
fn is_ident(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}