Skip to content
This repository was archived by the owner on Jan 8, 2024. It is now read-only.

Commit 64aec7f

Browse files
Merge pull request #35 from Chia-Network/20220607-fix-inline-destructuring
20220607 fix inline destructuring
2 parents e11d7c2 + f1111d6 commit 64aec7f

File tree

10 files changed

+509
-56
lines changed

10 files changed

+509
-56
lines changed

.github/workflows/build-test.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,23 @@ jobs:
136136
python -c 'import clvm_rs; print(clvm_rs.__file__)'
137137
python -c 'import clvm_tools_rs; print(clvm_tools_rs.__file__)'
138138
139+
- name: Verify recompilation of old sources match
140+
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.python, '3.7')
141+
run: |
142+
. ./activate
143+
# Build cmd line tools
144+
PYO3_PYTHON=`which python` cargo build --no-default-features --release
145+
146+
# Grab chia-blockchain
147+
git clone https://github.com/Chia-Network/chia-blockchain
148+
149+
# Check recompiles
150+
cp support/recompile_check.py chia-blockchain
151+
(cd chia-blockchain && python recompile_check.py)
152+
153+
# Ran successfully, remove
154+
rm -rf chia-blockchain
155+
139156
- name: Run tests from clvm
140157
run: |
141158
. ./activate

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "clvm_tools_rs"
3-
version = "0.1.11"
3+
version = "0.1.12"
44
edition = "2018"
55
authors = ["Art Yerkes <[email protected]>"]
66
description = "tools for working with chialisp language; compiler, repl, python and wasm bindings"

