├── .editorconfig ├── .gitignore ├── .npmignore ├── .prettierrc ├── README.md ├── demo-src ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.module.ts │ │ ├── app.routes.ts │ │ ├── cel-service.service.ts │ │ └── pokemon.ts │ ├── assets │ │ └── .gitkeep │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.css ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── docs ├── 3rdpartylicenses.txt ├── chunk-YGFNLDZQ.js ├── favicon.ico ├── index.html ├── main.Y3E57JFA.js ├── polyfills.4IKBPQSR.js └── styles.K5YR3RCG.css ├── index.ts ├── jest.config.js ├── package.json ├── src ├── CelSpec.ts ├── CelSpecGrammar.ts ├── CelSpecOptions.ts ├── CelSpecParser.ts ├── Interfaces.ts ├── formatters │ ├── FormatterBase.ts │ └── TextFormatter.ts └── index.ts ├── tests ├── basic.spec.ts ├── comparisons.spec.ts ├── custom.spec.ts ├── lists.spec.ts └── logic.spec.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.md] 10 | insert_final_newline = false 11 | trim_trailing_whitespace = false 12 | 13 | [*.{js,jsx,json,ts,tsx,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependencies 7 | node_modules/ 8 | 9 | # Coverage 10 | coverage 11 | 12 | # Transpiled files 13 | dist/ 14 | dist.browser/ 15 | 16 | # JetBrains IDEs 17 | .idea/ 18 | 19 | # Optional npm cache directory 20 | .npm 21 | 22 | # Optional eslint cache 23 | .eslintcache 24 | 25 | # Misc 26 | .DS_Store 27 | 28 | # Sample 29 | .sample 30 | 31 | /.vscode/ 32 | yarn.lock 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | coverage/ 3 | tests/ 4 | node_modules/ 5 | dist/node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "overrides": [ 5 | { 6 | "files": "*.ts", 7 | "options": { 8 | "parser": "typescript" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common Expression Language - JS 2 | 3 | ## Supported functionality (subset) 4 | [x] Basic types and primitives 5 | [x] Comparisons 6 | [x] Lists 7 | [~] Logic - Ternary conditional, logical AND, logical OR 8 | [ ] etc 9 | 10 | ## Usage 11 | 12 | ``` 13 | npm install @fleker/cel-js 14 | ``` 15 | 16 | ### Create a list 17 | 18 | ```javascript 19 | const expr = '["-1"]' 20 | const expected = { 21 | list_value: { 22 | values: { string_value: `-1` } 23 | } 24 | } 25 | 26 | const celSpec = new CelSpec(); 27 | const ast = celSpec.toAST(expr, {}); 28 | const tf = new TextFormatter({}, {}) 29 | 30 | const cel = tf.format(ast) 31 | expect(cel).toStrictEqual(expected) // Returns `true` 32 | ``` 33 | 34 | ### Pass-in variables through a JSON object 35 | 36 | ```javascript 37 | const expr = 'x' 38 | const bindings = { 39 | x: 123 40 | } 41 | const expected = { 42 | int64_value: 123 43 | } 44 | const celSpec = new CelSpec(); 45 | const ast = celSpec.toAST(expr, {}); 46 | const bindingsAst = (() => { 47 | if (!bindings) return {} 48 | const tf = new TextFormatter({}, bindings) 49 | let res = {} 50 | for (const [key, entry] of Object.entries(bindings)) { 51 | const entryAst = celSpec.toAST(`${entry}`) 52 | const entryCel = tf.format(entryAst) 53 | res[key] = entryCel 54 | } 55 | return res 56 | })() 57 | const tf = new TextFormatter({}, bindingsAst) 58 | 59 | const cel = tf.format(ast) 60 | expect(cel).toStrictEqual(expected) // Returns `true` 61 | ``` 62 | -------------------------------------------------------------------------------- /demo-src/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /demo-src/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /demo-src/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /demo-src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /demo-src/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /demo-src/README.md: -------------------------------------------------------------------------------- 1 | # DemoSrc 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /demo-src/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "demo-src": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:application", 15 | "options": { 16 | "outputPath": "../docs", 17 | "index": "src/index.html", 18 | "browser": "src/main.ts", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "tsConfig": "tsconfig.app.json", 23 | "assets": [ 24 | "src/favicon.ico", 25 | "src/assets" 26 | ], 27 | "styles": [ 28 | "src/styles.css" 29 | ], 30 | "scripts": [] 31 | }, 32 | "configurations": { 33 | "production": { 34 | "budgets": [ 35 | { 36 | "type": "initial", 37 | "maximumWarning": "500kb", 38 | "maximumError": "1mb" 39 | }, 40 | { 41 | "type": "anyComponentStyle", 42 | "maximumWarning": "2kb", 43 | "maximumError": "4kb" 44 | } 45 | ], 46 | "outputHashing": "all" 47 | }, 48 | "development": { 49 | "optimization": false, 50 | "extractLicenses": false, 51 | "sourceMap": true 52 | } 53 | }, 54 | "defaultConfiguration": "production" 55 | }, 56 | "serve": { 57 | "builder": "@angular-devkit/build-angular:dev-server", 58 | "configurations": { 59 | "production": { 60 | "browserTarget": "demo-src:build:production" 61 | }, 62 | "development": { 63 | "browserTarget": "demo-src:build:development" 64 | } 65 | }, 66 | "defaultConfiguration": "development" 67 | }, 68 | "extract-i18n": { 69 | "builder": "@angular-devkit/build-angular:extract-i18n", 70 | "options": { 71 | "browserTarget": "demo-src:build" 72 | } 73 | }, 74 | "test": { 75 | "builder": "@angular-devkit/build-angular:karma", 76 | "options": { 77 | "polyfills": [ 78 | "zone.js", 79 | "zone.js/testing" 80 | ], 81 | "tsConfig": "tsconfig.spec.json", 82 | "assets": [ 83 | "src/favicon.ico", 84 | "src/assets" 85 | ], 86 | "styles": [ 87 | "src/styles.css" 88 | ], 89 | "scripts": [] 90 | } 91 | } 92 | } 93 | } 94 | }, 95 | "cli": { 96 | "analytics": "74e1a390-1685-4a95-b755-c414e6167363" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /demo-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-src", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.0.0", 14 | "@angular/common": "^16.0.0", 15 | "@angular/compiler": "^16.0.0", 16 | "@angular/core": "^16.0.0", 17 | "@angular/forms": "^16.0.0", 18 | "@angular/platform-browser": "^16.0.0", 19 | "@angular/platform-browser-dynamic": "^16.0.0", 20 | "@fleker/cel-js": "^1.0.0", 21 | "rxjs": "~7.8.0", 22 | "zone.js": "~0.13.0" 23 | }, 24 | "devDependencies": { 25 | "@angular-devkit/build-angular": "^16.0.0", 26 | "@angular/cli": "^16.0.0", 27 | "@angular/compiler-cli": "^16.0.0", 28 | "typescript": "~5.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo-src/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fleker/cel-js/17d308bf7d3e4f83117d9408ca6e524f03b809f9/demo-src/src/app/app.component.css -------------------------------------------------------------------------------- /demo-src/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

Your Pokémon

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 27 | 28 | 29 |
Dex # dexSpecies speciesType(s) typesForm form
{{pokemon.dex}}{{pokemon.species}} 19 | {{pokemon.type1}} 20 | /{{pokemon.type2}} 21 | 23 | 24 | {{pokemon.form}} Form 25 | 26 |
-------------------------------------------------------------------------------- /demo-src/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | imports: [AppComponent], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have the 'demo-src' title`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('demo-src'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, demo-src'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /demo-src/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CelServiceService } from './cel-service.service'; 3 | import { Pokemon } from './pokemon'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | title = 'demo-src'; 12 | 13 | playerPokemon: Pokemon[] = [{ 14 | dex: 1, 15 | species: 'Bulbasaur', 16 | type1: 'Grass', 17 | type2: 'Poison', 18 | }, { 19 | dex: 4, 20 | species: 'Charmander', 21 | type1: 'Fire', 22 | }, { 23 | dex: 12, 24 | species: 'Butterfree', 25 | type1: 'Bug', 26 | type2: 'Flying', 27 | }, { 28 | dex: 201, 29 | species: 'Unown', 30 | type1: 'Psychic', 31 | form: '?' 32 | }] 33 | 34 | filterPlayerPokemon: Pokemon[] = [...this.playerPokemon] 35 | 36 | cel = '' 37 | searching = false 38 | 39 | constructor(private celService: CelServiceService) {} 40 | 41 | search() { 42 | this.searching = true 43 | window.requestAnimationFrame(async () => { 44 | this.filterPlayerPokemon = await this.celService.run(this.playerPokemon, this.cel) 45 | this.searching = false 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /demo-src/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | 4 | import { routes } from './app.routes'; 5 | 6 | export const appConfig: ApplicationConfig = { 7 | providers: [provideRouter(routes)] 8 | }; 9 | -------------------------------------------------------------------------------- /demo-src/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | 5 | import { FormsModule } from '@angular/forms'; 6 | import { AppComponent } from './app.component'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | AppComponent, 11 | ], 12 | imports: [ 13 | BrowserModule, 14 | FormsModule, 15 | BrowserAnimationsModule, 16 | ], 17 | bootstrap: [AppComponent] 18 | }) 19 | export class AppModule { } -------------------------------------------------------------------------------- /demo-src/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const routes: Routes = []; 4 | -------------------------------------------------------------------------------- /demo-src/src/app/cel-service.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CelSpec, TextFormatter } from '@fleker/cel-js'; 3 | import { Subject } from 'rxjs'; 4 | import { Pokemon } from './pokemon'; 5 | 6 | type Entry = Pokemon 7 | type Entries = Entry[] 8 | interface Task { 9 | fn: (() => Promise) 10 | runId: number 11 | } 12 | 13 | interface SubscriberTask { 14 | fn: (() => Promise) 15 | runId: number 16 | } 17 | 18 | interface CelEvent { 19 | pokemon?: Entry 20 | pct: number 21 | } 22 | 23 | declare let window: { 24 | scheduler: { 25 | // eslint-disable-next-line @typescript-eslint/ban-types 26 | yield: Function 27 | }, 28 | // eslint-disable-next-line @typescript-eslint/ban-types 29 | requestAnimationFrame: Function, 30 | } 31 | 32 | @Injectable({ 33 | providedIn: 'root' 34 | }) 35 | export class CelServiceService { 36 | customTags: string[] = [] 37 | schedulerYield = false 38 | runId = -1 39 | 40 | // Code listed below is part of Chrome's effort to improve the UI thread 41 | // While JS is still not multi-threaded, breaking up steps can help improve 42 | // performance for operations like catching and hatching. 43 | // See https://web.dev/optimize-long-tasks/ for future APIs. 44 | private yieldToMain() { 45 | if (this.schedulerYield && 'scheduler' in window && 'yield' in window.scheduler) { 46 | console.debug('using scheduler yield to perform a yield operation') 47 | return window.scheduler.yield() 48 | } 49 | return new Promise(resolve => { 50 | setTimeout(resolve, 0); 51 | }); 52 | } 53 | 54 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 55 | filterEntry(entry: Entry, ast: any, celSpec: CelSpec): boolean { 56 | const bindings = { 57 | species: `"${entry.species}"`, 58 | dex: `${entry.dex}`, 59 | types: entry.type2 ? 60 | `["${entry.type1}", "${entry.type2}"]` : 61 | `["${entry.type1}"]`, 62 | // FIXME Hack for Unown 63 | form: entry.form ? 64 | `'${entry.form?.replace(/[?]/, 'Question').replace(/[!]/, 'Exclamation')}'` 65 | : `''`, 66 | } 67 | 68 | const bindingsAst = (() => { 69 | if (!bindings) return {} 70 | const tf = new TextFormatter({}, bindings) 71 | const res: Record = { 72 | form: { 73 | string_value: '' 74 | }, // Just as default 75 | } 76 | for (const [key, entry] of Object.entries(bindings)) { 77 | const entryAst = celSpec.toAST(`${entry}`) 78 | try { 79 | const entryCel = tf.format(entryAst) 80 | res[key] = entryCel 81 | } catch (e) { 82 | console.error(`Cannot CEL bind ${key} as ${entry}: ${e}`, res, entryAst, ast) 83 | } 84 | } 85 | return res 86 | })() 87 | const tf = new TextFormatter({}, bindingsAst) 88 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 89 | const cel = tf.format(ast) as any 90 | if (cel.bool_value) { 91 | return true 92 | } 93 | return false 94 | } 95 | 96 | async run(inputEntries: Entries, expr: string) { 97 | if (!expr || !expr.length) expr = 'true' 98 | this.runId = Date.now() 99 | const celSpec = new CelSpec() 100 | const ast = celSpec.toAST(expr, {}); 101 | const tasks: Task[] = [] 102 | const filteredEntries: Entry[] = [] 103 | 104 | inputEntries.forEach(entry => { 105 | tasks.push({ 106 | runId: this.runId, 107 | fn: async () => { 108 | if (this.filterEntry(entry, ast, celSpec)) { 109 | filteredEntries.push(entry) 110 | } 111 | } 112 | }) 113 | }) 114 | while (tasks.length > 0) { 115 | const task = tasks.shift()! 116 | if (task.runId === this.runId) { 117 | await task.fn() 118 | if (tasks.length % 100 === 0) { 119 | await this.yieldToMain() 120 | } 121 | } 122 | } 123 | console.debug('CEL done') 124 | return filteredEntries 125 | } 126 | 127 | async runAndSubscribe(inputEntries: Entries, expr: string) { 128 | if (!expr || !expr.length) expr = 'true' 129 | this.runId = Date.now() 130 | const celSpec = new CelSpec() 131 | const ast = celSpec.toAST(expr, {}); 132 | return this.schedulerYield ? this.execWithScheduler(ast, celSpec, inputEntries) 133 | : this.execWithTasks(ast, celSpec, inputEntries) 134 | } 135 | 136 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 137 | async execWithTasks(ast: any, celSpec: CelSpec, inputEntries: Entries) { 138 | const tasks: SubscriberTask[] = [] 139 | 140 | inputEntries.forEach(entry => { 141 | tasks.push({ 142 | runId: this.runId, 143 | fn: async () => { 144 | if (this.filterEntry(entry, ast, celSpec)) { 145 | return entry 146 | } 147 | return undefined 148 | } 149 | }) 150 | }) 151 | 152 | const subscriber = new Subject() 153 | const taskTotal = tasks.length 154 | window.requestAnimationFrame(async () => { 155 | while (tasks.length > 0) { 156 | const task = tasks.shift()! 157 | if (task.runId === this.runId) { 158 | const entry = await task.fn() 159 | if (entry) { 160 | const pct = (taskTotal - tasks.length) / taskTotal * 100 161 | subscriber.next({ 162 | pokemon: entry, 163 | pct, 164 | }) 165 | // console.debug('CEL at', pct) 166 | } else if (tasks.length % 100 === 0) { 167 | const pct = (taskTotal - tasks.length) / taskTotal * 100 168 | subscriber.next({ 169 | pokemon: undefined, 170 | pct, 171 | }) 172 | await this.yieldToMain() 173 | // console.debug('CEL at', pct) 174 | } 175 | } 176 | } 177 | subscriber.next({ 178 | pokemon: undefined, 179 | pct: 0, 180 | }) 181 | // console.debug('CEL done') 182 | }) 183 | return subscriber 184 | } 185 | 186 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 187 | async execWithScheduler(ast: any, celSpec: CelSpec, inputEntries: Entries) { 188 | const subscriber = new Subject() 189 | let taskId = 0 190 | const taskTotal = inputEntries.length 191 | const runId = this.runId 192 | 193 | window.requestAnimationFrame(async () => { 194 | for (const entry of inputEntries) { 195 | if (runId === this.runId) { 196 | const match = this.filterEntry(entry, ast, celSpec) 197 | if (match) { 198 | const pct = taskId / taskTotal * 100 199 | subscriber.next({ 200 | pokemon: entry, 201 | pct, 202 | }) 203 | // console.debug('Scheduler Match', pct, entry) 204 | // await this.yieldToMain() 205 | } 206 | if (++taskId % 100 === 0) { 207 | const pct = taskId / taskTotal * 100 208 | subscriber.next({ 209 | pokemon: undefined, 210 | pct, 211 | }) 212 | console.debug('Scheduler Modul', pct) 213 | await this.yieldToMain() 214 | } 215 | } 216 | } 217 | 218 | subscriber.next({ 219 | pokemon: undefined, 220 | pct: 0, 221 | }) 222 | }) 223 | return subscriber 224 | } 225 | 226 | stop() { 227 | this.runId = Date.now() // Forces existing tasks to no-op 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /demo-src/src/app/pokemon.ts: -------------------------------------------------------------------------------- 1 | export type Type = 'Bug' | 'Grass' | 'Poison' | 'Ghost' | 'Psychic' 2 | | 'Steel' | 'Dark' | 'Fairy' | 'Dragon' | 'Flying' 3 | | 'Normal' | 'Fire' | 'Water' | 'Ice' | 'Electric' 4 | | 'Rock' | 'Ground' | 'Fighting' 5 | 6 | export interface Pokemon { 7 | dex: number 8 | species: string 9 | type1: Type 10 | type2?: Type 11 | form?: string 12 | } -------------------------------------------------------------------------------- /demo-src/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fleker/cel-js/17d308bf7d3e4f83117d9408ca6e524f03b809f9/demo-src/src/assets/.gitkeep -------------------------------------------------------------------------------- /demo-src/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fleker/cel-js/17d308bf7d3e4f83117d9408ca6e524f03b809f9/demo-src/src/favicon.ico -------------------------------------------------------------------------------- /demo-src/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DemoSrc 6 | 7 | 8 | 9 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /demo-src/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); -------------------------------------------------------------------------------- /demo-src/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | background-color: #333; 4 | color: #efefef; 5 | } -------------------------------------------------------------------------------- /demo-src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /demo-src/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "sourceMap": true, 15 | "declaration": false, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "lib": [ 23 | "ES2022", 24 | "dom" 25 | ] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demo-src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docs/3rdpartylicenses.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- 3 | Package: rxjs 4 | License: "Apache-2.0" 5 | 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, 15 | and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by 18 | the copyright owner that is granting the License. 19 | 20 | "Legal Entity" shall mean the union of the acting entity and all 21 | other entities that control, are controlled by, or are under common 22 | control with that entity. For the purposes of this definition, 23 | "control" means (i) the power, direct or indirect, to cause the 24 | direction or management of such entity, whether by contract or 25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity 29 | exercising permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, 32 | including but not limited to software source code, documentation 33 | source, and configuration files. 34 | 35 | "Object" form shall mean any form resulting from mechanical 36 | transformation or translation of a Source form, including but 37 | not limited to compiled object code, generated documentation, 38 | and conversions to other media types. 39 | 40 | "Work" shall mean the work of authorship, whether in Source or 41 | Object form, made available under the License, as indicated by a 42 | copyright notice that is included in or attached to the work 43 | (an example is provided in the Appendix below). 44 | 45 | "Derivative Works" shall mean any work, whether in Source or Object 46 | form, that is based on (or derived from) the Work and for which the 47 | editorial revisions, annotations, elaborations, or other modifications 48 | represent, as a whole, an original work of authorship. For the purposes 49 | of this License, Derivative Works shall not include works that remain 50 | separable from, or merely link (or bind by name) to the interfaces of, 51 | the Work and Derivative Works thereof. 52 | 53 | "Contribution" shall mean any work of authorship, including 54 | the original version of the Work and any modifications or additions 55 | to that Work or Derivative Works thereof, that is intentionally 56 | submitted to Licensor for inclusion in the Work by the copyright owner 57 | or by an individual or Legal Entity authorized to submit on behalf of 58 | the copyright owner. For the purposes of this definition, "submitted" 59 | means any form of electronic, verbal, or written communication sent 60 | to the Licensor or its representatives, including but not limited to 61 | communication on electronic mailing lists, source code control systems, 62 | and issue tracking systems that are managed by, or on behalf of, the 63 | Licensor for the purpose of discussing and improving the Work, but 64 | excluding communication that is conspicuously marked or otherwise 65 | designated in writing by the copyright owner as "Not a Contribution." 66 | 67 | "Contributor" shall mean Licensor and any individual or Legal Entity 68 | on behalf of whom a Contribution has been received by Licensor and 69 | subsequently incorporated within the Work. 70 | 71 | 2. Grant of Copyright License. Subject to the terms and conditions of 72 | this License, each Contributor hereby grants to You a perpetual, 73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 74 | copyright license to reproduce, prepare Derivative Works of, 75 | publicly display, publicly perform, sublicense, and distribute the 76 | Work and such Derivative Works in Source or Object form. 77 | 78 | 3. Grant of Patent License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | (except as stated in this section) patent license to make, have made, 82 | use, offer to sell, sell, import, and otherwise transfer the Work, 83 | where such license applies only to those patent claims licensable 84 | by such Contributor that are necessarily infringed by their 85 | Contribution(s) alone or by combination of their Contribution(s) 86 | with the Work to which such Contribution(s) was submitted. If You 87 | institute patent litigation against any entity (including a 88 | cross-claim or counterclaim in a lawsuit) alleging that the Work 89 | or a Contribution incorporated within the Work constitutes direct 90 | or contributory patent infringement, then any patent licenses 91 | granted to You under this License for that Work shall terminate 92 | as of the date such litigation is filed. 93 | 94 | 4. Redistribution. You may reproduce and distribute copies of the 95 | Work or Derivative Works thereof in any medium, with or without 96 | modifications, and in Source or Object form, provided that You 97 | meet the following conditions: 98 | 99 | (a) You must give any other recipients of the Work or 100 | Derivative Works a copy of this License; and 101 | 102 | (b) You must cause any modified files to carry prominent notices 103 | stating that You changed the files; and 104 | 105 | (c) You must retain, in the Source form of any Derivative Works 106 | that You distribute, all copyright, patent, trademark, and 107 | attribution notices from the Source form of the Work, 108 | excluding those notices that do not pertain to any part of 109 | the Derivative Works; and 110 | 111 | (d) If the Work includes a "NOTICE" text file as part of its 112 | distribution, then any Derivative Works that You distribute must 113 | include a readable copy of the attribution notices contained 114 | within such NOTICE file, excluding those notices that do not 115 | pertain to any part of the Derivative Works, in at least one 116 | of the following places: within a NOTICE text file distributed 117 | as part of the Derivative Works; within the Source form or 118 | documentation, if provided along with the Derivative Works; or, 119 | within a display generated by the Derivative Works, if and 120 | wherever such third-party notices normally appear. The contents 121 | of the NOTICE file are for informational purposes only and 122 | do not modify the License. You may add Your own attribution 123 | notices within Derivative Works that You distribute, alongside 124 | or as an addendum to the NOTICE text from the Work, provided 125 | that such additional attribution notices cannot be construed 126 | as modifying the License. 127 | 128 | You may add Your own copyright statement to Your modifications and 129 | may provide additional or different license terms and conditions 130 | for use, reproduction, or distribution of Your modifications, or 131 | for any such Derivative Works as a whole, provided Your use, 132 | reproduction, and distribution of the Work otherwise complies with 133 | the conditions stated in this License. 134 | 135 | 5. Submission of Contributions. Unless You explicitly state otherwise, 136 | any Contribution intentionally submitted for inclusion in the Work 137 | by You to the Licensor shall be under the terms and conditions of 138 | this License, without any additional terms or conditions. 139 | Notwithstanding the above, nothing herein shall supersede or modify 140 | the terms of any separate license agreement you may have executed 141 | with Licensor regarding such Contributions. 142 | 143 | 6. Trademarks. This License does not grant permission to use the trade 144 | names, trademarks, service marks, or product names of the Licensor, 145 | except as required for reasonable and customary use in describing the 146 | origin of the Work and reproducing the content of the NOTICE file. 147 | 148 | 7. Disclaimer of Warranty. Unless required by applicable law or 149 | agreed to in writing, Licensor provides the Work (and each 150 | Contributor provides its Contributions) on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 152 | implied, including, without limitation, any warranties or conditions 153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 154 | PARTICULAR PURPOSE. You are solely responsible for determining the 155 | appropriateness of using or redistributing the Work and assume any 156 | risks associated with Your exercise of permissions under this License. 157 | 158 | 8. Limitation of Liability. In no event and under no legal theory, 159 | whether in tort (including negligence), contract, or otherwise, 160 | unless required by applicable law (such as deliberate and grossly 161 | negligent acts) or agreed to in writing, shall any Contributor be 162 | liable to You for damages, including any direct, indirect, special, 163 | incidental, or consequential damages of any character arising as a 164 | result of this License or out of the use or inability to use the 165 | Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all 167 | other commercial damages or losses), even if such Contributor 168 | has been advised of the possibility of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing 171 | the Work or Derivative Works thereof, You may choose to offer, 172 | and charge a fee for, acceptance of support, warranty, indemnity, 173 | or other liability obligations and/or rights consistent with this 174 | License. However, in accepting such obligations, You may act only 175 | on Your own behalf and on Your sole responsibility, not on behalf 176 | of any other Contributor, and only if You agree to indemnify, 177 | defend, and hold each Contributor harmless for any liability 178 | incurred by, or claims asserted against, such Contributor by reason 179 | of your accepting any such warranty or additional liability. 180 | 181 | END OF TERMS AND CONDITIONS 182 | 183 | APPENDIX: How to apply the Apache License to your work. 184 | 185 | To apply the Apache License to your work, attach the following 186 | boilerplate notice, with the fields enclosed by brackets "[]" 187 | replaced with your own identifying information. (Don't include 188 | the brackets!) The text should be enclosed in the appropriate 189 | comment syntax for the file format. We also recommend that a 190 | file or class name and description of purpose be included on the 191 | same "printed page" as the copyright notice for easier 192 | identification within third-party archives. 193 | 194 | Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors 195 | 196 | Licensed under the Apache License, Version 2.0 (the "License"); 197 | you may not use this file except in compliance with the License. 198 | You may obtain a copy of the License at 199 | 200 | http://www.apache.org/licenses/LICENSE-2.0 201 | 202 | Unless required by applicable law or agreed to in writing, software 203 | distributed under the License is distributed on an "AS IS" BASIS, 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | See the License for the specific language governing permissions and 206 | limitations under the License. 207 | 208 | 209 | -------------------------------------------------------------------------------- 210 | Package: tslib 211 | License: "0BSD" 212 | 213 | Copyright (c) Microsoft Corporation. 214 | 215 | Permission to use, copy, modify, and/or distribute this software for any 216 | purpose with or without fee is hereby granted. 217 | 218 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 219 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 220 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 221 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 222 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 223 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 224 | PERFORMANCE OF THIS SOFTWARE. 225 | -------------------------------------------------------------------------------- 226 | Package: @angular/core 227 | License: "MIT" 228 | 229 | 230 | -------------------------------------------------------------------------------- 231 | Package: @angular/common 232 | License: "MIT" 233 | 234 | 235 | -------------------------------------------------------------------------------- 236 | Package: @angular/platform-browser 237 | License: "MIT" 238 | 239 | 240 | -------------------------------------------------------------------------------- 241 | Package: @angular/animations 242 | License: "MIT" 243 | 244 | 245 | -------------------------------------------------------------------------------- 246 | Package: @angular/forms 247 | License: "MIT" 248 | 249 | 250 | -------------------------------------------------------------------------------- 251 | Package: @fleker/cel-js 252 | License: "" 253 | 254 | 255 | -------------------------------------------------------------------------------- 256 | Package: zone.js 257 | License: "MIT" 258 | 259 | The MIT License 260 | 261 | Copyright (c) 2010-2023 Google LLC. https://angular.io/license 262 | 263 | Permission is hereby granted, free of charge, to any person obtaining a copy 264 | of this software and associated documentation files (the "Software"), to deal 265 | in the Software without restriction, including without limitation the rights 266 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 267 | copies of the Software, and to permit persons to whom the Software is 268 | furnished to do so, subject to the following conditions: 269 | 270 | The above copyright notice and this permission notice shall be included in 271 | all copies or substantial portions of the Software. 272 | 273 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 274 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 275 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 276 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 277 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 278 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 279 | THE SOFTWARE. 280 | 281 | -------------------------------------------------------------------------------- 282 | -------------------------------------------------------------------------------- /docs/chunk-YGFNLDZQ.js: -------------------------------------------------------------------------------- 1 | var m=Object.defineProperty,n=Object.defineProperties;var o=Object.getOwnPropertyDescriptors;var h=Object.getOwnPropertySymbols;var p=Object.prototype.hasOwnProperty,q=Object.prototype.propertyIsEnumerable;var i=(c,b,a)=>b in c?m(c,b,{enumerable:!0,configurable:!0,writable:!0,value:a}):c[b]=a,r=(c,b)=>{for(var a in b||={})p.call(b,a)&&i(c,a,b[a]);if(h)for(var a of h(b))q.call(b,a)&&i(c,a,b[a]);return c},s=(c,b)=>n(c,o(b));var t=(c,b,a)=>new Promise((j,g)=>{var k=d=>{try{e(a.next(d))}catch(f){g(f)}},l=d=>{try{e(a.throw(d))}catch(f){g(f)}},e=d=>d.done?j(d.value):Promise.resolve(d.value).then(k,l);e((a=a.apply(c,b)).next())});export{r as a,s as b,t as c}; 2 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fleker/cel-js/17d308bf7d3e4f83117d9408ca6e524f03b809f9/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DemoSrc 6 | 7 | 8 | 9 | 13 | 15 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/polyfills.4IKBPQSR.js: -------------------------------------------------------------------------------- 1 | import{a as We,b as qe}from"./chunk-YGFNLDZQ.js";(function(e){let n=e.performance;function s(A){n&&n.mark&&n.mark(A)}function r(A,h){n&&n.measure&&n.measure(A,h)}s("Zone");let i=e.__Zone_symbol_prefix||"__zone_symbol__";function l(A){return i+A}let m=e[l("forceDuplicateZoneCheck")]===!0;if(e.Zone){if(m||typeof e.Zone.__symbol__!="function")throw new Error("Zone already loaded.");return e.Zone}let E=(()=>{let h=class h{static assertZonePatched(){if(e.Promise!==oe.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let t=h.current;for(;t.parent;)t=t.parent;return t}static get current(){return W.zone}static get currentTask(){return ne}static __load_patch(t,_,w=!1){if(oe.hasOwnProperty(t)){if(!w&&m)throw Error("Already loaded patch: "+t)}else if(!e["__Zone_disable_"+t]){let L="Zone:"+t;s(L),oe[t]=_(e,h,Y),r(L,L)}}get parent(){return this._parent}get name(){return this._name}constructor(t,_){this._parent=t,this._name=_?_.name||"unnamed":"",this._properties=_&&_.properties||{},this._zoneDelegate=new v(this,this._parent&&this._parent._zoneDelegate,_)}get(t){let _=this.getZoneWith(t);if(_)return _._properties[t]}getZoneWith(t){let _=this;for(;_;){if(_._properties.hasOwnProperty(t))return _;_=_._parent}return null}fork(t){if(!t)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,t)}wrap(t,_){if(typeof t!="function")throw new Error("Expecting function got: "+t);let w=this._zoneDelegate.intercept(this,t,_),L=this;return function(){return L.runGuarded(w,this,arguments,_)}}run(t,_,w,L){W={parent:W,zone:this};try{return this._zoneDelegate.invoke(this,t,_,w,L)}finally{W=W.parent}}runGuarded(t,_=null,w,L){W={parent:W,zone:this};try{try{return this._zoneDelegate.invoke(this,t,_,w,L)}catch(a){if(this._zoneDelegate.handleError(this,a))throw a}}finally{W=W.parent}}runTask(t,_,w){if(t.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(t.zone||J).name+"; Execution: "+this.name+")");if(t.state===G&&(t.type===Q||t.type===P))return;let L=t.state!=y;L&&t._transitionTo(y,j),t.runCount++;let a=ne;ne=t,W={parent:W,zone:this};try{t.type==P&&t.data&&!t.data.isPeriodic&&(t.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,t,_,w)}catch(u){if(this._zoneDelegate.handleError(this,u))throw u}}finally{t.state!==G&&t.state!==d&&(t.type==Q||t.data&&t.data.isPeriodic?L&&t._transitionTo(j,y):(t.runCount=0,this._updateTaskCount(t,-1),L&&t._transitionTo(G,y,G))),W=W.parent,ne=a}}scheduleTask(t){if(t.zone&&t.zone!==this){let w=this;for(;w;){if(w===t.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${t.zone.name}`);w=w.parent}}t._transitionTo(z,G);let _=[];t._zoneDelegates=_,t._zone=this;try{t=this._zoneDelegate.scheduleTask(this,t)}catch(w){throw t._transitionTo(d,z,G),this._zoneDelegate.handleError(this,w),w}return t._zoneDelegates===_&&this._updateTaskCount(t,1),t.state==z&&t._transitionTo(j,z),t}scheduleMicroTask(t,_,w,L){return this.scheduleTask(new p(I,t,_,w,L,void 0))}scheduleMacroTask(t,_,w,L,a){return this.scheduleTask(new p(P,t,_,w,L,a))}scheduleEventTask(t,_,w,L,a){return this.scheduleTask(new p(Q,t,_,w,L,a))}cancelTask(t){if(t.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(t.zone||J).name+"; Execution: "+this.name+")");if(!(t.state!==j&&t.state!==y)){t._transitionTo(V,j,y);try{this._zoneDelegate.cancelTask(this,t)}catch(_){throw t._transitionTo(d,V),this._zoneDelegate.handleError(this,_),_}return this._updateTaskCount(t,-1),t._transitionTo(G,V),t.runCount=0,t}}_updateTaskCount(t,_){let w=t._zoneDelegates;_==-1&&(t._zoneDelegates=null);for(let L=0;LA.hasTask(c,t),onScheduleTask:(A,h,c,t)=>A.scheduleTask(c,t),onInvokeTask:(A,h,c,t,_,w)=>A.invokeTask(c,t,_,w),onCancelTask:(A,h,c,t)=>A.cancelTask(c,t)};class v{constructor(h,c,t){this._taskCounts={microTask:0,macroTask:0,eventTask:0},this.zone=h,this._parentDelegate=c,this._forkZS=t&&(t&&t.onFork?t:c._forkZS),this._forkDlgt=t&&(t.onFork?c:c._forkDlgt),this._forkCurrZone=t&&(t.onFork?this.zone:c._forkCurrZone),this._interceptZS=t&&(t.onIntercept?t:c._interceptZS),this._interceptDlgt=t&&(t.onIntercept?c:c._interceptDlgt),this._interceptCurrZone=t&&(t.onIntercept?this.zone:c._interceptCurrZone),this._invokeZS=t&&(t.onInvoke?t:c._invokeZS),this._invokeDlgt=t&&(t.onInvoke?c:c._invokeDlgt),this._invokeCurrZone=t&&(t.onInvoke?this.zone:c._invokeCurrZone),this._handleErrorZS=t&&(t.onHandleError?t:c._handleErrorZS),this._handleErrorDlgt=t&&(t.onHandleError?c:c._handleErrorDlgt),this._handleErrorCurrZone=t&&(t.onHandleError?this.zone:c._handleErrorCurrZone),this._scheduleTaskZS=t&&(t.onScheduleTask?t:c._scheduleTaskZS),this._scheduleTaskDlgt=t&&(t.onScheduleTask?c:c._scheduleTaskDlgt),this._scheduleTaskCurrZone=t&&(t.onScheduleTask?this.zone:c._scheduleTaskCurrZone),this._invokeTaskZS=t&&(t.onInvokeTask?t:c._invokeTaskZS),this._invokeTaskDlgt=t&&(t.onInvokeTask?c:c._invokeTaskDlgt),this._invokeTaskCurrZone=t&&(t.onInvokeTask?this.zone:c._invokeTaskCurrZone),this._cancelTaskZS=t&&(t.onCancelTask?t:c._cancelTaskZS),this._cancelTaskDlgt=t&&(t.onCancelTask?c:c._cancelTaskDlgt),this._cancelTaskCurrZone=t&&(t.onCancelTask?this.zone:c._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;let _=t&&t.onHasTask,w=c&&c._hasTaskZS;(_||w)&&(this._hasTaskZS=_?t:b,this._hasTaskDlgt=c,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=h,t.onScheduleTask||(this._scheduleTaskZS=b,this._scheduleTaskDlgt=c,this._scheduleTaskCurrZone=this.zone),t.onInvokeTask||(this._invokeTaskZS=b,this._invokeTaskDlgt=c,this._invokeTaskCurrZone=this.zone),t.onCancelTask||(this._cancelTaskZS=b,this._cancelTaskDlgt=c,this._cancelTaskCurrZone=this.zone))}fork(h,c){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,h,c):new E(h,c)}intercept(h,c,t){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,h,c,t):c}invoke(h,c,t,_,w){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,h,c,t,_,w):c.apply(t,_)}handleError(h,c){return this._handleErrorZS?this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,h,c):!0}scheduleTask(h,c){let t=c;if(this._scheduleTaskZS)this._hasTaskZS&&t._zoneDelegates.push(this._hasTaskDlgtOwner),t=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,h,c),t||(t=c);else if(c.scheduleFn)c.scheduleFn(c);else if(c.type==I)C(c);else throw new Error("Task is missing scheduleFn.");return t}invokeTask(h,c,t,_){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,h,c,t,_):c.callback.apply(t,_)}cancelTask(h,c){let t;if(this._cancelTaskZS)t=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,h,c);else{if(!c.cancelFn)throw Error("Task is not cancelable");t=c.cancelFn(c)}return t}hasTask(h,c){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,h,c)}catch(t){this.handleError(h,t)}}_updateTaskCount(h,c){let t=this._taskCounts,_=t[h],w=t[h]=_+c;if(w<0)throw new Error("More tasks executed then were scheduled.");if(_==0||w==0){let L={microTask:t.microTask>0,macroTask:t.macroTask>0,eventTask:t.eventTask>0,change:h};this.hasTask(this.zone,L)}}}class p{constructor(h,c,t,_,w,L){if(this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=h,this.source=c,this.data=_,this.scheduleFn=w,this.cancelFn=L,!t)throw new Error("callback is not defined");this.callback=t;let a=this;h===Q&&_&&_.useG?this.invoke=p.invokeTask:this.invoke=function(){return p.invokeTask.call(e,a,this,arguments)}}static invokeTask(h,c,t){h||(h=this),ee++;try{return h.runCount++,h.zone.runTask(h,c,t)}finally{ee==1&&T(),ee--}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(G,z)}_transitionTo(h,c,t){if(this._state===c||this._state===t)this._state=h,h==G&&(this._zoneDelegates=null);else throw new Error(`${this.type} '${this.source}': can not transition to '${h}', expecting state '${c}'${t?" or '"+t+"'":""}, was '${this._state}'.`)}toString(){return this.data&&typeof this.data.handleId<"u"?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}let M=l("setTimeout"),O=l("Promise"),N=l("then"),U=[],H=!1,K;function X(A){if(K||e[O]&&(K=e[O].resolve(0)),K){let h=K[N];h||(h=K.then),h.call(K,A)}else e[M](A,0)}function C(A){ee===0&&U.length===0&&X(T),A&&U.push(A)}function T(){if(!H){for(H=!0;U.length;){let A=U;U=[];for(let h=0;hW,onUnhandledError:q,microtaskDrainDone:q,scheduleMicroTask:C,showUncaughtError:()=>!E[l("ignoreConsoleErrorUncaughtError")],patchEventTarget:()=>[],patchOnProperties:q,patchMethod:()=>q,bindArguments:()=>[],patchThen:()=>q,patchMacroTask:()=>q,patchEventPrototype:()=>q,isIEOrEdge:()=>!1,getGlobalObjects:()=>{},ObjectDefineProperty:()=>q,ObjectGetOwnPropertyDescriptor:()=>{},ObjectCreate:()=>{},ArraySlice:()=>[],patchClass:()=>q,wrapWithCurrentZone:()=>q,filterProperties:()=>[],attachOriginToPatched:()=>q,_redefineProperty:()=>q,patchCallbacks:()=>q,nativeScheduleMicroTask:X},W={parent:null,zone:new E(null,null)},ne=null,ee=0;function q(){}return r("Zone","Zone"),e.Zone=E})(typeof window<"u"&&window||typeof self<"u"&&self||global);var me=Object.getOwnPropertyDescriptor,Ne=Object.defineProperty,Ie=Object.getPrototypeOf,at=Object.create,lt=Array.prototype.slice,Me="addEventListener",Le="removeEventListener",Se=Zone.__symbol__(Me),De=Zone.__symbol__(Le),ie="true",ce="false",pe=Zone.__symbol__("");function Ae(e,n){return Zone.current.wrap(e,n)}function je(e,n,s,r,i){return Zone.current.scheduleMacroTask(e,n,s,r,i)}var x=Zone.__symbol__,Pe=typeof window<"u",Te=Pe?window:void 0,$=Pe&&Te||typeof self=="object"&&self||global,ut="removeAttribute";function He(e,n){for(let s=e.length-1;s>=0;s--)typeof e[s]=="function"&&(e[s]=Ae(e[s],n+"_"+s));return e}function ft(e,n){let s=e.constructor.name;for(let r=0;r{let b=function(){return E.apply(this,He(arguments,s+"."+i))};return ae(b,E),b})(l)}}}function Je(e){return e?e.writable===!1?!1:!(typeof e.get=="function"&&typeof e.set>"u"):!0}var Ke=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,we=!("nw"in $)&&typeof $.process<"u"&&{}.toString.call($.process)==="[object process]",xe=!we&&!Ke&&!!(Pe&&Te.HTMLElement),Qe=typeof $.process<"u"&&{}.toString.call($.process)==="[object process]"&&!Ke&&!!(Pe&&Te.HTMLElement),be={},Xe=function(e){if(e=e||$.event,!e)return;let n=be[e.type];n||(n=be[e.type]=x("ON_PROPERTY"+e.type));let s=this||e.target||$,r=s[n],i;if(xe&&s===Te&&e.type==="error"){let l=e;i=r&&r.call(this,l.message,l.filename,l.lineno,l.colno,l.error),i===!0&&e.preventDefault()}else i=r&&r.apply(this,arguments),i!=null&&!i&&e.preventDefault();return i};function ze(e,n,s){let r=me(e,n);if(!r&&s&&me(s,n)&&(r={enumerable:!0,configurable:!0}),!r||!r.configurable)return;let i=x("on"+n+"patched");if(e.hasOwnProperty(i)&&e[i])return;delete r.writable,delete r.value;let l=r.get,m=r.set,E=n.slice(2),b=be[E];b||(b=be[E]=x("ON_PROPERTY"+E)),r.set=function(v){let p=this;if(!p&&e===$&&(p=$),!p)return;typeof p[b]=="function"&&p.removeEventListener(E,Xe),m&&m.call(p,null),p[b]=v,typeof v=="function"&&p.addEventListener(E,Xe,!1)},r.get=function(){let v=this;if(!v&&e===$&&(v=$),!v)return null;let p=v[b];if(p)return p;if(l){let M=l.call(this);if(M)return r.set.call(this,M),typeof v[ut]=="function"&&v.removeAttribute(n),M}return null},Ne(e,n,r),e[i]=!0}function et(e,n,s){if(n)for(let r=0;rfunction(m,E){let b=s(m,E);return b.cbIdx>=0&&typeof E[b.cbIdx]=="function"?je(b.name,E[b.cbIdx],b,i):l.apply(m,E)})}function ae(e,n){e[x("OriginalDelegate")]=n}var Ye=!1,Ze=!1;function dt(){try{let e=Te.navigator.userAgent;if(e.indexOf("MSIE ")!==-1||e.indexOf("Trident/")!==-1)return!0}catch{}return!1}function _t(){if(Ye)return Ze;Ye=!0;try{let e=Te.navigator.userAgent;(e.indexOf("MSIE ")!==-1||e.indexOf("Trident/")!==-1||e.indexOf("Edge/")!==-1)&&(Ze=!0)}catch{}return Ze}Zone.__load_patch("ZoneAwarePromise",(e,n,s)=>{let r=Object.getOwnPropertyDescriptor,i=Object.defineProperty;function l(a){if(a&&a.toString===Object.prototype.toString){let u=a.constructor&&a.constructor.name;return(u||"")+": "+JSON.stringify(a)}return a?a.toString():Object.prototype.toString.call(a)}let m=s.symbol,E=[],b=e[m("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")]===!0,v=m("Promise"),p=m("then"),M="__creationTrace__";s.onUnhandledError=a=>{if(s.showUncaughtError()){let u=a&&a.rejection;u?console.error("Unhandled Promise rejection:",u instanceof Error?u.message:u,"; Zone:",a.zone.name,"; Task:",a.task&&a.task.source,"; Value:",u,u instanceof Error?u.stack:void 0):console.error(a)}},s.microtaskDrainDone=()=>{for(;E.length;){let a=E.shift();try{a.zone.runGuarded(()=>{throw a.throwOriginal?a.rejection:a})}catch(u){N(u)}}};let O=m("unhandledPromiseRejectionHandler");function N(a){s.onUnhandledError(a);try{let u=n[O];typeof u=="function"&&u.call(this,a)}catch{}}function U(a){return a&&a.then}function H(a){return a}function K(a){return c.reject(a)}let X=m("state"),C=m("value"),T=m("finally"),J=m("parentPromiseValue"),G=m("parentPromiseState"),z="Promise.then",j=null,y=!0,V=!1,d=0;function I(a,u){return o=>{try{Y(a,u,o)}catch(f){Y(a,!1,f)}}}let P=function(){let a=!1;return function(o){return function(){a||(a=!0,o.apply(null,arguments))}}},Q="Promise resolved with itself",oe=m("currentTaskTrace");function Y(a,u,o){let f=P();if(a===o)throw new TypeError(Q);if(a[X]===j){let k=null;try{(typeof o=="object"||typeof o=="function")&&(k=o&&o.then)}catch(R){return f(()=>{Y(a,!1,R)})(),a}if(u!==V&&o instanceof c&&o.hasOwnProperty(X)&&o.hasOwnProperty(C)&&o[X]!==j)ne(o),Y(a,o[X],o[C]);else if(u!==V&&typeof k=="function")try{k.call(o,f(I(a,u)),f(I(a,!1)))}catch(R){f(()=>{Y(a,!1,R)})()}else{a[X]=u;let R=a[C];if(a[C]=o,a[T]===T&&u===y&&(a[X]=a[G],a[C]=a[J]),u===V&&o instanceof Error){let g=n.currentTask&&n.currentTask.data&&n.currentTask.data[M];g&&i(o,oe,{configurable:!0,enumerable:!1,writable:!0,value:g})}for(let g=0;g{try{let S=a[C],D=!!o&&T===o[T];D&&(o[J]=S,o[G]=R);let Z=u.run(g,void 0,D&&g!==K&&g!==H?[]:[S]);Y(o,!0,Z)}catch(S){Y(o,!1,S)}},o)}let q="function ZoneAwarePromise() { [native code] }",A=function(){},h=e.AggregateError;class c{static toString(){return q}static resolve(u){return Y(new this(null),y,u)}static reject(u){return Y(new this(null),V,u)}static any(u){if(!u||typeof u[Symbol.iterator]!="function")return Promise.reject(new h([],"All promises were rejected"));let o=[],f=0;try{for(let g of u)f++,o.push(c.resolve(g))}catch{return Promise.reject(new h([],"All promises were rejected"))}if(f===0)return Promise.reject(new h([],"All promises were rejected"));let k=!1,R=[];return new c((g,S)=>{for(let D=0;D{k||(k=!0,g(Z))},Z=>{R.push(Z),f--,f===0&&(k=!0,S(new h(R,"All promises were rejected")))})})}static race(u){let o,f,k=new this((S,D)=>{o=S,f=D});function R(S){o(S)}function g(S){f(S)}for(let S of u)U(S)||(S=this.resolve(S)),S.then(R,g);return k}static all(u){return c.allWithCallback(u)}static allSettled(u){return(this&&this.prototype instanceof c?this:c).allWithCallback(u,{thenCallback:f=>({status:"fulfilled",value:f}),errorCallback:f=>({status:"rejected",reason:f})})}static allWithCallback(u,o){let f,k,R=new this((Z,F)=>{f=Z,k=F}),g=2,S=0,D=[];for(let Z of u){U(Z)||(Z=this.resolve(Z));let F=S;try{Z.then(B=>{D[F]=o?o.thenCallback(B):B,g--,g===0&&f(D)},B=>{o?(D[F]=o.errorCallback(B),g--,g===0&&f(D)):k(B)})}catch(B){k(B)}g++,S++}return g-=2,g===0&&f(D),R}constructor(u){let o=this;if(!(o instanceof c))throw new Error("Must be an instanceof Promise.");o[X]=j,o[C]=[];try{let f=P();u&&u(f(I(o,y)),f(I(o,V)))}catch(f){Y(o,!1,f)}}get[Symbol.toStringTag](){return"Promise"}get[Symbol.species](){return c}then(u,o){let f=this.constructor?.[Symbol.species];(!f||typeof f!="function")&&(f=this.constructor||c);let k=new f(A),R=n.current;return this[X]==j?this[C].push(R,k,u,o):ee(this,R,k,u,o),k}catch(u){return this.then(null,u)}finally(u){let o=this.constructor?.[Symbol.species];(!o||typeof o!="function")&&(o=c);let f=new o(A);f[T]=T;let k=n.current;return this[X]==j?this[C].push(k,f,u,u):ee(this,k,f,u,u),f}}c.resolve=c.resolve,c.reject=c.reject,c.race=c.race,c.all=c.all;let t=e[v]=e.Promise;e.Promise=c;let _=m("thenPatched");function w(a){let u=a.prototype,o=r(u,"then");if(o&&(o.writable===!1||!o.configurable))return;let f=u.then;u[p]=f,a.prototype.then=function(k,R){return new c((S,D)=>{f.call(this,S,D)}).then(k,R)},a[_]=!0}s.patchThen=w;function L(a){return function(u,o){let f=a.apply(u,o);if(f instanceof c)return f;let k=f.constructor;return k[_]||w(k),f}}return t&&(w(t),le(e,"fetch",a=>L(a))),Promise[n.__symbol__("uncaughtPromiseErrors")]=E,c});Zone.__load_patch("toString",e=>{let n=Function.prototype.toString,s=x("OriginalDelegate"),r=x("Promise"),i=x("Error"),l=function(){if(typeof this=="function"){let v=this[s];if(v)return typeof v=="function"?n.call(v):Object.prototype.toString.call(v);if(this===Promise){let p=e[r];if(p)return n.call(p)}if(this===Error){let p=e[i];if(p)return n.call(p)}}return n.call(this)};l[s]=n,Function.prototype.toString=l;let m=Object.prototype.toString,E="[object Promise]";Object.prototype.toString=function(){return typeof Promise=="function"&&this instanceof Promise?E:m.call(this)}});var _e=!1;if(typeof window<"u")try{let e=Object.defineProperty({},"passive",{get:function(){_e=!0}});window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch{_e=!1}var Et={useG:!0},te={},tt={},nt=new RegExp("^"+pe+"(\\w+)(true|false)$"),rt=x("propagationStopped");function ot(e,n){let s=(n?n(e):e)+ce,r=(n?n(e):e)+ie,i=pe+s,l=pe+r;te[e]={},te[e][ce]=i,te[e][ie]=l}function Tt(e,n,s,r){let i=r&&r.add||Me,l=r&&r.rm||Le,m=r&&r.listeners||"eventListeners",E=r&&r.rmAll||"removeAllListeners",b=x(i),v="."+i+":",p="prependListener",M="."+p+":",O=function(C,T,J){if(C.isRemoved)return;let G=C.callback;typeof G=="object"&&G.handleEvent&&(C.callback=y=>G.handleEvent(y),C.originalDelegate=G);let z;try{C.invoke(C,T,[J])}catch(y){z=y}let j=C.options;if(j&&typeof j=="object"&&j.once){let y=C.originalDelegate?C.originalDelegate:C.callback;T[l].call(T,J.type,y,j)}return z};function N(C,T,J){if(T=T||e.event,!T)return;let G=C||T.target||e,z=G[te[T.type][J?ie:ce]];if(z){let j=[];if(z.length===1){let y=O(z[0],G,T);y&&j.push(y)}else{let y=z.slice();for(let V=0;V{throw V})}}}let U=function(C){return N(this,C,!1)},H=function(C){return N(this,C,!0)};function K(C,T){if(!C)return!1;let J=!0;T&&T.useG!==void 0&&(J=T.useG);let G=T&&T.vh,z=!0;T&&T.chkDup!==void 0&&(z=T.chkDup);let j=!1;T&&T.rt!==void 0&&(j=T.rt);let y=C;for(;y&&!y.hasOwnProperty(i);)y=Ie(y);if(!y&&C[i]&&(y=C),!y||y[b])return!1;let V=T&&T.eventNameToString,d={},I=y[b]=y[i],P=y[x(l)]=y[l],Q=y[x(m)]=y[m],oe=y[x(E)]=y[E],Y;T&&T.prepend&&(Y=y[x(T.prepend)]=y[T.prepend]);function W(o,f){return!_e&&typeof o=="object"&&o?!!o.capture:!_e||!f?o:typeof o=="boolean"?{capture:o,passive:!0}:o?typeof o=="object"&&o.passive!==!1?qe(We({},o),{passive:!0}):o:{passive:!0}}let ne=function(o){if(!d.isExisting)return I.call(d.target,d.eventName,d.capture?H:U,d.options)},ee=function(o){if(!o.isRemoved){let f=te[o.eventName],k;f&&(k=f[o.capture?ie:ce]);let R=k&&o.target[k];if(R){for(let g=0;gfunction(i,l){i[rt]=!0,r&&r.apply(i,l)})}function mt(e,n,s,r,i){let l=Zone.__symbol__(r);if(n[l])return;let m=n[l]=n[r];n[r]=function(E,b,v){return b&&b.prototype&&i.forEach(function(p){let M=`${s}.${r}::`+p,O=b.prototype;try{if(O.hasOwnProperty(p)){let N=e.ObjectGetOwnPropertyDescriptor(O,p);N&&N.value?(N.value=e.wrapWithCurrentZone(N.value,M),e._redefineProperty(b.prototype,p,N)):O[p]&&(O[p]=e.wrapWithCurrentZone(O[p],M))}else O[p]&&(O[p]=e.wrapWithCurrentZone(O[p],M))}catch{}}),m.call(n,E,b,v)},e.attachOriginToPatched(n[r],m)}function it(e,n,s){if(!s||s.length===0)return n;let r=s.filter(l=>l.target===e);if(!r||r.length===0)return n;let i=r[0].ignoreProperties;return n.filter(l=>i.indexOf(l)===-1)}function $e(e,n,s,r){if(!e)return;let i=it(e,n,s);et(e,i,r)}function Oe(e){return Object.getOwnPropertyNames(e).filter(n=>n.startsWith("on")&&n.length>2).map(n=>n.substring(2))}function pt(e,n){if(we&&!Qe||Zone[e.symbol("patchEvents")])return;let s=n.__Zone_ignore_on_properties,r=[];if(xe){let i=window;r=r.concat(["Document","SVGElement","Element","HTMLElement","HTMLBodyElement","HTMLMediaElement","HTMLFrameSetElement","HTMLFrameElement","HTMLIFrameElement","HTMLMarqueeElement","Worker"]);let l=dt()?[{target:i,ignoreProperties:["error"]}]:[];$e(i,Oe(i),s&&s.concat(l),Ie(i))}r=r.concat(["XMLHttpRequest","XMLHttpRequestEventTarget","IDBIndex","IDBRequest","IDBOpenDBRequest","IDBDatabase","IDBTransaction","IDBCursor","WebSocket"]);for(let i=0;i{let r=Oe(e);s.patchOnProperties=et,s.patchMethod=le,s.bindArguments=He,s.patchMacroTask=ht;let i=n.__symbol__("BLACK_LISTED_EVENTS"),l=n.__symbol__("UNPATCHED_EVENTS");e[l]&&(e[i]=e[l]),e[i]&&(n[i]=n[l]=e[i]),s.patchEventPrototype=yt,s.patchEventTarget=Tt,s.isIEOrEdge=_t,s.ObjectDefineProperty=Ne,s.ObjectGetOwnPropertyDescriptor=me,s.ObjectCreate=at,s.ArraySlice=lt,s.patchClass=ge,s.wrapWithCurrentZone=Ae,s.filterProperties=it,s.attachOriginToPatched=ae,s._redefineProperty=Object.defineProperty,s.patchCallbacks=mt,s.getGlobalObjects=()=>({globalSources:tt,zoneSymbolEventNames:te,eventNames:r,isBrowser:xe,isMix:Qe,isNode:we,TRUE_STR:ie,FALSE_STR:ce,ZONE_SYMBOL_PREFIX:pe,ADD_EVENT_LISTENER_STR:Me,REMOVE_EVENT_LISTENER_STR:Le})});function gt(e,n){n.patchMethod(e,"queueMicrotask",s=>function(r,i){Zone.current.scheduleMicroTask("queueMicrotask",i[0])})}var ve=x("zoneTask");function Ee(e,n,s,r){let i=null,l=null;n+=r,s+=r;let m={};function E(v){let p=v.data;return p.args[0]=function(){return v.invoke.apply(this,arguments)},p.handleId=i.apply(e,p.args),v}function b(v){return l.call(e,v.data.handleId)}i=le(e,n,v=>function(p,M){if(typeof M[0]=="function"){let O={isPeriodic:r==="Interval",delay:r==="Timeout"||r==="Interval"?M[1]||0:void 0,args:M},N=M[0];M[0]=function(){try{return N.apply(this,arguments)}finally{O.isPeriodic||(typeof O.handleId=="number"?delete m[O.handleId]:O.handleId&&(O.handleId[ve]=null))}};let U=je(n,M[0],O,E,b);if(!U)return U;let H=U.data.handleId;return typeof H=="number"?m[H]=U:H&&(H[ve]=U),H&&H.ref&&H.unref&&typeof H.ref=="function"&&typeof H.unref=="function"&&(U.ref=H.ref.bind(H),U.unref=H.unref.bind(H)),typeof H=="number"||H?H:U}else return v.apply(e,M)}),l=le(e,s,v=>function(p,M){let O=M[0],N;typeof O=="number"?N=m[O]:(N=O&&O[ve],N||(N=O)),N&&typeof N.type=="string"?N.state!=="notScheduled"&&(N.cancelFn&&N.data.isPeriodic||N.runCount===0)&&(typeof O=="number"?delete m[O]:O&&(O[ve]=null),N.zone.cancelTask(N)):v.apply(e,M)})}function kt(e,n){let{isBrowser:s,isMix:r}=n.getGlobalObjects();if(!s&&!r||!e.customElements||!("customElements"in e))return;let i=["connectedCallback","disconnectedCallback","adoptedCallback","attributeChangedCallback"];n.patchCallbacks(n,e.customElements,"customElements","define",i)}function vt(e,n){if(Zone[n.symbol("patchEventTarget")])return;let{eventNames:s,zoneSymbolEventNames:r,TRUE_STR:i,FALSE_STR:l,ZONE_SYMBOL_PREFIX:m}=n.getGlobalObjects();for(let b=0;b{let n=e[Zone.__symbol__("legacyPatch")];n&&n()});Zone.__load_patch("timers",e=>{let n="set",s="clear";Ee(e,n,s,"Timeout"),Ee(e,n,s,"Interval"),Ee(e,n,s,"Immediate")});Zone.__load_patch("requestAnimationFrame",e=>{Ee(e,"request","cancel","AnimationFrame"),Ee(e,"mozRequest","mozCancel","AnimationFrame"),Ee(e,"webkitRequest","webkitCancel","AnimationFrame")});Zone.__load_patch("blocking",(e,n)=>{let s=["alert","prompt","confirm"];for(let r=0;rfunction(b,v){return n.current.run(l,e,v,E)})}});Zone.__load_patch("EventTarget",(e,n,s)=>{bt(e,s),vt(e,s);let r=e.XMLHttpRequestEventTarget;r&&r.prototype&&s.patchEventTarget(e,s,[r.prototype])});Zone.__load_patch("MutationObserver",(e,n,s)=>{ge("MutationObserver"),ge("WebKitMutationObserver")});Zone.__load_patch("IntersectionObserver",(e,n,s)=>{ge("IntersectionObserver")});Zone.__load_patch("FileReader",(e,n,s)=>{ge("FileReader")});Zone.__load_patch("on_property",(e,n,s)=>{pt(s,e)});Zone.__load_patch("customElements",(e,n,s)=>{kt(e,s)});Zone.__load_patch("XHR",(e,n)=>{b(e);let s=x("xhrTask"),r=x("xhrSync"),i=x("xhrListener"),l=x("xhrScheduled"),m=x("xhrURL"),E=x("xhrErrorBeforeScheduled");function b(v){let p=v.XMLHttpRequest;if(!p)return;let M=p.prototype;function O(d){return d[s]}let N=M[Se],U=M[De];if(!N){let d=v.XMLHttpRequestEventTarget;if(d){let I=d.prototype;N=I[Se],U=I[De]}}let H="readystatechange",K="scheduled";function X(d){let I=d.data,P=I.target;P[l]=!1,P[E]=!1;let Q=P[i];N||(N=P[Se],U=P[De]),Q&&U.call(P,H,Q);let oe=P[i]=()=>{if(P.readyState===P.DONE)if(!I.aborted&&P[l]&&d.state===K){let W=P[n.__symbol__("loadfalse")];if(P.status!==0&&W&&W.length>0){let ne=d.invoke;d.invoke=function(){let ee=P[n.__symbol__("loadfalse")];for(let q=0;qfunction(d,I){return d[r]=I[2]==!1,d[m]=I[1],J.apply(d,I)}),G="XMLHttpRequest.send",z=x("fetchTaskAborting"),j=x("fetchTaskScheduling"),y=le(M,"send",()=>function(d,I){if(n.current[j]===!0||d[r])return y.apply(d,I);{let P={target:d,url:d[m],isPeriodic:!1,args:I,aborted:!1},Q=je(G,C,P,X,T);d&&d[E]===!0&&!P.aborted&&Q.state===K&&Q.invoke()}}),V=le(M,"abort",()=>function(d,I){let P=O(d);if(P&&typeof P.type=="string"){if(P.cancelFn==null||P.data&&P.data.aborted)return;P.zone.cancelTask(P)}else if(n.current[z]===!0)return V.apply(d,I)})}});Zone.__load_patch("geolocation",e=>{e.navigator&&e.navigator.geolocation&&ft(e.navigator.geolocation,["getCurrentPosition","watchPosition"])});Zone.__load_patch("PromiseRejectionEvent",(e,n)=>{function s(r){return function(i){st(e,r).forEach(m=>{let E=e.PromiseRejectionEvent;if(E){let b=new E(r,{promise:i.promise,reason:i.rejection});m.invoke(b)}})}}e.PromiseRejectionEvent&&(n[x("unhandledPromiseRejectionHandler")]=s("unhandledrejection"),n[x("rejectionHandledHandler")]=s("rejectionhandled"))});Zone.__load_patch("queueMicrotask",(e,n,s)=>{gt(e,s)}); 3 | -------------------------------------------------------------------------------- /docs/styles.K5YR3RCG.css: -------------------------------------------------------------------------------- 1 | body{background-color:#333;color:#efefef} 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export { CelSpec } from './src/CelSpec'; 2 | export { TextFormatter} from './src/formatters/TextFormatter'; 3 | export { NULL_VALUE } from './src/index'; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | transform: { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | moduleFileExtensions: [ 7 | "ts", 8 | "tsx", 9 | "js", 10 | "jsx", 11 | "json", 12 | "node", 13 | ], 14 | testRegex: '(/tests/.*|(\\.|/)(test|spec))\\.(ts|js)x?$', 15 | coverageDirectory: 'coverage', 16 | collectCoverageFrom: [ 17 | 'src/**/*.{ts,tsx,js,jsx}', 18 | '!src/**/*.d.ts', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cel-spec-js", 3 | "version": "1.0.0", 4 | "description": "CEL Spec for Node and Browser", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "dist.browser" 10 | ], 11 | "engines": { 12 | "node": ">= 10.13" 13 | }, 14 | "author": "", 15 | "license": "", 16 | "homepage": "", 17 | "repository": { 18 | "type": "git", 19 | "url": "" 20 | }, 21 | "bugs": { 22 | "url": "" 23 | }, 24 | "scripts": { 25 | "clean": "rimraf coverage dist tmp", 26 | "build": "tsc -p tsconfig.json", 27 | "build:browser": "browserify index.ts -p [tsify --noImplicityAny ] -s speechmarkdown -o ./dist.browser/speechmarkdown.js", 28 | "build:minify": "browserify index.ts -p [tsify --noImplicityAny ] -s speechmarkdown | uglifyjs -cm -o ./dist.browser/speechmarkdown.min.js", 29 | "build:watch": "tsc -w -p tsconfig.json", 30 | "lint": "tslint -t stylish --project \"tsconfig.json\"", 31 | "test": "jest --coverage", 32 | "test:single": "jest -t $1", 33 | "test:watch": "jest --watch" 34 | }, 35 | "devDependencies": { 36 | "@types/jest": "^24.0.18", 37 | "@types/node": "^12.7.11", 38 | "browserify": "^16.5.0", 39 | "clean-css": ">=4.1.11", 40 | "jest": "^24.9.0", 41 | "lodash": ">=4.17.13", 42 | "mixin-deep": ">=1.3.2", 43 | "prettier": "^1.18.2", 44 | "rimraf": "^3.0.0", 45 | "set-value": ">=2.0.1", 46 | "ts-jest": "^24.1.0", 47 | "tsify": "^4.0.1", 48 | "tslint": "^5.20.0", 49 | "tslint-config-prettier": "1.18.0", 50 | "tslint-microsoft-contrib": "^6.2.0", 51 | "tsutils": "^3.17.1", 52 | "typescript": "^3.6.3", 53 | "uglify": "^0.1.5", 54 | "uglify-js": "^3.6.0", 55 | "underscore.string": ">=3.3.5" 56 | }, 57 | "dependencies": { 58 | "myna-parser": "^2.5.1", 59 | "tslib": "^1.10.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/CelSpec.ts: -------------------------------------------------------------------------------- 1 | import { Parser } from './Interfaces'; 2 | import { CelSpecParser } from "./CelSpecParser"; 3 | import { CelSpecOptions } from "./CelSpecOptions"; 4 | 5 | export class CelSpec { 6 | 7 | private parser: Parser; 8 | 9 | private readonly defaults: CelSpecOptions = {}; 10 | 11 | constructor(private options?: CelSpecOptions) { 12 | this.options = { 13 | ... this.defaults, 14 | ... options 15 | }; 16 | } 17 | 18 | get Parser(): Parser { 19 | if (!this.parser) { 20 | this.parser = new CelSpecParser(); 21 | } 22 | 23 | return this.parser; 24 | } 25 | 26 | public toText(speechmarkdown: string, options?: CelSpecOptions): string { 27 | const methodOptions = { 28 | ... this.options, 29 | ... options 30 | }; 31 | 32 | const ast = this.Parser.parse(speechmarkdown); 33 | // const formatter = factory.createTextFormatter(methodOptions); 34 | 35 | return ast 36 | } 37 | 38 | public toSSML(speechmarkdown: string, options?: CelSpecOptions): string { 39 | const methodOptions = { 40 | ... this.options, 41 | ... options 42 | }; 43 | 44 | const ast = this.Parser.parse(speechmarkdown); 45 | // console.log(`AST: ${ast}`); 46 | // const formatter = factory.createFormatter(methodOptions); 47 | 48 | return ast 49 | } 50 | 51 | public toAST(speechmarkdown: string, options?: CelSpecOptions): any { 52 | return this.Parser.parse(speechmarkdown); 53 | } 54 | 55 | public toASTString(speechmarkdown: string, options?: CelSpecOptions): string { 56 | const ast = this.Parser.parse(speechmarkdown); 57 | return ast.toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/CelSpecGrammar.ts: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | // tslint:disable-next-line: max-func-body-length 5 | export function celSpecGrammar(myna: any): any { 6 | const m = myna; 7 | // Override parenthesis function to not use `.guardedSeq` 8 | // This sequence is too assertive, and may cause exceptions rather than just returning null 9 | m.parenthesized = (rule: any) => { 10 | return m.seq("(", m.ws, rule, m.ws, ")").setType("parenthesized"); 11 | } 12 | 13 | // tslint:disable-next-line: typedef 14 | // tslint:disable-next-line: max-func-body-length 15 | const grammar = new function () { 16 | // https://github.com/google/cel-spec/blob/master/tests/simple/testdata/basic.textproto 17 | // .self_eval_zeroish 18 | this.int64 = m.seq( 19 | m.opt('-'), 20 | m.digits, 21 | m.notAtChar('eu.'), // Does not advance parser 22 | ).ast 23 | // .self_eval_uint_zero 24 | this.uint64 = m.seq(m.digits, 'u').ast 25 | this.double = m.choice( 26 | // -2.3e+1 27 | // -2e-3 28 | m.seq(m.opt('-'), m.digits.oneOrMore, m.opt(m.seq(m.char('.'), m.digits.oneOrMore)), m.char('e'), m.char('+-'), m.digits.oneOrMore), 29 | // -2.3 30 | // 2.0 31 | // .99 32 | m.seq(m.opt('-'), m.digits.zeroOrMore, m.char('.'), m.digits.oneOrMore), 33 | ).ast 34 | 35 | const character = m.choice( 36 | m.letters, 37 | m.digits, 38 | m.space, 39 | m.char('+-!\\[]().'), 40 | m.char('áàéίασςιδοτÿ'), 41 | m.char('\x00\x01'), 42 | ) 43 | 44 | this.string = m.choice( 45 | m.seq( 46 | `'`, 47 | m.choice(character, '"').zeroOrMore, 48 | `'` 49 | ), 50 | m.seq( 51 | `"`, 52 | m.choice(character, "'").zeroOrMore, 53 | `"` 54 | ) 55 | ).ast 56 | this.rawString = m.choice( 57 | m.seq(`r'`, character.zeroOrMore, m.seq(`'`)), 58 | m.seq(`r"`, character.zeroOrMore, m.seq(`"`)) 59 | ).ast 60 | this.byteString = m.choice( 61 | m.seq(`b'`, character.zeroOrMore, m.seq(`'`)), 62 | m.seq(`b"`, character.zeroOrMore, m.seq(`"`)) 63 | ).ast 64 | this.boolean = m.choice( 65 | 'false', 'true' 66 | ).ast 67 | 68 | this.null = m.choice('null', 'NULL').ast 69 | 70 | this.variable = m.seq( 71 | m.letterLower.oneOrMore, 72 | m.seq( 73 | m.letterUpper, 74 | m.letterLower.zeroOrMore, 75 | ).zeroOrMore, 76 | m.digits.zeroOrMore 77 | ).ast 78 | 79 | const primitive = m.choice( 80 | this.int64, 81 | this.string, 82 | this.uint64, 83 | this.double, 84 | this.boolean, 85 | ) 86 | const primitiveArray = m.seq( 87 | primitive, m.opt(','), m.opt(m.space) 88 | ) 89 | this.list = m.seq( 90 | '[', 91 | primitiveArray.zeroOrMore, 92 | ']' 93 | ).ast 94 | 95 | const entry = m.seq( 96 | primitive, 97 | ':', 98 | m.opt(m.space), 99 | primitive, 100 | m.opt(','), 101 | m.opt(m.space) 102 | ) 103 | this.map = m.seq( 104 | '{', 105 | entry.zeroOrMore, 106 | '}' 107 | ).ast 108 | 109 | this.comparable = m.choice( 110 | '==', 111 | '!=', 112 | '>=', 113 | '<=', 114 | '>', 115 | '<', 116 | ).ast 117 | 118 | this.sizeOfObj = m.seq( 119 | 'size(', 120 | m.choice(this.list, this.map, this.variable), 121 | ')' 122 | ).ast 123 | 124 | this.comparisonInt64 = m.choice( 125 | // x < 123 126 | m.seq( 127 | m.choice(this.int64, this.sizeOfObj, this.variable), 128 | m.opt(m.space), 129 | this.comparable, 130 | m.opt(m.space), 131 | m.choice(this.int64, this.sizeOfObj) 132 | ), 133 | // 123 > x 134 | m.seq( 135 | m.choice(this.int64, this.sizeOfObj), 136 | m.opt(m.space), 137 | this.comparable, 138 | m.opt(m.space), 139 | m.choice(this.int64, this.variable) 140 | ) 141 | ).ast 142 | 143 | this.comparisonUint64 = m.choice( 144 | m.seq( 145 | m.choice(this.uint64, this.variable), 146 | m.opt(m.space), 147 | this.comparable, 148 | m.opt(m.space), 149 | this.uint64 150 | ), 151 | m.seq( 152 | this.uint64, 153 | m.opt(m.space), 154 | this.comparable, 155 | m.opt(m.space), 156 | m.choice(this.uint64, this.variable) 157 | ) 158 | ).ast 159 | 160 | this.comparisonDouble = m.choice( 161 | m.seq( 162 | m.choice(this.double, this.variable), 163 | m.opt(m.space), 164 | this.comparable, 165 | m.opt(m.space), 166 | this.double 167 | ), 168 | m.seq( 169 | this.double, 170 | m.opt(m.space), 171 | this.comparable, 172 | m.opt(m.space), 173 | m.choice(this.double, this.variable) 174 | ) 175 | ).ast 176 | 177 | this.comparisonString = m.choice( 178 | m.seq( 179 | m.choice(this.string, this.rawString, this.variable), 180 | m.opt(m.space), 181 | this.comparable, 182 | m.opt(m.space), 183 | m.choice(this.string, this.rawString) 184 | ), 185 | m.seq( 186 | m.choice(this.string, this.rawString), 187 | m.opt(m.space), 188 | this.comparable, 189 | m.opt(m.space), 190 | m.choice(this.string, this.rawString, this.variable) 191 | ) 192 | ).ast 193 | 194 | this.comparisonByteString = m.choice( 195 | // x < b'\x00' 196 | m.seq( 197 | m.choice(this.byteString, this.variable), 198 | m.opt(m.space), 199 | this.comparable, 200 | m.opt(m.space), 201 | this.byteString 202 | ), 203 | // b'\x00' > x 204 | m.seq( 205 | this.byteString, 206 | m.opt(m.space), 207 | this.comparable, 208 | m.opt(m.space), 209 | m.choice(this.byteString, this.variable) 210 | ) 211 | ).ast 212 | 213 | this.comparisonBoolean = m.choice( 214 | m.seq( 215 | m.choice(this.boolean, this.variable), 216 | m.opt(m.space), 217 | this.comparable, 218 | m.opt(m.space), 219 | this.boolean 220 | ), 221 | m.seq( 222 | this.boolean, 223 | m.opt(m.space), 224 | this.comparable, 225 | m.opt(m.space), 226 | m.choice(this.boolean, this.variable) 227 | ) 228 | ).ast 229 | 230 | this.comparisonNull = m.choice( 231 | m.seq( 232 | m.choice(this.null, this.variable), 233 | m.opt(m.space), 234 | this.comparable, 235 | m.opt(m.space), 236 | this.null 237 | ), 238 | m.seq( 239 | this.null, 240 | m.opt(m.space), 241 | this.comparable, 242 | m.opt(m.space), 243 | m.choice(this.null, this.variable) 244 | ) 245 | ).ast 246 | 247 | const comparisons = m.choice( 248 | this.comparisonNull, 249 | this.comparisonString, 250 | this.comparisonInt64, 251 | ) 252 | 253 | // This will catch primitive comparisons not matched above 254 | this.comparisonTypeMismatch = m.seq( 255 | primitive, 256 | m.opt(m.space), 257 | this.comparable, 258 | m.opt(m.space), 259 | primitive 260 | ).ast 261 | 262 | this.elementInObj = m.seq( 263 | primitive, 264 | m.space, 265 | 'in', 266 | m.space, 267 | m.choice(this.list, this.map, this.variable) 268 | ).ast 269 | 270 | this.indexOfObj = m.seq( 271 | m.choice(this.list, this.variable), 272 | '[', 273 | this.int64, 274 | ']' 275 | ).ast 276 | 277 | this.concatObj = m.seq( 278 | this.list, 279 | m.opt(m.space), 280 | '+', 281 | m.opt(m.space), 282 | this.list 283 | ).ast 284 | 285 | this.ternary = m.seq( 286 | this.boolean, 287 | m.space, 288 | '?', 289 | m.space, 290 | primitive, 291 | m.space, 292 | ':', 293 | m.space, 294 | primitive 295 | ).ast 296 | 297 | this.ternaryTypeMismatch = m.seq( 298 | primitive, 299 | m.space, 300 | '?', 301 | m.space, 302 | primitive, 303 | m.space, 304 | ':', 305 | m.space, 306 | primitive 307 | ).ast 308 | 309 | this.deepCompareObj = m.choice( 310 | m.seq( 311 | m.choice(this.map, this.concatObj, this.list, this.variable), 312 | m.opt(m.space), 313 | this.comparable, 314 | m.opt(m.space), 315 | m.choice(this.map, this.concatObj, this.list), 316 | ), 317 | m.seq( 318 | m.choice(this.map, this.concatObj, this.list), 319 | m.opt(m.space), 320 | this.comparable, 321 | m.opt(m.space), 322 | m.choice(this.map, this.concatObj, this.list, this.variable), 323 | ) 324 | ).ast 325 | 326 | this.logicalAnd = m.seq( 327 | m.choice(primitive, comparisons, this.variable), 328 | m.opt(m.space), 329 | '&&', 330 | m.opt(m.space), 331 | m.choice(primitive, comparisons, this.variable), 332 | ).ast 333 | 334 | this.logicalOr = m.seq( 335 | m.choice(primitive, comparisons, this.variable), 336 | m.opt(m.space), 337 | '||', 338 | m.opt(m.space), 339 | m.choice(primitive, comparisons, this.variable), 340 | ).ast 341 | 342 | this.logicalNot = m.seq( 343 | '!', 344 | m.choice(primitive, comparisons, this.variable) 345 | ).ast 346 | 347 | this.expr = m.choice( 348 | // Logical groups 349 | this.logicalAnd, 350 | this.logicalOr, 351 | this.logicalNot, 352 | // Groups 353 | this.comparisonInt64, 354 | this.comparisonUint64, 355 | this.comparisonDouble, 356 | this.comparisonString, 357 | this.comparisonByteString, 358 | this.comparisonBoolean, 359 | this.comparisonNull, 360 | this.deepCompareObj, 361 | this.ternary, 362 | this.ternaryTypeMismatch, 363 | this.comparisonTypeMismatch, // Catch rest 364 | // Collections 365 | this.elementInObj, 366 | this.sizeOfObj, 367 | this.indexOfObj, 368 | this.concatObj, 369 | // Individuals 370 | this.int64, 371 | this.uint64, 372 | this.double, 373 | this.string, 374 | this.rawString, 375 | this.byteString, 376 | this.boolean, 377 | this.null, 378 | this.list, 379 | this.map, 380 | this.variable, 381 | this.comparable, 382 | ).oneOrMore 383 | } 384 | 385 | // Register the grammar, providing a name and the default parse rule 386 | return m.registerGrammar("cel-spec", grammar, grammar.expr); 387 | } 388 | -------------------------------------------------------------------------------- /src/CelSpecOptions.ts: -------------------------------------------------------------------------------- 1 | export interface CelSpecOptions {} 2 | -------------------------------------------------------------------------------- /src/CelSpecParser.ts: -------------------------------------------------------------------------------- 1 | import { Myna } from 'myna-parser'; 2 | import { Parser } from './Interfaces'; 3 | import { celSpecGrammar } from './CelSpecGrammar'; 4 | 5 | export class CelSpecParser implements Parser { 6 | private parser: any; 7 | private myna: any; 8 | 9 | constructor() { 10 | this.myna = Myna; 11 | celSpecGrammar(this.myna) 12 | this.parser = this.myna.parsers['cel-spec']; 13 | } 14 | 15 | public parse(speechmarkdown: string): any { 16 | // if (speechmarkdown.length ==÷) 17 | // tslint:disable-next-line: no-unnecessary-local-variable 18 | return this.parser(speechmarkdown); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Parser { 2 | parse(markdown:string): any; 3 | } 4 | 5 | export interface Formatter { 6 | format(syntaxTree:any): any[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/formatters/FormatterBase.ts: -------------------------------------------------------------------------------- 1 | import { Formatter } from '../Interfaces'; 2 | import { CelSpecOptions } from '../CelSpecOptions'; 3 | 4 | export abstract class FormatterBase implements Formatter { 5 | 6 | protected constructor(protected options: CelSpecOptions) { 7 | this.options = options; 8 | } 9 | 10 | public abstract format(ast: any): any[]; 11 | 12 | // Adds each element of the array as markdown 13 | protected addArray(ast: any, lines: string[]): string[] { 14 | for (const child of ast) { 15 | this.formatFromAst(child, lines); 16 | } 17 | return lines; 18 | } 19 | 20 | protected processAst(ast: any, lines: string[]): void { 21 | if (ast instanceof Array) { 22 | this.addArray(ast, lines); 23 | } else { 24 | this.formatFromAst(ast, lines); 25 | } 26 | } 27 | 28 | protected abstract formatFromAst(ast: any, lines: string[]): string[]; 29 | } 30 | -------------------------------------------------------------------------------- /src/formatters/TextFormatter.ts: -------------------------------------------------------------------------------- 1 | import { CelSpecOptions } from '../CelSpecOptions'; 2 | import { FormatterBase } from './FormatterBase'; 3 | import { NULL_VALUE } from '..'; 4 | 5 | type Compare = '==' | '!=' | '>' | '>=' | '<' | '<=' 6 | 7 | const evaluateComparison = (comp1: any, op: Compare, comp2: any) => { 8 | if (op === '==') return comp1 === comp2 9 | if (op === '!=') return comp1 !== comp2 10 | if (op === '>') return comp1 > comp2 11 | if (op === '>=') return comp1 >= comp2 12 | if (op === '<') return comp1 < comp2 13 | if (op === '<=') return comp1 <= comp2 14 | } 15 | 16 | const deepArrayEvaluateComparison = (comp1: any[], op: Compare, comp2: any[]) => { 17 | if (op === '<' || op === '<=' || op === '>' || op === '>=') { 18 | // Can't deep compare w/ ops 19 | throw new Error(`{ message: "no such overload" }`) 20 | } 21 | return evaluateComparison( 22 | JSON.stringify(comp1), 23 | op, 24 | JSON.stringify(comp2) 25 | ) 26 | } 27 | 28 | const deepObjEvaluateComparison = (comp1: any, op: Compare, comp2: any) => { 29 | if (op === '<' || op === '<=' || op === '>' || op === '>=') { 30 | // Can't deep compare w/ ops 31 | throw new Error(`{ message: "no such overload" }`) 32 | } 33 | // Convert our CEL object into a traditional map 34 | let map1 = {} 35 | if (Array.isArray(comp1.entries)) { 36 | comp1.entries.forEach(entry => { 37 | map1[entry.key.string_value] = entry.value.string_value 38 | }) 39 | } else if (comp1.entries) { 40 | map1[comp1.entries.key.string_value] = 41 | comp1.entries.value.string_value 42 | } 43 | 44 | let map2 = {} 45 | if (Array.isArray(comp2.entries)) { 46 | comp2.entries.forEach(entry => { 47 | map2[entry.key.string_value] = entry.value.string_value 48 | }) 49 | } else if (comp2.entries) { 50 | map2[comp2.entries.key.string_value] = 51 | comp2.entries.value.string_value 52 | } 53 | 54 | // Turn objects (and arrays) into Entry arrays 55 | // so that we can then sort the data 56 | // before stringifying them, to resolve order issues 57 | return evaluateComparison( 58 | JSON.stringify(Object.entries(map1).sort()), 59 | op, 60 | JSON.stringify(Object.entries(map2).sort()) 61 | ) 62 | 63 | } 64 | 65 | export class TextFormatter extends FormatterBase { 66 | bindings = {} 67 | 68 | constructor(protected options: CelSpecOptions, bindings: any) { 69 | super(options); 70 | this.bindings = bindings 71 | } 72 | 73 | public format(ast: any): any[] { 74 | const lines = this.formatFromAst(ast, []); 75 | 76 | if (lines.length === 0) { 77 | return undefined 78 | } 79 | if (lines.length === 1) { 80 | return lines[0] 81 | } 82 | return lines; 83 | } 84 | 85 | protected formatFromAst(ast: any, lines: any[] = []): any[] { 86 | switch (ast.name) { 87 | case 'document': { 88 | this.processAst(ast.children, lines); 89 | return lines; 90 | } 91 | case 'int64': { 92 | lines.push({ 93 | int64_value: parseInt(ast.allText) 94 | }) 95 | return lines 96 | } 97 | case 'uint64': { 98 | lines.push({ 99 | uint64_value: parseInt(ast.allText) 100 | }) 101 | return lines 102 | } 103 | case 'double': { 104 | lines.push({ 105 | double_value: parseFloat(ast.allText) 106 | }) 107 | return lines 108 | } 109 | case 'string': { 110 | // console.log('str', ast.input, ast.allText) 111 | lines.push({ 112 | string_value: new String(ast.allText.substring(1, ast.allText.length - 1)).valueOf() 113 | }) 114 | return lines 115 | } 116 | case 'rawString': { 117 | lines.push({ 118 | string_value: ast.allText.substring(2, ast.allText.length - 1) 119 | }) 120 | return lines 121 | } 122 | case 'byteString': { 123 | const byteStr = ast.allText.substring(2, ast.allText.length - 1) as string 124 | // If the values are already in octal, why convert? 125 | if (byteStr.charAt(0) === '\\') { 126 | const octsArr = byteStr.split('\\') 127 | const bytesArr = [] 128 | for (let i = 1; i < octsArr.length; i++) { 129 | const oct = octsArr[i] 130 | if (oct.charAt(0) === 'x') { 131 | // Hex > Octal 132 | bytesArr.push('\\' + parseInt(`0${oct}`, 16).toString(8)) 133 | } else { 134 | bytesArr.push('\\' + oct) 135 | } 136 | } 137 | lines.push({ 138 | bytes_value: bytesArr.join('') 139 | }) 140 | return lines 141 | } 142 | // Turn each character into an octal 143 | const bytesArr = [] 144 | for (let i = 0; i < byteStr.length; i++) { 145 | bytesArr.push('\\' + byteStr.charCodeAt(i).toString(8)) 146 | } 147 | lines.push({ 148 | bytes_value: bytesArr.join('') 149 | }) 150 | return lines 151 | } 152 | case 'boolean': { 153 | lines.push({ 154 | bool_value: ast.allText === 'true' 155 | }) 156 | return lines 157 | } 158 | case 'null': { 159 | lines.push({ 160 | null_value: NULL_VALUE 161 | }) 162 | return lines 163 | } 164 | case 'list': { 165 | const sublines = [] 166 | this.processAst(ast.children, sublines); 167 | if (sublines.length >= 1) { 168 | lines.push({ 169 | list_value: { 170 | values: [...sublines] 171 | } 172 | }) 173 | return lines 174 | } 175 | lines.push({ 176 | list_value: {} 177 | }) 178 | return lines 179 | } 180 | case 'map': { 181 | const sublines = [] 182 | this.processAst(ast.children, sublines); 183 | if (sublines.length >= 2) { 184 | const entries = [] 185 | // Iterate through processed elements in pairs 186 | for (let i = 0; i < sublines.length; i += 2) { 187 | entries.push({ 188 | key: sublines[i], 189 | value: sublines[i + 1] 190 | }) 191 | } 192 | 193 | lines.push({ 194 | map_value: { entries } 195 | }) 196 | return lines 197 | } 198 | 199 | lines.push({ 200 | map_value: {} 201 | }) 202 | return lines 203 | } 204 | case 'variable': { 205 | // Perform variable lookup from our mapping 206 | if (!this.bindings[ast.allText]) { 207 | throw new Error(`{ message: "undeclared reference to '${ast.allText}' (in container '')" }`) 208 | } 209 | lines.push(this.bindings[ast.allText]) 210 | return lines 211 | } 212 | case 'comparisonInt64': 213 | case 'comparisonUint64': { 214 | // This should include three children 215 | // [int64, comparable, int64] 216 | const sublines = [] 217 | // console.log(ast.children) 218 | this.processAst(ast.children, sublines) 219 | // console.log(sublines) 220 | const int1 = (() => { 221 | if ('int64_value' in sublines[0]) { 222 | return sublines[0].int64_value 223 | } 224 | if ('uint64_value' in sublines[0]) { 225 | return sublines[0].uint64_value 226 | } 227 | return undefined 228 | })() 229 | const int2 = (() => { 230 | if ('int64_value' in sublines[1]) { 231 | return sublines[1].int64_value 232 | } 233 | if ('uint64_value' in sublines[1]) { 234 | return sublines[1].uint64_value 235 | } 236 | return undefined 237 | })() 238 | lines.push({ 239 | bool_value: evaluateComparison( 240 | int1, 241 | ast.children[1].allText, 242 | int2 243 | ) 244 | }) 245 | return lines 246 | } 247 | case 'comparisonDouble': { 248 | const sublines = [] 249 | this.processAst(ast.children, sublines) 250 | lines.push({ 251 | bool_value: evaluateComparison( 252 | sublines[0].double_value, 253 | ast.children[1].allText, 254 | sublines[1].double_value 255 | ) 256 | }) 257 | return lines 258 | } 259 | case 'comparisonString': { 260 | const sublines = [] 261 | this.processAst(ast.children, sublines) 262 | lines.push({ 263 | bool_value: evaluateComparison( 264 | sublines[0].string_value, 265 | ast.children[1].allText, 266 | sublines[1].string_value 267 | ) 268 | }) 269 | return lines 270 | } 271 | case 'comparisonByteString': { 272 | const sublines = [] 273 | this.processAst(ast.children, sublines) 274 | lines.push({ 275 | bool_value: evaluateComparison( 276 | sublines[0].bytes_value, 277 | ast.children[1].allText, 278 | sublines[1].bytes_value 279 | ) 280 | }) 281 | return lines 282 | } 283 | case 'comparisonBoolean': { 284 | const sublines = [] 285 | this.processAst(ast.children, sublines) 286 | lines.push({ 287 | bool_value: evaluateComparison( 288 | sublines[0].bool_value, 289 | ast.children[1].allText, 290 | sublines[1].bool_value 291 | ) 292 | }) 293 | return lines 294 | } 295 | case 'comparisonNull': { 296 | if (ast.children[1].allText !== '==' && 297 | ast.children[1].allText !== '!=') { 298 | throw new Error(`{ message: "no such overload" }`) 299 | } 300 | const sublines = [] 301 | this.processAst(ast.children, sublines) 302 | lines.push({ 303 | bool_value: evaluateComparison( 304 | sublines[0].null_value, 305 | ast.children[1].allText, 306 | sublines[1].null_value 307 | ) 308 | }) 309 | return lines 310 | } 311 | case 'deepCompareObj': { 312 | const sublines = [] 313 | this.processAst(ast.children, sublines) 314 | if (sublines[0].list_value) { 315 | // List eval 316 | lines.push({ 317 | bool_value: deepArrayEvaluateComparison( 318 | sublines[0].list_value, 319 | ast.children[1].allText, 320 | sublines[1].list_value 321 | ) 322 | }) 323 | return lines 324 | } 325 | 326 | // Object eval 327 | lines.push({ 328 | bool_value: deepObjEvaluateComparison( 329 | sublines[0].map_value, 330 | ast.children[1].allText, 331 | sublines[1].map_value 332 | ) 333 | }) 334 | return lines 335 | } 336 | case 'elementInObj': { 337 | const sublines = [] 338 | this.processAst(ast.children, sublines) 339 | if (sublines[1].list_value) { 340 | // { string_value: 'value' } 341 | // ^^^^^^^^^^^^ 342 | const key = Object.keys(sublines[0])[0] 343 | // { string_value: 'value' } 344 | // ^^^^^^^ 345 | const value = Object.values(sublines[0])[0] 346 | // List index 347 | lines.push({ 348 | bool_value: (() => { 349 | if (sublines[1].list_value.values) { 350 | return sublines[1].list_value.values.filter(val => { 351 | if (val[key] === value) return true 352 | return false 353 | }).length > 0 354 | } 355 | return false 356 | })() 357 | }) 358 | return lines 359 | } 360 | 361 | // Is a map 362 | // { string_value: 'value' } 363 | // ^^^^^^^ 364 | const key = Object.values(sublines[0])[0] 365 | lines.push({ 366 | bool_value: (() => { 367 | if (sublines[1].map_value.entries) { 368 | return sublines[1].map_value.entries.filter(entry => { 369 | if (entry.key.string_value === key) return true 370 | return false 371 | }).length > 0 372 | } 373 | return false 374 | })() 375 | }) 376 | return lines 377 | } 378 | case 'sizeOfObj': { 379 | const sublines = [] 380 | this.processAst(ast.children, sublines) 381 | if (sublines[0].list_value) { 382 | // List length 383 | if (!sublines[0].list_value.values) { 384 | lines.push({ 385 | int64_value: 0 386 | }) 387 | return lines 388 | } 389 | 390 | lines.push({ 391 | int64_value: sublines[0].list_value.values.length 392 | }) 393 | return lines 394 | } 395 | 396 | // Is a map 397 | if (!sublines[0].map_value.entries) { 398 | lines.push({ 399 | int64_value: 0 400 | }) 401 | return lines 402 | } 403 | 404 | lines.push({ 405 | int64_value: sublines[0].map_value.entries.length 406 | }) 407 | return lines 408 | } 409 | case 'indexOfObj': { 410 | const sublines = [] 411 | this.processAst(ast.children, sublines) 412 | const [array, index] = sublines 413 | if (array.list_value.values.length <= index.int64_value) { 414 | throw new Error('{ message: "invalid_argument" }') 415 | } 416 | 417 | lines.push( array.list_value.values[index.int64_value] ) 418 | return lines 419 | } 420 | case 'concatObj': { 421 | const sublines = [] 422 | this.processAst(ast.children, sublines) 423 | const [arrayLeft, arrayRight] = sublines 424 | const values = [] 425 | if (arrayLeft.list_value.values) { 426 | values.push(...arrayLeft.list_value.values) 427 | } 428 | if (arrayRight.list_value.values) { 429 | values.push(...arrayRight.list_value.values) 430 | } 431 | 432 | if (values.length === 0) { 433 | lines.push({ 434 | list_value: {} 435 | }) 436 | return lines 437 | } 438 | 439 | lines.push({ 440 | list_value: { 441 | values 442 | } 443 | }) 444 | return lines 445 | } 446 | case 'ternary': { 447 | const sublines = [] 448 | this.processAst(ast.children, sublines) 449 | const [boolean, trueValue, falseValue] = sublines 450 | lines.push(boolean.bool_value ? trueValue : falseValue) 451 | return lines 452 | } 453 | case 'logicalAnd': { 454 | const sublines = [] 455 | this.processAst(ast.children, sublines) 456 | const booleanMerge = sublines.reduce((acc, curr) => { 457 | if (acc.bool_value !== undefined) { 458 | // On first run 459 | return acc.bool_value && curr.bool_value 460 | } 461 | // Subsequent runs 462 | return acc && curr.bool_value 463 | }) 464 | 465 | lines.push({ 466 | bool_value: booleanMerge 467 | }) 468 | return lines 469 | } 470 | case 'logicalOr': { 471 | const sublines = [] 472 | this.processAst(ast.children, sublines) 473 | const booleanMerge = sublines.reduce((acc, curr) => { 474 | if (acc.bool_value !== undefined) { 475 | // On first run 476 | return acc.bool_value || curr.bool_value 477 | } 478 | if (acc !== true && acc !== false) { 479 | // This is not a boolean 480 | acc = true // Assume it's truthy 481 | } 482 | // Subsequent runs 483 | return acc || curr.bool_value 484 | }) 485 | 486 | lines.push({ 487 | bool_value: booleanMerge 488 | }) 489 | return lines 490 | } 491 | case 'logicalNot': { 492 | const sublines = [] 493 | this.processAst(ast.children, sublines) 494 | const [condition] = sublines 495 | if (!('bool_value' in condition)) { 496 | throw new Error('{ message: "no matching overload" }') 497 | } 498 | 499 | lines.push({ 500 | bool_value: !condition.bool_value 501 | }) 502 | return lines 503 | } 504 | case 'ternaryTypeMismatch': 505 | case 'comparisonTypeMismatch': { 506 | throw new Error(`{ message: "no such overload" }`) 507 | } 508 | default: { 509 | this.processAst(ast.children, lines); 510 | return lines; 511 | } 512 | } 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export const NULL_VALUE = 0 -------------------------------------------------------------------------------- /tests/basic.spec.ts: -------------------------------------------------------------------------------- 1 | import { CelSpec } from '../src/CelSpec'; 2 | import { TextFormatter } from '../src/formatters/TextFormatter'; 3 | import { NULL_VALUE } from '../src'; 4 | 5 | const genCel = (expr: string, bindings?: any) => { 6 | const speech = new CelSpec(); 7 | const ast = speech.toAST(expr, {}); 8 | const bindingsAst = (() => { 9 | if (!bindings) return {} 10 | const tf = new TextFormatter({}, bindings) 11 | let res = {} 12 | for (const [key, entry] of Object.entries(bindings)) { 13 | const entryAst = speech.toAST(`${entry}`) 14 | const entryCel = tf.format(entryAst) 15 | res[key] = entryCel 16 | } 17 | return res 18 | })() 19 | 20 | const tf = new TextFormatter({}, bindingsAst) 21 | return tf.format(ast) 22 | } 23 | 24 | describe('basic.self_eval_zeroish', () => { 25 | test('self_eval_int_zero', () => { 26 | const expr = '0' 27 | const expected = { 28 | int64_value: 0 29 | } 30 | 31 | const cel = genCel(expr) 32 | expect(cel).toStrictEqual(expected); 33 | }); 34 | 35 | test('self_eval_uint_zero', () => { 36 | const expr = '0u' 37 | const expected = { 38 | uint64_value: 0 39 | } 40 | 41 | const cel = genCel(expr) 42 | expect(cel).toStrictEqual(expected); 43 | }); 44 | 45 | test('self_eval_float_zero', () => { 46 | const expr = '0.0' 47 | const expected = { 48 | double_value: 0 49 | } 50 | 51 | const cel = genCel(expr) 52 | expect(cel).toStrictEqual(expected) 53 | }) 54 | 55 | test('self_eval_float_zerowithexp', () => { 56 | const expr = '0e+0' 57 | const expected = { 58 | double_value: 0 59 | } 60 | 61 | const cel = genCel(expr) 62 | expect(cel).toStrictEqual(expected) 63 | }) 64 | 65 | test('self_eval_string_empty', () => { 66 | const expr = `''` 67 | const expected = { 68 | string_value: '' 69 | } 70 | 71 | const cel = genCel(expr) 72 | expect(cel).toStrictEqual(expected) 73 | }) 74 | 75 | test('self_eval_string_empty_quotes', () => { 76 | const expr = `""` 77 | const expected = { 78 | string_value: '' 79 | } 80 | 81 | const cel = genCel(expr) 82 | expect(cel).toStrictEqual(expected) 83 | }) 84 | 85 | test('self_eval_string_raw_prefix', () => { 86 | const expr = `r""` 87 | const expected = { 88 | string_value: '' 89 | } 90 | 91 | const cel = genCel(expr) 92 | expect(cel).toStrictEqual(expected) 93 | }) 94 | 95 | test('self_eval_bytes_empty', () => { 96 | const expr = `b""` 97 | const expected = { 98 | bytes_value: '' 99 | } 100 | 101 | const cel = genCel(expr) 102 | expect(cel).toStrictEqual(expected) 103 | }) 104 | 105 | test('self_eval_bool_false', () => { 106 | const expr = 'false' 107 | const expected = { 108 | bool_value: false 109 | } 110 | 111 | const cel = genCel(expr) 112 | expect(cel).toStrictEqual(expected) 113 | }) 114 | 115 | test('self_eval_null', () => { 116 | const expr = 'null' 117 | const expected = { 118 | null_value: NULL_VALUE 119 | } 120 | 121 | const cel = genCel(expr) 122 | expect(cel).toStrictEqual(expected) 123 | }) 124 | 125 | test('self_eval_empty_list', () => { 126 | const expr = '[]' 127 | const expected = { 128 | list_value: {} 129 | } 130 | 131 | const cel = genCel(expr) 132 | expect(cel).toStrictEqual(expected) 133 | }) 134 | 135 | test('self_eval_empty_map', () => { 136 | const expr = '{}' 137 | const expected = { 138 | map_value: {} 139 | } 140 | 141 | const cel = genCel(expr) 142 | expect(cel).toStrictEqual(expected) 143 | }) 144 | }); 145 | 146 | describe('basic.self_eval_nonzeroish', () => { 147 | test('self_eval_int_nonzero', () => { 148 | const expr = '42' 149 | const expected = { 150 | int64_value: 42 151 | } 152 | 153 | const cel = genCel(expr) 154 | expect(cel).toStrictEqual(expected) 155 | }) 156 | 157 | test('self_eval_uint_nonzero', () => { 158 | const expr = '123456789u' 159 | const expected = { 160 | uint64_value: 123456789 161 | } 162 | 163 | const cel = genCel(expr) 164 | expect(cel).toStrictEqual(expected) 165 | }) 166 | 167 | test('self_eval_int_negative_min', () => { 168 | const expr = '-9223372036854775808' 169 | const expected = { 170 | int64_value: -9223372036854775808 171 | } 172 | 173 | const cel = genCel(expr) 174 | expect(cel).toStrictEqual(expected) 175 | }) 176 | 177 | test('self_eval_float_negative_exp', () => { 178 | const expr = '-2.3e+1' 179 | const expected = { 180 | double_value: -23 181 | } 182 | 183 | const cel = genCel(expr) 184 | expect(cel).toStrictEqual(expected) 185 | }) 186 | 187 | test('self_eval_string_excl', () => { 188 | const expr = `"!"` 189 | const expected = { 190 | string_value: '!' 191 | } 192 | 193 | const cel = genCel(expr) 194 | expect(cel).toStrictEqual(expected) 195 | }) 196 | 197 | test.skip('self_eval_string_escape_single_backslash', () => { 198 | const expr = `'\''` 199 | const expected = { 200 | string_value: `'` 201 | } 202 | 203 | const cel = genCel(expr) 204 | expect(cel).toStrictEqual(expected) 205 | }) 206 | 207 | test('self_eval_bytes_escape', () => { 208 | const expr = `b'ÿ'` 209 | const expected = { 210 | // Octal values are not respected 211 | // Also, the expected value seems wrong 212 | // bytes_value: '\303\277' 213 | bytes_value: '\\377' 214 | } 215 | 216 | const cel = genCel(expr) 217 | expect(cel).toStrictEqual(expected) 218 | }) 219 | 220 | test('self_eval_bytes_invalid_utf8', () => { 221 | const expr = `b'\\000\\xff'` 222 | const expected = { 223 | bytes_value: '\\000\\377' 224 | } 225 | 226 | const cel = genCel(expr) 227 | expect(cel).toStrictEqual(expected) 228 | }) 229 | 230 | test('self_eval_list_singleitem_int', () => { 231 | const expr = '[-1]' 232 | const expected = { 233 | list_value: { 234 | values: [{ int64_value: -1 }] 235 | } 236 | } 237 | 238 | const cel = genCel(expr) 239 | expect(cel).toStrictEqual(expected) 240 | }) 241 | 242 | test('self_eval_list_singleitem_double', () => { 243 | const expr = '[-1.0]' 244 | const expected = { 245 | list_value: { 246 | values: [{ double_value: -1 }] 247 | } 248 | } 249 | 250 | const cel = genCel(expr) 251 | expect(cel).toStrictEqual(expected) 252 | }) 253 | 254 | test('self_eval_list_singleitem_string', () => { 255 | const expr = '["-1"]' 256 | const expected = { 257 | list_value: { 258 | values: [{ string_value: `-1` }] 259 | } 260 | } 261 | 262 | const cel = genCel(expr) 263 | expect(cel).toStrictEqual(expected) 264 | }) 265 | 266 | test('self_eval_map_singleitem', () => { 267 | const expr = '{"k":"v"}' 268 | const expected = { 269 | map_value: { 270 | entries: [{ 271 | key: { string_value: "k" }, 272 | value: { string_value: "v" } 273 | }] 274 | } 275 | } 276 | 277 | const cel = genCel(expr) 278 | expect(cel).toStrictEqual(expected) 279 | }) 280 | 281 | test('self_eval_bool_true', () => { 282 | const expr = 'true' 283 | const expected = { bool_value: true } 284 | 285 | const cel = genCel(expr) 286 | expect(cel).toStrictEqual(expected) 287 | }) 288 | }) 289 | 290 | describe('variables', () => { 291 | test('self_eval_bound_lookup', () => { 292 | const expr = 'x' 293 | const bindings = { 294 | x: 123 295 | } 296 | const expected = { 297 | int64_value: 123 298 | } 299 | 300 | const cel = genCel(expr, bindings) 301 | expect(cel).toStrictEqual(expected) 302 | }) 303 | 304 | test('self_eval_unbound_lookup', () => { 305 | const expr = 'x' 306 | try { 307 | genCel(expr) 308 | expect(true).toBe(false) // Test exception 309 | } catch (e) { 310 | expect(e.message).toBe(`{ message: "undeclared reference to 'x' (in container '')" }`) 311 | } 312 | }) 313 | }) 314 | 315 | describe('reserved_const', () => { 316 | test('false', () => { 317 | const expr = 'false' 318 | const bindings = { 319 | false: true 320 | } 321 | const expected = { 322 | bool_value: false 323 | } 324 | 325 | const cel = genCel(expr, bindings) 326 | expect(cel).toStrictEqual(expected) 327 | }) 328 | 329 | test('true', () => { 330 | const expr = 'true' 331 | const bindings = { 332 | true: false 333 | } 334 | const expected = { 335 | bool_value: true 336 | } 337 | 338 | const cel = genCel(expr, bindings) 339 | expect(cel).toStrictEqual(expected) 340 | }) 341 | 342 | test('null', () => { 343 | const expr = 'null' 344 | const bindings = { 345 | null: true 346 | } 347 | const expected = { 348 | null_value: NULL_VALUE 349 | } 350 | 351 | const cel = genCel(expr, bindings) 352 | expect(cel).toStrictEqual(expected) 353 | }) 354 | }) 355 | -------------------------------------------------------------------------------- /tests/comparisons.spec.ts: -------------------------------------------------------------------------------- 1 | import { CelSpec } from '../src/CelSpec'; 2 | import { TextFormatter } from '../src/formatters/TextFormatter'; 3 | import { NULL_VALUE } from '../src'; 4 | 5 | const genCel = (expr: string, bindings?: any, debug?: boolean) => { 6 | const speech = new CelSpec(); 7 | const ast = speech.toAST(expr, {}); 8 | if (debug) console.log(expr, ast) 9 | if (debug) console.log(ast.children) 10 | if (debug) console.log(ast.children[0].children) 11 | const bindingsAst = (() => { 12 | if (!bindings) return {} 13 | const tf = new TextFormatter({}, bindings) 14 | let res = {} 15 | for (const [key, entry] of Object.entries(bindings)) { 16 | if (debug) console.log('res', res) 17 | if (debug) console.log('entry', key, entry, 'of', bindings) 18 | const entryAst = speech.toAST(JSON.stringify(entry)) 19 | if (debug) console.log('eAST', entryAst) 20 | const entryCel = tf.format(entryAst) 21 | res[key] = entryCel 22 | if (debug) console.log('res2', res) 23 | } 24 | if (debug) console.log('res-end', res) 25 | return res 26 | })() 27 | if (debug) console.log(bindings, bindingsAst) 28 | 29 | const tf = new TextFormatter({}, bindingsAst) 30 | return tf.format(ast) 31 | } 32 | 33 | describe('comparisons.eq_literal', () => { 34 | test('eq_int', () => { 35 | const expr = '1 == 1' 36 | const expected = { 37 | bool_value: true 38 | } 39 | 40 | const cel = genCel(expr) 41 | expect(cel).toStrictEqual(expected); 42 | }); 43 | 44 | test('not_eq_int', () => { 45 | const expr = '-1 == 1' 46 | const expected = { 47 | bool_value: false 48 | } 49 | 50 | const cel = genCel(expr) 51 | expect(cel).toStrictEqual(expected); 52 | }) 53 | 54 | test('eq_uint', () => { 55 | const expr = '2u == 2u' 56 | const expected = { 57 | bool_value: true 58 | } 59 | 60 | const cel = genCel(expr) 61 | expect(cel).toStrictEqual(expected); 62 | }) 63 | 64 | test('not_eq_uint', () => { 65 | const expr = '1u == 2u' 66 | const expected = { 67 | bool_value: false 68 | } 69 | 70 | const cel = genCel(expr) 71 | expect(cel).toStrictEqual(expected); 72 | }) 73 | 74 | test('eq_double', () => { 75 | const expr = '1.0 == 1.0e+0' 76 | const expected = { 77 | bool_value: true 78 | } 79 | 80 | const cel = genCel(expr) 81 | expect(cel).toStrictEqual(expected); 82 | }) 83 | 84 | test('not_eq_double', () => { 85 | const expr = '-1.0 == 1.0e+0' 86 | const expected = { 87 | bool_value: false 88 | } 89 | 90 | const cel = genCel(expr) 91 | expect(cel).toStrictEqual(expected); 92 | }) 93 | 94 | test('eq_string', () => { 95 | const expr = "'' == \"\"" 96 | const expected = { 97 | bool_value: true 98 | } 99 | 100 | const cel = genCel(expr) 101 | expect(cel).toStrictEqual(expected); 102 | }) 103 | 104 | test('not_eq_string', () => { 105 | const expr = "'a' == 'b'" 106 | const expected = { 107 | bool_value: false 108 | } 109 | 110 | const cel = genCel(expr) 111 | expect(cel).toStrictEqual(expected); 112 | }) 113 | 114 | test('eq_raw_string', () => { 115 | const expr = "'abc' == r'abc'" 116 | const expected = { 117 | bool_value: true 118 | } 119 | 120 | const cel = genCel(expr) 121 | expect(cel).toStrictEqual(expected); 122 | }) 123 | 124 | test('not_eq_string_case', () => { 125 | const expr = "'abc' == 'ABC'" 126 | const expected = { 127 | bool_value: false 128 | } 129 | 130 | const cel = genCel(expr) 131 | expect(cel).toStrictEqual(expected); 132 | }) 133 | 134 | test('eq_string_unicode', () => { 135 | const expr = "'ίσος' == 'ίσος'" 136 | const expected = { 137 | bool_value: true 138 | } 139 | 140 | const cel = genCel(expr) 141 | expect(cel).toStrictEqual(expected); 142 | }) 143 | 144 | test('not_eq_string_unicode_ascii', () => { 145 | const expr = "'a' == 'à'" 146 | const expected = { 147 | bool_value: false 148 | } 149 | 150 | const cel = genCel(expr) 151 | expect(cel).toStrictEqual(expected); 152 | }) 153 | 154 | test('eq_null', () => { 155 | const expr = 'null == null' 156 | const expected = { 157 | bool_value: true 158 | } 159 | 160 | const cel = genCel(expr) 161 | expect(cel).toStrictEqual(expected); 162 | }) 163 | 164 | test('eq_bool', () => { 165 | const expr = 'true == true' 166 | const expected = { 167 | bool_value: true 168 | } 169 | 170 | const cel = genCel(expr) 171 | expect(cel).toStrictEqual(expected); 172 | }) 173 | 174 | test('not_eq_bool', () => { 175 | const expr = 'false == true' 176 | const expected = { 177 | bool_value: false 178 | } 179 | 180 | const cel = genCel(expr) 181 | expect(cel).toStrictEqual(expected); 182 | }) 183 | 184 | test('eq_bytes', () => { 185 | const expr = "b'ÿ' == b'\\377'" 186 | const expected = { 187 | bool_value: true 188 | } 189 | 190 | const cel = genCel(expr) 191 | expect(cel).toStrictEqual(expected); 192 | }) 193 | 194 | test('not_eq_bytes', () => { 195 | const expr = "b'abc' == b'abcd'" 196 | const expected = { 197 | bool_value: false 198 | } 199 | 200 | const cel = genCel(expr) 201 | expect(cel).toStrictEqual(expected); 202 | }) 203 | 204 | test('eq_list_empty', () => { 205 | const expr = "[] == []" 206 | const expected = { 207 | bool_value: true 208 | } 209 | 210 | const cel = genCel(expr) 211 | expect(cel).toStrictEqual(expected); 212 | }) 213 | 214 | test('eq_list_numbers', () => { 215 | const expr = "[1, 2, 3] == [1, 2, 3]" 216 | const expected = { 217 | bool_value: true 218 | } 219 | 220 | const cel = genCel(expr) 221 | expect(cel).toStrictEqual(expected); 222 | }) 223 | 224 | test('not_eq_list_order', () => { 225 | const expr = "[1, 2, 3] == [1, 3, 2]" 226 | const expected = { 227 | bool_value: false 228 | } 229 | 230 | const cel = genCel(expr) 231 | expect(cel).toStrictEqual(expected); 232 | }) 233 | 234 | test('not_eq_list_string_case', () => { 235 | const expr = "['case'] == ['cAse']" 236 | const expected = { 237 | bool_value: false 238 | } 239 | 240 | const cel = genCel(expr) 241 | expect(cel).toStrictEqual(expected); 242 | }) 243 | 244 | test('eq_map_empty', () => { 245 | const expr = "{} == {}" 246 | const expected = { 247 | bool_value: true 248 | } 249 | 250 | const cel = genCel(expr) 251 | expect(cel).toStrictEqual(expected); 252 | }) 253 | 254 | test('eq_map_onekey', () => { 255 | const expr = "{'k':'v'} == {\"k\":\"v\"}" 256 | const expected = { 257 | bool_value: true 258 | } 259 | 260 | const cel = genCel(expr) 261 | expect(cel).toStrictEqual(expected); 262 | }) 263 | 264 | test('eq_map_doublevalue', () => { 265 | const expr = "{'k':1.0} == {'k':1e+0}" 266 | const expected = { 267 | bool_value: true 268 | } 269 | 270 | const cel = genCel(expr) 271 | expect(cel).toStrictEqual(expected); 272 | }) 273 | 274 | test('not_eq_map_value', () => { 275 | const expr = "{'k':'v'} == {'k':'v1'}" 276 | const expected = { 277 | bool_value: false 278 | } 279 | 280 | const cel = genCel(expr) 281 | expect(cel).toStrictEqual(expected); 282 | }) 283 | 284 | test('not_eq_map_extrakey', () => { 285 | const expr = "{'k':'v','k1':'v1'} == {'k':'v'}" 286 | const expected = { 287 | bool_value: false 288 | } 289 | 290 | const cel = genCel(expr) 291 | expect(cel).toStrictEqual(expected); 292 | }) 293 | 294 | test('eq_map_keyorder', () => { 295 | const expr = "{'k1':'v1','k2':'v2'} == {'k2':'v2','k1':'v1'}" 296 | const expected = { 297 | bool_value: true 298 | } 299 | 300 | const cel = genCel(expr) 301 | expect(cel).toStrictEqual(expected); 302 | }) 303 | 304 | test('not_eq_map_key_casing', () => { 305 | const expr = "{'key':'value'} == {'Key':'value'}" 306 | const expected = { 307 | bool_value: false 308 | } 309 | 310 | const cel = genCel(expr) 311 | expect(cel).toStrictEqual(expected); 312 | }) 313 | 314 | test('eq_mixed_types_error', () => { 315 | const expr = "1.0 == 1" 316 | try { 317 | genCel(expr) 318 | expect(true).toBe(false) // Expect error 319 | } catch (e) { 320 | expect(e.message).toBe(`{ message: "no such overload" }`) 321 | } 322 | }) 323 | 324 | // Skip this test because JS compares as `true` 325 | test.skip('eq_list_elem_mixed_types_error', () => { 326 | const expr = "[1] == [1.0]" 327 | try { 328 | genCel(expr) 329 | expect(true).toBe(false) // Expect error 330 | } catch (e) { 331 | expect(e.message).toBe(`{ message: "no such overload" }`) 332 | } 333 | }) 334 | 335 | // Skip this test as type comparisons don't throw errors 336 | test.skip('eq_map_value_mixed_types_error', () => { 337 | const expr = "{'k':'v', 1:1} == {'k':'v', 1:'v1'}" 338 | try { 339 | genCel(expr) 340 | expect(true).toBe(false) // Expect error 341 | } catch (e) { 342 | expect(e.message).toBe(`{ message: "no such overload" }`) 343 | } 344 | }) 345 | }) 346 | 347 | describe('comparisons.ne_literal', () => { 348 | test('ne_int', () => { 349 | const expr = "24 != 42" 350 | const expected = { 351 | bool_value: true 352 | } 353 | 354 | const cel = genCel(expr) 355 | expect(cel).toStrictEqual(expected); 356 | }) 357 | 358 | test('not_ne_int', () => { 359 | const expr = "1 != 1" 360 | const expected = { 361 | bool_value: false 362 | } 363 | 364 | const cel = genCel(expr) 365 | expect(cel).toStrictEqual(expected); 366 | }) 367 | 368 | test('ne_uint', () => { 369 | const expr = "1u != 2u" 370 | const expected = { 371 | bool_value: true 372 | } 373 | 374 | const cel = genCel(expr) 375 | expect(cel).toStrictEqual(expected); 376 | }) 377 | 378 | test('not_ne_uint', () => { 379 | const expr = "99u != 99u" 380 | const expected = { 381 | bool_value: false 382 | } 383 | 384 | const cel = genCel(expr) 385 | expect(cel).toStrictEqual(expected); 386 | }) 387 | 388 | test('ne_double', () => { 389 | const expr = "9.0e+3 != 9001.0" 390 | const expected = { 391 | bool_value: true 392 | } 393 | 394 | const cel = genCel(expr) 395 | expect(cel).toStrictEqual(expected); 396 | }) 397 | 398 | test('not_ne_double', () => { 399 | const expr = "1.0 != 1e+0" 400 | const expected = { 401 | bool_value: false 402 | } 403 | 404 | const cel = genCel(expr) 405 | expect(cel).toStrictEqual(expected); 406 | }) 407 | 408 | test('ne_string', () => { 409 | const expr = "'abc' != ''" 410 | const expected = { 411 | bool_value: true 412 | } 413 | 414 | const cel = genCel(expr) 415 | expect(cel).toStrictEqual(expected); 416 | }) 417 | 418 | test('not_ne_string', () => { 419 | const expr = "'abc' != 'abc'" 420 | const expected = { 421 | bool_value: false 422 | } 423 | 424 | const cel = genCel(expr) 425 | expect(cel).toStrictEqual(expected); 426 | }) 427 | 428 | test('ne_string_unicode', () => { 429 | const expr = "'résumé' != 'resume'" 430 | const expected = { 431 | bool_value: true 432 | } 433 | 434 | const cel = genCel(expr) 435 | expect(cel).toStrictEqual(expected); 436 | }) 437 | 438 | test('not_ne_string_unicode', () => { 439 | const expr = "'ίδιο' != 'ίδιο'" 440 | const expected = { 441 | bool_value: false 442 | } 443 | 444 | const cel = genCel(expr) 445 | expect(cel).toStrictEqual(expected); 446 | }) 447 | 448 | test('ne_bytes', () => { 449 | const expr = "b'\\x00\\xFF' != b'ÿ'" 450 | const expected = { 451 | bool_value: true 452 | } 453 | 454 | const cel = genCel(expr) 455 | expect(cel).toStrictEqual(expected); 456 | }) 457 | 458 | test('not_ne_bytes', () => { 459 | const expr = "b'\\377' != b'ÿ'" 460 | const expected = { 461 | bool_value: false 462 | } 463 | 464 | const cel = genCel(expr) 465 | expect(cel).toStrictEqual(expected); 466 | }) 467 | 468 | test('ne_bool', () => { 469 | const expr = "false != true" 470 | const expected = { 471 | bool_value: true 472 | } 473 | 474 | const cel = genCel(expr) 475 | expect(cel).toStrictEqual(expected); 476 | }) 477 | 478 | test('not_ne_bool', () => { 479 | const expr = "true != true" 480 | const expected = { 481 | bool_value: false 482 | } 483 | 484 | const cel = genCel(expr) 485 | expect(cel).toStrictEqual(expected); 486 | }) 487 | 488 | test('not_ne_null', () => { 489 | const expr = "null != null" 490 | const expected = { 491 | bool_value: false 492 | } 493 | 494 | const cel = genCel(expr) 495 | expect(cel).toStrictEqual(expected); 496 | }) 497 | 498 | test('ne_list_empty', () => { 499 | const expr = "[] != [1]" 500 | const expected = { 501 | bool_value: true 502 | } 503 | 504 | const cel = genCel(expr) 505 | expect(cel).toStrictEqual(expected); 506 | }) 507 | 508 | test('not_ne_list_empty', () => { 509 | const expr = "[] != []" 510 | const expected = { 511 | bool_value: false 512 | } 513 | 514 | const cel = genCel(expr) 515 | expect(cel).toStrictEqual(expected); 516 | }) 517 | 518 | test('ne_list_bool', () => { 519 | const expr = "[true, false, true] != [true, true, false]" 520 | const expected = { 521 | bool_value: true 522 | } 523 | 524 | const cel = genCel(expr) 525 | expect(cel).toStrictEqual(expected); 526 | }) 527 | 528 | test('not_ne_list_bool', () => { 529 | const expr = "[false, true] != [false, true]" 530 | const expected = { 531 | bool_value: false 532 | } 533 | 534 | const cel = genCel(expr) 535 | expect(cel).toStrictEqual(expected); 536 | }) 537 | 538 | // Right now this does not support nested arrays/maps 539 | test.skip('not_ne_list_of_list', () => { 540 | const expr = "[[]] != [[]]" 541 | const expected = { 542 | bool_value: false 543 | } 544 | 545 | const cel = genCel(expr) 546 | expect(cel).toStrictEqual(expected); 547 | }) 548 | 549 | test('ne_map_by_value', () => { 550 | const expr = "{'k':'v'} != {'k':'v1'}" 551 | const expected = { 552 | bool_value: true 553 | } 554 | 555 | const cel = genCel(expr) 556 | expect(cel).toStrictEqual(expected); 557 | }) 558 | 559 | test('ne_map_by_key', () => { 560 | const expr = "{'k':true} != {'k1':true}" 561 | const expected = { 562 | bool_value: true 563 | } 564 | 565 | const cel = genCel(expr) 566 | expect(cel).toStrictEqual(expected); 567 | }) 568 | 569 | test('not_ne_map_int_to_float', () => { 570 | const expr = "{1:1.0} != {1:1.0}" 571 | const expected = { 572 | bool_value: false 573 | } 574 | 575 | const cel = genCel(expr) 576 | expect(cel).toStrictEqual(expected); 577 | }) 578 | 579 | test('not_ne_map_key_order', () => { 580 | const expr = "{'a':'b','c':'d'} != {'c':'d','a':'b'}" 581 | const expected = { 582 | bool_value: false 583 | } 584 | 585 | const cel = genCel(expr) 586 | expect(cel).toStrictEqual(expected); 587 | }) 588 | 589 | test('ne_mixed_types_error', () => { 590 | const expr = "2u != 2" 591 | try { 592 | const cel = genCel(expr) 593 | expect(true).toBe(false) // Expect error 594 | } catch (e) { 595 | expect(e.message).toBe('{ message: "no such overload" }') 596 | } 597 | }) 598 | }) 599 | 600 | describe('comparisons.lt_literal', () => { 601 | test('lt_int', () => { 602 | const expr = "-1 < 0" 603 | const expected = { 604 | bool_value: true 605 | } 606 | 607 | const cel = genCel(expr) 608 | expect(cel).toStrictEqual(expected); 609 | }) 610 | 611 | test('not_lt_int', () => { 612 | const expr = '0 < 0' 613 | const expected = { 614 | bool_value: false 615 | } 616 | 617 | const cel = genCel(expr) 618 | expect(cel).toStrictEqual(expected); 619 | }) 620 | 621 | test('lt_uint', () => { 622 | const expr = "0u < 1u" 623 | const expected = { 624 | bool_value: true 625 | } 626 | 627 | const cel = genCel(expr) 628 | expect(cel).toStrictEqual(expected); 629 | }) 630 | 631 | test('not_lt_uint', () => { 632 | const expr = "2u < 2u" 633 | const expected = { 634 | bool_value: false 635 | } 636 | 637 | const cel = genCel(expr) 638 | expect(cel).toStrictEqual(expected); 639 | }) 640 | 641 | test('lt_double', () => { 642 | const expr = "1.0 < 1.0000001" 643 | const expected = { 644 | bool_value: true 645 | } 646 | 647 | const cel = genCel(expr) 648 | expect(cel).toStrictEqual(expected); 649 | }) 650 | 651 | test('not_lt_double', () => { 652 | // Following IEEE 754, negative zero compares equal to zero 653 | const expr = "-0.0 < 0.0" 654 | const expected = { 655 | bool_value: false 656 | } 657 | 658 | const cel = genCel(expr) 659 | expect(cel).toStrictEqual(expected); 660 | }) 661 | 662 | test('lt_string', () => { 663 | const expr = "'a' < 'b'" 664 | const expected = { 665 | bool_value: true 666 | } 667 | 668 | const cel = genCel(expr) 669 | expect(cel).toStrictEqual(expected); 670 | }) 671 | 672 | test('lt_string_empty_to_nonempty', () => { 673 | const expr = "'' < 'a'" 674 | const expected = { 675 | bool_value: true 676 | } 677 | 678 | const cel = genCel(expr) 679 | expect(cel).toStrictEqual(expected); 680 | }) 681 | 682 | test('lt_string_case', () => { 683 | const expr = "'Abc' < 'aBC'" 684 | const expected = { 685 | bool_value: true 686 | } 687 | 688 | const cel = genCel(expr) 689 | expect(cel).toStrictEqual(expected); 690 | }) 691 | 692 | test('lt_string_length', () => { 693 | const expr = "'abc' < 'abcd'" 694 | const expected = { 695 | bool_value: true 696 | } 697 | 698 | const cel = genCel(expr) 699 | expect(cel).toStrictEqual(expected); 700 | }) 701 | 702 | test.skip('lt_string_diacritical_mark_sensitive', () => { 703 | // Verifies that the we're not using a string comparison function 704 | // that strips diacritical marks (á) 705 | const expr = "'a' < '\\u00E1'" 706 | const expected = { 707 | bool_value: true 708 | } 709 | 710 | const cel = genCel(expr) 711 | expect(cel).toStrictEqual(expected); 712 | }) 713 | 714 | test('not_lt_string_empty', () => { 715 | const expr = "'' < ''" 716 | const expected = { 717 | bool_value: false 718 | } 719 | 720 | const cel = genCel(expr) 721 | expect(cel).toStrictEqual(expected); 722 | }) 723 | 724 | test('not_lt_string_same', () => { 725 | const expr = "'abc' < 'abc'" 726 | const expected = { 727 | bool_value: false 728 | } 729 | 730 | const cel = genCel(expr) 731 | expect(cel).toStrictEqual(expected); 732 | }) 733 | 734 | test('not_lt_string_case_length', () => { 735 | const expr = "'a' < 'AB'" 736 | const expected = { 737 | bool_value: false 738 | } 739 | 740 | const cel = genCel(expr) 741 | expect(cel).toStrictEqual(expected); 742 | }) 743 | 744 | test('lt_bytes', () => { 745 | const expr = "b'a' < b'b'" 746 | const expected = { 747 | bool_value: true 748 | } 749 | 750 | const cel = genCel(expr) 751 | expect(cel).toStrictEqual(expected); 752 | }) 753 | 754 | test('not_lt_bytes_same', () => { 755 | const expr = "b'abc' < b'abc'" 756 | const expected = { 757 | bool_value: false 758 | } 759 | 760 | const cel = genCel(expr) 761 | expect(cel).toStrictEqual(expected); 762 | }) 763 | 764 | test('not_lt_bytes_width', () => { 765 | const expr = "b'á' < b'b'" 766 | const expected = { 767 | bool_value: false 768 | } 769 | 770 | const cel = genCel(expr) 771 | expect(cel).toStrictEqual(expected); 772 | }) 773 | 774 | test('lt_bool_false_first', () => { 775 | const expr = "false < true" 776 | const expected = { 777 | bool_value: true 778 | } 779 | 780 | const cel = genCel(expr) 781 | expect(cel).toStrictEqual(expected); 782 | }) 783 | 784 | test('not_lt_bool_same', () => { 785 | const expr = "true < true" 786 | const expected = { 787 | bool_value: false 788 | } 789 | 790 | const cel = genCel(expr) 791 | expect(cel).toStrictEqual(expected); 792 | }) 793 | 794 | test('not_lt_bool_true_first', () => { 795 | const expr = "true < false" 796 | const expected = { 797 | bool_value: false 798 | } 799 | 800 | const cel = genCel(expr) 801 | expect(cel).toStrictEqual(expected); 802 | }) 803 | 804 | test('lt_list_unsupported', () => { 805 | const expr = "[0] < [1]" 806 | try { 807 | const cel = genCel(expr) 808 | expect(true).toBe(false) 809 | } catch (e) { 810 | expect(e.message).toBe('{ message: "no such overload" }') 811 | } 812 | }) 813 | 814 | test('lt_map_unsupported', () => { 815 | const expr = "{0:'a'} < {1:'b'}" 816 | try { 817 | const cel = genCel(expr) 818 | expect(true).toBe(false) 819 | } catch (e) { 820 | expect(e.message).toBe('{ message: "no such overload" }') 821 | } 822 | }) 823 | 824 | test('lt_null_unsupported', () => { 825 | const expr = "null < null" 826 | try { 827 | const cel = genCel(expr) 828 | expect(true).toBe(false) 829 | } catch (e) { 830 | expect(e.message).toBe('{ message: "no such overload" }') 831 | } 832 | }) 833 | 834 | test('lt_mixed_types_error', () => { 835 | const expr = "'foo' < 1024" 836 | try { 837 | const cel = genCel(expr) 838 | expect(true).toBe(false) 839 | } catch (e) { 840 | expect(e.message).toBe('{ message: "no such overload" }') 841 | } 842 | }) 843 | }) 844 | 845 | describe('comparisons.gt_literal', () => { 846 | test('gt_int', () => { 847 | const expr = "42 > -42" 848 | const expected = { 849 | bool_value: true 850 | } 851 | 852 | const cel = genCel(expr) 853 | expect(cel).toStrictEqual(expected); 854 | }) 855 | 856 | test('not_gt_int', () => { 857 | const expr = "0 > 0" 858 | const expected = { 859 | bool_value: false 860 | } 861 | 862 | const cel = genCel(expr) 863 | expect(cel).toStrictEqual(expected); 864 | }) 865 | 866 | test('gt_uint', () => { 867 | const expr = "48u > 46u" 868 | const expected = { 869 | bool_value: true 870 | } 871 | 872 | const cel = genCel(expr) 873 | expect(cel).toStrictEqual(expected); 874 | }) 875 | 876 | test('not_gt_uint', () => { 877 | const expr = "0u > 999u" 878 | const expected = { 879 | bool_value: false 880 | } 881 | 882 | const cel = genCel(expr) 883 | expect(cel).toStrictEqual(expected); 884 | }) 885 | 886 | test('gt_double', () => { 887 | const expr = "1e+1 > 1e+0" 888 | const expected = { 889 | bool_value: true 890 | } 891 | 892 | const cel = genCel(expr) 893 | expect(cel).toStrictEqual(expected); 894 | }) 895 | 896 | test('not_gt_double', () => { 897 | const expr = ".99 > 9.9e-1" 898 | const expected = { 899 | bool_value: false 900 | } 901 | 902 | const cel = genCel(expr) 903 | expect(cel).toStrictEqual(expected); 904 | }) 905 | 906 | test('gt_string_case', () => { 907 | const expr = "'abc' > 'aBc'" 908 | const expected = { 909 | bool_value: true 910 | } 911 | 912 | const cel = genCel(expr) 913 | expect(cel).toStrictEqual(expected); 914 | }) 915 | 916 | test('gt_string_to_empty', () => { 917 | const expr = "'A' > ''" 918 | const expected = { 919 | bool_value: true 920 | } 921 | 922 | const cel = genCel(expr) 923 | expect(cel).toStrictEqual(expected); 924 | }) 925 | 926 | test('not_gt_string_empty_to_empty', () => { 927 | const expr = "'' > ''" 928 | const expected = { 929 | bool_value: false 930 | } 931 | 932 | const cel = genCel(expr) 933 | expect(cel).toStrictEqual(expected); 934 | }) 935 | 936 | test('gt_string_unicode', () => { 937 | const expr = "'α' > 'omega'" 938 | const expected = { 939 | bool_value: true 940 | } 941 | 942 | const cel = genCel(expr) 943 | expect(cel).toStrictEqual(expected); 944 | }) 945 | 946 | test('gt_bytes_one', () => { 947 | const expr = "b'\x01' > b'\x00'" 948 | const expected = { 949 | bool_value: true 950 | } 951 | 952 | const cel = genCel(expr) 953 | expect(cel).toStrictEqual(expected); 954 | }) 955 | 956 | test('gt_bytes_one_to_empty', () => { 957 | const expr = "b'\x00' > b''" 958 | const expected = { 959 | bool_value: true 960 | } 961 | 962 | const cel = genCel(expr) 963 | expect(cel).toStrictEqual(expected); 964 | }) 965 | 966 | test('not_gt_bytes_sorting', () => { 967 | const expr = "b'\x00\x01' > b'\x01'" 968 | const expected = { 969 | bool_value: false 970 | } 971 | 972 | const cel = genCel(expr) 973 | expect(cel).toStrictEqual(expected); 974 | }) 975 | 976 | test('gt_bool_true_false', () => { 977 | const expr = "true > false" 978 | const expected = { 979 | bool_value: true 980 | } 981 | 982 | const cel = genCel(expr) 983 | expect(cel).toStrictEqual(expected); 984 | }) 985 | 986 | test('not_gt_bool_false_true', () => { 987 | const expr = "false > true" 988 | const expected = { 989 | bool_value: false 990 | } 991 | 992 | const cel = genCel(expr) 993 | expect(cel).toStrictEqual(expected); 994 | }) 995 | 996 | test('not_gt_bool_same', () => { 997 | const expr = "true > true" 998 | const expected = { 999 | bool_value: false 1000 | } 1001 | 1002 | const cel = genCel(expr) 1003 | expect(cel).toStrictEqual(expected); 1004 | }) 1005 | 1006 | test('gt_null_unsupported', () => { 1007 | const expr = "null > null" 1008 | try { 1009 | const cel = genCel(expr) 1010 | expect(true).toBe(false) 1011 | } catch (e) { 1012 | expect(e.message).toBe('{ message: "no such overload" }') 1013 | } 1014 | }) 1015 | 1016 | test('gt_list_unsupported', () => { 1017 | const expr = "[0] > [1]" 1018 | try { 1019 | const cel = genCel(expr) 1020 | expect(true).toBe(false) 1021 | } catch (e) { 1022 | expect(e.message).toBe('{ message: "no such overload" }') 1023 | } 1024 | }) 1025 | 1026 | test('gt_map_unsupported', () => { 1027 | const expr = "{0:'a'} > {1:'b'}" 1028 | try { 1029 | const cel = genCel(expr) 1030 | expect(true).toBe(false) 1031 | } catch (e) { 1032 | expect(e.message).toBe('{ message: "no such overload" }') 1033 | } 1034 | }) 1035 | 1036 | test('gt_mixed_types_error', () => { 1037 | const expr = "'foo' > 1024" 1038 | try { 1039 | const cel = genCel(expr) 1040 | expect(true).toBe(false) 1041 | } catch (e) { 1042 | expect(e.message).toBe('{ message: "no such overload" }') 1043 | } 1044 | }) 1045 | }) 1046 | 1047 | describe('comparisons.lte_literal', () => { 1048 | test('lte_int_lt', () => { 1049 | const expr = "0 <= 1" 1050 | const expected = { 1051 | bool_value: true 1052 | } 1053 | 1054 | const cel = genCel(expr) 1055 | expect(cel).toStrictEqual(expected); 1056 | }) 1057 | 1058 | test('lte_int_eq', () => { 1059 | const expr = "1 <= 1" 1060 | const expected = { 1061 | bool_value: true 1062 | } 1063 | 1064 | const cel = genCel(expr) 1065 | expect(cel).toStrictEqual(expected); 1066 | }) 1067 | 1068 | test('not_lte_int_gt', () => { 1069 | const expr = "1 <= -1" 1070 | const expected = { 1071 | bool_value: false 1072 | } 1073 | 1074 | const cel = genCel(expr) 1075 | expect(cel).toStrictEqual(expected); 1076 | }) 1077 | 1078 | test('lte_uint_lt', () => { 1079 | const expr = "0u <= 1u" 1080 | const expected = { 1081 | bool_value: true 1082 | } 1083 | 1084 | const cel = genCel(expr) 1085 | expect(cel).toStrictEqual(expected); 1086 | }) 1087 | 1088 | test('lte_uint_eq', () => { 1089 | const expr = "1u <= 1u" 1090 | const expected = { 1091 | bool_value: true 1092 | } 1093 | 1094 | const cel = genCel(expr) 1095 | expect(cel).toStrictEqual(expected); 1096 | }) 1097 | 1098 | test('not_lte_uint_gt', () => { 1099 | const expr = "1u <= 0u" 1100 | const expected = { 1101 | bool_value: false 1102 | } 1103 | 1104 | const cel = genCel(expr) 1105 | expect(cel).toStrictEqual(expected); 1106 | }) 1107 | 1108 | test('lte_double_lt', () => { 1109 | const expr = "0.0 <= 0.1e-31" 1110 | const expected = { 1111 | bool_value: true 1112 | } 1113 | 1114 | const cel = genCel(expr) 1115 | expect(cel).toStrictEqual(expected); 1116 | }) 1117 | 1118 | test('lte_double_eq', () => { 1119 | const expr = "0.0 <= 0e-1" 1120 | const expected = { 1121 | bool_value: true 1122 | } 1123 | 1124 | const cel = genCel(expr) 1125 | expect(cel).toStrictEqual(expected); 1126 | }) 1127 | 1128 | test('not_lte_double_gt', () => { 1129 | const expr = "1.0 <= 0.99" 1130 | const expected = { 1131 | bool_value: false 1132 | } 1133 | 1134 | const cel = genCel(expr) 1135 | expect(cel).toStrictEqual(expected); 1136 | }) 1137 | 1138 | test('lte_string_empty', () => { 1139 | const expr = "'' <= ''" 1140 | const expected = { 1141 | bool_value: true 1142 | } 1143 | 1144 | const cel = genCel(expr) 1145 | expect(cel).toStrictEqual(expected); 1146 | }) 1147 | 1148 | test('lte_string_from_empty', () => { 1149 | const expr = "'' <= 'a'" 1150 | const expected = { 1151 | bool_value: true 1152 | } 1153 | 1154 | const cel = genCel(expr) 1155 | expect(cel).toStrictEqual(expected); 1156 | }) 1157 | 1158 | test('not_lte_string_to_empty', () => { 1159 | const expr = "'a' <= ''" 1160 | const expected = { 1161 | bool_value: false 1162 | } 1163 | 1164 | const cel = genCel(expr) 1165 | expect(cel).toStrictEqual(expected); 1166 | }) 1167 | 1168 | test('lte_string_lexicographical', () => { 1169 | const expr = "'aBc' <= 'abc'" 1170 | const expected = { 1171 | bool_value: true 1172 | } 1173 | 1174 | const cel = genCel(expr) 1175 | expect(cel).toStrictEqual(expected); 1176 | }) 1177 | 1178 | test('lte_string_unicode_eq', () => { 1179 | const expr = "'α' <= 'α'" 1180 | const expected = { 1181 | bool_value: true 1182 | } 1183 | 1184 | const cel = genCel(expr) 1185 | expect(cel).toStrictEqual(expected); 1186 | }) 1187 | 1188 | test('lte_string_unicode_lt', () => { 1189 | const expr = "'a' <= 'α'" 1190 | const expected = { 1191 | bool_value: true 1192 | } 1193 | 1194 | const cel = genCel(expr) 1195 | expect(cel).toStrictEqual(expected); 1196 | }) 1197 | 1198 | test('not_lte_string_unicode', () => { 1199 | const expr = "'α' <= 'a'" 1200 | const expected = { 1201 | bool_value: false 1202 | } 1203 | 1204 | const cel = genCel(expr) 1205 | expect(cel).toStrictEqual(expected); 1206 | }) 1207 | 1208 | test('lte_bytes_empty', () => { 1209 | const expr = "b'' <= b'\x00'" 1210 | const expected = { 1211 | bool_value: true 1212 | } 1213 | 1214 | const cel = genCel(expr) 1215 | expect(cel).toStrictEqual(expected); 1216 | }) 1217 | 1218 | test('not_lte_bytes_length', () => { 1219 | const expr = "b'\x01\x00' <= b'\x01'" 1220 | const expected = { 1221 | bool_value: false 1222 | } 1223 | 1224 | const cel = genCel(expr) 1225 | expect(cel).toStrictEqual(expected); 1226 | }) 1227 | 1228 | test('lte_bool_false_true', () => { 1229 | const expr = "false <= true" 1230 | const expected = { 1231 | bool_value: true 1232 | } 1233 | 1234 | const cel = genCel(expr) 1235 | expect(cel).toStrictEqual(expected); 1236 | }) 1237 | 1238 | test('lte_bool_false_false', () => { 1239 | const expr = "false <= false" 1240 | const expected = { 1241 | bool_value: true 1242 | } 1243 | 1244 | const cel = genCel(expr) 1245 | expect(cel).toStrictEqual(expected); 1246 | }) 1247 | 1248 | test('lte_bool_true_false', () => { 1249 | const expr = "true <= false" 1250 | const expected = { 1251 | bool_value: false 1252 | } 1253 | 1254 | const cel = genCel(expr) 1255 | expect(cel).toStrictEqual(expected); 1256 | }) 1257 | 1258 | test('lte_null_unsupported', () => { 1259 | const expr = "null <= null" 1260 | try { 1261 | const cel = genCel(expr) 1262 | expect(true).toBe(false) 1263 | } catch (e) { 1264 | expect(e.message).toBe('{ message: "no such overload" }') 1265 | } 1266 | }) 1267 | 1268 | test('lte_list_unsupported', () => { 1269 | const expr = "[0] <= [1]" 1270 | try { 1271 | const cel = genCel(expr) 1272 | expect(true).toBe(false) 1273 | } catch (e) { 1274 | expect(e.message).toBe('{ message: "no such overload" }') 1275 | } 1276 | }) 1277 | 1278 | test('lte_map_unsupported', () => { 1279 | const expr = "{0:'a'} <= {1:'b'}" 1280 | try { 1281 | const cel = genCel(expr) 1282 | expect(true).toBe(false) 1283 | } catch (e) { 1284 | expect(e.message).toBe('{ message: "no such overload" }') 1285 | } 1286 | }) 1287 | 1288 | test('lte_mixed_types_error', () => { 1289 | const expr = "'foo' <= 1024" 1290 | try { 1291 | const cel = genCel(expr) 1292 | expect(true).toBe(false) 1293 | } catch (e) { 1294 | expect(e.message).toBe('{ message: "no such overload" }') 1295 | } 1296 | }) 1297 | }) 1298 | 1299 | describe('comparisons.gte_literal', () => { 1300 | test('gte_int_gt', () => { 1301 | const expr = "0 >= -1" 1302 | const expected = { 1303 | bool_value: true 1304 | } 1305 | 1306 | const cel = genCel(expr) 1307 | expect(cel).toStrictEqual(expected); 1308 | }) 1309 | 1310 | test('gte_int_eq', () => { 1311 | const expr = "999 >= 999" 1312 | const expected = { 1313 | bool_value: true 1314 | } 1315 | 1316 | const cel = genCel(expr) 1317 | expect(cel).toStrictEqual(expected); 1318 | }) 1319 | 1320 | test('not_gte_int_lt', () => { 1321 | const expr = "999 >= 1000" 1322 | const expected = { 1323 | bool_value: false 1324 | } 1325 | 1326 | const cel = genCel(expr) 1327 | expect(cel).toStrictEqual(expected); 1328 | }) 1329 | 1330 | test('gte_uint_gt', () => { 1331 | const expr = "1u >= 0u" 1332 | const expected = { 1333 | bool_value: true 1334 | } 1335 | 1336 | const cel = genCel(expr) 1337 | expect(cel).toStrictEqual(expected); 1338 | }) 1339 | 1340 | test('gte_uint_eq', () => { 1341 | const expr = "0u >= 0u" 1342 | const expected = { 1343 | bool_value: true 1344 | } 1345 | 1346 | const cel = genCel(expr) 1347 | expect(cel).toStrictEqual(expected); 1348 | }) 1349 | 1350 | test('not_gte_uint_lt', () => { 1351 | const expr = "1u >= 10u" 1352 | const expected = { 1353 | bool_value: false 1354 | } 1355 | 1356 | const cel = genCel(expr) 1357 | expect(cel).toStrictEqual(expected); 1358 | }) 1359 | 1360 | test('gte_double_gt', () => { 1361 | const expr = "1e+1 >= 1e+0" 1362 | const expected = { 1363 | bool_value: true 1364 | } 1365 | 1366 | const cel = genCel(expr) 1367 | expect(cel).toStrictEqual(expected); 1368 | }) 1369 | 1370 | test('gte_double_eq', () => { 1371 | const expr = "9.80665 >= 9.80665e+0" 1372 | const expected = { 1373 | bool_value: true 1374 | } 1375 | 1376 | const cel = genCel(expr) 1377 | expect(cel).toStrictEqual(expected); 1378 | }) 1379 | 1380 | test('not_gte_double_lt', () => { 1381 | const expr = "0.9999 >= 1.0" 1382 | const expected = { 1383 | bool_value: false 1384 | } 1385 | 1386 | const cel = genCel(expr) 1387 | expect(cel).toStrictEqual(expected); 1388 | }) 1389 | 1390 | test('gte_string_empty', () => { 1391 | const expr = "'' >= ''" 1392 | const expected = { 1393 | bool_value: true 1394 | } 1395 | 1396 | const cel = genCel(expr) 1397 | expect(cel).toStrictEqual(expected); 1398 | }) 1399 | 1400 | test('gte_string_to_empty', () => { 1401 | const expr = "'a' >= ''" 1402 | const expected = { 1403 | bool_value: true 1404 | } 1405 | 1406 | const cel = genCel(expr) 1407 | expect(cel).toStrictEqual(expected); 1408 | }) 1409 | 1410 | test('gte_string_empty_to_nonempty', () => { 1411 | const expr = "'' >= 'a'" 1412 | const expected = { 1413 | bool_value: false 1414 | } 1415 | 1416 | const cel = genCel(expr) 1417 | expect(cel).toStrictEqual(expected); 1418 | }) 1419 | 1420 | test('gte_string_length', () => { 1421 | const expr = "'abcd' >= 'abc'" 1422 | const expected = { 1423 | bool_value: true 1424 | } 1425 | 1426 | const cel = genCel(expr) 1427 | expect(cel).toStrictEqual(expected); 1428 | }) 1429 | 1430 | test('not_gte_string_lexicographical', () => { 1431 | const expr = "'abc' >= 'abd'" 1432 | const expected = { 1433 | bool_value: false 1434 | } 1435 | 1436 | const cel = genCel(expr) 1437 | expect(cel).toStrictEqual(expected); 1438 | }) 1439 | 1440 | test('gte_string_unicode_eq', () => { 1441 | const expr = "'τ' >= 'τ'" 1442 | const expected = { 1443 | bool_value: true 1444 | } 1445 | 1446 | const cel = genCel(expr) 1447 | expect(cel).toStrictEqual(expected); 1448 | }) 1449 | 1450 | test('gte_string_unicode_gt', () => { 1451 | const expr = "'τ' >= 't'" 1452 | const expected = { 1453 | bool_value: true 1454 | } 1455 | 1456 | const cel = genCel(expr) 1457 | expect(cel).toStrictEqual(expected); 1458 | }) 1459 | 1460 | test('not_get_string_unicode', () => { 1461 | const expr = "'t' >= 'τ'" 1462 | const expected = { 1463 | bool_value: false 1464 | } 1465 | 1466 | const cel = genCel(expr) 1467 | expect(cel).toStrictEqual(expected); 1468 | }) 1469 | 1470 | test('gte_bytes_to_empty', () => { 1471 | const expr = "b'\x00' >= b''" 1472 | const expected = { 1473 | bool_value: true 1474 | } 1475 | 1476 | const cel = genCel(expr) 1477 | expect(cel).toStrictEqual(expected); 1478 | }) 1479 | 1480 | test('not_gte_bytes_empty_to_nonempty', () => { 1481 | const expr = "b'' >= b'\x00'" 1482 | const expected = { 1483 | bool_value: false 1484 | } 1485 | 1486 | const cel = genCel(expr) 1487 | expect(cel).toStrictEqual(expected); 1488 | }) 1489 | 1490 | test('gte_bytes_samelength', () => { 1491 | const expr = "b'\x00\x01' >= b'\x01\x00'" 1492 | const expected = { 1493 | bool_value: false 1494 | } 1495 | 1496 | const cel = genCel(expr) 1497 | expect(cel).toStrictEqual(expected); 1498 | }) 1499 | 1500 | test('gte_bool_gt', () => { 1501 | const expr = "true >= false" 1502 | const expected = { 1503 | bool_value: true 1504 | } 1505 | 1506 | const cel = genCel(expr) 1507 | expect(cel).toStrictEqual(expected); 1508 | }) 1509 | 1510 | test('gte_bool_eq', () => { 1511 | const expr = "true >= true" 1512 | const expected = { 1513 | bool_value: true 1514 | } 1515 | 1516 | const cel = genCel(expr) 1517 | expect(cel).toStrictEqual(expected); 1518 | }) 1519 | 1520 | test('not_gte_bool_lt', () => { 1521 | const expr = "false >= true" 1522 | const expected = { 1523 | bool_value: false 1524 | } 1525 | 1526 | const cel = genCel(expr) 1527 | expect(cel).toStrictEqual(expected); 1528 | }) 1529 | 1530 | test('gte_null_unsupported', () => { 1531 | const expr = "null >= null" 1532 | try { 1533 | const cel = genCel(expr) 1534 | expect(true).toBe(false) 1535 | } catch (e) { 1536 | expect(e.message).toBe('{ message: "no such overload" }') 1537 | } 1538 | }) 1539 | 1540 | test('gte_list_unsupported', () => { 1541 | const expr = "[0] >= [1]" 1542 | try { 1543 | const cel = genCel(expr) 1544 | expect(true).toBe(false) 1545 | } catch (e) { 1546 | expect(e.message).toBe('{ message: "no such overload" }') 1547 | } 1548 | }) 1549 | 1550 | test('gte_map_unsupported', () => { 1551 | const expr = "{0:'a'} >= {1:'b'}" 1552 | try { 1553 | const cel = genCel(expr) 1554 | expect(true).toBe(false) 1555 | } catch (e) { 1556 | expect(e.message).toBe('{ message: "no such overload" }') 1557 | } 1558 | }) 1559 | 1560 | test('gte_mixed_types_error', () => { 1561 | const expr = "'foo' >= 1024" 1562 | try { 1563 | const cel = genCel(expr) 1564 | expect(true).toBe(false) 1565 | } catch (e) { 1566 | expect(e.message).toBe('{ message: "no such overload" }') 1567 | } 1568 | }) 1569 | }) 1570 | 1571 | describe('comparisons.in_list_literal', () => { 1572 | it('elem_not_in_empty_list', () => { 1573 | const expr = "'empty' in []" 1574 | const expected = { 1575 | bool_value: false 1576 | } 1577 | 1578 | const cel = genCel(expr) 1579 | expect(cel).toStrictEqual(expected); 1580 | }) 1581 | 1582 | it('elem_in_list', () => { 1583 | const expr = "'elem' in ['elem', 'elemA', 'elemB']" 1584 | const expected = { 1585 | bool_value: true 1586 | } 1587 | 1588 | const cel = genCel(expr) 1589 | expect(cel).toStrictEqual(expected); 1590 | }) 1591 | 1592 | it('elem_not_in_list', () => { 1593 | const expr = "'not' in ['elem1', 'elem2', 'elem3']" 1594 | const expected = { 1595 | bool_value: false 1596 | } 1597 | 1598 | const cel = genCel(expr) 1599 | expect(cel).toStrictEqual(expected); 1600 | }) 1601 | 1602 | it('elem_in_mixed_type_list', () => { 1603 | // Set membership tests should succeed if the 'elem' exists in a mixed 1604 | // element type list. 1605 | const expr = "'elem' in [1, 'elem', 2]" 1606 | const expected = { 1607 | bool_value: true 1608 | } 1609 | 1610 | const cel = genCel(expr) 1611 | expect(cel).toStrictEqual(expected); 1612 | }) 1613 | 1614 | // Skip this test as JS is fine with the element not being present 1615 | it.skip('elem_in_mixed_type_list_error', () => { 1616 | // Set membership tests should error if the 'elem' does not exist in a 1617 | // mixed element type list as containment is equivalent to the macro 1618 | // exists() behavior. 1619 | const expr = "'elem' in [1u, 'str', 2, b'bytes']" 1620 | try { 1621 | const cel = genCel(expr) 1622 | expect(true).toBe(false) 1623 | } catch (e) { 1624 | expect(e.message).toBe('{ message: "no such overload" }') 1625 | } 1626 | }) 1627 | }) 1628 | 1629 | describe('comparisons.in_map_literal', () => { 1630 | it('key_not_in_empty_map', () => { 1631 | const expr = "'empty' in {}" 1632 | const expected = { 1633 | bool_value: false 1634 | } 1635 | 1636 | const cel = genCel(expr) 1637 | expect(cel).toStrictEqual(expected); 1638 | }) 1639 | 1640 | it('key_in_map', () => { 1641 | const expr = "'key' in {'key':'1', 'other':'2'}" 1642 | const expected = { 1643 | bool_value: true 1644 | } 1645 | 1646 | const cel = genCel(expr) 1647 | expect(cel).toStrictEqual(expected); 1648 | }) 1649 | 1650 | it('key_not_in_map', () => { 1651 | const expr = "'key' in {'lock':1, 'gate':2}" 1652 | const expected = { 1653 | bool_value: false 1654 | } 1655 | 1656 | const cel = genCel(expr) 1657 | expect(cel).toStrictEqual(expected); 1658 | }) 1659 | 1660 | it('key_in_mixed_key_type_map', () => { 1661 | const expr = "'key' in {3:3.0, 'key':2u}" 1662 | const expected = { 1663 | bool_value: true 1664 | } 1665 | 1666 | const cel = genCel(expr) 1667 | expect(cel).toStrictEqual(expected); 1668 | }) 1669 | 1670 | it.skip('key_in_mixed_key_type_map_error', () => { 1671 | const expr = "'key' in {1u:'str', 2:b'bytes'}" 1672 | try { 1673 | const cel = genCel(expr) 1674 | expect(true).toBe(false) 1675 | } catch (e) { 1676 | expect(e.message).toBe('{ message: "no such overload" }') 1677 | } 1678 | }) 1679 | }) 1680 | 1681 | describe('comparisons.bound', () => { 1682 | it('bytes_gt_left_false', () => { 1683 | const expr = "x > b'\x30'" 1684 | const bindings = { 1685 | x: "\x30" 1686 | } 1687 | const expected = { 1688 | bool_value: false 1689 | } 1690 | 1691 | const cel = genCel(expr, bindings) 1692 | expect(cel).toStrictEqual(expected); 1693 | }) 1694 | 1695 | it('int_lte_right_true', () => { 1696 | const expr = "123 <= x" 1697 | const bindings = { 1698 | x: 124 1699 | } 1700 | const expected = { 1701 | bool_value: true 1702 | } 1703 | 1704 | const cel = genCel(expr, bindings) 1705 | expect(cel).toStrictEqual(expected); 1706 | }) 1707 | 1708 | it('bool_lt_right_true', () => { 1709 | const expr = "false < x" 1710 | const bindings = { 1711 | x: true 1712 | } 1713 | const expected = { 1714 | bool_value: true 1715 | } 1716 | 1717 | const cel = genCel(expr, bindings) 1718 | expect(cel).toStrictEqual(expected); 1719 | }) 1720 | 1721 | it('double_ne_left_false', () => { 1722 | const expr = "x != 9.8" 1723 | const bindings = { 1724 | x: 9.8 1725 | } 1726 | const expected = { 1727 | bool_value: false 1728 | } 1729 | 1730 | const cel = genCel(expr, bindings) 1731 | expect(cel).toStrictEqual(expected); 1732 | }) 1733 | 1734 | it('map_ne_right_false', () => { 1735 | const expr = "{'a':'b','c':'d'} != x" 1736 | const bindings = { 1737 | x: { 1738 | a: 'b', 1739 | c: 'd', 1740 | } 1741 | } 1742 | const expected = { 1743 | bool_value: false 1744 | } 1745 | 1746 | const cel = genCel(expr, bindings) 1747 | expect(cel).toStrictEqual(expected); 1748 | }) 1749 | 1750 | it('null_eq_left_true', () => { 1751 | const expr = "x == null" 1752 | const bindings = { 1753 | x: null 1754 | } 1755 | const expected = { 1756 | bool_value: true 1757 | } 1758 | 1759 | const cel = genCel(expr, bindings) 1760 | expect(cel).toStrictEqual(expected); 1761 | }) 1762 | 1763 | it('list_eq_right_false', () => { 1764 | const expr = "[1, 2] == x" 1765 | const bindings = { 1766 | x: [2, 1] 1767 | } 1768 | const expected = { 1769 | bool_value: false 1770 | } 1771 | 1772 | const cel = genCel(expr, bindings) 1773 | expect(cel).toStrictEqual(expected); 1774 | }) 1775 | 1776 | it('string_gte_right_true', () => { 1777 | const expr = "'abcd' >= x" 1778 | const bindings = { 1779 | x: 'abc' 1780 | } 1781 | const expected = { 1782 | bool_value: true 1783 | } 1784 | 1785 | const cel = genCel(expr, bindings) 1786 | expect(cel).toStrictEqual(expected); 1787 | }) 1788 | 1789 | it('uint_eq_right_false', () => { 1790 | const expr = "999u == x" 1791 | const bindings = { 1792 | x: 1000 1793 | } 1794 | const expected = { 1795 | bool_value: false 1796 | } 1797 | 1798 | const cel = genCel(expr, bindings) 1799 | expect(cel).toStrictEqual(expected); 1800 | }) 1801 | 1802 | it('null_lt_right_no_such_overload', () => { 1803 | // There is no _<_ operation for null, 1804 | // even if both operands are null 1805 | const expr = "null < x" 1806 | const bindings = { 1807 | x: null 1808 | } 1809 | try { 1810 | genCel(expr, bindings) 1811 | expect(true).toBe(false) 1812 | } catch (e) { 1813 | expect(e.message).toBe('{ message: "no such overload" }') 1814 | } 1815 | }) 1816 | }) 1817 | -------------------------------------------------------------------------------- /tests/custom.spec.ts: -------------------------------------------------------------------------------- 1 | import { CelSpec } from '../src/CelSpec'; 2 | import { TextFormatter } from '../src/formatters/TextFormatter'; 3 | import { NULL_VALUE } from '../src'; 4 | 5 | const genCel = (expr: string, bindings?: any, debug?: boolean) => { 6 | const speech = new CelSpec(); 7 | const ast = speech.toAST(expr, {}); 8 | if (debug) console.log(expr, ast) 9 | if (debug) console.log(ast.children) 10 | if (debug) console.log(ast.children[0].children) 11 | const bindingsAst = (() => { 12 | if (!bindings) return {} 13 | const tf = new TextFormatter({}, bindings) 14 | let res = {} 15 | for (const [key, entry] of Object.entries(bindings)) { 16 | if (debug) console.log('res', res) 17 | if (debug) console.log('entry', key, entry, 'of', bindings) 18 | const entryAst = speech.toAST(JSON.stringify(entry)) 19 | if (debug) console.log('eAST', entryAst) 20 | const entryCel = tf.format(entryAst) 21 | res[key] = entryCel 22 | if (debug) console.log('res2', res) 23 | } 24 | if (debug) console.log('res-end', res) 25 | return res 26 | })() 27 | if (debug) console.log(bindings, bindingsAst) 28 | 29 | const tf = new TextFormatter({}, bindingsAst) 30 | return tf.format(ast) 31 | } 32 | 33 | describe('custom.box', () => { 34 | test('grass_only', () => { 35 | const expr = "'Grass' in boxTypes1" 36 | const bindings = { 37 | boxTypes1: ['Grass', 'Water'] 38 | } 39 | 40 | const expected = { 41 | bool_value: true 42 | } 43 | 44 | const cel = genCel(expr, bindings) 45 | expect(cel).toStrictEqual(expected); 46 | }) 47 | 48 | test('poison_only', () => { 49 | const expr = "'Poison' in boxTypes1" 50 | const bindings = { 51 | boxTypes1: ['Grass', 'Water'] 52 | } 53 | 54 | const expected = { 55 | bool_value: false 56 | } 57 | 58 | const cel = genCel(expr, bindings) 59 | expect(cel).toStrictEqual(expected); 60 | }) 61 | 62 | test('must_be_shiny', () => { 63 | const expr = 'shiny' 64 | const bindings = { 65 | shiny: false 66 | } 67 | 68 | const expected = { 69 | bool_value: false 70 | } 71 | 72 | const cel = genCel(expr, bindings) 73 | expect(cel).toStrictEqual(expected); 74 | }) 75 | 76 | test('non_shiny', () => { 77 | const expr = '!shiny' 78 | const bindings = { 79 | shiny: false 80 | } 81 | 82 | const expected = { 83 | bool_value: true 84 | } 85 | 86 | const cel = genCel(expr, bindings) 87 | expect(cel).toStrictEqual(expected); 88 | }) 89 | 90 | test('var0', () => { 91 | const expr = 'var == 0' 92 | const bindings = { 93 | id: 12, 94 | var: 0, 95 | species: 'Butterfree', 96 | type1: 'Bug', 97 | type2: 'Flying' 98 | } 99 | const expected = { 100 | bool_value: true 101 | } 102 | 103 | const cel = genCel(expr, bindings) 104 | expect(cel).toStrictEqual(expected); 105 | }) 106 | 107 | test('Not var0', () => { 108 | const expr = 'var == 0' 109 | const bindings = { 110 | id: 12, 111 | var: null, 112 | species: 'Butterfree', 113 | type1: 'Bug', 114 | type2: 'Flying' 115 | } 116 | const expected = { 117 | bool_value: false 118 | } 119 | 120 | const cel = genCel(expr, bindings) 121 | expect(cel).toStrictEqual(expected); 122 | }) 123 | }) 124 | 125 | describe('custom.gts', () => { 126 | test('One Unown', () => { 127 | const expr = 'id == 201' 128 | const bindings = { 129 | id: 201, 130 | form: 'F', 131 | species: 'Unown', 132 | type1: 'Psychic' 133 | } 134 | const expected = { 135 | bool_value: true 136 | } 137 | 138 | const cel = genCel(expr, bindings) 139 | expect(cel).toStrictEqual(expected); 140 | }) 141 | 142 | test('Press F to pay Unown', () => { 143 | const expr = 'id == 201 && form == "F"' 144 | const bindings = { 145 | id: 201, 146 | form: 'F', 147 | species: 'Unown', 148 | type1: 'Psychic' 149 | } 150 | const expected = { 151 | bool_value: true 152 | } 153 | 154 | const cel = genCel(expr, bindings) 155 | expect(cel).toStrictEqual(expected); 156 | 157 | const expr2 = 'id == 201 && form == "F"' 158 | const bindings2 = { 159 | id: 201, 160 | form: 'G', 161 | species: 'Unown', 162 | type1: 'Psychic' 163 | } 164 | const expected2 = { 165 | bool_value: false 166 | } 167 | 168 | const cel2 = genCel(expr2, bindings2) 169 | expect(cel2).toStrictEqual(expected2); 170 | }) 171 | 172 | test('Unown', () => { 173 | const expr = '201 in offerIds' 174 | const bindings = { 175 | offerIds: [6, 201, 201, 8] 176 | } 177 | const expected = { 178 | bool_value: true 179 | } 180 | 181 | const cel = genCel(expr, bindings) 182 | expect(cel).toStrictEqual(expected); 183 | }) 184 | 185 | test('Punctuation - Mr. Mime', () => { 186 | const expr = `'Mr. Mime'` 187 | const expected = { 188 | string_value: 'Mr. Mime' 189 | } 190 | const cel = genCel(expr, {}) 191 | expect(cel).toStrictEqual(expected); 192 | }) 193 | 194 | test('Punctuation - Farfetch\'d', () => { 195 | const expr = `"Farfetch'd"` 196 | const expected = { 197 | string_value: 'Farfetch\'d' 198 | } 199 | const cel = genCel(expr, {}) 200 | expect(cel).toStrictEqual(expected); 201 | }) 202 | }) 203 | -------------------------------------------------------------------------------- /tests/lists.spec.ts: -------------------------------------------------------------------------------- 1 | import { CelSpec } from '../src/CelSpec'; 2 | import { TextFormatter } from '../src/formatters/TextFormatter'; 3 | import { NULL_VALUE } from '../src'; 4 | 5 | const genCel = (expr: string, bindings?: any, debug?: boolean) => { 6 | const speech = new CelSpec(); 7 | const ast = speech.toAST(expr, {}); 8 | if (debug) console.log(expr, ast) 9 | if (debug) console.log(ast.children) 10 | if (debug) console.log(ast.children.map(child => child.children)) 11 | const bindingsAst = (() => { 12 | if (!bindings) return {} 13 | const tf = new TextFormatter({}, bindings) 14 | let res = {} 15 | for (const [key, entry] of Object.entries(bindings)) { 16 | if (debug) console.log('res', res) 17 | if (debug) console.log('entry', key, entry, 'of', bindings) 18 | const entryAst = speech.toAST(JSON.stringify(entry)) 19 | if (debug) console.log('eAST', entryAst) 20 | const entryCel = tf.format(entryAst) 21 | res[key] = entryCel 22 | if (debug) console.log('res2', res) 23 | } 24 | if (debug) console.log('res-end', res) 25 | return res 26 | })() 27 | if (debug) console.log(bindings, bindingsAst) 28 | 29 | const tf = new TextFormatter({}, bindingsAst) 30 | return tf.format(ast) 31 | } 32 | 33 | describe('lists.concatentation', () => { 34 | test('list_append', () => { 35 | const expr = "[0, 1, 2] + [3, 4, 5] == [0, 1, 2, 3, 4, 5]" 36 | const expected = { bool_value: true } 37 | 38 | const cel = genCel(expr) 39 | expect(cel).toStrictEqual(expected); 40 | }) 41 | 42 | test('list_not_commutative', () => { 43 | const expr = "[0, 1, 2] + [3, 4, 5] == [3, 4, 5, 0, 1, 2]" 44 | const expected = { bool_value: false } 45 | 46 | const cel = genCel(expr) 47 | expect(cel).toStrictEqual(expected); 48 | }) 49 | 50 | test('list_repeat', () => { 51 | const expr = "[2] + [2]" 52 | const expected = { 53 | list_value: { 54 | values: [{ 55 | int64_value: 2 56 | }, { 57 | int64_value: 2 58 | }] 59 | } 60 | } 61 | 62 | const cel = genCel(expr) 63 | expect(cel).toStrictEqual(expected); 64 | }) 65 | 66 | test('empty_empty', () => { 67 | const expr = "[] + []" 68 | const expected = { list_value: {} } 69 | 70 | const cel = genCel(expr) 71 | expect(cel).toStrictEqual(expected); 72 | }) 73 | 74 | test('left_unit', () => { 75 | const expr = "[] + [3, 4]" 76 | const expected = { 77 | list_value: { 78 | values: [{ 79 | int64_value: 3 80 | }, { 81 | int64_value: 4 82 | }] 83 | } 84 | } 85 | 86 | const cel = genCel(expr) 87 | expect(cel).toStrictEqual(expected); 88 | }) 89 | 90 | test('right_unit', () => { 91 | const expr = "[1, 2] + []" 92 | const expected = { 93 | list_value: { 94 | values: [{ 95 | int64_value: 1 96 | }, { 97 | int64_value: 2 98 | }] 99 | } 100 | } 101 | 102 | const cel = genCel(expr) 103 | expect(cel).toStrictEqual(expected); 104 | }) 105 | }) 106 | 107 | describe('lists.index', () => { 108 | test('zero_based', () => { 109 | const expr = "[7, 8, 9][0]" 110 | const expected = { int64_value: 7 } 111 | 112 | const cel = genCel(expr) 113 | expect(cel).toStrictEqual(expected); 114 | }) 115 | 116 | test('singleton', () => { 117 | const expr = "[0, 1, 1, 2, 3, 5, 8, 13][4]" 118 | const expected = { int64_value: 3 } 119 | 120 | const cel = genCel(expr) 121 | expect(cel).toStrictEqual(expected); 122 | }) 123 | 124 | test('last', () => { 125 | const expr = "['George', 'John', 'Paul', 'Ringo'][3]" 126 | const expected = { string_value: 'Ringo' } 127 | 128 | const cel = genCel(expr) 129 | expect(cel).toStrictEqual(expected); 130 | }) 131 | 132 | test('range', () => { 133 | const expr = "[1, 2, 3][3]" 134 | try { 135 | genCel(expr) 136 | expect(true).toBe(false) // Should fail 137 | } catch (e) { 138 | expect(e.message).toBe('{ message: "invalid_argument" }') 139 | } 140 | }) 141 | }) 142 | 143 | describe('lists.in', () => { 144 | test('empty', () => { 145 | const expr = "7 in []" 146 | const expected = { 147 | bool_value: false 148 | } 149 | 150 | const cel = genCel(expr) 151 | expect(cel).toStrictEqual(expected); 152 | }) 153 | 154 | test('singleton', () => { 155 | const expr = "4u in [4u]" 156 | const expected = { bool_value: true } 157 | 158 | const cel = genCel(expr) 159 | expect(cel).toStrictEqual(expected); 160 | }) 161 | 162 | test('first', () => { 163 | const expr = "'alpha' in ['alpha', 'beta', 'gamma']" 164 | const expected = { bool_value: true } 165 | 166 | const cel = genCel(expr) 167 | expect(cel).toStrictEqual(expected); 168 | }) 169 | 170 | test('middle', () => { 171 | const expr = "3 in [5, 4, 3, 2, 1]" 172 | const expected = { bool_value: true } 173 | 174 | const cel = genCel(expr) 175 | expect(cel).toStrictEqual(expected); 176 | }) 177 | 178 | test('last', () => { 179 | const expr = "20u in [4u, 6u, 8u, 12u, 20u]" 180 | const expected = { bool_value: true } 181 | 182 | const cel = genCel(expr) 183 | expect(cel).toStrictEqual(expected); 184 | }) 185 | 186 | test('missing', () => { 187 | const expr = "'hawaiian' in ['meat', 'veggie', 'margarita', 'cheese']" 188 | const expected = { bool_value: false } 189 | 190 | const cel = genCel(expr) 191 | expect(cel).toStrictEqual(expected); 192 | }) 193 | }) 194 | 195 | describe('lists.size', () => { 196 | test('list_empty', () => { 197 | const expr = "size([])" 198 | const expected = { int64_value: 0 } 199 | 200 | const cel = genCel(expr) 201 | expect(cel).toStrictEqual(expected); 202 | }) 203 | 204 | test('list', () => { 205 | const expr = "size([1, 2, 3])" 206 | const expected = { int64_value: 3 } 207 | 208 | const cel = genCel(expr) 209 | expect(cel).toStrictEqual(expected); 210 | }) 211 | 212 | test('map_empty', () => { 213 | const expr = "size({})" 214 | const expected = { int64_value: 0 } 215 | 216 | const cel = genCel(expr) 217 | expect(cel).toStrictEqual(expected); 218 | }) 219 | 220 | test('map', () => { 221 | const expr = "size({1: 'one', 2: 'two', 3: 'three'})" 222 | const expected = { int64_value: 3 } 223 | 224 | const cel = genCel(expr) 225 | expect(cel).toStrictEqual(expected); 226 | }) 227 | }) 228 | -------------------------------------------------------------------------------- /tests/logic.spec.ts: -------------------------------------------------------------------------------- 1 | import { CelSpec } from '../src/CelSpec'; 2 | import { TextFormatter } from '../src/formatters/TextFormatter'; 3 | import { NULL_VALUE } from '../src'; 4 | 5 | const genCel = (expr: string, bindings?: any, debug?: boolean) => { 6 | const speech = new CelSpec(); 7 | const ast = speech.toAST(expr, {}); 8 | if (debug) console.log(expr, ast) 9 | if (debug) console.log(ast.children) 10 | if (debug) console.log(ast.children.map(child => child.children)) 11 | const bindingsAst = (() => { 12 | if (!bindings) return {} 13 | const tf = new TextFormatter({}, bindings) 14 | let res = {} 15 | for (const [key, entry] of Object.entries(bindings)) { 16 | if (debug) console.log('res', res) 17 | if (debug) console.log('entry', key, entry, 'of', bindings) 18 | const entryAst = speech.toAST(JSON.stringify(entry)) 19 | if (debug) console.log('eAST', entryAst) 20 | const entryCel = tf.format(entryAst) 21 | res[key] = entryCel 22 | if (debug) console.log('res2', res) 23 | } 24 | if (debug) console.log('res-end', res) 25 | return res 26 | })() 27 | if (debug) console.log(bindings, bindingsAst) 28 | 29 | const tf = new TextFormatter({}, bindingsAst) 30 | return tf.format(ast) 31 | } 32 | 33 | describe('logic.conditional', () => { 34 | test('true_case', () => { 35 | const expr = "true ? 1 : 2" 36 | const expected = { int64_value: 1 } 37 | 38 | const cel = genCel(expr) 39 | expect(cel).toStrictEqual(expected); 40 | }) 41 | 42 | test('false_case', () => { 43 | const expr = "false ? 'foo' : 'bar'" 44 | const expected = { string_value: 'bar' } 45 | 46 | const cel = genCel(expr) 47 | expect(cel).toStrictEqual(expected); 48 | }) 49 | 50 | // Should throw error `{ message: "division by zero" }` 51 | // but parser does not support mathematical operations right now 52 | test.skip('error_case', () => { 53 | const expr = "2 / 0 > 4 ? 'baz' : 'quux'" 54 | }) 55 | 56 | test('mixed_type', () => { 57 | const expr = "true ? 'cows' : 17" 58 | const expected = { string_value: 'cows' } 59 | 60 | const cel = genCel(expr) 61 | expect(cel).toStrictEqual(expected); 62 | }) 63 | 64 | test('bad_type', () => { 65 | const expr = "'cows' ? false : 17" 66 | try { 67 | genCel(expr) 68 | expect(true).toBe(false) // Should fail 69 | } catch(e) { 70 | expect(e.message).toBe(`{ message: "no such overload" }`) 71 | } 72 | }) 73 | }) 74 | 75 | describe('logic.AND', () => { 76 | test('all_true', () => { 77 | const expr = "true && true" 78 | const expected = { bool_value: true } 79 | 80 | const cel = genCel(expr) 81 | expect(cel).toStrictEqual(expected); 82 | }) 83 | 84 | test('all_false', () => { 85 | const expr = "false && false" 86 | const expected = { bool_value: false } 87 | 88 | const cel = genCel(expr) 89 | expect(cel).toStrictEqual(expected); 90 | }) 91 | 92 | test('false_left', () => { 93 | const expr = "false && true" 94 | const expected = { bool_value: false } 95 | 96 | const cel = genCel(expr) 97 | expect(cel).toStrictEqual(expected); 98 | }) 99 | 100 | test('false_right', () => { 101 | const expr = "true && false" 102 | const expected = { bool_value: false } 103 | 104 | const cel = genCel(expr) 105 | expect(cel).toStrictEqual(expected); 106 | }) 107 | 108 | test('short_circuit_type_left', () => { 109 | const expr = "false && 32" 110 | const expected = { bool_value: false } 111 | 112 | const cel = genCel(expr) 113 | expect(cel).toStrictEqual(expected); 114 | }) 115 | 116 | test('short_circuit_type_right', () => { 117 | const expr = "'horses' && false" 118 | const expected = { bool_value: false } 119 | 120 | const cel = genCel(expr) 121 | expect(cel).toStrictEqual(expected); 122 | }) 123 | 124 | // Skip arithmetic 125 | test.skip('short_circuit_error_left', () => { 126 | const expr = "false && (2 / 0 > 3 ? false : true)" 127 | const expected = { bool_value: false } 128 | 129 | const cel = genCel(expr) 130 | expect(cel).toStrictEqual(expected); 131 | }) 132 | 133 | test.skip('short_circuit_error_right', () => { 134 | const expr = "(2 / 0 > 3 ? false : true) && false" 135 | const expected = { bool_value: false } 136 | 137 | const cel = genCel(expr) 138 | expect(cel).toStrictEqual(expected); 139 | }) 140 | 141 | test.skip('error_right', () => { 142 | const expr = "true && 1/0 != 0" 143 | try { 144 | genCel(expr) 145 | expect(true).toBe(false) // Should fail 146 | } catch (e) { 147 | expect(e.message).toBe(`{ message: "no matching overload" }`) 148 | } 149 | }) 150 | 151 | test.skip('error_left', () => { 152 | const expr = "1/0 != 0 && true" 153 | try { 154 | genCel(expr) 155 | expect(true).toBe(false) // Should fail 156 | } catch (e) { 157 | expect(e.message).toBe(`{ message: "no matching overload" }`) 158 | } 159 | }) 160 | 161 | test.skip('no_overload', () => { 162 | const expr = "'less filling' && 'tastes great'" 163 | try { 164 | genCel(expr) 165 | expect(true).toBe(false) // Should fail 166 | } catch (e) { 167 | expect(e.message).toBe(`{ message: "no matching overload" }`) 168 | } 169 | }) 170 | }) 171 | 172 | describe('logic.OR', () => { 173 | test('all_true', () => { 174 | const expr = "true || true" 175 | const expected = { bool_value: true } 176 | 177 | const cel = genCel(expr) 178 | expect(cel).toStrictEqual(expected); 179 | }) 180 | 181 | test('all_false', () => { 182 | const expr = "false || false" 183 | const expected = { bool_value: false } 184 | 185 | const cel = genCel(expr) 186 | expect(cel).toStrictEqual(expected); 187 | }) 188 | 189 | test('false_left', () => { 190 | const expr = "false || true" 191 | const expected = { bool_value: true } 192 | 193 | const cel = genCel(expr) 194 | expect(cel).toStrictEqual(expected); 195 | }) 196 | 197 | test('false_right', () => { 198 | const expr = "true || false" 199 | const expected = { bool_value: true } 200 | 201 | const cel = genCel(expr) 202 | expect(cel).toStrictEqual(expected); 203 | }) 204 | 205 | test('short_circuit_type_left', () => { 206 | const expr = "true || 32" 207 | const expected = { bool_value: true } 208 | 209 | const cel = genCel(expr) 210 | expect(cel).toStrictEqual(expected); 211 | }) 212 | 213 | test('short_circuit_type_right', () => { 214 | const expr = "'horses' || true" 215 | const expected = { bool_value: true } 216 | 217 | const cel = genCel(expr) 218 | expect(cel).toStrictEqual(expected); 219 | }) 220 | 221 | // No support for arithmetic 222 | test.skip('short_circuit_error_left', () => { 223 | const expr = "true || (2 / 0 > 3 ? false : true)" 224 | const expected = { bool_value: true } 225 | 226 | const cel = genCel(expr) 227 | expect(cel).toStrictEqual(expected); 228 | }) 229 | 230 | test.skip('short_circuit_error_right', () => { 231 | const expr = "(2 / 0 > 3 ? false : true) || true" 232 | const expected = { bool_value: true } 233 | 234 | const cel = genCel(expr) 235 | expect(cel).toStrictEqual(expected); 236 | }) 237 | 238 | test.skip('error_right', () => { 239 | const expr = "false || 1/0 != 0" 240 | try { 241 | genCel(expr) 242 | expect(true).toBe(false) // Should fail 243 | } catch (e) { 244 | expect(e.message).toBe(`{ message: "no matching overload" }`) 245 | } 246 | }) 247 | 248 | test.skip('error_left', () => { 249 | const expr = "1/0 != 0 || false" 250 | try { 251 | genCel(expr) 252 | expect(true).toBe(false) // Should fail 253 | } catch (e) { 254 | expect(e.message).toBe(`{ message: "no matching overload" }`) 255 | } 256 | }) 257 | 258 | test.skip('no_overload', () => { 259 | const expr = "'less filling' || 'tastes great'" 260 | try { 261 | genCel(expr) 262 | expect(true).toBe(false) // Should fail 263 | } catch (e) { 264 | expect(e.message).toBe(`{ message: "no matching overload" }`) 265 | } 266 | }) 267 | }) 268 | 269 | describe('logic.NOT', () => { 270 | test('not_true', () => { 271 | const expr = "!true" 272 | const expected = { bool_value: false } 273 | 274 | const cel = genCel(expr) 275 | expect(cel).toStrictEqual(expected); 276 | }) 277 | 278 | test('not_false', () => { 279 | const expr = '!false' 280 | const expected = { bool_value: true } 281 | 282 | const cel = genCel(expr) 283 | expect(cel).toStrictEqual(expected); 284 | }) 285 | 286 | test('no_overload', () => { 287 | const expr = '!0' 288 | 289 | try { 290 | genCel(expr) 291 | expect(true).toBe(false) 292 | } catch (e) { 293 | expect(e.message).toBe('{ message: "no matching overload" }') 294 | } 295 | }) 296 | }) 297 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ "es2017" ], 4 | "module": "ES6", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "noImplicitThis": false, 8 | "removeComments": false, 9 | "preserveConstEnums": true, 10 | "strict": true, 11 | "outDir": "dist", 12 | "rootDir": ".", 13 | "target": "ES6", 14 | "sourceMap": true, 15 | "strictNullChecks": false, 16 | "moduleResolution": "node", 17 | "esModuleInterop": true 18 | }, 19 | "include": [ 20 | "index.ts", 21 | "src/**/*", 22 | "test/**/*" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-microsoft-contrib/recommended", 4 | "tslint-config-prettier" 5 | ], 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**/*" 9 | ] 10 | }, 11 | "rules": { 12 | "mocha-no-side-effect-code": false, 13 | "missing-jsdoc": false, 14 | "no-relative-imports": false, 15 | "export-name": false, 16 | "promise-function-async": false, 17 | "no-void-expression": false, 18 | "no-redundant-jsdoc": false, 19 | "prefer-type-cast": false, 20 | "typedef": [ 21 | true, 22 | "parameter", 23 | "arrow-parameter", 24 | "property-declaration", 25 | "member-variable-declaration" 26 | ] 27 | } 28 | } 29 | --------------------------------------------------------------------------------