ferrovue 0.3.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 (85) hide show
  1. package/README.md +32 -13
  2. package/dist/attrs.d.ts +2 -52
  3. package/dist/attrs.js +18 -448
  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 +0 -17
  11. package/dist/client.js +0 -12
  12. package/dist/compiler.js +25 -90
  13. package/dist/component.js +10 -86
  14. package/dist/context.d.ts +4 -66
  15. package/dist/context.js +2 -33
  16. package/dist/diff.d.ts +0 -3
  17. package/dist/diff.js +0 -9
  18. package/dist/emitter.d.ts +0 -8
  19. package/dist/emitter.js +0 -12
  20. package/dist/expr.d.ts +0 -70
  21. package/dist/expr.js +27 -890
  22. package/dist/fallthrough.d.ts +9 -0
  23. package/dist/fallthrough.js +78 -0
  24. package/dist/fixture.d.ts +2 -1
  25. package/dist/fixture.js +37 -22
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +1 -0
  28. package/dist/islands.d.ts +1 -0
  29. package/dist/islands.js +1 -0
  30. package/dist/lists.d.ts +0 -11
  31. package/dist/lists.js +4 -39
  32. package/dist/loops.d.ts +3 -0
  33. package/dist/loops.js +164 -0
  34. package/dist/model.d.ts +14 -143
  35. package/dist/model.js +27 -12
  36. package/dist/narrowing.d.ts +19 -0
  37. package/dist/narrowing.js +126 -0
  38. package/dist/numbers.d.ts +8 -0
  39. package/dist/numbers.js +73 -0
  40. package/dist/parens.d.ts +0 -27
  41. package/dist/parens.js +0 -44
  42. package/dist/plugin.d.ts +84 -0
  43. package/dist/plugin.js +26 -0
  44. package/dist/plugins/i18n.d.ts +23 -0
  45. package/dist/{i18n.js → plugins/i18n.js} +76 -19
  46. package/dist/plugins/index.d.ts +2 -0
  47. package/dist/plugins/index.js +8 -0
  48. package/dist/plugins/router-link.d.ts +8 -0
  49. package/dist/{router.js → plugins/router-link.js} +19 -61
  50. package/dist/plugins/router.d.ts +42 -0
  51. package/dist/plugins/router.js +256 -0
  52. package/dist/plugins/scoped.d.ts +12 -0
  53. package/dist/plugins/scoped.js +125 -0
  54. package/dist/plugins/shared-types.d.ts +2 -0
  55. package/dist/plugins/shared-types.js +7 -0
  56. package/dist/plugins/stores.d.ts +37 -0
  57. package/dist/plugins/stores.js +377 -0
  58. package/dist/plugins/teleport.d.ts +5 -0
  59. package/dist/plugins/teleport.js +55 -0
  60. package/dist/rust.d.ts +1 -7
  61. package/dist/rust.js +38 -160
  62. package/dist/script.d.ts +0 -6
  63. package/dist/script.js +13 -128
  64. package/dist/slots.d.ts +13 -0
  65. package/dist/slots.js +160 -0
  66. package/dist/strings.d.ts +13 -0
  67. package/dist/strings.js +208 -0
  68. package/dist/styles.d.ts +14 -0
  69. package/dist/styles.js +181 -0
  70. package/dist/template.d.ts +1 -42
  71. package/dist/template.js +14 -738
  72. package/dist/testing.d.ts +1 -0
  73. package/dist/testing.js +1 -8
  74. package/dist/types.d.ts +2 -0
  75. package/dist/typescript.d.ts +0 -13
  76. package/dist/typescript.js +8 -41
  77. package/dist/vite.d.ts +1 -0
  78. package/dist/vite.js +1 -23
  79. package/package.json +7 -2
  80. package/dist/i18n.d.ts +0 -27
  81. package/dist/router.d.ts +0 -17
  82. package/dist/scoped.d.ts +0 -24
  83. package/dist/scoped.js +0 -177
  84. package/dist/stores.d.ts +0 -6
  85. package/dist/stores.js +0 -229
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,164 +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;
112
- /** `camelize` in `@vue/shared`: how Vue matches an attribute a parent passes to a declared prop. */
113
70
  export declare function camelize(key: string): string;
