Skip to content

Commit

Permalink
refactor error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
koladilip committed Dec 7, 2022
1 parent 196f266 commit 6de6d51
Show file tree
Hide file tree
Showing 34 changed files with 463 additions and 144 deletions.
153 changes: 149 additions & 4 deletions docs/syntax.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Template is a set of statements and result the last statement is the output of the template.

### Variables
## Variables
```js
const a = 1
let b = a + 2
a + b
```

### Input and Bindings
## Input and Bindings
Input refers to the JSON document we would like to process using a template. Bindings refer to additional data or functions we would provide to process the data efficiently.

Example:
Expand All @@ -18,7 +18,7 @@ Example:
* `{defaultName: 'World'}` is bindings.
* `$.defaultName` refers to "defaultName" property of the bindings. Refer the [example](../test/scenarios/bindings/template.jt) for more clarity.

### Arrays
## Arrays
```js
let arr = [1, 2, 3, 4]
let a = arr[1, 2] // [2, 3]
Expand All @@ -27,7 +27,7 @@ let c = arr[-2:] // [3, 4]
```
Refer the [example](../test/scenarios/arrays/template.jt) for more clarity.

### Objects
## Objects
```js
let key = "some key"
// { "a": 1, "b": 2, "c": 3, "some key": 4 }
Expand All @@ -38,3 +38,148 @@ let c = obj{["a", "b"]} // { "a": 1, "b": 2}
let d = obj{~["a", "b"]} // { "c": 3, "some key": 4}
```
Refer the [example](../test/scenarios/objects/template.jt) for more clarity.

