├── .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 | Dex # dex |
9 | Species species |
10 | Type(s) types |
11 | Form form |
12 |
13 |
14 |
15 |
16 | {{pokemon.dex}} |
17 | {{pokemon.species}} |
18 |
19 | {{pokemon.type1}}
20 | /{{pokemon.type2}}
21 | |
22 |
23 |
24 | {{pokemon.form}} Form
25 |
26 | |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------