114
- /** Whether a component declares the prop a parent passes as `key`, as `setFullProps` decides. */
115
71
  export declare function declares(comp: Component, key: string): boolean;
116
- /** Whether a component may be passed attributes beyond its props, and so takes `fv::Attrs`. */
117
72
  export declare function takesAttrs(comp: Component): boolean;
118
73
  export declare function rustStr(s: string): string;
119
- /** One character as a Rust `char` literal. */
120
74
  export declare function rustChar(ch: string): string;
121
75
  export declare class GenError extends Error {
122
76
  }
123
77
  export interface Component {
124
- /** `TagEditor` */
125
78
  name: string;
126
- /** `tag_editor` */
127
79
  module: string;
128
80
  file: string;
129
- /** The file's text, which an error quotes. */
130
81
  source?: string;
131
- /** Vue's compiled template mapped back to the template, and where the template's content starts
132
- * in the file, for an error found in the compiled code. */
133
82
  templateMap?: SourceMapConsumer;
134
83
  templateStart?: {
135
84
  line: number;
136
85
  column: number;
137
86
  };
138
- /** A root `<Transition>` or `<KeepAlive>` around anything but one plain element — a `v-if`, a
139
- * `<template>` — whose content Vue's server gives no attributes and its client does. */
140
87
  attrsDropped?: N;
141
88
  props: Struct;
142
89
  structs: Map<string, Struct>;
143
- /** The local name `TrustedHtml` was imported under from `ferrovue/types`, if it was. */
144
90
  trustedName: string | null;
145
- /** The local name `Float` was imported under from `ferrovue/types`, if it was. */
146
91
  floatName: string | null;
147
- /** Types imported as another component's `Props`: local name → that component's name. */
148
92
  childProps: Map<string, string>;
149
- /** The components it imports, by name. */
150
93
  imports: Set<string>;
151
- /** The slots its template renders with `<slot>`, by name, in order of first appearance. */
152
94
  slotNames: string[];
153
- /** Whether its template holds `<RouterView>`: the page, which the server supplies. */
154
- routerView: boolean;
155
- /** Whether its template holds `<RouterLink>`. */
156
- routerLink: boolean;
157
- /** Whether it reads the route itself: `useRoute()`, or `$route` in the template. */
158
- readsRoute: boolean;
159
- /** Whether it renders a `<RouterLink>` or reads the route, itself or through a child, and so
160
- * takes the route. */
161
- usesRoute: boolean;
162
- /** Whether its setup reads a store. */
163
- readsStores: boolean;
164
- /** Whether it reads a store, itself or through a child, and so takes the stores' state. */
165
- usesStores: boolean;
166
- /** Whether it translates — \`$t\`, or \`useI18n()\` in setup — and whether it or a child does, and
167
- * so takes the request's \`I18n\`. */
168
- readsI18n: boolean;
169
- usesI18n: boolean;
170
- /** Whether it renders a \`<Teleport>\`, and whether it or a child does, and so takes the page's
171
- * \`Teleports\`. */
172
- readsTeleports: boolean;
173
- usesTeleports: boolean;
174
- /** `defineModel` bindings: local name → the prop it reads. */
95
+ takes: Set<string>;
175
96
  models: Map<string, string>;
176
- /** Type aliases it declares: name → the type. */
177
97
  aliases: Map<string, N>;
178
- /** Types it imports from elsewhere: local name → the type, marked with where it lives. */
179
98
  importedTypes: Map<string, Ty>;
180
- /** Its scoped slots, by name: the props each one's outlets pass, known once its render is
181
- * generated, which is why a child is generated before its parents. */
182
99
  slotShapes: Map<string, Struct>;
183
- /** `data-v-…`, the id its `<style scoped>` gives its elements, or `null` without one. */
184
- scopeId: string | null;
185
- /** Whether its scoped styles use `:slotted()`, so that its outlets pass a slot scope id. */
186
- slotted: boolean;
187
- /** `inheritAttrs` from `defineOptions`: `false` drops what a parent passes on to its root. */
188
100
  inheritAttrs: boolean;
189
- /** Whether a parent may hand its root scope ids — its own, those passed on to it, or a slot's —
190
- * which it then takes as `fv_attrs`. Known once every component is read (`scopeFlow`). */
191
101
  inherits: boolean;
192
- /** Whether its outlets may pass slot content a slot scope id, which the content then takes as
193
- * `fv_sid`. Known once every component is read, as `inherits` is. */
194
102
  passesSlotIds: boolean;
195
- /** The attributes a parent may pass it beyond its props, by name: what falls through to its root,
196
- * or to the element that binds `$attrs`. With any, its `render_scoped` takes them as
197
- * `fv::Attrs`. Known once every component is read (`attrsFlow`). */
198
103
  attrNames: Set<string>;
199
- /** Whether its `$attrs` may hold scope ids: it is the root of a component that may be handed
200
- * some, which passes them on as attributes. */
201
104
  idsInAttrs: boolean;
202
105
  }
