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

Implement display_list #738

Merged
merged 8 commits into from
Aug 23, 2020
Merged
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 .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -29,6 +29,6 @@ jobs:
- run: yarn autocomplete
- run: yarn format:ci
- run: yarn tslint
- run: yarn test
- run: yarn test --testPathIgnorePatterns='.*benchmark.*'
env:
CI: true
9 changes: 9 additions & 0 deletions docs/lib/list.js
Original file line number Diff line number Diff line change
@@ -382,4 +382,13 @@ function accumulate(f, initial, xs) {
accumulate(f, initial, tail(xs)));
}

/**
* Optional second argument.
* Similar to <CODE>display</CODE>, but formats well-formed lists nicely if detected.
* @param {value} xs - list structure to be displayed
* @param {string} s to be displayed, preceding <CODE>xs</CODE>
* @returns {value} xs, the first argument value
*/
function display_list(xs, s) {}

// \end{lstlisting} // \texttt{list.js END}
2 changes: 1 addition & 1 deletion scripts/test-coveralls.sh
Original file line number Diff line number Diff line change
@@ -2,5 +2,5 @@

set -euo pipefail

jest --coverage --coverageReporters=text-lcov | \
jest --coverage --coverageReporters=text-lcov --testPathIgnorePatterns='.*benchmark.*' | \
coveralls
4 changes: 4 additions & 0 deletions src/__tests__/__snapshots__/environment.ts.snap
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ Array [
"apply_in_underlying_javascript": [Function],
"array_length": [Function],
"display": [Function],
"display_list": [Function],
"draw_data": [Function],
"error": [Function],
"get_time": [Function],
@@ -194,6 +195,7 @@ Array [
"apply_in_underlying_javascript": [Function],
"array_length": [Function],
"display": [Function],
"display_list": [Function],
"draw_data": [Function],
"error": [Function],
"get_time": [Function],
@@ -281,6 +283,7 @@ Array [
"apply_in_underlying_javascript": [Function],
"array_length": [Function],
"display": [Function],
"display_list": [Function],
"draw_data": [Function],
"error": [Function],
"get_time": [Function],
@@ -362,6 +365,7 @@ Array [
"apply_in_underlying_javascript": [Function],
"array_length": [Function],
"display": [Function],
"display_list": [Function],
"draw_data": [Function],
"error": [Function],
"get_time": [Function],
14 changes: 7 additions & 7 deletions src/__tests__/__snapshots__/stringify.ts.snap
Original file line number Diff line number Diff line change
@@ -106,7 +106,7 @@ stringify(o);",
\\"k\\": 0,
\\"l\\": 0,
\\"m\\": 0,
\\"n\\": 0 }",
\\"n\\": 0}",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -422,7 +422,7 @@ stringify(arr);",
96,
97,
98,
99 ]",
99]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -560,7 +560,7 @@ Object {
[ 86,
[ 87,
[ 88,
[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]",
[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -1019,7 +1019,7 @@ Object {
"pretranspiled": "",
"result": "[... \\"lambda_expression\\",
[... [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ...] ...]",
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -1053,7 +1053,7 @@ Object {
"pretranspiled": "",
"result": "[ \\"lambda_expression\\",
[ [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ] ]",
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -1087,7 +1087,7 @@ Object {
"pretranspiled": "",
"result": "[.........\\"lambda_expression\\",
[.........[[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null].........].........]",
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
@@ -1121,7 +1121,7 @@ Object {
"pretranspiled": "",
"result": "[ \\"lambda_expression\\",
[ [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ] ]",
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]",
"resultStatus": "finished",
"transpiled": "const native = nativeStorage;
const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\");
14 changes: 7 additions & 7 deletions src/__tests__/stringify.ts
Original file line number Diff line number Diff line change
@@ -190,7 +190,7 @@ test('String representation of huge lists are nice', () => {
[ 86,
[ 87,
[ 88,
[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]"
[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"
`)
})
// tslint:enable:max-line-length
@@ -305,7 +305,7 @@ test('String representation of huge arrays are nice', () => {
96,
97,
98,
99 ]"
99]"
`)
})

@@ -360,7 +360,7 @@ test('String representation of big objects are nice', () => {
\\"k\\": 0,
\\"l\\": 0,
\\"m\\": 0,
\\"n\\": 0 }"
\\"n\\": 0}"
`)
})

@@ -440,7 +440,7 @@ test('String representation with default (2 space) indent', () => {
).toMatchInlineSnapshot(`
"[ \\"lambda_expression\\",
[ [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ] ]"
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]"
`)
})

@@ -453,7 +453,7 @@ test('String representation with more than 10 space indent should trim to 10 spa
).toMatchInlineSnapshot(`
"[ \\"lambda_expression\\",
[ [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ] ]"
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]"
`)
})

@@ -466,7 +466,7 @@ test('String representation with custom indent', () => {
).toMatchInlineSnapshot(`
"[... \\"lambda_expression\\",
[... [[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null] ...] ...]"
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]"
`)
})

@@ -479,7 +479,7 @@ test('String representation with long custom indent gets trimmed to 10 character
).toMatchInlineSnapshot(`
"[.........\\"lambda_expression\\",
[.........[[\\"name\\", [\\"x\\", null]], null],
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null].........].........]"
[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]"
`)
})
// tslint:enable:max-line-length
7 changes: 7 additions & 0 deletions src/createContext.ts
Original file line number Diff line number Diff line change
@@ -156,6 +156,12 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn
}
return rawDisplay(stringify(v), s === placeholder ? undefined : s), v
}
const displayList = (v: Value, s: any = placeholder) => {
if (s !== placeholder && typeof s !== 'string') {
throw new TypeError('display_list expects the second argument to be a string')
}
return list.rawDisplayList(display, v, s === placeholder ? undefined : s)
}
const prompt = (v: Value) => {
const start = Date.now()
const promptResult = externalBuiltIns.prompt(v, '', context.externalContext)
@@ -224,6 +230,7 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn
defineBuiltin(context, 'is_null(val)', list.is_null)
defineBuiltin(context, 'list(...values)', list.list)
defineBuiltin(context, 'draw_data(xs)', visualiseList)
defineBuiltin(context, 'display_list(val, prepend = undefined)', displayList)
}
}

975 changes: 975 additions & 0 deletions src/stdlib/__tests__/__snapshots__/list.ts.snap

Large diffs are not rendered by default.

118 changes: 118 additions & 0 deletions src/stdlib/__tests__/list-benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { stripIndent } from '../../utils/formatters'
import { testSuccess } from '../../utils/testing'
import * as list from '../list'

test('display_list is linear runtime', () => {
const placeholder = Symbol('placeholder')
const noDisplayList = (v: any, s: any = placeholder) => {
if (s !== placeholder && typeof s !== 'string') {
throw new TypeError('display_list expects the second argument to be a string')
}
return list.rawDisplayList((x: any) => x, v, s === placeholder ? undefined : s)
}

return expect(
testSuccess(
stripIndent`
const build_inf = (i, f) => {
const t = list(f(i));
let p = t;
for (let n = i - 1; n >= 0; n = n - 1) {
p = pair(f(n), p);
}
set_tail(t, p);
return p;
};
const make_complex_list = n => {
// makes a complex list structure with O(n) pairs
const cuberootn = math_floor(math_pow(n, 0.33));
return build_list(cuberootn, _ => build_inf(cuberootn, _ => build_list(cuberootn, i =>i)));
};
const time_display_list = xs => {
const starttime = get_time();
no_display_list(xs);
return get_time() - starttime;
};
// Warm up
time_display_list(make_complex_list(5000));
// measure
const ns = [
// 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000
// ^-- times 3
14000, 10000, 20000, 15000, 17000, 11000, 13000, 12000, 15000, 12000, 19000, 10000, 13000, 14000, 12000, 18000, 17000, 13000, 19000, 16000, 18000, 18000, 20000, 20000, 16000, 11000, 16000, 10000, 17000, 15000, 19000, 11000, 14000
];
const xvalues = [];
const yvalues = [];
for (let i = 0; i < array_length(ns); i = i + 1) {
const xs = make_complex_list(ns[i]);
const t = time_display_list(xs);
xvalues[i] = math_log(ns[i]);
yvalues[i] = math_log(t);
}
// linear regression adapted from https://dracoblue.net/dev/linear-least-squares-in-javascript/
function findLineByLeastSquares(values_x, values_y) {
let sum_x = 0;
let sum_y = 0;
let sum_xy = 0;
let sum_xx = 0;
let count = 0;
/*
* We'll use those variables for faster read/write access.
*/
let x = 0;
let y = 0;
let values_length = array_length(values_x);
/*
* Nothing to do.
*/
if (values_length === 0) {
return [ [], [] ];
} else {}
/*
* Calculate the sum for each of the parts necessary.
*/
for (let v = 0; v < values_length; v = v + 1) {
x = values_x[v];
y = values_y[v];
sum_x = sum_x + x;
sum_y = sum_y + y;
sum_xx = sum_xx + x*x;
sum_xy = sum_xy + x*y;
count = count + 1;
}
/*
* Calculate m and b for the formular:
* y = x * m + b
*/
let m = (count*sum_xy - sum_x*sum_y) / (count*sum_xx - sum_x*sum_x);
let b = (sum_y/count) - (m*sum_x)/count;
return pair(m, b);
}
// best fit
const line = findLineByLeastSquares(xvalues, yvalues);
const slope = head(line);
slope;
`,
{
chapter: 3,
native: false, // we're measuring a builtin, no need for native
testBuiltins: {
no_display_list: noDisplayList
}
}
// ).toMatchInlineSnapshot(`1.0463991530659391`)
).then(testResult => testResult.result)
).resolves.toBeLessThan(1.2)
// estimated power is less than 1.2
// means it's probably near 1
// => probably linear?
}, 100000)
334 changes: 333 additions & 1 deletion src/stdlib/__tests__/list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { stripIndent } from '../../utils/formatters'
import { expectParsedError, expectResult } from '../../utils/testing'
import { expectDisplayResult, expectParsedError, expectResult } from '../../utils/testing'

test('list creates list', () => {
return expectResult(
@@ -562,3 +562,335 @@ describe('These tests are reporting weird line numbers, as list functions are no
)
})
})

describe('display_list', () => {
test('standard acyclic', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(5, i=>i));
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(0, 1, 2, 3, 4)",
]
`)
})

test('standard acyclic 2', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(5, i=>build_list(i, j=>j)));
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(null, list(0), list(0, 1), list(0, 1, 2), list(0, 1, 2, 3))",
]
`)
})

test('standard acyclic with pairs', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(5, i=>build_list(i, j=>pair(j, j))));
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(null,
list([0, 0]),
list([0, 0], [1, 1]),
list([0, 0], [1, 1], [2, 2]),
list([0, 0], [1, 1], [2, 2], [3, 3]))",
]
`)
})

