Skip to content

Commit 6c76304

Browse files
committed
simple eval: naive support for simple destructuring
1 parent 501e3b1 commit 6c76304

File tree

2 files changed

+71
-61
lines changed

2 files changed

+71
-61
lines changed

lib/simple_eval.ts

+63-61
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,12 @@ type EvaluatableOps = ExprLikeOps | StatLikeOps
145145
type PairOp = BaseOp<O.pair>
146146
type RefOp = BaseOp<O.ref>
147147
type ArrayHoleOp = BaseLiteralOp<L.array_hole>
148-
interface DestructurePairOp extends BaseOp<O.pair> { readonly x: DeclareOp; readonly y: null }
148+
interface DestructurePairOp extends BaseOp<O.pair> { readonly x: DeclareOp | DestructuringComposedOp; readonly y: null }
149149
interface RefAssignOp extends BaseOp<O.assign> { readonly y: RefOp | DestructuringComposedOp }
150150
interface PlainRefAssignOp extends RefAssignOp { readonly y: RefOp }
151151
interface BaseDestructuringComposedOp<T extends "[" | "{"> extends BaseOp<O.composed> {
152-
readonly q: readonly (T extends "[" ? DeclareOp | ArrayHoleOp : RefOp | PlainRefAssignOp | DestructurePairOp)[]
152+
readonly q: T extends "[" ? readonly (DeclareOp | DestructuringComposedOp | ArrayHoleOp)[]
153+
: readonly (RefOp | PlainRefAssignOp | DestructurePairOp)[]
153154
readonly x: T
154155
readonly y: AnalysedVars
155156
}
@@ -243,13 +244,6 @@ const isArray = Array.isArray as { <T> (x: readonly T[] | CoreOp<keyof OpValues>
243244
const isVarAction = <K extends AllStatPrefix> (s: K): s is K & VarActions => "let,const,var".includes(s)
244245
const resetRe_ = (): true => (<RegExpOne> /a?/).test("") as true
245246
const objCreate = DefaultObject.create as { (proto: null): VarDict; <T> (o: null): SafeDict<T> }
246-
const objEntries = !(Build.BTypes & BrowserType.Chrome)
247-
|| Build.MinCVer >= BrowserVer.MinEnsuredES$Object$$values$and$$entries ? DefaultObject.entries! as never
248-
: DefaultObject.entries as unknown as undefined || (<T extends string> (object: object): [T, unknown][] => {
249-
const entries: ReturnType<ObjectConstructor["entries"]> = []
250-
for (const name of DefaultObject.keys(object)) { entries.push([name, (object as Dict<unknown>)[name]]) }
251-
return entries as [T, unknown][]
252-
})
253247
const throwSyntax = (error: string): never => { throw new SyntaxError(error) }
254248
const ValueProperty = (value: unknown, writable: boolean, enumerable: boolean, config: boolean): PropertyDescriptor =>
255249
({ value, writable, enumerable, configurable: config })
@@ -280,6 +274,20 @@ if (!kHasMap) {
280274
Map2.prototype.set = function <K extends string, V> (this: Map2<K, V>, k: K, v: V) { this.m![k] = v }
281275
}
282276

277+
const collectEnumerable = (src: any, filterKey: (key: string | symbol) => boolean
278+
, props: { [s: string | number | symbol]: PropertyDescriptor & Partial<SafeObject> }) => {
279+
const kSymbol = !(Build.BTypes&BrowserType.Chrome)||Build.MinCVer >= BrowserVer.MinEnsuredES6$ForOf$Map$SetAnd$Symbol
280+
const GetSymbols = DefaultObject.getOwnPropertySymbols
281+
for (const symbol of (Object.keys(src as object) as (string | symbol)[])
282+
.concat(kSymbol || GetSymbols ? GetSymbols!(src) : [])) {
283+
const prop = filterKey(symbol) ? DefaultObject.getOwnPropertyDescriptor(src, symbol as symbol) : null
284+
if (prop?.enumerable) {
285+
props[symbol] = ValueProperty(prop.writable !== void 0 ? prop.value : (src as any)[symbol]
286+
, true, true, true)
287+
}
288+
}
289+
}
290+
283291
//#endregion helper functions
284292

285293
//#region tokenize
@@ -724,13 +732,16 @@ const parseTree = (tokens_: readonly Token[], inNewFunc: boolean | null | undefi
724732
const getEscapeAnalyser = (): (func: BaseOp<O.fn>) => void => {
725733
interface WritableTempBlockOp extends Pick<BaseOp<O.block>, "o" | "q"> {
726734
/** consts */ x: VarName[] | null, /** lets */ y: VarName[] | null }
727-
const ToVarNames = (out: VarName[], ops: readonly (DeclareOp | DestructurePairOp | ArrayHoleOp)[]): VarName[] => {
735+
const ToVarNames = (out: VarName[]
736+
, ops: readonly (DeclareOp | DestructurePairOp | DestructuringComposedOp | ArrayHoleOp)[]): VarName[] => {
728737
for (let op of ops) {
729738
op = op.o === O.pair ? op.x : op
730739
if (op.o === O.ref) {
731-
out.push(op.q)
740+
op.q !== kDots && out.push(op.q)
732741
} else if (op.o === O.literal) { /* empty */ }
733-
else if (op.y.o === O.ref) {
742+
else if (op.o === O.composed) {
743+
ToVarNames(out, op.q)
744+
} else if (op.y.o === O.ref) {
734745
out.push(op.y.q)
735746
} else {
736747
ToVarNames(out, op.y.q)
@@ -1109,70 +1120,72 @@ const evalLet = (action: VarActions | "arg", declarations: readonly DeclareOp[],
11091120

11101121
const evalDestructuring = (destructOp: DestructuringComposedOp, composed_value: any, parentOp: RefAssignOp|null):void=>{
11111122
if (destructOp.x === "[") {
1112-
let index = 0, iterator = evalIter(composed_value, parentOp ? parentOp.x : )
1123+
const iterator = evalIter(composed_value, parentOp ? parentOp.x : Op(O.ref, kUnknown as VarName, 0, 0))
1124+
let index = 0, cur: IteratorResult<any> = { value: void 0, done: false }
11131125
for (const op of destructOp.q) {
1114-
if (op.o === O.ref && op.q === kDots) {
1115-
const { y, i } = _resolveVarRef(destructOp.q[index + 1] as RefOp, R.eveNotInited)
1116-
y[i] = [].slice.call(composed_value, index) as unknown as number
1117-
break
1126+
if (!cur.done) {
1127+
cur = Build.BTypes & BrowserType.Chrome && Build.MinCVer < BrowserVer.MinEnsuredES6$ForOf$Map$SetAnd$Symbol
1128+
&& !kIterator ? index < composed_value.length ? { value: composed_value[index], done: false }
1129+
: { value: void 0, done: true } : iterator!.next()
11181130
}
11191131
if (op.o === O.literal) {
11201132
if (0) { op.q satisfies L.array_hole }
1121-
1122-
continue
1123-
}
1124-
const keyOp = op.o === O.ref ? op.q : op.o === O.assign ? op.y.o === O.ref ? op.y.q : index : op.q
1125-
const key: string | number | symbol = evalAccessKey(typeof keyOp === "object" ? opEvals[keyOp.o](keyOp) : keyOp)
1126-
const target: DeclareOp = op.o === O.pair ? op.x : op
1127-
let value = composed_value[key]
1128-
const useDefault = value === void 0 && target.o === O.assign
1129-
if (useDefault) {
1130-
value = opEvals[target.x.o](target.x)
1131-
}
1132-
const ref = target.o === O.ref ? _resolveVarRef(target, R.eveNotInited)
1133-
: target.y.o === O.ref ? _resolveVarRef(target.y, R.eveNotInited)
1134-
: null
1135-
if (ref !== null) {
1136-
ref.y[ref.i] = value
1133+
} else if (op.o === O.ref && op.q === kDots) { // q[index + 1]: RefOp | DestructuringComposedOp
1134+
const arr = cur.done ? [] : [cur.value]
1135+
if (Build.BTypes & BrowserType.Chrome && Build.MinCVer < BrowserVer.MinEnsuredES6$ForOf$Map$SetAnd$Symbol
1136+
&& !kIterator) {
1137+
while (++index < composed_value.length) { arr.push(composed_value[index]) }
1138+
} else {
1139+
while (!(cur = iterator!.next()).done) { arr.push(cur.value) }
1140+
}
1141+
iter(kDots, destructOp.q[index + 1] as Exclude<typeof destructOp.q[0], ArrayHoleOp>, arr)
1142+
break
11371143
} else {
1138-
evalDestructuring((target as Exclude<typeof target, RefOp> & { y: BaseOp<O.composed> }).y, value
1139-
, useDefault ? target : Op(O.assign, "=", Op(O.access, "["
1140-
, parentOp ? parentOp.x : Op(O.ref, `(${kDots})` as VarName, 0, 0)
1141-
, typeof keyOp === "object" ? keyOp : Op(O.literal, L.plain, keyOp, null))
1142-
, target.y as DestructuringComposedOp) as RefAssignOp)
1144+
iter(index, op, cur.value)
11431145
}
11441146
index++
11451147
}
1146-
1148+
} else if (isLooselyNull(composed_value)) {
1149+
const first = destructOp.q[0], desc = first.o == O.ref ? first.q !== kDots ? first.q : ""
1150+
: first.o === O.pair && (typeof first.q !== "object" || first.q.o !== O.comma) && first.x.o !== O.assign
1151+
? typeof first.q === "object" ? evalLiteral(first.q) : first.q : ""
1152+
throwType("Cannot destructure " + (desc ? "property '" + desc + "' of '" : "'")
1153+
+ (parentOp && ToString(parentOp.x, (1 << O.call) | (1 << O.access) | (1 << O.unary)) || kUnknown)
1154+
+ "' as it is " + composed_value + ".")
11471155
} else {
11481156
const visited = new Map2<string, 1>()
11491157
for (const op of destructOp.q) {
11501158
if (op.o === O.ref && op.q === kDots) {
1151-
// @todo
1159+
const props: Parameters<typeof collectEnumerable>[2] = objCreate(null) as any
1160+
collectEnumerable(composed_value, key => !visited.get(key as string), props)
1161+
const sub_value = (objCreate as typeof DefaultObject.create)(DefaultObject.prototype, props)
1162+
iter(kDots, destructOp.q[destructOp.q.length - 1] as RefOp, sub_value)
11521163
break
11531164
}
11541165
const keyOp: string | ExprLikeOps = op.o === O.ref ? op.q : op.o === O.assign ? op.y.q : op.q
11551166
const key: string | number | symbol = evalAccessKey(typeof keyOp === "object" ? opEvals[keyOp.o](keyOp) : keyOp)
1156-
const target: DeclareOp = op.o === O.pair ? op.x : op
1157-
let value = composed_value[key]
1167+
iter(keyOp, op.o === O.pair ? op.x : op, composed_value[key])
1168+
visited.set(key as string, 1)
1169+
}
1170+
}
1171+
function iter(keyOp: string | number | ExprLikeOps, target: DeclareOp | DestructuringComposedOp, value: any) {
11581172
const useDefault = value === void 0 && target.o === O.assign
11591173
if (useDefault) {
11601174
value = opEvals[target.x.o](target.x)
11611175
}
11621176
const ref = target.o === O.ref ? _resolveVarRef(target, R.eveNotInited)
1163-
: target.y.o === O.ref ? _resolveVarRef(target.y, R.eveNotInited)
1177+
: target.o === O.assign && target.y.o === O.ref ? _resolveVarRef(target.y, R.eveNotInited)
11641178
: null
11651179
if (ref !== null) {
11661180
ref.y[ref.i] = value
11671181
} else {
1168-
evalDestructuring((target as Exclude<typeof target, RefOp> & { y: BaseOp<O.composed> }).y, value
1182+
evalDestructuring(target.o === O.composed ? target
1183+
: (target as Exclude<typeof target, RefOp> & { y: BaseOp<O.composed> }).y, value
11691184
, useDefault ? target : Op(O.assign, "=", Op(O.access, "["
1170-
, parentOp ? parentOp.x : Op(O.ref, `(${kDots})` as VarName, 0, 0)
1185+
, parentOp ? parentOp.x : Op(O.ref, kUnknown as VarName, 0, 0)
11711186
, typeof keyOp === "object" ? keyOp : Op(O.literal, L.plain, keyOp, null))
11721187
, target.y as DestructuringComposedOp) as RefAssignOp)
11731188
}
1174-
visited.set(key as string, 1)
1175-
}
11761189
}
11771190
}
11781191

@@ -1400,19 +1413,8 @@ const evalNever = (op: BaseOp<KStatLikeO | O.pair | O.fnDesc>): void => {
14001413
if (isRef && item.q === kDots) {
14011414
i++
14021415
const src = opEvals[arr[i].o](arr[i])
1403-
if (typeof src === "object" && src !== null) {
1404-
const HasSymbol = !(Build.BTypes & BrowserType.Chrome)
1405-
|| Build.MinCVer >= BrowserVer.MinEnsuredES6$ForOf$Map$SetAnd$Symbol
1406-
const GetSymbols = HasSymbol ? 0 as never : DefaultObject.getOwnPropertySymbols
1407-
const symbols = HasSymbol ? DefaultObject.getOwnPropertySymbols!(src) : GetSymbols ? GetSymbols(src) : []
1408-
for (const item of objEntries(src as object)) { props[item[0]] = ValueProperty(item[1], true, true, true) }
1409-
for (const symbol of symbols) {
1410-
const prop = DefaultObject.getOwnPropertyDescriptor(src, symbol)
1411-
if (prop?.enumerable) {
1412-
props[symbol] = ValueProperty(prop.writable !== void 0 ? prop.value : (src as any)[symbol]
1413-
, true, true, true)
1414-
}
1415-
}
1416+
if (typeof src === "object" && !isLooselyNull(src)) {
1417+
collectEnumerable(src, () => true, props)
14161418
}
14171419
continue
14181420
}
@@ -1432,7 +1434,7 @@ const evalNever = (op: BaseOp<KStatLikeO | O.pair | O.fnDesc>): void => {
14321434
newProto = value as object | null // a second key of the "__proto__" literal is a syntax error on Chrome 96
14331435
}
14341436
}
1435-
return DefaultObject.create(newProto, props)
1437+
return (objCreate as typeof DefaultObject.create)(newProto, props)
14361438
}, evalLiteral = (op: LiteralOp): unknown => {
14371439
switch (op.q) {
14381440
case L.plain: return op.x

tests/unit/simple-js-eval.html

+8
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@
209209
VApi.v.noNative && VApi.v.noNative();
210210

211211
// =============================== assert ===============================
212+
assert(function () { const a = {b:1,c:2,d:3}, {b, c, d, e} = a, {b: b2, c: c2, ...f} = a; return [b,c,d,e,f,b2,c2] })
213+
assert(function () { const a = [1,2,3], [b,c,d,e] = a; return [b,c,d,e] })
214+
assert(function () {
215+
try { const {b,c,d} = null; return b } catch (e) { console.log(e); return (e + "").indexOf("destructure") > 0 }
216+
})
217+
assert(function () {
218+
try { const {...b} = void 0; return b } catch (e) { console.log(e); return (e + "").indexOf("destructure") > 0 }
219+
})
212220
assert(function () { return {...null, ...123} })
213221
assert(function () {
214222
return navigator.userAgent.match(/\bChrom(?:e|ium)\/(\d+)/)

0 commit comments

Comments
 (0)