-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
296964c
commit 6b9336e
Showing
39 changed files
with
37,097 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Dictionary as Dict } from "lodash"; | ||
import { CompiledComponent, EventContext, Properties as Props } from "protocol"; | ||
|
||
export type Context<T extends Props = Record<string, any>> = | ||
| PropMap<T> | ||
| EventContext | ||
| CompiledComponent<string, T>; | ||
|
||
export type Key<T> = Extract<keyof T, string>; | ||
|
||
export type Prop<T extends Props> = (ctx: Context<T>) => T[keyof T]; | ||
|
||
export type PropMap<T extends Props> = Dict<Prop<T>>; |
25 changes: 25 additions & 0 deletions
25
client/src/components/renderer/parser-v140/__tests__/applyScope.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { applyScope } from "../applyScope"; | ||
|
||
describe("applyScope", () => { | ||
it("correctly propagates context", () => { | ||
const result = applyScope<{ a: number; b: number }>( | ||
{ a: ({ b }) => b() + 1 }, | ||
{ a: ({ a }) => a() * 3 } | ||
); | ||
expect(result.a({ b: 3 })).toEqual(12); | ||
expect(result.a({ b: () => 3 })).toEqual(12); | ||
}); | ||
it("correctly chains contexts", () => { | ||
const result = applyScope<{ a: number; b: number }>( | ||
{ a: ({ b }) => b() + 1 }, | ||
{ a: ({ a }) => a() * 3 } | ||
); | ||
const result2 = applyScope<{ a: number; b: number }>( | ||
{ b: ({ b }) => b() + 3 }, | ||
result | ||
); | ||
expect(result2.a({ b: 3 })).toEqual(21); | ||
expect(result2.a({ b: () => 3 })).toEqual(21); | ||
}); | ||
}); |
33 changes: 33 additions & 0 deletions
33
client/src/components/renderer/parser-v140/__tests__/parse.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { Context } from "../Context"; | ||
import { parse } from "../parse"; | ||
|
||
describe("parse", () => { | ||
const ctx: Context = { a: 1 }; | ||
const result = parse<{ | ||
rect: { x: number; y: string; z: number; w: number }; | ||
}>([{ $: "rect2", y: 4 }], { | ||
rect2: [ | ||
{ $: "rect", x: 2, y: "{{ctx.y + 1}}" }, | ||
{ $: "rect", y: "{{1 + 2}} a", z: "{{1 + 2}}", w: "{{ctx.a}}" }, | ||
], | ||
})(ctx); | ||
it("flattens correctly", () => { | ||
expect(result).toMatchObject([{ $: "rect" }, { $: "rect" }]); | ||
}); | ||
it("creates a deferred property", () => { | ||
expect(result[0].x).toEqual(2); | ||
}); | ||
it("evaluates a computed property", () => { | ||
expect(result[1].z).toEqual(3); | ||
}); | ||
it("evaluates a template string computed property", () => { | ||
expect(result[1].y).toEqual("3 a"); | ||
}); | ||
it("reads from context", () => { | ||
expect(result[1].w).toEqual(1); | ||
}); | ||
it("propagates context correctly", () => { | ||
expect(result[0].y).toEqual(5); | ||
}); | ||
}); |
37 changes: 37 additions & 0 deletions
37
client/src/components/renderer/parser-v140/__tests__/parseProperty.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { parseProperty } from "../parseProperty"; | ||
|
||
describe("parseProperty", () => { | ||
it("parses a simple property", () => { | ||
const f = parseProperty(3); | ||
expect(f({})).toEqual(3); | ||
}); | ||
it("parses nested array", () => { | ||
const f = parseProperty([1, 2, [1, 2]]); | ||
expect(f({})).toEqual([1, 2, [1, 2]]); | ||
}); | ||
it("parses nested object", () => { | ||
const f = parseProperty({ a: 1, b: { x: 1, y: 2 } }); | ||
expect(f({})).toEqual({ a: 1, b: { x: 1, y: 2 } }); | ||
}); | ||
it("parses mixed object", () => { | ||
const f = parseProperty({ a: 1, b: [1, 2] }); | ||
expect(f({})).toEqual({ a: 1, b: [1, 2] }); | ||
}); | ||
it("parses computed property", () => { | ||
const f = parseProperty("{{1}}"); | ||
expect(f({})).toEqual(1); | ||
}); | ||
it("parses computed with context", () => { | ||
const f = parseProperty("{{ctx.a}}"); | ||
expect(f({ a: 1 })).toEqual(1); | ||
}); | ||
it("parses computed with nested context", () => { | ||
const f = parseProperty("{{ctx.a.b[0]}}"); | ||
expect(f({ a: { b: [1] } })).toEqual(1); | ||
}); | ||
it("parses string coercion property", () => { | ||
const f = parseProperty("a {{ctx.a}}"); | ||
expect(f({ a: 2 })).toEqual("a 2"); | ||
}); | ||
}); |
17 changes: 17 additions & 0 deletions
17
client/src/components/renderer/parser-v140/__tests__/parseString.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { parseString } from "../parseString"; | ||
|
||
describe("parseString", () => { | ||
it("parses single terms", () => { | ||
const f = parseString("{{1}}"); | ||
expect(f({})).toEqual(1); | ||
}); | ||
it("parses multiple terms", () => { | ||
const f = parseString("{{false && true}}"); | ||
expect(f({})).toEqual(false); | ||
}); | ||
it("coerces templates into strings", () => { | ||
const f = parseString("B{{'a' + + 'a'}}a"); | ||
expect(f({})).toEqual("BaNaNa"); | ||
}); | ||
}); |
14 changes: 14 additions & 0 deletions
14
client/src/components/renderer/parser-v140/__tests__/parseToken.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { parseToken } from "../parseToken"; | ||
|
||
describe("parseToken", () => { | ||
it("parses expressions", () => { | ||
const f = parseToken("1 + ctx.a + ctx.b"); | ||
expect( | ||
f({ | ||
a: 2, | ||
b: 3, | ||
}) | ||
).toEqual(6); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Properties as Props } from "protocol"; | ||
import { Context, PropMap } from "./Context"; | ||
import { mapProperties } from "./mapProperties"; | ||
|
||
export function applyScope<T extends Props>( | ||
scope: PropMap<T>, | ||
props: PropMap<T> | ||
): PropMap<T> { | ||
return Object.setPrototypeOf( | ||
mapProperties(props, (prop) => (provided: Context<T>) => prop(scope)), | ||
scope | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { parse } from "./parse"; |
21 changes: 21 additions & 0 deletions
21
client/src/components/renderer/parser-v140/mapProperties.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { ObjectIterator } from "lodash"; | ||
import { Properties as Props } from "protocol"; | ||
import { Context } from "./Context"; | ||
|
||
/** | ||
* Iterates over the properties of a scope or component, | ||
* ignoring the component name property `$`. | ||
* @param context The properties to iterate against. | ||
* @param f | ||
* @returns | ||
*/ | ||
export function mapProperties<T extends Props, TResult>( | ||
context: Context<T> = {}, | ||
f: ObjectIterator<Context<T>, TResult> | ||
): Context<T> & { $: string } { | ||
const out: any = {}; | ||
for (const key of Object.keys(context)) { | ||
out[key] = key === "$" ? context[key] : f(context[key], key, context); | ||
} | ||
return Object.setPrototypeOf(out, context); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { isFunction } from "lodash"; | ||
import { Properties as Props } from "protocol"; | ||
import { Context, PropMap } from "./Context"; | ||
import { mapProperties } from "./mapProperties"; | ||
import { parseProperty } from "./parseProperty"; | ||
|
||
export function normalize<T extends Props>( | ||
context: Context<T> = {} | ||
): PropMap<T> { | ||
return mapProperties(context, (v) => (isFunction(v) ? v : parseProperty(v))); | ||
} | ||
|
||
export function normalizeConstant(obj: Context<any> = {}) { | ||
return new Proxy(obj, { | ||
get: (obj, p) => (typeof obj[p] === "function" ? obj[p] : () => obj[p]), | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Dictionary as Dict, flatMap, map, mapValues, range } from "lodash"; | ||
import { | ||
CompiledComponent, | ||
ComponentDefinition, | ||
ComponentDefinitionMap, | ||
IntrinsicComponentMap, | ||
TraceComponent, | ||
} from "protocol"; | ||
import { Context } from "./Context"; | ||
import { applyScope } from "./applyScope"; | ||
import { mapProperties } from "./mapProperties"; | ||
import { normalize } from "./normalize"; | ||
import { parseProperty } from "./parseProperty"; | ||
|
||
function transformOne(component: TraceComponent<string, Dict<any>>) { | ||
const { $for, $if, ...rest } = component; | ||
if ($for || $if) { | ||
const { $let = "i", $from = 0, $to = 1, $step = 1 } = $for ?? {}; | ||
const condition = parseProperty($if); | ||
const from = parseProperty($from); | ||
const to = parseProperty($to); | ||
const step = parseProperty($step); | ||
return ($: Context = {}) => | ||
!$if || condition($) | ||
? range(from($), to($), step($)).map((i) => { | ||
const scoped = applyScope($, normalize({ [$let]: i })); | ||
return applyScope(scoped, normalize(rest as any)); | ||
}) | ||
: []; | ||
} else { | ||
return ($: Context = {}) => { | ||
const scoped = applyScope($, normalize(rest as any)); | ||
return [scoped]; | ||
}; | ||
} | ||
} | ||
|
||
type Compiled<T extends string> = ( | ||
context: Context | ||
) => Compiled<T>[] | CompiledComponent<T, Dict<any>>[]; | ||
|
||
/** | ||
* A parser for a list of Components | ||
* @param definition a list of Components | ||
* @param context user injected context (from parent Components) | ||
* @returns a list of parsed Components | ||
*/ | ||
export function parse<T extends IntrinsicComponentMap>( | ||
definition: ComponentDefinition<string, Dict<any>>, | ||
components: ComponentDefinitionMap | ||
): ( | ||
context: Context | ||
) => CompiledComponent<Extract<keyof T, string>, Record<string, any>>[] { | ||
const parseOne = (element: TraceComponent<string, Dict<any>>) => { | ||
const { $ } = element; | ||
const scoped = transformOne(element); | ||
return $ in components | ||
? (context: Context) => | ||
flatMap(scoped(context), (elem) => flatMap(store[$], (f) => f(elem))) | ||
: (context: Context) => | ||
map(scoped(context), (elem) => | ||
Object.setPrototypeOf( | ||
mapProperties(elem, (prop) => prop(elem)), | ||
null | ||
) | ||
); | ||
}; | ||
const store: Dict<Compiled<Extract<keyof T, string>>[]> = mapValues( | ||
components, | ||
(elements) => map(elements, parseOne) | ||
); | ||
const entry = flatMap(definition, parseOne); | ||
return (context) => flatMap(entry, (e) => e(context)); | ||
} |
25 changes: 25 additions & 0 deletions
25
client/src/components/renderer/parser-v140/parseProperty.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { map, mapValues } from "lodash"; | ||
import { Prop } from "./Context"; | ||
import { parseString } from "./parseString"; | ||
/** | ||
* Parses a single property (recursively calling down if required) | ||
* @param prop the value of the property to parse | ||
* @param context additional context for the property | ||
* @returns a Function which takes in context and returns the properties value | ||
*/ | ||
export function parseProperty(prop: any): Prop<any> { | ||
switch (prop?.constructor) { | ||
case Array: { | ||
const parsed = map(prop, parseProperty); | ||
return (ctx) => map(parsed, (f) => f(ctx)); | ||
} | ||
case Object: { | ||
const parsed = mapValues(prop, parseProperty); | ||
return (ctx) => mapValues(parsed, (f) => f(ctx)); | ||
} | ||
case String: | ||
return parseString(prop); | ||
default: | ||
return () => prop; | ||
} | ||
} |
Oops, something went wrong.