test('standard acyclic with pairs 2', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(5, i=>build_list(i, j=>pair(build_list(j, k=>k), j))));
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(null,
list([null, 0]),
list([null, 0], [list(0), 1]),
list([null, 0], [list(0), 1], [list(0, 1), 2]),
list([null, 0], [list(0), 1], [list(0, 1), 2], [list(0, 1, 2), 3]))",
]
`)
})

test('returns argument', () => {
return expectResult(
stripIndent`
const xs = build_list(5, i=>i);
xs === display_list(xs);
// Note reference equality
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`true`)
})

test('returns cyclic argument', () => {
return expectResult(
stripIndent`
const build_inf = (i, f) => {
const t = list(f(i));
let p = t;
for (let n = i - 1; n >= 0; n = n - 1) {
p = pair(f(n), p);
}
set_tail(t, p);
return p;
};
const xs = build_inf(5, i=>i);
xs === display_list(xs);
// Note reference equality
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`true`)
})

test('supports prepend string', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(5, i=>i), "build_list:");
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"build_list: list(0, 1, 2, 3, 4)",
]
`)
})

test('checks prepend type', () => {
return expectParsedError(
stripIndent`
display_list(build_list(5, i=>i), true);
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(
`"Line 1: TypeError: display_list expects the second argument to be a string"`
)
})

/**************
* FUZZ TESTS *
**************/

test('MCE fuzz test', () => {
return expectDisplayResult(
stripIndent`
display_list(parse('const twice = f => x => {const result = f(f(x)); return two;};'));
0; // suppress long result in snapshot
`,
{ chapter: 4, native: true }
).toMatchInlineSnapshot(`
Array [
"list(\\"constant_declaration\\",
list(\\"name\\", \\"twice\\"),
list(\\"lambda_expression\\",
list(list(\\"name\\", \\"f\\")),
list(\\"return_statement\\",
list(\\"lambda_expression\\",
list(list(\\"name\\", \\"x\\")),
list(\\"block\\",
list(\\"sequence\\",
list(list(\\"constant_declaration\\",
list(\\"name\\", \\"result\\"),
list(\\"application\\",
list(\\"name\\", \\"f\\"),
list(list(\\"application\\", list(\\"name\\", \\"f\\"), list(list(\\"name\\", \\"x\\")))))),
list(\\"return_statement\\", list(\\"name\\", \\"two\\")))))))))",
]
`)
})

test('standard acyclic multiline', () => {
return expectDisplayResult(
stripIndent`
display_list(build_list(20, i=>build_list(i, j=>j)));
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(null,
list(0),
list(0, 1),
list(0, 1, 2),
list(0, 1, 2, 3),
list(0, 1, 2, 3, 4),
list(0, 1, 2, 3, 4, 5),
list(0, 1, 2, 3, 4, 5, 6),
list(0, 1, 2, 3, 4, 5, 6, 7),
list(0, 1, 2, 3, 4, 5, 6, 7, 8),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18))",
]
`)
})

test('infinite list', () => {
return expectDisplayResult(
stripIndent`
const p = list(1);
set_tail(p, p);
display_list(p);
0; // suppress long result in snapshot
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`
Array [
"[1, ...<circular>]",
]
`)
})

