@jigs.design/compiler 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +28 -2
- package/dist/ast.d.ts +78 -0
- package/dist/block-hoist.js +237 -0
- package/dist/check-code.d.ts +46 -0
- package/dist/check-code.js +700 -0
- package/dist/codegen.d.ts +8 -0
- package/dist/codegen.js +219 -0
- package/dist/compile.d.ts +177 -0
- package/dist/compile.js +950 -0
- package/dist/hoist.d.ts +20 -0
- package/dist/hoist.js +76 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/offsets.d.ts +18 -0
- package/dist/offsets.js +40 -0
- package/dist/parse.d.ts +16 -0
- package/dist/parse.js +690 -0
- package/dist/patch.d.ts +55 -0
- package/dist/patch.js +140 -0
- package/dist/script.d.ts +14 -0
- package/dist/script.js +695 -0
- package/dist/signature.d.ts +5 -0
- package/dist/signature.js +25 -0
- package/dist/spec.d.ts +29 -0
- package/dist/spec.js +14 -0
- package/dist/suggest.d.ts +8 -0
- package/dist/suggest.js +53 -0
- package/package.json +27 -3
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { parsePickle } from "./parse.js";
|
|
2
|
+
//#region src/signature.ts
|
|
3
|
+
function formatAttr(prop) {
|
|
4
|
+
if (prop.shorthand === true) return prop.name;
|
|
5
|
+
if (prop.value.kind === "literal") return typeof prop.value.value === "string" ? `${prop.name}=${JSON.stringify(prop.value.value)}` : `${prop.name}={${JSON.stringify(prop.value.value)}}`;
|
|
6
|
+
return `${prop.name}={${prop.value.code}}`;
|
|
7
|
+
}
|
|
8
|
+
/** A `<slot/>` anywhere in the define's body, ignoring nested defines. */
|
|
9
|
+
function hasSlot(nodes) {
|
|
10
|
+
for (const node of nodes) {
|
|
11
|
+
if (node.type !== "element" || node.name === "define") continue;
|
|
12
|
+
if (node.name === "slot" || hasSlot(node.children)) return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
/** `<statCard label value delta={0} note="">…</statCard>` when the define has a slot. */
|
|
17
|
+
function recipeSignature(recipe) {
|
|
18
|
+
const define = parsePickle(recipe.source).children.find((node) => node.type === "element" && node.name === "define");
|
|
19
|
+
if (define === void 0) return `<${recipe.name}>`;
|
|
20
|
+
const attrs = define.props.filter((prop) => prop.name !== "name").map((prop) => formatAttr(prop));
|
|
21
|
+
const open = attrs.length === 0 ? `<${recipe.name}>` : `<${recipe.name} ${attrs.join(" ")}>`;
|
|
22
|
+
return hasSlot(define.children) ? `${open}…</${recipe.name}>` : open;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { recipeSignature };
|
package/dist/spec.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/spec.d.ts
|
|
2
|
+
/** What the compiler checks a literal prop against. */
|
|
3
|
+
export type PropSpec = {
|
|
4
|
+
kind: "boolean";
|
|
5
|
+
} | {
|
|
6
|
+
default?: string;
|
|
7
|
+
kind: "enum";
|
|
8
|
+
values: ReadonlyArray<string>;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "handler";
|
|
11
|
+
} | {
|
|
12
|
+
default?: number;
|
|
13
|
+
kind: "number";
|
|
14
|
+
max?: number;
|
|
15
|
+
min?: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "rows";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "string";
|
|
20
|
+
};
|
|
21
|
+
/** One component the compiler knows: what it may contain and its props. */
|
|
22
|
+
export interface ComponentSpec {
|
|
23
|
+
/** What the component may contain; `none` drops children at compile time. */
|
|
24
|
+
children: "block" | "inline" | "none";
|
|
25
|
+
props: Readonly<Record<string, PropSpec>>;
|
|
26
|
+
}
|
|
27
|
+
/** The components a document may use, by tag name. */
|
|
28
|
+
export type CompileCatalog = Readonly<Record<string, ComponentSpec>>;
|
|
29
|
+
//#endregion
|
package/dist/spec.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/spec.ts
|
|
2
|
+
/** Whether a literal written in the source fits `spec`. */
|
|
3
|
+
function isValidValue(spec, value) {
|
|
4
|
+
switch (spec.kind) {
|
|
5
|
+
case "boolean": return typeof value === "boolean";
|
|
6
|
+
case "enum": return typeof value === "string" && spec.values.includes(value);
|
|
7
|
+
case "number": return typeof value === "number" && Number.isFinite(value) && (spec.min === void 0 || value >= spec.min) && (spec.max === void 0 || value <= spec.max);
|
|
8
|
+
case "string": return typeof value === "string";
|
|
9
|
+
case "handler":
|
|
10
|
+
case "rows": return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { isValidValue };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/suggest.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Up to `count` candidates closest to `name`, nearest first. A candidate that
|
|
4
|
+
* contains `name` or is contained in it counts as close; otherwise it must be
|
|
5
|
+
* within a third of the name's length.
|
|
6
|
+
*/
|
|
7
|
+
export declare function nearest(name: string, candidates: Iterable<string>, count: number): Array<string>;
|
|
8
|
+
//#endregion
|
package/dist/suggest.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/suggest.ts
|
|
2
|
+
/** Edit distance, case-insensitive, where swapping two neighbours counts as one edit. */
|
|
3
|
+
function distance(a, b) {
|
|
4
|
+
const left = a.toLowerCase();
|
|
5
|
+
const right = b.toLowerCase();
|
|
6
|
+
const rows = [Array.from({ length: right.length + 1 }, (_, index) => index)];
|
|
7
|
+
for (let row = 1; row <= left.length; row++) {
|
|
8
|
+
const current = [row];
|
|
9
|
+
const above = rows[row - 1] ?? [];
|
|
10
|
+
for (let column = 1; column <= right.length; column++) {
|
|
11
|
+
const cost = left.charAt(row - 1) === right.charAt(column - 1) ? 0 : 1;
|
|
12
|
+
let best = Math.min((above[column] ?? 0) + 1, (current[column - 1] ?? 0) + 1, (above[column - 1] ?? 0) + cost);
|
|
13
|
+
if (row > 1 && column > 1 && left.charAt(row - 1) === right.charAt(column - 2) && left.charAt(row - 2) === right.charAt(column - 1)) best = Math.min(best, (rows[row - 2]?.[column - 2] ?? 0) + 1);
|
|
14
|
+
current.push(best);
|
|
15
|
+
}
|
|
16
|
+
rows.push(current);
|
|
17
|
+
}
|
|
18
|
+
return rows[left.length]?.[right.length] ?? 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Up to `count` candidates closest to `name`, nearest first. A candidate that
|
|
22
|
+
* contains `name` or is contained in it counts as close; otherwise it must be
|
|
23
|
+
* within a third of the name's length.
|
|
24
|
+
*/
|
|
25
|
+
function nearest(name, candidates, count) {
|
|
26
|
+
const lower = name.toLowerCase();
|
|
27
|
+
const limit = Math.max(1, Math.floor(name.length / 3));
|
|
28
|
+
const scored = [];
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
if (candidate === name) continue;
|
|
31
|
+
const other = candidate.toLowerCase();
|
|
32
|
+
const contains = Math.min(lower.length, other.length) >= 3 && (other.includes(lower) || lower.includes(other));
|
|
33
|
+
const score = distance(name, candidate);
|
|
34
|
+
if (score <= limit || contains) scored.push({
|
|
35
|
+
candidate,
|
|
36
|
+
score: contains ? Math.min(score, limit) : score
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return scored.toSorted((a, b) => a.score - b.score || a.candidate.localeCompare(b.candidate)).slice(0, count).map((entry) => entry.candidate);
|
|
40
|
+
}
|
|
41
|
+
/** The `count` candidates nearest to `name`, however far, for listing a few alternatives. */
|
|
42
|
+
function closest(name, candidates, count) {
|
|
43
|
+
const lower = name.toLowerCase();
|
|
44
|
+
return candidates.map((candidate) => {
|
|
45
|
+
const other = candidate.toLowerCase();
|
|
46
|
+
return {
|
|
47
|
+
candidate,
|
|
48
|
+
score: Math.min(lower.length, other.length) >= 3 && (other.includes(lower) || lower.includes(other)) ? 0 : distance(name, candidate)
|
|
49
|
+
};
|
|
50
|
+
}).toSorted((a, b) => a.score - b.score || a.candidate.localeCompare(b.candidate)).slice(0, count).map((entry) => entry.candidate);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { closest, nearest };
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jigs.design/compiler",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Today's Pickle compiler: parser, checker against a catalog, code generator, recipes, and the edit helpers. The v2 rewrite replaces it.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/yoamomonstruos/pickle.git",
|
|
9
|
+
"directory": "packages/compiler"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"acorn": "8.19.0"
|
|
29
|
+
}
|
|
6
30
|
}
|