Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new macro record-with with proc &record:with #246

Merged
merged 2 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "calcit"
version = "0.8.57"
version = "0.8.58"
authors = ["jiyinyiyong <[email protected]>"]
edition = "2021"
license = "MIT"
Expand Down
15 changes: 14 additions & 1 deletion calcit/test-record.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
def Lagopus $ new-class-record BirdClass :Lagopus :name
|main! $ %{} :CodeEntry (:doc |)
:code $ quote
defn main! () (test-record) (test-methods) (test-match) (test-polymorphism) (test-edn) (do true)
defn main! () (test-record) (test-methods) (test-match) (test-polymorphism) (test-edn) (test-record-with) (do true)
|test-edn $ %{} :CodeEntry (:doc |)
:code $ quote
fn ()
Expand Down Expand Up @@ -95,6 +95,19 @@
-> l1 (.rename |LagopusB) (.show)
assert= (&record:class l1)
&record:class $ &record:with-class a1 BirdClass
|test-record-with $ %{} :CodeEntry (:doc "|test record-with")
:code $ quote
fn () (log-title "|Testing record-with")
let
Person $ new-record :City :name :age :position
p1 $ %{} Person (:name |Chen) (:age 20) (:position :hangzhou)
p2 $ record-with p1 (:age 21) (:position :shanghai)
; println |P2 p2
assert= 20 $ get p1 :age
assert= 21 $ get p2 :age
assert= :hangzhou $ get p1 :position
assert= :shanghai $ get p2 :position
assert= |Chen $ get p2 :name
|test-record $ %{} :CodeEntry (:doc |)
:code $ quote
fn () (log-title "|Testing record")
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@calcit/procs",
"version": "0.8.57",
"version": "0.8.58",
"main": "./lib/calcit.procs.mjs",
"devDependencies": {
"@types/node": "^20.11.28",
Expand Down
1 change: 1 addition & 0 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ fn handle_proc_internal(name: CalcitProc, args: &[Calcit], call_stack: &CallStac
CalcitProc::NewRecord => records::new_record(args),
CalcitProc::NewClassRecord => records::new_class_record(args),
CalcitProc::NativeRecord => records::call_record(args),
CalcitProc::NativeRecordWith => records::record_with(args),
CalcitProc::NativeRecordClass => records::get_class(args),
CalcitProc::NativeRecordWithClass => records::with_class(args),
CalcitProc::NativeRecordFromMap => records::record_from_map(args),
Expand Down
53 changes: 53 additions & 0 deletions src/builtins/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,59 @@ pub fn call_record(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
}
}

/// takes a record and pairs of key value(flatterned), and update the record. raise error if key not existed in the record
pub fn record_with(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
let args_size = xs.len();
if args_size < 3 {
return CalcitErr::err_nodes("&record:with expected at least 3 arguments, got:", xs);
}
match &xs[0] {
Calcit::Record(
record @ CalcitRecord {
name,
fields: def_fields,
values: v0,
class,
},
) => {
if (args_size - 1).rem(2) == 0 {
let size = (args_size - 1) / 2;
let mut values: Vec<Calcit> = (**v0).to_owned();

for idx in 0..size {
let k_idx = idx * 2 + 1;
let v_idx = k_idx + 1;
match &xs[k_idx] {
Calcit::Tag(s) => match record.index_of(s.ref_str()) {
Some(pos) => {
xs[v_idx].clone_into(&mut values[pos]);
}
None => return CalcitErr::err_str(format!("unexpected field {s} for {def_fields:?}")),
},
Calcit::Symbol { sym: s, .. } | Calcit::Str(s) => match record.index_of(s) {
Some(pos) => {
xs[v_idx].clone_into(&mut values[pos]);
}
None => return CalcitErr::err_str(format!("unexpected field {s} for {def_fields:?}")),
},
a => return CalcitErr::err_str(format!("expected field in string/tag, got: {a}")),
}
}

Ok(Calcit::Record(CalcitRecord {
name: name.to_owned(),
fields: def_fields.to_owned(),
values: Arc::new(values),
class: class.to_owned(),
}))
} else {
CalcitErr::err_nodes("&record:with expected pairs, got:", xs)
}
}
a => CalcitErr::err_str(format!("&record:with expected a record as prototype, got: {a}")),
}
}

pub fn get_class(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
let args_size = xs.len();
if args_size != 1 {
Expand Down
2 changes: 2 additions & 0 deletions src/calcit/proc_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ pub enum CalcitProc {
NewClassRecord,
#[strum(serialize = "&%{}")]
NativeRecord,
#[strum(serialize = "&record:with")]
NativeRecordWith,
#[strum(serialize = "&record:class")]
NativeRecordClass,
#[strum(serialize = "&record:with-class")]
Expand Down
12 changes: 12 additions & 0 deletions src/cirru/calcit-core.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,18 @@
|' $ %{} :CodeEntry (:doc "|alias for []")
:code $ quote
def "'" []
|record-with $ %{} :CodeEntry (:doc "|macro to extend existing record with new values in pairs, internally using &record:with which takes flattern items")
:code $ quote
defmacro record-with (record & pairs)
; "check if args are in pairs"
if
not $ and (list? pairs)
every? pairs $ fn (xs)
and (list? xs) (&= 2 $ count xs)
raise $ str-spaced "|record-with expects a list of pairs (each with exactly two elements), got:" pairs
; "call &record:with"
quasiquote $ &record:with ~record $ ~@ $ &list:concat & pairs

:ns $ %{} :CodeEntry (:doc "|built-in function and macros in `calcit.core`")
:code $ quote
ns calcit.core $ :require
22 changes: 22 additions & 0 deletions ts-src/js-record.mts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ export let _$n__PCT__$M_ = (proto: CalcitValue, ...xs: Array<CalcitValue>): Calc
}
};

/// update record with new values
export let _$n_record_$o_with = (proto: CalcitValue, ...xs: Array<CalcitValue>): CalcitValue => {
if (proto instanceof CalcitRecord) {
if (xs.length % 2 !== 0) {
throw new Error("Expected even number of key/value");
}
let values = proto.values.slice();
for (let i = 0; i < xs.length; i += 2) {
let k = castTag(xs[i]);
let v = xs[i + 1];
let idx = findInFields(proto.fields, k);
if (idx < 0) {
throw new Error(`Cannot find field ${k} among ${proto.fields}`);
}
values[idx] = v;
}
return new CalcitRecord(proto.name, proto.fields, values, proto.klass);
} else {
throw new Error("Expected prototype to be a record");
}
};

export let _$n_record_$o_get_name = (x: CalcitRecord): CalcitTag => {
if (x instanceof CalcitRecord) {
return x.name;
Expand Down
Loading