@venn-lang/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/dist/index.d.ts +3654 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +13137 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +61 -0
  8. package/src/ast/call-args.ts +15 -0
  9. package/src/ast/dotted-path.ts +22 -0
  10. package/src/ast/index.ts +7 -0
  11. package/src/ast/is-statement.ts +40 -0
  12. package/src/ast/split-call.ts +28 -0
  13. package/src/ast/walk.ts +6 -0
  14. package/src/codes/build-problem.ts +24 -0
  15. package/src/codes/catalog.ts +35 -0
  16. package/src/codes/code.types.ts +7 -0
  17. package/src/codes/index.ts +3 -0
  18. package/src/compile/compile.ts +169 -0
  19. package/src/compile/compile.types.ts +40 -0
  20. package/src/compile/index.ts +3 -0
  21. package/src/compile/lex-scope.ts +78 -0
  22. package/src/compile/nodes/binary.ts +56 -0
  23. package/src/compile/nodes/call.ts +50 -0
  24. package/src/compile/nodes/collection.ts +83 -0
  25. package/src/compile/nodes/const-lit.ts +30 -0
  26. package/src/compile/nodes/fast-binary.ts +71 -0
  27. package/src/compile/nodes/fn.ts +87 -0
  28. package/src/compile/nodes/index.ts +8 -0
  29. package/src/compile/nodes/literal.ts +86 -0
  30. package/src/compile/nodes/map-fields.ts +79 -0
  31. package/src/compile/nodes/member.ts +32 -0
  32. package/src/events/envelope.types.ts +17 -0
  33. package/src/events/event-data.types.ts +25 -0
  34. package/src/events/ids.types.ts +10 -0
  35. package/src/events/index.ts +5 -0
  36. package/src/events/run-plan.types.ts +18 -0
  37. package/src/events/status.types.ts +2 -0
  38. package/src/expand/accepted-kinds.ts +23 -0
  39. package/src/expand/deco/deco-decorator.ts +77 -0
  40. package/src/expand/deco/deco-env.ts +24 -0
  41. package/src/expand/deco/deco.types.ts +43 -0
  42. package/src/expand/deco/document-decos.ts +64 -0
  43. package/src/expand/deco/index.ts +7 -0
  44. package/src/expand/deco/read-signature.ts +57 -0
  45. package/src/expand/deco/run-body.ts +119 -0
  46. package/src/expand/decorate-callable.ts +49 -0
  47. package/src/expand/decorations.ts +41 -0
  48. package/src/expand/expand.ts +108 -0
  49. package/src/expand/expand.types.ts +78 -0
  50. package/src/expand/handles/around-verbs.ts +35 -0
  51. package/src/expand/handles/binding-verbs.ts +27 -0
  52. package/src/expand/handles/common-verbs.ts +40 -0
  53. package/src/expand/handles/fn-verbs.ts +58 -0
  54. package/src/expand/handles/handle.types.ts +33 -0
  55. package/src/expand/handles/index.ts +4 -0
  56. package/src/expand/handles/kind-of.ts +36 -0
  57. package/src/expand/handles/make-handle.ts +100 -0
  58. package/src/expand/handles/missing-verb.ts +27 -0
  59. package/src/expand/handles/runnable-verbs.ts +13 -0
  60. package/src/expand/handles/type-verbs.ts +56 -0
  61. package/src/expand/index.ts +40 -0
  62. package/src/expand/make-context.ts +69 -0
  63. package/src/expand/node-meta.ts +30 -0
  64. package/src/expand/node-span.ts +17 -0
  65. package/src/expand/swap-node.ts +28 -0
  66. package/src/expand/wrong-kind.ts +102 -0
  67. package/src/expr/cell.types.ts +25 -0
  68. package/src/expr/closure.ts +53 -0
  69. package/src/expr/closure.types.ts +31 -0
  70. package/src/expr/eval-env.types.ts +9 -0
  71. package/src/expr/evaluate.ts +18 -0
  72. package/src/expr/frame.ts +60 -0
  73. package/src/expr/index.ts +14 -0
  74. package/src/expr/invoke.ts +140 -0
  75. package/src/expr/member-value.ts +91 -0
  76. package/src/expr/methods/index.ts +65 -0
  77. package/src/expr/methods/list-grouping.ts +107 -0
  78. package/src/expr/methods/list-methods.ts +57 -0
  79. package/src/expr/methods/list-selection.ts +142 -0
  80. package/src/expr/methods/map-extras.ts +87 -0
  81. package/src/expr/methods/map-methods.ts +17 -0
  82. package/src/expr/methods/number-methods.ts +29 -0
  83. package/src/expr/methods/string-extras.ts +66 -0
  84. package/src/expr/methods/string-methods.ts +32 -0
  85. package/src/expr/methods/unit-methods.ts +54 -0
  86. package/src/expr/namespace.ts +20 -0
  87. package/src/expr/native.types.ts +40 -0
  88. package/src/expr/operators.ts +91 -0
  89. package/src/expr/pending.ts +47 -0
  90. package/src/expr/prelude.ts +75 -0
  91. package/src/expr/task.ts +71 -0
  92. package/src/format/format-text.ts +22 -0
  93. package/src/format/format.types.ts +18 -0
  94. package/src/format/from-settings.ts +40 -0
  95. package/src/format/index.ts +5 -0
  96. package/src/format/organize-header.ts +66 -0
  97. package/src/format/reindent.ts +35 -0
  98. package/src/generated/ast.ts +2383 -0
  99. package/src/generated/grammar.ts +5236 -0
  100. package/src/generated/module.ts +25 -0
  101. package/src/grammar/venn.langium +291 -0
  102. package/src/graph/graph.types.ts +20 -0
  103. package/src/graph/index.ts +2 -0
  104. package/src/graph/to-graph.ts +87 -0
  105. package/src/index.ts +93 -0
  106. package/src/interpolation/compile-template.ts +40 -0
  107. package/src/interpolation/index.ts +4 -0
  108. package/src/interpolation/interpolation.types.ts +18 -0
  109. package/src/interpolation/scan-interpolations.ts +47 -0
  110. package/src/interpolation/template.types.ts +19 -0
  111. package/src/lang/default-services.ts +14 -0
  112. package/src/lang/index.ts +3 -0
  113. package/src/lang/venn-lexer.ts +34 -0
  114. package/src/lang/venn-module.ts +51 -0
  115. package/src/module/index.ts +5 -0
  116. package/src/module/specifier.ts +21 -0
  117. package/src/parse/error-to-problem.ts +43 -0
  118. package/src/parse/index.ts +3 -0
  119. package/src/parse/parse-expression.ts +27 -0
  120. package/src/parse/parse-output.types.ts +8 -0
  121. package/src/parse/parse.ts +26 -0
  122. package/src/problem/build-diff.ts +116 -0
  123. package/src/problem/diff.types.ts +20 -0
  124. package/src/problem/format-value.ts +40 -0
  125. package/src/problem/index.ts +8 -0
  126. package/src/problem/problem-error.ts +15 -0
  127. package/src/problem/problem.types.ts +21 -0
  128. package/src/problem/related.types.ts +7 -0
  129. package/src/problem/severity.types.ts +2 -0
  130. package/src/problem/span.types.ts +8 -0
  131. package/src/typecheck/action-signature.ts +53 -0
  132. package/src/typecheck/builtin-types.ts +63 -0
  133. package/src/typecheck/builtins.ts +267 -0
  134. package/src/typecheck/catalog.types.ts +16 -0
  135. package/src/typecheck/check-deco.ts +174 -0
  136. package/src/typecheck/check-stmts.ts +151 -0
  137. package/src/typecheck/check-types.ts +232 -0
  138. package/src/typecheck/context.ts +31 -0
  139. package/src/typecheck/imported-types.ts +127 -0
  140. package/src/typecheck/index.ts +26 -0
  141. package/src/typecheck/infer.ts +416 -0
  142. package/src/typecheck/kind-types.ts +79 -0
  143. package/src/typecheck/member-docs.ts +211 -0
  144. package/src/typecheck/named-types.ts +57 -0
  145. package/src/typecheck/prelude-types.ts +152 -0
  146. package/src/typecheck/reshaped-fns.ts +58 -0
  147. package/src/typecheck/scheme.ts +75 -0
  148. package/src/typecheck/seed-params.ts +72 -0
  149. package/src/typecheck/seed-values.ts +64 -0
  150. package/src/typecheck/show.ts +64 -0
  151. package/src/typecheck/solidify.ts +60 -0
  152. package/src/typecheck/spec-to-type.ts +88 -0
  153. package/src/typecheck/type-env.ts +46 -0
  154. package/src/typecheck/type-ref.ts +62 -0
  155. package/src/typecheck/type.types.ts +168 -0
  156. package/src/typecheck/unify.ts +192 -0
  157. package/src/typecheck/unit-types.ts +49 -0
  158. package/src/units/combine.ts +97 -0
  159. package/src/units/index.ts +6 -0
  160. package/src/units/parse-instant.ts +12 -0
  161. package/src/units/parse-number.ts +48 -0
  162. package/src/units/unit-guards.ts +16 -0
  163. package/src/units/unit.types.ts +38 -0
  164. package/src/value/equals.ts +4 -0
  165. package/src/value/index.ts +4 -0
  166. package/src/value/is-numeric.ts +6 -0
  167. package/src/value/truthiness.ts +9 -0
  168. package/src/value/value.types.ts +12 -0
