@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +473 -0
  2. package/dist/index.d.ts +462 -0
  3. package/dist/index.js +10307 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +58 -0
  6. package/src/analyzer/analyzer.ts +499 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +157 -0
  12. package/src/api/errors.ts +145 -0
  13. package/src/api/expression.ts +156 -0
  14. package/src/api/index.ts +122 -0
  15. package/src/api/inspect.ts +99 -0
  16. package/src/api/registry.ts +128 -0
  17. package/src/api/types.ts +210 -0
  18. package/src/compiler/compiler.ts +546 -0
  19. package/src/compiler/index.ts +2 -0
  20. package/src/compiler/prototype-context-adapter.ts +99 -0
  21. package/src/compiler/types.ts +24 -0
  22. package/src/index.ts +107 -0
  23. package/src/interpreter/README.md +78 -0
  24. package/src/interpreter/interpreter.ts +475 -0
  25. package/src/interpreter/types.ts +108 -0
  26. package/src/lexer/char-tables.ts +37 -0
  27. package/src/lexer/errors.ts +31 -0
  28. package/src/lexer/index.ts +5 -0
  29. package/src/lexer/lexer.ts +745 -0
  30. package/src/lexer/token.ts +104 -0
  31. package/src/lexer2/index.md +232 -0
  32. package/src/lexer2/index.perf.test.ts +68 -0
  33. package/src/lexer2/index.test.ts +549 -0
  34. package/src/lexer2/index.ts +1251 -0
  35. package/src/lexer2/notes.md +173 -0
  36. package/src/lexer2/optimization-summary.md +718 -0
  37. package/src/parser/ast-factory.ts +220 -0
  38. package/src/parser/ast.ts +144 -0
  39. package/src/parser/collection-parser.ts +89 -0
  40. package/src/parser/diagnostic-messages.ts +216 -0
  41. package/src/parser/diagnostics.ts +85 -0
  42. package/src/parser/error-reporter.ts +230 -0
  43. package/src/parser/index.ts +3 -0
  44. package/src/parser/literal-parser.ts +103 -0
  45. package/src/parser/parse-error.ts +16 -0
  46. package/src/parser/parser-error-factory.ts +141 -0
  47. package/src/parser/parser-state.ts +134 -0
  48. package/src/parser/parser.ts +1272 -0
  49. package/src/parser/pprint.ts +169 -0
  50. package/src/parser/precedence-manager.ts +64 -0
  51. package/src/parser/source-mapper.ts +248 -0
  52. package/src/parser/special-constructs.ts +142 -0
  53. package/src/parser/token-navigator.ts +110 -0
  54. package/src/parser/types.ts +60 -0
  55. package/src/parser2/index.md +177 -0
  56. package/src/parser2/index.perf.test.ts +184 -0
  57. package/src/parser2/index.test.ts +305 -0
  58. package/src/parser2/index.ts +578 -0
  59. package/src/parser2/optimization-summary.md +176 -0
  60. package/src/registry/default-analyzers.ts +257 -0
  61. package/src/registry/default-compilers.ts +31 -0
  62. package/src/registry/index.ts +96 -0
  63. package/src/registry/operations/arithmetic.ts +506 -0
  64. package/src/registry/operations/collection.ts +425 -0
  65. package/src/registry/operations/comparison.ts +432 -0
  66. package/src/registry/operations/existence.ts +703 -0
  67. package/src/registry/operations/filtering.ts +358 -0
  68. package/src/registry/operations/literals.ts +341 -0
  69. package/src/registry/operations/logical.ts +439 -0
  70. package/src/registry/operations/math.ts +128 -0
  71. package/src/registry/operations/membership.ts +132 -0
  72. package/src/registry/operations/navigation.ts +52 -0
  73. package/src/registry/operations/string.ts +507 -0
  74. package/src/registry/operations/subsetting.ts +174 -0
  75. package/src/registry/operations/type-checking.ts +162 -0
  76. package/src/registry/operations/type-conversion.ts +404 -0
  77. package/src/registry/operations/type-operators.ts +308 -0
  78. package/src/registry/operations/utility.ts +644 -0
  79. package/src/registry/registry.ts +146 -0
  80. package/src/registry/types.ts +161 -0
  81. package/src/registry/utils/evaluation-helpers.ts +93 -0
  82. package/src/registry/utils/index.ts +3 -0
  83. package/src/registry/utils/type-system.ts +173 -0
  84. package/src/runtime/context.ts +158 -0
  85. package/src/runtime/debug-context.ts +135 -0