test('infinite list 2', () => {
return expectDisplayResult(
stripIndent`
const p = list(1, 2, 3);
set_tail(tail(tail(p)), p);
display_list(p);
0; // suppress long result in snapshot
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`
Array [
"[1, [2, [3, ...<circular>]]]",
]
`)
})

test('reusing lists', () => {
return expectDisplayResult(
stripIndent`
const p = list(1);
const p2 = pair(p, p);
const p3 = list(p, p2);
display_list(p3);
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(list(1), list(list(1), 1))",
]
`)
})

test('reusing lists 2', () => {
return expectDisplayResult(
stripIndent`
const p1 = pair(1, null);
const p2 = pair(2, p1);
const p3 = list(p1, p2);
display_list(p3);
0; // suppress long result in snapshot
`,
{ chapter: 2, native: true }
).toMatchInlineSnapshot(`
Array [
"list(list(1), list(2, 1))",
]
`)
})
test('list of infinite list', () => {
return expectDisplayResult(
stripIndent`
const build_inf = i => {
const t = list(i);
let p = t;
for (let n = i - 1; n >= 0; n = n - 1) {
p = pair(n, p);
}
set_tail(t, p);
return p;
};
display_list(build_list(5, build_inf));
0; // suppress long result in snapshot
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`
Array [
"list([0, ...<circular>],
[0, [1, ...<circular>]],
[0, [1, [2, ...<circular>]]],
[0, [1, [2, [3, ...<circular>]]]],
[0, [1, [2, [3, [4, ...<circular>]]]]])",
]
`)
})

