initial commit
This commit is contained in:
commit
8a9f3d82a7
|
@ -0,0 +1 @@
|
|||
target/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"abacus-core",
|
||||
"abacus-ui"
|
||||
]
|
|
@ -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"
|
||||
|
||||
|
|
@ -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)]);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mod dataframe;
|
||||
mod engine;
|
||||
|
||||
pub use engine::Engine;
|
||||
|
||||
use rhai::EvalAltResult;
|
||||
type ScriptResult<T> = std::result::Result<T, Box<EvalAltResult>>;
|
|
@ -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" }
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue