Skip to content
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
82 changes: 41 additions & 41 deletions dist/@rip/rip.js
Original file line number Diff line number Diff line change
Expand Up @@ -13161,19 +13161,27 @@ ${pad}`);
this.patternNames(v, names, true);
return names;
}
rangedByHeader(node, markVar, vars, iter, step) {
rangedHeader(node, markVar, vars, iter, step) {
const [dots, from, to] = iter;
const cmpUp = dots === ".." ? "<=" : "<";
const cmpDown = dots === ".." ? ">=" : ">";
const numText = (s) => typeof s === "string" && /^[0-9.]/.test(s) ? s : null;
const posLit = numText(step) ?? (isNode(step) && step[0] === "+" && step.length === 2 ? numText(step[1]) : null);
const negLit = isNode(step) && step[0] === "-" && step.length === 2 ? numText(step[1]) : null;
if ((posLit ?? negLit) !== null && Number((posLit ?? negLit).replace(/_/g, "")) === 0) {
const signedText = (x) => numText(x) ?? (isNode(x) && (x[0] === "+" || x[0] === "-") && x.length === 2 && numText(x[1]) !== null ? `${x[0] === "-" ? "-" : ""}${numText(x[1])}` : null);
const literal = (x) => {
const text = signedText(x);
return text === null ? null : Number(text.replace(/_/g, ""));
};
const stepLit = step === null ? null : literal(step);
if (stepLit === 0) {
throw this.positionedError(node, "emitter: a BY step of 0 never advances the loop");
}
const fromLit = literal(from);
const toLit = literal(to);
const descends = step === null ? fromLit !== null && toLit !== null && fromLit > toLit : stepLit !== null && stepLit < 0;
const v = vars[0];
const toRef = this.singleReadIterable(to) ? null : this.loopTempName("_ref");
const stepRef = posLit !== null || negLit !== null ? null : this.loopTempName("_step");
const idx = vars.length === 2 ? vars[1] : null;
const toRef = toLit !== null || this.singleReadIterable(to) ? null : this.loopTempName("_ref");
const stepRef = step === null || stepLit !== null ? null : this.loopTempName("_step");
this.b.emit("for (let ");
markVar(v);
this.b.emit(" = ");
Expand All @@ -13186,32 +13194,42 @@ ${pad}`);
this.b.emit(`, ${stepRef} = `);
this.mark(node, "step", () => this.expr(step));
}
if (idx !== null) {
this.b.emit(", ");
markVar(idx);
this.b.emit(" = 0");
}
const toText = () => {
if (toRef !== null)
this.b.emit(toRef);
else
this.expr(to);
};
const advance = () => {
if (stepRef !== null)
this.b.emit(`${v} += ${stepRef}`);
else if (step === null)
this.b.emit(descends ? `${v}--` : `${v}++`);
else {
this.b.emit(`${v} ${descends ? "-=" : "+="} `);
this.mark(node, "step", () => this.b.emit(String(Math.abs(stepLit))));
}
if (idx !== null)
this.b.emit(`, ${idx}++`);
};
this.b.emit("; ");
if (stepRef !== null) {
this.b.emit(`${stepRef} > 0 ? ${v} ${cmpUp} `);
toText();
this.b.emit(` : ${v} ${cmpDown} `);
toText();
this.b.emit(`; ${v} += ${stepRef})`);
} else if (negLit !== null) {
this.b.emit(`${v} ${cmpDown} `);
toText();
this.b.emit(`; ${v} -= `);
this.mark(node, "step", () => this.b.emit(negLit));
this.b.emit(")");
} else {
this.b.emit(`${v} ${cmpUp} `);
this.b.emit(`${v} ${descends ? cmpDown : cmpUp} `);
toText();
this.b.emit(`; ${v} += `);
this.mark(node, "step", () => this.b.emit(posLit));
this.b.emit(")");
}
this.b.emit("; ");
advance();
this.b.emit(")");
}
forIn(node, ind) {
this.inCtrl(() => this.forInCtrl(node, ind));
Expand All @@ -13226,26 +13244,8 @@ ${pad}`);
if (isNode(vars[0]) && (isRange(iter) || step !== null)) {
throw this.positionedError(node, "emitter: pattern loop variables with ranges or BY steps are not supported yet");
}
if (isRange(iter) && step === null) {
const [dots, from, to] = iter;
const toRef = this.singleReadIterable(to) ? null : this.loopTempName("_ref");
this.b.emit("for (let ");
markVar(vars[0]);
this.b.emit(" = ");
this.expr(from);
if (toRef) {
this.b.emit(`, ${toRef} = `);
this.expr(to);
}
this.b.emit(`; ${vars[0]} ${dots === ".." ? "<=" : "<"} `);
if (toRef)
this.b.emit(toRef);
else
this.expr(to);
this.b.emit(`; ${vars[0]}++) `);
this.guardedBlock(body, guard, ind);
} else if (isRange(iter)) {
this.rangedByHeader(node, markVar, vars, iter, step);
if (isRange(iter)) {
this.rangedHeader(node, markVar, vars, iter, step);
this.b.emit(" ");
this.guardedBlock(body, guard, ind);
} else if (step !== null) {
Expand Down Expand Up @@ -13568,8 +13568,8 @@ ${pad ?? ""}`);
if (isNode(vars[0]) && (isRange(iter) || step !== null)) {
throw this.positionedError(node, "emitter: pattern loop variables with ranges or BY steps are not supported yet");
}
if (isRange(iter) && step !== null) {
this.rangedByHeader(node, markVar, vars, iter, step);
if (isRange(iter)) {
this.rangedHeader(node, markVar, vars, iter, step);
return setups;
}
if (step !== null) {
Expand Down Expand Up @@ -30600,7 +30600,7 @@ var sha256 = function(input) {
let w = new Uint32Array(64);
let i = 0;
while (i < padded.length) {
for (let j of ((s, e2) => Array.from({ length: Math.max(0, Math.abs(e2 - s)) }, (_, i2) => s + i2 * (s <= e2 ? 1 : -1)))(0, 16)) {
for (let j = 0;j < 16; j++) {
w[j] = view.getUint32(i + j * 4, false);
}
for (let j = 16;j < 64; j++) {
Expand Down Expand Up @@ -30637,7 +30637,7 @@ var sha256 = function(input) {
}
let digest = new Uint8Array(32);
let out = new DataView(digest.buffer);
for (let i2 of ((s, e2) => Array.from({ length: Math.max(0, Math.abs(e2 - s)) }, (_, i3) => s + i3 * (s <= e2 ? 1 : -1)))(0, 8)) {
for (let i2 = 0;i2 < 8; i2++) {
out.setUint32(i2 * 4, H[i2], false);
}
return digest;
Expand Down
232 changes: 116 additions & 116 deletions dist/@rip/rip.min.js

Large diffs are not rendered by default.

Binary file modified dist/@rip/rip.min.js.br
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/pdf/pdf.rip
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ postScriptName = (dv, bytes, name) ->
at = strings + dv.getUint16(p + 10)
continue unless id is 6
s = if plat is 3
(String.fromCharCode dv.getUint16(at + j * 2) for j in [0...len / 2]).join ''
(String.fromCharCode dv.getUint16(at + j * 2) for j in [0...len >> 1]).join ''
else
(String.fromCharCode bytes[at + j] for j in [0...len]).join ''
s = s.replace /[^A-Za-z0-9+-]/g, ''
Expand Down
89 changes: 43 additions & 46 deletions src/emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6430,53 +6430,68 @@ class Emitter {
return names;
}

// Ranged for with a BY step. Literal steps take static-direction
// headers (a positive literal ascends toward TO, a negative one
// descends); every other step binds ONCE and the header tests its
// sign, so both directions terminate and a zero step iterates
// nothing (a zero LITERAL rejects — it never advances). Impure
// bounds bind once beside the counter.
rangedByHeader(node, markVar, vars, iter, step) {
// Ranged for, every form. Direction: a literal step's sign decides
// (positive ascends, negative descends, zero rejects — it never
// advances); a variable step binds ONCE and the header tests its
// sign; with no step, two literal bounds decide statically and any
// variable bound ascends (`by -1` descends). Impure bounds bind once
// beside the counter. A second loop variable is the iteration index.
rangedHeader(node, markVar, vars, iter, step) {
const [dots, from, to] = iter;
const cmpUp = dots === '..' ? '<=' : '<';
const cmpDown = dots === '..' ? '>=' : '>';
const numText = (s) => (typeof s === 'string' && /^[0-9.]/.test(s) ? s : null);
const posLit = numText(step) ??
(isNode(step) && step[0] === '+' && step.length === 2 ? numText(step[1]) : null);
const negLit = isNode(step) && step[0] === '-' && step.length === 2 ? numText(step[1]) : null;
if ((posLit ?? negLit) !== null && Number((posLit ?? negLit).replace(/_/g, '')) === 0) {
const signedText = (x) => numText(x) ??
(isNode(x) && (x[0] === '+' || x[0] === '-') && x.length === 2 && numText(x[1]) !== null
? `${x[0] === '-' ? '-' : ''}${numText(x[1])}`
: null);
const literal = (x) => {
const text = signedText(x);
return text === null ? null : Number(text.replace(/_/g, ''));
};
const stepLit = step === null ? null : literal(step);
if (stepLit === 0) {
throw this.positionedError(node, 'emitter: a BY step of 0 never advances the loop');
}
const fromLit = literal(from);
const toLit = literal(to);
const descends = step === null
? fromLit !== null && toLit !== null && fromLit > toLit
: stepLit !== null && stepLit < 0;
const v = vars[0];
const toRef = this.singleReadIterable(to) ? null : this.loopTempName('_ref');
const stepRef = posLit !== null || negLit !== null ? null : this.loopTempName('_step');
const idx = vars.length === 2 ? vars[1] : null;
const toRef = toLit !== null || this.singleReadIterable(to) ? null : this.loopTempName('_ref');
const stepRef = step === null || stepLit !== null ? null : this.loopTempName('_step');
this.b.emit('for (let ');
markVar(v);
this.b.emit(' = ');
this.expr(from);
if (toRef !== null) { this.b.emit(`, ${toRef} = `); this.expr(to); }
if (stepRef !== null) { this.b.emit(`, ${stepRef} = `); this.mark(node, 'step', () => this.expr(step)); }
if (idx !== null) { this.b.emit(', '); markVar(idx); this.b.emit(' = 0'); }
const toText = () => { if (toRef !== null) this.b.emit(toRef); else this.expr(to); };
const advance = () => {
if (stepRef !== null) this.b.emit(`${v} += ${stepRef}`);
else if (step === null) this.b.emit(descends ? `${v}--` : `${v}++`);
else {
this.b.emit(`${v} ${descends ? '-=' : '+='} `);
this.mark(node, 'step', () => this.b.emit(String(Math.abs(stepLit))));
}
if (idx !== null) this.b.emit(`, ${idx}++`);
};
this.b.emit('; ');
if (stepRef !== null) {
this.b.emit(`${stepRef} > 0 ? ${v} ${cmpUp} `);
toText();
this.b.emit(` : ${v} ${cmpDown} `);
toText();
this.b.emit(`; ${v} += ${stepRef})`);
} else if (negLit !== null) {
this.b.emit(`${v} ${cmpDown} `);
toText();
this.b.emit(`; ${v} -= `);
this.mark(node, 'step', () => this.b.emit(negLit));
this.b.emit(')');
} else {
this.b.emit(`${v} ${cmpUp} `);
this.b.emit(`${v} ${descends ? cmpDown : cmpUp} `);
toText();
this.b.emit(`; ${v} += `);
this.mark(node, 'step', () => this.b.emit(posLit));
this.b.emit(')');
}
this.b.emit('; ');
advance();
this.b.emit(')');
}

forIn(node, ind) {
Expand All @@ -6497,26 +6512,8 @@ class Emitter {
if (isNode(vars[0]) && (isRange(iter) || step !== null)) {
throw this.positionedError(node, 'emitter: pattern loop variables with ranges or BY steps are not supported yet');
}
if (isRange(iter) && step === null) {
const [dots, from, to] = iter;
// The condition re-reads TO each iteration: an impure bound
// binds once in the init (a pure one keeps its bytes).
const toRef = this.singleReadIterable(to) ? null : this.loopTempName('_ref');
this.b.emit('for (let ');
markVar(vars[0]);
this.b.emit(' = ');
this.expr(from);
if (toRef) {
this.b.emit(`, ${toRef} = `);
this.expr(to);
}
this.b.emit(`; ${vars[0]} ${dots === '..' ? '<=' : '<'} `);
if (toRef) this.b.emit(toRef);
else this.expr(to);
this.b.emit(`; ${vars[0]}++) `);
this.guardedBlock(body, guard, ind);
} else if (isRange(iter)) {
this.rangedByHeader(node, markVar, vars, iter, step);
if (isRange(iter)) {
this.rangedHeader(node, markVar, vars, iter, step);
this.b.emit(' ');
this.guardedBlock(body, guard, ind);
} else if (step !== null) {
Expand Down Expand Up @@ -6859,8 +6856,8 @@ class Emitter {
if (isNode(vars[0]) && (isRange(iter) || step !== null)) {
throw this.positionedError(node, 'emitter: pattern loop variables with ranges or BY steps are not supported yet');
}
if (isRange(iter) && step !== null) {
this.rangedByHeader(node, markVar, vars, iter, step);
if (isRange(iter)) {
this.rangedHeader(node, markVar, vars, iter, step);
return setups;
}
if (step !== null) {
Expand Down
2 changes: 1 addition & 1 deletion test/corpus/expected/compose4.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function sum(list) {
let s = sum([1, 2, 3]);
let v = (() => {
const result = [];
for (let n of ((s, e) => Array.from({length: Math.abs(e - s) + 1}, (_, i) => s + (i * (s <= e ? 1 : -1))))(1, 3)) {
for (let n = 1; n <= 3; n++) {
result.push((n * 2));
}
return result;
Expand Down
2 changes: 1 addition & 1 deletion test/corpus/expected/compose4.map.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":3,"file":"compose4.rip.js","sources":["compose4.rip"],"sourcesContent":["# composition-and-boundary fixture: control flow in applicable\n# matrix positions, statement-position row included. Inapplicable cells:\n# - for/switch/try/loop/throw are statement-only this wave (expression\n# position rejects loudly — an IIFE lowering of\n# try/switch-as-expression is\n# IIFE wrapping, deferred, incl. as the implicit\n# return of a function body)\n# - comprehensions are VALUE-position expressions (deterministic IIFE\n# deliberately absent); statement position keeps the plain\n# statement lowering\n#, member-object position with it\ndef sum(list)\n total = 0\n parts = (n for n in list)\n total = parts.length\n return total\n\ns = sum([1, 2, 3])\nv = (n * 2 for n in [1..3])\nw = v[0]\nlabel = \"\"\nswitch s\n when 6\n label = \"six\"\n else\n label = \"other\"\n\n# trivia at a block boundary\n\ncaught = 0\ntry\n missing()\ncatch err\n caught = 1 if err\n\nu = s in [5, 6]\nloop\n s -= 1\n break unless s > 0\n\n# statement-position row: postfix update forms are valid statements\ns++ if u\n"],"names":["sum","list","total","parts","n","length","s","v","w","label","caught","missing","err","u"],"mappings":"AAWA,SAAIA,GAAG,CAACC,MACN;MAAAC,QAAQ;MACRC,QAAS;;;kBAAAC;;;;EACTF,QAAQC,MAAME;EACd,OAAOH;;IAETI,IAAIN,GAAG,CAAC,CAAC;IACTO,IAAK;;gBAAe,6FAAC,GAAG;iBAAnBH,EAAE,EAAE;;;;IACTI,IAAID,EAAE;IACNE,QAAQ;AACR,QAAOH;AACL;IACEG,QAAQ;;;IAERA,QAAQ;;;IAIZC,SAAS;AACT,IACE;EAAAC,OAAO;SACHC,KACJ;EAAA,IAAcA,KAAdF,SAAS;;IAEXG,IAAI,+EAAAP,GAAK,CAAC;AACV,aACE;EAAAA,EAAE,GAAG;EACL,IAAa,EAAAA,EAAE,EAAE;;AAGnB,IAAOO,GAAP,CAAAP"}
{"version":3,"file":"compose4.rip.js","sources":["compose4.rip"],"sourcesContent":["# composition-and-boundary fixture: control flow in applicable\n# matrix positions, statement-position row included. Inapplicable cells:\n# - for/switch/try/loop/throw are statement-only this wave (expression\n# position rejects loudly — an IIFE lowering of\n# try/switch-as-expression is\n# IIFE wrapping, deferred, incl. as the implicit\n# return of a function body)\n# - comprehensions are VALUE-position expressions (deterministic IIFE\n# deliberately absent); statement position keeps the plain\n# statement lowering\n#, member-object position with it\ndef sum(list)\n total = 0\n parts = (n for n in list)\n total = parts.length\n return total\n\ns = sum([1, 2, 3])\nv = (n * 2 for n in [1..3])\nw = v[0]\nlabel = \"\"\nswitch s\n when 6\n label = \"six\"\n else\n label = \"other\"\n\n# trivia at a block boundary\n\ncaught = 0\ntry\n missing()\ncatch err\n caught = 1 if err\n\nu = s in [5, 6]\nloop\n s -= 1\n break unless s > 0\n\n# statement-position row: postfix update forms are valid statements\ns++ if u\n"],"names":["sum","list","total","parts","n","length","s","v","w","label","caught","missing","err","u"],"mappings":"AAWA,SAAIA,GAAG,CAACC,MACN;MAAAC,QAAQ;MACRC,QAAS;;;kBAAAC;;;;EACTF,QAAQC,MAAME;EACd,OAAOH;;IAETI,IAAIN,GAAG,CAAC,CAAC;IACTO,IAAK;;;iBAAAH,EAAE,EAAE;;;;IACTI,IAAID,EAAE;IACNE,QAAQ;AACR,QAAOH;AACL;IACEG,QAAQ;;;IAERA,QAAQ;;;IAIZC,SAAS;AACT,IACE;EAAAC,OAAO;SACHC,KACJ;EAAA,IAAcA,KAAdF,SAAS;;IAEXG,IAAI,+EAAAP,GAAK,CAAC;AACV,aACE;EAAAA,EAAE,GAAG;EACL,IAAa,EAAAA,EAAE,EAAE;;AAGnB,IAAOO,GAAP,CAAAP"}
46 changes: 46 additions & 0 deletions test/rip/comprehensions.rip
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,49 @@ test 'object comprehension handles a large accumulated result', '''
out = {k: v for k, v of src}
[Object.keys(out).length, out['1999']]
''', [2000, 1999]

# ==============================================================================
# Range comprehensions share the counting header: no materialized range
# ==============================================================================

test 'postfix range literal descending', "out = []\nout.push i for i in [3..1]\nout", [3, 2, 1]
test 'postfix range [1..n] with n = 0 runs nothing', "n = 0\nout = []\nout.push i for i in [1..n]\nout", []
test 'comprehension variable start above end is empty', "hi = 3\nlo = 1\n(i for i in [hi..lo])", []
test 'comprehension variable range descends with by -1', "hi = 3\nlo = 1\n(i for i in [hi..lo] by -1)", [3, 2, 1]
test 'comprehension negative literal bounds', "(i for i in [2..-2])", [2, 1, 0, -1, -2]
test 'comprehension fractional bound compares directly', "len = 5\n(i for i in [0...len / 2])", [0, 1, 2]
test 'comprehension two-variable range binds the index', "([v, i] for v, i in [1..3])", [[1, 0], [2, 1], [3, 2]]
test 'comprehension bigint bounds', "(String(i) for i in [1n..3n])", ['1', '2', '3']
test 'comprehension nested literal ranges', "([i, j] for j in [1..2] for i in [1..2])", [[[1, 1], [1, 2]], [[2, 1], [2, 2]]]

test 'comprehension range bounds evaluate once each', '''
calls = 0
top = -> calls++; 3
[(i for i in [1..top()]), calls]
''', [[1, 2, 3], 1]

code 'comprehension range compiles to a counting loop', '''
squares = (k * k for k in [1..3])
''', '''
let squares = (() => {
const result = [];
for (let k = 1; k <= 3; k++) {
result.push((k * k));
}
return result;
})();
'''

code 'postfix range binds an impure bound once', '''
total = 0
total += k for k in [1..n]
''', '''
let total = 0;
(() => {
const result = [];
for (let k = 1, _ref = n; k <= _ref; k++) {
result.push((total += k));
}
return result;
})();
'''
Loading