106
+ export declare function blankComponent(name: string, module: string, file: string, structs?: Map<string, Struct>): Component;
203
107
  export interface Scope {
204
108
  comp: Component;
205
109
  components: Map<string, Component>;
206
- /** Setup bindings the server can evaluate: `ref(...)` and `computed(() => ...)`. */
207
110
  setup: Map<string, Val>;
208
- /** Imported child components, by local name. */
209
111
  children: Map<string, string>;
210
- /** Imported helpers, by local name → helper name. */
211
112
  helpers: Map<string, string>;
212
- /** Loop variables in scope. */
213
113
  locals: Map<string, Val>;
214
- /** The identifier `defineProps` was assigned to in the script, if any. */
215
114
  propsIdent: string | null;
216
- /** Setup bindings that are refs — `ref`, `computed`, `storeToRefs`, `defineModel` — which code in
217
- * the script reads through `.value`, and the template unwrapped. */
218
115
  refs: Set<string>;
219
- /** Setup bindings the server does not evaluate, and why. The template may name one only from an
220
- * event handler, which the server drops. */
221
116
  clientOnly: Map<string, string>;
222
- /** Optional values a surrounding `v-if` has found present, by the JavaScript path that named them
223
- * — what TypeScript narrows, and what the generated `if let` bound. */
224
117
  narrowed: Map<string, Val>;
225
- /** The name the compiled template resolved this component itself under, when it uses itself. */
226
118
  selfAlias: {
227
119
  name: string | null;
228
120
  };
229
- /** What the helper calls translated so far can write, shared by every copy of the scope. */
230
121
  helperBytes: {
231
122
  n: number;
232
123
  };
233
- /** The names the compiled template resolved `RouterLink` and `RouterView` under. */
234
- router: Map<string, "RouterLink" | "RouterView">;
235
- /** Directives the compiled template resolved by name: local → the directive's name. */
236
124
  directives: Map<string, string>;
237
- /** Setup bindings that are vue-i18n's \`t\`, from \`const { t } = useI18n()\`. */
238
- i18nT: Set<string>;
239
- /** Inside slot content whose emptiness is decided at run time: each push that is not a comment
240
- * sets the closure's `filled`, which is how Vue tells content from nothing (`ssrRenderSlot`). */
241
125
  fill: boolean;
242
- /** Inside a `<RouterLink>`'s slot, which Vue renders from virtual nodes rather than pushes: an
243
- * untaken `v-if` is `<!--v-if-->` there, not `<!---->`. */
244
126
  vnode: boolean;
245
- /** The Rust value of `_attrs`, the scope ids this render's root inherits, as `ssrRenderAttrs`
246
- * writes them; `null` when the component inherits none. */
247
127
  attrs: string | null;
248
- /** The Rust `fv::Attrs` a parent passes this render beyond its props — `$attrs`, and what
249
- * `_attrs` holds before the scope ids — or `null` when no parent passes any. */
250
128
  fallthrough: string | null;
251
- /** Setup bindings holding `useAttrs()`, which the template may bind as `$attrs`. */
252
129
  attrsBindings: Set<string>;
253
- /** The Rust value of `_scopeId` inside slot content: the slot scope id the content is given, or
254
- * `null` where it is always empty. */
255
130
  sid: string | null;
256
- /** Inside a `v-for` over a list of the props: the Rust name of its item, and the list. */
257
131
  loop?: {
258
132
  item: string;
259
133
  over: string;
260
134
  };
135
+ plugins: Map<Plugin, unknown>;
261
136
  }
