aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/declarations.ts6
-rw-r--r--src/eval.ts171
-rw-r--r--src/index.ts4
-rw-r--r--src/utils/adt.ts22
4 files changed, 152 insertions, 51 deletions
diff --git a/src/declarations.ts b/src/declarations.ts
index 0571116..5c2f9a3 100644
--- a/src/declarations.ts
+++ b/src/declarations.ts
@@ -1,4 +1,4 @@
-import { EvalActions, evalExpr } from './eval'
+import { EvalActions, evalExprAsString } from './eval'
import { Expr, Selector, SelectorComp, parseDeclarations } from './parser'
import { match, matchString } from './utils/adt'
@@ -22,7 +22,9 @@ export const evaluateDeclaration = async (
const props = await Promise.all(
[...properties.entries()].map(async ([key, expr]) => {
// Ignore errors?
- const result = await evalExpr(expr, actions).catch(e => console.warn(e))
+ const result = await evalExprAsString(expr, actions).catch((e: any) =>
+ console.warn(e),
+ )
return [key, result ?? ''] as const
}),
)
diff --git a/src/eval.ts b/src/eval.ts
index 01d29c6..26b3df0 100644
--- a/src/eval.ts
+++ b/src/eval.ts
@@ -1,5 +1,5 @@
import { CSSUnit, Expr } from './parser'
-import { match, matchString } from './utils/adt'
+import { Enum, constructors, match, matchString } from './utils/adt'
export interface EvalActions {
addClass(id: string, classes: string): Promise<void>
@@ -34,12 +34,27 @@ export interface EvalActions {
callMethod(
id: string | undefined,
method: string,
- args: EvalValue[],
+ args: (string | undefined)[],
): Promise<void>
// calculate ??
}
-type EvalValue = string | undefined | void
+export type EvalValue = Enum<{
+ String: string
+ Number: number
+ Boolean: boolean
+ Lazy: Expr[]
+ Void: never
+ VarIdentifier: string
+ // Object: Record<any, any>
+}>
+export const EvalValue = constructors<EvalValue>()
+
+export const evalExprAsString = async (
+ expr: Expr,
+ actions: EvalActions,
+): Promise<string | undefined> =>
+ evalValueToString(await evalExpr(expr, actions))
export const evalExpr = async (
expr: Expr,
@@ -47,64 +62,104 @@ export const evalExpr = async (
): Promise<EvalValue> =>
match<Promise<EvalValue>, Expr>(expr, {
Call: async ({ name, args }) => getFunctions(name, args, actions),
- LiteralString: async s => s,
+ LiteralString: async s => EvalValue.String(s),
LiteralNumber: async ({ value, unit }) =>
- matchString<number, CSSUnit>(unit, {
- s: () => value * 1000,
- _: () => value,
- }).toString(),
- Identifier: async s => s,
- VarIdentifier: async s => s,
- _: async _ => undefined,
+ EvalValue.Number(
+ matchString<number, CSSUnit>(unit, {
+ s: () => value * 1000,
+ _: () => value,
+ }),
+ ),
+ Identifier: async s => EvalValue.String(s),
+ VarIdentifier: async s => EvalValue.VarIdentifier(s),
+ _: async _ => EvalValue.Void(),
+ })
+
+const evalValueToString = (val: EvalValue): string | undefined =>
+ match<string | undefined, EvalValue>(val, {
+ String: s => s.replace(/(^'|")|('|"$)/g, ''),
+ Boolean: b => `${b}`,
+ Number: n => `${n}`,
+ _: () => undefined,
+ })
+
+const evalValueToNumber = (val: EvalValue): number | undefined =>
+ match<number | undefined, EvalValue>(val, {
+ String: s => parseInt(s, 10),
+ Boolean: b => (b ? 1 : 0),
+ Number: n => n,
+ _: () => undefined,
})
-const getFunctions = (name: string, args: Expr[], actions: EvalActions) => {
+const evalValueToBoolean = (val: EvalValue): boolean =>
+ match<boolean, EvalValue>(val, {
+ String: s => !['false', '', '0'].includes(s.replace(/(^'|")|('|"$)/g, '')),
+ Boolean: b => b,
+ Number: n => !!n,
+ _: () => false,
+ })
+
+const getFunctions = (
+ name: string,
+ args: Expr[],
+ actions: EvalActions,
+): Promise<EvalValue> => {
const getVariable = async () => {
const varName = await evalExpr(args[0], actions)
const defaultValue = args[1] && (await evalExpr(args[1], actions))
- return varName && (actions.getVariable(varName) ?? defaultValue)
+
+ return match<Promise<EvalValue>, EvalValue>(varName, {
+ VarIdentifier: async name => {
+ const value = await actions.getVariable(name)
+ return value === undefined ? defaultValue : EvalValue.String(value)
+ },
+ _: async () => EvalValue.Void(),
+ })
}
return matchString<Promise<EvalValue>>(name, {
'add-class': async () => {
- const id = await evalExpr(args[0], actions)
- const classes = await evalExpr(args[1], actions)
+ const id = evalValueToString(await evalExpr(args[0], actions))
+ const classes = evalValueToString(await evalExpr(args[1], actions))
if (id && classes) {
await actions.addClass(id, classes)
}
+ return EvalValue.Void()
},
'remove-class': async () => {
- const id = await evalExpr(args[0], actions)
- const classes = await evalExpr(args[1], actions)
+ const id = evalValueToString(await evalExpr(args[0], actions))
+ const classes = evalValueToString(await evalExpr(args[1], actions))
if (id && classes) {
await actions.removeClass(id, classes)
}
+ return EvalValue.Void()
},
if: async () => {
- const cond = await evalExpr(args[0], actions)
- const FALSEY = ['0', 'false']
- if (cond && !FALSEY.includes(cond.replace(/(^'|")|('|"$)/g, ''))) {
+ const cond = evalValueToBoolean(await evalExpr(args[0], actions))
+ if (cond) {
return evalExpr(args[1], actions)
} else {
return evalExpr(args[2], actions)
}
},
delay: async () => {
- const num = await evalExpr(args[0], actions)
- num && (await actions.delay(parseInt(num, 10)))
+ const num = evalValueToNumber(await evalExpr(args[0], actions))
+ num !== undefined ? await actions.delay(num) : undefined
+ return EvalValue.Void()
},
'js-eval': async () => {
- const js = await evalExpr(args[0], actions)
+ const js = evalValueToString(await evalExpr(args[0], actions))
return js && (await actions.jsEval(js))
},
'load-cssx': async () => {
- const id = await evalExpr(args[0], actions)
- const url = await evalExpr(args[1], actions)
+ const id = evalValueToString(await evalExpr(args[0], actions))
+ const url = evalValueToString(await evalExpr(args[1], actions))
if (id && url) {
await actions.loadCssx(id, url)
}
+ return EvalValue.Void()
},
var: getVariable,
@@ -113,59 +168,81 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) => {
update: async () => {
const [id, name, value] =
args.length >= 3
- ? await evalArgs(args, 3, actions)
- : [undefined, ...(await evalArgs(args, 2, actions))]
+ ? (await evalArgs(args, 3, actions)).map(evalValueToString)
+ : [
+ undefined,
+ ...(await evalArgs(args, 2, actions)).map(evalValueToString),
+ ]
if (name) {
- actions.updateVariable(id ?? undefined, name, value ?? '')
+ await actions.updateVariable(id ?? undefined, name, value ?? '')
}
+ return EvalValue.Void()
},
'set-attr': async () => {
const [id, name, value] =
args.length >= 3
- ? await evalArgs(args, 3, actions)
- : [undefined, ...(await evalArgs(args, 2, actions))]
+ ? (await evalArgs(args, 3, actions)).map(evalValueToString)
+ : [
+ undefined,
+ ...(await evalArgs(args, 2, actions)).map(evalValueToString),
+ ]
if (name) {
actions.setAttribute(id ?? undefined, name, value ?? '')
}
+ return EvalValue.Void()
},
attr: async () => {
const [id, name] =
args.length >= 2
- ? await evalArgs(args, 2, actions)
- : [undefined, await evalExpr(args[0], actions)]
+ ? (await evalArgs(args, 2, actions)).map(evalValueToString)
+ : [undefined, evalValueToString(await evalExpr(args[0], actions))]
if (name) {
- return actions.getAttribute(id as string | undefined, name)
+ const val = await actions.getAttribute(id as string | undefined, name)
+ return val === undefined ? EvalValue.Void() : EvalValue.String(val)
}
+ return EvalValue.Void()
},
- 'prevent-default': async () => actions.withEvent(e => e.preventDefault()),
+ 'prevent-default': async () => {
+ await actions.withEvent(e => e.preventDefault())
+ return EvalValue.Void()
+ },
request: async () => {
- const url = await evalExpr(args[0], actions)
- const method = (args[1] && (await evalExpr(args[1], actions))) ?? 'post'
+ const url = evalValueToString(await evalExpr(args[0], actions))
+ const method =
+ (args[1] && evalValueToString(await evalExpr(args[1], actions))) ||
+ 'post'
if (url) {
const data = await actions.getFormData()
await actions.sendRequest({ method, url, data })
}
+ return EvalValue.Void()
},
'add-children': async () => {
- const id = await evalExpr(args[0], actions)
- if (id) actions.addChildren(id, args.slice(1))
+ const id = evalValueToString(await evalExpr(args[0], actions))
+ if (id) await actions.addChildren(id, args.slice(1))
+ return EvalValue.Void()
},
- 'remove-element': async () =>
- actions.removeElement(
- (args[0] && (await evalExpr(args[0], actions))) ?? undefined,
- ),
- call: async () => {
- const [id, method, ...methodArgs] = await Promise.all(
- args.map(a => evalExpr(a, actions)),
- )
+ 'remove-element': async () => {
+ const selector =
+ (args[0] && evalValueToString(await evalExpr(args[0], actions))) ??
+ undefined
+ if (selector) await actions.removeElement(selector)
+ return EvalValue.Void()
+ },
+
+ 'call-method': async () => {
+ const [id, method, ...methodArgs] = (
+ await Promise.all(args.map(a => evalExpr(a, actions)))
+ ).map(evalValueToString)
if (id && method) {
- actions.callMethod(id, method, methodArgs)
+ await actions.callMethod(id, method, methodArgs)
}
+ return EvalValue.Void()
},
_: () => Promise.reject(new Error('not supposed to be here')),
diff --git a/src/index.ts b/src/index.ts
index a448924..52f3a1b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-import { EvalActions, evalExpr } from './eval'
+import { EvalActions, evalExpr, evalExprAsString } from './eval'
import {
extractDeclaration,
DeclarationEval,
@@ -274,7 +274,7 @@ export const manageElement = async (
try {
const exprs = parse(text)
$element.textContent =
- (exprs[0] ? await evalExpr(exprs[0], actions) : text) ?? text
+ (exprs[0] ? await evalExprAsString(exprs[0], actions) : text) ?? text
} catch (e) {
$element.textContent = text
}
diff --git a/src/utils/adt.ts b/src/utils/adt.ts
index 45d9e50..3ae387c 100644
--- a/src/utils/adt.ts
+++ b/src/utils/adt.ts
@@ -7,6 +7,28 @@ export const match = <R, T extends Tag<string, any>>(
},
): R => ((pattern as any)[tag.tag] || (pattern._ as any))(tag.value)
+// type TagValues<
+// T extends Tag<any, string>,
+// Keys extends Array<string>,
+// Values extends Array<any> = [],
+// > = Keys extends []
+// ? Values
+// : Keys extends [
+// infer key extends string,
+// ...infer restOfKeys extends string[],
+// ]
+// ? TagValues<T, restOfKeys, [...Values, TagValue<T, key>]>
+// : never
+//
+// export const ifLet = <T extends Tag<string, any>, Keys extends Array<T['tag']>>(
+// tag: T,
+// kinds: Keys,
+// cb: (...values: TagValues<T, Keys>) => void,
+// ): void => {
+// const values = kinds.map(k => (tag.tag === k ? tag.value : undefined))
+// ;(cb as any)(...values)
+// }
+
export const matchString = <R, T extends string = string>(
key: T,
pattern: {