Skip to content

Commit 442af37

Browse files
committed
migrate from yaml-ast-parser -> yaml package
The module we were using the parse strings into yaml syntax trees was not maintained, and had an awkward API. This meant that implementing new features which dependend on YAML serialization and deserialization were destined to be difficult. This includes implementing the print function (#51) which is on the hot path for the first iteration of an executable program. This uses the `npm:yaml` module (https://github.com/eemeli/yaml) as the basis for converting strings into yaml AST. To do this, it uses a "read" function which does a 1:1 mapping of yaml ast values to PlatformScript values. the `read()` operation does not do anything beyond this. So, for example, `read("$a-ref")` will be a `PSString`, rather than a `PSRef`. There is a `recognize()` operation that converts the "literal platformscript" value into one that contains references, functions, and templates. The "evaluation pipeline" now looks like this: `pipe(read, recognize, eval)` Mostly this change is limited to the `read`, however it does have some small implications for the higher level apis because now references are "recognized", not read directly, and so a reference value is not a JavaScript string, but rather a `PSString`. The same applies for function definitions. We now store the `head` of the function as a reference to the `PSString` that gave rise to it. So, for example, in the lambda: ```yaml (x)=>: Hello %(x) ``` The `head` is the `PSString` `(x)=>` > 💡When we have macros, we will probably insert that after `recognize`, but > before `eval`. e.g. `pipe(read, recognize, macroexpand, eval)`
1 parent 282a69b commit 442af37

13 files changed

Lines changed: 340 additions & 261 deletions

File tree

convert.ts

Lines changed: 1 addition & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
import type {
2-
YAMLMapping,
3-
YAMLNode,
4-
YAMLScalar,
5-
YAMLSequence,
6-
} from "./deps.ts";
7-
import {
8-
PSLiteral,
9-
PSMapEntry,
10-
PSMapKey,
11-
PSString,
12-
PSTemplate,
13-
PSValue,
14-
} from "./types.ts";
15-
16-
import { parseYAML } from "./deps.ts";
17-
import * as data from "./data.ts";
1+
import { PSMapEntry, PSMapKey, PSValue } from "./types.ts";
182

193
/**
204
* Convert a JavaScript value into a PlatformScript value
@@ -78,139 +62,3 @@ export function ps2js(value: PSValue): unknown {
7862
return value.value;
7963
}
8064
}
81-
82-
/**
83-
* Convert source YAML into a PlatformScript Literal
84-
*/
85-
export function yaml2ps(node: YAMLNode): PSLiteral<PSValue> {
86-
if (node.kind === 0) { // String, Number, Boolean
87-
let scalar = node as YAMLScalar;
88-
if (
89-
scalar.singleQuoted || scalar.doubleQuoted ||
90-
typeof scalar.valueObject === "undefined"
91-
) {
92-
let expressions = matchTemplate(scalar.value);
93-
if (expressions.length > 0) {
94-
return createLiteral({
95-
type: "template",
96-
value: scalar.value,
97-
expressions,
98-
}, node);
99-
}
100-
let match = matchReference(scalar.value);
101-
if (match) {
102-
return createLiteral({
103-
type: "ref",
104-
value: scalar.value,
105-
key: match.key,
106-
path: match.path,
107-
}, node);
108-
}
109-
return createLiteral({
110-
type: "string",
111-
value: scalar.value,
112-
}, node);
113-
} else {
114-
let value = scalar.valueObject;
115-
let type = typeof scalar.valueObject;
116-
if (!["number", "boolean"].includes(type)) {
117-
throw new Error(`unknown scalar type: ${type}`);
118-
}
119-
return createLiteral({ type: type as "number" | "boolean", value }, node);
120-
}
121-
} else if (node.kind === 2) { // Map
122-
let mappings: YAMLMapping[] = node.mappings ?? [];
123-
let [first] = mappings;
124-
if (!first) {
125-
return { type: "map", value: new Map(), node };
126-
} else {
127-
let fnmatch = first.key.value.match(/^\(\s*(.*)\)\s*=>$/);
128-
if (fnmatch) {
129-
let param = fnmatch[1];
130-
let body = yaml2ps(first.value);
131-
return createLiteral({
132-
type: "fn",
133-
param: { name: param },
134-
value: {
135-
type: "platformscript",
136-
body,
137-
},
138-
}, node);
139-
} else {
140-
return createLiteral({
141-
type: "map",
142-
value: new Map(mappings.map((m) => {
143-
let value = yaml2ps(m.value);
144-
145-
// use method syntax
146-
let mmatch = m.key.value.match(/^\s*(.+)\((.*)\)\s*$/);
147-
if (mmatch) {
148-
let [, key, param] = mmatch;
149-
return [data.string(key), {
150-
type: "fn",
151-
param: { name: param },
152-
value: {
153-
type: "platformscript",
154-
body: value,
155-
},
156-
} as PSValue];
157-
} else {
158-
return [yaml2ps(m.key) as PSString, value];
159-
}
160-
})),
161-
}, node);
162-
}
163-
}
164-
} else if (node.kind === 3) { // List
165-
let list = node as YAMLSequence;
166-
return createLiteral({
167-
type: "list",
168-
value: list.items.map(yaml2ps),
169-
}, node);
170-
} else {
171-
console.dir({ node }, { depth: 10 });
172-
throw new Error(`unknown YAMLNode of kind ${node.kind}`);
173-
}
174-
}
175-
176-
function matchTemplate(value: string) {
177-
let valueIdx = 0;
178-
let exprIdx = 1;
179-
let regex = /%\(([\s\S]+?)\)/gmd;
180-
let i = value.matchAll(regex);
181-
182-
let expressions: PSTemplate["expressions"] = [];
183-
184-
for (let next = i.next(); !next.done; next = i.next()) {
185-
let match = next.value;
186-
let expression = yaml2ps(parseYAML(match[exprIdx]));
187-
188-
expressions.push({
189-
expression,
190-
191-
//@ts-expect-error RegExpMatchArray#indices not yet in the default TS lib
192-
range: match.indices[valueIdx],
193-
});
194-
}
195-
return expressions;
196-
}
197-
198-
function matchReference(value: string) {
199-
let pathIdx = 1;
200-
let match = value.match(/^\$(\S+)$/);
201-
202-
if (match) {
203-
let [key, ...path] = match[pathIdx].split(".");
204-
return {
205-
key,
206-
path,
207-
};
208-
}
209-
}
210-
211-
function createLiteral(value: PSValue, node: YAMLNode): PSLiteral {
212-
return Object.defineProperty(value, "node", {
213-
enumerable: false,
214-
value: node,
215-
}) as PSLiteral;
216-
}

data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function string(value: string): PSString {
2727
export function ref(key: string, path: string[]): PSRef {
2828
return {
2929
type: "ref",
30-
value: `$${[key].concat(path).join(".")}`,
30+
value: string(`$${[key].concat(path).join(".")}`),
3131
key,
3232
path,
3333
};

deps.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
export { load as parseYAML } from "https://esm.sh/yaml-ast-parser@0.0.43";
1+
export { default as yaml } from "https://esm.sh/yaml@2.2.1";
2+
export type { ParsedNode as YAMLParsedNode } from "https://esm.sh/yaml@2.2.1";
23
export * from "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.0/mod.ts";
34
export { resolve } from "https://deno.land/std@0.163.0/path/posix.ts";
4-
5-
export type {
6-
YAMLMapping,
7-
YAMLNode,
8-
YAMLScalar,
9-
YAMLSequence,
10-
} from "https://esm.sh/yaml-ast-parser@0.0.43";
115
export type {
126
Operation,
137
Task,

evaluate.ts

Lines changed: 19 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type {
22
PSEnv,
3-
PSFn,
43
PSLiteral,
54
PSMap,
65
PSMapKey,
@@ -9,8 +8,8 @@ import type {
98
} from "./types.ts";
109
import type { Operation } from "./deps.ts";
1110

12-
import { parseYAML } from "./deps.ts";
13-
import { yaml2ps } from "./convert.ts";
11+
import { read } from "./read.ts";
12+
import { recognize } from "./recognize.ts";
1413
import { concat, lookup, map, Maybe } from "./psmap.ts";
1514
import * as data from "./data.ts";
1615

@@ -19,7 +18,7 @@ type Segment = {
1918
value: string;
2019
} | {
2120
type: "expr";
22-
ref: PSLiteral;
21+
ref: PSValue;
2322
};
2423

2524
function* segments(t: PSTemplate): Generator<Segment> {
@@ -28,7 +27,7 @@ function* segments(t: PSTemplate): Generator<Segment> {
2827
let idx = 0;
2928
for (let expr of expressions) {
3029
let [start, end] = expr.range;
31-
let substr = t.value.slice(idx, start);
30+
let substr = t.value.value.slice(idx, start);
3231
yield {
3332
type: "const",
3433
value: substr,
@@ -39,16 +38,16 @@ function* segments(t: PSTemplate): Generator<Segment> {
3938
};
4039
idx = end;
4140
}
42-
if (idx < t.value.length) {
41+
if (idx < t.value.value.length) {
4342
yield {
4443
type: "const",
45-
value: t.value.slice(idx),
44+
value: t.value.value.slice(idx),
4645
};
4746
}
4847
} else {
4948
yield {
5049
type: "const",
51-
value: t.value,
50+
value: t.value.value,
5251
};
5352
}
5453
}
@@ -59,7 +58,7 @@ export function createYSEnv(parent = global): PSEnv {
5958
let scope = concat(parent, context);
6059
let env = createYSEnv(scope);
6160

62-
let value = yield* bind($value, scope, []);
61+
let value = yield* bind(recognize($value), scope, []);
6362

6463
if (value.type === "ref") {
6564
return value;
@@ -82,7 +81,7 @@ export function createYSEnv(parent = global): PSEnv {
8281
`${fn.value} is not a function. It is of type '${fn.type}'`,
8382
);
8483
}
85-
return yield* env.call(fn, arg, rest);
84+
return yield* env.call(fn, yield* env.eval(arg), rest);
8685
} else if (value.type === "map") {
8786
let entries: [PSMapKey, PSValue][] = [];
8887
for (let [k, v] of value.value.entries()) {
@@ -179,52 +178,8 @@ export const global: PSMap = {
179178
]),
180179
};
181180

182-
export function parse(source: string, filename = "script"): PSLiteral<PSValue> {
183-
let yaml = parseYAML(source, { filename });
184-
let [error] = yaml.errors;
185-
if (!yaml) {
186-
throw new SyntaxError(`empty string is not a YAML Document`);
187-
} else if (error) {
188-
throw error;
189-
}
190-
return yaml2ps(yaml);
191-
}
192-
193-
export function strip(literal: PSValue): PSValue {
194-
//@ts-expect-error stripping is safe because we're just dropping the node
195-
let { node: _, ...value } = literal;
196-
if (value.type === "map") {
197-
let map = value.value;
198-
return {
199-
type: "map",
200-
value: new Map(
201-
[...map.entries()].map(([k, v]) => [strip(k) as PSMapKey, strip(v)]),
202-
),
203-
};
204-
} else if (value.type === "list") {
205-
let list = value.value;
206-
return {
207-
type: "list",
208-
value: list.map((val) => strip(val as PSLiteral<PSValue>)),
209-
};
210-
} else if (value.type === "fn" && value.value.type === "platformscript") {
211-
let { body } = value.value;
212-
return {
213-
...value,
214-
value: {
215-
type: "platformscript",
216-
body: strip(body),
217-
},
218-
};
219-
} else if (value.type === "fncall") {
220-
return {
221-
...value,
222-
value: strip(value.value) as PSFn,
223-
arg: strip(value.arg),
224-
};
225-
} else {
226-
return value;
227-
}
181+
export function parse(source: string, _filename = "script"): PSValue {
182+
return read(source);
228183
}
229184

230185
// this is kinda cheesy. We should beef up this check.
@@ -321,44 +276,21 @@ function* bind(
321276
}
322277
return data.list(result);
323278
} else if (value.type === "map") {
324-
let entries = [...value.value.entries()];
325-
let [first, ...rest] = entries;
326-
327-
if (!first) {
328-
//TODO: what is this for?
329-
return { type: "boolean", value: false };
330-
} else {
331-
let [key, value] = first;
332-
if (key.type === "ref") {
333-
let fn = mask.includes(key.key) ? key : yield* bind(key, scope, mask);
334-
if (fn.type !== "fn" && fn.type !== "ref") {
335-
throw new Error(
336-
`'${key.value}' is not a function, it is a ${fn.type}`,
337-
);
338-
}
339-
return {
340-
type: "fncall",
341-
value: fn,
342-
arg: yield* bind(value, scope, mask),
343-
rest: { type: "map", value: new Map(rest) },
344-
};
345-
} else {
346-
let $entries = [] as [PSMapKey, PSValue][];
347-
for (let [k, v] of entries) {
348-
$entries.push([k, yield* bind(v, scope, mask)]);
349-
}
350-
return {
351-
type: "map",
352-
value: new Map($entries),
353-
};
354-
}
279+
let $entries = [] as [PSMapKey, PSValue][];
280+
for (let [k, v] of value.value.entries()) {
281+
$entries.push([k, yield* bind(v, scope, mask)]);
355282
}
283+
return {
284+
type: "map",
285+
value: new Map($entries),
286+
};
356287
} else if (value.type === "fn" && value.value.type === "platformscript") {
357288
let { param, value: { body } } = value;
358289
return {
359290
...value,
360291
value: {
361292
type: "platformscript",
293+
head: value.value.head,
362294
body: yield* bind(body, scope, mask.concat(param.name)),
363295
},
364296
};

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./types.ts";
22
export * from "./convert.ts";
3+
export * from "./read.ts";
34
export * from "./evaluate.ts";
45
export * from "./load.ts";
56
export * from "./platformscript.ts";

psmap.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ export type Maybe<T> =
3737
value: void;
3838
};
3939

40-
export function lookup(key: string, map: PSMap): Maybe<PSValue> {
40+
export function lookup(
41+
key: string | number | boolean,
42+
map: PSMap,
43+
): Maybe<PSValue> {
4144
for (let entry of map.value.entries()) {
4245
let [k, value] = entry;
43-
if (k.value.toString() === key) {
46+
if (k.value === key) {
4447
return { type: "just", value };
4548
}
4649
}

0 commit comments

Comments
 (0)