test('list of infinite list of list', () => {
return expectDisplayResult(
stripIndent`
const build_inf = (i, f) => {
const t = list(f(i));
let p = t;
for (let n = i - 1; n >= 0; n = n - 1) {
p = pair(f(n), p);
}
set_tail(t, p);
return p;
};
display_list(build_list(3, i => build_inf(i, i => build_list(i, i=>i))));
0; // suppress long result in snapshot
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`
Array [
"list([null, ...<circular>],
[null, [list(0), ...<circular>]],
[null, [list(0), [list(0, 1), ...<circular>]]])",
]
`)
})

test('infinite list of list of infinite list', () => {
return expectDisplayResult(
stripIndent`
const build_inf = (i, f) => {
const t = list(f(i));
let p = t;
for (let n = i - 1; n >= 0; n = n - 1) {
p = pair(f(n), p);
}
set_tail(t, p);
return p;
};
display_list(build_inf(3, i => build_list(i, i => build_inf(i, i=>i))));
0; // suppress long result in snapshot
`,
{ chapter: 3, native: true }
).toMatchInlineSnapshot(`
Array [
"[ null,
[ list([0, ...<circular>]),
[ list([0, ...<circular>], [0, [1, ...<circular>]]),
[ list([0, ...<circular>], [0, [1, ...<circular>]], [0, [1, [2, ...<circular>]]]),
...<circular>]]]]",
]
`)
})
})
86 changes: 85 additions & 1 deletion src/stdlib/list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { stringify } from '../utils/stringify'
import { stringify, ArrayLike } from '../utils/stringify'
import { Value } from '../types'

// list.ts: Supporting lists in the Scheme style, using pairs made
// up of two-element JavaScript array (vector)
@@ -118,3 +119,86 @@ export function set_tail(xs: any, x: any) {
)
}
}

export function rawDisplayList(display: any, xs: Value, prepend: string) {
const visited: Set<Value> = new Set() // Everything is put into this set, values, arrays, and even objects if they exist
const asListObjects: Map<NonEmptyList, NonEmptyList | ListObject> = new Map() // maps original list nodes to new list nodes

// We will convert list-like structures in xs to ListObject.
class ListObject implements ArrayLike {
replPrefix = 'list('
replSuffix = ')'
replArrayContents(): Value[] {
const result: Value[] = []
let curXs = this.listNode
while (curXs !== null) {
result.push(head(curXs))
curXs = tail(curXs)
}
return result
}
listNode: List

constructor(listNode: List) {
this.listNode = listNode
}
}
function getListObject(curXs: Value): Value {
return asListObjects.get(curXs) || curXs
}

const pairsToProcess: Value[] = []
let i = 0
pairsToProcess.push(xs)
// we need the guarantee that if there are any proper lists,
// then the nodes of the proper list appear as a subsequence of this array.
// We ensure this by always adding the tail after the current node is processed.
// This means that sometimes, we add the same pair more than once!
// But because we only process each pair once due to the visited check,
// and each pair can only contribute to at most 3 items in this array,
// this array has O(n) elements.
while (i < pairsToProcess.length) {
const curXs = pairsToProcess[i]
i++
if (visited.has(curXs)) {
continue
}
visited.add(curXs)
if (!is_pair(curXs)) {
continue
}
pairsToProcess.push(head(curXs), tail(curXs))
}

// go through pairs in reverse to ensure the dependencies are resolved first
while (pairsToProcess.length > 0) {
const curXs = pairsToProcess.pop()
if (!is_pair(curXs)) {
continue
}
const h = head(curXs)
const t = tail(curXs)
const newTail = getListObject(t) // the reason why we need the above guarantee
const newXs =
is_null(newTail) || newTail instanceof ListObject
? new ListObject(pair(h, t)) // tail is a proper list
: pair(h, t) // it's not a proper list, make a copy of the pair so we can change references below
asListObjects.set(curXs, newXs)
}

for (const curXs of asListObjects.values()) {
if (is_pair(curXs)) {
set_head(curXs, getListObject(head(curXs)))
set_tail(curXs, getListObject(tail(curXs)))
} else if (curXs instanceof ListObject) {
set_head(curXs.listNode, getListObject(head(curXs.listNode)))
let newTail = getListObject(tail(curXs.listNode))
if (newTail instanceof ListObject) {
newTail = newTail.listNode
}
set_tail(curXs.listNode, newTail)
}
}
display(getListObject(xs), prepend)
return xs
}
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@ const globals = native0.globals;
const is_null = globals.previousScope.variables.get(\\"is_null\\").getValue();
const list = globals.previousScope.variables.get(\\"list\\").getValue();
const draw_data = globals.previousScope.variables.get(\\"draw_data\\").getValue();
const display_list = globals.previousScope.variables.get(\\"display_list\\").getValue();
const set_head = globals.previousScope.variables.get(\\"set_head\\").getValue();
const set_tail = globals.previousScope.variables.get(\\"set_tail\\").getValue();
const array_length = globals.previousScope.variables.get(\\"array_length\\").getValue();
@@ -208,6 +209,7 @@ const globals = native.globals;
const is_null = globals.previousScope.variables.get(\\"is_null\\").getValue();
const list = globals.previousScope.variables.get(\\"list\\").getValue();
const draw_data = globals.previousScope.variables.get(\\"draw_data\\").getValue();
const display_list = globals.previousScope.variables.get(\\"display_list\\").getValue();
const set_head = globals.previousScope.variables.get(\\"set_head\\").getValue();
const set_tail = globals.previousScope.variables.get(\\"set_tail\\").getValue();
const array_length = globals.previousScope.variables.get(\\"array_length\\").getValue();
2 changes: 1 addition & 1 deletion src/transpiler/__tests__/native.ts
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ test('Proper stringify-ing of arguments during potentially infinite iterative fu
[ function f(x) {
return f(x);
},
[() => 1, [[1, 2, 3], null]] ] ] ]) ...`)
[() => 1, [[1, 2, 3], null]]]]]) ...`)
})

test('test increasing time limit for functions', async () => {
42 changes: 40 additions & 2 deletions src/utils/stringify.ts
Original file line number Diff line number Diff line change
@@ -24,6 +24,20 @@ function indentify(indent: string, s: string): string {
.join('\n')
}

export interface ArrayLike {
replPrefix: string
replSuffix: string
replArrayContents: () => Value[]
}

function isArrayLike(v: Value) {
return (
typeof v.replPrefix === 'string' &&
typeof v.replSuffix === 'string' &&
typeof v.replArrayContents === 'function'
)
}

export const stringify = (
value: Value,
indent: number | string = 2,
@@ -37,8 +51,8 @@ export const stringify = (
const indentString = makeIndent(indent)
const arrPrefix = '[' + indentString.substring(1)
const objPrefix = '{' + indentString.substring(1)
const arrSuffix = indentString.substring(0, indentString.length - 1) + ']'
const objSuffix = indentString.substring(0, indentString.length - 1) + '}'
const arrSuffix = ']'
const objSuffix = '}'

// Util functions

@@ -79,6 +93,28 @@ ${indentify(indentString.repeat(indentLevel), valueStrs[1])}${arrSuffix}`
}
}

