@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.
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/index.d.ts +3654 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13137 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/src/ast/call-args.ts +15 -0
- package/src/ast/dotted-path.ts +22 -0
- package/src/ast/index.ts +7 -0
- package/src/ast/is-statement.ts +40 -0
- package/src/ast/split-call.ts +28 -0
- package/src/ast/walk.ts +6 -0
- package/src/codes/build-problem.ts +24 -0
- package/src/codes/catalog.ts +35 -0
- package/src/codes/code.types.ts +7 -0
- package/src/codes/index.ts +3 -0
- package/src/compile/compile.ts +169 -0
- package/src/compile/compile.types.ts +40 -0
- package/src/compile/index.ts +3 -0
- package/src/compile/lex-scope.ts +78 -0
- package/src/compile/nodes/binary.ts +56 -0
- package/src/compile/nodes/call.ts +50 -0
- package/src/compile/nodes/collection.ts +83 -0
- package/src/compile/nodes/const-lit.ts +30 -0
- package/src/compile/nodes/fast-binary.ts +71 -0
- package/src/compile/nodes/fn.ts +87 -0
- package/src/compile/nodes/index.ts +8 -0
- package/src/compile/nodes/literal.ts +86 -0
- package/src/compile/nodes/map-fields.ts +79 -0
- package/src/compile/nodes/member.ts +32 -0
- package/src/events/envelope.types.ts +17 -0
- package/src/events/event-data.types.ts +25 -0
- package/src/events/ids.types.ts +10 -0
- package/src/events/index.ts +5 -0
- package/src/events/run-plan.types.ts +18 -0
- package/src/events/status.types.ts +2 -0
- package/src/expand/accepted-kinds.ts +23 -0
- package/src/expand/deco/deco-decorator.ts +77 -0
- package/src/expand/deco/deco-env.ts +24 -0
- package/src/expand/deco/deco.types.ts +43 -0
- package/src/expand/deco/document-decos.ts +64 -0
- package/src/expand/deco/index.ts +7 -0
- package/src/expand/deco/read-signature.ts +57 -0
- package/src/expand/deco/run-body.ts +119 -0
- package/src/expand/decorate-callable.ts +49 -0
- package/src/expand/decorations.ts +41 -0
- package/src/expand/expand.ts +108 -0
- package/src/expand/expand.types.ts +78 -0
- package/src/expand/handles/around-verbs.ts +35 -0
- package/src/expand/handles/binding-verbs.ts +27 -0
- package/src/expand/handles/common-verbs.ts +40 -0
- package/src/expand/handles/fn-verbs.ts +58 -0
- package/src/expand/handles/handle.types.ts +33 -0
- package/src/expand/handles/index.ts +4 -0
- package/src/expand/handles/kind-of.ts +36 -0
- package/src/expand/handles/make-handle.ts +100 -0
- package/src/expand/handles/missing-verb.ts +27 -0
- package/src/expand/handles/runnable-verbs.ts +13 -0
- package/src/expand/handles/type-verbs.ts +56 -0
- package/src/expand/index.ts +40 -0
- package/src/expand/make-context.ts +69 -0
- package/src/expand/node-meta.ts +30 -0
- package/src/expand/node-span.ts +17 -0
- package/src/expand/swap-node.ts +28 -0
- package/src/expand/wrong-kind.ts +102 -0
- package/src/expr/cell.types.ts +25 -0
- package/src/expr/closure.ts +53 -0
- package/src/expr/closure.types.ts +31 -0
- package/src/expr/eval-env.types.ts +9 -0
- package/src/expr/evaluate.ts +18 -0
- package/src/expr/frame.ts +60 -0
- package/src/expr/index.ts +14 -0
- package/src/expr/invoke.ts +140 -0
- package/src/expr/member-value.ts +91 -0
- package/src/expr/methods/index.ts +65 -0
- package/src/expr/methods/list-grouping.ts +107 -0
- package/src/expr/methods/list-methods.ts +57 -0
- package/src/expr/methods/list-selection.ts +142 -0
- package/src/expr/methods/map-extras.ts +87 -0
- package/src/expr/methods/map-methods.ts +17 -0
- package/src/expr/methods/number-methods.ts +29 -0
- package/src/expr/methods/string-extras.ts +66 -0
- package/src/expr/methods/string-methods.ts +32 -0
- package/src/expr/methods/unit-methods.ts +54 -0
- package/src/expr/namespace.ts +20 -0
- package/src/expr/native.types.ts +40 -0
- package/src/expr/operators.ts +91 -0
- package/src/expr/pending.ts +47 -0
- package/src/expr/prelude.ts +75 -0
- package/src/expr/task.ts +71 -0
- package/src/format/format-text.ts +22 -0
- package/src/format/format.types.ts +18 -0
- package/src/format/from-settings.ts +40 -0
- package/src/format/index.ts +5 -0
- package/src/format/organize-header.ts +66 -0
- package/src/format/reindent.ts +35 -0
- package/src/generated/ast.ts +2383 -0
- package/src/generated/grammar.ts +5236 -0
- package/src/generated/module.ts +25 -0
- package/src/grammar/venn.langium +291 -0
- package/src/graph/graph.types.ts +20 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/to-graph.ts +87 -0
- package/src/index.ts +93 -0
- package/src/interpolation/compile-template.ts +40 -0
- package/src/interpolation/index.ts +4 -0
- package/src/interpolation/interpolation.types.ts +18 -0
- package/src/interpolation/scan-interpolations.ts +47 -0
- package/src/interpolation/template.types.ts +19 -0
- package/src/lang/default-services.ts +14 -0
- package/src/lang/index.ts +3 -0
- package/src/lang/venn-lexer.ts +34 -0
- package/src/lang/venn-module.ts +51 -0
- package/src/module/index.ts +5 -0
- package/src/module/specifier.ts +21 -0
- package/src/parse/error-to-problem.ts +43 -0
- package/src/parse/index.ts +3 -0
- package/src/parse/parse-expression.ts +27 -0
- package/src/parse/parse-output.types.ts +8 -0
- package/src/parse/parse.ts +26 -0
- package/src/problem/build-diff.ts +116 -0
- package/src/problem/diff.types.ts +20 -0
- package/src/problem/format-value.ts +40 -0
- package/src/problem/index.ts +8 -0
- package/src/problem/problem-error.ts +15 -0
- package/src/problem/problem.types.ts +21 -0
- package/src/problem/related.types.ts +7 -0
- package/src/problem/severity.types.ts +2 -0
- package/src/problem/span.types.ts +8 -0
- package/src/typecheck/action-signature.ts +53 -0
- package/src/typecheck/builtin-types.ts +63 -0
- package/src/typecheck/builtins.ts +267 -0
- package/src/typecheck/catalog.types.ts +16 -0
- package/src/typecheck/check-deco.ts +174 -0
- package/src/typecheck/check-stmts.ts +151 -0
- package/src/typecheck/check-types.ts +232 -0
- package/src/typecheck/context.ts +31 -0
- package/src/typecheck/imported-types.ts +127 -0
- package/src/typecheck/index.ts +26 -0
- package/src/typecheck/infer.ts +416 -0
- package/src/typecheck/kind-types.ts +79 -0
- package/src/typecheck/member-docs.ts +211 -0
- package/src/typecheck/named-types.ts +57 -0
- package/src/typecheck/prelude-types.ts +152 -0
- package/src/typecheck/reshaped-fns.ts +58 -0
- package/src/typecheck/scheme.ts +75 -0
- package/src/typecheck/seed-params.ts +72 -0
- package/src/typecheck/seed-values.ts +64 -0
- package/src/typecheck/show.ts +64 -0
- package/src/typecheck/solidify.ts +60 -0
- package/src/typecheck/spec-to-type.ts +88 -0
- package/src/typecheck/type-env.ts +46 -0
- package/src/typecheck/type-ref.ts +62 -0
- package/src/typecheck/type.types.ts +168 -0
- package/src/typecheck/unify.ts +192 -0
- package/src/typecheck/unit-types.ts +49 -0
- package/src/units/combine.ts +97 -0
- package/src/units/index.ts +6 -0
- package/src/units/parse-instant.ts +12 -0
- package/src/units/parse-number.ts +48 -0
- package/src/units/unit-guards.ts +16 -0
- package/src/units/unit.types.ts +38 -0
- package/src/value/equals.ts +4 -0
- package/src/value/index.ts +4 -0
- package/src/value/is-numeric.ts +6 -0
- package/src/value/truthiness.ts +9 -0
- 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,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,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
|
+
}
|