mirror of
https://github.com/PaddiM8/kalker.git
synced 2025-03-13 21:08:18 +01:00
187 lines
5.8 KiB
Rust
187 lines
5.8 KiB
Rust
use crate::ast::{compare_enums, Expr, Stmt, Unit};
|
|
use crate::lexer::TokenKind;
|
|
use crate::prelude;
|
|
use crate::symbol_table::SymbolTable;
|
|
|
|
pub struct Context<'a> {
|
|
symbol_table: &'a mut SymbolTable,
|
|
angle_unit: Unit,
|
|
}
|
|
|
|
impl<'a> Context<'a> {
|
|
pub fn new(angle_unit: Unit, symbol_table: &'a mut SymbolTable) -> Self {
|
|
Context {
|
|
angle_unit: angle_unit.clone(),
|
|
symbol_table,
|
|
}
|
|
}
|
|
|
|
pub fn interpret(&mut self, statements: Vec<Stmt>) -> Result<Option<f64>, String> {
|
|
for (i, stmt) in statements.iter().enumerate() {
|
|
let value = eval_stmt(self, stmt);
|
|
|
|
if i == statements.len() - 1 {
|
|
if let Stmt::Expr(_) = stmt {
|
|
return Ok(Some(value?));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn eval_stmt(context: &mut Context, stmt: &Stmt) -> Result<f64, String> {
|
|
match stmt {
|
|
Stmt::VarDecl(identifier, _) => eval_var_decl_stmt(context, stmt, identifier),
|
|
Stmt::FnDecl(_, _, _) => eval_fn_decl_stmt(context),
|
|
Stmt::Expr(expr) => eval_expr_stmt(context, &expr),
|
|
}
|
|
}
|
|
|
|
fn eval_var_decl_stmt(context: &mut Context, stmt: &Stmt, identifier: &str) -> Result<f64, String> {
|
|
context.symbol_table.insert(&identifier, stmt.clone());
|
|
Ok(0f64)
|
|
}
|
|
|
|
fn eval_fn_decl_stmt(_: &mut Context) -> Result<f64, String> {
|
|
Ok(0f64) // Nothing needs to happen here, since the parser will already have added the FnDecl's to the symbol table.
|
|
}
|
|
|
|
fn eval_expr_stmt(context: &mut Context, expr: &Expr) -> Result<f64, String> {
|
|
eval_expr(context, &expr)
|
|
}
|
|
|
|
fn eval_expr(context: &mut Context, expr: &Expr) -> Result<f64, String> {
|
|
match expr {
|
|
Expr::Binary(left, op, right) => eval_binary_expr(context, &left, op, &right),
|
|
Expr::Unary(_, expr) => eval_unary_expr(context, expr),
|
|
Expr::Unit(expr, kind) => eval_unit_expr(context, expr, kind),
|
|
Expr::Var(identifier) => eval_var_expr(context, identifier),
|
|
Expr::Literal(value) => eval_literal_expr(context, value),
|
|
Expr::Group(expr) => eval_group_expr(context, &expr),
|
|
Expr::FnCall(identifier, expressions) => {
|
|
eval_fn_call_expr(context, identifier, expressions)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn eval_binary_expr(
|
|
context: &mut Context,
|
|
left: &Expr,
|
|
op: &TokenKind,
|
|
right: &Expr,
|
|
) -> Result<f64, String> {
|
|
let left = eval_expr(context, &left)?;
|
|
let right = eval_expr(context, &right)?;
|
|
|
|
Ok(match op {
|
|
TokenKind::Plus => left + right,
|
|
TokenKind::Minus => left - right,
|
|
TokenKind::Star => left * right,
|
|
TokenKind::Slash => left / right,
|
|
TokenKind::Power => left.powf(right),
|
|
_ => 0f64,
|
|
})
|
|
}
|
|
|
|
fn eval_unary_expr(context: &mut Context, expr: &Expr) -> Result<f64, String> {
|
|
eval_expr(context, &expr).clone()
|
|
}
|
|
|
|
fn eval_unit_expr(context: &mut Context, expr: &Expr, kind: &TokenKind) -> Result<f64, String> {
|
|
let x = eval_expr(context, &expr);
|
|
let unit = kind.to_unit()?;
|
|
|
|
// Don't do any angle conversions if the defauly angle unit is the same as the unit kind
|
|
match unit {
|
|
Unit::Degrees | Unit::Radians => {
|
|
if compare_enums(&context.angle_unit, &unit) {
|
|
return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
match unit {
|
|
Unit::Degrees => Ok(x?.to_radians()),
|
|
Unit::Radians => Ok(x?.to_degrees()),
|
|
}
|
|
}
|
|
|
|
fn eval_var_expr(context: &mut Context, identifier: &str) -> Result<f64, String> {
|
|
// If there is a constant with this name, return a literal expression with its value
|
|
if let Some(value) = prelude::CONSTANTS.get(identifier) {
|
|
return eval_expr(context, &Expr::Literal(value.to_string()));
|
|
}
|
|
|
|
// Look for the variable in the symbol table
|
|
let var_decl = context.symbol_table.get(identifier).cloned();
|
|
match var_decl {
|
|
Some(Stmt::VarDecl(_, expr)) => eval_expr(context, &expr),
|
|
_ => Err(format!("Undefined variable: '{}'.", identifier)),
|
|
}
|
|
}
|
|
|
|
fn eval_literal_expr(_: &mut Context, value: &str) -> Result<f64, String> {
|
|
match value.parse() {
|
|
Ok(parsed_value) => Ok(parsed_value),
|
|
Err(_) => Err(format!("Invalid number literal: '{}'.", value)),
|
|
}
|
|
}
|
|
|
|
fn eval_group_expr(context: &mut Context, expr: &Expr) -> Result<f64, String> {
|
|
eval_expr(context, expr)
|
|
}
|
|
|
|
fn eval_fn_call_expr(
|
|
context: &mut Context,
|
|
identifier: &str,
|
|
expressions: &Vec<Expr>,
|
|
) -> Result<f64, String> {
|
|
let prelude_func = match expressions.len() {
|
|
1 => {
|
|
let x = eval_expr(context, &expressions[0])?;
|
|
prelude::call_unary_func(identifier, x, &context.angle_unit)
|
|
}
|
|
2 => {
|
|
let x = eval_expr(context, &expressions[0])?;
|
|
let y = eval_expr(context, &expressions[1])?;
|
|
prelude::call_binary_func(identifier, x, y, &context.angle_unit)
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
if let Some(result) = prelude_func {
|
|
return Ok(result);
|
|
}
|
|
|
|
let stmt_definition = context
|
|
.symbol_table
|
|
.get(&format!("{}()", identifier))
|
|
.cloned();
|
|
|
|
match stmt_definition {
|
|
Some(Stmt::FnDecl(_, arguments, fn_body)) => {
|
|
if arguments.len() != expressions.len() {
|
|
return Err(format!(
|
|
"Expected {} arguments in function '{}' but found {}.",
|
|
arguments.len(),
|
|
identifier,
|
|
expressions.len()
|
|
));
|
|
}
|
|
|
|
// Initialise the arguments as their own variables.
|
|
for (i, argument) in arguments.iter().enumerate() {
|
|
eval_stmt(
|
|
context,
|
|
&Stmt::VarDecl(argument.clone(), Box::new(expressions[i].clone())),
|
|
)?;
|
|
}
|
|
|
|
return eval_expr(context, &*fn_body);
|
|
}
|
|
_ => Err(format!("Undefined function: '{}'.", identifier)),
|
|
}
|
|
}
|