myxo-lang 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONCURRENCY.md +207 -0
- package/HOW_IT_WORKS.md +235 -0
- package/INDEPENDENCE.md +24 -0
- package/MYXO_PROMPT.md +139 -0
- package/README.md +494 -0
- package/ROADMAP.md +200 -0
- package/SPEC.md +181 -0
- package/VISION.md +249 -0
- package/builtins.js +361 -0
- package/command-fence.js +75 -0
- package/errors.js +45 -0
- package/examples/agent.myx +31 -0
- package/examples/fenced-agent.js +78 -0
- package/examples/fib.myx +11 -0
- package/examples/fibers.myx +57 -0
- package/examples/flow-routing.myx +12 -0
- package/examples/geo.myx +12 -0
- package/examples/hello.myx +2 -0
- package/examples/host.js +30 -0
- package/examples/james-myxo-demo.js +60 -0
- package/examples/living-mesh.myx +22 -0
- package/examples/match.myx +18 -0
- package/examples/mathlib.js +8 -0
- package/examples/mathlib.pl +11 -0
- package/examples/mathlib.py +23 -0
- package/examples/mcp-host.js +37 -0
- package/examples/nexus-mesh.myx +22 -0
- package/examples/nexus.myx +29 -0
- package/examples/ouroboros.myx +2 -0
- package/examples/outward-gate.myx +23 -0
- package/examples/physarum.myx +75 -0
- package/examples/polyglot-host.js +17 -0
- package/examples/polyglot.myx +11 -0
- package/examples/resilient.myx +28 -0
- package/examples/scheduler.myx +46 -0
- package/examples/the-law.myx +27 -0
- package/examples/use-geo.myx +9 -0
- package/format.js +206 -0
- package/interpreter.js +1092 -0
- package/lexer.js +173 -0
- package/mcp-bridge.js +77 -0
- package/mcp-framing.js +34 -0
- package/mcp-server.js +57 -0
- package/myxo-concurrent.js +110 -0
- package/myxo-live-worker.js +30 -0
- package/myxo-live.js +83 -0
- package/myxo-lsp.js +226 -0
- package/myxo-par-worker.js +39 -0
- package/myxo-plan.js +207 -0
- package/myxo-run.js +63 -0
- package/myxo.js +296 -0
- package/package.json +27 -0
- package/parser.js +662 -0
- package/polyglot-host.js +39 -0
- package/polyglot.js +191 -0
- package/receipt.js +71 -0
- package/std.myx +88 -0
- package/tools/memo-fuzz.js +224 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# physarum.myx — the slime mold's law, run properly, in the language named after it.
|
|
2
|
+
#
|
|
3
|
+
# Physarum polycephalum has no brain, yet it rebuilds the Tokyo rail network:
|
|
4
|
+
# it floods every route, then each tube THICKENS with the flow it carries and
|
|
5
|
+
# THINS continuously on its own. Useful paths reinforce; the rest decay and
|
|
6
|
+
# vanish. Tero (2010) showed the network's character is set by HOW flow feeds
|
|
7
|
+
# back into a tube: sharp feedback (flow squared) → one winner survives (lean);
|
|
8
|
+
# soft feedback (sqrt of flow) → the detours stay alive as backups (resilient).
|
|
9
|
+
# One organism, one dial. Myxo's old mesh did a toy version (+1 per read, manual
|
|
10
|
+
# prune); this is the real dynamic, and the dial is passed in as a function.
|
|
11
|
+
#
|
|
12
|
+
# Run: node myxo.js examples/physarum.myx
|
|
13
|
+
|
|
14
|
+
# Two food sources (A, B). Each reachable by a short DIRECT route and a longer
|
|
15
|
+
# DETOUR. Shorter tubes conduct more flow, so direct routes win — but how hard
|
|
16
|
+
# they win depends entirely on the feedback shape.
|
|
17
|
+
seed len = { "A.direct": 1, "A.detour": 3, "B.direct": 1, "B.detour": 4 }
|
|
18
|
+
seed dest = { "A.direct": "A", "A.detour": "A", "B.direct": "B", "B.detour": "B" }
|
|
19
|
+
seed dests = ["A", "B"]
|
|
20
|
+
seed REINFORCE = 0.9
|
|
21
|
+
seed FADE = 0.92 # gentle continuous decay — same for both runs
|
|
22
|
+
|
|
23
|
+
agent fresh() {
|
|
24
|
+
report { "A.direct": 1, "A.detour": 1, "B.direct": 1, "B.detour": 1 }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# One forage cycle. `shape` is the feedback law — the dial Tero found.
|
|
28
|
+
# Flow splits across a destination's routes by conductance (strength / length);
|
|
29
|
+
# each tube thickens by REINFORCE * shape(flow); then EVERY tube thins by FADE.
|
|
30
|
+
agent step(cond, shape) {
|
|
31
|
+
for each d in dests {
|
|
32
|
+
seed total = 0
|
|
33
|
+
for each r in keys(cond) {
|
|
34
|
+
when dest[r] == d { total = total + (cond[r] / len[r]) }
|
|
35
|
+
}
|
|
36
|
+
for each r in keys(cond) {
|
|
37
|
+
when dest[r] == d {
|
|
38
|
+
seed flow = (cond[r] / len[r]) / total
|
|
39
|
+
cond[r] = cond[r] + REINFORCE * shape(flow)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for each r in keys(cond) { cond[r] = cond[r] * FADE }
|
|
44
|
+
report cond
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Run the network to rest, then show each tube's share of its destination.
|
|
48
|
+
agent simulate(shape, cycles, label) {
|
|
49
|
+
seed cond = fresh()
|
|
50
|
+
seed t = 0
|
|
51
|
+
reinforce t < cycles {
|
|
52
|
+
cond = step(cond, shape)
|
|
53
|
+
t = t + 1
|
|
54
|
+
}
|
|
55
|
+
emit ""
|
|
56
|
+
emit label
|
|
57
|
+
for each d in dests {
|
|
58
|
+
seed tot = 0
|
|
59
|
+
for each r in keys(cond) { when dest[r] == d { tot = tot + cond[r] } }
|
|
60
|
+
for each r in keys(cond) {
|
|
61
|
+
when dest[r] == d {
|
|
62
|
+
seed pct = floor(cond[r] / tot * 100)
|
|
63
|
+
seed mark = "pruned"
|
|
64
|
+
when pct >= 20 { mark = "ALIVE" }
|
|
65
|
+
emit " " + r + " " + str(pct) + "% of route to " + d + " [" + mark + "]"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
emit "Physarum routing in Myxo — same forage, one dial (the feedback shape):"
|
|
72
|
+
simulate(agent(q) { report q * q }, 18, "[ LEAN — sharp feedback (flow squared) ] one winner, detours starve:")
|
|
73
|
+
simulate(agent(q) { report sqrt(q) }, 18, "[ RESILIENT — soft feedback (sqrt flow) ] detours kept as live backups:")
|
|
74
|
+
emit ""
|
|
75
|
+
emit "Brainless. One rule. Efficiency or fault-tolerance, by a single dial — exactly what Tero found."
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Host: wire the language runners as FENCED capabilities, run the .myx, print the audit ledger.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { run } = require('../myxo');
|
|
6
|
+
const { pycall, pyeval, jscall, jseval, plcall, pleval, sh } = require('../polyglot');
|
|
7
|
+
|
|
8
|
+
let audit = [];
|
|
9
|
+
const src = fs.readFileSync(path.join(__dirname, 'polyglot.myx'), 'utf8');
|
|
10
|
+
const out = run(src, {
|
|
11
|
+
capture: true,
|
|
12
|
+
natives: { pycall, pyeval, jscall, jseval, plcall, pleval, sh }, // language runners -> fenced capabilities (gated by `needs`)
|
|
13
|
+
onAudit: (l) => { audit = l; },
|
|
14
|
+
});
|
|
15
|
+
process.stdout.write(out);
|
|
16
|
+
console.log('--- audit ledger (the law saw every cross-language call) ---');
|
|
17
|
+
for (const e of audit) console.log(' ', e.cap, e.ok ? 'OK -> ' + e.result : 'REFUSED (' + e.error + ')');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Myxo as the connective layer for ALL languages: each language is a FENCED pathway, governed by the one law.
|
|
2
|
+
# Python, Node, and Perl plug in through the SAME contract (defineLang); one audit ledger records every call.
|
|
3
|
+
needs pycall, jscall, plcall, sh
|
|
4
|
+
|
|
5
|
+
emit "python add =", pycall("examples/mathlib.py", "add", 2, 3)
|
|
6
|
+
emit "node add =", jscall("examples/mathlib.js", "add", 10, 20)
|
|
7
|
+
emit "perl add =", plcall("examples/mathlib.pl", "add", 4, 5)
|
|
8
|
+
|
|
9
|
+
seed s = jscall("examples/mathlib.js", "stats", [10, 4, 7, 2])
|
|
10
|
+
emit "node stats: sum =", s["sum"], " max =", s["max"], " n =", s["n"]
|
|
11
|
+
emit "shell =", sh("echo one-contract-many-languages")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# resilient.myx — error handling + the expanded stdlib, the Nexus way.
|
|
2
|
+
# Shows: words/split, mesh-as-counter, value-returning `or`, attempt/rescue/fail.
|
|
3
|
+
|
|
4
|
+
# Rank words by how often they appear (split + mesh + the `or default` idiom).
|
|
5
|
+
agent word_counts(text) {
|
|
6
|
+
seed counts = {}
|
|
7
|
+
for each w in words(lower(text)) {
|
|
8
|
+
counts[w] = (counts[w] or 0) + 1 # missing key is void; void or 0 -> 0
|
|
9
|
+
}
|
|
10
|
+
report counts
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
seed text = "the mesh reinforces the path the mesh keeps"
|
|
14
|
+
emit "counts:", word_counts(text)
|
|
15
|
+
|
|
16
|
+
# A safe lookup: a pathway that might not exist, handled instead of crashing.
|
|
17
|
+
agent safe_strength(counts, key) {
|
|
18
|
+
attempt {
|
|
19
|
+
when not has(counts, key) { fail "no such pathway: " + key }
|
|
20
|
+
report counts[key]
|
|
21
|
+
} rescue err {
|
|
22
|
+
report err["message"] # turn a failure into a graceful value
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
seed counts = word_counts(text)
|
|
27
|
+
emit safe_strength(counts, "mesh") # present -> 2
|
|
28
|
+
emit safe_strength(counts, "wallet") # absent -> a clean message, no crash
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# scheduler.myx — the Physarum scheduler: the reinforce/decay law applied to EXECUTION.
|
|
2
|
+
# `schedule(name, workers, items)` distributes a batch across a learning pool of worker-agents, runs the
|
|
3
|
+
# chunks on real worker threads IN PARALLEL, and updates each worker's conductance from its measured speed —
|
|
4
|
+
# so fast workers pull more of the flux on later calls, and a failed worker decays and its items reroute.
|
|
5
|
+
#
|
|
6
|
+
# A worker takes a CHUNK (a list of items) and returns a results list — one result per item, in order.
|
|
7
|
+
|
|
8
|
+
# --- a simple homogeneous pool: square every number, across two workers, in parallel ---
|
|
9
|
+
agent square(chunk) {
|
|
10
|
+
seed out = []
|
|
11
|
+
for each x in chunk { out = out + [x * x] }
|
|
12
|
+
report out
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
emit "squares: " + str(schedule("squares", [square, square], [1, 2, 3, 4, 5, 6, 7, 8]))
|
|
16
|
+
|
|
17
|
+
# --- a heterogeneous pool: one fast worker, one slow worker; watch the pool LEARN ---
|
|
18
|
+
agent fast(chunk) {
|
|
19
|
+
seed out = []
|
|
20
|
+
for each x in chunk { out = out + [x] }
|
|
21
|
+
report out
|
|
22
|
+
}
|
|
23
|
+
agent slow(chunk) {
|
|
24
|
+
seed out = []
|
|
25
|
+
for each x in chunk {
|
|
26
|
+
seed j = 0
|
|
27
|
+
reinforce j < 300000 { j = j + 1 } # this tube is genuinely slower
|
|
28
|
+
out = out + [x]
|
|
29
|
+
}
|
|
30
|
+
report out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# run a few batches so the pool can measure speed and re-balance the flux
|
|
34
|
+
reinforce 4 times {
|
|
35
|
+
schedule("work", [fast, slow], [1, 2, 3, 4, 5, 6, 7, 8])
|
|
36
|
+
}
|
|
37
|
+
emit "learned conductances (fast should dominate): " + str(flows("work"))
|
|
38
|
+
|
|
39
|
+
# --- failover: a dead tube; its work reroutes to the survivor, result intact ---
|
|
40
|
+
agent boom(chunk) { fail "this tube is dead" }
|
|
41
|
+
agent ok(chunk) {
|
|
42
|
+
seed out = []
|
|
43
|
+
for each x in chunk { out = out + [x * 10] }
|
|
44
|
+
report out
|
|
45
|
+
}
|
|
46
|
+
emit "failover (boom dies, ok takes over): " + str(schedule("resilient", [boom, ok], [1, 2, 3]))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# the-law.myx — the core law of the Nexus, executed.
|
|
2
|
+
# "Agents do not merge. Useful pathways reinforce. Useless pathways decay."
|
|
3
|
+
# Run with --trace to watch the mesh: node myxo.js examples/the-law.myx --trace
|
|
4
|
+
|
|
5
|
+
agent reinforce_path(name, pulses) {
|
|
6
|
+
seed strength = 0
|
|
7
|
+
reinforce strength < pulses {
|
|
8
|
+
strength = strength + 1
|
|
9
|
+
}
|
|
10
|
+
report name + " reinforced to " + str(strength)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
seed telegram = reinforce_path("telegram", 5)
|
|
14
|
+
seed wallet = reinforce_path("wallet", 3)
|
|
15
|
+
seed bridge = reinforce_path("bridge", 1)
|
|
16
|
+
|
|
17
|
+
emit telegram
|
|
18
|
+
emit wallet
|
|
19
|
+
emit bridge
|
|
20
|
+
|
|
21
|
+
# Useful pathways reinforce through use — reading telegram raises its strength.
|
|
22
|
+
emit telegram
|
|
23
|
+
emit telegram
|
|
24
|
+
|
|
25
|
+
# A pathway gone quiet is let go.
|
|
26
|
+
decay bridge
|
|
27
|
+
emit "bridge has decayed from the mesh"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# use-geo.myx — weave a strand and use what it exposes (v0.3 modules).
|
|
2
|
+
|
|
3
|
+
weave "geo.myx" # flat: pulls area_circle, area_rect, TAU into scope
|
|
4
|
+
emit round(area_circle(2), 2) # PI * 4 -> 12.57
|
|
5
|
+
emit area_rect(3, 4) # 12
|
|
6
|
+
emit round(TAU, 4) # 6.2832
|
|
7
|
+
|
|
8
|
+
weave "geo.myx" as geo # namespaced: the exposed names as a mesh
|
|
9
|
+
emit round(geo["area_circle"](1), 2) # PI -> 3.14
|
package/format.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// format.js - canonical Myxo source printer. It formats the parsed AST, so invalid code is refused instead of
|
|
3
|
+
// guessed through. Comments ARE preserved: they aren't in the AST, so we collect them from the REAL lexer
|
|
4
|
+
// (string/interpolation-aware) and re-attach by line — own-line comments above the next statement, a trailing
|
|
5
|
+
// comment on its statement's line. v1 scope is statement-granular: none are ever dropped or fabricated, but a
|
|
6
|
+
// comment trailing a one-line block or a multi-line construct, or one inside an inline `agent(){}` expression,
|
|
7
|
+
// may render on its own line / shift to the nearest statement boundary. `--drop-comments` strips them.
|
|
8
|
+
|
|
9
|
+
const { parse } = require('./parser');
|
|
10
|
+
const { tokenize } = require('./lexer');
|
|
11
|
+
|
|
12
|
+
const PREC = { or: 1, and: 2, '==': 3, '!=': 3, '>': 4, '<': 4, '>=': 4, '<=': 4, '+': 5, '-': 5, '*': 6, '/': 6, '%': 6 };
|
|
13
|
+
const IND = ' ';
|
|
14
|
+
|
|
15
|
+
function q(s) { return JSON.stringify(String(s)); }
|
|
16
|
+
function pad(n) { return IND.repeat(n); }
|
|
17
|
+
|
|
18
|
+
function formatSource(src, opts = {}) {
|
|
19
|
+
if (opts.dropComments) return formatProgram(parse(src), []);
|
|
20
|
+
const comments = [];
|
|
21
|
+
tokenize(src, comments); // the real, string-aware scanner — never mistakes a '#' inside a string for a comment
|
|
22
|
+
return formatProgram(parse(src), comments);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function newCtx(comments) { return { comments: comments || [], i: 0 }; }
|
|
26
|
+
function ownLineComments(ctx, beforeLine, level) { // flush every comment strictly before `beforeLine`, each on its own line
|
|
27
|
+
let s = '';
|
|
28
|
+
while (ctx.i < ctx.comments.length && ctx.comments[ctx.i].line < beforeLine) {
|
|
29
|
+
const c = ctx.comments[ctx.i++];
|
|
30
|
+
s += pad(level) + (c.text ? '# ' + c.text : '#') + '\n';
|
|
31
|
+
}
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
function trailingComment(ctx, line) { // a comment that trails code on this exact line
|
|
35
|
+
if (ctx.i < ctx.comments.length && ctx.comments[ctx.i].line === line && ctx.comments[ctx.i].trailing) {
|
|
36
|
+
const c = ctx.comments[ctx.i++];
|
|
37
|
+
return c.text ? ' # ' + c.text : ' #';
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// A statement's LAST source line — its closing `}` for a block statement — so a comment trailing that line
|
|
43
|
+
// attaches to the whole statement, not to whatever inner statement happens to start on the same line.
|
|
44
|
+
function stmtEndLine(s) {
|
|
45
|
+
const b = (s.elseBlock && s.elseBlock.endLine) || (s.catchBlock && s.catchBlock.endLine)
|
|
46
|
+
|| (s.body && s.body.endLine) || (s.thenBlock && s.thenBlock.endLine) || s.matchEnd;
|
|
47
|
+
return b || s.line;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatProgram(ast, comments = []) {
|
|
51
|
+
const ctx = newCtx(comments);
|
|
52
|
+
let out = '';
|
|
53
|
+
for (const s of ast.body) {
|
|
54
|
+
out += ownLineComments(ctx, s.line, 0) + fmtStmt(s, 0, ctx) + trailingComment(ctx, stmtEndLine(s)) + '\n';
|
|
55
|
+
}
|
|
56
|
+
out += ownLineComments(ctx, Infinity, 0); // footer / trailing comments after the last statement
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fmtBlock(body, level, ctx = newCtx()) {
|
|
61
|
+
const tail = body.endLine ? () => ownLineComments(ctx, body.endLine, level + 1).replace(/\n+$/, '') : () => '';
|
|
62
|
+
if (!body.length) { const t = tail(); return t ? ' {\n' + t + '\n' + pad(level) + '}' : ' {}'; }
|
|
63
|
+
const parts = body.map(s => ownLineComments(ctx, s.line, level + 1) + fmtStmt(s, level + 1, ctx) + trailingComment(ctx, stmtEndLine(s)));
|
|
64
|
+
let inner = parts.join('\n');
|
|
65
|
+
const t = tail(); // comments after the last statement but before the closing brace stay INSIDE
|
|
66
|
+
if (t) inner += '\n' + t;
|
|
67
|
+
return ' {\n' + inner + '\n' + pad(level) + '}';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fmtParams(params) {
|
|
71
|
+
return '(' + params.map(p => {
|
|
72
|
+
if (p.rest) return '...' + p.name;
|
|
73
|
+
return p.name + (p.paramType ? ': ' + p.paramType : '') + (p.def != null ? ' = ' + fmtExpr(p.def) : '');
|
|
74
|
+
}).join(', ') + ')';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fmtNeed(item) {
|
|
78
|
+
if (!item.limit) return item.name;
|
|
79
|
+
const parts = [];
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(item.limit, 'max')) parts.push('max ' + item.limit.max);
|
|
81
|
+
if (Object.prototype.hasOwnProperty.call(item.limit, 'total')) parts.push('total ' + item.limit.total);
|
|
82
|
+
return item.name + '(' + parts.join(', ') + ')';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fmtStmt(s, level, ctx = newCtx()) {
|
|
86
|
+
const p = pad(level);
|
|
87
|
+
switch (s.type) {
|
|
88
|
+
case 'Seed': return p + `seed ${s.name}${s.declType ? ': ' + s.declType : ''} = ${fmtExpr(s.value)}`;
|
|
89
|
+
case 'SeedDestructure': return p + `seed ${fmtPattern(s.pattern)} = ${fmtExpr(s.value)}`;
|
|
90
|
+
case 'Decay': return p + `decay ${fmtExpr(s.target)}`;
|
|
91
|
+
case 'Emit': return p + 'emit ' + s.args.map(a => fmtExpr(a)).join(', ');
|
|
92
|
+
case 'When': {
|
|
93
|
+
let out = p + `when ${fmtExpr(s.cond)}` + fmtBlock(s.thenBlock, level, ctx);
|
|
94
|
+
if (s.elseBlock) {
|
|
95
|
+
if (s.elseBlock.length === 1 && s.elseBlock[0].type === 'When') {
|
|
96
|
+
out += ' otherwise ' + fmtStmt(s.elseBlock[0], 0, ctx);
|
|
97
|
+
} else {
|
|
98
|
+
out += ' otherwise' + fmtBlock(s.elseBlock, level, ctx);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
case 'Reinforce': return p + `reinforce ${fmtExpr(s.cond)}` + fmtBlock(s.body, level, ctx);
|
|
104
|
+
case 'ReinforceTimes': return p + `reinforce ${fmtExpr(s.count)} times` + fmtBlock(s.body, level, ctx);
|
|
105
|
+
case 'ForEach': return p + `for each ${s.varName} in ${fmtExpr(s.iterable)}` + fmtBlock(s.body, level, ctx);
|
|
106
|
+
case 'Agent': return p + `agent ${s.name}${fmtParams(s.params)}${s.returnType ? ': ' + s.returnType : ''}` + fmtBlock(s.body, level, ctx);
|
|
107
|
+
case 'Report': return p + (s.value && s.value.type !== 'Void' ? 'report ' + fmtExpr(s.value) : 'report');
|
|
108
|
+
case 'Attempt': return p + 'attempt' + fmtBlock(s.tryBlock, level, ctx) + ` rescue ${s.errName}` + fmtBlock(s.catchBlock, level, ctx);
|
|
109
|
+
case 'Fail': {
|
|
110
|
+
const isDefault = s.value && s.value.type === 'String' && s.value.value === 'failed';
|
|
111
|
+
return p + (isDefault ? 'fail' : 'fail ' + fmtExpr(s.value));
|
|
112
|
+
}
|
|
113
|
+
case 'Weave': return p + 'weave ' + fmtExpr(s.path) + (s.alias ? ` as ${s.alias}` : '');
|
|
114
|
+
case 'Expose': return p + `expose ${s.name}`;
|
|
115
|
+
case 'Needs': return p + 'needs ' + s.items.map(fmtNeed).join(', ');
|
|
116
|
+
case 'Assign': return p + `${fmtExpr(s.target)} = ${fmtExpr(s.value)}`;
|
|
117
|
+
case 'Test': return p + `test ${q(s.name)}` + fmtBlock(s.body, level, ctx);
|
|
118
|
+
case 'Expect': return p + 'expect ' + fmtMatcher(s);
|
|
119
|
+
case 'Match': {
|
|
120
|
+
if (!s.arms.length) return p + `match ${fmtExpr(s.subject)} {}`;
|
|
121
|
+
const arms = s.arms.map(a => pad(level + 1) + fmtPattern(a.pattern) + fmtBlock(a.body, level + 1, ctx)).join('\n');
|
|
122
|
+
return p + `match ${fmtExpr(s.subject)} {\n${arms}\n${pad(level)}}`;
|
|
123
|
+
}
|
|
124
|
+
case 'Give': return p + `give ${fmtExpr(s.value)} to ${fmtExpr(s.channel)}`;
|
|
125
|
+
case 'Take': return p + `take ${s.name} from ${fmtExpr(s.channel)}`;
|
|
126
|
+
case 'Yield': return p + 'yield';
|
|
127
|
+
case 'ExpressionStatement': return p + fmtExpr(s.expr);
|
|
128
|
+
default: throw new Error(`cannot format statement type ${s.type}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function fmtMatcher(s) {
|
|
133
|
+
const a = fmtExpr(s.actual);
|
|
134
|
+
const m = s.matcher;
|
|
135
|
+
switch (m.kind) {
|
|
136
|
+
case 'is': return `${a} is ${fmtExpr(m.expected)}`;
|
|
137
|
+
case 'isnot': return `${a} is not ${fmtExpr(m.expected)}`;
|
|
138
|
+
case 'fail': return `${a} to fail`;
|
|
139
|
+
case 'failwith': return `${a} to fail with ${fmtExpr(m.expected)}`;
|
|
140
|
+
default: return a; // truthy
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function fmtPattern(pp) {
|
|
145
|
+
switch (pp.type) {
|
|
146
|
+
case 'PWild': return '_';
|
|
147
|
+
case 'PBind': return pp.name;
|
|
148
|
+
case 'PLit': return fmtExpr(pp.expr);
|
|
149
|
+
case 'PList': {
|
|
150
|
+
const parts = pp.elements.map(fmtPattern);
|
|
151
|
+
if (pp.rest) parts.push('...' + pp.rest);
|
|
152
|
+
return '[' + parts.join(', ') + ']';
|
|
153
|
+
}
|
|
154
|
+
case 'PMesh': return pp.pairs.length ? '{ ' + pp.pairs.map(pr =>
|
|
155
|
+
(pr.pattern.type === 'PBind' && pr.pattern.name === pr.key) ? pr.key : `${q(pr.key)}: ${fmtPattern(pr.pattern)}`
|
|
156
|
+
).join(', ') + ' }' : '{}';
|
|
157
|
+
default: throw new Error(`cannot format pattern ${pp.type}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function exprPrec(e) {
|
|
162
|
+
if (e.type === 'Pipe') return 0;
|
|
163
|
+
if (e.type === 'Binary') return PREC[e.op] || 0;
|
|
164
|
+
if (e.type === 'Unary') return 7;
|
|
165
|
+
if (e.type === 'Call' || e.type === 'Index') return 9;
|
|
166
|
+
return 10;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function par(s, need) { return need ? '(' + s + ')' : s; }
|
|
170
|
+
|
|
171
|
+
function fmtExpr(e, parent = 0, side = '') {
|
|
172
|
+
switch (e.type) {
|
|
173
|
+
case 'Number': return String(e.value);
|
|
174
|
+
case 'String': return q(e.value);
|
|
175
|
+
case 'Bool': return e.value ? 'live' : 'dead';
|
|
176
|
+
case 'Void': return 'void';
|
|
177
|
+
case 'Identifier': return e.name;
|
|
178
|
+
case 'Interp':
|
|
179
|
+
return '"' + e.parts.map(part => part.type === 'String'
|
|
180
|
+
? String(part.value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/{/g, '\\{')
|
|
181
|
+
: '{' + fmtExpr(part) + '}').join('') + '"';
|
|
182
|
+
case 'Unary': {
|
|
183
|
+
const mine = exprPrec(e);
|
|
184
|
+
const out = e.op === 'not' ? `not ${fmtExpr(e.operand, mine)}` : '-' + fmtExpr(e.operand, mine);
|
|
185
|
+
return par(out, mine < parent);
|
|
186
|
+
}
|
|
187
|
+
case 'Binary': {
|
|
188
|
+
const mine = exprPrec(e);
|
|
189
|
+
const left = fmtExpr(e.left, mine, 'left');
|
|
190
|
+
const right = fmtExpr(e.right, mine + (['-', '/', '%'].includes(e.op) ? 1 : 0), 'right');
|
|
191
|
+
return par(`${left} ${e.op} ${right}`, mine < parent || (mine === parent && side === 'right'));
|
|
192
|
+
}
|
|
193
|
+
case 'Pipe': return par(`${fmtExpr(e.left, 0)} | ${fmtExpr(e.right, 1)}`, parent > 0);
|
|
194
|
+
case 'Call': return fmtExpr(e.callee, 9) + '(' + e.args.map(a => fmtExpr(a)).join(', ') + ')';
|
|
195
|
+
case 'Index': return fmtExpr(e.object, 9) + '[' + fmtExpr(e.index) + ']';
|
|
196
|
+
case 'List': return '[' + e.elements.map(x => fmtExpr(x)).join(', ') + ']';
|
|
197
|
+
case 'Mesh': return e.pairs.length ? '{ ' + e.pairs.map(([k, v]) => `${fmtExpr(k)}: ${fmtExpr(v)}`).join(', ') + ' }' : '{}';
|
|
198
|
+
case 'AgentExpr': return 'agent' + fmtParams(e.params) + (e.returnType ? ': ' + e.returnType : '') + fmtBlock(e.body, 0);
|
|
199
|
+
case 'Dispatch': return 'dispatch ' + fmtExpr(e.call);
|
|
200
|
+
case 'Gather': return 'gather ' + fmtExpr(e.expr, 8);
|
|
201
|
+
case 'Spawn': return 'spawn ' + fmtExpr(e.call);
|
|
202
|
+
default: throw new Error(`cannot format expression type ${e.type}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = { formatSource, formatProgram };
|