Skip to content

Commit ba54825

Browse files
committed
Updating the documentation revealed a number of improvements that could be made, so I made them.
1 parent a9efebb commit ba54825

File tree

9 files changed

+215
-184
lines changed

9 files changed

+215
-184
lines changed

README.md

Lines changed: 12 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,31 @@
11
# expressions
22

3-
Parser for the expression language used in the spreadsheet plugin
3+
Parser for the expression language used in the spreadsheet plugin. This document outlines the Rust portion of the
4+
library. For JavaScript see [the JS bindings](expression-js/README.md).
45

56
## Quickstart
67

78
In order to evaluate expressions, three things must happen;
89

910
1. A context object containing a [Data Provider](#Data Provider) must be registered
1011
2. The list of all functions, variables and operators needs to be defined
11-
3. The expression needs to be passed in to the parser.
12+
3. The expression needs to be passed into the parser.
1213

1314
## Data Provider
1415

15-
A data provider is an object which converts addresses into values.
16-
It is used as a translation layer between the tabular data and the expression.
17-
18-
To achieve this, you need to define
19-
1. A provider structure
20-
2. The row type the provider serves.
21-
3. Implement the `DataSource` and `Row` traits respectively.
22-
4. Parse the actual data source to gain knowledge of the available data.
23-
24-
## Example Provider
25-
26-
We will be constructing a provider which sits atop a CSV parser which we will not be implementing here.
27-
28-
### 1. CSV Parser
29-
30-
For demonstration purposes, we will assume that a CSV parser yields a structure which implements the following trait:
16+
A data provider is an object which converts addresses into values. Addresses are arbitrary text tokens wrapped in
17+
braces. The provider determines their meaning.
3118

3219
```rust
33-
pub trait CSVDocument {
34-
fn get_cell(&self, row: usize, col: usize) -> &str;
35-
fn get_cell_mut(&mut self, row: usize, col: usize) -> &mut str;
36-
37-
/// Iterates over all cells of a column
38-
fn iter_col(&self, col: usize) -> impl Iterator<Item=impl AsRef<str>>;
39-
/// Iterates over all cells of a row
40-
fn iter_row(&self, row: usize) -> impl Iterator<Item=impl AsRef<str>>;
41-
42-
/// Iterates over all cells of a column mutably
43-
fn iter_col_mut(&mut self, row: usize) -> impl Iterator<Item=impl AsMut<str>>;
44-
/// Iterates over all cells of a row mutably
45-
fn iter_row_mut(&mut self, row: usize) -> impl Iterator<Item=impl AsMut<str>>;
46-
47-
/// Iterates over all rows
48-
fn iter_rows(&self) -> impl Iterator<Item=Vec<impl AsRef<str>>>;
49-
/// Iterates over all rows mutably
50-
fn iter_rows_mut(&mut self) -> impl Iterator<Item=Vec<impl AsMut<str>>>;
51-
}
52-
```
53-
54-
We will assume the structure which implements this is called `CSVFile`.
55-
56-
```rust
57-
#[derive(CSVDocument)]
58-
pub struct CSVFile {
59-
// --- snip ---
60-
}
61-
```
62-
63-
### 2. Data Source
20+
struct Provider;
6421

65-
The provider will assume the first row contains the headers. Thus, the referenced cell will be shifted up by 1 row.
66-
67-
```rust
68-
use std::collections::HashMap;
69-
use expressions::{
70-
DataSource,
71-
Row
72-
};
73-
74-
pub struct CSVRow {
75-
column_name_to_column_index: HashMap<String, usize>,
76-
values: Vec<String>
77-
}
78-
79-
impl DataSource for CSVFile {
80-
type Rows = CSVRow;
81-
82-
fn list_columns(&self) -> impl Iterator<Item=impl AsRef<str>> {
83-
self.iter_col(0)
84-
}
85-
86-
fn tuples(&self) -> impl Iterator<Item=impl AsRef<CSVRow>> {
87-
let columns = self.iter_row(0)
88-
.enumerate()
89-
.map(|(a, i)| (i.to_owned(), a))
90-
.collect::<HashMap<_, _>>();
22+
impl expression::DataSource for Provider {
23+
fn query(&self, query: impl AsRef<str>) -> Option<expression::Object> {
24+
// Parse the query however you need to.
25+
// For example, the format `column:row`
26+
27+
let (column, row) = query.as_ref().split_at(query.as_ref().rfind(':'));
9128

92-
self.iter_rows()
93-
.skip(1)
94-
.enumerate()
95-
.map(|(a, values)| CSVRow {
96-
column_name_to_column_index: columns.clone(),
97-
values
98-
})
99-
}
100-
101-
fn tuples_mut(&mut self) -> impl Iterator<Item=impl AsMut<CSVRow>> {
102-
let columns = self.iter_row(0)
103-
.enumerate()
104-
.map(|(a, i)| (i.to_owned(), a))
105-
.collect::<HashMap<_, _>>();
106-
107-
self.iter_rows_mut()
108-
.skip(1)
109-
.enumerate()
110-
.map(|(a, values)| CSVRow {
111-
column_name_to_column_index: columns.clone(),
112-
values:
113-
})
11429
}
11530
}
11631
```

examples/parse.rs

Lines changed: 0 additions & 47 deletions
This file was deleted.

examples/repl.rs

Lines changed: 129 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,146 @@
1+
use expression::Context;
2+
use expression::DataSource;
3+
use expression::Object;
4+
use std::fmt::Debug;
5+
use std::io::BufRead;
6+
use std::io::BufReader;
7+
use std::io::BufWriter;
18
use std::io::Write;
2-
use expression::{
3-
Context,
4-
DataSource,
5-
eval::Object
6-
};
7-
8-
struct RowInner {
9-
col1: String,
10-
col2: f64
11-
}
129

13-
struct ExampleProvider {
14-
values: Vec<String>
10+
/// A simple REPL to demonstrate the use of the expression crate.
11+
/// # Features:
12+
/// - Basic expression evaluation
13+
/// - Simple table for demonstrating addresses
14+
/// - Demonstrate complex usage of the API
15+
///
16+
/// Type an expression into the REPL and press enter.
17+
/// The following commands are available:
18+
/// - /exit: Exit the REPL
19+
/// - /dump: Dump the table
20+
/// - /set <addr>: Set the value of an address
21+
/// - /func <name> <arg1> <arg2> ...: Define a function
22+
///
23+
/// # Addresses:
24+
/// Addresses are of the form `column:row` where `column` is the name of the column and `row` is the row number. Therefore, columns may contain `:`.
25+
26+
#[derive(Debug, Clone)]
27+
struct Table {
28+
columns: Vec<String>,
29+
data: Vec<Object>,
1530
}
1631

17-
impl DataSource for ExampleProvider {
32+
impl DataSource for Table {
1833
fn query(&self, query: impl AsRef<str>) -> Option<Object> {
19-
let index = query.as_ref().parse::<usize>().ok()?;
34+
let (column, row) = query.as_ref().split_at(query.as_ref().rfind(':')?);
35+
let col = self.columns.iter().position(|c| c == column)?;
36+
37+
self.data
38+
.get((&row[1..]).parse::<usize>().ok()? * self.columns.len() + col)
39+
.map(|i| i.clone())
40+
}
41+
}
42+
43+
impl Table {
44+
pub fn empty<Col: AsRef<str>>(cols: impl AsRef<[Col]>) -> Self {
45+
Self {
46+
columns: cols.as_ref().iter().map(|c| c.as_ref().to_string()).collect(),
47+
data: vec![],
48+
}
49+
}
50+
51+
pub fn set(&mut self, addr: impl AsRef<str>, value: Object) -> Option<Object> {
52+
let (column, row) = addr.as_ref().split_at(addr.as_ref().rfind(':')?);
53+
let col = self.columns.iter().position(|c| c == column)?;
54+
55+
let index = (&row[1..]).parse::<usize>().ok()? * self.columns.len() + col;
56+
if index >= self.data.len() {
57+
self.data.resize(index + 1, Object::Nothing);
58+
}
2059

21-
self.values.get(index)
22-
.map(|i| Object::String(i.clone()))
60+
self.data[index] = value.clone();
61+
62+
Some(value)
2363
}
2464
}
2565

26-
pub fn main() -> std::io::Result<()> {
27-
let cx = Context::new(ExampleProvider {
28-
values: vec![]
29-
});
66+
fn prompt(prompt: impl AsRef<str>) -> String {
67+
let mut stdin = BufReader::new(std::io::stdin());
68+
69+
std::io::stderr().write_all(prompt.as_ref().as_bytes()).unwrap();
70+
std::io::stderr().flush().unwrap();
3071

31-
let mut buffer = String::new();
72+
let mut cmd = String::new();
73+
stdin.read_line(&mut cmd).unwrap();
3274

33-
eprint!("> ");
34-
std::io::stderr().flush()?;
75+
cmd.trim().to_string()
76+
}
3577

36-
while let Ok(len) = std::io::stdin().read_line(&mut buffer) {
37-
let program = buffer[0..len].trim();
78+
mod commands {
79+
use crate::prompt;
80+
use crate::Table;
81+
use expression::Context;
82+
use expression::Object;
3883

39-
match cx.evaluate(program) {
40-
Ok(result) => println!("{:#?}", result),
41-
Err(err) => println!("Error: {:?}", err)
84+
pub fn set(cx: &mut Context<Table>, addr: impl AsRef<str>) {
85+
let addr = addr.as_ref();
86+
87+
match cx.evaluate(prompt("--> ")) {
88+
Ok(v) => {
89+
match cx.provider_mut().set(addr, v.clone()) {
90+
Some(v) => eprintln!("Set {{{addr}}} to {v}"),
91+
None => eprintln!("Unable to set {{{addr}}}"),
92+
};
93+
}
94+
Err(err) => eprintln!("{}", err),
4295
}
96+
}
97+
pub fn func(cx: &mut Context<Table>, func: impl AsRef<str>) {
98+
let bindings = func
99+
.as_ref()
100+
.split_whitespace()
101+
.map(str::trim)
102+
.map(ToOwned::to_owned)
103+
.collect::<Vec<_>>();
104+
105+
let (name, bindings) = bindings.split_first().unwrap();
106+
let bindings = bindings.to_vec(); // Create owned copy of bindings
107+
108+
let body = prompt("--> ");
43109

44-
eprint!("> ");
45-
std::io::stderr().flush()?;
46-
buffer.clear();
110+
cx.push_fn(name, move |mut cx, args| {
111+
for (a, arg) in bindings.iter().enumerate() {
112+
cx.push_global(arg, args.get(a).cloned().unwrap_or(Object::Nothing));
113+
}
114+
115+
cx.evaluate(&body)
116+
});
47117
}
118+
pub fn global(cx: &mut Context<Table>, name: impl AsRef<str>) {
119+
let name = name.as_ref();
48120

49-
Ok(())
50-
}
121+
match cx.evaluate(prompt("--> ")) {
122+
Ok(v) => cx.push_global(name, v),
123+
Err(err) => eprintln!("{}", err)
124+
};
125+
}
126+
}
127+
128+
pub fn main() {
129+
let mut cx = Context::new(Table::empty(["a", "b", "c"]));
130+
131+
loop {
132+
let cmd = prompt("> ");
133+
134+
match cmd.trim() {
135+
"/exit" => break,
136+
"/dump" => println!("{:#?}", cx.provider()),
137+
cmd if cmd.starts_with("/set ") => commands::set(&mut cx, cmd[5..].trim()),
138+
cmd if cmd.starts_with("/func ") => commands::func(&mut cx, cmd[6..].trim()),
139+
cmd if cmd.starts_with("/glob ") => commands::global(&mut cx, cmd[6..].trim()),
140+
cmd => match cx.evaluate(cmd) {
141+
Ok(v) => println!("{}", v),
142+
Err(err) => eprintln!("{}", err),
143+
},
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)