## Functions
### Normal functions
```js
let fn = function(arg1, arg2){
arg1 + arg2
}
```
The result of the last statement of function will be returned as result of the function. We can also use rest params (`...args`).
### Lambda/Short functions
```js
let fn = array.map(lambda 2 * ?0);
```
This function gets converted to:
```js
let fn = array.map(function(args) {
2 * args[0]
})
```
Lambda functions are short to express the intention and it is convenient sometimes.
### Async functions
```js
let fn = async function(arg1, arg2){
const result = await doSomethingAsync(arg1, arg2)
doSomethingSync(result)
}
```
**Note:** When we want to use async functions then we need to create template engine using `JsonTemplateEngine.create`. If you create a template this way then it will be created as an async function so we can `await` anywhere in the template.
```js
let result = await doSomething(.a, .b)
```
## Paths
Paths are used to access properties in `input`, `bindings` and `variables`.
### Simple Paths
Simple paths support limited path features and get translated as direct property access statements in the generate javascript code.
`a.b.c` gets translated to `a?.b?.c` so they are very fast compared to [Rich paths](#rich-paths). Simple paths are ideal when we know the object structure.
**Supported features:**
* [Simple Selectors](#simple-selectors)
* [Single Index Filters](#single-index-or-property-filters)
Refer the [example](../test/scenarios/paths/simple_path.jt) for more clarity.
### Rich Paths
Rich paths gets converted complex code to support different variations in the data.
If we use rich path for expression: `a.b.c` then it automatically following variations.
* `[{"a": { "b": [{"c": 2}]}}]`
* `{"a": { "b": [{"c": 2}]}}`
* `{"a": [{ "b": [{"c": 2}]}]}`
* Automatically handles selection from nested objects and arrays.
#### Simple selectors
```js
let x = a.b.c;
let y = a."some key".c
```
Refer the [example](../test/scenarios/selectors/template.jt) for more clarity.
#### Wildcard selectors
```js
a.*.c // selects c from any direct property of a
```
Refer the [example](../test/scenarios/selectors/wild_cards.jt) for more clarity.
#### Descendent selectors
```js
// selects c from any child property of a
// a.b.c, a.b1.b2.c or a.b1.b2.b3.c
let x = a..c;
let y = a.."some key";
```
Refer the [example](../test/scenarios/selectors/template.jt) for more clarity.
#### Single Index or Property Filters
```js
let x = a[0].c;
let y = a[-1].c; // selects last element from array
let z = a["some key"].c
```
Refer the [example](../test/scenarios/filters/array_filters.jt) for more clarity.
#### Multi Indexes or Properties Filters
```js
let x = a[0, 2, 5].c;
let y = a["some key1", "some key2"].c;
```
Refer the [example](../test/scenarios/filters/array_filters.jt) for more clarity.
#### Range filters
```js
let x = a[2:5].c;
let y = a[:-2].c;
let z = a[2:].c;
```
#### Object Property Filters
```js
let x = obj{["a", "b"]}; // selects a and b
let y = obj{~["a", "b"]}; // selects all properties except a and b
```
Refer the [example](../test/scenarios/filters/object_indexes.jt) for more clarity.
#### Conditional or Object Filters
```js
let x = obj{.a > 1};
```
Refer the [example](../test/scenarios/filters/object_filters.jt) for more clarity.
#### Block expressions
```js
let x = obj.({
a: .a + 1,
b: .b + 2
});
let x = obj.([.a+1, .b+2]);
```
Refer the [example](../test/scenarios/paths/block.jt) for more clarity.
### Path Options
We can mention defaultPathType while creating engine instance.
```js
// For using simple path as default path type
// a.b.c will be treated as simple path
JsonTemplateEngine.create(`a.b.c`, {defaultPathType: PathType.SIMPLE});
// For using rich path as default path type
// a.b.c will be treated as rich path
JsonTemplateEngine.create(`a.b.c`, {defaultPathType: PathType.RICH});
```
We can override the default path option using tags.
```js
// Use ~s to treat a.b.c as simple path
~s a.b.c
// Use ~r to treat a.b.c as rich path
~r a.b.c
```
**Note:** Rich paths are slower compare to the simple paths.
## Compile time expressions
Compile time expressions are evaluated during compilation phase using compileTimeBindings option.
```js
// {{$.a.b.c}} gets translated to 1 and
// final translated code will be "let a = 1;"
JsonTemplateEngine.create(`let a = {{$.a.b.c}};`, {
compileTimeBindings: {
a: {
b: {
c: 1
}
}
}
});
```
We can use compile time expressions to generate a template and then recompile it as expression. Refer the [example](../test/scenarios/compile_time_expressions/two_level_path_processing.jt).
7 changes: 6 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,19 @@ This library generates a javascript function code from the template and then use
1. [Objects](test/scenarios/objects/template.jt)
1. [Functions](test/scenarios/functions/template.jt)
1. [Bindings](test/scenarios/bindings/template.jt)
1. [Comments](test/scenarios/comments/template.jt)
1. [Paths](test/scenarios/paths/template.jt)
* [Filters](test/scenarios/filters/template.jt)
* [Selectors](test/scenarios/selectors/template.jt)
* [Context Variables](test/scenarios/selectors/context_variables.jt)
* [Context variables](test/scenarios/selectors/context_variables.jt)
* [Simple paths](test/scenarios/paths/simple_path.jt)
* [Rich paths](test/scenarios/paths/rich_path.jt)
* [Paths options](test/scenarios/paths/options.jt)
1. [Conditions](test/scenarios/conditions/template.jt)
* [Comparisons](test/scenarios/comparisons/template.jt)
1. [Math operations](test/scenarios/math/template.jt)
1. [Logical operations](test/scenarios/logics/template.jt)
1. [Compile time expressions](test/scenarios/compile_time_expressions/template.jt)

For more examples, refer [Scenarios](test/scenarios)

Expand Down
4 changes: 2 additions & 2 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY } from './constants';
import { JsonTemplateLexer } from './lexer';
import { JsonTemplateParser } from './parser';
import { JsonTemplateTranslator } from './translator';
import { Dictionary, EngineOptions, Expression } from './types';
import { EngineOptions, Expression } from './types';
import { CommonUtils } from './utils';

export class JsonTemplateEngine {
Expand Down Expand Up @@ -62,7 +62,7 @@ export class JsonTemplateEngine {
return translator.translate();
}

evaluate(data: any, bindings: Dictionary<any> = {}): any {
evaluate(data: any, bindings: Record<string, any> = {}): any {
return this.fn(data || {}, bindings);
}
}
2 changes: 1 addition & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class JsonTemplateParserError extends Error {
}
}

export class JsosTemplateTranslatorError extends Error {
export class JsonTemplateTranslatorError extends Error {
constructor(message: string) {
super(message);
}
Expand Down
22 changes: 9 additions & 13 deletions src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export class JsonTemplateLexer {
private readonly codeChars: string[];
private buf: Token[];
private idx = 0;
private lastParsedToken?: Token;

constructor(template: string) {
this.buf = [];
this.codeChars = template.split('');
Expand All @@ -27,8 +29,8 @@ export class JsonTemplateLexer {
return this.idx;
}

getCodeChars(start: number, end: number): string[] {
return this.codeChars.slice(start, end);
getCode(start: number, end: number): string {
return this.codeChars.slice(start, end).join('');
}

match(value?: string, steps = 0): boolean {
Expand Down Expand Up @@ -250,16 +252,14 @@ export class JsonTemplateLexer {
}

lex(): Token {
let token;

if (this.buf[0]) {
this.idx = this.buf[0].range[1];
token = this.buf[0];
this.lastParsedToken = this.buf[0];
this.buf = this.buf.slice(1);
return token;
return this.lastParsedToken;
}

return this.advance();
this.lastParsedToken = this.advance();
return this.lastParsedToken;
}

static isLiteralToken(token: Token) {
Expand All @@ -282,15 +282,11 @@ export class JsonTemplateLexer {
this.throwError(MESSAGES.UNEXP_TOKEN, token.value);
}

getContext(length = 10): string {
return this.codeChars.slice(this.idx - length, this.idx + length).join('');
}

private throwError(messageFormat: string, ...args): never {
const msg = messageFormat.replace(/%(\d)/g, (_, idx) => {
return args[idx];
});
throw new JsonTemplateLexerError(msg + ' at ' + this.getContext(15));
throw new JsonTemplateLexerError(msg);
}

private static isDigit(ch: string) {
Expand Down
Loading

0 comments on commit 6de6d51

Please sign in to comment.