@@ -0,0 +1,25 @@
1
+ /******************************************************************************
2
+ * This file was generated by langium-cli 4.3.0.
3
+ * DO NOT EDIT MANUALLY!
4
+ ******************************************************************************/
5
+
6
+ import type { LangiumSharedCoreServices, LangiumCoreServices, LangiumGeneratedCoreServices, LangiumGeneratedSharedCoreServices, LanguageMetaData, Module } from 'langium';
7
+ import { VennAstReflection } from './ast.js';
8
+ import { VennGrammar } from './grammar.js';
9
+
10
+ export const VennLanguageMetaData = {
11
+ languageId: 'venn',
12
+ fileExtensions: ['.vn'],
13
+ caseInsensitive: false,
14
+ mode: 'development'
15
+ } as const satisfies LanguageMetaData;
16
+
17
+ export const VennGeneratedSharedModule: Module<LangiumSharedCoreServices, LangiumGeneratedSharedCoreServices> = {
18
+ AstReflection: () => new VennAstReflection()
19
+ };
20
+
21
+ export const VennGeneratedModule: Module<LangiumCoreServices, LangiumGeneratedCoreServices> = {
22
+ Grammar: () => VennGrammar(),
23
+ LanguageMetaData: () => VennLanguageMetaData,
24
+ parser: {}
25
+ };
@@ -0,0 +1,291 @@
1
+ grammar Venn
2
+
3
+ // Statements and declarations are separated by `NL` (a newline or `;`).
4
+ // `NL` is suppressed inside `( )` and `[ ]` by the lexer, so calls, arg lists
5
+ // and list literals may still span multiple physical lines.
6
+ // Newlines follow each item rather than preceding it, so there is a single
7
+ // leading `NL*` and no two unbounded newline consumers meet at a decision
8
+ // point, which Chevrotain reports as ambiguous.
9
+ entry Document:
10
+ NL*
11
+ ('module' name=QualifiedName NL*)?
12
+ (imports+=ImportDecl NL*)*
13
+ (decls+=Declaration NL*)*;
14
+
15
+ ImportDecl: UseDecl | ValueImport;
16
+
17
+ UseDecl: 'use' pkg=STRING ('as' alias=ID)?;
18
+
19
+ ValueImport:
20
+ (export?='pub')? 'import'
21
+ ( '{' names+=ID (',' names+=ID)* '}'
22
+ | '*' 'as' wildcard=ID
23
+ | default=ID )
24
+ 'from' path=STRING;
25
+
26
+ // The top level of a file. In test mode the runner picks out the `flow`s; in
27
+ // script mode it executes the statements top to bottom, the file itself being
28
+ // the program. Declarations (flow/fn/type/…) only run when called.
29
+ Declaration:
30
+ (annotations+=Annotation NL*)*
31
+ ( FlowDecl | FragmentDecl | FnDecl | DecoDecl | TypeDecl | FactoryDecl | DatasetDecl
32
+ | ConfigDecl | MatrixDecl | ReportDecl | LifecycleDecl
33
+ | IfStmt | ForEachStmt | RepeatStmt | WhileStmt | ParallelStmt | RaceStmt
34
+ | TryStmt | RunStmt | LetStmt | ExpectStmt | ActionCall );
35
+
36
+ Annotation: '@' name=ID ('(' (args=ArgList)? ')')?;
37
+
38
+ FlowDecl: 'flow' title=STRING body=Block;
39
+
40
+ FragmentDecl:
41
+ (export?='pub')? 'fragment' name=ID '(' (params=ParamList)? ')'
42
+ ('->' returns=TypeRef)? body=Block;
43
+
44
+ // A pure function (§08: no steps, no I/O). Its body is one expression after
45
+ // `=>`, or a block of local bindings ending in the expression it returns.
46
+ // The last expression is the return value; `return` is optional and explicit.
47
+ FnDecl:
48
+ (export?='pub')? 'fn' name=ID '(' (params=ParamList)? ')'
49
+ ('->' returns=TypeRef)? body=FnBody;
50
+
51
+ FnBody:
52
+ '=>' result=Expr
53
+ | '{' NL* (locals+=LetStmt NL+)* 'return'? result=Expr NL* '}';
54
+
55
+ // A decorator written in the language itself. The first parameter *is* the
56
+ // target, and the type on it (`Fn`, `Flow`, `Step`, `Binding`, `Type`,
57
+ // `Resource`, `Node`) is what this decorates, so nothing written here names a
58
+ // node of the compiler's own tree. The parameters after it are the decorator's
59
+ // own arguments: `@retry(3)` binds the second one. The body runs at expansion
60
+ // time, before the program exists, so it is pure the way `fn` is. The checker
61
+ // enforces that, not the grammar.
62
+ DecoDecl:
63
+ (export?='pub')? 'deco' name=ID '(' (params=ParamList)? ')' body=Block;
64
+
65
+ ConfigDecl: 'config' body=MapLit;
66
+ MatrixDecl: 'matrix' body=MapLit;
67
+ ReportDecl: 'report' reporters+=Expr (',' reporters+=Expr)*;
68
+
69
+ TypeDecl: 'type' name=ID ('=' alias=TypeRef | body=TypeBody);
70
+ TypeBody: '{' (',' | NL)* fields+=FieldDecl ((',' | NL)+ fields+=FieldDecl)* (',' | NL)* '}';
71
+ // A field carries decorators like anything else. The `NL*` is what lets the
72
+ // annotation sit on its own line, which is where everyone writes it, and it
73
+ // cannot be mistaken for the separator between fields: a field starts with `@`
74
+ // or a name, never with a newline.
75
+ FieldDecl: (annotations+=Annotation NL*)* name=ID (optional?='?')? ':' fieldType=TypeRef;
76
+
77
+ FactoryDecl: 'factory' name=ID body=MapLit;
78
+ DatasetDecl: 'dataset' name=ID (':' declaredType=TypeRef)? '=' value=Expr;
79
+
80
+ ParamList: params+=Param (',' params+=Param)*;
81
+ // A parameter carries decorators too. No `NL*` here: a parameter list lives
82
+ // inside `( )`, where the lexer already dropped the newlines.
83
+ Param: (annotations+=Annotation)* name=ID (':' paramType=TypeRef)?;
84
+
85
+ TypeRef: members+=SingleType ('|' members+=SingleType)*;
86
+ SingleType:
87
+ {infer NamedType} name=QualifiedName ('<' args+=TypeRef (',' args+=TypeRef)* '>')?
88
+ | {infer LiteralType} value=STRING;
89
+
90
+ Block: '{' NL* (stmts+=Statement (NL+ stmts+=Statement)* NL*)? '}';
91
+
92
+ Statement:
93
+ (annotations+=Annotation NL*)*
94
+ ( StepDecl | GroupDecl | IfStmt | ForEachStmt | RepeatStmt | WhileStmt
95
+ | ParallelStmt | RaceStmt | TryStmt | LifecycleDecl | LetStmt | CaptureStmt
96
+ | ExpectStmt | RunStmt | ReturnStmt | BreakStmt | ContinueStmt | ActionCall );
97
+
98
+ StepDecl: 'step' title=STRING body=Block;
99
+ GroupDecl: 'group' title=STRING body=Block;
100
+
101
+ IfStmt: 'if' cond=Expr then=Block ('else' otherwise=ElseBranch)?;
102
+ ElseBranch: IfStmt | Block;
103
+
104
+ ForEachStmt: 'forEach' item=ID 'in' source=Expr (opts=MapLit)? body=Block;
105
+ RepeatStmt: 'repeat' count=Expr ('as' index=ID)? body=Block;
106
+ WhileStmt: 'while' cond=Expr body=Block;
107
+
108
+ ParallelStmt: 'parallel' (opts=MapLit)? body=Block;
109
+ RaceStmt: 'race' (opts=MapLit)? body=Block;
110
+ TryStmt:
111
+ 'try' body=Block
112
+ ('catch' (error=ID)? handler=Block)?
113
+ ('finally' finalizer=Block)?;
114
+
115
+ // What runs around the body rather than in it. `defer` is the lexical one: it
116
+ // belongs to the block it is written in; the named hooks belong to the run.
117
+ //
118
+ // `on` reacts to an event of the *run*: `on failure`, `on success`. Not to one
119
+ // of the machine, because a program being stopped is what `teardown` already
120
+ // means, and a second spelling for it would make one word mean two things.
121
+ LifecycleDecl:
122
+ hook=('setup' | 'teardown' | 'beforeEach' | 'afterEach' | 'defer') body=Block
123
+ | 'on' event=ID ('(' arg=Expr ')')? body=Block;
124
+
125
+ // `let plan = "pro"` binds a value; `let auth = http.post url { … }` binds what
126
+ // an action returned. Both are one rule: `Expr` stops before a bare argument
127
+ // (same shape as `expect subject matcher`), so trailing args and opts, and only
128
+ // those, mark this as a call.
129
+ LetStmt:
130
+ kind=('let' | 'const') name=ID (':' declaredType=TypeRef)? '='
131
+ value=Expr (args+=ActionArg)* (opts=MapLit)?;
132
+
133
+ // Removed from the language: `capture` and `let` mean the same thing. Still
134
+ // parsed so the editor can say so and offer the one-word fix, instead of
135
+ // failing with a syntax error that explains nothing.
136
+ CaptureStmt: 'capture' name=ID '=' value=Expr (opts=MapLit)?;
137
+ RunStmt: 'run' target=QualifiedName '(' (args=ArgList)? ')' ('as' bind=ID)?;
138
+ ReturnStmt: {infer ReturnStmt} 'return' (value=Expr)?;
139
+ BreakStmt: {infer BreakStmt} 'break';
140
+ ContinueStmt: {infer ContinueStmt} 'continue';
141
+
142
+ // `expect` is boolean-expression based, or a bareword matcher clause. A newline
143
+ // (or `;`) after the subject terminates the statement, so a trailing matcher
144
+ // word is unambiguous with the next statement's leading identifier. `.all { … }`
145
+ // groups checks (one per line); `.soft` records without aborting.
146
+ ExpectStmt:
147
+ 'expect' ('.' modifier=('soft' | 'all'))?
148
+ ( '{' NL* (checks+=Expr (NL+ checks+=Expr)* NL*)? '}'
149
+ | (negate?='not')? subject=Expr (matcher=MatcherClause)? );
150
+
151
+ // A registry matcher applied to the subject: `contains "x"`, `oneOf [a, b]`,
152
+ // `closeTo 99.00 { within: 0.01 }`, `empty`. Args reuse ActionArg, never a bare
153
+ // map: the trailing `{ … }` is always `opts`.
154
+ MatcherClause: name=ID (args+=ActionArg)* (opts=MapLit)?;
155
+
156
+ // The rule that absorbs every protocol, present and future.
157
+ //
158
+ // The brackets are what make a method callable for its effect alone:
159
+ // `conn.close()` is a statement, not a value nobody reads. Which of the two a
160
+ // dotted target means, a plugin's verb or a method on something in scope, is
161
+ // decided by what the name resolves to, not by how the call is written.
162
+ ActionCall:
163
+ target=QualifiedName (called?='(' (call=ArgList)? ')')?
164
+ (args+=ActionArg)* (opts=MapLit)?;
165
+
166
+ // A positional argument: postfix over an Atom (member/index/call), but NEVER a
167
+ // bare map literal, since that trailing `{ … }` is always `opts`.
168
+ ActionArg infers Expr:
169
+ Atom (
170
+ {infer Member.receiver=current} (optional?='?.' | '.') member=ID
171
+ | {infer Index.receiver=current} '[' index=Expr ']'
172
+ | {infer Call.callee=current} '(' (args=ArgList)? ')'
173
+ )*;
174
+
175
+ Expr: Ternary;
176
+
177
+ Ternary infers Expr:
178
+ Coalesce ({infer Ternary.condition=current} '?' then=Expr ':' otherwise=Expr)?;
179
+
180
+ Coalesce infers Expr:
181
+ LogicalOr ({infer Binary.left=current} operator='??' right=LogicalOr)*;
182
+
183
+ LogicalOr infers Expr:
184
+ LogicalAnd ({infer Binary.left=current} operator='||' right=LogicalAnd)*;
185
+
186
+ LogicalAnd infers Expr:
187
+ Equality ({infer Binary.left=current} operator='&&' right=Equality)*;
188
+
189
+ Equality infers Expr:
190
+ Relational ({infer Binary.left=current} operator=('==' | '!=' | '~=') right=Relational)*;
191
+
192
+ Relational infers Expr:
193
+ Additive ({infer Binary.left=current} operator=('<=' | '>=' | '<' | '>' | 'in') right=Additive)*;
194
+
195
+ Additive infers Expr:
196
+ Multiplicative ({infer Binary.left=current} operator=('+' | '-') right=Multiplicative)*;
197
+
198
+ Multiplicative infers Expr:
199
+ Unary ({infer Binary.left=current} operator=('*' | '/' | '%') right=Unary)*;
200
+
201
+ Unary infers Expr:
202
+ {infer Unary} operator=('!' | '-') operand=Unary
203
+ | Postfix;
204
+
205
+ Postfix infers Expr:
206
+ Primary (
207
+ {infer Member.receiver=current} (optional?='?.' | '.') member=Word
208
+ | {infer Index.receiver=current} '[' index=Expr ']'
209
+ | {infer Call.callee=current} '(' (args=ArgList)? ')'
210
+ )*;
211
+
212
+ Primary infers Expr:
213
+ Atom | MapLit;
214
+
215
+ Atom infers Expr:
216
+ {infer NumberLit} raw=NUMBER
217
+ | {infer InstantLit} value=INSTANT
218
+ | {infer StringLit} value=STRING
219
+ | {infer BoolLit} value=('true' | 'false')
220
+ | {infer NullLit} 'null'
221
+ | {infer FnExpr} 'fn' '(' (params=ParamList)? ')' ('->' returns=TypeRef)? body=FnBody
222
+ // The same function, written the way most people's fingers already type it:
223
+ // one parameter needs no brackets, more than one does. `(a) => b` and `(a)`
224
+ // differ only at their fourth token, which is why the parser looks that far.
225
+ | {infer FnExpr} params=BareParam '=>' body=ArrowBody
226
+ | {infer FnExpr} '(' (params=ParamList)? ')' ('->' returns=TypeRef)? '=>' body=ArrowBody
227
+ | {infer Ref} name=RefName
228
+ | ListLit
229
+ | '(' Expr ')';
230
+
231
+ // The unbracketed parameter carries no type: `f(x: number => …)` already means
232
+ // a named argument called `x`, and named arguments win. Annotate it and you get
233
+ // the brackets back, which is the rule TypeScript settled on.
234
+ BareParam infers ParamList: params+=BareParamName;
235
+ BareParamName infers Param: name=ID;
236
+
237
+ // An arrow's body is one expression. A block would read as a map literal, so
238
+ // `{ … }` bodies keep the `fn` spelling, where the braces can't be mistaken.
239
+ ArrowBody infers FnBody:
240
+ result=Expr;
241
+
242
+ ListLit infers ListLit:
243
+ '[' (items+=Expr (',' items+=Expr)* ','?)? ']';
244
+
245
+ MapLit infers MapLit:
246
+ '{' (',' | NL)*
247
+ (entries+=MapEntry ((',' | NL)+ entries+=MapEntry)* (',' | NL)*)?
248
+ '}';
249
+
250
+ MapEntry: key=MapKey ':' value=Expr;
251
+ MapKey returns string: Word | STRING;
252
+
253
+ ArgList: args+=Arg (',' args+=Arg)*;
254
+ Arg: (name=ID ':')? value=Expr;
255
+
256
+ // A qualified name: first segment is a bare ID (never a keyword, so it can't
257
+ // clash with a statement keyword), later segments may be keywords (ws.expect).
258
+ QualifiedName returns string: ID ('.' Word)*;
259
+
260
+ // A variable reference: a bare ID, or a keyword that is also used as a value in
261
+ // expression position: `matrix.browser`, and the `@scope(flow|step)` lifetimes.
262
+ // Safe: no statement starts with a bare expression, so these can't shadow a decl.
263
+ RefName returns string: ID | 'matrix' | 'flow' | 'step';
264
+
265
+ // A name in member / map-key / qualified-tail position: ID or ANY keyword.
266
+ // Safe because none of these positions can start a statement.
267
+ Word returns string:
268
+ ID | 'module' | 'use' | 'as' | 'pub' | 'import' | 'from' | 'flow'
269
+ | 'fragment' | 'fn' | 'deco' | 'return' | 'const' | 'let' | 'config'
270
+ | 'matrix' | 'report' | 'type' | 'factory' | 'dataset' | 'setup' | 'teardown'
271
+ | 'beforeEach' | 'afterEach' | 'defer' | 'on' | 'step' | 'group' | 'if'
272
+ | 'else' | 'forEach' | 'in' | 'repeat' | 'while' | 'parallel' | 'race' | 'try'
273
+ | 'catch' | 'finally' | 'capture' | 'run' | 'break' | 'continue' | 'expect'
274
+ | 'all' | 'soft' | 'not';
275
+
276
+ // A statement terminator: one or more newlines or `;`, plus adjacent blanks.
277
+ // Not hidden, because the parser consumes it. The lexer drops it inside ( ) and [ ].
278
+ terminal NL: /([ \t]*(\r?\n|;)[ \t]*)+/;
279
+ hidden terminal WS: /[ \t]+/;
280
+ hidden terminal COMMENT: /#[^\n\r]*/;
281
+
282
+ // ISO-8601 instant is a first-class literal (matched before NUMBER by maximal munch).
283
+ terminal INSTANT: /[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?Z/;
284
+ // Double- or single-quoted. Single quotes let a string appear inside a `${…}`
285
+ // that is itself inside a double-quoted string, without escaping.
286
+ terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/;
287
+ // A number with an optional unit suffix, parsed in TS (units/): "200","300ms","2mb","0.1%".
288
+ // `_` may group digits (`1_000_000`, `9_999.999_9`) but only between them, so
289
+ // `_1`, `1_` and `1__0` are not numbers.
290
+ terminal NUMBER: /[0-9]+(_[0-9]+)*(\.[0-9]+(_[0-9]+)*)?(ms|kb|mb|gb|b|s|m|h|%)?/;
291
+ terminal ID: /[_a-zA-Z]\w*/;
@@ -0,0 +1,20 @@
1
+ /** A node in the visual graph derived from the AST (§22). */
2
+ export interface GraphNode {
3
+ id: string;
4
+ kind: string;
5
+ label: string;
6
+ parent?: string;
7
+ }
8
+
9
+ /** A directed edge between two graph nodes (sequential flow or branch). */
10
+ export interface GraphEdge {
11
+ from: string;
12
+ to: string;
13
+ label?: string;
14
+ }
15
+
16
+ /** The graph the editor renders: nodes plus edges, derived purely from the AST. */
17
+ export interface Graph {
18
+ nodes: GraphNode[];
19
+ edges: GraphEdge[];
20
+ }
@@ -0,0 +1,2 @@
1
+ export type { Graph, GraphEdge, GraphNode } from "./graph.types.js";
2
+ export { toGraph } from "./to-graph.js";
@@ -0,0 +1,87 @@
1
+ import type { Block, Document, ExpectStmt, FlowDecl, Statement } from "../generated/ast.js";
2
+ import {
3
+ isActionCall,
4
+ isExpectStmt,
5
+ isFlowDecl,
6
+ isForEachStmt,
7
+ isGroupDecl,
8
+ isIfStmt,
9
+ isParallelStmt,
10
+ isRaceStmt,
11
+ isRepeatStmt,
12
+ isStepDecl,
13
+ isWhileStmt,
14
+ } from "../generated/ast.js";
15
+ import type { Graph, GraphNode } from "./graph.types.js";
16
+
17
+ /** Derive the node graph (§22) from a parsed document — a pure AST transform. */
18
+ export function toGraph(doc: Document): Graph {
19
+ const graph: Graph = { nodes: [], edges: [] };
20
+ doc.decls.filter(isFlowDecl).forEach((flow, index) => {
21
+ addFlow(graph, flow, `flow-${index}`);
22
+ });
23
+ return graph;
24
+ }
25
+
26
+ function addFlow(graph: Graph, flow: FlowDecl, id: string): void {
27
+ graph.nodes.push({ id, kind: "flow", label: flow.title });
28
+ walkBlock(graph, flow.body, id);
29
+ }
30
+
31
+ function walkBlock(graph: Graph, block: Block, parent: string): void {
32
+ let previous: string | undefined;
33
+ block.stmts.forEach((stmt, index) => {
34
+ const nodeId = addStatement(graph, stmt, `${parent}/n${index}`, parent);
35
+ if (nodeId && previous) graph.edges.push({ from: previous, to: nodeId });
36
+ if (nodeId) previous = nodeId;
37
+ });
38
+ }
39
+
40
+ function addStatement(
41
+ graph: Graph,
42
+ stmt: Statement,
43
+ id: string,
44
+ parent: string,
45
+ ): string | undefined {
46
+ const container = containerOf(stmt);
47
+ if (container) return addContainer({ graph, id, parent, ...container });
48
+ if (isActionCall(stmt)) return addNode(graph, { id, kind: "action", label: stmt.target, parent });
49
+ if (isExpectStmt(stmt))
50
+ return addNode(graph, { id, kind: "expect", label: exprText(stmt), parent });
51
+ return undefined;
52
+ }
53
+
54
+ function containerOf(stmt: Statement): { kind: string; label: string; body: Block } | undefined {
55
+ if (isStepDecl(stmt)) return { kind: "step", label: stmt.title, body: stmt.body };
56
+ if (isGroupDecl(stmt)) return { kind: "group", label: stmt.title, body: stmt.body };
57
+ if (isForEachStmt(stmt))
58
+ return { kind: "forEach", label: `forEach ${stmt.item}`, body: stmt.body };
59
+ if (isRepeatStmt(stmt)) return { kind: "repeat", label: "repeat", body: stmt.body };
60
+ if (isWhileStmt(stmt)) return { kind: "while", label: "while", body: stmt.body };
61
+ if (isParallelStmt(stmt)) return { kind: "parallel", label: "parallel", body: stmt.body };
62
+ if (isRaceStmt(stmt)) return { kind: "race", label: "race", body: stmt.body };
63
+ if (isIfStmt(stmt)) return { kind: "if", label: "if", body: stmt.then };
64
+ return undefined;
65
+ }
66
+
67
+ function addContainer(args: {
68
+ graph: Graph;
69
+ id: string;
70
+ parent: string;
71
+ kind: string;
72
+ label: string;
73
+ body: Block;
74
+ }): string {
75
+ addNode(args.graph, { id: args.id, kind: args.kind, label: args.label, parent: args.parent });
76
+ walkBlock(args.graph, args.body, args.id);
77
+ return args.id;
78
+ }
79
+
80
+ function addNode(graph: Graph, node: GraphNode): string {
81
+ graph.nodes.push(node);
82
+ return node.id;
83
+ }
84
+
85
+ function exprText(stmt: ExpectStmt): string {
86
+ return (stmt as { $cstNode?: { text?: string } }).$cstNode?.text ?? "expect";
87
+ }
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ // @venn-lang/core: the fixed kernel. Worker-safe, with no `node:*` (enforced by
2
+ // tsdown platform "neutral" and a tsconfig without @types/node).
3
+
4
+ export * from "./ast/index.js";
5
+ export * from "./codes/index.js";
6
+ export type { CompiledBody, Thunk } from "./compile/index.js";
7
+ export { closureOfDecl, compileExpr } from "./compile/index.js";
8
+ export * from "./events/index.js";
9
+ // Decorators: the expansion phase, run between parsing and everything else.
10
+ export * from "./expand/index.js";
11
+ export type { Cell, CellEnv, Closure, EvalEnv, NativeFn } from "./expr/index.js";
12
+ export {
13
+ callClosure,
14
+ childEnv,
15
+ display,
16
+ evaluate,
17
+ hasCells,
18
+ invoke,
19
+ invoke1,
20
+ isCallable,
21
+ isClosure,
22
+ isNamespaceValue,
23
+ isNativeFn,
24
+ memberValue,
25
+ namespaceValue,
26
+ nativeFn,
27
+ PRELUDE_VALUES,
28
+ typeName,
29
+ } from "./expr/index.js";
30
+ // Formatting: shared by `venn fmt` and the editor so both agree.
31
+ export * from "./format/index.js";
32
+ // The generated AST (Document, FlowDecl, StepDecl, ActionCall, Expr, type guards…).
33
+ export * from "./generated/ast.js";
34
+ // Langium services (for advanced hosts; the CLI/runtime use `parse`).
35
+ export {
36
+ VennGeneratedModule,
37
+ VennGeneratedSharedModule,
38
+ VennLanguageMetaData,
39
+ } from "./generated/module.js";
40
+ // AST → node graph (§22).
41
+ export * from "./graph/index.js";
42
+ // `${…}` placeholders: one description, shared by the evaluator and the editor.
43
+ export * from "./interpolation/index.js";
44
+ export { createVennServices, VennLexer, vennServices } from "./lang/index.js";
45
+ export {
46
+ isPackageSpecifier,
47
+ type SpecifierKind,
48
+ specifierKind,
49
+ } from "./module/index.js";
50
+ export type { ParseOutput } from "./parse/index.js";
51
+ export { EXPRESSION_OFFSET, parse, parseExpression } from "./parse/index.js";
52
+ export * from "./problem/index.js";
53
+ // Static type inference (Hindley-Milner) + generics.
54
+ export {
55
+ BUILTIN_TYPES,
56
+ type BuiltinType,
57
+ type CheckTypesOptions,
58
+ type CheckTypesResult,
59
+ checkTypes,
60
+ createContext,
61
+ DYNAMIC,
62
+ type FnType,
63
+ type ImportedTypes,
64
+ importedTypes,
65
+ isBuiltinType,
66
+ isPrelude,
67
+ KIND_SPECS,
68
+ KIND_TYPES,
69
+ type LiteralType,
70
+ literal,
71
+ MEMBER_DOCS,
72
+ type MemberDoc,
73
+ memberKind,
74
+ memberType,
75
+ type OpaqueType,
76
+ opaque,
77
+ PRELUDE_SPECS,
78
+ type PreludeArg,
79
+ type PreludeSpec,
80
+ prune,
81
+ type RecordType,
82
+ type ResolveRef,
83
+ resolveMember,
84
+ showType,
85
+ showTypes,
86
+ specToType,
87
+ type Type,
88
+ type TypeCatalog,
89
+ type UnionType,
90
+ union,
91
+ } from "./typecheck/index.js";
92
+ export * from "./units/index.js";
93
+ export * from "./value/index.js";
@@ -0,0 +1,40 @@
1
+ import { parseExpression } from "../parse/index.js";
2
+ import { scanInterpolations } from "./scan-interpolations.js";
3
+ import type { Template, TemplateHole } from "./template.types.js";
4
+
5
+ /**
6
+ * Templates already compiled, by the literal's text.
7
+ *
8
+ * Splitting a literal means a full parse per `${…}`, and the result is a pure
9
+ * function of the text, so it is done once. A `.vn` file holds a fixed set of
10
+ * string literals, so this holds one entry per literal.
11
+ */
12
+ const compiled = new Map<string, Template>();
13
+
14
+ /**
15
+ * Split a string literal into the constant text around its placeholders and the
16
+ * parsed expression filling each one.
17
+ *
18
+ * @param text The literal's value, placeholders included.
19
+ * @returns The template, memoised: the same text always yields the same object.
20
+ */
21
+ export function compileTemplate(text: string): Template {
22
+ const known = compiled.get(text);
23
+ if (known) return known;
24
+ const template = split(text);
25
+ compiled.set(text, template);
26
+ return template;
27
+ }
28
+
29
+ function split(text: string): Template {
30
+ const chunks: string[] = [];
31
+ const holes: TemplateHole[] = [];
32
+ let cursor = 0;
33
+ for (const slot of scanInterpolations(text)) {
34
+ chunks.push(text.slice(cursor, slot.start));
35
+ holes.push({ source: slot.source, expr: parseExpression(slot.source) });
36
+ cursor = slot.end;
37
+ }
38
+ chunks.push(text.slice(cursor));
39
+ return { chunks, holes };
40
+ }
@@ -0,0 +1,4 @@
1
+ export { compileTemplate } from "./compile-template.js";
2
+ export type { InterpolationSlot } from "./interpolation.types.js";
3
+ export { scanInterpolations } from "./scan-interpolations.js";
4
+ export type { Template, TemplateHole } from "./template.types.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * One `${…}` placeholder found inside a string, located precisely enough to
3
+ * highlight, hover and jump to what is written inside it.
4
+ *
5
+ * Offsets are relative to the text handed to the scanner, so the runtime can
6
+ * scan the cooked value while the language server scans the raw source token.
7
+ * One description of what `${…}` means, serving both.
8
+ */
9
+ export interface InterpolationSlot {
10
+ /** Offset of the opening `${`. */
11
+ start: number;
12
+ /** Offset just past the closing `}`. */
13
+ end: number;
14
+ /** The expression source between the braces, trimmed. */
15
+ source: string;
16
+ /** Offset of {@link source} itself, past `${` and any leading blanks. */
17
+ sourceStart: number;
18
+ }
@@ -0,0 +1,47 @@
1
+ import type { InterpolationSlot } from "./interpolation.types.js";
2
+
3
+ /**
4
+ * Find every `${…}` placeholder in a string.
5
+ *
6
+ * The single description of where interpolation starts and stops: the evaluator
7
+ * substitutes what this returns, and the language server colours, hovers and
8
+ * resolves the very same spans. An unclosed `${` ends the scan, so the text
9
+ * after it is ordinary characters.
10
+ *
11
+ * @returns The slots, in the order they appear.
12
+ */
13
+ export function scanInterpolations(text: string): InterpolationSlot[] {
14
+ const slots: InterpolationSlot[] = [];
15
+ let open = text.indexOf("${");
16
+ while (open !== -1) {
17
+ const close = closingBrace(text, open + 2);
18
+ if (close === -1) break;
19
+ slots.push(slotAt({ text, open, close }));
20
+ open = text.indexOf("${", close + 1);
21
+ }
22
+ return slots;
23
+ }
24
+
25
+ /** The `}` that closes the placeholder, counting nesting so `${ {a:1}.a }` works. */
26
+ function closingBrace(text: string, from: number): number {
27
+ let depth = 1;
28
+ for (let index = from; index < text.length; index += 1) {
29
+ if (text[index] === "{") depth += 1;
30
+ else if (text[index] === "}") {
31
+ depth -= 1;
32
+ if (depth === 0) return index;
33
+ }
34
+ }
35
+ return -1;
36
+ }
37
+
38
+ function slotAt(args: { text: string; open: number; close: number }): InterpolationSlot {
39
+ const inner = args.text.slice(args.open + 2, args.close);
40
+ const lead = inner.length - inner.trimStart().length;
41
+ return {
42
+ start: args.open,
43
+ end: args.close + 1,
44
+ source: inner.trim(),
45
+ sourceStart: args.open + 2 + lead,
46
+ };
47
+ }
@@ -0,0 +1,19 @@
1
+ import type { Expr } from "../generated/ast.js";
2
+
3
+ /** One `${…}`, resolved from text to something the evaluator can just run. */
4
+ export interface TemplateHole {
5
+ /** The placeholder's source, kept for the error when it does not parse. */
6
+ readonly source: string;
7
+ /** The parsed expression, or undefined when the source is not one. */
8
+ readonly expr: Expr | undefined;
9
+ }
10
+
11
+ /**
12
+ * A string literal split once into the text around its placeholders and what
13
+ * fills them. `chunks` has exactly one more entry than `holes`: `chunks[i]`
14
+ * comes before `holes[i]`, and the last chunk is the tail.
15
+ */
16
+ export interface Template {
17
+ readonly chunks: readonly string[];
18
+ readonly holes: readonly TemplateHole[];
19
+ }
@@ -0,0 +1,14 @@
1
+ import type { LangiumCoreServices } from "langium";
2
+ import { createVennServices } from "./venn-module.js";
3
+
4
+ let cached: LangiumCoreServices | undefined;
5
+
6
+ /**
7
+ * The Langium core services that `parse()` uses, built once on first call.
8
+ * Constructing the Chevrotain parser is the expensive part of a first parse,
9
+ * so it must not happen per document.
10
+ */
11
+ export function vennServices(): LangiumCoreServices {
12
+ cached ??= createVennServices();
13
+ return cached;
14
+ }
@@ -0,0 +1,3 @@
1
+ export { vennServices } from "./default-services.js";
2
+ export { VennLexer } from "./venn-lexer.js";
3
+ export { createVennServices } from "./venn-module.js";