Skip to content

Commit 904c624

Browse files
authored
Shell: Grammar e2e integration (#1714)
1 parent 4826ffd commit 904c624

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1152
-389
lines changed

ts/packages/actionGrammar/src/grammarCompiler.ts

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,13 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { Rule, RuleDefinition, ValueNode } from "./grammarParser.js";
5-
6-
type StringPart = {
7-
type: "string";
8-
9-
value: string[];
10-
11-
/* TODO: cache the regexp?
12-
regexp?: RegExp;
13-
regexpWithPendingWildcards?: RegExp;
14-
*/
15-
};
16-
17-
type VarStringPart = {
18-
type: "wildcard";
19-
variable: string;
20-
optional?: boolean | undefined;
21-
22-
typeName: string; // Do we need this?
23-
};
24-
25-
type VarNumberPart = {
26-
type: "number";
27-
variable: string;
28-
optional?: boolean | undefined;
29-
};
30-
31-
type RulesPart = {
32-
type: "rules";
33-
34-
rules: GrammarRule[];
35-
name?: string; // Do we need this?
36-
37-
variable?: string;
38-
optional?: boolean | undefined;
39-
};
40-
41-
type GrammarPart = StringPart | VarStringPart | VarNumberPart | RulesPart;
42-
export type GrammarRule = {
43-
parts: GrammarPart[];
44-
value?: ValueNode | undefined;
45-
};
46-
47-
export type Grammar = {
48-
rules: GrammarRule[];
49-
};
4+
import {
5+
Grammar,
6+
GrammarPart,
7+
GrammarRule,
8+
StringPart,
9+
} from "./grammarTypes.js";
10+
import { Rule, RuleDefinition } from "./grammarRuleParser.js";
5011

5112
type DefinitionMap = Map<
5213
string,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import {
5+
Grammar,
6+
GrammarJson,
7+
GrammarPart,
8+
GrammarPartJson,
9+
GrammarRule,
10+
GrammarRuleJson,
11+
} from "./grammarTypes.js";
12+
13+
export function grammarFromJson(json: GrammarJson): Grammar {
14+
const start = json[0];
15+
const indexToRules: Map<number, GrammarRule[]> = new Map();
16+
function grammarRuleFromJson(r: GrammarRuleJson, json: GrammarJson) {
17+
return {
18+
parts: r.parts.map((p) => grammarPartFromJson(p, json)),
19+
value: r.value,
20+
};
21+
}
22+
function grammarPartFromJson(
23+
p: GrammarPartJson,
24+
json: GrammarJson,
25+
): GrammarPart {
26+
switch (p.type) {
27+
case "string":
28+
case "wildcard":
29+
case "number":
30+
return p;
31+
case "rules":
32+
let rules = indexToRules.get(p.index);
33+
if (rules === undefined) {
34+
rules = [];
35+
indexToRules.set(p.index, rules);
36+
for (const r of json[p.index]) {
37+
rules.push(grammarRuleFromJson(r, json));
38+
}
39+
}
40+
return {
41+
type: "rules",
42+
name: p.name,
43+
rules,
44+
variable: p.variable,
45+
optional: p.optional,
46+
};
47+
}
48+
}
49+
50+
return {
51+
rules: start.map((r) => grammarRuleFromJson(r, json)),
52+
};
53+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { compileGrammar, Grammar } from "./grammarCompiler.js";
5-
import { parseGrammar } from "./grammarParser.js";
4+
import { compileGrammar } from "./grammarCompiler.js";
5+
import { parseGrammarRules } from "./grammarRuleParser.js";
6+
import { Grammar } from "./grammarTypes.js";
67

7-
export function loadGrammar(fileName: string, content: string): Grammar {
8-
const definitions = parseGrammar(fileName, content);
8+
export function loadGrammarRules(fileName: string, content: string): Grammar {
9+
const definitions = parseGrammarRules(fileName, content);
910
const grammar = compileGrammar(definitions);
1011
return grammar;
1112
}

ts/packages/actionGrammar/src/grammarMatcher.ts

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { ValueNode } from "./grammarParser.js";
4+
import { ValueNode } from "./grammarRuleParser.js";
55
import registerDebug from "debug";
66
// REVIEW: switch to RegExp.escape() when it becomes available.
77
import escapeMatch from "regexp.escape";
8-
import { Grammar, GrammarRule } from "./grammarCompiler.js";
8+
import { Grammar, GrammarRule } from "./grammarTypes.js";
99

1010
const debugMatch = registerDebug("typeagent:grammar:match");
1111

@@ -31,6 +31,7 @@ type MatchedValue =
3131
type MatchedValueNode = {
3232
valueId: number;
3333
value: MatchedValue;
34+
wildcard: boolean;
3435
prev: MatchedValueNode | undefined;
3536
};
3637

@@ -69,22 +70,35 @@ type MatchState = {
6970
| undefined;
7071
};
7172

72-
function getMatchedValue(
73-
valueId: ValueIdNode,
73+
function getMatchedValueNode(
74+
valueId: number,
7475
values: MatchedValueNode | undefined,
75-
): MatchedValue | undefined {
76+
): MatchedValueNode {
7677
let v: MatchedValueNode | undefined = values;
77-
while (v !== undefined && v.valueId !== valueId.valueId) {
78+
while (v !== undefined && v.valueId !== valueId) {
7879
v = v.prev;
7980
}
80-
return v?.value;
81+
if (v === undefined) {
82+
throw new Error(`Internal error: Missing value for ${valueId}`);
83+
}
84+
return v;
8185
}
8286

87+
type GrammarMatchStat = {
88+
matchedValueCount: number;
89+
wildcardCharCount: number;
90+
entityWildcardPropertyNames: string[];
91+
};
92+
export type GrammarMatchResult = GrammarMatchStat & {
93+
match: unknown;
94+
};
95+
8396
function createValue(
97+
stat: GrammarMatchStat,
8498
node: ValueNode | undefined,
8599
valueIds: ValueIdNode | undefined,
86100
values: MatchedValueNode | undefined,
87-
): any {
101+
): unknown {
88102
if (node === undefined) {
89103
if (valueIds === undefined) {
90104
throw new Error("Internal error: default matched values");
@@ -94,9 +108,10 @@ function createValue(
94108
`Internal error: No value definitions for multiple values`,
95109
);
96110
}
97-
const value = getMatchedValue(valueIds, values);
111+
const valueNode = getMatchedValueNode(valueIds.valueId, values);
112+
const value = valueNode.value;
98113
if (typeof value === "object") {
99-
return createValue(value.node, value.valueIds, values);
114+
return createValue(stat, value.node, value.valueIds, values);
100115
}
101116
return value;
102117
}
@@ -108,14 +123,14 @@ function createValue(
108123
const obj: Record<string, any> = {};
109124

110125
for (const [k, v] of Object.entries(node.value)) {
111-
obj[k] = createValue(v, valueIds, values);
126+
obj[k] = createValue(stat, v, valueIds, values);
112127
}
113128
return obj;
114129
}
115130
case "array": {
116131
const arr: any[] = [];
117132
for (const v of node.value) {
118-
arr.push(createValue(v, valueIds, values));
133+
arr.push(createValue(stat, v, valueIds, values));
119134
}
120135
return arr;
121136
}
@@ -129,10 +144,26 @@ function createValue(
129144
`Internal error: No value for variable '${node.name}. Values: ${JSON.stringify(valueIds)}'`,
130145
);
131146
}
132-
const value = getMatchedValue(v, values);
147+
const valueNode = getMatchedValueNode(v.valueId, values);
148+
const value = valueNode.value;
133149
if (typeof value === "object") {
134-
return createValue(value.node, value.valueIds, values);
150+
return createValue(stat, value.node, value.valueIds, values);
151+
}
152+
153+
// undefined means optional, don't count
154+
if (value !== undefined) {
155+
stat.matchedValueCount++;
135156
}
157+
158+
if (valueNode.wildcard) {
159+
if (typeof value !== "string") {
160+
throw new Error(
161+
`Internal error: Wildcard has non-string value for variable '${node.name}'`,
162+
);
163+
}
164+
stat.wildcardCharCount += value.length;
165+
}
166+
136167
return value;
137168
}
138169
}
@@ -162,14 +193,14 @@ function createCaptureWildcardState(
162193
newIndex: number,
163194
) {
164195
const { start: wildcardStart, valueId } = state.pendingWildcard!;
165-
const wildcard = captureWildcard(request, wildcardStart, wildcardEnd);
166-
if (wildcard === undefined) {
196+
const wildcardStr = captureWildcard(request, wildcardStart, wildcardEnd);
197+
if (wildcardStr === undefined) {
167198
return undefined;
168199
}
169200
const newState = { ...state };
170201
newState.index = newIndex;
171202
newState.pendingWildcard = undefined;
172-
addValueWithId(newState, valueId, wildcard);
203+
addValueWithId(newState, valueId, wildcardStr, true);
173204
return newState;
174205
}
175206

@@ -183,10 +214,12 @@ function addValueWithId(
183214
state: MatchState,
184215
valueId: number,
185216
matchedValue: MatchedValue,
217+
wildcard: boolean,
186218
) {
187219
state.values = {
188220
valueId,
189221
value: matchedValue,
222+
wildcard,
190223
prev: state.values,
191224
};
192225
}
@@ -197,13 +230,13 @@ function addValue(
197230
matchedValue: MatchedValue,
198231
) {
199232
const valueId = addValueId(state, name);
200-
addValueWithId(state, valueId, matchedValue);
233+
addValueWithId(state, valueId, matchedValue, false);
201234
}
202235

203236
function finalizeRule(
204237
state: MatchState,
205238
request: string,
206-
results: any[],
239+
results: GrammarMatchResult[],
207240
pending: MatchState[],
208241
) {
209242
const nested = state.nested;
@@ -245,7 +278,7 @@ function finalizeRule(
245278
return;
246279
}
247280
state.index = request.length;
248-
addValueWithId(state, state.pendingWildcard.valueId, value);
281+
addValueWithId(state, state.pendingWildcard.valueId, value, true);
249282
}
250283
if (state.index < request.length) {
251284
// Detect trailing separators
@@ -267,19 +300,31 @@ function finalizeRule(
267300
debugMatch(
268301
`Matched at end of input. Matched ids: ${JSON.stringify(state.valueIds)}, values: ${JSON.stringify(state.values)}'`,
269302
);
270-
results.push(createValue(state.rule.value, state.valueIds, state.values));
303+
304+
const matchResult: GrammarMatchResult = {
305+
match: undefined,
306+
matchedValueCount: 0,
307+
wildcardCharCount: 0,
308+
entityWildcardPropertyNames: [],
309+
};
310+
matchResult.match = createValue(
311+
matchResult,
312+
state.rule.value,
313+
state.valueIds,
314+
state.values,
315+
);
316+
results.push(matchResult);
271317
}
272318

273-
type MatchResult = any;
274-
function matchRules(grammar: Grammar, request: string): MatchResult[] {
319+
function matchRules(grammar: Grammar, request: string): GrammarMatchResult[] {
275320
const pending: MatchState[] = grammar.rules.map((r, i) => ({
276321
name: `<Start>[${i}]`,
277322
rule: r,
278323
partIndex: 0,
279324
index: 0,
280325
nextValueId: 0,
281326
}));
282-
const results: MatchResult[] = [];
327+
const results: GrammarMatchResult[] = [];
283328
while (pending.length > 0) {
284329
const state = pending.shift()!;
285330
const { rule, partIndex } = state;

ts/packages/actionGrammar/src/grammarParser.ts renamed to ts/packages/actionGrammar/src/grammarRuleParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ const debugParse = registerDebug("typeagent:grammar:parse");
4747
* <SingleLineComment> ::= "//" [^\n]* "\n"
4848
* <MultiLineComment> ::= "/*" .* "*\/"
4949
*/
50-
export function parseGrammar(
50+
export function parseGrammarRules(
5151
fileName: string,
5252
content: string,
5353
): RuleDefinition[] {
54-
const parser = new CacheGrammarParser(fileName, content);
54+
const parser = new GrammarRuleParser(fileName, content);
5555
const definitions = parser.parse();
5656
debugParse(JSON.stringify(definitions, undefined, 2));
5757
return definitions;
@@ -147,7 +147,7 @@ export function isExpressionSpecialChar(char: string) {
147147
return expressionsSpecialChar.includes(char);
148148
}
149149

150-
class CacheGrammarParser {
150+
class GrammarRuleParser {
151151
private curr: number = 0;
152152
constructor(
153153
private readonly fileName: string,

ts/packages/actionGrammar/src/grammarWriter.ts renamed to ts/packages/actionGrammar/src/grammarRuleWriter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import {
88
Rule,
99
RuleDefinition,
1010
ValueNode,
11-
} from "./grammarParser.js";
11+
} from "./grammarRuleParser.js";
1212

13-
export function writeGrammar(grammar: RuleDefinition[]): string {
13+
export function writeGrammarRules(grammar: RuleDefinition[]): string {
1414
const result: string[] = [];
1515
for (const def of grammar) {
1616
writeRuleDefinition(result, def);

0 commit comments

Comments
 (0)