@@ -0,0 +1,110 @@
1
+ import type { Token } from '../lexer/token';
2
+ import { TokenType } from '../lexer/token';
3
+
4
+ /**
5
+ * Handles token navigation and lookahead operations for the parser
6
+ */
7
+ export class TokenNavigator {
8
+ private tokens: Token[];
9
+ private current: number = 0;
10
+
11
+ constructor(tokens: Token[]) {
12
+ this.tokens = tokens;
13
+ }
14
+
15
+ /**
16
+ * Check if the current token matches any of the given types
17
+ */
18
+ match(...types: TokenType[]): boolean {
19
+ for (const type of types) {
20
+ if (this.check(type)) {
21
+ this.advance();
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+
28
+ /**
29
+ * Check if the current token is of the given type
30
+ */
31
+ check(type: TokenType): boolean {
32
+ if (this.isAtEnd()) return false;
33
+ return this.peek().type === type;
34
+ }
35
+
36
+ /**
37
+ * Advance to the next token and return the previous one
38
+ */
39
+ advance(): Token {
40
+ if (!this.isAtEnd()) this.current++;
41
+ return this.previous();
42
+ }
43
+
44
+ /**
45
+ * Check if we've reached the end of tokens
46
+ */
47
+ isAtEnd(): boolean {
48
+ return this.peek().type === TokenType.EOF;
49
+ }
50
+
51
+ /**
52
+ * Get the current token without advancing
53
+ */
54
+ peek(): Token {
55
+ if (this.tokens.length === 0) {
56
+ // Return a synthetic EOF token when there are no tokens
57
+ return {
58
+ type: TokenType.EOF,
59
+ value: '',
60
+ position: { line: 1, column: 1, offset: 0 }
61
+ } as Token;
62
+ }
63
+ return this.tokens[this.current] ?? this.tokens[this.tokens.length - 1]!;
64
+ }
65
+
66
+ /**
67
+ * Get the previous token
68
+ */
69
+ previous(): Token {
70
+ return this.tokens[this.current - 1]!;
71
+ }
72
+
73
+ /**
74
+ * Get current position in token stream
75
+ */
76
+ getCurrentPosition(): number {
77
+ return this.current;
78
+ }
79
+
80
+ /**
81
+ * Restore position in token stream
82
+ */
83
+ restorePosition(position: number): void {
84
+ this.current = position;
85
+ }
86
+
87
+ /**
88
+ * Consume a token of the expected type or throw an error
89
+ */
90
+ consume(type: TokenType, errorCallback: (token: Token) => Error): Token {
91
+ if (this.check(type)) return this.advance();
92
+ throw errorCallback(this.peek());
93
+ }
94
+
95
+ /**
96
+ * Skip tokens while a condition is true
97
+ */
98
+ skipWhile(condition: (token: Token) => boolean): void {
99
+ while (!this.isAtEnd() && condition(this.peek())) {
100
+ this.advance();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Check if any of the token types match current token
106
+ */
107
+ checkAny(types: TokenType[]): boolean {
108
+ return types.some(type => this.check(type));
109
+ }
110
+ }
@@ -0,0 +1,60 @@
1
+ import type { ASTNode } from './ast';
2
+ import type { ErrorCode } from '../api/errors';
3
+
4
+ export interface ParserOptions {
5
+ maxErrors?: number;
6
+ throwOnError?: boolean; // When true, throws on first error instead of collecting diagnostics
7
+ trackRanges?: boolean; // Enable source range tracking for each AST node (useful for IDEs)
8
+ errorRecovery?: boolean; // Enable error recovery to continue parsing after errors (useful for IDEs)
9
+ }
10
+
11
+ export interface ParseResult {
12
+ ast: ASTNode;
13
+ diagnostics: ParseDiagnostic[];
14
+ hasErrors: boolean;
15
+ isPartial?: boolean; // Present when errorRecovery is enabled
16
+ ranges?: Map<ASTNode, TextRange>; // Present when trackRanges is enabled
17
+ }
18
+
19
+ export interface ParseDiagnostic {
20
+ range: TextRange;
21
+ severity: DiagnosticSeverity;
22
+ code: ErrorCode;
23
+ message: string;
24
+ source: 'fhirpath-parser';
25
+ relatedInformation?: RelatedInformation[];
26
+ }
27
+
28
+ export enum DiagnosticSeverity {
29
+ Error = 1,
30
+ Warning = 2,
31
+ Information = 3,
32
+ Hint = 4
33
+ }
34
+
35
+ export interface TextRange {
36
+ start: Position;
37
+ end: Position;
38
+ }
39
+
40
+ export interface Position {
41
+ line: number;
42
+ character: number;
43
+ offset: number;
44
+ }
45
+
46
+ export interface RelatedInformation {
47
+ location: TextRange;
48
+ message: string;
49
+ }
50
+
51
+ export enum ParseContext {
52
+ Expression,
53
+ FunctionCall,
54
+ IndexExpression,
55
+ BinaryExpression,
56
+ UnaryExpression,
57
+ CollectionLiteral,
58
+ TypeCast,
59
+ MembershipTest
60
+ }
@@ -0,0 +1,177 @@
1
+ # FHIRPath Parser2 - Recursive Descent with Pratt Parsing
2
+
3
+ This parser implements a recursive-descent parser with Pratt operator precedence parsing for FHIRPath expressions. It's designed to be simple, efficient, and self-contained.
4
+
5
+ ## Architecture Overview
6
+
7
+ The parser consists of:
8
+ - **Lexical Analysis**: Uses `lexer2` to tokenize the input
9
+ - **Recursive Descent**: Top-down parsing for primary expressions
10
+ - **Pratt Parsing**: Handles operator precedence and associativity
11
+ - **AST Construction**: Builds a typed Abstract Syntax Tree
12
+
13
+ ## Parser Flow
14
+
15
+ ### 1. Initialization
16
+
17
+ ```typescript
18
+ const parser = new Parser(input);
19
+ // 1. Creates a Lexer instance
20
+ // 2. Tokenizes entire input upfront
21
+ // 3. Stores tokens in array for random access
22
+ ```
23
+
24
+ ### 2. Main Entry Point
25
+
26
+ ```typescript
27
+ parse(): ASTNode
28
+ ```
29
+ - Calls `expression()` to parse the entire input
30
+ - Ensures all tokens are consumed (no trailing tokens)
31
+ - Returns the root AST node
32
+
33
+ ### 3. Expression Parsing (Pratt Algorithm)
34
+
35
+ ```typescript
36
+ parseExpressionWithPrecedence(minPrecedence): ASTNode
37
+ ```
38
+
39
+ The core of the parser uses Pratt parsing:
40
+
41
+ 1. **Parse Primary Expression** - Get the left-hand side
42
+ 2. **Parse Operators Loop** - While we have operators with precedence >= minPrecedence:
43
+ - Consume the operator
44
+ - Parse right-hand side with appropriate precedence
45
+ - Create binary/special nodes
46
+
47
+ #### Operator Precedence (highest to lowest):
48
+
49
+ | Precedence | Operators | Associativity |
50
+ |------------|-----------|---------------|
51
+ | 100 | `.` (member), `[` (index), `(` (call) | Left |
52
+ | 90 | `is`, `as` | Left |
53
+ | 80 | `*`, `/`, `div`, `mod` | Left |
54
+ | 70 | `+`, `-` | Left |
55
+ | 60 | `&` | Left |
56
+ | 50 | `<`, `>`, `<=`, `>=` | Left |
57
+ | 40 | `=`, `!=`, `~`, `!~` | Left |
58
+ | 35 | `in`, `contains` | Left |
59
+ | 30 | `and` | Left |
60
+ | 20 | `or`, `xor` | Left |
61
+ | 10 | `implies` | Right |
62
+ | 5 | `|` (union) | Left |
63
+
64
+ ### 4. Primary Expression Parsing
65
+
66
+ ```typescript
67
+ parsePrimary(): ASTNode
68
+ ```
69
+
70
+ Handles atomic expressions:
71
+ - **Literals**: Numbers, strings, booleans, null, datetime, time
72
+ - **Variables**: `$this`, `$index`, `$total`, `%env`
73
+ - **Identifiers**: Simple or delimited (backtick) identifiers
74
+ - **Parentheses**: `(expression)`
75
+ - **Collections**: `{element1, element2, ...}`
76
+ - **Unary Operators**: `+expr`, `-expr`
77
+
78
+ ### 5. Special Constructs
79
+
80
+ #### Member Access and Function Calls
81
+
82
+ After parsing `.identifier`, the parser checks if it's followed by `(` to distinguish between:
83
+ - Property access: `Patient.name`
84
+ - Method call: `Patient.name.where(...)`
85
+
86
+ ```typescript
87
+ parseInvocation(): ASTNode
88
+ // 1. Parse identifier after dot
89
+ // 2. Check for '(' to determine if it's a function call
90
+ // 3. Return appropriate node type
91
+ ```
92
+
93
+ #### Type Operations
94
+
95
+ - **Type Test**: `expression is TypeName`
96
+ - **Type Cast**: `expression as TypeName`
97
+
98
+ Both create special node types rather than generic binary operators.
99
+
100
+ #### Union Operator
101
+
102
+ The `|` operator is special - it creates/extends a UnionNode with multiple operands:
103
+ ```typescript
104
+ a | b | c => UnionNode { operands: [a, b, c] }
105
+ ```
106
+
107
+ ### 6. AST Node Types
108
+
109
+ All AST nodes implement the base `ASTNode` interface:
110
+ ```typescript
111
+ interface ASTNode {
112
+ type: NodeType;
113
+ position: Position;
114
+ }
115
+ ```
116
+
117
+ Node types include:
118
+ - **Identifier**: Simple identifiers like `name`
119
+ - **TypeOrIdentifier**: Uppercase identifiers like `Patient`
120
+ - **Literal**: Values with type information
121
+ - **Binary**: Binary operators with left/right operands
122
+ - **Unary**: Unary operators with single operand
123
+ - **Function**: Function calls with name and arguments
124
+ - **Variable**: Special variables (`$this`, `%env`)
125
+ - **Index**: Array/collection indexing
126
+ - **Union**: Multiple operands joined by `|`
127
+ - **MembershipTest**: `is` operator
128
+ - **TypeCast**: `as` operator
129
+ - **Collection**: `{}` expressions
130
+
131
+ ## Example Parse Flow
132
+
133
+ For expression: `Patient.name.where(use = 'official').given`
134
+
135
+ 1. **Primary**: Parse `Patient` → TypeOrIdentifierNode
136
+ 2. **Dot operator** (precedence 100):
137
+ - Parse `name` → IdentifierNode
138
+ - Create BinaryNode(DOT, Patient, name)
139
+ 3. **Dot operator**:
140
+ - Parse `where` → IdentifierNode
141
+ - Check for `(` → It's a function call!
142
+ - Parse arguments: `use = 'official'`
143
+ - Create FunctionNode(where, [BinaryNode(EQ, use, 'official')])
144
+ - Create BinaryNode(DOT, Patient.name, where(...))
145
+ 4. **Dot operator**:
146
+ - Parse `given` → IdentifierNode
147
+ - Create BinaryNode(DOT, Patient.name.where(...), given)
148
+
149
+ ## Key Design Decisions
150
+
151
+ 1. **Tokenize Upfront**: All tokens are generated before parsing begins, allowing lookahead and backtracking if needed.
152
+
153
+ 2. **Pratt Parsing**: Eliminates the need for separate grammar rules for each precedence level, making the parser more maintainable.
154
+
155
+ 3. **Special Node Types**: Instead of generic nodes, specific types like `MembershipTest` and `TypeCast` preserve semantic information.
156
+
157
+ 4. **Self-Contained**: All types are defined within the module, making it independent and easy to understand.
158
+
159
+ 5. **Position Tracking**: Every node includes position information for error reporting and tooling support.
160
+
161
+ ## Usage
162
+
163
+ ```typescript
164
+ import { parse } from './parser2';
165
+
166
+ const ast = parse('Patient.name.given');
167
+ console.log(JSON.stringify(ast, null, 2));
168
+ ```
169
+
170
+ ## Error Handling
171
+
172
+ The parser throws descriptive errors for:
173
+ - Unexpected tokens
174
+ - Missing closing delimiters
175
+ - Invalid syntax constructs
176
+
177
+ Errors include the problematic token value for debugging.
@@ -0,0 +1,184 @@
1
+ import { describe, it } from 'bun:test';
2
+ import { Parser } from './index';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ describe('Parser2 Performance', () => {
7
+ it('measures parser performance on fixture expressions', () => {
8
+ runPerformanceTest();
9
+ });
10
+ });
11
+
12
+ function runPerformanceTest() {
13
+ const fixturesPath = path.join(process.cwd(), 'test', 'fixtures');
14
+ const iterations = 5000; // Fewer iterations than lexer since parsing is more expensive
15
+
16
+ // Read all fixture files
17
+ const fixtureFiles = fs.readdirSync(fixturesPath)
18
+ .filter(file => file.endsWith('.json'))
19
+ .map(file => ({
20
+ name: file,
21
+ path: path.join(fixturesPath, file)
22
+ }));
23
+
24
+ console.log(`\nRunning parser2 performance tests with ${iterations} iterations per expression\n`);
25
+
26
+ let totalExpressions = 0;
27
+ let totalIterations = 0;
28
+ let totalTime = 0;
29
+ let totalTokens = 0;
30
+ let totalNodes = 0;
31
+ const expressionStats: { expression: string; time: number; tokens: number; nodes: number }[] = [];
32
+
33
+ for (const fixture of fixtureFiles) {
34
+ console.log(`Processing ${fixture.name}...`);
35
+
36
+ const content = fs.readFileSync(fixture.path, 'utf-8');
37
+ const expressions: string[] = JSON.parse(content);
38
+
39
+ for (const expression of expressions) {
40
+ if (!expression) continue;
41
+
42
+ // Warm up run and get stats
43
+ const warmupParser = new Parser(expression);
44
+ const ast = warmupParser.parse();
45
+ const tokenCount = countTokens(warmupParser);
46
+ const nodeCount = countNodes(ast);
47
+
48
+ // Measure total time for all iterations
49
+ const start = performance.now();
50
+ for (let j = 0; j < iterations; j++) {
51
+ const parser = new Parser(expression);
52
+ parser.parse();
53
+ }
54
+ const end = performance.now();
55
+
56
+ const totalTimeForExpression = end - start;
57
+ totalTime += totalTimeForExpression;
58
+ totalExpressions++;
59
+ totalIterations += iterations;
60
+ totalTokens += tokenCount * iterations;
61
+ totalNodes += nodeCount * iterations;
62
+
63
+ expressionStats.push({
64
+ expression,
65
+ time: totalTimeForExpression / iterations,
66
+ tokens: tokenCount,
67
+ nodes: nodeCount
68
+ });
69
+ }
70
+ }
71
+
72
+ const avgTimePerExpression = totalTime / totalIterations;
73
+ const avgTokensPerExpression = totalTokens / totalIterations;
74
+ const avgNodesPerExpression = totalNodes / totalIterations;
75
+
76
+ // Sort by time to find slowest expressions
77
+ expressionStats.sort((a, b) => b.time - a.time);
78
+
79
+ console.log('\n' + '='.repeat(70));
80
+ console.log('PARSER2 PERFORMANCE RESULTS');
81
+ console.log('='.repeat(70));
82
+ console.log(`Total expressions: ${totalExpressions}`);
83
+ console.log(`Total iterations: ${totalIterations}`);
84
+ console.log(`Total time: ${(totalTime / 1000).toFixed(2)}s`);
85
+ console.log(`Time per expression: ${avgTimePerExpression.toFixed(4)}ms`);
86
+ console.log(`Expressions per second: ${(1000 / avgTimePerExpression).toFixed(0)}`);
87
+ console.log(`Average tokens per expression: ${avgTokensPerExpression.toFixed(1)}`);
88
+ console.log(`Average AST nodes per expression: ${avgNodesPerExpression.toFixed(1)}`);
89
+
90
+ console.log('\n' + '='.repeat(70));
91
+ console.log('TOP 10 SLOWEST EXPRESSIONS');
92
+ console.log('='.repeat(70));
93
+ console.log('Time (ms) | Tokens | Nodes | Expression');
94
+ console.log('-'.repeat(70));
95
+
96
+ for (let i = 0; i < Math.min(10, expressionStats.length); i++) {
97
+ const stat = expressionStats[i];
98
+ const expr = stat.expression.length > 40
99
+ ? stat.expression.substring(0, 37) + '...'
100
+ : stat.expression;
101
+ console.log(
102
+ `${stat.time.toFixed(4).padStart(9)} | ` +
103
+ `${stat.tokens.toString().padStart(6)} | ` +
104
+ `${stat.nodes.toString().padStart(5)} | ` +
105
+ expr
106
+ );
107
+ }
108
+
109
+ // Calculate complexity metrics
110
+ const complexityStats = expressionStats.map(stat => ({
111
+ ...stat,
112
+ timePerToken: stat.time / stat.tokens,
113
+ timePerNode: stat.time / stat.nodes
114
+ }));
115
+
116
+ console.log('\n' + '='.repeat(70));
117
+ console.log('COMPLEXITY ANALYSIS');
118
+ console.log('='.repeat(70));
119
+
120
+ // Group by token count ranges
121
+ const tokenRanges = [
122
+ { min: 0, max: 5, label: '1-5 tokens' },
123
+ { min: 5, max: 10, label: '6-10 tokens' },
124
+ { min: 10, max: 20, label: '11-20 tokens' },
125
+ { min: 20, max: 50, label: '21-50 tokens' },
126
+ { min: 50, max: Infinity, label: '50+ tokens' }
127
+ ];
128
+
129
+ console.log('\nPerformance by expression complexity:');
130
+ console.log('Token Range | Count | Avg Time (ms) | Time/Token (μs)');
131
+ console.log('-'.repeat(55));
132
+
133
+ for (const range of tokenRanges) {
134
+ const inRange = complexityStats.filter(
135
+ s => s.tokens > range.min && s.tokens <= range.max
136
+ );
137
+ if (inRange.length > 0) {
138
+ const avgTime = inRange.reduce((sum, s) => sum + s.time, 0) / inRange.length;
139
+ const avgTimePerToken = inRange.reduce((sum, s) => sum + s.timePerToken, 0) / inRange.length;
140
+ console.log(
141
+ `${range.label.padEnd(12)} | ${inRange.length.toString().padStart(5)} | ` +
142
+ `${avgTime.toFixed(4).padStart(13)} | ${(avgTimePerToken * 1000).toFixed(2).padStart(15)}`
143
+ );
144
+ }
145
+ }
146
+ }
147
+
148
+ function countTokens(parser: Parser): number {
149
+ // Access the private tokens array via reflection
150
+ return (parser as any).tokens.length;
151
+ }
152
+
153
+ function countNodes(node: any): number {
154
+ if (!node) return 0;
155
+
156
+ let count = 1;
157
+
158
+ // Count nodes in common structures
159
+ if (node.left) count += countNodes(node.left);
160
+ if (node.right) count += countNodes(node.right);
161
+ if (node.operand) count += countNodes(node.operand);
162
+ if (node.expression) count += countNodes(node.expression);
163
+ if (node.index) count += countNodes(node.index);
164
+ if (node.name && typeof node.name === 'object') count += countNodes(node.name);
165
+
166
+ // Count nodes in arrays
167
+ if (node.arguments) {
168
+ for (const arg of node.arguments) {
169
+ count += countNodes(arg);
170
+ }
171
+ }
172
+ if (node.elements) {
173
+ for (const elem of node.elements) {
174
+ count += countNodes(elem);
175
+ }
176
+ }
177
+ if (node.operands) {
178
+ for (const op of node.operands) {
179
+ count += countNodes(op);
180
+ }
181
+ }
182
+
183
+ return count;
184
+ }