use std::collections::HashMap;
use diag_lang::{Diagnostic, Label, Severity};
use parser_lang::{Parser, Pratt, Span, Token, TokenKind};
use repl_lang::{Input, Status};
#[derive(Debug, PartialEq, Eq)]
pub enum Reply {
Value(i64),
Bound { name: String, value: i64 },
}
#[derive(Default)]
pub struct Calc {
bindings: HashMap<String, (i64, Span)>,
tokens: Vec<Token<Kind>>,
}
impl Calc {
pub fn new() -> Self {
Self::default()
}
pub fn run(&mut self, input: Input<'_>) -> Status<Result<Reply, Vec<Diagnostic>>> {
lex(input, &mut self.tokens);
let mut parser = Parser::new(&self.tokens);
let entry = parse_entry(&mut parser, input);
let errors = parser.into_errors();
if input.is_incomplete(&errors) {
return Status::Incomplete;
}
let result = match entry {
Some(entry) if errors.is_empty() => self.eval_entry(entry, input),
_ => Err(errors),
};
Status::Complete(result)
}
fn eval_entry(&mut self, entry: Entry, input: Input<'_>) -> Result<Reply, Vec<Diagnostic>> {
match entry {
Entry::Expr(expr) => self
.eval(&expr, input)
.map(Reply::Value)
.map_err(|d| vec![d]),
Entry::Let { name, value } => {
let text = slice(input, name).to_owned();
if let Some(&(_, first)) = self.bindings.get(&text) {
return Err(vec![
Diagnostic::new(
Severity::Error,
format!("`{text}` is already defined"),
Label::new(name, "defined again here"),
)
.with_secondary(Label::new(first, "first defined here"))
.with_help("bindings are immutable; choose another name"),
]);
}
let value = self.eval(&value, input).map_err(|d| vec![d])?;
let _previous = self.bindings.insert(text.clone(), (value, name));
Ok(Reply::Bound { name: text, value })
}
}
}
#[allow(clippy::result_large_err)]
fn eval(&self, expr: &Expr, input: Input<'_>) -> Result<i64, Diagnostic> {
match expr {
Expr::Num { value, .. } => Ok(*value),
Expr::Var { span } => {
let name = slice(input, *span);
self.bindings
.get(name)
.map(|&(value, _)| value)
.ok_or_else(|| {
Diagnostic::new(
Severity::Error,
format!("cannot find `{name}`"),
Label::new(*span, "not defined"),
)
.with_help(format!("define it first: `let {name} = ...`"))
})
}
Expr::Neg { operand, span } => {
let value = self.eval(operand, input)?;
value.checked_neg().ok_or_else(|| overflow(*span))
}
Expr::Binary {
op,
op_span,
left,
right,
} => {
let l = self.eval(left, input)?;
let r = self.eval(right, input)?;
let span = left.span().merge(right.span());
let zero = || {
Diagnostic::new(
Severity::Error,
"division by zero",
Label::new(*op_span, "cannot divide"),
)
.with_secondary(Label::new(right.span(), "this is zero"))
};
match op {
Kind::Plus => l.checked_add(r).ok_or_else(|| overflow(span)),
Kind::Minus => l.checked_sub(r).ok_or_else(|| overflow(span)),
Kind::Star => l.checked_mul(r).ok_or_else(|| overflow(span)),
Kind::Slash if r == 0 => Err(zero()),
Kind::Slash => l.checked_div(r).ok_or_else(|| overflow(span)),
Kind::Percent if r == 0 => Err(zero()),
Kind::Percent => l.checked_rem(r).ok_or_else(|| overflow(span)),
_ => match u32::try_from(r) {
Ok(exp) => l.checked_pow(exp).ok_or_else(|| overflow(span)),
Err(_) => Err(Diagnostic::new(
Severity::Error,
"exponent out of range",
Label::new(right.span(), format!("{r} is not a valid exponent")),
)),
},
}
}
}
}
}
fn overflow(span: Span) -> Diagnostic {
Diagnostic::new(
Severity::Error,
"arithmetic overflow",
Label::new(span, "result does not fit in 64 bits"),
)
}
fn slice(input: Input<'_>, span: Span) -> &str {
let base = input.base().to_usize();
&input.text()[span.start().to_usize() - base..span.end().to_usize() - base]
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
Num,
Name,
Let,
Assign,
Plus,
Minus,
Star,
Slash,
Percent,
Caret,
LParen,
RParen,
Space,
Comment,
Unknown,
Eof,
}
impl TokenKind for Kind {
fn is_trivia(&self) -> bool {
matches!(self, Kind::Space | Kind::Comment)
}
fn is_eof(&self) -> bool {
matches!(self, Kind::Eof)
}
}
fn lex(input: Input<'_>, tokens: &mut Vec<Token<Kind>>) {
tokens.clear();
let mut cursor = input.cursor();
while let Some(c) = cursor.bump() {
let kind = match c {
c if c.is_whitespace() => {
cursor.eat_while(char::is_whitespace);
Kind::Space
}
'#' => {
cursor.eat_while(|c| c != '\n');
Kind::Comment
}
'0'..='9' => {
cursor.eat_while(|c| c.is_ascii_digit() || c == '_');
Kind::Num
}
c if c.is_alphabetic() || c == '_' => {
cursor.eat_while(|c| c.is_alphanumeric() || c == '_');
if cursor.lexeme() == "let" {
Kind::Let
} else {
Kind::Name
}
}
'=' => Kind::Assign,
'+' => Kind::Plus,
'-' => Kind::Minus,
'*' => Kind::Star,
'/' => Kind::Slash,
'%' => Kind::Percent,
'^' => Kind::Caret,
'(' => Kind::LParen,
')' => Kind::RParen,
_ => Kind::Unknown,
};
tokens.push(cursor.emit(kind));
}
tokens.push(Token::new(Kind::Eof, Span::empty(cursor.pos().to_u32())));
}
enum Entry {
Expr(Expr),
Let { name: Span, value: Expr },
}
enum Expr {
Num {
value: i64,
span: Span,
},
Var {
span: Span,
},
Neg {
operand: Box<Expr>,
span: Span,
},
Binary {
op: Kind,
op_span: Span,
left: Box<Expr>,
right: Box<Expr>,
},
}
impl Expr {
fn span(&self) -> Span {
match self {
Expr::Num { span, .. } | Expr::Var { span } | Expr::Neg { span, .. } => *span,
Expr::Binary { left, right, .. } => left.span().merge(right.span()),
}
}
}
fn parse_entry(p: &mut Parser<'_, Kind>, input: Input<'_>) -> Option<Entry> {
let mut grammar = Grammar { input };
let entry = if p.eat(|k| *k == Kind::Let).is_some() {
let name = p.expect(|k| *k == Kind::Name, "a name")?.span();
let _assign = p.expect(|k| *k == Kind::Assign, "`=`")?;
let value = grammar.parse(p)?;
Entry::Let { name, value }
} else {
Entry::Expr(grammar.parse(p)?)
};
if !p.at_end() {
p.error("expected an operator or the end of the entry");
return None;
}
Some(entry)
}
struct Grammar<'a> {
input: Input<'a>,
}
impl<'t> Pratt<'t, Kind> for Grammar<'_> {
type Output = Expr;
fn prefix(&mut self, p: &mut Parser<'t, Kind>) -> Option<Expr> {
if p.at_end() {
p.error("expected an expression");
return None;
}
let token = p.bump()?;
let span = token.span();
match token.kind() {
Kind::Num => match slice(self.input, span).replace('_', "").parse::<i64>() {
Ok(value) => Some(Expr::Num { value, span }),
Err(_) => {
p.error_at(span, "number does not fit in 64 bits");
None
}
},
Kind::Name => Some(Expr::Var { span }),
Kind::Minus => {
let operand = self.expression(p, 5)?;
let span = span.merge(operand.span());
Some(Expr::Neg {
operand: Box::new(operand),
span,
})
}
Kind::LParen => {
let inner = self.expression(p, 0)?;
let _close = p.expect(|k| *k == Kind::RParen, "`)`")?;
Some(inner)
}
Kind::Unknown => {
p.error_at(span, "unexpected character");
None
}
_ => {
p.error_at(span, "expected an expression");
None
}
}
}
fn infix_binding(&self, kind: &Kind) -> Option<(u8, u8)> {
match kind {
Kind::Plus | Kind::Minus => Some((1, 2)),
Kind::Star | Kind::Slash | Kind::Percent => Some((3, 4)),
Kind::Caret => Some((7, 6)),
_ => None,
}
}
fn infix(&mut self, op: &'t Token<Kind>, left: Expr, right: Expr) -> Option<Expr> {
Some(Expr::Binary {
op: *op.kind(),
op_span: op.span(),
left: Box::new(left),
right: Box::new(right),
})
}
}