Skip to content

Commit 128b135

Browse files
committed
Got function calling working
1 parent 5fa1da8 commit 128b135

File tree

3 files changed

+133
-36
lines changed

3 files changed

+133
-36
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ edition = "2021"
55

66
[dev-dependencies]
77
rstest = "0.23.0"
8+
9+
[dependencies]
10+
log = "0.4.22"
11+
pretty_env_logger = "0.5.0"

src/lib.rs

+96-34
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// lib.rs
2-
use std::io::Write;
2+
use std::io::{self, Write};
33
use std::collections::HashMap;
44
use std::error::Error;
5+
use log::*;
56

67
#[derive(Debug, Clone)]
78
pub enum Token {
@@ -20,6 +21,7 @@ pub enum Token {
2021
Divide,
2122
LParen,
2223
RParen,
24+
Function(String), // Represents a function invocation like :add
2325
Open,
2426
ReadLines,
2527
Clear,
@@ -30,43 +32,50 @@ impl std::str::FromStr for Token {
3032
type Err = &'static str;
3133

3234
fn from_str(s: &str) -> Result<Self, Self::Err> {
33-
match s {
34-
"." => Ok(Token::Display),
35-
"+" => Ok(Token::Plus),
36-
"-" => Ok(Token::Minus),
37-
"*" => Ok(Token::Multiply),
38-
"/" => Ok(Token::Divide),
39-
"(" => Ok(Token::LParen),
40-
")" => Ok(Token::RParen),
41-
42-
"open" => Ok(Token::Open),
43-
"readlines" => Ok(Token::ReadLines),
44-
"clear" => Ok(Token::Clear),
45-
"listdir" => Ok(Token::ListDir),
46-
47-
"arrnum" => Ok(Token::MakeArrayNumberI32),
48-
"arrtxt" => Ok(Token::MakeArrayText),
49-
"join" => Ok(Token::Join),
50-
_ => {
51-
if let Ok(number) = s.parse::<i32>() {
52-
Ok(Token::NumberI32(number))
53-
} else {
54-
Ok(Token::Text(s.to_string()))
55-
}
35+
debug!("Parsing token: {}", s);
36+
if s.starts_with(':') {
37+
if s.len() == 1 {
38+
Err("Invalid function name")
39+
} else {
40+
Ok(Token::Function(s[1..].to_string()))
5641
}
42+
} else if s == "." {
43+
Ok(Token::Display)
44+
} else if s == "+" {
45+
Ok(Token::Plus)
46+
} else if s == "-" {
47+
Ok(Token::Minus)
48+
} else if s == "*" {
49+
Ok(Token::Multiply)
50+
} else if s == "/" {
51+
Ok(Token::Divide)
52+
} else if s == "(" {
53+
Ok(Token::LParen)
54+
} else if s == ")" {
55+
Ok(Token::RParen)
56+
} else if s == "clear" {
57+
Ok(Token::Clear)
58+
} else if s == "listdir" {
59+
Ok(Token::ListDir)
60+
} else if let Ok(number) = s.parse::<i32>() {
61+
Ok(Token::NumberI32(number))
62+
} else {
63+
Ok(Token::Text(s.to_string()))
5764
}
5865
}
5966
}
6067

68+
#[derive(Default)]
6169
pub struct Stack {
6270
items: Vec<Token>,
71+
pub functions: HashMap<String, Vec<Token>>, // Stores function definitions
72+
literal_mode: bool,
6373
}
6474

75+
6576
impl Stack {
6677
pub fn new() -> Stack {
67-
Stack {
68-
items: vec![],
69-
}
78+
Stack::default()
7079
}
7180

7281
pub fn clear(&mut self) {
@@ -79,15 +88,29 @@ impl Stack {
7988

8089
pub fn display(&self) {
8190
println!("{}", self.repr());
91+
println!("Functions: {:?}", self.functions);
8292
}
8393

8494
pub fn push_str(&mut self, item: &str) -> Result<(), Box<dyn Error>> {
85-
match item.parse::<Token>() {
86-
Ok(token) => {
87-
self.push(token)?;
88-
},
89-
Err(_) => {
90-
self.push(Token::Text(item.to_string()))?;
95+
if item.starts_with('`') && item.len() > 1 {
96+
// Single-token literal
97+
self.push(Token::Text(item[1..].to_string()))?;
98+
} else if item == "`" {
99+
// Enter literal mode
100+
self.literal_mode = true;
101+
} else if item == "~" && self.literal_mode {
102+
// Exit literal mode
103+
self.literal_mode = false;
104+
} else if self.literal_mode {
105+
// Add as literal in literal mode
106+
self.push(Token::Text(item.to_string()))?;
107+
} else {
108+
match item {
109+
":make-fn" => self.make_function()?,
110+
_ => match item.parse::<Token>() {
111+
Ok(token) => self.push(token)?,
112+
Err(_) => self.push(Token::Text(item.to_string()))?,
113+
},
91114
}
92115
}
93116
Ok(())
@@ -104,6 +127,7 @@ impl Stack {
104127
Token::Minus => self.subtract()?,
105128
Token::Multiply => self.multiply()?,
106129
Token::Divide => self.divide()?,
130+
Token::Function(name) => self.call_function(&name)?,
107131
Token::LParen => todo!(),
108132
Token::RParen => todo!(),
109133
Token::Open => todo!(),
@@ -118,6 +142,45 @@ impl Stack {
118142
Ok(())
119143
}
120144

145+
fn call_function(&mut self, name: &str) -> Result<(), Box<dyn Error>> {
146+
debug!("Calling function: {}", name);
147+
if let Some(definition) = self.functions.get(name) {
148+
debug!("Function definition: {:?}", definition);
149+
for token in definition.clone() {
150+
let s = match token {
151+
Token::Text(value) => value, // End marker
152+
_ => return Err("Expected a text value".into()),
153+
};
154+
let operation = s.parse::<Token>()?;
155+
self.push(operation)?;
156+
}
157+
Ok(())
158+
} else {
159+
Err(format!("Undefined function: {}", name).into())
160+
}
161+
}
162+
163+
fn make_function(&mut self) -> Result<(), Box<dyn Error>> {
164+
let mut tokens = vec![];
165+
166+
// Collect tokens until the stack contains the function name
167+
while let Some(token) = self.pop() {
168+
match token {
169+
Token::Text(value) if value == "~" => break, // End marker
170+
Token::Text(value) => tokens.push(Token::Text(value)),
171+
_ => return Err("Expected a text value".into()),
172+
}
173+
}
174+
debug!("Function tokens: {:?}", tokens);
175+
176+
if let Some(Token::Text(name)) = tokens.pop() {
177+
self.functions.insert(name, tokens);
178+
Ok(())
179+
} else {
180+
Err("Expected a function name".into())
181+
}
182+
}
183+
121184
fn join(&mut self) -> Result<(), Box<dyn Error>> {
122185
if let Some(Token::ArrayText(items)) = self.pop() {
123186
let total = items.join("");
@@ -245,5 +308,4 @@ impl Stack {
245308
output.push(self.repr());
246309
Ok(output)
247310
}
248-
249311
}

tests/test_basic.rs

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
/// Tests for the Plenty interpreter. We will load in scripts of Plenty and execute them,
22
/// checking the output against the expected output.
3+
use log::*;
34
use plenty::Stack;
4-
use rstest::rstest;
5+
use pretty_env_logger;
6+
use rstest::{rstest, fixture};
7+
8+
#[fixture]
9+
fn setup_logger() {
10+
let _ = pretty_env_logger::try_init();
11+
}
512

613
#[rstest]
714
#[case("1 2 + .", vec!["[NumberI32(3)]"])]
@@ -10,8 +17,32 @@ use rstest::rstest;
1017
#[case("10 2 - .", vec!["[NumberI32(8)]"])]
1118
#[case("1 2 3 4 5 6 +", vec!["[NumberI32(1), NumberI32(2), NumberI32(3), NumberI32(4), NumberI32(11)]"])]
1219
#[case("1 2 +\n3 +", vec!["[NumberI32(6)]"])]
13-
fn test_programs(#[case] program: &str, #[case] expected_output: Vec<&str>) {
20+
fn test_programs(setup_logger: (), #[case] program: &str, #[case] expected_output: Vec<&str>) {
21+
let mut stack = Stack::new();
22+
let actual_output = stack.run_program(program).unwrap();
23+
assert_eq!(actual_output, expected_output);
24+
}
25+
26+
// Let's test function creation
27+
#[rstest]
28+
#[case("` add + ~ :make-fn", "{\"add\": [Text(\"+\")]}")]
29+
fn test_function_creation(setup_logger: (), #[case] program: &str, #[case] expected_output: &str) {
30+
let mut stack = Stack::new();
31+
let _actual_output = stack.run_program(program).unwrap();
32+
debug!("{:?}", stack.functions);
33+
let out = format!("{:?}", stack.functions);
34+
assert_eq!(out, expected_output);
35+
}
36+
37+
// Let's test function calling
38+
#[rstest]
39+
// Define a new function, and then call it
40+
#[case("` add + ~ :make-fn\n1 2 :add", vec!["[NumberI32(3)]"])]
41+
// Important: no difference between newline or space
42+
#[case("` add + ~ :make-fn 1 2 :add", vec!["[NumberI32(3)]"])]
43+
fn test_function_calling(setup_logger: (), #[case] program: &str, #[case] expected_output: Vec<&str>) {
1444
let mut stack = Stack::new();
1545
let actual_output = stack.run_program(program).unwrap();
46+
debug!("after run program: {:?}", stack.repr());
1647
assert_eq!(actual_output, expected_output);
1748
}

0 commit comments

Comments
 (0)