Skip to content

Commit 4b011ea

Browse files
authored
Merge pull request #63 from thefrontside/modern-yaml-parser
migrate from `yaml-ast-parser` -> `yaml` package
2 parents 282a69b + a7d58aa commit 4b011ea

14 files changed

Lines changed: 415 additions & 346 deletions

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)