parsing
This commit is contained in:
parent
9d0a24d4cd
commit
c5a0180f04
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -433,6 +433,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
|||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -491,6 +492,12 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.3"
|
||||
|
@ -505,6 +512,7 @@ dependencies = [
|
|||
"enum_dispatch",
|
||||
"font-kit",
|
||||
"raylib",
|
||||
"termion",
|
||||
"test-case",
|
||||
]
|
||||
|
||||
|
@ -626,6 +634,12 @@ dependencies = [
|
|||
"bitflags 2.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
|
@ -794,6 +808,18 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "4.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eaa98560e51a2cf4f0bb884d8b2098a9ea11ecf3b7078e9c68242c74cc923a7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"numtoa",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "3.3.1"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -9,12 +9,20 @@ clay-layout = { path = "../clay-ui-rust" }
|
|||
enum_dispatch = "0.3.13"
|
||||
font-kit = "0.14.2"
|
||||
raylib = { version = "5.0.2", features = ["wayland"] }
|
||||
termion = "4.0.3"
|
||||
test-case = "3.3.1"
|
||||
|
||||
[features]
|
||||
async = []
|
||||
|
||||
[lib]
|
||||
name = "libopenbirch"
|
||||
path = "src/lib/lib.rs"
|
||||
|
||||
# [[bin]]
|
||||
# name = "openbirch-gui"
|
||||
# path = "src/app/bin.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "openbirch_iced"
|
||||
path = "src/app/bin.rs"
|
||||
name = "openbirch-repl"
|
||||
path = "src/cli-repl.rs"
|
||||
|
|
12
flake.lock
12
flake.lock
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739020877,
|
||||
"narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=",
|
||||
"lastModified": 1739866667,
|
||||
"narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a79cfe0ebd24952b580b1cf08cd906354996d547",
|
||||
"rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -29,11 +29,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1736216977,
|
||||
"narHash": "sha256-EMueGrzBpryM8mgOyoyJ7DdNRRk09ug1ggcLLp0WrCQ=",
|
||||
"lastModified": 1739932111,
|
||||
"narHash": "sha256-WkayjH0vuGw0hx2gmjTUGFRvMKpM17gKcpL/U8EUUw0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "bbe7e4e7a70d235db4bbdcabbf8a2f6671881dd7",
|
||||
"rev": "75b2271c5c087d830684cd5462d4410219acc367",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
200
src/cli-repl.rs
Normal file
200
src/cli-repl.rs
Normal file
|
@ -0,0 +1,200 @@
|
|||
use std::io::{self, StdoutLock, Write, stdout};
|
||||
|
||||
use libopenbirch::environment::Environment;
|
||||
use libopenbirch::node::Node;
|
||||
use libopenbirch::parser::{Lexer, Parser};
|
||||
#[cfg(feature = "async")]
|
||||
use termion::AsyncReader;
|
||||
use termion::color;
|
||||
use termion::event::Key;
|
||||
use termion::input::TermReadEventsAndRaw;
|
||||
use termion::raw::{IntoRawMode, RawTerminal};
|
||||
|
||||
pub struct Input {
|
||||
#[cfg(not(feature = "async"))]
|
||||
stdin: std::io::Stdin,
|
||||
|
||||
stdout: RawTerminal<StdoutLock<'static>>,
|
||||
buffer: String,
|
||||
current_char: usize,
|
||||
}
|
||||
impl Input {
|
||||
pub fn new() -> Self {
|
||||
#[cfg(not(feature = "async"))]
|
||||
let stdin = std::io::stdin();
|
||||
let stdout = stdout().lock().into_raw_mode().unwrap();
|
||||
|
||||
Self {
|
||||
stdin,
|
||||
stdout,
|
||||
buffer: "".into(),
|
||||
current_char: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_line(
|
||||
buffer: &String,
|
||||
stdout: &mut RawTerminal<StdoutLock<'static>>,
|
||||
current_char: usize,
|
||||
) {
|
||||
print!("\r{}> {}", termion::clear::CurrentLine, buffer,);
|
||||
let left_diff = buffer.len() - current_char;
|
||||
if left_diff > 0 {
|
||||
print!("{}", termion::cursor::Left(left_diff.try_into().unwrap()));
|
||||
}
|
||||
let _ = stdout.flush();
|
||||
}
|
||||
|
||||
/// Gets input from `io::stdin`.
|
||||
/// Returns `None` when the user presses <C-c> or types `:quit`
|
||||
pub fn get(&mut self) -> Result<Option<String>, io::Error> {
|
||||
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
|
||||
loop {
|
||||
for b in (&mut self.stdin).events_and_raw() {
|
||||
let (event, _slice) = b?;
|
||||
|
||||
match event {
|
||||
termion::event::Event::Key(key) => match key {
|
||||
Key::Char(char) if char == '\n' => {
|
||||
if &self.buffer == ":quit" {
|
||||
println!("\r");
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted));
|
||||
}
|
||||
|
||||
let r = self.buffer.clone();
|
||||
self.buffer.clear();
|
||||
self.current_char = 0;
|
||||
print!("\n\r");
|
||||
return Ok(Some(r));
|
||||
}
|
||||
Key::Backspace => {
|
||||
if self.current_char == 0 {
|
||||
continue;
|
||||
}
|
||||
self.current_char -= 1;
|
||||
self.buffer.remove(self.current_char);
|
||||
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
|
||||
let _ = self.stdout.flush();
|
||||
}
|
||||
Key::Delete => {
|
||||
if self.current_char == self.buffer.len() {
|
||||
continue;
|
||||
}
|
||||
self.buffer.remove(self.current_char);
|
||||
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
|
||||
let _ = self.stdout.flush();
|
||||
}
|
||||
Key::Left => {
|
||||
if self.current_char == 0 {
|
||||
continue;
|
||||
}
|
||||
self.current_char -= 1;
|
||||
print!("{}", termion::cursor::Left(1));
|
||||
let _ = self.stdout.flush();
|
||||
}
|
||||
Key::Right => {
|
||||
if self.current_char == self.buffer.len() {
|
||||
continue;
|
||||
}
|
||||
self.current_char += 1;
|
||||
print!("{}", termion::cursor::Right(1));
|
||||
let _ = self.stdout.flush();
|
||||
}
|
||||
Key::Char(char) => {
|
||||
self.buffer.insert(self.current_char, char);
|
||||
self.current_char += 1;
|
||||
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
|
||||
}
|
||||
Key::Ctrl(c) if c == 'c' => {
|
||||
println!("\r");
|
||||
return Err(io::Error::from(io::ErrorKind::Interrupted));
|
||||
}
|
||||
_ => {
|
||||
#[cfg(debug_assertions)]
|
||||
return Err(io::Error::other(format!(
|
||||
"Key {key:?} is not implemented"
|
||||
)));
|
||||
}
|
||||
},
|
||||
termion::event::Event::Mouse(mouse_event) => {}
|
||||
termion::event::Event::Unsupported(items) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_raw(&mut self) {
|
||||
self.stdout.suspend_raw_mode();
|
||||
}
|
||||
|
||||
pub fn enable_raw(&mut self) {
|
||||
self.stdout.activate_raw_mode();
|
||||
}
|
||||
}
|
||||
|
||||
fn print_err(i: usize, exp: String) {
|
||||
println!("\r{}{}^", " ".repeat(i + 2), color::Fg(color::Yellow));
|
||||
println!(
|
||||
"\r{}{}{}",
|
||||
color::Fg(color::Red),
|
||||
exp,
|
||||
color::Fg(color::Reset)
|
||||
);
|
||||
}
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
let mut input = Input::new();
|
||||
|
||||
let mut env = Environment::new();
|
||||
|
||||
while let Some(source) = input.get()? {
|
||||
let mut lexer = Lexer::new(&source);
|
||||
let tokens_result = lexer.lex();
|
||||
|
||||
if tokens_result.is_err() {
|
||||
match tokens_result.err().unwrap() {
|
||||
libopenbirch::parser::LexerError::UnexpectedChar(i, exp) => print_err(i, exp),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
input.disable_raw();
|
||||
|
||||
let tokens = tokens_result.unwrap();
|
||||
let mut parser = Parser::new(tokens);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
input.enable_raw();
|
||||
|
||||
let nodes = match parser.parse() {
|
||||
Ok(nodes) => nodes,
|
||||
Err(err) => {
|
||||
match err {
|
||||
libopenbirch::parser::ParserError::UnexpectedEndOfTokens(exp) => {
|
||||
print_err(source.len(), exp)
|
||||
}
|
||||
libopenbirch::parser::ParserError::UnexpectedToken(i, exp) => print_err(i, exp),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
print!("{}", color::Fg(color::Blue));
|
||||
|
||||
for node in nodes {
|
||||
let evaluated = node.evaluate(&mut env);
|
||||
match evaluated {
|
||||
Ok(result) => println!("\r\t{}", result.as_string(None)),
|
||||
Err(exp) => print_err(0, exp),
|
||||
}
|
||||
}
|
||||
|
||||
print!("{}", color::Fg(color::Reset));
|
||||
|
||||
// println!("\t{}{source}{}", termion::color::Fg(termion::color::Blue), termion::color::Fg(termion::color::Reset));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -2,6 +2,7 @@ use std::io::{self, stdout, BufRead, Write};
|
|||
|
||||
pub mod node;
|
||||
pub mod environment;
|
||||
pub mod parser;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -41,10 +41,10 @@ impl Node for Add {
|
|||
}
|
||||
|
||||
impl Add {
|
||||
pub fn new(left: NodeEnum, right: NodeEnum) -> Self {
|
||||
pub fn new(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Self {
|
||||
Self {
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
left,
|
||||
right,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +1,67 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::environment::Environment;
|
||||
use crate::{environment::Environment, node::function::FunctionType};
|
||||
|
||||
use super::{Node, NodeEnum, Precedence, symbol::Symbol};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Call {
|
||||
left: Rc<NodeEnum>,
|
||||
right: Vec<Rc<NodeEnum>>,
|
||||
function: Rc<NodeEnum>,
|
||||
arguments: Vec<Rc<NodeEnum>>,
|
||||
}
|
||||
|
||||
impl Node for Call {
|
||||
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, String> {
|
||||
todo!();
|
||||
let function = match self.left.as_ref() {
|
||||
NodeEnum::Symbol(symbol) => match symbol.evaluate(env)?.as_ref() {
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot call {} as a function",
|
||||
symbol.as_string(Some(env))
|
||||
));
|
||||
}
|
||||
|
||||
NodeEnum::Function(function) => function,
|
||||
},
|
||||
NodeEnum::Function(function) => function,
|
||||
NodeEnum::Call(call) => match call.evaluate(env)?.as_ref() {
|
||||
NodeEnum::Function(function) => function,
|
||||
|
||||
// FIXME: This might fail for long chains of calls
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot call {} as a function",
|
||||
self.left.as_string(None)
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot call {} as a function",
|
||||
self.left.as_string(None)
|
||||
));
|
||||
}
|
||||
// Evaluate callee and error if its not a function
|
||||
let evaluated = self.function.evaluate(env)?;
|
||||
let func = if let NodeEnum::Function(func) = evaluated.as_ref() {
|
||||
func
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Cannot call {} as a function",
|
||||
evaluated.as_string(Some(env))
|
||||
));
|
||||
};
|
||||
|
||||
// match function {
|
||||
//
|
||||
// }
|
||||
todo!()
|
||||
// Check if argument counts match
|
||||
let fargs = func.get_arguments();
|
||||
if fargs.len() != self.arguments.len() {
|
||||
return Err(format!(
|
||||
"Error calling function. Expected {} arguments, but got {}",
|
||||
func.get_arguments().len(),
|
||||
self.arguments.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Call function body with arguments
|
||||
match func.get_body() {
|
||||
// Pass arguments to native function
|
||||
FunctionType::Native(_name, native_function) => native_function(&self.arguments),
|
||||
FunctionType::UserFunction(node_enum) => {
|
||||
// TODO: Push scope
|
||||
// Define variables
|
||||
fargs
|
||||
.iter()
|
||||
.zip(&self.arguments)
|
||||
.for_each(|(symbol, value)| {
|
||||
env.insert(symbol.get_value(), value.clone());
|
||||
});
|
||||
let ev = node_enum.evaluate(env);
|
||||
// TODO: Pop scope
|
||||
// Return evaluated return value for function
|
||||
ev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_string(&self, _: Option<&Environment>) -> String {
|
||||
todo!()
|
||||
fn as_string(&self, env: Option<&Environment>) -> String {
|
||||
let arguments = self
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|x| x.as_string(env))
|
||||
.reduce(|a, b| a + ", " + &b)
|
||||
.unwrap();
|
||||
format!("{}({})", self.function.as_string(env), arguments)
|
||||
}
|
||||
|
||||
fn precedence(&self) -> Precedence {
|
||||
|
|
|
@ -2,9 +2,11 @@ use std::rc::Rc;
|
|||
|
||||
use super::{Environment, Node, Precedence};
|
||||
|
||||
pub type ConstantValue = f64;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Constant {
|
||||
value: f64,
|
||||
value: ConstantValue,
|
||||
}
|
||||
|
||||
impl Node for Constant {
|
||||
|
@ -22,15 +24,15 @@ impl Node for Constant {
|
|||
}
|
||||
|
||||
impl Constant {
|
||||
pub fn new(value: f64) -> Self {
|
||||
pub fn new(value: ConstantValue) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> f64 {
|
||||
pub fn get_value(&self) -> ConstantValue {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, value: f64) {
|
||||
pub fn set_value(&mut self, value: ConstantValue) {
|
||||
self.value = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ impl Node for Divide {
|
|||
}
|
||||
|
||||
impl Divide {
|
||||
pub fn new(left: NodeEnum, right: NodeEnum) -> Self {
|
||||
pub fn new(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Self {
|
||||
Self {
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
left,
|
||||
right
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use super::{Node, NodeEnum, Precedence, symbol::Symbol};
|
|||
pub enum FunctionType {
|
||||
Native(
|
||||
&'static str,
|
||||
fn(Vec<Rc<NodeEnum>>) -> Result<Rc<NodeEnum>, String>,
|
||||
fn(&Vec<Rc<NodeEnum>>) -> Result<Rc<NodeEnum>, String>,
|
||||
),
|
||||
UserFunction(Rc<NodeEnum>),
|
||||
}
|
||||
|
@ -50,4 +50,12 @@ impl Function {
|
|||
// pub fn new(t: FunctionType, ) -> Self {
|
||||
//
|
||||
// }
|
||||
|
||||
pub fn get_body(&self) -> &FunctionType {
|
||||
&self.function
|
||||
}
|
||||
|
||||
pub fn get_arguments(&self) -> &Vec<Symbol> {
|
||||
&self.arguments
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,24 @@ pub enum Bool {
|
|||
False,
|
||||
}
|
||||
|
||||
impl Node for Bool {
|
||||
fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, String> {
|
||||
Ok(Rc::new(self.clone().into()))
|
||||
}
|
||||
|
||||
fn as_string(&self, _: Option<&Environment>) -> String {
|
||||
match self {
|
||||
Bool::True => "true",
|
||||
Bool::False => "false",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn precedence(&self) -> Precedence {
|
||||
Precedence::Primary
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub enum ElseBranchEnum {
|
||||
ElseIf(Rc<IfElse>),
|
||||
|
@ -25,12 +43,37 @@ impl Node for IfElse {
|
|||
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, String> {
|
||||
let condition_evaluated = self.condition.evaluate(env)?;
|
||||
|
||||
if let super::NodeEnum::Bool(bool) = condition_evaluated {
|
||||
let condition = if let NodeEnum::Bool(bool) = condition_evaluated.as_ref() {
|
||||
bool
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Cannot evaluate {} to a bool",
|
||||
condition_evaluated.as_string(Some(env))
|
||||
));
|
||||
};
|
||||
|
||||
fn evaluate_block(
|
||||
block: &Vec<Rc<NodeEnum>>,
|
||||
env: &mut Environment,
|
||||
) -> Result<Rc<NodeEnum>, String> {
|
||||
// TODO: Push new scope
|
||||
if let Some((last, to_evaluate)) = block.split_last() {
|
||||
for expr in to_evaluate {
|
||||
expr.evaluate(env)?;
|
||||
}
|
||||
last.evaluate(env)
|
||||
} else {
|
||||
Err("Empty if statemenent true branch")?
|
||||
}
|
||||
// TODO: Pop scope
|
||||
}
|
||||
|
||||
match condition {
|
||||
Bool::True => evaluate_block(&self.true_branch, env),
|
||||
Bool::False => match &self.else_branch {
|
||||
ElseBranchEnum::ElseIf(if_else) => if_else.evaluate(env),
|
||||
ElseBranchEnum::Block(node_enums) => evaluate_block(node_enums, env),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, fmt::Display, rc::Rc};
|
||||
use std::{fmt::Display, rc::Rc};
|
||||
|
||||
use add::Add;
|
||||
use assign::Assign;
|
||||
|
@ -10,7 +10,6 @@ use enum_dispatch::enum_dispatch;
|
|||
use function::Function;
|
||||
use if_else::{Bool, IfElse};
|
||||
use multiply::Multiply;
|
||||
use node_ref::NodeRef;
|
||||
use subtract::Subtract;
|
||||
use symbol::Symbol;
|
||||
|
||||
|
@ -51,10 +50,12 @@ pub enum NodeEnum {
|
|||
Bool,
|
||||
IfElse,
|
||||
|
||||
// Logical
|
||||
// Logical operators
|
||||
// In,
|
||||
// Or,
|
||||
// Where,
|
||||
// Not,
|
||||
// Or,
|
||||
// And
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
|
|
@ -41,10 +41,10 @@ impl Node for Multiply {
|
|||
}
|
||||
|
||||
impl Multiply {
|
||||
pub fn new(left: NodeEnum, right: NodeEnum) -> Self {
|
||||
pub fn new(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Self {
|
||||
Self {
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
left,
|
||||
right
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,10 +41,10 @@ impl Node for Subtract {
|
|||
}
|
||||
|
||||
impl Subtract {
|
||||
pub fn new(left: NodeEnum, right: NodeEnum) -> Self {
|
||||
pub fn new(left: Rc<NodeEnum>, right: Rc<NodeEnum>) -> Self {
|
||||
Self {
|
||||
left: Rc::new(left),
|
||||
right: Rc::new(right),
|
||||
left,
|
||||
right,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
231
src/lib/parser/mod.rs
Normal file
231
src/lib/parser/mod.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use std::{collections::HashMap, iter::Peekable, rc::Rc, slice::Iter, vec::IntoIter};
|
||||
|
||||
use crate::node::{
|
||||
NodeEnum,
|
||||
add::Add,
|
||||
constant::{Constant, ConstantValue},
|
||||
divide::Divide,
|
||||
multiply::Multiply,
|
||||
subtract::Subtract,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Token(usize, TokenType);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TokenType {
|
||||
// Space,
|
||||
Number(ConstantValue),
|
||||
|
||||
Plus,
|
||||
Minus,
|
||||
Star,
|
||||
Slash,
|
||||
|
||||
RParen,
|
||||
LParen,
|
||||
|
||||
If,
|
||||
Then,
|
||||
Else,
|
||||
End,
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
source: &'a String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LexerError {
|
||||
UnexpectedChar(usize, String),
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(source: &'a String) -> Self {
|
||||
Self { source }
|
||||
}
|
||||
|
||||
pub fn lex(&'a mut self) -> Result<Vec<Token>, LexerError> {
|
||||
let mut src = self.source.chars().peekable();
|
||||
let mut i = 0;
|
||||
let mut tokens = vec![];
|
||||
|
||||
while let Some(c) = src.next() {
|
||||
match c {
|
||||
// Collapse spaces into a single Space token
|
||||
' ' => {
|
||||
while src.peek() == Some(&' ') {
|
||||
src.next();
|
||||
i += 1;
|
||||
}
|
||||
// tokens.push(Token(i, TokenType::Space));
|
||||
}
|
||||
// Comments with //
|
||||
'/' if src.peek() == Some(&'/') => {
|
||||
while src.next() != Some('\n') {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
// Numbers with decimal points
|
||||
'0'..'9' | '.' => {
|
||||
let mut digit = String::from(c);
|
||||
loop {
|
||||
let d = src.peek();
|
||||
let mut has_decimal = c == '.';
|
||||
match d {
|
||||
Some('0'..'9') => {
|
||||
digit.push(*d.unwrap());
|
||||
src.next();
|
||||
i += 1;
|
||||
}
|
||||
#[allow(unused_assignments)] // For some reason it thinks has_decimal
|
||||
// is never read
|
||||
Some('.') => {
|
||||
if has_decimal {
|
||||
return Err(LexerError::UnexpectedChar(
|
||||
i,
|
||||
"Invalid digit with multiple decimal points".into(),
|
||||
));
|
||||
}
|
||||
|
||||
digit.push(*d.unwrap());
|
||||
has_decimal = true;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let number = digit.parse::<ConstantValue>().unwrap();
|
||||
|
||||
tokens.push(Token(i, TokenType::Number(number)));
|
||||
}
|
||||
|
||||
'+' => tokens.push(Token(i, TokenType::Plus)),
|
||||
'-' => tokens.push(Token(i, TokenType::Minus)),
|
||||
'*' => tokens.push(Token(i, TokenType::Star)),
|
||||
'/' => tokens.push(Token(i, TokenType::Slash)),
|
||||
|
||||
'(' => tokens.push(Token(i, TokenType::LParen)),
|
||||
')' => tokens.push(Token(i, TokenType::RParen)),
|
||||
|
||||
_ => {
|
||||
return Err(LexerError::UnexpectedChar(
|
||||
i,
|
||||
format!("Unexpected char {}", c),
|
||||
));
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ParserError {
|
||||
UnexpectedEndOfTokens(String),
|
||||
UnexpectedToken(usize, String),
|
||||
}
|
||||
|
||||
/// Recursive descent parser
|
||||
pub struct Parser {
|
||||
tokens: Peekable<IntoIter<Token>>,
|
||||
}
|
||||
|
||||
type Tokens<'a> = Peekable<Iter<'a, Token>>;
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
// #[cfg(debug_assertions)]
|
||||
// println!("\r{tokens:#?}");
|
||||
Self {
|
||||
tokens: tokens.into_iter().peekable(),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse tokens recursively and descendentantly
|
||||
pub fn parse(&mut self) -> Result<Vec<Rc<NodeEnum>>, ParserError> {
|
||||
let mut expressions = vec![];
|
||||
|
||||
while self.tokens.peek().is_some() {
|
||||
expressions.push(self.expression()?);
|
||||
}
|
||||
|
||||
Ok(expressions)
|
||||
}
|
||||
|
||||
fn expression(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
self.equality()
|
||||
}
|
||||
|
||||
fn equality(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
// TODO: Implement equality
|
||||
self.comparison()
|
||||
}
|
||||
|
||||
fn comparison(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
// TODO: Implement comparison
|
||||
self.term()
|
||||
}
|
||||
|
||||
fn term(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
let expr = self.factor()?;
|
||||
if let Some(Token(_, TokenType::Plus)) = self.tokens.peek() {
|
||||
self.tokens.next();
|
||||
Ok(Rc::new(Add::new(expr, self.comparison()?).into()))
|
||||
} else if let Some(Token(_, TokenType::Minus)) = self.tokens.peek() {
|
||||
self.tokens.next();
|
||||
Ok(Rc::new(Subtract::new(expr, self.comparison()?).into()))
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
fn factor(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
let expr = self.unary()?;
|
||||
if let Some(Token(_, TokenType::Star)) = self.tokens.peek() {
|
||||
self.tokens.next();
|
||||
Ok(Rc::new(Multiply::new(expr, self.comparison()?).into()))
|
||||
} else if let Some(Token(_, TokenType::Slash)) = self.tokens.peek() {
|
||||
self.tokens.next();
|
||||
Ok(Rc::new(Divide::new(expr, self.comparison()?).into()))
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
fn unary(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
self.exponent()
|
||||
}
|
||||
|
||||
fn exponent(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
self.call()
|
||||
}
|
||||
|
||||
fn call(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
self.function()
|
||||
}
|
||||
|
||||
fn function(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
self.primary()
|
||||
}
|
||||
|
||||
fn primary(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
|
||||
let (i, token) = if let Some(Token(i, token)) = self.tokens.next() {
|
||||
(i, token)
|
||||
} else {
|
||||
return Err(ParserError::UnexpectedEndOfTokens(
|
||||
"Expected a Primary here".into(),
|
||||
));
|
||||
};
|
||||
|
||||
match token {
|
||||
TokenType::Number(value) => Ok(Rc::new(Constant::new(value).into())),
|
||||
_ => Err(ParserError::UnexpectedToken(
|
||||
i,
|
||||
format!("Unexpected token {token:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ mod arithmetic {
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{environment, node::{
|
||||
add::Add, constant::Constant, divide::Divide, multiply::Multiply, subtract::Subtract, Node, NodeEnum
|
||||
add::Add, constant::{Constant, ConstantValue}, divide::Divide, multiply::Multiply, subtract::Subtract, Node, NodeEnum
|
||||
}};
|
||||
use environment::Environment;
|
||||
|
||||
|
@ -11,13 +11,13 @@ mod arithmetic {
|
|||
#[test_case(69.0, 420.0, 489.0 ; "when both are positive")]
|
||||
#[test_case(-2.0, -4.0, -6.0 ; "when both are negative")]
|
||||
#[test_case(0.0, 0.0, 0.0 ; "when both are zero")]
|
||||
#[test_case(f64::INFINITY, 0.0, f64::INFINITY ; "infinity")]
|
||||
// #[test_case(f64::NAN, 0.0, f64::NAN ; "NaN")] // cant test NaN because NaN != NaN
|
||||
fn addition(a: f64, b: f64, e: f64) {
|
||||
#[test_case(ConstantValue::INFINITY, 0.0, ConstantValue::INFINITY ; "infinity")]
|
||||
// #[test_case(ConstantValue::NAN, 0.0, ConstantValue::NAN ; "NaN")] // cant test NaN because NaN != NaN
|
||||
fn addition(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
|
||||
let mut env = Environment::new();
|
||||
|
||||
let a = Constant::new(a).into();
|
||||
let b = Constant::new(b).into();
|
||||
let a = Rc::new(Constant::new(a).into());
|
||||
let b = Rc::new(Constant::new(b).into());
|
||||
let d: NodeEnum = Add::new(a, b).into();
|
||||
|
||||
let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();
|
||||
|
@ -33,15 +33,15 @@ mod arithmetic {
|
|||
#[test_case(69.0, 420.0, -351.0 ; "when both are positive")]
|
||||
#[test_case(-2.0, -4.0, 2.0 ; "when both are negative")]
|
||||
#[test_case(0.0, 0.0, 0.0 ; "when both are zero")]
|
||||
#[test_case(f64::INFINITY, 0.0, f64::INFINITY ; "infinity")]
|
||||
// #[test_case(f64::NAN, 0.0, f64::NAN ; "NaN")] // cant test NaN because NaN != NaN
|
||||
fn subtraction(aa: f64, bb: f64, e: f64) {
|
||||
#[test_case(ConstantValue::INFINITY, 0.0, ConstantValue::INFINITY ; "infinity")]
|
||||
// #[test_case(ConstantValue::NAN, 0.0, ConstantValue::NAN ; "NaN")] // cant test NaN because NaN != NaN
|
||||
fn subtraction(aa: ConstantValue, bb: ConstantValue, e: ConstantValue) {
|
||||
let mut env = Environment::new();
|
||||
|
||||
let a = Constant::new(0.0).into();
|
||||
let b = Constant::new(aa).into();
|
||||
let c = Constant::new(bb).into();
|
||||
let d: NodeEnum = Subtract::new(Add::new(a, b).into(), c).into();
|
||||
let a = Rc::new(Constant::new(0.0).into());
|
||||
let b = Rc::new(Constant::new(aa).into());
|
||||
let c = Rc::new(Constant::new(bb).into());
|
||||
let d: NodeEnum = Subtract::new(Rc::new(Add::new(a, b).into()), c).into();
|
||||
|
||||
let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();
|
||||
|
||||
|
@ -58,11 +58,11 @@ mod arithmetic {
|
|||
#[test_case(5.0, -10.0, -50.0 ; "when right is negative")]
|
||||
#[test_case(-5.0, -10.0, 50.0 ; "when both are negative")]
|
||||
#[test_case(2734589235234.23, 0.0, 0.0 ; "when 0 is involved")]
|
||||
fn multiplication(a: f64, b: f64, e: f64) {
|
||||
fn multiplication(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
|
||||
let mut env = Environment::new();
|
||||
|
||||
let a = Constant::new(a).into();
|
||||
let b = Constant::new(b).into();
|
||||
let a = Rc::new(Constant::new(a).into());
|
||||
let b = Rc::new(Constant::new(b).into());
|
||||
let d: NodeEnum = Multiply::new(a, b).into();
|
||||
|
||||
let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();
|
||||
|
@ -79,11 +79,11 @@ mod arithmetic {
|
|||
#[test_case(-5.0, 10.0, -0.5 ; "when left is negative")]
|
||||
#[test_case(5.0, -10.0, -0.5 ; "when right is negative")]
|
||||
#[test_case(-5.0, -10.0, 0.5 ; "when both are negative")]
|
||||
fn division(a: f64, b: f64, e: f64) {
|
||||
fn division(a: ConstantValue, b: ConstantValue, e: ConstantValue) {
|
||||
let mut env = Environment::new();
|
||||
|
||||
let a = Constant::new(a).into();
|
||||
let b = Constant::new(b).into();
|
||||
let a = Rc::new(Constant::new(a).into());
|
||||
let b = Rc::new(Constant::new(b).into());
|
||||
let d: NodeEnum = Divide::new(a, b).into();
|
||||
|
||||
let value = Rc::<NodeEnum>::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap();
|
||||
|
@ -107,8 +107,10 @@ mod functions {
|
|||
mod expected_errors {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::node::constant::ConstantValue;
|
||||
|
||||
#[test_case(5.0, 0.0 ; "divide by zero")]
|
||||
fn division(a: f64, b: f64) {
|
||||
fn division(a: ConstantValue, b: ConstantValue) {
|
||||
let _ = a+b;
|
||||
}
|
||||
}
|
||||
|
@ -116,13 +118,15 @@ mod expected_errors {
|
|||
mod misc {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::node::constant::ConstantValue;
|
||||
|
||||
#[test_case(30, '+', 60, '-', 20, "(30+60)-20" ; "add and subtract")]
|
||||
fn convert_to_string(
|
||||
a: impl Into<f64>,
|
||||
a: impl Into<ConstantValue>,
|
||||
op1: char,
|
||||
b: impl Into<f64>,
|
||||
b: impl Into<ConstantValue>,
|
||||
op2: char,
|
||||
c: impl Into<f64>,
|
||||
c: impl Into<ConstantValue>,
|
||||
e: &'static str,
|
||||
) {
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue