initial commit

This commit is contained in:
Joe Bellus 2022-10-08 16:48:17 -04:00
commit 8a9f3d82a7
10 changed files with 3444 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

2633
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[workspace]
members = [
"abacus-core",
"abacus-ui"
]

15
abacus-core/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "abacus-core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence
syntect = "5.0.0"
rhai = "1.10.1"
polars = { version = "0.24.3", features = ["lazy", "rows"] }
tracing-subscriber = "0.3"

View File

@ -0,0 +1,477 @@
use super::ScriptResult;
use polars::prelude::{IntoLazy, NamedFrom, SerReader};
use std::{
any::TypeId,
ops::{Add, Deref},
str::FromStr,
};
use rhai::EvalAltResult;
pub fn setup_engine(engine: &mut rhai::Engine) {
//polar data frame
engine.register_type::<DataFrame>();
engine.register_fn("dataframe", script_functions::dataframe);
engine.register_fn("select", DataFrame::s_select);
engine.register_fn("load_csv", DataFrame::load_csv);
// polar series
engine.register_type::<Series>();
engine.register_indexer_get(Series::s_get);
engine.register_fn("series", script_functions::series);
engine.register_fn("head", Series::s_head);
engine.register_fn("+", Series::add);
engine.register_fn("sort", Series::s_sort);
engine.register_fn("sum", Series::s_sum);
engine.register_fn("add", Series::s_op_add);
engine.register_fn("column", DataFrame::s_column);
engine.register_fn("+", script_functions::add_series_i64);
engine.register_fn("+", script_functions::add_series_f64);
engine.register_fn("-", script_functions::subtract_series_i64);
engine.register_fn("-", script_functions::subtract_series_f64);
// polar expressions
engine.register_type::<DataFrameExpression>();
engine.register_fn("sum", DataFrameExpression::sum);
engine.register_fn("mean", DataFrameExpression::mean);
engine.register_fn("min", DataFrameExpression::min);
engine.register_fn("max", DataFrameExpression::max);
engine.register_fn("first", DataFrameExpression::first);
engine.register_fn("count", DataFrameExpression::count);
engine.register_fn("eq", DataFrameExpression::eq);
engine.register_fn("lt", DataFrameExpression::lt);
engine.register_fn("lte", DataFrameExpression::lte);
engine.register_fn("gt", DataFrameExpression::gt);
engine.register_fn("gte", DataFrameExpression::gte);
engine.register_fn("filter", DataFrameExpression::filter);
engine.register_fn("column", script_functions::column);
engine.register_fn("count", script_functions::count);
engine.register_fn("sum", script_functions::sum);
engine.register_fn("mean", script_functions::mean);
engine.register_fn("min", script_functions::min);
engine.register_fn("max", script_functions::max);
engine.register_fn("first", script_functions::first);
}
#[derive(Clone, Debug)]
pub struct DataFrame(polars::frame::DataFrame);
impl Deref for DataFrame {
type Target = polars::frame::DataFrame;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DataFrame {
pub fn s_column(&mut self, name: &str) -> ScriptResult<Series> {
Ok(Series(
self.0.column(name).map_err(|e| e.to_string())?.clone(),
))
}
pub fn load_csv(path: &str) -> ScriptResult<DataFrame> {
let df = polars::io::csv::CsvReader::from_path(path)
.map_err(|e| e.to_string())?
.has_header(true)
.finish()
.map_err(|e| e.to_string())?;
Ok(DataFrame(df))
}
pub fn s_select(&mut self, expressions: rhai::Array) -> ScriptResult<DataFrame> {
let expressions = expressions
.into_iter()
.filter_map(|i| i.try_cast::<DataFrameExpression>())
.map(polars::lazy::dsl::Expr::from)
.collect::<Vec<_>>();
Ok(DataFrame(
self.0
.clone()
.lazy()
.select(expressions)
.collect()
.map_err(|e| e.to_string())?,
))
}
}
#[derive(Clone, Debug)]
pub struct DataFrameExpression(polars::lazy::dsl::Expr);
impl From<DataFrameExpression> for polars::lazy::dsl::Expr {
fn from(source: DataFrameExpression) -> Self {
source.0
}
}
impl DataFrameExpression {
fn first(&mut self) -> Self {
DataFrameExpression(self.0.clone().first())
}
fn sum(&mut self) -> Self {
DataFrameExpression(self.0.clone().sum())
}
fn mean(&mut self) -> Self {
DataFrameExpression(self.0.clone().mean())
}
fn max(&mut self) -> Self {
DataFrameExpression(self.0.clone().max())
}
fn min(&mut self) -> Self {
DataFrameExpression(self.0.clone().min())
}
fn count(&mut self) -> Self {
DataFrameExpression(self.0.clone().count())
}
fn eq(&mut self, e: rhai::Dynamic) -> Self {
DataFrameExpression(self.0.clone().eq(DataFrameExpression::from(e)))
}
fn gt(&mut self, e: rhai::Dynamic) -> Self {
DataFrameExpression(self.0.clone().gt(DataFrameExpression::from(e)))
}
fn lt(&mut self, e: rhai::Dynamic) -> Self {
DataFrameExpression(self.0.clone().lt(DataFrameExpression::from(e)))
}
fn gte(&mut self, e: rhai::Dynamic) -> Self {
DataFrameExpression(self.0.clone().gt_eq(DataFrameExpression::from(e)))
}
fn lte(&mut self, e: rhai::Dynamic) -> Self {
DataFrameExpression(self.0.clone().lt_eq(DataFrameExpression::from(e)))
}
pub fn filter(&mut self, expr: DataFrameExpression) -> DataFrameExpression {
DataFrameExpression(self.0.clone().filter(expr.0))
}
}
impl From<rhai::Dynamic> for DataFrameExpression {
fn from(source: rhai::Dynamic) -> Self {
match source {
x if x.is::<i64>() => DataFrameExpression(x.as_int().unwrap().into()),
x if x.is::<f64>() => DataFrameExpression(x.as_float().unwrap().into()),
x if x.is::<String>() => DataFrameExpression(x.into_string().unwrap()[..].into()),
x if x.is::<bool>() => DataFrameExpression(x.as_bool().unwrap().into()),
_ => DataFrameExpression(polars::lazy::dsl::Expr::default()),
}
}
}
#[derive(Clone, Debug)]
pub struct Series(polars::series::Series);
impl Deref for Series {
type Target = polars::series::Series;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Add for Series {
type Output = Series;
fn add(self, rhs: Self) -> Self::Output {
Series(self.0 + rhs.0)
}
}
impl Series {
pub fn s_head(&mut self, n: i64) -> Series {
Series(self.0.head(Some(n as usize)))
}
pub fn s_sort(&mut self, reverse: bool) -> Series {
Series(self.0.sort(reverse))
}
pub fn s_sum(&mut self) -> i64 {
self.0.sum().unwrap_or_default()
}
pub fn s_get(&mut self, n: i64) -> ScriptResult<rhai::Dynamic> {
let value = self.get(n as usize);
match value {
polars::datatypes::AnyValue::Utf8(v) => {
Ok(rhai::Dynamic::from_str(v).unwrap_or_default())
}
polars::prelude::AnyValue::Null => Ok(rhai::Dynamic::UNIT),
polars::prelude::AnyValue::Boolean(v) => Ok(rhai::Dynamic::from_bool(v)),
polars::prelude::AnyValue::UInt8(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::UInt16(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::UInt32(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::UInt64(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Int8(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Int16(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Int32(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Int64(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Float32(v) => Ok(rhai::Dynamic::from_float(v as f64)),
polars::prelude::AnyValue::Float64(v) => Ok(rhai::Dynamic::from_float(v as f64)),
polars::prelude::AnyValue::Date(v) => Ok(rhai::Dynamic::from_int(v as i64)),
polars::prelude::AnyValue::Datetime(_, _, _) => Ok(rhai::Dynamic::from_int(0)),
polars::prelude::AnyValue::Duration(_, _) => Ok(rhai::Dynamic::from_int(0)),
polars::prelude::AnyValue::Time(_) => Ok(rhai::Dynamic::from_int(0)),
polars::prelude::AnyValue::List(s) => Ok(rhai::Dynamic::from(Series(s))),
polars::prelude::AnyValue::Utf8Owned(v) => {
Ok(rhai::Dynamic::from_str(&v).unwrap_or_default())
}
}
}
pub fn s_op_add(self, series: Series) -> Series {
Series(self.0 + series.0)
}
}
mod script_functions {
use super::*;
pub fn add_series_i64(a: Series, b: i64) -> Series {
Series(a.0 + b)
}
pub fn add_series_f64(a: Series, b: f64) -> Series {
Series(a.0 + b)
}
pub fn subtract_series_i64(a: Series, b: i64) -> Series {
Series(a.0 - b)
}
pub fn subtract_series_f64(a: Series, b: f64) -> Series {
Series(a.0 - b)
}
pub fn column(name: &str) -> DataFrameExpression {
DataFrameExpression(polars::prelude::col(name))
}
pub fn sum(name: &str) -> DataFrameExpression {
DataFrameExpression(polars::prelude::sum(name))
}
pub fn min(name: &str) -> DataFrameExpression {
DataFrameExpression(polars::prelude::min(name))
}
pub fn max(name: &str) -> DataFrameExpression {
DataFrameExpression(polars::prelude::max(name))
}
pub fn mean(name: &str) -> DataFrameExpression {
DataFrameExpression(polars::prelude::mean(name))
}
pub fn first() -> DataFrameExpression {
DataFrameExpression(polars::prelude::first())
}
pub fn count() -> DataFrameExpression {
DataFrameExpression(polars::prelude::count())
}
pub fn dataframe(mp: rhai::Map) -> ScriptResult<DataFrame> {
let mut series_vec = vec![];
for (key, value) in mp.into_iter() {
if let Some(array) = value.try_cast::<rhai::Array>() {
let s = series(&key, array)?;
series_vec.push(s);
}
}
if let Ok(df) = polars::frame::DataFrame::new(series_vec.into_iter().map(|i| i.0).collect())
{
Ok(DataFrame(df))
} else {
Ok(DataFrame(polars::frame::DataFrame::default()))
}
}
pub fn series(name: &str, arr: rhai::Array) -> std::result::Result<Series, Box<EvalAltResult>> {
if let Some(i) = arr.first() {
let series = if i.type_id() == TypeId::of::<i64>() {
polars::series::Series::new(
name,
arr.into_iter()
.map(|i| i.cast::<i64>())
.collect::<Vec<i64>>(),
)
} else if i.type_id() == TypeId::of::<f64>() {
polars::series::Series::new(
name,
arr.into_iter()
.map(|i| i.cast::<f64>())
.collect::<Vec<f64>>(),
)
} else {
polars::series::Series::new(
name,
arr.into_iter()
.map(|i| i.cast::<String>())
.collect::<Vec<String>>(),
)
};
Ok(Series(series))
} else {
let v: Vec<i64> = vec![];
let s = polars::series::Series::new(name, v);
Ok(Series(s))
}
}
}
#[cfg(test)]
mod tests {
use crate::engine::tests::process;
#[test]
pub fn simple_dataframe() {
let frame = process(r#"dataframe(#{ floats: [1.0, 2.0, 3.0], ints: [1,2,3], strings: ["one", "two", "three"] }) "#).into_frame();
let s = frame
.column("floats")
.unwrap()
.f64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(1.), Some(2.), Some(3.)]);
let s = frame
.column("ints")
.unwrap()
.i64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(1), Some(2), Some(3)]);
let s = frame
.column("strings")
.unwrap()
.utf8()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some("one"), Some("two"), Some("three")]);
}
#[test]
pub fn dataframe_get_column() {
let series =
process(r#"dataframe(#{ item: [1.0, 2.0, 3.0] }).column("item") "#).into_series();
let s = series.f64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(1.), Some(2.), Some(3.)]);
}
#[test]
pub fn simple_series() {
let series = process(r#"series("ages;", [18.,21.,25.,35.])"#).into_series();
let s = series.f64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(18.), Some(21.), Some(25.), Some(35.)]);
}
#[test]
pub fn series_head() {
let series = process(r#"series("ages", [18.,21.,25.,35.]).head(1)"#).into_series();
let s = series.f64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(18.)]);
}
#[test]
pub fn series_sort() {
let series =
process(r#"series("ages", [18.,21.,25.,35.]).sort(true).head(1)"#).into_series();
let s = series.f64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(35.)]);
}
#[test]
pub fn series_index() {
let s = process(r#"series("ages", [18, 21, 25, 35]).sort(true)[1]"#).into_scalar();
assert_eq!(s.cast::<i64>(), 25);
}
#[test]
pub fn series_add() {
let series = process(
r#"
let s1 = series("ages", [1, 1, 1, 1]);
let s2 = series("ages", [18, 21, 25, 35]);
s1 + s2
"#,
)
.into_series();
let s = series.i64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(19), Some(22), Some(26), Some(36)]);
}
#[test]
pub fn test_load_csv() {
let series =
process(r#"load_csv("test/data.csv").column("age").sort(true).head(1)"#).into_series();
let s = series.i64().unwrap().into_iter().collect::<Vec<_>>();
assert_eq!(s, vec![Some(32)]);
}
#[test]
pub fn test_dataframe_select() {
let res = process(r#" let data = load_csv("test/data.csv"); data.select([sum("age")]) "#)
.into_frame();
let s = res
.column("age")
.unwrap()
.i64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(72)]);
let res = process(r#" let data = load_csv("test/data.csv"); data.select([min("age")]) "#)
.into_frame();
let s = res
.column("age")
.unwrap()
.i64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(18)]);
let res = process(r#" let data = load_csv("test/data.csv"); data.select([max("age")]) "#)
.into_frame();
let s = res
.column("age")
.unwrap()
.i64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(32)]);
let res = process(r#" let data = load_csv("test/data.csv"); data.select([mean("age")]) "#)
.into_frame();
let s = res
.column("age")
.unwrap()
.f64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(24.0)]);
}
#[test]
pub fn test_dataframe_filter() {
let res = process(r#" let data = load_csv("test/data.csv"); data.select([column("age").filter(column("age").eq(18)).sum()]) "#)
.into_frame();
let s = res
.column("age")
.unwrap()
.i64()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(s, vec![Some(18)]);
}
}

