476 lines
18 KiB
Rust
476 lines
18 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use cord_trig::ir::{NodeId, TrigGraph, TrigOp};
|
|
use crate::token::Token;
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct UserFunc {
|
|
pub(crate) params: Vec<String>,
|
|
pub(crate) defaults: Vec<Option<Vec<Token>>>,
|
|
pub(crate) body: Vec<Token>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct Schematic {
|
|
pub(crate) params: Vec<String>,
|
|
pub(crate) defaults: Vec<Option<Vec<Token>>>,
|
|
pub(crate) body: Vec<Token>,
|
|
pub(crate) value_returning: bool,
|
|
}
|
|
|
|
pub(crate) struct ExprParser<'a> {
|
|
pub(crate) tokens: &'a [Token],
|
|
pub(crate) token_lines: &'a [usize],
|
|
pub(crate) source_lines: &'a [&'a str],
|
|
pub(crate) pos: usize,
|
|
pub(crate) graph: TrigGraph,
|
|
pub(crate) input_x: Option<NodeId>,
|
|
pub(crate) input_y: Option<NodeId>,
|
|
pub(crate) input_z: Option<NodeId>,
|
|
pub(crate) ref_a: Option<&'a TrigGraph>,
|
|
pub(crate) ref_b: Option<&'a TrigGraph>,
|
|
pub(crate) vars: HashMap<String, NodeId>,
|
|
pub(crate) funcs: HashMap<String, UserFunc>,
|
|
pub(crate) schematics: HashMap<String, Schematic>,
|
|
pub(crate) objects: Vec<String>,
|
|
pub(crate) object_nodes: HashMap<String, NodeId>,
|
|
pub(crate) obj_results: HashSet<NodeId>,
|
|
pub(crate) cast_nodes: Vec<(String, NodeId)>,
|
|
pub(crate) cast_all: bool,
|
|
pub(crate) plot_nodes: Vec<NodeId>,
|
|
pub(crate) plot_all: bool,
|
|
pub(crate) bare_exprs: Vec<NodeId>,
|
|
pub(crate) vars_since_last_cast: u32,
|
|
pub(crate) exprs_since_last_plot: u32,
|
|
pub(crate) used_vars: HashSet<String>,
|
|
pub(crate) warnings: Vec<String>,
|
|
}
|
|
|
|
impl<'a> ExprParser<'a> {
|
|
pub(crate) fn new(
|
|
tokens: &'a [Token],
|
|
token_lines: &'a [usize],
|
|
source_lines: &'a [&'a str],
|
|
ref_a: Option<&'a TrigGraph>,
|
|
ref_b: Option<&'a TrigGraph>,
|
|
) -> Self {
|
|
ExprParser {
|
|
tokens,
|
|
token_lines,
|
|
source_lines,
|
|
pos: 0,
|
|
graph: TrigGraph::new(),
|
|
input_x: None,
|
|
input_y: None,
|
|
input_z: None,
|
|
ref_a,
|
|
ref_b,
|
|
vars: HashMap::new(),
|
|
funcs: HashMap::new(),
|
|
schematics: HashMap::new(),
|
|
objects: Vec::new(),
|
|
object_nodes: HashMap::new(),
|
|
obj_results: HashSet::new(),
|
|
cast_nodes: Vec::new(),
|
|
cast_all: false,
|
|
plot_nodes: Vec::new(),
|
|
plot_all: false,
|
|
bare_exprs: Vec::new(),
|
|
vars_since_last_cast: 0,
|
|
exprs_since_last_plot: 0,
|
|
used_vars: HashSet::new(),
|
|
warnings: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn mark_obj(&mut self, node: NodeId) -> NodeId {
|
|
self.obj_results.insert(node);
|
|
node
|
|
}
|
|
|
|
pub(crate) fn is_obj_node(&self, node: NodeId) -> bool {
|
|
self.obj_results.contains(&node)
|
|
}
|
|
|
|
pub(crate) fn get_x(&mut self) -> NodeId {
|
|
*self.input_x.get_or_insert_with(|| self.graph.push(TrigOp::InputX))
|
|
}
|
|
|
|
pub(crate) fn get_y(&mut self) -> NodeId {
|
|
*self.input_y.get_or_insert_with(|| self.graph.push(TrigOp::InputY))
|
|
}
|
|
|
|
pub(crate) fn get_z(&mut self) -> NodeId {
|
|
*self.input_z.get_or_insert_with(|| self.graph.push(TrigOp::InputZ))
|
|
}
|
|
|
|
pub(crate) fn peek(&self) -> Option<&Token> {
|
|
self.tokens.get(self.pos)
|
|
}
|
|
|
|
pub(crate) fn advance(&mut self) -> Option<&Token> {
|
|
let t = self.tokens.get(self.pos);
|
|
self.pos += 1;
|
|
t
|
|
}
|
|
|
|
pub(crate) fn skip_separators(&mut self) {
|
|
while matches!(self.peek(), Some(Token::Semi) | Some(Token::Newline)) {
|
|
self.advance();
|
|
}
|
|
}
|
|
|
|
pub(crate) fn current_line(&self) -> usize {
|
|
let idx = if self.pos > 0 { self.pos - 1 } else { 0 };
|
|
self.token_lines.get(idx).copied().unwrap_or(0)
|
|
}
|
|
|
|
pub(crate) fn err_at(&self, msg: String) -> String {
|
|
let ln = self.current_line();
|
|
if ln == 0 || self.source_lines.is_empty() {
|
|
return msg;
|
|
}
|
|
let src = self.source_lines.get(ln - 1).unwrap_or(&"");
|
|
let src = src.trim();
|
|
if src.is_empty() {
|
|
format!("line {ln}: {msg}")
|
|
} else {
|
|
format!("line {ln}: {msg} | {src}")
|
|
}
|
|
}
|
|
|
|
pub(crate) fn expect(&mut self, expected: &Token) -> Result<(), String> {
|
|
let t = self.advance().cloned();
|
|
match t {
|
|
Some(ref t) if t == expected => Ok(()),
|
|
Some(t) => Err(self.err_at(format!("expected {expected:?}, got {t:?}"))),
|
|
None => Err(self.err_at(format!("expected {expected:?}, got end of input"))),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_program(&mut self) -> Result<NodeId, String> {
|
|
let mut last = None;
|
|
loop {
|
|
self.skip_separators();
|
|
if self.pos >= self.tokens.len() { break; }
|
|
|
|
if self.is_func_def() {
|
|
self.parse_func_def()?;
|
|
if self.pos >= self.tokens.len() { break; }
|
|
continue;
|
|
}
|
|
if matches!(self.peek(), Some(Token::Ident(s)) if s == "sch") {
|
|
self.parse_sch_def()?;
|
|
if self.pos >= self.tokens.len() { break; }
|
|
continue;
|
|
}
|
|
if matches!(self.peek(), Some(Token::Ident(s)) if s == "cast")
|
|
&& matches!(self.tokens.get(self.pos + 1), Some(Token::LParen))
|
|
{
|
|
self.advance();
|
|
self.advance();
|
|
if matches!(self.peek(), Some(Token::RParen)) {
|
|
self.advance();
|
|
self.cast_all = true;
|
|
self.vars_since_last_cast = 0;
|
|
self.skip_separators();
|
|
continue;
|
|
}
|
|
match self.peek().cloned() {
|
|
Some(Token::Ident(name)) if matches!(self.tokens.get(self.pos + 1), Some(Token::RParen)) => {
|
|
if !self.vars.contains_key(&name) {
|
|
return Err(self.err_at(format!("'{name}' is not defined")));
|
|
}
|
|
let node_id = *self.vars.get(&name).unwrap();
|
|
self.advance();
|
|
self.advance();
|
|
self.cast_nodes.push((name, node_id));
|
|
self.vars_since_last_cast = 0;
|
|
self.skip_separators();
|
|
continue;
|
|
}
|
|
_ => return Err("cast() expects a variable name or no arguments".into()),
|
|
}
|
|
}
|
|
if matches!(self.peek(), Some(Token::Ident(s)) if s == "plot")
|
|
&& matches!(self.tokens.get(self.pos + 1), Some(Token::LParen))
|
|
{
|
|
self.advance();
|
|
self.advance();
|
|
if matches!(self.peek(), Some(Token::RParen)) {
|
|
self.advance();
|
|
self.plot_all = true;
|
|
self.exprs_since_last_plot = 0;
|
|
self.skip_separators();
|
|
continue;
|
|
}
|
|
let node = self.parse_additive()?;
|
|
self.expect(&Token::RParen)?;
|
|
self.plot_nodes.push(node);
|
|
self.exprs_since_last_plot = 0;
|
|
self.skip_separators();
|
|
continue;
|
|
}
|
|
if let Some(Token::Ident(_)) = self.peek() {
|
|
if matches!(self.tokens.get(self.pos + 1), Some(Token::Dot)) {
|
|
if let Some(Token::Ident(method)) = self.tokens.get(self.pos + 2) {
|
|
if method == "cast"
|
|
&& matches!(self.tokens.get(self.pos + 3), Some(Token::LParen))
|
|
&& matches!(self.tokens.get(self.pos + 4), Some(Token::RParen))
|
|
{
|
|
let name = match self.peek().cloned() {
|
|
Some(Token::Ident(n)) => n,
|
|
_ => unreachable!(),
|
|
};
|
|
if !self.object_nodes.contains_key(&name) {
|
|
if self.vars.contains_key(&name) {
|
|
return Err(format!("'{name}' is not an Obj — cannot call .cast()"));
|
|
}
|
|
return Err(format!("'{name}' is not defined"));
|
|
}
|
|
let node_id = *self.object_nodes.get(&name).unwrap();
|
|
self.pos += 5;
|
|
self.cast_nodes.push((name, node_id));
|
|
self.vars_since_last_cast = 0;
|
|
self.skip_separators();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if matches!(self.peek(), Some(Token::Ident(s)) if s == "let") {
|
|
self.advance();
|
|
let name = match self.advance().cloned() {
|
|
Some(Token::Ident(n)) => n,
|
|
_ => return Err(self.err_at("expected variable name after 'let'".into())),
|
|
};
|
|
let mut is_obj = false;
|
|
if matches!(self.peek(), Some(Token::Colon)) {
|
|
self.advance();
|
|
match self.advance().cloned() {
|
|
Some(Token::Ident(ty)) => {
|
|
if ty == "Obj" || ty == "obj" {
|
|
is_obj = true;
|
|
}
|
|
}
|
|
_ => return Err(self.err_at("expected type name after ':'".into())),
|
|
}
|
|
}
|
|
self.expect(&Token::Eq)?;
|
|
let val = self.parse_additive()?;
|
|
self.vars.insert(name.clone(), val);
|
|
if is_obj || self.is_obj_node(val) {
|
|
self.objects.push(name.clone());
|
|
self.object_nodes.insert(name, val);
|
|
}
|
|
self.vars_since_last_cast += 1;
|
|
self.skip_separators();
|
|
last = Some(val);
|
|
} else if self.is_reassignment() {
|
|
let name = match self.advance().cloned() {
|
|
Some(Token::Ident(n)) => n,
|
|
_ => unreachable!(),
|
|
};
|
|
self.advance();
|
|
let val = self.parse_additive()?;
|
|
if self.object_nodes.contains_key(&name) {
|
|
self.object_nodes.insert(name.clone(), val);
|
|
}
|
|
self.vars.insert(name, val);
|
|
self.vars_since_last_cast += 1;
|
|
self.skip_separators();
|
|
last = Some(val);
|
|
} else if self.pos < self.tokens.len() {
|
|
let node = self.parse_additive()?;
|
|
last = Some(node);
|
|
self.bare_exprs.push(node);
|
|
self.exprs_since_last_plot += 1;
|
|
self.skip_separators();
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if self.pos >= self.tokens.len() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let output_node = last;
|
|
let cast_var_names: HashSet<&str> = self.cast_nodes.iter().map(|(n, _)| n.as_str()).collect();
|
|
let plot_nodes_set: HashSet<NodeId> = self.plot_nodes.iter().copied().collect();
|
|
for (name, &id) in &self.vars {
|
|
if !self.used_vars.contains(name)
|
|
&& !cast_var_names.contains(name.as_str())
|
|
&& !plot_nodes_set.contains(&id)
|
|
&& output_node != Some(id)
|
|
{
|
|
self.warnings.push(format!("unused variable: {name}"));
|
|
}
|
|
}
|
|
|
|
match output_node {
|
|
Some(node) => Ok(node),
|
|
None if self.cast_all || !self.cast_nodes.is_empty()
|
|
|| self.plot_all || !self.plot_nodes.is_empty() =>
|
|
{
|
|
Ok(self.graph.push(TrigOp::Const(0.0)))
|
|
}
|
|
None => Err("empty expression".into()),
|
|
}
|
|
}
|
|
|
|
fn is_reassignment(&self) -> bool {
|
|
if let Some(Token::Ident(name)) = self.tokens.get(self.pos) {
|
|
if self.vars.contains_key(name) {
|
|
return matches!(self.tokens.get(self.pos + 1), Some(Token::Eq));
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub(crate) fn parse_additive(&mut self) -> Result<NodeId, String> {
|
|
let mut left = self.parse_multiplicative()?;
|
|
loop {
|
|
match self.peek() {
|
|
Some(Token::Plus) => {
|
|
self.advance();
|
|
let right = self.parse_multiplicative()?;
|
|
if self.is_obj_node(left) != self.is_obj_node(right) {
|
|
return Err(self.err_at("cannot add Obj and Num".into()));
|
|
}
|
|
left = self.graph.push(TrigOp::Add(left, right));
|
|
}
|
|
Some(Token::Minus) => {
|
|
self.advance();
|
|
let right = self.parse_multiplicative()?;
|
|
left = self.graph.push(TrigOp::Sub(left, right));
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_multiplicative(&mut self) -> Result<NodeId, String> {
|
|
let mut left = self.parse_power()?;
|
|
loop {
|
|
match self.peek() {
|
|
Some(Token::Star) => {
|
|
self.advance();
|
|
let right = self.parse_power()?;
|
|
left = self.graph.push(TrigOp::Mul(left, right));
|
|
}
|
|
Some(Token::Slash) => {
|
|
self.advance();
|
|
let right = self.parse_power()?;
|
|
left = self.graph.push(TrigOp::Div(left, right));
|
|
}
|
|
_ => break,
|
|
}
|
|
}
|
|
Ok(left)
|
|
}
|
|
|
|
fn parse_power(&mut self) -> Result<NodeId, String> {
|
|
let base = self.parse_unary()?;
|
|
if self.peek() == Some(&Token::Caret) {
|
|
self.advance();
|
|
let exp = self.parse_unary()?;
|
|
if let Some(TrigOp::Const(n)) = self.graph.nodes.get(exp as usize) {
|
|
let n = *n;
|
|
if n == 2.0 {
|
|
return Ok(self.graph.push(TrigOp::Mul(base, base)));
|
|
} else if n == 3.0 {
|
|
let sq = self.graph.push(TrigOp::Mul(base, base));
|
|
return Ok(self.graph.push(TrigOp::Mul(sq, base)));
|
|
}
|
|
}
|
|
Ok(self.graph.push(TrigOp::Mul(base, exp)))
|
|
} else {
|
|
Ok(base)
|
|
}
|
|
}
|
|
|
|
fn parse_unary(&mut self) -> Result<NodeId, String> {
|
|
if self.peek() == Some(&Token::Minus) {
|
|
self.advance();
|
|
let val = self.parse_unary()?;
|
|
Ok(self.graph.push(TrigOp::Neg(val)))
|
|
} else {
|
|
self.parse_atom()
|
|
}
|
|
}
|
|
|
|
fn parse_atom(&mut self) -> Result<NodeId, String> {
|
|
while matches!(self.peek(), Some(Token::Newline)) {
|
|
self.advance();
|
|
}
|
|
match self.advance().cloned() {
|
|
Some(Token::Num(n)) => Ok(self.graph.push(TrigOp::Const(n))),
|
|
Some(Token::Ident(name)) => {
|
|
match name.as_str() {
|
|
"x" => Ok(self.get_x()),
|
|
"y" => Ok(self.get_y()),
|
|
"z" => Ok(self.get_z()),
|
|
"pi" | "PI" => Ok(self.graph.push(TrigOp::Const(std::f64::consts::PI))),
|
|
"tau" | "TAU" => Ok(self.graph.push(TrigOp::Const(2.0 * std::f64::consts::PI))),
|
|
"e" | "E" => Ok(self.graph.push(TrigOp::Const(std::f64::consts::E))),
|
|
"A" => {
|
|
if let Some(g) = self.ref_a {
|
|
Ok(self.inline_graph(g))
|
|
} else {
|
|
Err("A is not defined".into())
|
|
}
|
|
}
|
|
"B" => {
|
|
if let Some(g) = self.ref_b {
|
|
Ok(self.inline_graph(g))
|
|
} else {
|
|
Err("B is not defined".into())
|
|
}
|
|
}
|
|
"reg" => Ok(self.graph.push(TrigOp::Const(f64::NAN))),
|
|
"map" => self.parse_map(),
|
|
_ => {
|
|
if let Some(&node_id) = self.vars.get(&name) {
|
|
self.used_vars.insert(name);
|
|
return Ok(node_id);
|
|
}
|
|
self.parse_function_call(&name)
|
|
}
|
|
}
|
|
}
|
|
Some(Token::LParen) => {
|
|
let inner = self.parse_additive()?;
|
|
self.expect(&Token::RParen)?;
|
|
Ok(inner)
|
|
}
|
|
Some(t) => Err(format!("unexpected token: {t:?}")),
|
|
None => Err("unexpected end of input".into()),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_arg_list(&mut self) -> Result<Vec<NodeId>, String> {
|
|
let mut args = Vec::new();
|
|
self.skip_separators();
|
|
if self.peek() == Some(&Token::RParen) {
|
|
return Ok(args);
|
|
}
|
|
args.push(self.parse_additive()?);
|
|
while { self.skip_separators(); self.peek() == Some(&Token::Comma) } {
|
|
self.advance();
|
|
self.skip_separators();
|
|
args.push(self.parse_additive()?);
|
|
}
|
|
self.skip_separators();
|
|
Ok(args)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn require_args(name: &str, args: &[NodeId], expected: usize) -> Result<(), String> {
|
|
if args.len() != expected {
|
|
Err(format!("{name}() requires {expected} argument(s), got {}", args.len()))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|