const stringifyArrayLike = (arrayLike: ArrayLike, indentLevel: number) => {
const prefix = arrayLike.replPrefix
const suffix = arrayLike.replSuffix
const prefixIndented = prefix + indentString.substring(prefix.length)
const suffixIndented = suffix
const xs = arrayLike.replArrayContents()

ancestors.add(arrayLike)
const valueStrs = xs.map(x => stringifyValue(x, 0))
ancestors.delete(arrayLike)

if (shouldMultiline(valueStrs)) {
// indent second element onwards to match with first element
return `${prefixIndented}${indentify(
indentString.repeat(indentLevel) + ' '.repeat(prefixIndented.length),
valueStrs.join(',\n')
).substring(prefixIndented.length)}${suffixIndented}`
} else {
return `${prefix}${valueStrs.join(', ')}${suffix}`
}
}

const stringifyObject = (obj: object, indentLevel: number) => {
ancestors.add(obj)
const valueStrs = Object.entries(obj).map(entry => {
@@ -121,6 +157,8 @@ ${indentify(indentString.repeat(indentLevel), valueStrs[1])}${arrSuffix}`
return v.toReplString()
} else if (Array.isArray(v)) {
return stringifyArray(v, indentLevel)
} else if (isArrayLike(v)) {
return stringifyArrayLike(v, indentLevel)
} else {
return stringifyObject(v, indentLevel)
}
4 changes: 2 additions & 2 deletions src/vm/__tests__/__snapshots__/svml-machine.ts.snap
Original file line number Diff line number Diff line change
@@ -1603,7 +1603,7 @@ display(permutations(list(1,2,3)));",
"[ [1, [2, [3, null]]],
[ [1, [3, [2, null]]],
[ [2, [1, [3, null]]],
[[2, [3, [1, null]]], [[3, [1, [2, null]]], [[3, [2, [1, null]]], null]]] ] ] ]",
[[2, [3, [1, null]]], [[3, [1, [2, null]]], [[3, [2, [1, null]]], null]]]]]]",
],
"errors": Array [],
"parsedErrors": "",
@@ -1792,7 +1792,7 @@ display(eval_stream(s, 10));",
"[ [1, 2],
[ [1, 3],
[ [2, 3],
[[1, 4], [[2, 4], [[1, 5], [[3, 4], [[1, 6], [[2, 5], [[1, 7], null]]]]]]] ] ] ]",
[[1, 4], [[2, 4], [[1, 5], [[3, 4], [[1, 6], [[2, 5], [[1, 7], null]]]]]]]]]]",
],
"errors": Array [],
"parsedErrors": "",
4 changes: 2 additions & 2 deletions src/vm/__tests__/svml-machine.ts
Original file line number Diff line number Diff line change
@@ -964,7 +964,7 @@ describe('standard program execution', () => {
"[ [1, [2, [3, null]]],
[ [1, [3, [2, null]]],
[ [2, [1, [3, null]]],
[[2, [3, [1, null]]], [[3, [1, [2, null]]], [[3, [2, [1, null]]], null]]] ] ] ]",
[[2, [3, [1, null]]], [[3, [1, [2, null]]], [[3, [2, [1, null]]], null]]]]]]",
]
`)
})
@@ -1000,7 +1000,7 @@ describe('standard program execution', () => {
"[ [1, 2],
[ [1, 3],
[ [2, 3],
[[1, 4], [[2, 4], [[1, 5], [[3, 4], [[1, 6], [[2, 5], [[1, 7], null]]]]]]] ] ] ]",
[[1, 4], [[2, 4], [[1, 5], [[3, 4], [[1, 6], [[2, 5], [[1, 7], null]]]]]]]]]]",
]
`)
})