src/classic/clvm_tools/stages/stage_2/defaults.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ fn DEFAULT_MACROS_SRC() -> Vec<&'static str> {
7777
(function (unquote C)))
7878
@)))
7979
"},
80+
indoc! {"
81+
(q \"__chia__enlist\"
82+
(a (q #a (q #a 2 (c 2 (c 3 (q))))
83+
(c (q #a (i 5
84+
(q #c (q . 4)
85+
(c 9 (c (a 2 (c 2 (c 13 (q))))
86+
(q)))
87+
)
88+
(q 1))
89+
1)
90+
1))
91+
2))
92+
"},
8093
];
8194
}
8295

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero};
2+
use crate::classic::clvm::sexp::{enlist, first, flatten, foldM, mapM, non_nil, proper_list, rest};
3+
use crate::compiler::gensym::gensym;
4+
use crate::util::Number;
5+
use clvm_rs::allocator::{Allocator, NodePtr, SExp};
6+
use clvm_rs::reduction::EvalErr;
7+
use num_bigint::ToBigInt;
8+
use std::collections::HashMap;
9+
10+
// If this is an at capture of the form
11+
// (@ name substructure)
12+
// then return name and substructure.
13+
pub fn is_at_capture(
14+
allocator: &mut Allocator,
15+
tree_first: NodePtr,
16+
tree_rest: NodePtr,
17+
) -> Option<(NodePtr, NodePtr)> {
18+
match (
19+
allocator.sexp(tree_first),
20+
proper_list(allocator, tree_rest, true),
21+
) {
22+
(SExp::Atom(a), Some(spec)) => {
23+
if allocator.buf(&a) == vec!['@' as u8] && spec.len() == 2 {
24+
return Some((spec[0], spec[1]));
25+
}
26+
}
27+
_ => {}
28+
}
29+
30+
None
31+
}
32+
33+
// (unquote X)
34+
fn wrap_in_unquote(allocator: &mut Allocator, code: NodePtr) -> Result<NodePtr, EvalErr> {
35+
let unquote_atom = allocator.new_atom("unquote".as_bytes())?;
36+
enlist(allocator, &vec![unquote_atom, code])
37+
}
38+
39+
// (__chia__enlist X)
40+
fn wrap_in_compile_time_list(allocator: &mut Allocator, code: NodePtr) -> Result<NodePtr, EvalErr> {
41+
let chia_enlist_atom = allocator.new_atom("__chia__enlist".as_bytes())?;
42+
enlist(allocator, &vec![chia_enlist_atom, code])
43+
}
44+
45+
// Create the sequence of individual tree moves that will translate to
46+
// (f ...) and (r ...) wrapping to select the given path from a larger structure.
47+
fn create_path_selection_plan(
48+
allocator: &mut Allocator,
49+
path: Number,
50+
operators: &mut Vec<bool>,
51+
) -> Result<(), EvalErr> {
52+
if path <= bi_one() {
53+
Ok(())
54+
} else {
55+
operators.push(path.clone() % 2_u32.to_bigint().unwrap() == bi_one());
56+
create_path_selection_plan(allocator, path / 2_u32.to_bigint().unwrap(), operators)
57+
}
58+
}
59+
60+
// Given a path and code to be wrapped, generate a lookup by path into that code.
61+
fn wrap_path_selection(
62+
allocator: &mut Allocator,
63+
path: Number,
64+
wrapped: NodePtr,
65+
) -> Result<NodePtr, EvalErr> {
66+
let mut operator_stack = Vec::new();
67+
let mut tail = wrapped;
68+
create_path_selection_plan(allocator, path, &mut operator_stack)?;
69+
for o in operator_stack.iter() {
70+
let head_op = if *o { vec![6] } else { vec![5] };
71+
let head_atom = allocator.new_atom(&head_op)?;
72+
tail = enlist(allocator, &vec![head_atom, tail])?;
73+
}
74+
Ok(tail)
75+
}
76+
77+
// Called for each top level argument (left branch) of the argument list of
78+
// an inline function that does destructuring (has any substructure or non
79+
// linearity in its argument list).
80+
//
81+
// If further captures are encountered, we record them in selections but
82+
// must continue their substructure as though it belongs to the current capture
83+
// as the classic macro system handles destructuring on the source text rather
84+
// than the argument values, so we must eliminate all deep references past the
85+
// top of the argument list.
86+
fn formulate_path_selections_for_destructuring_arg(
87+
allocator: &mut Allocator,
88+
arg_sexp: NodePtr,
89+
arg_path: Number,
90+
arg_depth: Number,
91+
referenced_from: Option<NodePtr>,
92+
selections: &mut HashMap<Vec<u8>, NodePtr>,
93+
) -> Result<NodePtr, EvalErr> {
94+
match allocator.sexp(arg_sexp) {
95+
SExp::Pair(a, b) => {
96+
let next_depth = arg_depth.clone() * 2_u32.to_bigint().unwrap();
97+
if let Some((capture, substructure)) = is_at_capture(allocator, a, b) {
98+
if let SExp::Atom(cbuf) = allocator.sexp(capture) {
99+
let (new_arg_path, new_arg_depth, tail) =
100+
if let Some(prev_ref) = referenced_from {
101+
(arg_path, arg_depth, prev_ref)
102+
} else {
103+
let capture_code = wrap_in_unquote(allocator, capture)?;
104+
let qtail = wrap_path_selection(
105+
allocator,
106+
arg_path.clone() + arg_depth.clone(),
107+
capture_code,
108+
)?;
109+
(bi_zero(), bi_one(), qtail)
110+
};
111+
112+
selections.insert(allocator.buf(&cbuf).to_vec(), tail);
113+
114+
formulate_path_selections_for_destructuring_arg(
115+
allocator,
116+
substructure,
117+
new_arg_path,
118+
new_arg_depth,
119+
Some(tail),
120+
selections,
121+
);
122+
return Ok(arg_sexp);
123+
}
124+
}
125+
126+
if let Some(_) = referenced_from {
127+
let f = formulate_path_selections_for_destructuring_arg(
128+
allocator,
129+
a,
130+
arg_path.clone(),
131+
next_depth.clone(),
132+
referenced_from.clone(),
133+
selections,
134+
)?;
135+
let r = formulate_path_selections_for_destructuring_arg(
136+
allocator,
137+
b,
138+
arg_depth.clone() + arg_path,
139+
next_depth,
140+
referenced_from,
141+
selections,
142+
)?;
143+
allocator.new_pair(f, r)
144+
} else {
145+
let ref_name = gensym("destructuring_capture".as_bytes().to_vec());
146+
let at_atom = allocator.new_atom("@".as_bytes())?;
147+
let name_atom = allocator.new_atom(&ref_name)?;
148+
let new_arg_list = enlist(allocator, &vec![at_atom, name_atom, arg_sexp])?;
149+
formulate_path_selections_for_destructuring_arg(
150+
allocator,
151+
new_arg_list,
152+
bi_zero(),
153+
bi_one(),
154+
None,
155+
selections,
156+
)
157+
}
158+
}
159+
SExp::Atom(b) => {
160+
let buf = allocator.buf(&b).to_vec();
161+
if buf.len() > 0 {
162+
if let Some(capture) = referenced_from {
163+
let tail = wrap_path_selection(
164+
allocator,
165+
arg_path.clone() + arg_depth.clone(),
166+
capture,
167+
)?;
168+
selections.insert(buf, tail);
169+
return Ok(arg_sexp);
170+
}
171+
}
172+
Ok(arg_sexp)
173+
}
174+
}
175+
}
176+
177+
// These generate a new argument list that will use at-captures to identify
178+
// roots to pick data out of in the eventual macro code that's emitted. This
179+
// is needed because macros and functions work differently. While functions
180+
// conceptually receive an environment and choose values out of it, macros
181+
// bind parameters to the source code the user used to invoke them; therefore
182+
// destructuring can be problematic
183+
//
184+
// Consider this example:
185+
//
186+
// (defun-inline F ((A B C)) (+ A B C))
187+
//
188+
// Without supporting destructuring consciously, this will be turned by
189+
// classic chialisp into a macro like this:
190+
//
191+
// (defmacro F ((A B C)) (+ A B C))
192+
//
193+
// Which destructures the source text of the program:
194+
//
195+
// (F (4 1 (list 2 3))) would be expected to output 6
196+
//
197+
// But instead, the destructuring gives:
198+
//
199+
// (+ 4 1 (list 2 3))
200+
//
201+
// We insert a capture for any top level argument that is non-proper:
202+
//
203+
// (defun-inline F ((@ destructuring_capture_$_1 (A B C))) (+ A B C))
204+
//
205+
// And "selections" contains the code that should be used in place of simply
206+
// unquoting a named argument:
207+
//
208+
// { "A": (f (unquote destructuring_capture_$_1)),
209+
// "B": (f (r (unquote destructuring_capture_$_1))
210+
// ...
211+
//
212+
// There is a unique case to deal with:
213+
//
214+
// (defun-inline offset-of-pt (@ pt (X Y)) (+ X (* 8 Y)))
215+
//
216+
// Because pt represents the entire argument list, it will be in this form when
217+
// unquoted:
218+
//
219+
// (offset-of-pt 3 2) -> pt = (3 2)
220+
//
221+
// When substituted:
222+
//
223+
// (offset-of-pt 3 2) -> (+ (f (3 2)) (* 8 (f (r (3 2)))))
224+
//
225+
// Simply quoting won't solve it, because the code may do something
226+
//
227+
// (offset-of-pt (+ 1 Q) (- W 2)) -> (+ (f ((+ 1 Q) (- W 2))) ...)
228+
//
229+
// So we need a macro like "list" that starts not from the entire input
230+
// environment but that destructures just its first argument as a list,
231+
// so i adapted list into __chia__enlist.
232+
// When so wrapped, the user may then destructure the capture argument.
233+
pub fn formulate_path_selections_for_destructuring(
234+
allocator: &mut Allocator,
235+
args_sexp: NodePtr,
236+
selections: &mut HashMap<Vec<u8>, NodePtr>,
237+
) -> Result<NodePtr, EvalErr> {
238+
if let SExp::Pair(a, b) = allocator.sexp(args_sexp) {
239+
if let Some((capture, substructure)) = is_at_capture(allocator, a, b) {
240+
if let SExp::Atom(cbuf) = allocator.sexp(capture) {
241+
let quoted_arg_list = wrap_in_unquote(allocator, capture)?;
242+
let tail = wrap_in_compile_time_list(allocator, quoted_arg_list)?;
243+
let buf = allocator.buf(&cbuf);
244+
selections.insert(buf.to_vec(), tail);
245+
let newsub = formulate_path_selections_for_destructuring_arg(
246+
allocator,
247+
substructure,
248+
bi_zero(),
249+
bi_one(),
250+
Some(tail),
251+
selections,
252+
)?;
253+
return enlist(allocator, &vec![a, capture, newsub]);
254+
}
255+
}
256+
let f = formulate_path_selections_for_destructuring_arg(
257+
allocator,
258+
a,
259+
bi_zero(),
260+
bi_one(),
261+
None,
262+
selections,
263+
)?;
264+
let r = formulate_path_selections_for_destructuring(allocator, b, selections)?;
265+
allocator.new_pair(f, r)
266+
} else {
267+
Ok(args_sexp)
268+
}
269+
}
270+
271+
// If true, these arguments represent a destructuring of some kind.
272+
// In the case of inlines in classic chialisp, we must adjust how arguments
273+
// are passed down to the macro body that gets created for the inline function.
274+
pub fn is_inline_destructure(allocator: &mut Allocator, args_sexp: NodePtr) -> bool {
275+
if let SExp::Pair(a, b) = allocator.sexp(args_sexp) {
276+
if let SExp::Pair(_, _) = allocator.sexp(a) {
277+
return true;
278+
}
279+
280+
return is_inline_destructure(allocator, b);
281+
}
282+
283+
false
284+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod compile;
22
pub mod defaults;
33
pub mod helpers;
4+
pub mod inline;
45
pub mod module;
56
pub mod operators;
67
pub mod optimize;

0 commit comments

Comments
 (0)