100
abacus-core/src/engine.rs Normal file
View File

@ -0,0 +1,100 @@
use crate::dataframe;
#[derive(Debug)]
pub struct Engine {
pub blocks: Vec<Block>,
engine: rhai::Engine,
}
impl Default for Engine {
fn default() -> Self {
let mut engine = rhai::Engine::new();
engine.set_fast_operators(false);
dataframe::setup_engine(&mut engine);
Self {
engine,
blocks: vec![Block::default()],
}
}
}
impl Engine {
pub fn process(&mut self) {
for block in self.blocks.iter_mut() {
match self.engine.eval::<rhai::Dynamic>(&block.script) {
Ok(res) if res.is::<dataframe::DataFrame>() => {
let frame = rhai::Dynamic::cast::<dataframe::DataFrame>(res);
block.output = Output::DataFrame(frame);
}
Ok(res) if res.is::<dataframe::Series>() => {
let frame = rhai::Dynamic::cast::<dataframe::Series>(res);
block.output = Output::Series(frame);
}
Ok(res) => {
block.output = Output::Scalar(res);
}
Err(e) => {
block.output = Output::Error(e.to_string());
}
}
}
}
}
#[derive(Debug, Clone)]
pub enum Output {
None,
Scalar(rhai::Dynamic),
DataFrame(dataframe::DataFrame),
Series(dataframe::Series),
Error(String),
}
impl Output {
pub fn into_frame(self) -> dataframe::DataFrame {
if let Self::DataFrame(v) = self {
return v;
}
panic!("Not a dataframe");
}
pub fn into_series(self) -> dataframe::Series {
if let Self::Series(v) = self {
return v;
}
panic!("Not a series");
}
pub fn into_scalar(self) -> rhai::Dynamic {
if let Self::Scalar(v) = self {
return v;
}
panic!("Not a scalar");
}
}
#[derive(Debug)]
pub struct Block {
pub script: String,
pub output: Output,
}
impl Default for Block {
fn default() -> Self {
Self {
script: Default::default(),
output: Output::None,
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
pub fn process(script: &str) -> Output {
let mut engine = Engine::default();
engine.blocks[0].script = script.into();
engine.process();
engine.blocks[0].output.clone()
}
}

7
abacus-core/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
mod dataframe;
mod engine;
pub use engine::Engine;
use rhai::EvalAltResult;
type ScriptResult<T> = std::result::Result<T, Box<EvalAltResult>>;

10
abacus-ui/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "abacus-ui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
druid = { git = "https://github.com/linebender/druid.git" }
abacus-core = { path = "../abacus-core" }

160
abacus-ui/src/editor.rs Normal file
View File

@ -0,0 +1,160 @@
use druid::{
piet::{Text, TextLayout, TextLayoutBuilder},
Color, Data, Event, FontFamily, Lens, LifeCycle, RenderContext, Widget,
};
#[derive(Clone, Data, PartialEq, Eq)]
pub enum EditMode {
Normal,
Insert,
}
pub struct AbacusEditor;
#[derive(Data, Lens, Clone)]
pub struct EditorData {
pub raw_content: String,
pub cursor_pos: usize,
pub mode: EditMode,
}
impl Default for EditorData {
fn default() -> Self {
Self {
raw_content: "let x = 1;\nx+1".to_string(),
cursor_pos: 5,
mode: EditMode::Normal,
}
}
}
impl EditorData {
pub fn push_str(&mut self, s: &str) {
self.raw_content.push_str(s);
self.cursor_to_end();
}
pub fn push(&mut self, c: char) {
self.raw_content.push(c);
self.cursor_to_end();
}
pub fn cursor_to_end(&mut self) {
self.cursor_pos = self.raw_content.len();
}
pub fn delete_char_back(&mut self) {
self.raw_content.pop();
self.cursor_to_end();
}
}
impl Widget<EditorData> for AbacusEditor {
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &EditorData, env: &druid::Env) {
let size = ctx.size();
let rect = size.to_rect();
if ctx.is_focused() {
ctx.fill(rect, &Color::rgb8(10, 10, 10));
} else {
ctx.fill(rect, &Color::rgb8(20, 20, 20));
}
let layout = ctx
.text()
.new_text_layout(data.raw_content.clone())
.font(FontFamily::MONOSPACE, 24.0)
.text_color(Color::rgb8(255, 255, 255))
.build()
.unwrap();
dbg!(layout.rects_for_range((data.cursor_pos - 1)..data.cursor_pos));
ctx.fill(
layout
.rects_for_range((data.cursor_pos - 1)..data.cursor_pos)
.last()
.unwrap(),
&Color::rgb8(50, 50, 50),
);
ctx.draw_text(&layout, (1.0, 1.0));
dbg!(ctx.is_focused());
}
fn event(
&mut self,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut EditorData,
env: &druid::Env,
) {
match event {
Event::KeyUp(e) => match &e.key {
druid::keyboard_types::Key::Character(c) => {
data.push_str(c);
ctx.request_paint();
}
druid::keyboard_types::Key::Enter => {
data.push('\n');
ctx.request_paint();
}
druid::keyboard_types::Key::Backspace => {
data.delete_char_back();
ctx.request_paint();
}
druid::keyboard_types::Key::Escape => {
data.mode == EditMode::Normal;
}
e => {
dbg!(e);
}
},
Event::MouseDown(_) => {
if !ctx.is_focused() {
ctx.request_focus();
}
}
_ => {}
}
}
fn lifecycle(
&mut self,
ctx: &mut druid::LifeCycleCtx,
event: &druid::LifeCycle,
data: &EditorData,
env: &druid::Env,
) {
match event {
LifeCycle::FocusChanged(_) => {
ctx.request_paint();
}
LifeCycle::WidgetAdded => {
// ctx.register_text_input(document)
}
LifeCycle::BuildFocusChain => {
ctx.register_for_focus();
}
_ => {}
}
}
fn update(
&mut self,
ctx: &mut druid::UpdateCtx,
old_data: &EditorData,
data: &EditorData,
env: &druid::Env,
) {
}
fn layout(
&mut self,
ctx: &mut druid::LayoutCtx,
bc: &druid::BoxConstraints,
data: &EditorData,
env: &druid::Env,
) -> druid::Size {
bc.max()
}
}

35
abacus-ui/src/main.rs Normal file
View File

@ -0,0 +1,35 @@
mod editor;
use druid::widget::{Align, Flex, Label, Padding, RawLabel};
use druid::{AppLauncher, Color, Data, PlatformError, Widget, WidgetExt, WindowDesc};
use editor::EditorData;
fn build_ui() -> impl Widget<editor::EditorData> {
Padding::new(
10.0,
Flex::row()
.with_flex_child(
Flex::column().with_flex_child(editor::AbacusEditor, 1.0),
1.0,
)
.with_flex_child(
Flex::column()
.with_flex_child(
Label::new("top right").background(Color::rgb8(0, 0, 255)),
1.0,
)
.with_flex_child(Align::centered(Label::new("bottom right")), 1.0),
1.0,
),
)
}
fn main() -> Result<(), PlatformError> {
AppLauncher::with_window(WindowDesc::new(build_ui())).launch(editor::EditorData::default())?;
Ok(())
}
#[derive(Clone, Data)]
struct CodeEditor {
code: String,
}