Skip to content

Commit

Permalink
Merge up
Browse files Browse the repository at this point in the history
  • Loading branch information
prozacchiwawa committed Sep 14, 2023
2 parents 5038f8b + 308a98f commit 70a4ada
Show file tree
Hide file tree
Showing 23 changed files with 7,678 additions and 102 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ jobs:
- name: Test wasm
run: node wasm/tests/index.js

- name: Test clvm-js like wasm interface
run: |
cd wasm/tests/clvm-tools-interface && npm install && yarn test
- name: Upload npm pkg artifacts
uses: actions/upload-artifact@v3
with:
Expand Down
2 changes: 2 additions & 0 deletions wasm/Cargo.lock

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

2 changes: 2 additions & 0 deletions wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ clvmr = { version = "0.3.0", features = ["pre-eval"] }
wasm-bindgen = "=0.2.83"
wasm-bindgen-test = "=0.3.25"
js-sys = "0.3.60"
num-bigint = "0.4.0"
num-traits = "0.2.15"
115 changes: 115 additions & 0 deletions wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,118 @@ Prerequisite:
# Make sure you're at <clvm_tools_rs root>/wasm
node ./tests/index.js
```

Program
===

Program is exported by ```clvm_tools_rs``` and contains a ```to``` function
among a few others. Its use is very like Program.to in the python code and
similar to chiaminejp's ```clvm_tools``` library. It produces a value that
can be used together with other such values, can be curried (and uncurried)
converted to the hex representation and run.

```Program.to(javascript_value)```

Converts javascript values to SExp objects which can be used together and run
as CLVM programs or used as program arguments. This conversion follows simple
conventions that were established in ```clvm_tools```.

- There's a tuple object returned by the ```t``` function (2 arguments) which
produces a cons.

- javascript arrays are treated as linear proper lists. Each element appears
as the first of a cons with the rest of the converted list as its tail. The
list is terminated by a nil.

- an object which has a serialize method treats the result of ```o.serialize()```
as an array-like object which specifies the byte values of the object's atom
representation. This covers bls primitives such as G1Element and G2Element.

- javascript numbers, bignums, strings and bools are treated as atoms.

- javascript objects which contain an array-like ```pair``` member are treated
the same as tuple objects above.

```Program.from_hex(hex_str)```

Converts a string of pairs of hex digits into the CLVM deserialized form of the
object.

```Program.null()```

Returns a null object.

The returned objects have these methods:

SExp methods
===

```SExp.toString()```

Convert the object to its hex representation.

```SExp.as_pair()```

If it is a cons, return a tuple-compatible object containing a ```pair``` array
with 2 elements, otherwise null.

```SExp.listp()```

Return true if the object is a cons.

```SExp.nullp()```

Return true if the object is falsey.

```SExp.as_int()```

Returns a javascript number that fits within the 32-bit integers representing the object's atom value, or throw.

```SExp.as_bigint()```

Returns a javascript big number representing the value of the given atom or throw.

```SExp.first()```

If the object is a cons, return its first or left component, or throw if not.

```SExp.rest()```

If the object is a cons, return its rest or right component, or throw if not.

```SExp.cons(other)```

Creates an SExp which is a cons of this sexp and other.

```SExp.run(env)```

Runs the indicated SExp as a program with the given environment.

```SExp.as_bin()```

Serialize the object into an array of byte values.

```SExp.list_len()```

Give the number of conses one needs to traverse until reaching a non-cons rest.
For a proper list, this gives the list's length.

```SExp.as_javascript()```

Return a javascript value that allows the given SExp to be inspected via
javascript.

```SExp.curry(a, b, c ...)```

Given a number of positional arguments, build a curried application that provides
values for the left arguments of some runnable CLVM code, giving code that can
be correctly called with fewer arguments. This is common for providing values to
the upper case parameters of chialisp programs, such as coin puzzles.

```SExp.uncurry(program) -> [inner_program, [args...]]```
```SExp.uncurry_error(program)```

Uncurry returns an array with the inner program and the retrievable arguments
separated out, or the original program and null. uncurry_error throws instead
of returning a value if the object wasn't a curried program.

79 changes: 42 additions & 37 deletions wasm/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::jsval::{
btreemap_to_object, get_property, js_object_from_sexp, js_pair, object_to_value,
read_string_to_string_map, sexp_from_js_object,
};
use crate::objects::Program;

#[cfg(feature = "wee_alloc")]
#[global_allocator]
Expand Down Expand Up @@ -61,7 +62,7 @@ thread_local! {
};
}

fn get_next_id() -> i32 {
pub fn get_next_id() -> i32 {
NEXT_ID.with(|n| n.fetch_add(1, Ordering::SeqCst) as i32)
}

Expand Down Expand Up @@ -96,42 +97,39 @@ where
runcell.replace_with(|coll| {
let mut work_collection = HashMap::new();
swap(coll, &mut work_collection);
match work_collection.get_mut(&this_id) {
Some(r_ref) => {
result = f(r_ref);
}
_ => {}
if let Some(r_ref) = work_collection.get_mut(&this_id) {
result = f(r_ref);
}
work_collection
});
});
result
}

fn create_clvm_runner_err(error: String) -> JsValue {
pub fn create_clvm_runner_err(error: String) -> JsValue {
let array = js_sys::Array::new();
array.set(
0,
js_pair(JsValue::from_str("error"), JsValue::from_str(&error)),
);
return object_to_value(&js_sys::Object::from_entries(&array).unwrap());
object_to_value(&js_sys::Object::from_entries(&array).unwrap())
}

fn create_clvm_runner_run_failure(err: &RunFailure) -> JsValue {
match err {
RunFailure::RunErr(l, e) => {
return create_clvm_runner_err(format!("{}: Error {}", l.to_string(), e));
create_clvm_runner_err(format!("{}: Error {}", l, e))
}
RunFailure::RunExn(l, e) => {
return create_clvm_runner_err(format!("{}: Exn {}", l.to_string(), e.to_string()))
create_clvm_runner_err(format!("{}: Exn {}", l, e))
}
}
}

fn create_clvm_compile_failure(err: &CompileErr) -> JsValue {
match err {
CompileErr(l, e) => {
return create_clvm_runner_err(format!("{}: Error {}", l.to_string(), e));
create_clvm_runner_err(format!("{}: Error {}", l, e))
}
}
}
Expand All @@ -146,7 +144,7 @@ impl CldbSingleBespokeOverride for JsBespokeOverride {
// When the user returns, try to convert the result back to sexp.
fn get_override(&self, env: Rc<SExp>) -> Result<Rc<SExp>, RunFailure> {
let args = js_sys::Array::new();
args.set(0, js_object_from_sexp(env.clone()));
args.set(0, js_object_from_sexp(env.clone()).map_err(|_| RunFailure::RunErr(env.loc(), "error converting override value".to_string()))?);
self.fun
.apply(&JsValue::null(), &args)
.map_err(|e| {
Expand All @@ -158,7 +156,7 @@ impl CldbSingleBespokeOverride for JsBespokeOverride {
})
.and_then(|v| {
sexp_from_js_object(env.loc(), &v)
.map(|s| Ok(s))
.map(Ok)
.unwrap_or_else(|| {
Err(RunFailure::RunErr(
env.loc(),
Expand All @@ -182,8 +180,8 @@ pub fn create_clvm_runner(
) -> JsValue {
let mut allocator = Allocator::new();
let runner = Rc::new(DefaultProgramRunner::new());
let args_srcloc = Srcloc::start(&"*args*".to_string());
let prog_srcloc = Srcloc::start(&"*program*".to_string());
let args_srcloc = Srcloc::start("*args*");
let prog_srcloc = Srcloc::start("*program*");
let mut prim_map = HashMap::new();
let mut override_funs: HashMap<String, Box<dyn CldbSingleBespokeOverride>> = HashMap::new();

Expand All @@ -207,14 +205,11 @@ pub fn create_clvm_runner(
}
};

for ent in js_sys::Object::keys(&overrides).values() {
for ent in js_sys::Object::keys(overrides).values() {
let key = ent.unwrap().as_string().unwrap();
let val = get_property(&overrides, &key).unwrap();
match val.dyn_ref::<js_sys::Function>() {
Some(f) => {
override_funs.insert(key, Box::new(JsBespokeOverride { fun: f.clone() }));
}
_ => {}
let val = get_property(overrides, &key).unwrap();
if let Some(f) = val.dyn_ref::<js_sys::Function>() {
override_funs.insert(key, Box::new(JsBespokeOverride { fun: f.clone() }));
}
}

Expand Down Expand Up @@ -246,15 +241,15 @@ pub fn create_clvm_runner(
let this_id = get_next_id();
insert_runner(this_id, JsRunStep { allocator, cldbrun });

return JsValue::from(this_id);
JsValue::from(this_id)
}

#[wasm_bindgen]
pub fn final_value(runner: i32) -> JsValue {
with_runner(runner, |r| {
r.cldbrun.final_result().map(|v| js_object_from_sexp(v))
r.cldbrun.final_result().map(|v| js_object_from_sexp(v).unwrap_or_else(|e| e))
})
.unwrap_or_else(|| JsValue::null())
.unwrap_or_else(JsValue::null)
}

#[wasm_bindgen]
Expand All @@ -273,7 +268,7 @@ pub fn run_step(runner: i32) -> JsValue {
r.cldbrun.step(&mut r.allocator)
})
.map(|result_hash| btreemap_to_object(result_hash.iter()))
.unwrap_or_else(|| JsValue::null())
.unwrap_or_else(JsValue::null)
}

fn make_compile_output(result_stream: &Stream, symbol_table: &HashMap<String, String>) -> JsValue {
Expand All @@ -284,10 +279,8 @@ fn make_compile_output(result_stream: &Stream, symbol_table: &HashMap<String, St
js_pair(JsValue::from_str("hex"), JsValue::from_str(&output_hex)),
);
let symbol_array = js_sys::Array::new();
let mut idx = 0;
for (k, v) in symbol_table.iter() {
symbol_array.set(idx, js_pair(JsValue::from_str(&k), JsValue::from_str(&v)));
idx += 1;
for (idx, (k, v)) in symbol_table.iter().enumerate() {
symbol_array.set(idx as u32, js_pair(JsValue::from_str(k), JsValue::from_str(v)));
}
let symbol_object = object_to_value(&js_sys::Object::from_entries(&symbol_array).unwrap());
array.set(1, js_pair(JsValue::from_str("symbols"), symbol_object));
Expand Down Expand Up @@ -346,7 +339,7 @@ pub fn compose_run_function(
function_name: String,
) -> JsValue {
let mut allocator = Allocator::new();
let loc = Srcloc::start(&"*js*".to_string());
let loc = Srcloc::start("*js*");
let symbol_table = match read_string_to_string_map(symbol_table_js) {
Ok(s) => s,
Err(e) => {
Expand Down Expand Up @@ -386,7 +379,7 @@ pub fn compose_run_function(
},
Ok(x) => x,
};

let function_path = match path_to_function(main_env.1.clone(), &hash_bytes.data().clone()) {
Some(p) => p,
_ => {
Expand Down Expand Up @@ -416,7 +409,7 @@ pub fn compose_run_function(
#[wasm_bindgen]
pub fn create_repl() -> i32 {
let allocator = Allocator::new();
let opts = Rc::new(DefaultCompilerOpts::new(&"*repl*".to_string()));
let opts = Rc::new(DefaultCompilerOpts::new("*repl*"));
let runner = Rc::new(DefaultProgramRunner::new());
let repl = Repl::new(opts, runner.clone());
let new_id = get_next_id();
Expand Down Expand Up @@ -469,26 +462,38 @@ pub fn repl_run_string(repl_id: i32, input: String) -> JsValue {
r.process_line(a, input)
} else {
Err(CompileErr(
Srcloc::start(&"*repl*".to_string()),
Srcloc::start("*repl*"),
"no such repl".to_string(),
))
}
})
.map(|v| v.map(|v| js_object_from_sexp(v.to_sexp())))
.map(|v| v.map(|v| js_object_from_sexp(v.to_sexp()).unwrap_or_else(|e| e)))
.unwrap_or_else(|e| {
Some(create_clvm_runner_err(format!(
"{}: {}",
e.0.to_string(),
e.1
)))
})
.unwrap_or_else(|| JsValue::null())
.unwrap_or_else(JsValue::null)
}

#[wasm_bindgen]
pub fn sexp_to_string(v: &JsValue) -> JsValue {
let loc = Srcloc::start(&"*val*".to_string());
let loc = Srcloc::start("*val*");

sexp_from_js_object(loc, v)
.map(|s| JsValue::from_str(&s.to_string()))
.unwrap_or_else(|| create_clvm_runner_err("unable to convert to value".to_string()))
}

#[wasm_bindgen]
pub fn h(v: String) -> Result<Vec<u8>, JsValue> {
let hex_data = Bytes::new_validated(Some(UnvalidatedBytesFromType::Hex(v))).map_err(|_| js_sys::JsString::from("bad hex input"))?;
Ok(hex_data.data().clone())
}

#[wasm_bindgen]
pub fn t(a: &JsValue, b: &JsValue) -> Result<JsValue, JsValue> {
Program::as_pair_internal(&Program::cons_internal(&Program::to(a)?, &Program::to(b)?)?)
}
Loading

0 comments on commit 70a4ada

Please sign in to comment.