11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT License.
33
4- import { ValueNode } from "./grammarParser .js" ;
4+ import { ValueNode } from "./grammarRuleParser .js" ;
55import registerDebug from "debug" ;
66// REVIEW: switch to RegExp.escape() when it becomes available.
77import escapeMatch from "regexp.escape" ;
8- import { Grammar , GrammarRule } from "./grammarCompiler .js" ;
8+ import { Grammar , GrammarRule } from "./grammarTypes .js" ;
99
1010const debugMatch = registerDebug ( "typeagent:grammar:match" ) ;
1111
@@ -31,6 +31,7 @@ type MatchedValue =
3131type 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+
8396function 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
203236function 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 ;
0 commit comments