ferrovue 0.2.0 → 0.4.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/README.md +103 -15
- package/dist/attrs.d.ts +8 -42
- package/dist/attrs.js +148 -297
- package/dist/calls.d.ts +4 -0
- package/dist/calls.js +219 -0
- package/dist/children.d.ts +9 -0
- package/dist/children.js +273 -0
- package/dist/classes.d.ts +13 -0
- package/dist/classes.js +176 -0
- package/dist/cli.js +121 -27
- package/dist/client.d.ts +11 -2
- package/dist/client.js +45 -9
- package/dist/compiler.d.ts +4 -0
- package/dist/compiler.js +37 -90
- package/dist/component.js +19 -82
- package/dist/context.d.ts +6 -68
- package/dist/context.js +25 -38
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +68 -0
- package/dist/emitter.d.ts +0 -8
- package/dist/emitter.js +0 -12
- package/dist/expr.d.ts +0 -70
- package/dist/expr.js +30 -855
- package/dist/fallthrough.d.ts +9 -0
- package/dist/fallthrough.js +78 -0
- package/dist/fixture.d.ts +2 -1
- package/dist/fixture.js +37 -22
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/islands.d.ts +6 -0
- package/dist/islands.js +5 -0
- package/dist/lists.d.ts +0 -11
- package/dist/lists.js +4 -39
- package/dist/loops.d.ts +3 -0
- package/dist/loops.js +164 -0
- package/dist/model.d.ts +22 -130
- package/dist/model.js +37 -9
- package/dist/narrowing.d.ts +19 -0
- package/dist/narrowing.js +126 -0
- package/dist/numbers.d.ts +8 -0
- package/dist/numbers.js +73 -0
- package/dist/parens.d.ts +0 -27
- package/dist/parens.js +0 -44
- package/dist/plugin.d.ts +84 -0
- package/dist/plugin.js +26 -0
- package/dist/plugins/i18n.d.ts +23 -0
- package/dist/{i18n.js → plugins/i18n.js} +76 -19
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/router-link.d.ts +8 -0
- package/dist/{router.js → plugins/router-link.js} +65 -57
- package/dist/plugins/router.d.ts +42 -0
- package/dist/plugins/router.js +256 -0
- package/dist/plugins/scoped.d.ts +12 -0
- package/dist/{scoped.js → plugins/scoped.js} +55 -20
- package/dist/plugins/shared-types.d.ts +2 -0
- package/dist/plugins/shared-types.js +7 -0
- package/dist/plugins/stores.d.ts +37 -0
- package/dist/plugins/stores.js +377 -0
- package/dist/plugins/teleport.d.ts +5 -0
- package/dist/plugins/teleport.js +55 -0
- package/dist/rust.d.ts +2 -5
- package/dist/rust.js +66 -161
- package/dist/script.d.ts +0 -6
- package/dist/script.js +22 -121
- package/dist/slots.d.ts +13 -0
- package/dist/slots.js +160 -0
- package/dist/strings.d.ts +13 -0
- package/dist/strings.js +208 -0
- package/dist/styles.d.ts +14 -0
- package/dist/styles.js +181 -0
- package/dist/template.d.ts +2 -40
- package/dist/template.js +40 -691
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -8
- package/dist/types.d.ts +2 -0
- package/dist/typescript.d.ts +0 -13
- package/dist/typescript.js +8 -41
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +35 -14
- package/package.json +11 -2
- package/dist/i18n.d.ts +0 -27
- package/dist/router.d.ts +0 -17
- package/dist/scoped.d.ts +0 -10
- package/dist/stores.d.ts +0 -6
- package/dist/stores.js +0 -227
package/dist/model.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { SourceMapConsumer } from "source-map-js";
|
|
2
|
+
import type { Plugin } from "./plugin.ts";
|
|
2
3
|
export type N = any;
|
|
3
4
|
export type Ty = {
|
|
4
5
|
k: "str";
|
|
5
6
|
} | {
|
|
6
7
|
k: "int";
|
|
7
|
-
}
|
|
8
|
-
/** `Float` from `ferrovue/types`, or a fractional literal: an `f64`, a JavaScript number. */
|
|
9
|
-
| {
|
|
8
|
+
} | {
|
|
10
9
|
k: "float";
|
|
11
10
|
} | {
|
|
12
11
|
k: "bool";
|
|
@@ -18,82 +17,42 @@ export type Ty = {
|
|
|
18
17
|
} | {
|
|
19
18
|
k: "list";
|
|
20
19
|
of: Ty;
|
|
21
|
-
}
|
|
22
|
-
/** `Record<string, T>`: an object used as a dictionary, `ferrovue::Record` in Rust, its keys in
|
|
23
|
-
* JavaScript's order. */
|
|
24
|
-
| {
|
|
20
|
+
} | {
|
|
25
21
|
k: "record";
|
|
26
22
|
of: Ty;
|
|
27
|
-
}
|
|
28
|
-
/** `store` marks a Pinia store's state, or a type inside it, declared in a store's own file;
|
|
29
|
-
* `home` a type declared elsewhere: `"types"` for a shared `.ts` file, or the component whose
|
|
30
|
-
* `.vue` file declares it. */
|
|
31
|
-
| {
|
|
32
|
-
k: "struct";
|
|
33
|
-
name: string;
|
|
34
|
-
store?: true;
|
|
35
|
-
home?: string;
|
|
36
|
-
}
|
|
37
|
-
/** `TrustedHtml` from `ferrovue/types`: the configured Rust type, which `v-html` writes raw. */
|
|
38
|
-
| {
|
|
23
|
+
} | StructTy | {
|
|
39
24
|
k: "html";
|
|
40
|
-
}
|
|
41
|
-
/** Another component's `Props`, imported from its `.vue` file: what `v-bind` hands that child. */
|
|
42
|
-
| {
|
|
25
|
+
} | {
|
|
43
26
|
k: "child";
|
|
44
27
|
name: string;
|
|
28
|
+
} | PluginTys[keyof PluginTys];
|
|
29
|
+
export interface StructTy {
|
|
30
|
+
k: "struct";
|
|
31
|
+
name: string;
|
|
32
|
+
home?: string;
|
|
45
33
|
}
|
|
46
|
-
|
|
47
|
-
| {
|
|
48
|
-
k: "route";
|
|
49
|
-
}
|
|
50
|
-
/** `route.params`. */
|
|
51
|
-
| {
|
|
52
|
-
k: "params";
|
|
34
|
+
export interface PluginTys {
|
|
53
35
|
}
|
|
54
|
-
/** `route.query`, and one of its values: a string, \`null\`, an array of those, or absent. */
|
|
55
|
-
| {
|
|
56
|
-
k: "queryobj";
|
|
57
|
-
} | {
|
|
58
|
-
k: "query";
|
|
59
|
-
};
|
|
60
36
|
export interface Field {
|
|
61
37
|
js: string;
|
|
62
38
|
rust: string;
|
|
63
39
|
ty: Ty;
|
|
64
|
-
/** For an optional prop: the Rust value Vue uses when it is absent — its declared default, or
|
|
65
|
-
* `false` for a boolean, which Vue casts. Reading the prop reads this in place of `None`. */
|
|
66
40
|
dflt?: string;
|
|
67
41
|
}
|
|
68
42
|
export interface Struct {
|
|
69
43
|
name: string;
|
|
70
44
|
fields: Field[];
|
|
71
|
-
/** The props a scoped slot's outlet passes: fields that borrow from the render, read as they are. */
|
|
72
45
|
slot?: true;
|
|
73
46
|
}
|
|
74
|
-
/** A translated expression: Rust source, and the type it evaluates to. `konst` is a boolean known
|
|
75
|
-
* at generation time, which is how `Array.isArray` on a value that is never an array folds away. */
|
|
76
47
|
export interface Val {
|
|
77
48
|
code: string;
|
|
78
49
|
ty: Ty;
|
|
79
50
|
konst?: boolean;
|
|
80
|
-
/** For an integer computed from others: the same computation on doubles, before it is rounded
|
|
81
|
-
* back to an \`i64\` — which keeps JavaScript's \`-0\`, so a division by it is \`-Infinity\`. */
|
|
82
51
|
f64?: string;
|
|
83
|
-
/** For a list a template computes — `filter`, `map`, `split`, `Object.keys` — an iterator of its
|
|
84
|
-
* items, each a `Cow<str>`, a copied number or boolean, or a borrowed object. `code` is then those
|
|
85
|
-
* items collected into a `Vec`. */
|
|
86
52
|
iter?: string;
|
|
87
|
-
/** For a string, or a list of strings: it may hold half of a surrogate pair, which JavaScript
|
|
88
|
-
* keeps and ferrovue writes as U+FFFD — so two such values are never compared or joined. */
|
|
89
53
|
lone?: boolean;
|
|
90
|
-
/** For an optional string a temporary owns: that `Option<Cow<str>>`, whose `.as_deref()` is
|
|
91
|
-
* `code`. A setup `let` keeps this. */
|
|
92
54
|
held?: string;
|
|
93
|
-
/** A number known at generation time: a literal, or literals JavaScript computed. */
|
|
94
55
|
num?: number;
|
|
95
|
-
/** For a string built by `format!`: its format string and arguments, which a string built from it
|
|
96
|
-
* joins rather than formatting it again. */
|
|
97
56
|
format?: {
|
|
98
57
|
text: string;
|
|
99
58
|
args: string[];
|
|
@@ -106,143 +65,76 @@ export declare const BOOL: Ty;
|
|
|
106
65
|
export declare const UNDEF: Ty;
|
|
107
66
|
export declare const opt: (of: Ty) => Ty;
|
|
108
67
|
export declare const RUST_KEYWORDS: Set<string>;
|
|
109
|
-
/** Types the generated code names, which an interface of the same name would shadow. */
|
|
110
68
|
export declare const RUST_PRELUDE: Set<string>;
|
|
111
69
|
export declare function snake(js: string): string;
|
|
70
|
+
export declare function camelize(key: string): string;
|
|
71
|
+
export declare function declares(comp: Component, key: string): boolean;
|
|
72
|
+
export declare function takesAttrs(comp: Component): boolean;
|
|
112
73
|
export declare function rustStr(s: string): string;
|
|
113
|
-
/** One character as a Rust `char` literal. */
|
|
114
74
|
export declare function rustChar(ch: string): string;
|
|
115
75
|
export declare class GenError extends Error {
|
|
116
76
|
}
|
|
117
77
|
export interface Component {
|
|
118
|
-
/** `TagEditor` */
|
|
119
78
|
name: string;
|
|
120
|
-
/** `tag_editor` */
|
|
121
79
|
module: string;
|
|
122
80
|
file: string;
|
|
123
|
-
/** The file's text, which an error quotes. */
|
|
124
81
|
source?: string;
|
|
125
|
-
/** Vue's compiled template mapped back to the template, and where the template's content starts
|
|
126
|
-
* in the file, for an error found in the compiled code. */
|
|
127
82
|
templateMap?: SourceMapConsumer;
|
|
128
83
|
templateStart?: {
|
|
129
84
|
line: number;
|
|
130
85
|
column: number;
|
|
131
86
|
};
|
|
87
|
+
attrsDropped?: N;
|
|
132
88
|
props: Struct;
|
|
133
89
|
structs: Map<string, Struct>;
|
|
134
|
-
/** The local name `TrustedHtml` was imported under from `ferrovue/types`, if it was. */
|
|
135
90
|
trustedName: string | null;
|
|
136
|
-
/** The local name `Float` was imported under from `ferrovue/types`, if it was. */
|
|
137
91
|
floatName: string | null;
|
|
138
|
-
/** Types imported as another component's `Props`: local name → that component's name. */
|
|
139
92
|
childProps: Map<string, string>;
|
|
140
|
-
/** The components it imports, by name. */
|
|
141
93
|
imports: Set<string>;
|
|
142
|
-
/** The slots its template renders with `<slot>`, by name, in order of first appearance. */
|
|
143
94
|
slotNames: string[];
|
|
144
|
-
|
|
145
|
-
routerView: boolean;
|
|
146
|
-
/** Whether its template holds `<RouterLink>`. */
|
|
147
|
-
routerLink: boolean;
|
|
148
|
-
/** Whether it reads the route itself: `useRoute()`, or `$route` in the template. */
|
|
149
|
-
readsRoute: boolean;
|
|
150
|
-
/** Whether it renders a `<RouterLink>` or reads the route, itself or through a child, and so
|
|
151
|
-
* takes the route. */
|
|
152
|
-
usesRoute: boolean;
|
|
153
|
-
/** Whether its setup reads a store. */
|
|
154
|
-
readsStores: boolean;
|
|
155
|
-
/** Whether it reads a store, itself or through a child, and so takes the stores' state. */
|
|
156
|
-
usesStores: boolean;
|
|
157
|
-
/** Whether it translates — \`$t\`, or \`useI18n()\` in setup — and whether it or a child does, and
|
|
158
|
-
* so takes the request's \`I18n\`. */
|
|
159
|
-
readsI18n: boolean;
|
|
160
|
-
usesI18n: boolean;
|
|
161
|
-
/** Whether it renders a \`<Teleport>\`, and whether it or a child does, and so takes the page's
|
|
162
|
-
* \`Teleports\`. */
|
|
163
|
-
readsTeleports: boolean;
|
|
164
|
-
usesTeleports: boolean;
|
|
165
|
-
/** `defineModel` bindings: local name → the prop it reads. */
|
|
95
|
+
takes: Set<string>;
|
|
166
96
|
models: Map<string, string>;
|
|
167
|
-
/** Type aliases it declares: name → the type. */
|
|
168
97
|
aliases: Map<string, N>;
|
|
169
|
-
/** Types it imports from elsewhere: local name → the type, marked with where it lives. */
|
|
170
98
|
importedTypes: Map<string, Ty>;
|
|
171
|
-
/** Its scoped slots, by name: the props each one's outlets pass, known once its render is
|
|
172
|
-
* generated, which is why a child is generated before its parents. */
|
|
173
99
|
slotShapes: Map<string, Struct>;
|
|
174
|
-
/** `data-v-…`, the id its `<style scoped>` gives its elements, or `null` without one. */
|
|
175
|
-
scopeId: string | null;
|
|
176
|
-
/** Whether its scoped styles use `:slotted()`, so that its outlets pass a slot scope id. */
|
|
177
|
-
slotted: boolean;
|
|
178
|
-
/** `inheritAttrs` from `defineOptions`: `false` drops what a parent passes on to its root. */
|
|
179
100
|
inheritAttrs: boolean;
|
|
180
|
-
/** Whether a parent may hand its root scope ids — its own, those passed on to it, or a slot's —
|
|
181
|
-
* which it then takes as `fv_attrs`. Known once every component is read (`scopeFlow`). */
|
|
182
101
|
inherits: boolean;
|
|
183
|
-
/** Whether its outlets may pass slot content a slot scope id, which the content then takes as
|
|
184
|
-
* `fv_sid`. Known once every component is read, as `inherits` is. */
|
|
185
102
|
passesSlotIds: boolean;
|
|
103
|
+
attrNames: Set<string>;
|
|
104
|
+
idsInAttrs: boolean;
|
|
186
105
|
}
|
|
106
|
+
export declare function blankComponent(name: string, module: string, file: string, structs?: Map<string, Struct>): Component;
|
|
187
107
|
export interface Scope {
|
|
188
108
|
comp: Component;
|
|
189
109
|
components: Map<string, Component>;
|
|
190
|
-
/** Setup bindings the server can evaluate: `ref(...)` and `computed(() => ...)`. */
|
|
191
110
|
setup: Map<string, Val>;
|
|
192
|
-
/** Imported child components, by local name. */
|
|
193
111
|
children: Map<string, string>;
|
|
194
|
-
/** Imported helpers, by local name → helper name. */
|
|
195
112
|
helpers: Map<string, string>;
|
|
196
|
-
/** Loop variables in scope. */
|
|
197
113
|
locals: Map<string, Val>;
|
|
198
|
-
/** The identifier `defineProps` was assigned to in the script, if any. */
|
|
199
114
|
propsIdent: string | null;
|
|
200
|
-
/** Setup bindings that are refs — `ref`, `computed`, `storeToRefs`, `defineModel` — which code in
|
|
201
|
-
* the script reads through `.value`, and the template unwrapped. */
|
|
202
115
|
refs: Set<string>;
|
|
203
|
-
/** Setup bindings the server does not evaluate, and why. The template may name one only from an
|
|
204
|
-
* event handler, which the server drops. */
|
|
205
116
|
clientOnly: Map<string, string>;
|
|
206
|
-
/** Optional values a surrounding `v-if` has found present, by the JavaScript path that named them
|
|
207
|
-
* — what TypeScript narrows, and what the generated `if let` bound. */
|
|
208
117
|
narrowed: Map<string, Val>;
|
|
209
|
-
/** The name the compiled template resolved this component itself under, when it uses itself. */
|
|
210
118
|
selfAlias: {
|
|
211
119
|
name: string | null;
|
|
212
120
|
};
|
|
213
|
-
/** What the helper calls translated so far can write, shared by every copy of the scope. */
|
|
214
121
|
helperBytes: {
|
|
215
122
|
n: number;
|
|
216
123
|
};
|
|
217
|
-
/** The names the compiled template resolved `RouterLink` and `RouterView` under. */
|
|
218
|
-
router: Map<string, "RouterLink" | "RouterView">;
|
|
219
|
-
/** Directives the compiled template resolved by name: local → the directive's name. */
|
|
220
124
|
directives: Map<string, string>;
|
|
221
|
-
/** Setup bindings that are vue-i18n's \`t\`, from \`const { t } = useI18n()\`. */
|
|
222
|
-
i18nT: Set<string>;
|
|
223
|
-
/** Inside slot content whose emptiness is decided at run time: each push that is not a comment
|
|
224
|
-
* sets the closure's `filled`, which is how Vue tells content from nothing (`ssrRenderSlot`). */
|
|
225
125
|
fill: boolean;
|
|
226
|
-
/** Inside a `<RouterLink>`'s slot, which Vue renders from virtual nodes rather than pushes: an
|
|
227
|
-
* untaken `v-if` is `<!--v-if-->` there, not `<!---->`. */
|
|
228
126
|
vnode: boolean;
|
|
229
|
-
/** The Rust value of `_attrs`, the scope ids this render's root inherits, as `ssrRenderAttrs`
|
|
230
|
-
* writes them; `null` when the component inherits none. */
|
|
231
127
|
attrs: string | null;
|
|
232
|
-
|
|
233
|
-
|
|
128
|
+
fallthrough: string | null;
|
|
129
|
+
attrsBindings: Set<string>;
|
|
234
130
|
sid: string | null;
|
|
235
|
-
/** Inside a `v-for` over a list of the props: the Rust name of its item, and the list. */
|
|
236
131
|
loop?: {
|
|
237
132
|
item: string;
|
|
238
133
|
over: string;
|
|
239
134
|
};
|
|
135
|
+
plugins: Map<Plugin, unknown>;
|
|
240
136
|
}
|
|
241
|
-
/** Where a node came from, which decides how its position is read: `source` for an AST parsed
|
|
242
|
-
* from the file itself (a script block, a store, a type file), whose positions are the file's;
|
|
243
|
-
* `template` for Vue's compiled template, whose positions map back through its source map. */
|
|
244
137
|
export type Origin = "source" | "template";
|
|
245
|
-
/** Mark every node of an AST with where it came from, so that an error can point into the file. */
|
|
246
138
|
export declare function tagAst(node: N, origin: Origin, seen?: WeakSet<object>): void;
|
|
247
139
|
export declare function fail(comp: Component, what: string, node?: N): never;
|
|
248
140
|
export declare function sameTy(a: Ty, b: Ty): boolean;
|
package/dist/model.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* The compiler's model: the types it gives values, the shapes of components and scopes, and the errors it raises. (Not `types.ts`, which is the public `ferrovue/types` module.) */
|
|
2
1
|
export const STR = { k: "str" };
|
|
3
2
|
export const INT = { k: "int" };
|
|
4
3
|
export const FLOAT = { k: "float" };
|
|
@@ -6,12 +5,24 @@ export const BOOL = { k: "bool" };
|
|
|
6
5
|
export const UNDEF = { k: "undef" };
|
|
7
6
|
export const opt = (of) => (of.k === "opt" ? of : { k: "opt", of });
|
|
8
7
|
export const RUST_KEYWORDS = new Set("as break const continue crate else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while async await dyn abstract become box do final macro override priv typeof unsized virtual yield try".split(" "));
|
|
9
|
-
|
|
8
|
+
const NOT_RAW = new Set(["self", "Self", "super", "crate"]);
|
|
10
9
|
export const RUST_PRELUDE = new Set(["Option", "Some", "None", "Vec", "String", "Box", "Result", "Ok", "Err", "Cow", "Slots", "Default"]);
|
|
11
10
|
export function snake(js) {
|
|
12
11
|
const s = js.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase()).replace(/^_/, "");
|
|
12
|
+
if (NOT_RAW.has(s))
|
|
13
|
+
return `${s}_`;
|
|
13
14
|
return RUST_KEYWORDS.has(s) ? `r#${s}` : s;
|
|
14
15
|
}
|
|
16
|
+
export function camelize(key) {
|
|
17
|
+
return key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
|
|
18
|
+
}
|
|
19
|
+
export function declares(comp, key) {
|
|
20
|
+
const camel = camelize(key);
|
|
21
|
+
return comp.props.fields.some((f) => camelize(f.js) === camel);
|
|
22
|
+
}
|
|
23
|
+
export function takesAttrs(comp) {
|
|
24
|
+
return comp.attrNames.size > 0;
|
|
25
|
+
}
|
|
15
26
|
export function rustStr(s) {
|
|
16
27
|
let out = '"';
|
|
17
28
|
for (const ch of s) {
|
|
@@ -33,7 +44,6 @@ export function rustStr(s) {
|
|
|
33
44
|
}
|
|
34
45
|
return out + '"';
|
|
35
46
|
}
|
|
36
|
-
/** One character as a Rust `char` literal. */
|
|
37
47
|
export function rustChar(ch) {
|
|
38
48
|
if (ch === "'")
|
|
39
49
|
return "'\\''";
|
|
@@ -43,9 +53,31 @@ export function rustChar(ch) {
|
|
|
43
53
|
}
|
|
44
54
|
export class GenError extends Error {
|
|
45
55
|
}
|
|
46
|
-
|
|
56
|
+
export function blankComponent(name, module, file, structs = new Map()) {
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
module,
|
|
60
|
+
file,
|
|
61
|
+
props: { name: "Props", fields: [] },
|
|
62
|
+
structs,
|
|
63
|
+
trustedName: null,
|
|
64
|
+
floatName: null,
|
|
65
|
+
childProps: new Map(),
|
|
66
|
+
imports: new Set(),
|
|
67
|
+
slotNames: [],
|
|
68
|
+
takes: new Set(),
|
|
69
|
+
models: new Map(),
|
|
70
|
+
aliases: new Map(),
|
|
71
|
+
importedTypes: new Map(),
|
|
72
|
+
slotShapes: new Map(),
|
|
73
|
+
inheritAttrs: true,
|
|
74
|
+
inherits: false,
|
|
75
|
+
passesSlotIds: false,
|
|
76
|
+
attrNames: new Set(),
|
|
77
|
+
idsInAttrs: false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
47
80
|
export function tagAst(node, origin, seen = new WeakSet()) {
|
|
48
|
-
// `compileScript` links some nodes back to others, so the tree may hold cycles.
|
|
49
81
|
if (!node || typeof node !== "object" || seen.has(node))
|
|
50
82
|
return;
|
|
51
83
|
seen.add(node);
|
|
@@ -64,7 +96,6 @@ export function tagAst(node, origin, seen = new WeakSet()) {
|
|
|
64
96
|
tagAst(v, origin, seen);
|
|
65
97
|
}
|
|
66
98
|
}
|
|
67
|
-
/** A node's position in the component's own file — line from 1, column from 0 — or `null`. */
|
|
68
99
|
function locate(comp, node) {
|
|
69
100
|
const start = node?.loc?.start;
|
|
70
101
|
if (!start)
|
|
@@ -75,19 +106,16 @@ function locate(comp, node) {
|
|
|
75
106
|
const o = comp.templateMap.originalPositionFor({ line: start.line, column: start.column });
|
|
76
107
|
if (o.line == null || o.column == null)
|
|
77
108
|
return null;
|
|
78
|
-
// The template's content starts partway along the `<template>` line.
|
|
79
109
|
const t = comp.templateStart;
|
|
80
110
|
return o.line === 1 ? { line: t.line, column: t.column - 1 + o.column } : { line: t.line + o.line - 1, column: o.column };
|
|
81
111
|
}
|
|
82
112
|
return null;
|
|
83
113
|
}
|
|
84
|
-
/** The source line, with a caret under the column, as rustc shows it. */
|
|
85
114
|
function snippet(comp, at) {
|
|
86
115
|
const text = comp.source?.split("\n")[at.line - 1];
|
|
87
116
|
if (text === undefined)
|
|
88
117
|
return "";
|
|
89
118
|
const n = String(at.line);
|
|
90
|
-
// Tabs are kept, so the caret lines up whatever the tab width.
|
|
91
119
|
const lead = text.slice(0, at.column).replace(/[^\t]/g, " ");
|
|
92
120
|
return `\n ${n} | ${text}\n ${" ".repeat(n.length)} | ${lead}^`;
|
|
93
121
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
2
|
+
export declare function known(v: Val): boolean | undefined;
|
|
3
|
+
export declare function truthy(v: Val): string;
|
|
4
|
+
export declare function pathOf(n: N): string | null;
|
|
5
|
+
export interface Presence {
|
|
6
|
+
path: string;
|
|
7
|
+
pattern: (name: string) => string;
|
|
8
|
+
option: string;
|
|
9
|
+
present: string;
|
|
10
|
+
of: Ty;
|
|
11
|
+
negated: boolean;
|
|
12
|
+
truthy: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function presence(s: Scope, n: N): Presence | null;
|
|
15
|
+
export declare function narrowTo(s: Scope, p: Presence, name: string): Scope;
|
|
16
|
+
export declare function narrowing(s: Scope, p: Presence, present: (s: Scope) => string, absent: (s: Scope) => string): string;
|
|
17
|
+
export declare function choice(test: string, yes: string, no: string): string;
|
|
18
|
+
export declare function cond(s: Scope, n: N): string;
|
|
19
|
+
export declare function boolOf(code: string): Val;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { BOOL } from "./model.js";
|
|
2
|
+
import { ctx } from "./context.js";
|
|
3
|
+
import { claim } from "./plugin.js";
|
|
4
|
+
import { atom, bare, binary, condition, ifElse, logical, not, occurrences, receiver } from "./parens.js";
|
|
5
|
+
import { isNumber, negatedOrder } from "./numbers.js";
|
|
6
|
+
import { expr } from "./expr.js";
|
|
7
|
+
export function known(v) {
|
|
8
|
+
if (v.konst !== undefined)
|
|
9
|
+
return v.konst;
|
|
10
|
+
if (v.ty.k === "str" && /^"(?:[^"\\]|\\.)*"$/.test(v.code))
|
|
11
|
+
return v.code !== '""';
|
|
12
|
+
if (v.num !== undefined)
|
|
13
|
+
return v.num !== 0 && !Number.isNaN(v.num);
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function truthy(v) {
|
|
17
|
+
const k = known(v);
|
|
18
|
+
if (k !== undefined)
|
|
19
|
+
return String(k);
|
|
20
|
+
const branches = v.ty.k === "str" || isNumber(v.ty) ? ifElse(v.code) : null;
|
|
21
|
+
if (branches)
|
|
22
|
+
return choice(branches.test, truthy({ code: branches.yes, ty: v.ty }), truthy({ code: branches.no, ty: v.ty }));
|
|
23
|
+
switch (v.ty.k) {
|
|
24
|
+
case "str":
|
|
25
|
+
return `!${receiver(v.code)}.is_empty()`;
|
|
26
|
+
case "int":
|
|
27
|
+
return binary(v.code, "!=", "0");
|
|
28
|
+
case "float":
|
|
29
|
+
return `${binary(v.code, "!=", "0.0")} && !${receiver(v.code)}.is_nan()`;
|
|
30
|
+
case "bool":
|
|
31
|
+
return v.code;
|
|
32
|
+
case "undef":
|
|
33
|
+
return "false";
|
|
34
|
+
case "opt":
|
|
35
|
+
return `${atom(v.code)}.is_some_and(|v| ${truthy({ code: "v", ty: v.ty.of })})`;
|
|
36
|
+
default:
|
|
37
|
+
return claim((p) => p.values?.truthy?.(v)) ?? "true";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function pathOf(n) {
|
|
41
|
+
if (n.type === "Identifier")
|
|
42
|
+
return n.name;
|
|
43
|
+
if (n.type !== "MemberExpression")
|
|
44
|
+
return null;
|
|
45
|
+
const base = pathOf(n.object);
|
|
46
|
+
if (base === null)
|
|
47
|
+
return null;
|
|
48
|
+
if (!n.computed && n.property.type === "Identifier")
|
|
49
|
+
return `${base}.${n.property.name}`;
|
|
50
|
+
if (n.computed && n.property.type === "StringLiteral")
|
|
51
|
+
return `${base}.${n.property.value}`;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export function presence(s, n) {
|
|
55
|
+
for (const p of ctx.plugins) {
|
|
56
|
+
const own = p.presence?.(s, n);
|
|
57
|
+
if (own !== undefined)
|
|
58
|
+
return own;
|
|
59
|
+
}
|
|
60
|
+
let target = n;
|
|
61
|
+
let negated = false;
|
|
62
|
+
let byTruth = true;
|
|
63
|
+
if (n.type === "UnaryExpression" && n.operator === "!") {
|
|
64
|
+
target = n.argument;
|
|
65
|
+
negated = true;
|
|
66
|
+
}
|
|
67
|
+
else if (n.type === "BinaryExpression" && (n.operator === "===" || n.operator === "!==")) {
|
|
68
|
+
const isUndef = (m) => m.type === "Identifier" && m.name === "undefined";
|
|
69
|
+
if (isUndef(n.right))
|
|
70
|
+
target = n.left;
|
|
71
|
+
else if (isUndef(n.left))
|
|
72
|
+
target = n.right;
|
|
73
|
+
else
|
|
74
|
+
return null;
|
|
75
|
+
negated = n.operator === "===";
|
|
76
|
+
byTruth = false;
|
|
77
|
+
}
|
|
78
|
+
const path = pathOf(target);
|
|
79
|
+
if (path === null)
|
|
80
|
+
return null;
|
|
81
|
+
const v = expr(s, target);
|
|
82
|
+
if (v.ty.k !== "opt")
|
|
83
|
+
return null;
|
|
84
|
+
const of = v.ty.of;
|
|
85
|
+
const scalar = of.k === "str" || of.k === "int" || of.k === "float" || of.k === "bool";
|
|
86
|
+
const filtered = byTruth && scalar;
|
|
87
|
+
const option = filtered ? `${atom(v.code)}.filter(|v| ${truthy({ code: "*v", ty: of })})` : v.code;
|
|
88
|
+
const present = filtered ? truthy(v) : `${atom(v.code)}.is_some()`;
|
|
89
|
+
return { path, of, negated, truthy: byTruth, option, present, pattern: (name) => `let Some(${name}) = ${option}` };
|
|
90
|
+
}
|
|
91
|
+
export function narrowTo(s, p, name) {
|
|
92
|
+
return { ...s, narrowed: new Map(s.narrowed).set(p.path, { code: name, ty: p.of }) };
|
|
93
|
+
}
|
|
94
|
+
export function narrowing(s, p, present, absent) {
|
|
95
|
+
const name = `n${++ctx.narrowCount}`;
|
|
96
|
+
const then = bare(present(narrowTo(s, p, name)));
|
|
97
|
+
const otherwise = bare(absent(s));
|
|
98
|
+
if (occurrences(then, name))
|
|
99
|
+
return `if ${p.pattern(name)} { ${then} } else { ${otherwise} }`;
|
|
100
|
+
return choice(p.present, then, otherwise);
|
|
101
|
+
}
|
|
102
|
+
export function choice(test, yes, no) {
|
|
103
|
+
const binds = test.startsWith("let ");
|
|
104
|
+
if (yes === no && !binds)
|
|
105
|
+
return yes;
|
|
106
|
+
if (yes === "true" && no === "false" && !binds)
|
|
107
|
+
return test;
|
|
108
|
+
if (yes === "false" && no === "true" && !binds)
|
|
109
|
+
return not(test);
|
|
110
|
+
return `if ${condition(test)} { ${yes} } else { ${no} }`;
|
|
111
|
+
}
|
|
112
|
+
export function cond(s, n) {
|
|
113
|
+
if (n.type === "LogicalExpression" && (n.operator === "||" || n.operator === "&&")) {
|
|
114
|
+
const p = presence(s, n.left);
|
|
115
|
+
if (p && p.negated === (n.operator === "||")) {
|
|
116
|
+
return narrowing(s, p, (inner) => cond(inner, n.right), () => String(n.operator === "||"));
|
|
117
|
+
}
|
|
118
|
+
return logical(cond(s, n.left), n.operator, cond(s, n.right));
|
|
119
|
+
}
|
|
120
|
+
if (n.type === "UnaryExpression" && n.operator === "!")
|
|
121
|
+
return negatedOrder(s, n.argument)?.code ?? not(cond(s, n.argument));
|
|
122
|
+
return truthy(expr(s, n));
|
|
123
|
+
}
|
|
124
|
+
export function boolOf(code) {
|
|
125
|
+
return code === "true" || code === "false" ? { code, ty: BOOL, konst: code === "true" } : { code, ty: BOOL };
|
|
126
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type N, type Scope, type Ty, type Val } from "./model.ts";
|
|
2
|
+
export declare function isNumber(ty: Ty): boolean;
|
|
3
|
+
export declare function asF64(v: Val): string;
|
|
4
|
+
export declare function intFromF64(f64: string): Val;
|
|
5
|
+
export declare function numberVal(x: number, int: boolean): Val;
|
|
6
|
+
export declare function arithmetic(a: Val, op: string, b: Val, int: boolean): Val;
|
|
7
|
+
export declare function compare(a: Val, op: string, b: Val): Val;
|
|
8
|
+
export declare function negatedOrder(s: Scope, n: N): Val | null;
|
package/dist/numbers.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BOOL, FLOAT, INT } from "./model.js";
|
|
2
|
+
import { AS, binary, FLIPPED, operand } from "./parens.js";
|
|
3
|
+
import { expr } from "./expr.js";
|
|
4
|
+
export function isNumber(ty) {
|
|
5
|
+
return ty.k === "int" || ty.k === "float";
|
|
6
|
+
}
|
|
7
|
+
export function asF64(v) {
|
|
8
|
+
if (v.f64 !== undefined)
|
|
9
|
+
return v.f64;
|
|
10
|
+
return v.ty.k === "float" ? v.code : `${operand(v.code, AS)} as f64`;
|
|
11
|
+
}
|
|
12
|
+
export function intFromF64(f64) {
|
|
13
|
+
return { code: `${operand(f64, AS)} as i64`, ty: INT, f64 };
|
|
14
|
+
}
|
|
15
|
+
function floatLiteral(x) {
|
|
16
|
+
if (Number.isNaN(x))
|
|
17
|
+
return "f64::NAN";
|
|
18
|
+
if (!Number.isFinite(x))
|
|
19
|
+
return x > 0 ? "f64::INFINITY" : "f64::NEG_INFINITY";
|
|
20
|
+
const spelled = String(Math.abs(x)).replace("e+", "e");
|
|
21
|
+
return `${x < 0 || Object.is(x, -0) ? "-" : ""}${/[.e]/.test(spelled) ? spelled : `${spelled}.0`}f64`;
|
|
22
|
+
}
|
|
23
|
+
export function numberVal(x, int) {
|
|
24
|
+
if (!int)
|
|
25
|
+
return { code: floatLiteral(x), ty: FLOAT, num: x };
|
|
26
|
+
if (!Number.isSafeInteger(x))
|
|
27
|
+
return { ...intFromF64(floatLiteral(x)), num: x };
|
|
28
|
+
return { code: `${x === 0 ? 0 : x}i64`, ty: INT, f64: floatLiteral(x), num: x };
|
|
29
|
+
}
|
|
30
|
+
function fold(a, op, b) {
|
|
31
|
+
switch (op) {
|
|
32
|
+
case "+":
|
|
33
|
+
return a + b;
|
|
34
|
+
case "-":
|
|
35
|
+
return a - b;
|
|
36
|
+
case "*":
|
|
37
|
+
return a * b;
|
|
38
|
+
case "/":
|
|
39
|
+
return a / b;
|
|
40
|
+
case "%":
|
|
41
|
+
return a % b;
|
|
42
|
+
case "<":
|
|
43
|
+
return a < b;
|
|
44
|
+
case ">":
|
|
45
|
+
return a > b;
|
|
46
|
+
case "<=":
|
|
47
|
+
return a <= b;
|
|
48
|
+
case ">=":
|
|
49
|
+
return a >= b;
|
|
50
|
+
default:
|
|
51
|
+
return a === b;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function arithmetic(a, op, b, int) {
|
|
55
|
+
if (a.num !== undefined && b.num !== undefined)
|
|
56
|
+
return numberVal(fold(a.num, op, b.num), int);
|
|
57
|
+
const f64 = binary(asF64(a), op, asF64(b));
|
|
58
|
+
return int ? intFromF64(f64) : { code: f64, ty: FLOAT };
|
|
59
|
+
}
|
|
60
|
+
export function compare(a, op, b) {
|
|
61
|
+
if (a.num !== undefined && b.num !== undefined) {
|
|
62
|
+
const konst = fold(a.num, op, b.num);
|
|
63
|
+
return { code: String(konst), ty: BOOL, konst };
|
|
64
|
+
}
|
|
65
|
+
return { code: binary(asF64(a), op, asF64(b)), ty: BOOL };
|
|
66
|
+
}
|
|
67
|
+
export function negatedOrder(s, n) {
|
|
68
|
+
if (n.type !== "BinaryExpression" || !(n.operator in FLIPPED))
|
|
69
|
+
return null;
|
|
70
|
+
const a = expr(s, n.left);
|
|
71
|
+
const b = expr(s, n.right);
|
|
72
|
+
return a.ty.k === "int" && b.ty.k === "int" ? compare(a, FLIPPED[n.operator], b) : null;
|
|
73
|
+
}
|
package/dist/parens.d.ts
CHANGED
|
@@ -2,50 +2,23 @@ export declare const OR = 1;
|
|
|
2
2
|
export declare const CMP = 3;
|
|
3
3
|
export declare const AS = 6;
|
|
4
4
|
export declare const UNARY = 7;
|
|
5
|
-
/** How tightly `code` binds as an operand. Read from its text: only its outermost level counts, and
|
|
6
|
-
* anything the scan does not recognise counts as `LOOSE`, which only ever adds parentheses. */
|
|
7
5
|
export declare function binding(code: string): number;
|
|
8
|
-
/** How many times `code` names the variable `name`: not inside a literal, and not as a field, which
|
|
9
|
-
* `.name` is — though `1..=name` is the variable. */
|
|
10
6
|
export declare function occurrences(code: string, name: string): number;
|
|
11
|
-
/** `code` as an operand that must bind at least as tightly as `min`. */
|
|
12
7
|
export declare function operand(code: string, min: number): string;
|
|
13
|
-
/** `code` before `.method()` or `.field`. */
|
|
14
8
|
export declare function atom(code: string): string;
|
|
15
|
-
/** `code` as a whole expression — an argument, a binding's value, a block's result — without
|
|
16
|
-
* parentheses that only wrap all of it. */
|
|
17
9
|
export declare function bare(code: string): string;
|
|
18
|
-
/** Whether `code` is one bracketed group: `(…)`, `[…]`, `{…}`. */
|
|
19
10
|
export declare function enclosed(code: string): boolean;
|
|
20
|
-
/** `code` as an `if`'s condition: bare, unless it is a block, whose `{` Rust would read as the
|
|
21
|
-
* start of the body. */
|
|
22
11
|
export declare function condition(code: string): string;
|
|
23
|
-
/** `a op b`, each side parenthesised only where Rust would read it otherwise. Rust's comparisons do
|
|
24
|
-
* not chain, so both of their operands bind more tightly than they do. A float literal beside a
|
|
25
|
-
* value drops its suffix, the value giving it its type, but keeps it beside another literal. */
|
|
26
12
|
export declare function binary(a: string, op: string, b: string): string;
|
|
27
|
-
/** `-code`, where `-(-x)` is `x`. */
|
|
28
13
|
export declare function negate(code: string): string;
|
|
29
|
-
/** Each ordering comparison's opposite, where neither side is NaN. */
|
|
30
14
|
export declare const FLIPPED: Record<string, string>;
|
|
31
|
-
/** `!code`, folded where it can be: `!true` is `false`, `!!x` is `x`, `!(a == b)` is `a != b`, and
|
|
32
|
-
* `!(a < b)` is `a >= b` unless one is NaN. */
|
|
33
15
|
export declare function not(code: string): string;
|
|
34
|
-
/** `a && b` and `a || b`, with a constant side folded away. Neither side has effects. */
|
|
35
16
|
export declare function logical(a: string, op: "&&" | "||", b: string): string;
|
|
36
|
-
/** `if test { yes } else { no }` taken apart, or `null` for anything else. */
|
|
37
17
|
export declare function ifElse(code: string): {
|
|
38
18
|
test: string;
|
|
39
19
|
yes: string;
|
|
40
20
|
no: string;
|
|
41
21
|
} | null;
|
|
42
|
-
/** A `&str` expression where a `&str` parameter takes it: `&*x` is `&x`, which deref coercion turns
|
|
43
|
-
* into the `&str`, and `&**v` is `v`. */
|
|
44
22
|
export declare function strArg(code: string): string;
|
|
45
|
-
/** `recv.call` for a method of `str` that returns a `bool`: on each branch when `recv` is an `if`,
|
|
46
|
-
* whose branches may borrow different types that only a parameter would coerce. */
|
|
47
23
|
export declare function callOn(recv: string, call: string): string;
|
|
48
|
-
/** The receiver of a method of `str` or `f64`: `&*x` and `*v` are `x` and `v`, which auto-deref
|
|
49
|
-
* reaches the value through. Not for a method that references have too, such as `to_owned`. A
|
|
50
|
-
* float literal keeps its suffix, without which the method's receiver type would be ambiguous. */
|
|
51
24
|
export declare function receiver(code: string): string;
|