From ea525c7f33ddbb2686b7a756b19bb7c90227a137 Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Sat, 29 May 2021 11:44:07 +0800 Subject: [PATCH 1/7] initial support for tuple --- calcit/snapshots/test.cirru | 13 ++++++++++++- src/builtins.rs | 2 ++ src/builtins/lists.rs | 8 ++++++++ src/builtins/meta.rs | 11 ++++++++++- src/cirru/calcit-core.cirru | 6 ++++++ src/data/cirru.rs | 8 +++++++- src/primes.rs | 17 ++++++++++++++++- src/runner.rs | 1 + 8 files changed, 62 insertions(+), 4 deletions(-) diff --git a/calcit/snapshots/test.cirru b/calcit/snapshots/test.cirru index f86498e4..ad3c1133 100644 --- a/calcit/snapshots/test.cirru +++ b/calcit/snapshots/test.cirru @@ -242,6 +242,15 @@ assert= |1 -> a (invoke :inc) (invoke :show) + |test-tuple $ quote + fn () + log-title "|Testing tuple" + + assert= :tuple (type-of (:: :a :b)) + assert= :a (nth (:: :a :b) 0) + assert= :b (nth (:: :a :b) 1) + assert= 2 (count (:: :a :b)) + |reload! $ quote defn reload! () nil @@ -276,9 +285,11 @@ test-fn-eq test-refs - + test-invoke + test-tuple + inside-eval: test-gynienic/main! diff --git a/src/builtins.rs b/src/builtins.rs index 8cc8147e..32badf10 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -33,6 +33,7 @@ pub fn is_proc_name(s: &str) -> bool { | "write-cirru-edn" | "turn-symbol" | "turn-keyword" + | "::" // unstable // effects | "echo" | "println" // alias for echo @@ -163,6 +164,7 @@ pub fn handle_proc(name: &str, args: &CalcitItems) -> Result { "write-cirru-edn" => meta::write_cirru_edn(args), "turn-symbol" => meta::turn_symbol(args), "turn-keyword" => meta::turn_keyword(args), + "::" => meta::new_tuple(args), // unstable solution for the name // effects "echo" => effects::echo(args), "println" => effects::echo(args), // alias diff --git a/src/builtins/lists.rs b/src/builtins/lists.rs index 02121628..ed15a237 100644 --- a/src/builtins/lists.rs +++ b/src/builtins/lists.rs @@ -14,6 +14,7 @@ pub fn new_list(xs: &CalcitItems) -> Result { pub fn empty_ques(xs: &CalcitItems) -> Result { match xs.get(0) { Some(Calcit::Nil) => Ok(Calcit::Bool(true)), + Some(Calcit::Tuple(..)) => Ok(Calcit::Bool(false)), Some(Calcit::List(ys)) => Ok(Calcit::Bool(ys.is_empty())), Some(Calcit::Map(ys)) => Ok(Calcit::Bool(ys.is_empty())), Some(Calcit::Set(ys)) => Ok(Calcit::Bool(ys.is_empty())), @@ -26,6 +27,7 @@ pub fn empty_ques(xs: &CalcitItems) -> Result { pub fn count(xs: &CalcitItems) -> Result { match xs.get(0) { Some(Calcit::Nil) => Ok(Calcit::Number(0.0)), + Some(Calcit::Tuple(..)) => Ok(Calcit::Number(2.0)), Some(Calcit::List(ys)) => Ok(Calcit::Number(ys.len() as f64)), Some(Calcit::Map(ys)) => Ok(Calcit::Number(ys.len() as f64)), Some(Calcit::Set(ys)) => Ok(Calcit::Number(ys.len() as f64)), @@ -39,6 +41,12 @@ pub fn count(xs: &CalcitItems) -> Result { pub fn nth(xs: &CalcitItems) -> Result { match (xs.get(0), xs.get(1)) { (Some(Calcit::Nil), Some(Calcit::Number(_))) => Ok(Calcit::Nil), + (Some(Calcit::Tuple(a, b)), Some(Calcit::Number(n))) => match f64_to_usize(*n) { + Ok(0) => Ok((**a).clone()), + Ok(1) => Ok((**b).to_owned()), + Ok(m) => Err(format!("Tuple only got 2 elements, trying to index with {}", m)), + Err(e) => Err(format!("nth expect usize, {}", e)), + }, (Some(Calcit::List(ys)), Some(Calcit::Number(n))) => match f64_to_usize(*n) { Ok(idx) => match ys.get(idx) { Some(v) => Ok(v.clone()), diff --git a/src/builtins/meta.rs b/src/builtins/meta.rs index 3f652713..21d83091 100644 --- a/src/builtins/meta.rs +++ b/src/builtins/meta.rs @@ -2,7 +2,7 @@ use crate::call_stack; use crate::data::cirru; use crate::data::edn; use crate::primes; -use crate::primes::{Calcit, CalcitItems}; +use crate::primes::{Calcit, CalcitItems, CrListWrap}; use crate::util::number::f64_to_usize; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -21,6 +21,7 @@ pub fn type_of(xs: &CalcitItems) -> Result { Calcit::Str(..) => Ok(Calcit::Keyword(String::from("string"))), Calcit::Thunk(..) => Ok(Calcit::Keyword(String::from("thunk"))), // internal Calcit::Ref(..) => Ok(Calcit::Keyword(String::from("ref"))), + Calcit::Tuple(..) => Ok(Calcit::Keyword(String::from("tuple"))), Calcit::Recur(..) => Ok(Calcit::Keyword(String::from("recur"))), Calcit::List(..) => Ok(Calcit::Keyword(String::from("list"))), Calcit::Set(..) => Ok(Calcit::Keyword(String::from("set"))), @@ -183,3 +184,11 @@ pub fn turn_keyword(xs: &CalcitItems) -> Result { None => Err(String::from("turn-keyword expected 1 argument, got nothing")), } } + +pub fn new_tuple(xs: &CalcitItems) -> Result { + if xs.len() != 2 { + Err(format!("Tuple expected 2 arguments, got {}", CrListWrap(xs.to_owned()))) + } else { + Ok(Calcit::Tuple(Box::new(xs[0].to_owned()), Box::new(xs[1].to_owned()))) + } +} diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index 66bca56e..5caefc21 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -139,6 +139,12 @@ |nil? $ quote defn nil? (x) $ &= (type-of x) :nil + |ref? $ quote + defn ref? (x) $ &= (type-of x) :ref + + |tuple? $ quote + defn tuple? (x) $ &= (type-of x) :tuple + |record? $ quote defn record? (x) $ &= (type-of x) :record diff --git a/src/data/cirru.rs b/src/data/cirru.rs index 3094f087..26b56ef0 100644 --- a/src/data/cirru.rs +++ b/src/data/cirru.rs @@ -15,7 +15,13 @@ pub fn code_to_calcit(xs: &Cirru, ns: &str) -> Result { "&tab" => Ok(Calcit::Str(String::from("\t"))), "" => Err(String::from("Empty string is invalid")), _ => match s.chars().next().unwrap() { - ':' => Ok(Calcit::Keyword(String::from(&s[1..]))), + ':' => { + if s == "::" { + Ok(Calcit::Proc(String::from("::"))) // special tuple syntax + } else { + Ok(Calcit::Keyword(String::from(&s[1..]))) + } + } '"' | '|' => Ok(Calcit::Str(String::from(&s[1..]))), '0' if s.starts_with("0x") => match u32::from_str_radix(&s[2..], 16) { Ok(n) => Ok(Calcit::Number(n as f64)), diff --git a/src/primes.rs b/src/primes.rs index 28653933..247a0ee4 100644 --- a/src/primes.rs +++ b/src/primes.rs @@ -43,6 +43,7 @@ pub enum Calcit { Thunk(Box), /// holding a path to its state Ref(String), + Tuple(Box, Box), Recur(CalcitItems), // not data, but for recursion List(CalcitItems), Set(im::HashSet), @@ -87,6 +88,7 @@ impl fmt::Display for Calcit { } // TODO, escaping choices Calcit::Thunk(v) => f.write_str(&format!("(&thunk {})", v)), Calcit::Ref(name) => f.write_str(&format!("(&ref {})", name)), + Calcit::Tuple(a, b) => f.write_str(&format!("(:: {} {})", a, b)), Calcit::Recur(xs) => { f.write_str("(&recur")?; for x in xs { @@ -234,9 +236,14 @@ impl Hash for Calcit { v.hash(_state); } Calcit::Ref(name) => { - "quote:".hash(_state); + "ref:".hash(_state); name.hash(_state); } + Calcit::Tuple(a, b) => { + "tuple:".hash(_state); + a.hash(_state); + b.hash(_state); + } Calcit::Recur(v) => { "list:".hash(_state); v.hash(_state); @@ -331,6 +338,13 @@ impl Ord for Calcit { (Calcit::Ref(_), _) => Less, (_, Calcit::Ref(_)) => Greater, + (Calcit::Tuple(a, b), Calcit::Tuple(c, d)) => match a.cmp(c) { + Equal => b.cmp(d), + v => v, + }, + (Calcit::Tuple(_, _), _) => Less, + (_, Calcit::Tuple(_, _)) => Greater, + (Calcit::Recur(a), Calcit::Recur(b)) => a.cmp(b), (Calcit::Recur(_), _) => Less, (_, Calcit::Recur(_)) => Greater, @@ -401,6 +415,7 @@ impl PartialEq for Calcit { (Calcit::Str(a), Calcit::Str(b)) => a == b, (Calcit::Thunk(a), Calcit::Thunk(b)) => a == b, (Calcit::Ref(a), Calcit::Ref(b)) => a == b, + (Calcit::Tuple(a, b), Calcit::Tuple(c, d)) => a == c && b == d, (Calcit::List(a), Calcit::List(b)) => a == b, (Calcit::Set(a), Calcit::Set(b)) => a == b, (Calcit::Map(a), Calcit::Map(b)) => a == b, diff --git a/src/runner.rs b/src/runner.rs index 80ff6448..34643445 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -41,6 +41,7 @@ pub fn evaluate_expr( Calcit::Str(_) => Ok(expr.clone()), Calcit::Thunk(code) => evaluate_expr(code, scope, file_ns, program_code), Calcit::Ref(_) => Ok(expr.clone()), + Calcit::Tuple(..) => Ok(expr.clone()), Calcit::Recur(_) => unreachable!("recur not expected to be from symbol"), Calcit::List(xs) => match xs.get(0) { None => Err(format!("cannot evaluate empty expr: {}", expr)), From c119b5d8ae8204faf5435a4fe635d122b3958823 Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Sat, 29 May 2021 18:27:44 +0800 Subject: [PATCH 2/7] prototype of native method syntax; bump 0.3.21 --- .gitignore | 5 +- Cargo.lock | 2 +- Cargo.toml | 2 +- calcit/snapshots/test-js.cirru | 1 + calcit/snapshots/test-string.cirru | 8 ++ lib/calcit-data.ts | 34 ++++++++ lib/calcit.procs.ts | 65 ++++++++++++++- package.json | 2 +- src/bin/cr.rs | 9 +++ src/builtins.rs | 3 +- src/builtins/meta.rs | 89 ++++++++++++++++++++- src/builtins/records.rs | 3 + src/cirru/calcit-core.cirru | 124 +++++++++++++++++++++++++++++ src/codegen/emit_js.rs | 39 ++++++++- src/codegen/emit_js/snippets.rs | 17 +++- src/data/cirru.rs | 10 ++- src/primes.rs | 1 + src/runner.rs | 8 +- src/runner/preprocess.rs | 2 +- 19 files changed, 409 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index e2be755c..03ead405 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ js-out/ node_modules/ lib/*.js -builds/ \ No newline at end of file +builds/ + + +.compact-inc.cirru diff --git a/Cargo.lock b/Cargo.lock index 42ba4cc7..fc8a6e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "calcit_runner" -version = "0.3.20" +version = "0.3.21" dependencies = [ "chrono", "cirru_edn", diff --git a/Cargo.toml b/Cargo.toml index 0e4c119c..d013fc66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calcit_runner" -version = "0.3.20" +version = "0.3.21" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/calcit/snapshots/test-js.cirru b/calcit/snapshots/test-js.cirru index 31a1e7b7..18f9e99f 100644 --- a/calcit/snapshots/test-js.cirru +++ b/calcit/snapshots/test-js.cirru @@ -23,6 +23,7 @@ js/console.log "|is a Number" .log js/console |demo + js/console.log "|Dates in difference syntax" (.!now js/Date) (.now js/Date) js/console.log $ .-PI js/Math js/console.log $ aget js/Math |PI diff --git a/calcit/snapshots/test-string.cirru b/calcit/snapshots/test-string.cirru index 34592c32..1ae5b586 100644 --- a/calcit/snapshots/test-string.cirru +++ b/calcit/snapshots/test-string.cirru @@ -191,6 +191,12 @@ format-to-lisp $ quote $ nil? nil , "|(nil? nil)" + |test-methods $ quote + defn test-methods () + log-title "|Testing methods" + + assert= 3 (.count |abc) + |main! $ quote defn main! () log-title "|Testing str" @@ -214,6 +220,8 @@ test-lisp-style + test-methods + do true :proc $ quote () diff --git a/lib/calcit-data.ts b/lib/calcit-data.ts index 9edb7b71..9f3bed97 100644 --- a/lib/calcit-data.ts +++ b/lib/calcit-data.ts @@ -108,6 +108,28 @@ export class CrDataRef { } } +export class CrDataTuple { + fst: CrDataValue; + snd: CrDataValue; + cachedHash: Hash; + constructor(a: CrDataValue, b: CrDataValue) { + this.fst = a; + this.snd = b; + } + get(n: number) { + if (n == 0) { + return this.fst; + } else if (n == 1) { + return this.snd; + } else { + throw new Error("Tuple only have 2 elements"); + } + } + toString(): string { + return `(&tuple ${this.fst.toString()} ${this.snd.toString()})`; + } +} + export type CrDataFn = (...xs: CrDataValue[]) => CrDataValue; export class CrDataList { @@ -499,6 +521,7 @@ export type CrDataValue = | CrDataKeyword | CrDataSymbol | CrDataRef + | CrDataTuple | CrDataFn | CrDataRecur // should not be exposed to function | CrDataRecord @@ -528,6 +551,7 @@ let defaultHash_false = valueHash("false:"); let defaultHash_symbol = valueHash("symbol:"); let defaultHash_fn = valueHash("fn:"); let defaultHash_ref = valueHash("ref:"); +let defaultHash_tuple = valueHash("tuple:"); let defaultHash_set = valueHash("set:"); let defaultHash_list = valueHash("list:"); let defaultHash_map = valueHash("map:"); @@ -575,6 +599,13 @@ let hashFunction = (x: CrDataValue): Hash => { x.cachedHash = h; return h; } + if (x instanceof CrDataTuple) { + let base = defaultHash_list; + base = mergeValueHash(base, hashFunction(x.fst)); + base = mergeValueHash(base, hashFunction(x.snd)); + x.cachedHash = base; + return base; + } if (x instanceof CrDataSet) { // TODO not using dirty solution for code let base = defaultHash_set; @@ -658,6 +689,9 @@ export let toString = (x: CrDataValue, escaped: boolean): string => { if (x instanceof CrDataRef) { return x.toString(); } + if (x instanceof CrDataTuple) { + return x.toString(); + } console.warn("Unknown structure to string, better use `console.log`", x); return `${x}`; diff --git a/lib/calcit.procs.ts b/lib/calcit.procs.ts index cb7c0089..2da095bc 100644 --- a/lib/calcit.procs.ts +++ b/lib/calcit.procs.ts @@ -19,6 +19,7 @@ import { CrDataRecord, getStringName, findInFields, + CrDataTuple, } from "./calcit-data"; import { fieldsEqual } from "./record-procs"; @@ -51,6 +52,9 @@ export let type_of = (x: any): CrDataKeyword => { if (x instanceof CrDataRef) { return kwd("ref"); } + if (x instanceof CrDataTuple) { + return kwd("tuple"); + } if (x instanceof CrDataSymbol) { return kwd("symbol"); } @@ -91,6 +95,9 @@ export let count = (x: CrDataValue): number => { if (x instanceof CrDataList) { return x.len(); } + if (x instanceof CrDataTuple) { + return 2; + } if (x instanceof CrDataMap) { return x.len(); } @@ -491,6 +498,9 @@ export let nth = function (xs: CrDataValue, k: CrDataValue) { if (xs instanceof CrDataList) { return xs.get(k); } + if (xs instanceof CrDataTuple) { + return xs.get(k); + } if (xs instanceof CrDataRecord) { if (k < 0 || k >= xs.fields.length) { throw new Error("Out of bound"); @@ -1722,13 +1732,66 @@ export let _AND_js_object = (...xs: CrDataValue[]): Record }; /** notice, Nim version of format-time takes format */ -export let format_time = (timeSecNumber: number, format?: string) => { +export let format_time = (timeSecNumber: number, format?: string): string => { if (format != null) { console.error("format of calcit-js not implemented"); } return new Date(timeSecNumber * 1000).toISOString(); }; +export let _COL__COL_ = (a: CrDataValue, b: CrDataValue): CrDataTuple => { + return new CrDataTuple(a, b); +}; + +// mutable place for core to register +let calcit_builtin_classes = { + number: null as CrDataRecord, + string: null as CrDataRecord, + set: null as CrDataRecord, + list: null as CrDataRecord, + map: null as CrDataRecord, + record: null as CrDataRecord, +}; + +// need to register code from outside +export let register_calcit_builtin_classes = (options: typeof calcit_builtin_classes) => { + Object.assign(calcit_builtin_classes, options); +}; + +export let invoke_method = (p: string, obj: CrDataValue, ...args: CrDataValue[]) => { + let klass: CrDataRecord; + let rawValue = obj; + if (obj instanceof CrDataTuple) { + if (obj.fst instanceof CrDataRecord) { + klass = obj.fst; + rawValue = obj.snd; + } else { + throw new Error("Method invoking expected a record as class"); + } + } else if (typeof obj === "number") { + klass = calcit_builtin_classes.number; + } else if (typeof obj === "string") { + klass = calcit_builtin_classes.string; + } else if (obj instanceof CrDataSet) { + klass = calcit_builtin_classes.set; + } else if (obj instanceof CrDataList) { + klass = calcit_builtin_classes.list; + } else if (obj instanceof CrDataRecord) { + klass = calcit_builtin_classes.record; + } else { + return (obj as any)[p](...args); // trying to call JavaScript method + } + if (klass == null) { + throw new Error("Cannot find class for this object for invoking"); + } + let method = klass.get(p); + if (typeof method === "function") { + return method(rawValue, ...args); + } else { + throw new Error("Method for invoking is not a function"); + } +}; + // special procs have to be defined manually export let reduce = foldl; export let conj = append; diff --git a/package.json b/package.json index a7868f41..ba208104 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@calcit/procs", - "version": "0.3.20", + "version": "0.3.21", "main": "./lib/calcit.procs.js", "devDependencies": { "@types/node": "^15.0.1", diff --git a/src/bin/cr.rs b/src/bin/cr.rs index 231557f4..4cffdf91 100644 --- a/src/bin/cr.rs +++ b/src/bin/cr.rs @@ -63,6 +63,15 @@ fn main() -> Result<(), String> { .unwrap(); let emit_path = cli_matches.value_of("emit-path").or(Some("js-out")).unwrap(); + // make sure builtin classes are touched + runner::preprocess::preprocess_ns_def( + &calcit_runner::primes::CORE_NS, + &calcit_runner::primes::BUILTIN_CLASSES_ENTRY, + &program_code, + &calcit_runner::primes::BUILTIN_CLASSES_ENTRY, + None, + )?; + let task = if cli_matches.is_present("emit-js") { run_codegen(&init_fn, &reload_fn, &program_code, &emit_path, false) } else if cli_matches.is_present("emit-ir") { diff --git a/src/builtins.rs b/src/builtins.rs index 32badf10..31fd799f 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -277,7 +277,7 @@ pub fn handle_proc(name: &str, args: &CalcitItems) -> Result { "get-record-name" => records::get_record_name(args), "turn-map" => records::turn_map(args), "relevant-record?" => records::relevant_record_ques(args), - a => Err(format!("TODO proc: {}", a)), + a => Err(format!("No such proc: {}", a)), } } @@ -355,5 +355,6 @@ pub fn is_js_syntax_procs(s: &str) -> bool { | "to-calcit-data" | "to-cirru-edn" | "to-js-data" + | "invoke-method" // dynamically ) } diff --git a/src/builtins/meta.rs b/src/builtins/meta.rs index 21d83091..8709eab8 100644 --- a/src/builtins/meta.rs +++ b/src/builtins/meta.rs @@ -1,8 +1,12 @@ +use crate::builtins; +use crate::builtins::records::find_in_fields; use crate::call_stack; use crate::data::cirru; use crate::data::edn; use crate::primes; use crate::primes::{Calcit, CalcitItems, CrListWrap}; +use crate::program; +use crate::runner; use crate::util::number::f64_to_usize; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -187,8 +191,91 @@ pub fn turn_keyword(xs: &CalcitItems) -> Result { pub fn new_tuple(xs: &CalcitItems) -> Result { if xs.len() != 2 { - Err(format!("Tuple expected 2 arguments, got {}", CrListWrap(xs.to_owned()))) + Err(format!("tuple expected 2 arguments, got {}", CrListWrap(xs.to_owned()))) } else { Ok(Calcit::Tuple(Box::new(xs[0].to_owned()), Box::new(xs[1].to_owned()))) } } + +pub fn invoke_method( + name: &str, + invoke_args: &CalcitItems, + program_code: &program::ProgramCodeData, +) -> Result { + let (class, raw_value) = match invoke_args.get(0) { + Some(Calcit::Tuple(a, b)) => ((**a).to_owned(), (**b).to_owned()), + Some(Calcit::Number(..)) => { + // classed should already be preprocessed + let code = gen_sym("&core-number-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + Some(Calcit::Str(..)) => { + let code = gen_sym("&core-string-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + Some(Calcit::Set(..)) => { + let code = gen_sym("&core-set-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + Some(Calcit::List(..)) => { + let code = gen_sym("&core-list-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + Some(Calcit::Map(..)) => { + let code = gen_sym("&core-map-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + Some(Calcit::Record(..)) => { + let code = gen_sym("&core-record-class"); + let class = runner::evaluate_expr(&code, &im::HashMap::new(), primes::CORE_NS, program_code)?; + (class, invoke_args[0].to_owned()) + } + x => return Err(format!("cannot decide a class from: {:?}", x)), + }; + match &class { + Calcit::Record(_, fields, values) => { + match find_in_fields(&fields, name) { + Some(idx) => { + let mut method_args: im::Vector = im::vector![]; + method_args.push_back(raw_value.to_owned()); + let mut at_first = true; + for x in invoke_args { + if at_first { + at_first = false + } else { + method_args.push_back(x.to_owned()) + } + } + + match &values[idx] { + // dirty copy... + Calcit::Fn(_, def_ns, _, def_scope, args, body) => { + runner::run_fn(&method_args, def_scope, args, body, def_ns, program_code) + } + Calcit::Proc(proc) => builtins::handle_proc(&proc, &method_args), + y => Err(format!("expected a function to invoke, got: {}", y)), + } + } + None => Err(format!("missing field `{}` in {:?}", name, fields)), + } + } + x => Err(format!("method invoking expected a record as class, got: {}", x)), + } +} + +fn gen_sym(sym: &str) -> Calcit { + Calcit::Symbol( + String::from("&core-map-class"), + String::from(primes::CORE_NS), + Some(primes::SymbolResolved::ResolvedDef( + String::from(primes::CORE_NS), + String::from(sym), + None, + )), + ) +} diff --git a/src/builtins/records.rs b/src/builtins/records.rs index 37682e33..d7c21e35 100644 --- a/src/builtins/records.rs +++ b/src/builtins/records.rs @@ -134,6 +134,9 @@ pub fn relevant_record_ques(xs: &CalcitItems) -> Result { } pub fn find_in_fields(xs: &[String], y: &str) -> Option { + if xs.is_empty() { + return None; + } let mut lower = 0; let mut upper = xs.len() - 1; diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index 5caefc21..bc3b20ee 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -1078,6 +1078,11 @@ quote-replace new-record (quote ~name) ~@xs + |defrecord! $ quote + defmacro defrecord! (name & pairs) + quasiquote + %{} (new-record (quote ~name) (~@ (map pairs first))) ~@pairs + |;nil $ quote defmacro ;nil (& _body) nil @@ -1133,3 +1138,122 @@ f $ &get proto name assert "|expected function" (fn? f) f (nth pair 1) & params + + |&core-number-class $ quote + defrecord! &core-number-class + :ceil ceil + :floor floor + :format format-number + :inc inc + :pow pow + :round round + :sqrt sqrt + + |&core-string-class $ quote + defrecord! &core-string-class + :blank? blank? + :count count + :empty empty + :ends-with? ends-with? + :get nth + :parse-float parse-float + :parse-json parse-json + :replace replace + :split split + :split-lines split-lines + :starts-with? starts-with? + :strip-prefix strip-prefix + :strip-suffix strip-suffix + :substr substr + :trim trim + + |&core-set-class $ quote + defrecord! &core-set-class + :add coll-append + :difference difference + :exclude exclude + :empty empty + :empty? empty? + :include include + :includes? includes? + :intersection intersection + :to-list set->list + :union union + + |&core-map-class $ quote + defrecord! &core-map-class + :assoc assoc + :contains? contains? + :count count + :dissoc dissoc + :empty empty + :empty? empty? + :get &get + :get-in get-in + :includes? includes? + :keys keys + :keys-non-nil keys-non-nil + :map-kv map-kv + :merge merge + :select-keys select-keys + :to-pairs to-pairs + :unselect-keys unselect-keys + + |&core-record-class $ quote + defrecord! &core-record-class + :get-name get-record-name + :same-kind? relevant-record? + :turn-map turn-map + + |&core-list-class $ quote + defrecord! &core-list-class + :any? any? + :add coll-append + :append append + :assoc assoc + :assoc-after assoc-after + :assoc-before assoc-before + :butlast butlast + :concat concat + :count count + :drop drop + :each each + :empty empty + :empty? empty? + :filter filter + :filter-not filter-not + :find-index find-index + :foldl foldl + :frequencies frequencies + :get nth + :get-in get-in + :group-by group-by + :has-index? has-index? + :index-of index-of + :interleave interleave + :join join + :map map + :map-indexed map-indexed + :max max + :min min + :nth nth + :pairs-map pairs-map + :prepend prepend + :reduce reduce + :rest rest + :reverse reverse + :section-by section-by + :slice slice + :sort sort + :take take + :zipmap zipmap + + |&init-builtin-classes! $ quote + defn &init-builtin-classes! () + ; "this function to make sure builtin classes are loaded" + identity &core-number-class + identity &core-string-class + identity &core-set-class + identity &core-list-class + identity &core-map-class + identity &core-record-class diff --git a/src/codegen/emit_js.rs b/src/codegen/emit_js.rs index 04f30df1..4c2106fd 100644 --- a/src/codegen/emit_js.rs +++ b/src/codegen/emit_js.rs @@ -160,6 +160,8 @@ fn quote_to_js(xs: &Calcit, var_prefix: &str) -> Result { Calcit::Bool(b) => Ok(b.to_string()), Calcit::Number(n) => Ok(n.to_string()), Calcit::Nil => Ok(String::from("null")), + // mainly for methods, which are recognized during reading + Calcit::Proc(p) => Ok(format!("new {}CrDataSymbol({})", var_prefix, escape_cirru_str(&p))), Calcit::List(ys) => { let mut chunk = String::from(""); for y in ys { @@ -386,8 +388,9 @@ fn gen_call_code( } } } - _ if s.starts_with('.') => { - let name = s.strip_prefix('.').unwrap(); + _ if s.starts_with(".!") => { + // special syntax for calling a static method, previously using `.` but now occupied + let name = s.strip_prefix(".!").unwrap(); if matches_js_var(name) { match body.get(0) { Some(obj) => { @@ -406,6 +409,27 @@ fn gen_call_code( Err(format!("invalid member accessor {}", s)) } } + _ if s.starts_with('.') => { + let name = s.strip_prefix('.').unwrap(); + if matches_js_var(name) { + match body.get(0) { + Some(obj) => { + let args = body.skip(1); + let args_code = gen_args_code(&args, ns, local_defs, file_imports)?; + Ok(format!( + "{}invoke_method({},{},{})", + var_prefix, + escape_cirru_str(&name), // TODO need confirm + to_js_code(&obj, ns, local_defs, file_imports)?, + args_code + )) + } + None => Err(format!("expected 1 object, got {}", xs)), + } + } else { + Err(format!("invalid member accessor {}", s)) + } + } _ => { // TODO let args_code = gen_args_code(&body, ns, &local_defs, file_imports)?; @@ -939,6 +963,7 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { let mut defs_code = String::from(""); // code generated by functions let mut vals_code = String::from(""); // code generated by thunks + let mut direct_code = String::from(""); // dirty code to run directly let mut import_code = if ns == "calcit.core" { snippets::tmpl_import_procs(wrap_js_str("@calcit/procs")) @@ -1010,6 +1035,11 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { println!("[Warn] strange case for generating a definition: {}", f) } } + + if ns == primes::CORE_NS { + // add at end of file to register builtin classes + direct_code.push_str(&snippets::tmpl_classes_registering()) + } } let collected_imports = file_imports.into_inner(); @@ -1057,7 +1087,10 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { } let js_file_path = code_emit_path.join(to_js_file_name(&ns, false)); // TODO mjs_mode - let wrote_new = write_file_if_changed(&js_file_path, &format!("{}\n{}\n{}", import_code, defs_code, vals_code)); + let wrote_new = write_file_if_changed( + &js_file_path, + &format!("{}\n{}\n{}\n{}", import_code, defs_code, vals_code, direct_code), + ); if wrote_new { println!("Emitted js file: {}", js_file_path.to_str().unwrap()); } else { diff --git a/src/codegen/emit_js/snippets.rs b/src/codegen/emit_js/snippets.rs index 6bbe406f..e52167ba 100644 --- a/src/codegen/emit_js/snippets.rs +++ b/src/codegen/emit_js/snippets.rs @@ -97,7 +97,7 @@ pub fn tmpl_tail_recursion( pub fn tmpl_import_procs(name: String) -> String { format!( " -import {{kwd, arrayToList, listToArray, CrDataRecur}} from {}; +import {{kwd, arrayToList, listToArray, CrDataList, CrDataSymbol, CrDataRecur}} from {}; import * as $calcit_procs from {}; export * from {}; ", @@ -114,3 +114,18 @@ export var {} = () => {{/* Macro */}} name, name ) } + +pub fn tmpl_classes_registering() -> String { + String::from( + " +$calcit_procs.register_calcit_builtin_classes({ + list: _AND_core_list_class, + map: _AND_core_map_class, + number: _AND_core_number_class, + record: _AND_core_record_class, + set: _AND_core_set_class, + string: _AND_core_string_class, +}); +", + ) +} diff --git a/src/data/cirru.rs b/src/data/cirru.rs index 26b56ef0..acafb5ae 100644 --- a/src/data/cirru.rs +++ b/src/data/cirru.rs @@ -17,11 +17,19 @@ pub fn code_to_calcit(xs: &Cirru, ns: &str) -> Result { _ => match s.chars().next().unwrap() { ':' => { if s == "::" { - Ok(Calcit::Proc(String::from("::"))) // special tuple syntax + Ok(Calcit::Symbol(s.clone(), ns.to_owned(), None)) // special tuple syntax } else { Ok(Calcit::Keyword(String::from(&s[1..]))) } } + '.' => { + if s.starts_with(".-") || s.starts_with(".!") { + // try not to break js interop + Ok(Calcit::Symbol(s.clone(), ns.to_owned(), None)) + } else { + Ok(Calcit::Proc(s.to_owned())) // as native method syntax + } + } '"' | '|' => Ok(Calcit::Str(String::from(&s[1..]))), '0' if s.starts_with("0x") => match u32::from_str_radix(&s[2..], 16) { Ok(n) => Ok(Calcit::Number(n as f64)), diff --git a/src/primes.rs b/src/primes.rs index 247a0ee4..946af0dc 100644 --- a/src/primes.rs +++ b/src/primes.rs @@ -434,6 +434,7 @@ impl PartialEq for Calcit { } pub const CORE_NS: &str = "calcit.core"; +pub const BUILTIN_CLASSES_ENTRY: &str = "&init-builtin-classes!"; pub const GENERATED_NS: &str = "calcit.gen"; impl Calcit { diff --git a/src/runner.rs b/src/runner.rs index 34643445..cf6aa015 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -58,8 +58,12 @@ pub fn evaluate_expr( let values = evaluate_args(&rest_nodes, scope, file_ns, program_code)?; push_call_stack(file_ns, &p, StackKind::Proc, Calcit::Nil, &values); added_stack = true; - // println!("proc: {}", expr); - builtins::handle_proc(&p, &values) + if p.starts_with('.') { + builtins::meta::invoke_method(&p.strip_prefix('.').unwrap(), &values, program_code) + } else { + // println!("proc: {}", expr); + builtins::handle_proc(&p, &values) + } } Calcit::Syntax(s, def_ns) => { push_call_stack(file_ns, &s, StackKind::Syntax, expr.to_owned(), &rest_nodes); diff --git a/src/runner/preprocess.rs b/src/runner/preprocess.rs index 17f9fbe1..a5b4b37c 100644 --- a/src/runner/preprocess.rs +++ b/src/runner/preprocess.rs @@ -7,7 +7,7 @@ use crate::runner; use std::collections::HashSet; /// returns the resolved symbol, -/// if code related is not preprocessed, do it internal +/// if code related is not preprocessed, do it internally pub fn preprocess_ns_def( ns: &str, def: &str, From f0640e2bb8f56e9a2b89f30752f1f718ee179b8b Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Sat, 29 May 2021 22:51:07 +0800 Subject: [PATCH 3/7] fix duplication in classes registering; bump 0.3.22 --- Cargo.lock | 2 +- Cargo.toml | 2 +- calcit/calcit.cirru | 24 ++++++++++++++++++++++++ calcit/compact.cirru | 7 +++++-- package.json | 2 +- src/codegen/emit_js.rs | 9 ++++----- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc8a6e03..b34986d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "calcit_runner" -version = "0.3.21" +version = "0.3.22" dependencies = [ "chrono", "cirru_edn", diff --git a/Cargo.toml b/Cargo.toml index d013fc66..34ec0013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calcit_runner" -version = "0.3.21" +version = "0.3.22" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/calcit/calcit.cirru b/calcit/calcit.cirru index 11f51af6..59bfc538 100644 --- a/calcit/calcit.cirru +++ b/calcit/calcit.cirru @@ -100,6 +100,10 @@ :data $ {} |T $ {} (:type :leaf) (:by |u0) (:at 1619930582609) (:text |fib) |j $ {} (:type :leaf) (:by |u0) (:at 1619930582609) (:text |10) + |D $ {} (:type :leaf) (:by |u0) (:at 1622292794753) (:text |;) + |yT $ {} (:type :expr) (:by |u0) (:at 1622292783688) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292787836) (:text |try-method) |demos $ {} (:type :expr) (:by |u0) (:at 1618539520156) :data $ {} |yyyr $ {} (:type :expr) (:by |u0) (:at 1618681700994) @@ -457,6 +461,22 @@ :data $ {} |T $ {} (:type :leaf) (:by |u0) (:at 1618769533874) (:text |echo) |j $ {} (:type :leaf) (:by |u0) (:at 1618769535535) (:text "|\"many...") + |try-method $ {} (:type :expr) (:by |u0) (:at 1622292801677) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292802864) (:text |defn) + |j $ {} (:type :leaf) (:by |u0) (:at 1622292801677) (:text |try-method) + |r $ {} (:type :expr) (:by |u0) (:at 1622292801677) + :data $ {} + |v $ {} (:type :expr) (:by |u0) (:at 1622292803720) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292805545) (:text |println) + |j $ {} (:type :expr) (:by |u0) (:at 1622292805914) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292806869) (:text |.count) + |j $ {} (:type :expr) (:by |u0) (:at 1622292809130) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292811398) (:text |range) + |j $ {} (:type :leaf) (:by |u0) (:at 1622292816464) (:text |11) |rec-sum $ {} (:type :expr) (:by |u0) (:at 1618723127970) :data $ {} |T $ {} (:type :leaf) (:by |u0) (:at 1618723127970) (:text |defn) @@ -591,6 +611,10 @@ :data $ {} |T $ {} (:type :leaf) (:by |u0) (:at 1619930544016) (:text |fib) |j $ {} (:type :leaf) (:by |u0) (:at 1619935071727) (:text |40) + |D $ {} (:type :leaf) (:by |u0) (:at 1622292791514) (:text |;) + |y $ {} (:type :expr) (:by |u0) (:at 1622292799913) + :data $ {} + |T $ {} (:type :leaf) (:by |u0) (:at 1622292800206) (:text |try-method) |add-more $ {} (:type :expr) (:by |u0) (:at 1618730350902) :data $ {} |T $ {} (:type :leaf) (:by |u0) (:at 1618730354052) (:text |defmacro) diff --git a/calcit/compact.cirru b/calcit/compact.cirru index 98021613..2417e13c 100644 --- a/calcit/compact.cirru +++ b/calcit/compact.cirru @@ -17,7 +17,7 @@ |call-3 $ quote defn call-3 (a b c) (echo "\"a is:" a) (echo "\"b is:" b) (echo "\"c is:" c) |main! $ quote - defn main! () (demos) (fib 10) + defn main! () (demos) (; fib 10) (try-method) |demos $ quote defn demos () (echo "\"demo") echo $ &+ 2 2 @@ -57,6 +57,9 @@ test-args |call-many $ quote defn call-many (x0 & xs) (echo "\"many...") (echo "\"x0" x0) (echo "\"xs" xs) + |try-method $ quote + defn try-method () $ println + .count $ range 11 |rec-sum $ quote defn rec-sum (acc xs) if (empty? xs) acc $ recur @@ -77,7 +80,7 @@ |f1 $ quote defn f1 () $ echo "\"calling f1" |reload! $ quote - defn reload! () (println "\"reloaded 2") (fib 40) + defn reload! () (println "\"reloaded 2") (; fib 40) (try-method) |add-more $ quote defmacro add-more (acc x times) if (&< times 1) acc $ recur diff --git a/package.json b/package.json index ba208104..79e9de29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@calcit/procs", - "version": "0.3.21", + "version": "0.3.22", "main": "./lib/calcit.procs.js", "devDependencies": { "@types/node": "^15.0.1", diff --git a/src/codegen/emit_js.rs b/src/codegen/emit_js.rs index 4c2106fd..136933c3 100644 --- a/src/codegen/emit_js.rs +++ b/src/codegen/emit_js.rs @@ -1035,11 +1035,10 @@ pub fn emit_js(entry_ns: &str, emit_path: &str) -> Result<(), String> { println!("[Warn] strange case for generating a definition: {}", f) } } - - if ns == primes::CORE_NS { - // add at end of file to register builtin classes - direct_code.push_str(&snippets::tmpl_classes_registering()) - } + } + if ns == primes::CORE_NS { + // add at end of file to register builtin classes + direct_code.push_str(&snippets::tmpl_classes_registering()) } let collected_imports = file_imports.into_inner(); From 722dfa587b629048117ba451a366b0705c1ba1a6 Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Sun, 30 May 2021 02:08:22 +0800 Subject: [PATCH 4/7] adding some cases and fixed several edge cases; bump 0.3.23 --- Cargo.lock | 2 +- Cargo.toml | 2 +- calcit/snapshots/test-list.cirru | 14 ++++++ calcit/snapshots/test-map.cirru | 14 ++++++ calcit/snapshots/test-math.cirru | 9 ++++ calcit/snapshots/test-record.cirru | 9 ++++ calcit/snapshots/test-set.cirru | 7 +++ calcit/snapshots/test-string.cirru | 5 +- calcit/snapshots/test.cirru | 16 +++---- lib/calcit.procs.ts | 76 +++++++++++++++++------------- lib/record-procs.ts | 4 +- package.json | 2 +- src/cirru/calcit-core.cirru | 1 + src/codegen/emit_js.rs | 47 +++++++++++------- 14 files changed, 143 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b34986d5..4faf6b7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "calcit_runner" -version = "0.3.22" +version = "0.3.23" dependencies = [ "chrono", "cirru_edn", diff --git a/Cargo.toml b/Cargo.toml index 34ec0013..0c30e240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calcit_runner" -version = "0.3.22" +version = "0.3.23" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/calcit/snapshots/test-list.cirru b/calcit/snapshots/test-list.cirru index e33009e4..acf7f4d4 100644 --- a/calcit/snapshots/test-list.cirru +++ b/calcit/snapshots/test-list.cirru @@ -294,6 +294,18 @@ log-title "|Testing alias" assert= (' 1 2 3) ([] 1 2 3) + |test-methods $ quote + fn () + log-title "|Testing list methods" + assert= 3 + .count $ [] 1 2 3 + assert= + [] 4 5 6 + .map ([] 1 2 3) $ fn (x) (+ x 3) + assert= + [] 2 3 4 + .map ([] 1 2 3) .inc + |main! $ quote defn main! () @@ -326,6 +338,8 @@ test-doseq test-let[] + test-methods + do true :proc $ quote () diff --git a/calcit/snapshots/test-map.cirru b/calcit/snapshots/test-map.cirru index e3a2d073..65653347 100644 --- a/calcit/snapshots/test-map.cirru +++ b/calcit/snapshots/test-map.cirru @@ -173,7 +173,19 @@ unselect-keys ({} (:a 1) (:b 2) (:c 3)) ([] :c :d) {} (:a 1) (:b 2) + |test-methods $ quote + fn () + log-title "|Testing map methods" + + assert= 2 + .count $ {} (:a 1) (:b 2) + assert= + {} (:a 11) + .map-kv ({} (:a 1)) $ fn (k v) + [] k (+ v 10) + |main! $ quote + defn main! () log-title "|Testing maps" @@ -193,6 +205,8 @@ test-select + test-methods + do true :proc $ quote () diff --git a/calcit/snapshots/test-math.cirru b/calcit/snapshots/test-math.cirru index 503a7818..8c005de3 100644 --- a/calcit/snapshots/test-math.cirru +++ b/calcit/snapshots/test-math.cirru @@ -74,6 +74,13 @@ assert= true (integer? 1) assert= false (integer? 1.1) + |test-methods $ quote + fn () + log-title "|Testing number methods" + + assert= 1 $ .floor 1.1 + assert= 16 $ .pow 2 4 + |main! $ quote defn main! () log-title "|Testing numbers" @@ -89,6 +96,8 @@ test-integer + test-methods + do true :proc $ quote () diff --git a/calcit/snapshots/test-record.cirru b/calcit/snapshots/test-record.cirru index 25c4df20..7b58c930 100644 --- a/calcit/snapshots/test-record.cirru +++ b/calcit/snapshots/test-record.cirru @@ -92,10 +92,19 @@ |Cat $ quote defrecord Cat :name :color + |test-methods $ quote + fn () + log-title "|Testing record methods" + + assert= |Cat + .get-name Cat + |main! $ quote defn main! () test-record + test-methods + do true :proc $ quote () diff --git a/calcit/snapshots/test-set.cirru b/calcit/snapshots/test-set.cirru index fb73f298..a87f12be 100644 --- a/calcit/snapshots/test-set.cirru +++ b/calcit/snapshots/test-set.cirru @@ -53,11 +53,18 @@ assert= (#{} 1 2 3) (#{} 1 2 (+ 1 2)) assert-detect not $ = (#{} 1 2 3) (#{} 2 3 4) + |test-methods $ quote + fn () + assert= 3 + .count $ #{} 1 2 3 + |main! $ quote defn main! () log-title "|Testing set" test-set + test-methods + do true :proc $ quote () diff --git a/calcit/snapshots/test-string.cirru b/calcit/snapshots/test-string.cirru index 1ae5b586..6cf2391d 100644 --- a/calcit/snapshots/test-string.cirru +++ b/calcit/snapshots/test-string.cirru @@ -193,9 +193,12 @@ |test-methods $ quote defn test-methods () - log-title "|Testing methods" + log-title "|Testing string methods" assert= 3 (.count |abc) + assert= + [] |a |c + .split |abc |b |main! $ quote defn main! () diff --git a/calcit/snapshots/test.cirru b/calcit/snapshots/test.cirru index ad3c1133..cae6cd3d 100644 --- a/calcit/snapshots/test.cirru +++ b/calcit/snapshots/test.cirru @@ -227,20 +227,20 @@ defrecord %Num :inc :show |Num $ quote def Num $ %{} %Num - :inc $ fn (x) $ [] Num (&+ x 1) + :inc $ fn (x) $ :: Num (&+ x 1) :show $ fn (x) $ str x - |test-invoke $ quote + |test-method $ quote fn () - log-title "|Testing invoke" + log-title "|Testing method" let - a $ [] Num 0 + a $ :: Num 0 assert= - [] Num 2 - -> a (invoke :inc) (invoke :inc) + :: Num 2 + -> a .inc .inc assert= |1 - -> a (invoke :inc) (invoke :show) + -> a .inc .show |test-tuple $ quote fn () @@ -286,7 +286,7 @@ test-refs - test-invoke + test-method test-tuple diff --git a/lib/calcit.procs.ts b/lib/calcit.procs.ts index 2da095bc..79e9d4ba 100644 --- a/lib/calcit.procs.ts +++ b/lib/calcit.procs.ts @@ -352,7 +352,13 @@ export let _AND__EQ_ = (x: CrDataValue, y: CrDataValue): boolean => { } if (x instanceof CrDataRef) { if (y instanceof CrDataRef) { - return x === y; + return x.path === y.path; + } + return false; + } + if (x instanceof CrDataTuple) { + if (y instanceof CrDataTuple) { + return _AND__EQ_(x.fst, y.fst) && _AND__EQ_(x.snd, y.snd); } return false; } @@ -1758,39 +1764,43 @@ export let register_calcit_builtin_classes = (options: typeof calcit_builtin_cla Object.assign(calcit_builtin_classes, options); }; -export let invoke_method = (p: string, obj: CrDataValue, ...args: CrDataValue[]) => { - let klass: CrDataRecord; - let rawValue = obj; - if (obj instanceof CrDataTuple) { - if (obj.fst instanceof CrDataRecord) { - klass = obj.fst; - rawValue = obj.snd; +export let invoke_method = + (p: string) => + (obj: CrDataValue, ...args: CrDataValue[]) => { + let klass: CrDataRecord; + let rawValue = obj; + if (obj instanceof CrDataTuple) { + if (obj.fst instanceof CrDataRecord) { + klass = obj.fst; + rawValue = obj.snd; + } else { + throw new Error("Method invoking expected a record as class"); + } + } else if (typeof obj === "number") { + klass = calcit_builtin_classes.number; + } else if (typeof obj === "string") { + klass = calcit_builtin_classes.string; + } else if (obj instanceof CrDataSet) { + klass = calcit_builtin_classes.set; + } else if (obj instanceof CrDataList) { + klass = calcit_builtin_classes.list; + } else if (obj instanceof CrDataRecord) { + klass = calcit_builtin_classes.record; + } else if (obj instanceof CrDataMap) { + klass = calcit_builtin_classes.map; } else { - throw new Error("Method invoking expected a record as class"); - } - } else if (typeof obj === "number") { - klass = calcit_builtin_classes.number; - } else if (typeof obj === "string") { - klass = calcit_builtin_classes.string; - } else if (obj instanceof CrDataSet) { - klass = calcit_builtin_classes.set; - } else if (obj instanceof CrDataList) { - klass = calcit_builtin_classes.list; - } else if (obj instanceof CrDataRecord) { - klass = calcit_builtin_classes.record; - } else { - return (obj as any)[p](...args); // trying to call JavaScript method - } - if (klass == null) { - throw new Error("Cannot find class for this object for invoking"); - } - let method = klass.get(p); - if (typeof method === "function") { - return method(rawValue, ...args); - } else { - throw new Error("Method for invoking is not a function"); - } -}; + return (obj as any)[p](...args); // trying to call JavaScript method + } + if (klass == null) { + throw new Error("Cannot find class for this object for invoking"); + } + let method = klass.get(p); + if (typeof method === "function") { + return method(rawValue, ...args); + } else { + throw new Error("Method for invoking is not a function"); + } + }; // special procs have to be defined manually export let reduce = foldl; diff --git a/lib/record-procs.ts b/lib/record-procs.ts index 5afaba94..bdb5bf8d 100644 --- a/lib/record-procs.ts +++ b/lib/record-procs.ts @@ -70,9 +70,9 @@ export let _AND__PCT__MAP_ = (proto: CrDataValue, ...xs: Array): Cr } }; -export let get_record_name = (x: CrDataRecord): CrDataSymbol => { +export let get_record_name = (x: CrDataRecord): string => { if (x instanceof CrDataRecord) { - return new CrDataSymbol(x.name); + return x.name; } else { throw new Error("Expected a record"); } diff --git a/package.json b/package.json index 79e9de29..425517a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@calcit/procs", - "version": "0.3.22", + "version": "0.3.23", "main": "./lib/calcit.procs.js", "devDependencies": { "@types/node": "^15.0.1", diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index bc3b20ee..e8650405 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -1170,6 +1170,7 @@ |&core-set-class $ quote defrecord! &core-set-class :add coll-append + :count count :difference difference :exclude exclude :empty empty diff --git a/src/codegen/emit_js.rs b/src/codegen/emit_js.rs index 136933c3..eb2f0d69 100644 --- a/src/codegen/emit_js.rs +++ b/src/codegen/emit_js.rs @@ -207,7 +207,22 @@ fn to_js_code( // println!("gen proc {} under {}", s, ns,); // let resolved = Some(ResolvedDef(String::from(primes::CORE_NS), s.to_owned())); // gen_symbol_code(s, primes::CORE_NS, &resolved, ns, xs, local_defs) - Ok(format!("{}{}", proc_prefix, escape_var(s))) + + if s.starts_with('.') { + if s.starts_with(".-") || s.starts_with(".!") { + Err(format!("invalid js method {} at this position", s)) + } else { + // `.method` being used as a parameter + let name = s.strip_prefix('.').unwrap(); + Ok(format!( + "{}invoke_method({})", + var_prefix, + escape_cirru_str(&name), // TODO need confirm + )) + } + } else { + Ok(format!("{}{}", proc_prefix, escape_var(s))) + } } Calcit::Syntax(s, ..) => { let resolved = Some(ResolvedDef(String::from(primes::CORE_NS), s.to_owned(), None)); @@ -406,28 +421,24 @@ fn gen_call_code( None => Err(format!("expected 1 object, got {}", xs)), } } else { - Err(format!("invalid member accessor {}", s)) + Err(format!("invalid static member accessor {}", s)) } } _ if s.starts_with('.') => { let name = s.strip_prefix('.').unwrap(); - if matches_js_var(name) { - match body.get(0) { - Some(obj) => { - let args = body.skip(1); - let args_code = gen_args_code(&args, ns, local_defs, file_imports)?; - Ok(format!( - "{}invoke_method({},{},{})", - var_prefix, - escape_cirru_str(&name), // TODO need confirm - to_js_code(&obj, ns, local_defs, file_imports)?, - args_code - )) - } - None => Err(format!("expected 1 object, got {}", xs)), + match body.get(0) { + Some(obj) => { + let args = body.skip(1); + let args_code = gen_args_code(&args, ns, local_defs, file_imports)?; + Ok(format!( + "{}invoke_method({})({},{})", + var_prefix, + escape_cirru_str(&name), // TODO need confirm + to_js_code(&obj, ns, local_defs, file_imports)?, + args_code + )) } - } else { - Err(format!("invalid member accessor {}", s)) + None => Err(format!("expected 1 object, got {}", xs)), } } _ => { From 8aa0989db273952c02b8109163d3e731846db8b3 Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Mon, 31 May 2021 02:27:31 +0800 Subject: [PATCH 5/7] add .to-list , add .sort-by --- calcit/snapshots/test-list.cirru | 8 ++++++++ calcit/snapshots/test-map.cirru | 10 ++++++++++ lib/calcit.procs.ts | 22 ++++++++++++++++++++++ src/builtins.rs | 6 +++++- src/builtins/maps.rs | 17 +++++++++++++++++ src/builtins/meta.rs | 25 +++++++++++++++++++------ src/cirru/calcit-core.cirru | 12 ++++++++++++ 7 files changed, 93 insertions(+), 7 deletions(-) diff --git a/calcit/snapshots/test-list.cirru b/calcit/snapshots/test-list.cirru index acf7f4d4..b4deead1 100644 --- a/calcit/snapshots/test-list.cirru +++ b/calcit/snapshots/test-list.cirru @@ -306,6 +306,14 @@ [] 2 3 4 .map ([] 1 2 3) .inc + assert= + [] 4 3 2 1 + .sort-by ([] 1 2 3 4) negate + + assert= + [] 1 2 3 4 + .sort-by ([] 1 2 3 4) inc + |main! $ quote defn main! () diff --git a/calcit/snapshots/test-map.cirru b/calcit/snapshots/test-map.cirru index 65653347..4dde298e 100644 --- a/calcit/snapshots/test-map.cirru +++ b/calcit/snapshots/test-map.cirru @@ -183,6 +183,16 @@ {} (:a 11) .map-kv ({} (:a 1)) $ fn (k v) [] k (+ v 10) + + assert= + [] ([] :a 1) + .to-list $ {} (:a 1) + + assert= 2 + .count $ .to-list $ {} + :a 1 + :b 2 + |main! $ quote diff --git a/lib/calcit.procs.ts b/lib/calcit.procs.ts index 79e9d4ba..4d7eedb0 100644 --- a/lib/calcit.procs.ts +++ b/lib/calcit.procs.ts @@ -1802,6 +1802,28 @@ export let invoke_method = } }; +export let _AND_map_to_list = (m: CrDataValue): CrDataList => { + if (m instanceof CrDataMap) { + let ys = []; + for (let pair of m.pairs()) { + ys.push(new CrDataList(pair)); + } + return new CrDataList(ys); + } else { + throw new Error("&map-to-list expected a Map"); + } +}; + +export let _AND_compare = (a: CrDataValue, b: CrDataValue): number => { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } +}; + // special procs have to be defined manually export let reduce = foldl; export let conj = append; diff --git a/src/builtins.rs b/src/builtins.rs index 31fd799f..27d1a86c 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -34,6 +34,7 @@ pub fn is_proc_name(s: &str) -> bool { | "turn-symbol" | "turn-keyword" | "::" // unstable + | "&compare" // effects | "echo" | "println" // alias for echo @@ -122,6 +123,7 @@ pub fn is_proc_name(s: &str) -> bool { | "includes?" | "to-pairs" | "&merge-non-nil" + | "&map-to-list" // sets | "#{}" | "&include" @@ -164,7 +166,8 @@ pub fn handle_proc(name: &str, args: &CalcitItems) -> Result { "write-cirru-edn" => meta::write_cirru_edn(args), "turn-symbol" => meta::turn_symbol(args), "turn-keyword" => meta::turn_keyword(args), - "::" => meta::new_tuple(args), // unstable solution for the name + "::" => meta::new_tuple(args), // unstable solution for the name + "&compare" => meta::native_compare(args), // unstable solution for the name // effects "echo" => effects::echo(args), "println" => effects::echo(args), // alias @@ -255,6 +258,7 @@ pub fn handle_proc(name: &str, args: &CalcitItems) -> Result { "includes?" => maps::includes_ques(args), "to-pairs" => maps::to_pairs(args), "&merge-non-nil" => maps::call_merge_non_nil(args), + "&map-to-list" => maps::to_list(args), // sets "#{}" => sets::new_set(args), "&include" => sets::call_include(args), diff --git a/src/builtins/maps.rs b/src/builtins/maps.rs index 7480378b..2bbf9971 100644 --- a/src/builtins/maps.rs +++ b/src/builtins/maps.rs @@ -199,6 +199,7 @@ pub fn to_pairs(xs: &CalcitItems) -> Result { None => Err(String::from("to-pairs expected 1 argument, got nothing")), } } + pub fn call_merge_non_nil(xs: &CalcitItems) -> Result { match (xs.get(0), xs.get(1)) { (Some(Calcit::Map(xs)), Some(Calcit::Map(ys))) => { @@ -214,3 +215,19 @@ pub fn call_merge_non_nil(xs: &CalcitItems) -> Result { (_, _) => Err(format!("expected 2 arguments, got: {:?}", xs)), } } + +/// out to list, but with a arbitrary order +pub fn to_list(xs: &CalcitItems) -> Result { + match xs.get(0) { + Some(Calcit::Map(m)) => { + let mut ys: im::Vector = im::vector![]; + for (k, v) in m { + let zs: im::Vector = im::vector![k.to_owned(), v.to_owned()]; + ys.push_back(Calcit::List(zs)); + } + Ok(Calcit::List(ys)) + } + Some(a) => Err(format!("&map-to-list expected a map, got: {}", a)), + None => Err(String::from("&map-to-list expected a map, got nothing")), + } +} diff --git a/src/builtins/meta.rs b/src/builtins/meta.rs index 8709eab8..92292385 100644 --- a/src/builtins/meta.rs +++ b/src/builtins/meta.rs @@ -8,7 +8,9 @@ use crate::primes::{Calcit, CalcitItems, CrListWrap}; use crate::program; use crate::runner; use crate::util::number::f64_to_usize; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::cmp::Ordering; +use std::sync::atomic; +use std::sync::atomic::AtomicUsize; static SYMBOL_INDEX: AtomicUsize = AtomicUsize::new(0); static JS_SYMBOL_INDEX: AtomicUsize = AtomicUsize::new(0); @@ -52,7 +54,7 @@ pub fn format_to_lisp(xs: &CalcitItems) -> Result { } pub fn gensym(xs: &CalcitItems) -> Result { - let idx = SYMBOL_INDEX.fetch_add(1, Ordering::SeqCst); + let idx = SYMBOL_INDEX.fetch_add(1, atomic::Ordering::SeqCst); let n = idx + 1; // use 1 as first value since previous implementation did this let s = match xs.get(0) { @@ -74,22 +76,22 @@ pub fn gensym(xs: &CalcitItems) -> Result { } pub fn reset_gensym_index(_xs: &CalcitItems) -> Result { - let _ = SYMBOL_INDEX.swap(0, Ordering::SeqCst); + let _ = SYMBOL_INDEX.swap(0, atomic::Ordering::SeqCst); Ok(Calcit::Nil) } pub fn force_reset_gensym_index() -> Result<(), String> { - let _ = SYMBOL_INDEX.swap(0, Ordering::SeqCst); + let _ = SYMBOL_INDEX.swap(0, atomic::Ordering::SeqCst); Ok(()) } pub fn reset_js_gensym_index() { - let _ = JS_SYMBOL_INDEX.swap(0, Ordering::SeqCst); + let _ = JS_SYMBOL_INDEX.swap(0, atomic::Ordering::SeqCst); } // for emitting js pub fn js_gensym(name: &str) -> String { - let idx = JS_SYMBOL_INDEX.fetch_add(1, Ordering::SeqCst); + let idx = JS_SYMBOL_INDEX.fetch_add(1, atomic::Ordering::SeqCst); let n = idx + 1; // use 1 as first value since previous implementation did this let mut chunk = String::from(name); @@ -279,3 +281,14 @@ fn gen_sym(sym: &str) -> Calcit { )), ) } + +pub fn native_compare(xs: &CalcitItems) -> Result { + match (xs.get(0), xs.get(1)) { + (Some(a), Some(b)) => match a.cmp(b) { + Ordering::Less => Ok(Calcit::Number(-1.0)), + Ordering::Greater => Ok(Calcit::Number(1.0)), + Ordering::Equal => Ok(Calcit::Number(0.0)), + }, + (a, b) => Err(format!("&compare expected 2 values, got {:?} {:?}", a, b)), + } +} diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index e8650405..2530abed 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -1138,6 +1138,15 @@ f $ &get proto name assert "|expected function" (fn? f) f (nth pair 1) & params + + |&list-sort-by $ quote + defn &list-sort-by (xs f) + sort xs $ fn (a b) + &compare (f a) (f b) + + |negate $ quote + defn negate (x) + &- 0 x |&core-number-class $ quote defrecord! &core-number-class @@ -1197,8 +1206,10 @@ :map-kv map-kv :merge merge :select-keys select-keys + :to-list &map-to-list :to-pairs to-pairs :unselect-keys unselect-keys + :vals vals |&core-record-class $ quote defrecord! &core-record-class @@ -1246,6 +1257,7 @@ :section-by section-by :slice slice :sort sort + :sort-by &list-sort-by :take take :zipmap zipmap From efd51db0d2a086bb7142a9f1a951152d76ca880b Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Mon, 31 May 2021 12:18:54 +0800 Subject: [PATCH 6/7] make keyword callable inside Rust runtime --- calcit/snapshots/test.cirru | 6 ++++++ src/builtins/syntax.rs | 5 +---- src/runner.rs | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/calcit/snapshots/test.cirru b/calcit/snapshots/test.cirru index cae6cd3d..81376054 100644 --- a/calcit/snapshots/test.cirru +++ b/calcit/snapshots/test.cirru @@ -41,6 +41,12 @@ assert-detect not $ > :a :b assert-detect not $ > :aa :ab + inside-eval: + assert= ([] 1) + .map + [] $ &{} :a 1 + , :a + |test-id $ quote fn () assert= 9 $ count $ generate-id! 9 diff --git a/src/builtins/syntax.rs b/src/builtins/syntax.rs index 0e0ae2f9..cbfcf8b1 100644 --- a/src/builtins/syntax.rs +++ b/src/builtins/syntax.rs @@ -335,10 +335,7 @@ pub fn call_try( let values = im::vector![err_data]; runner::run_fn(&values, &def_scope, &args, &body, &def_ns, program_code) } - Calcit::Proc(proc) => { - println!("TRY"); - builtins::handle_proc(&proc, &im::vector![err_data]) - } + Calcit::Proc(proc) => builtins::handle_proc(&proc, &im::vector![err_data]), a => Err(format!("try expected a function handler, got: {}", a)), } } diff --git a/src/runner.rs b/src/runner.rs index cf6aa015..a55df663 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -105,6 +105,25 @@ pub fn evaluate_expr( } }) } + Calcit::Keyword(k) => { + if rest_nodes.len() == 1 { + let v = evaluate_expr(&rest_nodes[0], scope, file_ns, program_code)?; + + if let Calcit::Map(m) = v { + match m.get(&Calcit::Keyword(k.to_owned())) { + Some(value) => Ok(value.to_owned()), + None => Ok(Calcit::Nil), + } + } else { + Err(format!("expected a hashmap, got {}", v)) + } + } else { + Err(format!( + "keyword only takes 1 argument, got: {}", + CrListWrap(rest_nodes) + )) + } + } Calcit::Symbol(s, ns, resolved) => { Err(format!("cannot evaluate symbol directly: {}/{} {:?}", ns, s, resolved)) } From 1607b722b785b402c41a4e245d4fb73c429f8b91 Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Mon, 31 May 2021 12:23:32 +0800 Subject: [PATCH 7/7] support using keyword for sort-by --- calcit/snapshots/test-list.cirru | 12 ++++++++++++ src/cirru/calcit-core.cirru | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/calcit/snapshots/test-list.cirru b/calcit/snapshots/test-list.cirru index b4deead1..163c175e 100644 --- a/calcit/snapshots/test-list.cirru +++ b/calcit/snapshots/test-list.cirru @@ -313,6 +313,18 @@ assert= [] 1 2 3 4 .sort-by ([] 1 2 3 4) inc + + assert= + [] + {} (:v :a) (:n 1) + {} (:v :c) (:n 2) + {} (:v :b) (:n 3) + .sort-by + [] + {} (:v :a) (:n 1) + {} (:v :b) (:n 3) + {} (:v :c) (:n 2) + , :n |main! $ quote defn main! () diff --git a/src/cirru/calcit-core.cirru b/src/cirru/calcit-core.cirru index 2530abed..63b67018 100644 --- a/src/cirru/calcit-core.cirru +++ b/src/cirru/calcit-core.cirru @@ -1141,8 +1141,12 @@ |&list-sort-by $ quote defn &list-sort-by (xs f) - sort xs $ fn (a b) - &compare (f a) (f b) + if (keyword? f) + sort xs $ fn (a b) + &compare (&get a f) (&get b f) + + sort xs $ fn (a b) + &compare (f a) (f b) |negate $ quote defn negate (x)