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.
Files changed (86) hide show
  1. package/README.md +103 -15
  2. package/dist/attrs.d.ts +8 -42
  3. package/dist/attrs.js +148 -297
  4. package/dist/calls.d.ts +4 -0
  5. package/dist/calls.js +219 -0
  6. package/dist/children.d.ts +9 -0
  7. package/dist/children.js +273 -0
  8. package/dist/classes.d.ts +13 -0
  9. package/dist/classes.js +176 -0
  10. package/dist/cli.js +121 -27
  11. package/dist/client.d.ts +11 -2
  12. package/dist/client.js +45 -9
  13. package/dist/compiler.d.ts +4 -0
  14. package/dist/compiler.js +37 -90
  15. package/dist/component.js +19 -82
  16. package/dist/context.d.ts +6 -68
  17. package/dist/context.js +25 -38
  18. package/dist/diff.d.ts +1 -0
  19. package/dist/diff.js +68 -0
  20. package/dist/emitter.d.ts +0 -8
  21. package/dist/emitter.js +0 -12
  22. package/dist/expr.d.ts +0 -70
  23. package/dist/expr.js +30 -855
  24. package/dist/fallthrough.d.ts +9 -0
  25. package/dist/fallthrough.js +78 -0
  26. package/dist/fixture.d.ts +2 -1
  27. package/dist/fixture.js +37 -22
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +1 -0
  30. package/dist/islands.d.ts +6 -0
  31. package/dist/islands.js +5 -0
  32. package/dist/lists.d.ts +0 -11
  33. package/dist/lists.js +4 -39
  34. package/dist/loops.d.ts +3 -0
  35. package/dist/loops.js +164 -0
  36. package/dist/model.d.ts +22 -130
  37. package/dist/model.js +37 -9
  38. package/dist/narrowing.d.ts +19 -0
  39. package/dist/narrowing.js +126 -0
  40. package/dist/numbers.d.ts +8 -0
  41. package/dist/numbers.js +73 -0
  42. package/dist/parens.d.ts +0 -27
  43. package/dist/parens.js +0 -44
  44. package/dist/plugin.d.ts +84 -0
  45. package/dist/plugin.js +26 -0
  46. package/dist/plugins/i18n.d.ts +23 -0
  47. package/dist/{i18n.js → plugins/i18n.js} +76 -19
  48. package/dist/plugins/index.d.ts +2 -0
  49. package/dist/plugins/index.js +8 -0
  50. package/dist/plugins/router-link.d.ts +8 -0
  51. package/dist/{router.js → plugins/router-link.js} +65 -57
  52. package/dist/plugins/router.d.ts +42 -0
  53. package/dist/plugins/router.js +256 -0
  54. package/dist/plugins/scoped.d.ts +12 -0
  55. package/dist/{scoped.js → plugins/scoped.js} +55 -20
  56. package/dist/plugins/shared-types.d.ts +2 -0
  57. package/dist/plugins/shared-types.js +7 -0
  58. package/dist/plugins/stores.d.ts +37 -0
  59. package/dist/plugins/stores.js +377 -0
  60. package/dist/plugins/teleport.d.ts +5 -0
  61. package/dist/plugins/teleport.js +55 -0
  62. package/dist/rust.d.ts +2 -5
  63. package/dist/rust.js +66 -161
  64. package/dist/script.d.ts +0 -6
  65. package/dist/script.js +22 -121
  66. package/dist/slots.d.ts +13 -0
  67. package/dist/slots.js +160 -0
  68. package/dist/strings.d.ts +13 -0
  69. package/dist/strings.js +208 -0
  70. package/dist/styles.d.ts +14 -0
  71. package/dist/styles.js +181 -0
  72. package/dist/template.d.ts +2 -40
  73. package/dist/template.js +40 -691
  74. package/dist/testing.d.ts +1 -0
  75. package/dist/testing.js +1 -8
  76. package/dist/types.d.ts +2 -0
  77. package/dist/typescript.d.ts +0 -13
  78. package/dist/typescript.js +8 -41
  79. package/dist/vite.d.ts +1 -0
  80. package/dist/vite.js +35 -14
  81. package/package.json +11 -2
  82. package/dist/i18n.d.ts +0 -27
  83. package/dist/router.d.ts +0 -17
  84. package/dist/scoped.d.ts +0 -10
  85. package/dist/stores.d.ts +0 -6
  86. 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
- /** `useRoute()` or `$route`: the reader's location, as vue-router resolved it. */
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
- /** Whether its template holds `<RouterView>`: the page, which the server supplies. */
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
- /** The Rust value of `_scopeId` inside slot content: the slot scope id the content is given, or
233
- * `null` where it is always empty. */
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
- /** Types the generated code names, which an interface of the same name would shadow. */
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
- /** Mark every node of an AST with where it came from, so that an error can point into the file. */
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;
@@ -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;