262
- /** Where a node came from, which decides how its position is read: `source` for an AST parsed
263
- * from the file itself (a script block, a store, a type file), whose positions are the file's;
264
- * `template` for Vue's compiled template, whose positions map back through its source map. */
265
137
  export type Origin = "source" | "template";
266
- /** Mark every node of an AST with where it came from, so that an error can point into the file. */
267
138
  export declare function tagAst(node: N, origin: Origin, seen?: WeakSet<object>): void;
268
139
  export declare function fail(comp: Component, what: string, node?: N): never;
269
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,22 +5,21 @@ 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
  }
15
- /** `camelize` in `@vue/shared`: how Vue matches an attribute a parent passes to a declared prop. */
16
16
  export function camelize(key) {
17
17
  return key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
18
18
  }
19
- /** Whether a component declares the prop a parent passes as `key`, as `setFullProps` decides. */
20
19
  export function declares(comp, key) {
21
20
  const camel = camelize(key);
22
21
  return comp.props.fields.some((f) => camelize(f.js) === camel);
23
22
  }
24
- /** Whether a component may be passed attributes beyond its props, and so takes `fv::Attrs`. */
25
23
  export function takesAttrs(comp) {
26
24
  return comp.attrNames.size > 0;
27
25
  }
@@ -46,7 +44,6 @@ export function rustStr(s) {
46
44
  }
47
45
  return out + '"';
48
46
  }
49
- /** One character as a Rust `char` literal. */
50
47
  export function rustChar(ch) {
51
48
  if (ch === "'")
52
49
  return "'\\''";
@@ -56,9 +53,31 @@ export function rustChar(ch) {
56
53
  }
57
54
  export class GenError extends Error {
58
55
  }
59
- /** 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
+ }
60
80
  export function tagAst(node, origin, seen = new WeakSet()) {
61
- // `compileScript` links some nodes back to others, so the tree may hold cycles.
62
81
  if (!node || typeof node !== "object" || seen.has(node))
63
82
  return;
64
83
  seen.add(node);
@@ -77,7 +96,6 @@ export function tagAst(node, origin, seen = new WeakSet()) {
77
96
  tagAst(v, origin, seen);
78
97
  }
79
98
  }
80
- /** A node's position in the component's own file — line from 1, column from 0 — or `null`. */
81
99
  function locate(comp, node) {
82
100
  const start = node?.loc?.start;
83
101
  if (!start)
@@ -88,19 +106,16 @@ function locate(comp, node) {
88
106
  const o = comp.templateMap.originalPositionFor({ line: start.line, column: start.column });
89
107
  if (o.line == null || o.column == null)
90
108
  return null;
91
- // The template's content starts partway along the `<template>` line.
92
109
  const t = comp.templateStart;
93
110
  return o.line === 1 ? { line: t.line, column: t.column - 1 + o.column } : { line: t.line + o.line - 1, column: o.column };
94
111
  }
95
112
  return null;
96
113
  }
97
- /** The source line, with a caret under the column, as rustc shows it. */
98
114
  function snippet(comp, at) {
99
115
  const text = comp.source?.split("\n")[at.line - 1];
100
116
  if (text === undefined)
101
117
  return "";
102
118
  const n = String(at.line);
103
- // Tabs are kept, so the caret lines up whatever the tab width.
104
119
  const lead = text.slice(0, at.column).replace(/[^\t]/g, " ");
105
120
  return `\n ${n} | ${text}\n ${" ".repeat(n.length)} | ${lead}^`;
106
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;