Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions interfaces/interpolate.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ declare namespace Expand {
type Key = '$' | '=' | '^';
type Open = '{' | '(';
type Terminal = '}' | ')' | ' ' | '__null__';
type Op = 'v' | 's' | 'e' | 'f';
type Op = 'v' | 's' | 'e' | 'f' | 'n';

interface State {
detecting?: Key
header?: Key
op?: Op
terminal?: Terminal;
terminal?: Terminal
dirty?: boolean
escape?: boolean
sourceMap: number[]
escaped?: string
sourceMap?: number[]
}

interface Elem {
state: State
raw: any[]
interface Elem extends State {
out: any[]
source: any[]
subst: any[]
}

export interface Options {
dereferenceSync?: (sub: string, sourceMap?: number[]) => any
dereferenceSync?: (sub: string, sourceMap?: number[]) => any
dereference?: (sub: string, sourceMap?: number[]) => any
call?: (sub: any, sourceMap?: number[]) => any
fetch?: (sub: any, sourceMap?: number[]) => any
Expand Down
2 changes: 2 additions & 0 deletions interfaces/moss.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="typed-json-transform" />
/// <reference path="schema.d.ts" />

interface MossError {
name: 'MossError',
Expand All @@ -20,6 +21,7 @@ declare namespace Moss {
auto?: any
stack?: any
selectors?: any
schema?: any
merge?: {
operator: Merge.Operator,
precedence: { [x: string]: number }
Expand Down
18 changes: 18 additions & 0 deletions interfaces/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
declare namespace Moss {
namespace Schema {
// interface Options {
// scalarType?: string
// singleType?: Schema.Options
// multiType?: { [x: string]: Schema.Options } | Schema.Options[]
// isArray?: boolean
// isMap?: boolean
// }
interface Options {
type: string,
properties?: {[x: string]: Options},
items?: Options[],
$id?: string
}
type Description = Options | [Options] | string
}
}
4 changes: 2 additions & 2 deletions src/async.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference path="../interfaces/moss.d.ts" />

import { merge, mergeArray, mergeObject, amap as map, aokmap as okmap, arrayify, extend, check, clone, each, union, difference, sum, valueForKeyPath, all, isEqual, unflatten, flatObject, unsetKeyPath, setValueForKeyPath, mergeOrReturnAssignment } from 'typed-json-transform';
import { interpolateAsync as __interpolate } from './interpolate';
import { merge, mergeArray, mergeObject, okmap as okmapSync, amap as map, aokmap as okmap, arrayify, extend, check, clone, each, union, difference, sum, valueForKeyPath, all, isEqual, unflatten, flatObject, unsetKeyPath, setValueForKeyPath, mergeOrReturnAssignment, contains } from 'typed-json-transform';
import { interpolateAsync as __interpolate, reservedKeys } from './interpolate';
import { cascadeAsync as _cascade, shouldConstruct, select, parseSelectors } from './cascade';
import * as yaml from 'js-yaml';

Expand Down
181 changes: 85 additions & 96 deletions src/interpolate/async.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { check } from 'typed-json-transform';
const expression = require('../../compiled/expression');

import { newState, parse, reduce, append as _append, pop } from './shared';
import { newState, parse, reduce, append as _append } from './shared';

export async function tokenize(str: string, options: Expand.Options) {
const { dereference, dereferenceSync, call, shell, fetch } = options;
Expand All @@ -11,45 +11,42 @@ export async function tokenize(str: string, options: Expand.Options) {
let x = 0;
let y = 0;

const stack: Expand.Elem[][] = [[{ state: { sourceMap: [0, str.length] }, raw: [], subst: [], source: [] }]];
let ptr = stack[x][y];
const stack: Expand.Elem[] = [newState()];
let frame = stack[y];


const append = (char: string) => {
let nextChunk = false;
if (ptr.state.op) {
nextChunk = _append(ptr.subst, char)
} else {
nextChunk = _append(ptr.raw, char)
const append = (val: any) => {
if (frame.escape) {
frame.escaped += val;
return;
}
let nextChunk = false;
nextChunk = _append(frame.out, val)
if (nextChunk) {
ptr.source.push(char);
frame.source.push(val);
} else {
ptr.source[(ptr.source.length - 1) || 0] += char;
frame.source[(frame.source.length - 1) || 0] += val;
}
}

const stepBack = () => {
if (ptr.state.op) {
pop(ptr.subst);
} else {
pop(ptr.raw);
}
}

const open = (op: Expand.Op, terminal: Expand.Terminal) => {
stepBack();
ptr.state.header = null;
ptr.state.detecting = null;
const existing = ptr.state.op;
if (existing) {
y++;
stack[x][y] = newState();
ptr = stack[x][y];
ptr.raw = [];
const open = (char: string, op: Expand.Op, terminal: Expand.Terminal) => {
const { escape, escaped } = frame;
if (escape) {
frame.escape = null;
frame.escaped = '';
const directive = escaped.slice(1);
if (!directive){
throw new Error('explicit interpolate without 2 char prefix directive');
}
append(directive);
if (char) {
append(char);
}
}
ptr.state.op = op;
ptr.state.terminal = terminal;
frame.header = null;
frame.detecting = null;
y++;
stack[y] = newState({ op: escape ? 'n' : op, terminal });
frame = stack[y];
}

const sub = async (fn: (s: string, location: any) => any, str: string, sourceMap?: number[]) => {
Expand Down Expand Up @@ -85,78 +82,61 @@ export async function tokenize(str: string, options: Expand.Options) {
}

const close = async () => {
const op = ptr.state.op;
ptr.state.sourceMap = [offset, i + (ptr.state.terminal && ptr.state.terminal.length) - offset];
ptr.state.op = null;
ptr.state.terminal = null;
const { op, terminal, escape, escaped } = frame;
frame.sourceMap = [offset, i + (terminal && terminal.length) - offset];
frame.op = null;
frame.terminal = null;
if (escape && escaped) {
frame.escape = false;
frame.escaped = '';
append(escaped);
}
const swap = reduce(frame.out, frame.source);
let out: any;
let res;
const swap = reduce(ptr.subst, ptr.source);
if (check(swap, [Object, Array])) {
res = await call(swap);
out = await call(swap);
} else {
if (op == 'v') {
res = await sub(dereference, swap, ptr.state.sourceMap);
out = await sub(dereference, swap, frame.sourceMap);
} else if (op == 's') {
res = await sub(shell, swap, ptr.state.sourceMap);
out = await sub(shell, swap, frame.sourceMap);
} else if (op == 'f') {
res = await sub(fetch, swap, ptr.state.sourceMap);
out = await sub(fetch, swap, frame.sourceMap);
} else if (op == 'e') {
const deref = (str: string) => subSync(dereferenceSync, str, ptr.state.sourceMap)
res = await sub((s) => expression(deref, check).parse(s), swap, ptr.state.sourceMap)
const deref = (str: string) => subSync(dereferenceSync, str, frame.sourceMap)
out = await sub((s) => expression(deref, check).parse(s), swap, frame.sourceMap)
} else if (op == 'n') {
if (terminal != '__null__') append(terminal);
out = reduce(frame.out, frame.source);
}
}
if (y > 0) {
delete stack[x][y];
y--;
ptr = stack[x][y];
ptr.subst.push(res);
}
else {
if (res) { ptr.state.dirty = true };
ptr.raw.push(res);
x++;
y = 0;
stack[x] = [newState()];
ptr = stack[x][y];
}
// if (out) frame.dirty = true;
// const { out } = frame;
// const out = reduce(frame.out, frame.source);
// delete stack[y];
stack.length--
y--;
frame = stack[y];
append(out);
// frame.source.push(out);
}

for (i = 0; i != template.length; i++) {
const char = template[i];
if (ptr.state.escape) {
ptr.state.escape = false;
append(char);
} else {
const { detecting, header, op, terminal } = ptr.state;
switch (char) {
case '(':
if (detecting && (detecting == '$')) {
open('s', ')');
break;
} else {
append(char);
}
break;
case '{':
if (detecting) {
open(detecting == '$' ? 'v' : detecting == '^' ? 'f' : 'e', '}');
break;
} else {
append(char);
}
break;
case '}': case ')':
if (op && terminal == char) {
await close();
} else {
append(char);
}
const { detecting, header, op, terminal, escape, escaped } = frame;
switch (char) {
case '(':
if (detecting && (detecting == '$')) {
open(char, 's', ')');
break;
case ' ':
if (op && terminal == char) {
await close();
}
} else {
append(char);
}
break;
case '{':
if (detecting) {
open(char, detecting == '$' ? 'v' : detecting == '^' ? 'f' : 'e', '}');
break;
case '\\':
ptr.state.escape = true;
Expand All @@ -173,17 +153,26 @@ export async function tokenize(str: string, options: Expand.Options) {
} else if (detecting) {
ptr.state.detecting = null;
}
append(char);
break;
}
if (escape) {
frame.escape = false;
frame.escaped = null;
// console.log('append escaped', escaped)
append(escaped);
}
}
append(char);
break;
}
}
while (ptr.state.op) {
if (ptr.state.terminal == '}') throw { message: `expected a closing ${ptr.state.terminal}` }
if (frame.detecting) {
append(frame.detecting);
}
while (frame.op) {
if (frame.terminal == '}') throw { message: `expected a closing ${frame.terminal}` }
await close();
}
if (ptr.state.detecting) {
ptr.state.detecting = null;
if (frame.detecting) {
frame.detecting = null;
}
return stack;
};
Expand Down
6 changes: 5 additions & 1 deletion src/interpolate/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { interpolate as interpolateAsync } from './async'
import { interpolate } from './sync';

export { interpolate, interpolateAsync };
export { interpolate, interpolateAsync };

const jsonSchemaKeys = ['id', 'schema', 'ref', 'comment'];
const mongoKeys = ['set', 'unset', 'push', 'pull', 'gt', 'lt', 'gte', 'lte', 'exists'];
export const reservedKeys = jsonSchemaKeys.concat(mongoKeys);
17 changes: 7 additions & 10 deletions src/interpolate/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ export const pop = (stack: any[]) => {
const lastIndex = (stack.length - 1) || 0;
if (check(stack[lastIndex], String)) {
const s = stack[lastIndex];
const l = s.slice(-1);
stack[lastIndex] = s.slice(0, s.length - 1);
return l;
} else {
stack.pop();
return stack.pop();
}
}

Expand All @@ -127,19 +129,14 @@ export const reduce = (raw: any[], source: any[]) => {
return res;
}

export function newState(): Expand.Elem {
return { state: { sourceMap: [] }, raw: [], subst: [], source: [] };
export function newState(options?: Partial<Expand.Elem>): Expand.Elem {
return { sourceMap: [], out: [], source: [], ...options };
}

export function parse(tokens: Expand.Elem[][]) {
export function parse(tokens: Expand.Elem[]) {
let out = '';
let outSource = '';
let changed = false;
for (const e of tokens) {
const flat = reduce(e[0].raw, e[0].source);
out = join(out, flat, outSource, e[0].source[0]);
outSource = e[0].source.join('');
if (e[0].state.dirty) changed = true;
}
out = reduce(tokens[0].out, tokens[0].source);
return { value: out, changed: changed };
}
Loading