From 2274eaa88ce37cf23f24884b8cf7efacc412d86d Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 16 Oct 2024 10:45:47 +0900 Subject: [PATCH 1/3] Remove definer --- src/S_Core.bs.mjs | 71 ++++++---------------- src/S_Core.res | 146 ++++++++++++++++++++-------------------------- 2 files changed, 83 insertions(+), 134 deletions(-) diff --git a/src/S_Core.bs.mjs b/src/S_Core.bs.mjs index b39e817..077c969 100644 --- a/src/S_Core.bs.mjs +++ b/src/S_Core.bs.mjs @@ -1117,8 +1117,6 @@ function recursive(fn) { }; var schema = fn(placeholder); placeholder.f = schema.f; - schema.d = undefined; - schema.c = undefined; var initialParseOperationBuilder = schema.b; schema.b = (function (b, input, selfSchema, path) { var bb = scope(b); @@ -1556,12 +1554,8 @@ function processInputItems(b, ctx, input, schema, path) { b.c = b.c + typeFilterCode(b, typeFilter, schema$1, itemInput, path$1); } var bb = scope(b); - if (isObject && schema$1.d) { - processInputItems(bb, ctx, itemInput, schema$1, path$1); - } else { - var itemOutput = schema$1.b(bb, itemInput, schema$1, path$1); - addItemOutput(b, ctx, item, itemOutput); - } + var itemOutput = schema$1.b(bb, itemInput, schema$1, path$1); + addItemOutput(b, ctx, item, itemOutput); if (isLiteral) { b.c = b.c + allocateScope(bb) + prevCode; } else { @@ -1659,6 +1653,14 @@ function reverse$1(inputDefinition, toItem) { definitionToOutput(inputDefinition, ""); b.c = ctx.d + b.c; var isRootObject = original.t.TAG === "Object"; + var fallbackOutput = function (item, path) { + var itemSchema = item.t; + if (itemSchema.t.TAG !== "Literal") { + return invalidOperation(b, path, "Schema for " + item.i + " isn't registered"); + } + var value = itemSchema.t._0.value; + return "e[" + (b.g.e.push(value) - 1) + "]"; + }; var schemaOutput = function (schema, isObject, path) { var items = schema.t.items; var output = ""; @@ -1679,18 +1681,6 @@ function reverse$1(inputDefinition, toItem) { return "[" + output + "]"; } }; - var fallbackOutput = function (item, path) { - var itemSchema = item.t; - if (itemSchema.t.TAG !== "Literal") { - if (isRootObject && itemSchema.d) { - return schemaOutput(itemSchema, true, path + item.p); - } else { - return invalidOperation(b, path, "Schema for " + item.i + " isn't registered"); - } - } - var value = itemSchema.t._0.value; - return "e[" + (b.g.e.push(value) - 1) + "]"; - }; if (toItem === undefined) { return val(b, schemaOutput(original, isRootObject, path)); } @@ -1708,37 +1698,26 @@ function factory$3(definer) { var fields = {}; var items = []; var flatten = function (schema) { - if (schema.d) { - return schema.d(this); - } var message = "The " + schema.n() + " schema can't be flattened"; throw new Error("[rescript-schema] " + message); }; var field = function (fieldName, schema) { var inlinedLocation = JSON.stringify(fieldName); - var item = fields[fieldName]; - if (item !== undefined) { - if (item.t.d && schema.d) { - return schema.d(item.t.c); - } + var _item = fields[fieldName]; + if (_item !== undefined) { throw new Error("[rescript-schema] " + ("The field " + inlinedLocation + " defined twice with incompatible schemas")); } - var schema$1 = schema.d ? factory$3(schema.d) : schema; var item_p = "[" + inlinedLocation + "]"; - var item$1 = { - t: schema$1, + var item = { + t: schema, p: item_p, l: fieldName, i: inlinedLocation, s: itemSymbol }; - fields[fieldName] = item$1; - items.push(item$1); - if (schema$1.d) { - return schema$1.t.definition; - } else { - return item$1; - } + fields[fieldName] = item; + items.push(item); + return item; }; var tag = function (tag$1, asValue) { field(tag$1, literal(asValue)); @@ -1747,15 +1726,12 @@ function factory$3(definer) { return field(fieldName, getOr(factory(schema), or)); }; var nestedField = function (fieldName, nestedFieldName, schema) { - var item = fields[fieldName]; - if (item === undefined) { + var _item = fields[fieldName]; + if (_item === undefined) { return field(fieldName, factory$3(function (s) { return s.f(nestedFieldName, schema); })); } - if (item.t.d) { - return item.t.c.f(nestedFieldName, schema); - } var message = "The field " + JSON.stringify(fieldName) + " defined twice with incompatible schemas"; throw new Error("[rescript-schema] " + message); }; @@ -1781,8 +1757,6 @@ function factory$3(definer) { b: builder$1, f: typeFilter$1, i: 0, - d: definer, - c: ctx, m: empty, parseOrThrow: initialParseOrRaise, parse: jsParse, @@ -1795,11 +1769,6 @@ function factory$3(definer) { } function to(schema, definer) { - if (schema.d) { - return factory$3(function (ctx) { - return definer(schema.d(ctx)); - }); - } var item = { t: schema, p: "", @@ -1834,8 +1803,6 @@ function setUnknownKeys(schema, unknownKeys) { b: schema.b, f: schema.f, i: schema.i, - d: schema.d, - c: schema.c, m: schema.m, parseOrThrow: initialParseOrRaise, parse: jsParse, diff --git a/src/S_Core.res b/src/S_Core.res index 8afaf21..a1ed044 100644 --- a/src/S_Core.res +++ b/src/S_Core.res @@ -232,11 +232,6 @@ type rec t<'value> = { maybeTypeFilter: option<(b, ~inputVar: string) => string>, @as("i") mutable isAsyncSchema: isAsyncSchema, - @as("d") - mutable // Use char to unsafely prevent Caml_option applications - definer?: char, - @as("c") - mutable definerCtx?: char, @as("m") metadataMap: dict, @as("parseOrThrow") @@ -1700,10 +1695,6 @@ let recursive = fn => { // maybeTypeFilter (placeholder->Obj.magic)["f"] = schema.maybeTypeFilter - // Don't allow destructuring for recursive schemas - schema.definer = None - schema.definerCtx = None - let initialParseOperationBuilder = schema.builder schema.builder = Builder.make((b, ~input, ~selfSchema, ~path) => { let bb = b->B.scope @@ -2337,7 +2328,7 @@ module Object = { let typeFilter = (_b, ~inputVar) => `!${inputVar}||${inputVar}.constructor!==Object` - let rec processInputItems = (b: b, ~ctx: BuildCtx.t, ~input, ~schema, ~path) => { + let processInputItems = (b: b, ~ctx: BuildCtx.t, ~input, ~schema, ~path) => { let inputVar = b->B.Val.var(input) let items = schema->getItems @@ -2362,12 +2353,8 @@ module Object = { } let bb = b->B.scope - if isObject && schema.definer->Obj.magic { - bb->processInputItems(~ctx, ~input=itemInput, ~schema, ~path) - } else { - let itemOutput = bb->B.parse(~schema, ~input=itemInput, ~path) - b->BuildCtx.addItemOutput(~ctx, item, itemOutput) - } + let itemOutput = bb->B.parse(~schema, ~input=itemInput, ~path) + b->BuildCtx.addItemOutput(~ctx, item, itemOutput) // Parse literal fields first, because they are most often used as discriminants if isLiteral { @@ -2450,8 +2437,6 @@ module Object = { let itemSchema = item.schema if itemSchema->isLiteralSchema { b->B.embed(itemSchema->Literal.unsafeFromSchema->Literal.value) - } else if isRootObject && itemSchema.definer->Obj.magic { - schemaOutput(~isObject=true, ~schema=itemSchema, ~path=path->Path.concat(item.path)) } else { b->B.invalidOperation( ~path, @@ -2573,32 +2558,29 @@ module Object = { and to = { (schema: t<'value>, definer: 'value => 'variant): t<'variant> => { let schema = schema->toUnknown - if schema.definer->Obj.magic { - factory((ctx => definer((schema.definer->Obj.magic)(ctx)))->Obj.magic) - } else { - let item: item = { - schema, - path: Path.empty, - location: "", - inlinedLocation: `""`, - symbol: itemSymbol, - } - let definition: unknown = definer(item->Obj.magic)->Obj.magic - makeSchema( - ~name=schema.name, - ~tagged=schema.tagged, - ~builder=Builder.make((b, ~input, ~selfSchema, ~path) => { - let ctx = BuildCtx.make(~selfSchema) - let itemOutput = b->B.parse(~schema, ~input, ~path) - b->BuildCtx.addItemOutput(~ctx, item, itemOutput) - b->BuildCtx.toOutputVal(~ctx, ~outputDefinition=definition) - }), - ~maybeTypeFilter=schema.maybeTypeFilter, - ~metadataMap=schema.metadataMap, - ~reverse=reverse(~definition, ~toItem=item), - ) + let item: item = { + schema, + path: Path.empty, + location: "", + inlinedLocation: `""`, + symbol: itemSymbol, } + let definition: unknown = definer(item->Obj.magic)->Obj.magic + + makeSchema( + ~name=schema.name, + ~tagged=schema.tagged, + ~builder=Builder.make((b, ~input, ~selfSchema, ~path) => { + let ctx = BuildCtx.make(~selfSchema) + let itemOutput = b->B.parse(~schema, ~input, ~path) + b->BuildCtx.addItemOutput(~ctx, item, itemOutput) + b->BuildCtx.toOutputVal(~ctx, ~outputDefinition=definition) + }), + ~maybeTypeFilter=schema.maybeTypeFilter, + ~metadataMap=schema.metadataMap, + ~reverse=reverse(~definition, ~toItem=item), + ) } } and factory: @@ -2609,11 +2591,11 @@ module Object = { let ctx = { let flatten = schema => { - if schema.definer->Obj.magic { - (schema.definer->Obj.magic)(%raw(`this`)) - } else { - InternalError.panic(`The ${schema.name()} schema can't be flattened`) - } + // if schema.definer->Obj.magic { + // (schema.definer->Obj.magic)(%raw(`this`)) + // } else { + InternalError.panic(`The ${schema.name()} schema can't be flattened`) + // } } let field: @@ -2622,22 +2604,22 @@ module Object = { let schema = schema->toUnknown let inlinedLocation = fieldName->Stdlib.Inlined.Value.fromString switch fields->Stdlib.Dict.unsafeGetOption(fieldName) { - | Some(item: item) => - if item.schema.definer->Obj.magic && schema.definer->Obj.magic { - (schema.definer->Obj.magic)(item.schema.definerCtx->Obj.magic)->( - Obj.magic: unknown => value - ) - } else { - InternalError.panic( - `The field ${inlinedLocation} defined twice with incompatible schemas`, - ) - } + | Some(_item: item) => + // if item.schema.definer->Obj.magic && schema.definer->Obj.magic { + // (schema.definer->Obj.magic)(item.schema.definerCtx->Obj.magic)->( + // Obj.magic: unknown => value + // ) + // } else { + InternalError.panic( + `The field ${inlinedLocation} defined twice with incompatible schemas`, + ) + // } | None => { - let schema = if schema.definer->Obj.magic { - factory(schema.definer->Obj.magic) - } else { - schema - } + // let schema = if schema.definer->Obj.magic { + // factory(schema.definer->Obj.magic) + // } else { + // schema + // } let item: item = { schema, location: fieldName, @@ -2647,11 +2629,11 @@ module Object = { } fields->Js.Dict.set(fieldName, item) items->Js.Array2.push(item)->ignore - if schema.definer->Obj.magic { - schema->getOutputDefinition->(Obj.magic: unknown => value) - } else { - item->(Obj.magic: item => value) - } + // if schema.definer->Obj.magic { + // schema->getOutputDefinition->(Obj.magic: unknown => value) + // } else { + item->(Obj.magic: item => value) + // } } } } @@ -2669,17 +2651,17 @@ module Object = { (fieldName, nestedFieldName, schema) => { let schema = schema->toUnknown switch fields->Stdlib.Dict.unsafeGetOption(fieldName) { - | Some(item: item) => - if item.schema.definer->Obj.magic { - (item.schema.definerCtx->(Obj.magic: option => ctx)).field( - nestedFieldName, - schema, - )->(Obj.magic: unknown => value) - } else { - InternalError.panic( - `The field ${fieldName->Stdlib.Inlined.Value.fromString} defined twice with incompatible schemas`, - ) - } + | Some(_item: item) => + // if item.schema.definer->Obj.magic { + // (item.schema.definerCtx->(Obj.magic: option => ctx)).field( + // nestedFieldName, + // schema, + // )->(Obj.magic: unknown => value) + // } else { + InternalError.panic( + `The field ${fieldName->Stdlib.Inlined.Value.fromString} defined twice with incompatible schemas`, + ) + // } | None => field(fieldName, factory(s => s.field(nestedFieldName, schema)))->( Obj.magic: unknown => value @@ -2713,8 +2695,8 @@ module Object = { maybeTypeFilter: Some(typeFilter), name, metadataMap: Metadata.Map.empty, - definer: definer->Obj.magic, - definerCtx: ctx->Obj.magic, + // definer: definer->Obj.magic, + // definerCtx: ctx->Obj.magic, parseOrRaise: initialParseOrRaise, serializeToUnknownOrRaise: initialSerializeToUnknownOrRaise, serializeOrRaise: initialSerializeOrRaise, @@ -2741,8 +2723,8 @@ module Object = { maybeTypeFilter: schema.maybeTypeFilter, isAsyncSchema: schema.isAsyncSchema, metadataMap: schema.metadataMap, - definer: ?schema.definer, - definerCtx: ?schema.definerCtx, + // definer: ?schema.definer, + // definerCtx: ?schema.definerCtx, parseOrRaise: initialParseOrRaise, serializeToUnknownOrRaise: initialSerializeToUnknownOrRaise, serializeOrRaise: initialSerializeOrRaise, From 706daeda871d7876cefa29a2266c40bf08dd114b Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 16 Oct 2024 16:39:35 +0900 Subject: [PATCH 2/3] Add Proxy instead of symbol item field --- src/S.resi | 2 - src/S_Core.bs.mjs | 76 +++++++++++++++----------- src/S_Core.res | 132 ++++++++++++++++++++++++++-------------------- src/S_Core.resi | 2 - 4 files changed, 120 insertions(+), 92 deletions(-) diff --git a/src/S.resi b/src/S.resi index 3deb793..3166178 100644 --- a/src/S.resi +++ b/src/S.resi @@ -63,8 +63,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and schema<'value> = t<'value> and error = private {operation: operation, code: errorCode, path: Path.t} diff --git a/src/S_Core.bs.mjs b/src/S_Core.bs.mjs index 077c969..63e75a2 100644 --- a/src/S_Core.bs.mjs +++ b/src/S_Core.bs.mjs @@ -7,6 +7,8 @@ import * as Caml_option from "rescript/lib/es6/caml_option.js"; import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js"; import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js"; +var immutableEmpty = {}; + function fromString(string) { return JSON.stringify(string); } @@ -1496,21 +1498,21 @@ function addItemOutput(b, ctx, item, output) { } function toOutputVal(b, ctx, outputDefinition) { - var definitionToValue = function (definition, outputPath) { - var val = ctx.o.get(definition); - if (val !== undefined) { - return inline(b, val); - } + var definitionToValue = function (definition) { if (!(typeof definition === "object" && definition !== null)) { return "e[" + (b.g.e.push(definition) - 1) + "]"; } + var val = ctx.o.get(definition[itemSymbol]); + if (val !== undefined) { + return inline(b, val); + } var isArray = Array.isArray(definition); var keys = Object.keys(definition); var codeRef = isArray ? "[" : "{"; for(var idx = 0 ,idx_finish = keys.length; idx < idx_finish; ++idx){ var key = keys[idx]; var definition$1 = definition[key]; - var output = definitionToValue(definition$1, outputPath + ("[" + JSON.stringify(key) + "]")); + var output = definitionToValue(definition$1); if (idx !== 0) { codeRef = codeRef + ","; } @@ -1522,7 +1524,7 @@ function toOutputVal(b, ctx, outputDefinition) { isArray ? "]" : "}" ); }; - var syncOutput = definitionToValue(outputDefinition, ""); + var syncOutput = definitionToValue(outputDefinition); if (ctx.a === 0) { return val(b, syncOutput); } @@ -1600,6 +1602,18 @@ function name() { }).join(", ") + "})"; } +function proxify(item) { + return new Proxy(immutableEmpty, { + get: (function (param, prop) { + if (prop === itemSymbol) { + return item; + } else { + return (void 0); + } + }) + }); +} + function reverse$1(inputDefinition, toItem) { return function () { var original = this; @@ -1617,19 +1631,20 @@ function reverse$1(inputDefinition, toItem) { var embededOutputs = new WeakMap(); var definitionToOutput = function (definition, outputPath) { if (typeof definition === "object" && definition !== null) { - if (definition.s === itemSymbol) { - var embededOutput = embededOutputs.get(definition); + var item = definition[itemSymbol]; + if (item !== undefined) { + var embededOutput = embededOutputs.get(item); if (embededOutput !== undefined) { var itemInput = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema = definition.t.r(); + var schema = item.t.r(); var itemOutput = schema.b(b, itemInput, schema, path + outputPath); - b.c = b.c + ("if(" + $$var(b, embededOutput) + "!==" + $$var(b, itemOutput) + "){" + fail(b, "Multiple sources provided not equal data for " + definition.i, path) + "}"); + b.c = b.c + ("if(" + $$var(b, embededOutput) + "!==" + $$var(b, itemOutput) + "){" + fail(b, "Multiple sources provided not equal data for " + item.i, path) + "}"); return ; } var itemInput$1 = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema$1 = definition.t.r(); + var schema$1 = item.t.r(); var itemOutput$1 = schema$1.b(b, itemInput$1, schema$1, path + outputPath); - embededOutputs.set(definition, itemOutput$1); + embededOutputs.set(item, itemOutput$1); return ; } var keys = Object.keys(definition); @@ -1712,12 +1727,11 @@ function factory$3(definer) { t: schema, p: item_p, l: fieldName, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; fields[fieldName] = item; items.push(item); - return item; + return proxify(item); }; var tag = function (tag$1, asValue) { field(tag$1, literal(asValue)); @@ -1773,10 +1787,9 @@ function to(schema, definer) { t: schema, p: "", l: "", - i: "\"\"", - s: itemSymbol + i: "\"\"" }; - var definition = definer(item); + var definition = definer(proxify(item)); return makeSchema(schema.n, schema.t, schema.m, (function (b, input, selfSchema, path) { var ctx = make$1(selfSchema); var itemOutput = schema.b(b, input, schema, path); @@ -1843,11 +1856,10 @@ function factory$4(definer) { t: schema, p: item_p, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item$1; - return item$1; + return proxify(item$1); }; var tag = function (idx, asValue) { item(idx, literal(asValue)); @@ -1867,8 +1879,7 @@ function factory$4(definer) { t: unit, p: item_p, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item$1; } @@ -2323,8 +2334,7 @@ function definitionToSchema(definition) { t: schema, p: item_p, l: $$location, - i: inlinedLocation, - s: itemSymbol + i: inlinedLocation }; items[idx] = item; if (!isTransformed && schema !== schema.r()) { @@ -2332,17 +2342,19 @@ function definitionToSchema(definition) { } } + var definition$1 = items.map(proxify); var length = items.length; return makeSchema(name$1, { TAG: "Tuple", items: items, - definition: items + definition: definition$1 }, empty, builder$1, (function (b, inputVar) { return typeFilter(b, inputVar) + ("||" + inputVar + ".length!==" + length); - }), isTransformed ? reverse$1(items, undefined) : toSelf); + }), isTransformed ? reverse$1(definition$1, undefined) : toSelf); } var items$1 = []; var fields = {}; + var definition$2 = {}; var fieldNames = Object.keys(definition); var isTransformed$1 = false; for(var idx$1 = 0 ,idx_finish$1 = fieldNames.length; idx$1 < idx_finish$1; ++idx$1){ @@ -2354,11 +2366,11 @@ function definitionToSchema(definition) { t: schema$1, p: item_p$1, l: $$location$1, - i: inlinedLocation$1, - s: itemSymbol + i: inlinedLocation$1 }; items$1[idx$1] = item$1; fields[$$location$1] = item$1; + definition$2[$$location$1] = proxify(item$1); if (!isTransformed$1 && schema$1 !== schema$1.r()) { isTransformed$1 = true; } @@ -2369,8 +2381,8 @@ function definitionToSchema(definition) { items: items$1, fields: fields, unknownKeys: globalConfig.u, - definition: fields - }, empty, builder$1, typeFilter$1, isTransformed$1 ? reverse$1(fields, undefined) : toSelf); + definition: definition$2 + }, empty, builder$1, typeFilter$1, isTransformed$1 ? reverse$1(definition$2, undefined) : toSelf); } function matches(schema) { diff --git a/src/S_Core.res b/src/S_Core.res index a1ed044..c8165d3 100644 --- a/src/S_Core.res +++ b/src/S_Core.res @@ -11,6 +11,13 @@ module Obj = { } module Stdlib = { + module Proxy = { + type traps<'a> = {get?: (~target: 'a, ~prop: unknown) => unknown} + + @new + external make: ('a, traps<'a>) => 'a = "Proxy" + } + module Option = { external unsafeUnwrap: option<'a> => 'a = "%identity" } @@ -40,6 +47,8 @@ module Stdlib = { } module Object = { + let immutableEmpty = %raw(`{}`) + @val external internalClass: Js.Types.obj_val => string = "Object.prototype.toString.call" } @@ -91,6 +100,9 @@ module Stdlib = { @get_index external unsafeGetOption: (dict<'a>, string) => option<'a> = "" + @get_index + external unsafeGetOptionBySymbol: (dict<'a>, Js.Types.symbol) => option<'a> = "" + @inline let has = (dict, key) => { dict->Js.Dict.unsafeGet(key)->(Obj.magic: 'a => bool) @@ -274,8 +286,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and builder = (b, ~input: val, ~selfSchema: schema, ~path: Path.t) => val and val = { @@ -1969,11 +1979,11 @@ module Definition = { definition->Stdlib.Type.typeof === #object && definition !== %raw(`null`) let toConstant = (Obj.magic: t<'embeded> => unknown) - let toEmbeded = (Obj.magic: t<'embeded> => 'embeded) let toNode = (Obj.magic: t<'embeded> => node<'embeded>) @inline - let isEmbededItem = definition => (definition->toEmbeded).symbol === itemSymbol + let toEmbededItem = (definition: t<'embeded>): option => + definition->Obj.magic->Stdlib.Dict.unsafeGetOptionBySymbol(itemSymbol) } module Option = { @@ -2276,41 +2286,41 @@ module Object = { let toOutputVal = (b, ~ctx: t, ~outputDefinition) => { let syncOutput = { - let rec definitionToValue = (definition: Definition.t, ~outputPath) => { - switch ctx.outputs->Stdlib.WeakMap.get(definition->Definition.toEmbeded) { - | Some(val) => b->B.Val.inline(val) - | None => - if definition->Definition.isNode { - let node = definition->Definition.toNode - let isArray = Stdlib.Array.isArray(node) - let keys = node->Js.Dict.keys - - let codeRef = ref(isArray ? "[" : "{") - for idx in 0 to keys->Js.Array2.length - 1 { - let key = keys->Js.Array2.unsafe_get(idx) - let definition = node->Js.Dict.unsafeGet(key) - let output = - definition->definitionToValue( - ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), - ) - if idx !== 0 { - codeRef := codeRef.contents ++ "," + let rec definitionToValue = (definition: Definition.t) => { + if definition->Definition.isNode { + switch ctx.outputs->Stdlib.WeakMap.get( + definition->Definition.toEmbededItem->Stdlib.Option.unsafeUnwrap, + ) { + | Some(val) => b->B.Val.inline(val) + | None => { + let node = definition->Definition.toNode + let isArray = Stdlib.Array.isArray(node) + let keys = node->Js.Dict.keys + + let codeRef = ref(isArray ? "[" : "{") + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + let output = definition->definitionToValue + if idx !== 0 { + codeRef := codeRef.contents ++ "," + } + codeRef := + codeRef.contents ++ ( + isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` + ) } - codeRef := - codeRef.contents ++ ( - isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` - ) + codeRef.contents ++ (isArray ? "]" : "}") } - codeRef.contents ++ (isArray ? "]" : "}") - } else { - let constant = definition->Definition.toConstant - b->B.embed(constant) } + } else { + let constant = definition->Definition.toConstant + b->B.embed(constant) } } outputDefinition ->(Obj.magic: unknown => Definition.t) - ->definitionToValue(~outputPath=Path.empty) + ->definitionToValue } if ctx.asyncOutputs === None { @@ -2455,6 +2465,17 @@ module Object = { } } + let proxify = (item: item): 'a => + Stdlib.Object.immutableEmpty->Stdlib.Proxy.make({ + get: (~target as _, ~prop) => { + if prop === itemSymbol->Obj.magic { + item->Obj.magic + } else { + %raw(`void 0`) + } + }, + }) + let rec reverse = (~definition as inputDefinition, ~toItem=?) => () => { let original = %raw(`this`) let inputDefinition = inputDefinition->(Obj.magic: unknown => Definition.t) @@ -2482,8 +2503,8 @@ module Object = { let rec definitionToOutput = (definition: Definition.t, ~outputPath) => { if definition->Definition.isNode { - if definition->Definition.isEmbededItem { - let item = definition->Definition.toEmbeded + switch definition->Definition.toEmbededItem { + | Some(item) => switch embededOutputs->Stdlib.WeakMap.get(item) { | Some(embededOutput) => { let {schema} = item @@ -2517,16 +2538,17 @@ module Object = { embededOutputs->Stdlib.WeakMap.set(item, itemOutput)->ignore } } - } else { - let node = definition->Definition.toNode - let keys = node->Js.Dict.keys - for idx in 0 to keys->Js.Array2.length - 1 { - let key = keys->Js.Array2.unsafe_get(idx) - let definition = node->Js.Dict.unsafeGet(key) - definitionToOutput( - definition, - ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), - ) + | None => { + let node = definition->Definition.toNode + let keys = node->Js.Dict.keys + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + definitionToOutput( + definition, + ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + ) + } } } } else { @@ -2564,9 +2586,10 @@ module Object = { path: Path.empty, location: "", inlinedLocation: `""`, - symbol: itemSymbol, } - let definition: unknown = definer(item->Obj.magic)->Obj.magic + let definition: unknown = definer(item->proxify)->Obj.magic + + // let outputs = makeSchema( ~name=schema.name, @@ -2625,14 +2648,13 @@ module Object = { location: fieldName, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } fields->Js.Dict.set(fieldName, item) items->Js.Array2.push(item)->ignore // if schema.definer->Obj.magic { // schema->getOutputDefinition->(Obj.magic: unknown => value) // } else { - item->(Obj.magic: item => value) + item->proxify // } } } @@ -2782,10 +2804,9 @@ module Tuple = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) - item->(Obj.magic: item => value) + item->Object.proxify } } @@ -2811,7 +2832,6 @@ module Tuple = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) } @@ -3467,19 +3487,18 @@ module Schema = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) if !isTransformed.contents && schema !== schema.reverse() { isTransformed := true } } - let definition = items->(Obj.magic: array => unknown) + let definition = items->Js.Array2.map(Object.proxify)->(Obj.magic: array => unknown) makeSchema( ~name=Tuple.name, ~tagged=Tuple({ items, - definition, + definition, // FIXME: stop passing definition here }), ~builder=Object.builder, ~maybeTypeFilter=Some(Tuple.typeFilter(~length=items->Js.Array2.length)), @@ -3490,6 +3509,7 @@ module Schema = { let node = definition->(Obj.magic: unknown => dict) let items = [] let fields = Js.Dict.empty() + let definition = Js.Dict.empty() let fieldNames = node->Js.Dict.keys let isTransformed = ref(false) for idx in 0 to fieldNames->Js.Array2.length - 1 { @@ -3501,15 +3521,15 @@ module Schema = { location, inlinedLocation, path: inlinedLocation->Path.fromInlinedLocation, - symbol: itemSymbol, } items->Js.Array2.unsafe_set(idx, item) fields->Js.Dict.set(location, item) + definition->Js.Dict.set(location, item->Object.proxify) // FIXME: remove if !isTransformed.contents && schema !== schema.reverse() { isTransformed := true } } - let definition = fields->(Obj.magic: dict => unknown) + let definition = definition->(Obj.magic: dict => unknown) makeSchema( ~name=Object.name, ~tagged=Object({ diff --git a/src/S_Core.resi b/src/S_Core.resi index 2319a7c..9b1c344 100644 --- a/src/S_Core.resi +++ b/src/S_Core.resi @@ -63,8 +63,6 @@ and item = { location: string, @as("i") inlinedLocation: string, - @as("s") - symbol: Js.Types.symbol, } and schema<'value> = t<'value> and error = private {operation: operation, code: errorCode, path: Path.t} From 3cc49b339faa74acb1422dadcf5e04f1ac8918df Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Wed, 16 Oct 2024 18:03:06 +0900 Subject: [PATCH 3/3] Experiment with output items (failure so far) --- src/S.resi | 2 +- src/S_Core.bs.mjs | 84 ++++++++++++++++++++++++---------- src/S_Core.res | 114 +++++++++++++++++++++++++++++++++++++++++++--- src/S_Core.resi | 2 +- 4 files changed, 170 insertions(+), 32 deletions(-) diff --git a/src/S.resi b/src/S.resi index 3166178..239080f 100644 --- a/src/S.resi +++ b/src/S.resi @@ -55,7 +55,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t, diff --git a/src/S_Core.bs.mjs b/src/S_Core.bs.mjs index 63e75a2..a4c66fe 100644 --- a/src/S_Core.bs.mjs +++ b/src/S_Core.bs.mjs @@ -1498,7 +1498,7 @@ function addItemOutput(b, ctx, item, output) { } function toOutputVal(b, ctx, outputDefinition) { - var definitionToValue = function (definition) { + var definitionToValue = function (definition, outputPath) { if (!(typeof definition === "object" && definition !== null)) { return "e[" + (b.g.e.push(definition) - 1) + "]"; } @@ -1512,7 +1512,7 @@ function toOutputVal(b, ctx, outputDefinition) { for(var idx = 0 ,idx_finish = keys.length; idx < idx_finish; ++idx){ var key = keys[idx]; var definition$1 = definition[key]; - var output = definitionToValue(definition$1); + var output = definitionToValue(definition$1, outputPath + ("[" + JSON.stringify(key) + "]")); if (idx !== 0) { codeRef = codeRef + ","; } @@ -1524,7 +1524,7 @@ function toOutputVal(b, ctx, outputDefinition) { isArray ? "]" : "}" ); }; - var syncOutput = definitionToValue(outputDefinition); + var syncOutput = definitionToValue(outputDefinition, ""); if (ctx.a === 0) { return val(b, syncOutput); } @@ -1547,7 +1547,7 @@ function processInputItems(b, ctx, input, schema, path) { b.c = ""; var item = items[idx]; var itemPath = item.p; - var schema$1 = item.t; + var schema$1 = item.s; var itemInput = val(b, inputVar + itemPath); var path$1 = path + itemPath; var isLiteral = schema$1.t.TAG === "Literal"; @@ -1598,7 +1598,7 @@ function builder$1(b, input, selfSchema, path) { function name() { var schema = this; return "Object({" + schema.t.items.map(function (item) { - return item.i + ": " + item.t.n(); + return item.i + ": " + item.s.n(); }).join(", ") + "})"; } @@ -1636,13 +1636,13 @@ function reverse$1(inputDefinition, toItem) { var embededOutput = embededOutputs.get(item); if (embededOutput !== undefined) { var itemInput = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema = item.t.r(); + var schema = item.s.r(); var itemOutput = schema.b(b, itemInput, schema, path + outputPath); b.c = b.c + ("if(" + $$var(b, embededOutput) + "!==" + $$var(b, itemOutput) + "){" + fail(b, "Multiple sources provided not equal data for " + item.i, path) + "}"); return ; } var itemInput$1 = outputPath === "" ? input : val(b, inputVar + outputPath); - var schema$1 = item.t.r(); + var schema$1 = item.s.r(); var itemOutput$1 = schema$1.b(b, itemInput$1, schema$1, path + outputPath); embededOutputs.set(item, itemOutput$1); return ; @@ -1669,7 +1669,7 @@ function reverse$1(inputDefinition, toItem) { b.c = ctx.d + b.c; var isRootObject = original.t.TAG === "Object"; var fallbackOutput = function (item, path) { - var itemSchema = item.t; + var itemSchema = item.s; if (itemSchema.t.TAG !== "Literal") { return invalidOperation(b, path, "Schema for " + item.i + " isn't registered"); } @@ -1724,7 +1724,7 @@ function factory$3(definer) { } var item_p = "[" + inlinedLocation + "]"; var item = { - t: schema, + s: schema, p: item_p, l: fieldName, i: inlinedLocation @@ -1784,12 +1784,48 @@ function factory$3(definer) { function to(schema, definer) { var item = { - t: schema, + s: schema, p: "", l: "", i: "\"\"" }; var definition = definer(proxify(item)); + var outputItems = []; + var traverseDefinition = function (definition, path) { + if (typeof definition === "object" && definition !== null) { + var item = definition[itemSymbol]; + if (item !== undefined) { + outputItems.push({ + TAG: "Target", + path: path, + item: item + }); + return ; + } + var isArray = Array.isArray(definition); + outputItems.push(isArray ? ({ + TAG: "Tuple", + path: path + }) : ({ + TAG: "Object", + path: path + })); + var keys = Object.keys(definition); + for(var idx = 0 ,idx_finish = keys.length; idx < idx_finish; ++idx){ + var key = keys[idx]; + var definition$1 = definition[key]; + traverseDefinition(definition$1, path + ("[" + JSON.stringify(key) + "]")); + } + outputItems.push("ExitNode"); + return ; + } + outputItems.push({ + TAG: "Constant", + path: path, + value: definition + }); + }; + traverseDefinition(definition, ""); return makeSchema(schema.n, schema.t, schema.m, (function (b, input, selfSchema, path) { var ctx = make$1(selfSchema); var itemOutput = schema.b(b, input, schema, path); @@ -1839,7 +1875,7 @@ function strict(schema) { function name$1() { var schema = this; return "Tuple(" + schema.t.items.map(function (item) { - return item.t.n(); + return item.s.n(); }).join(", ") + ")"; } @@ -1853,7 +1889,7 @@ function factory$4(definer) { } var item_p = "[" + inlinedLocation + "]"; var item$1 = { - t: schema, + s: schema, p: item_p, l: $$location, i: inlinedLocation @@ -1876,7 +1912,7 @@ function factory$4(definer) { var inlinedLocation = "\"" + $$location + "\""; var item_p = "[" + inlinedLocation + "]"; var item$1 = { - t: unit, + s: unit, p: item_p, l: $$location, i: inlinedLocation @@ -2329,10 +2365,10 @@ function definitionToSchema(definition) { var $$location = idx.toString(); var inlinedLocation = "\"" + $$location + "\""; var schema = definitionToSchema(definition[idx]); - var item_p = "[" + inlinedLocation + "]"; + var path = "[" + inlinedLocation + "]"; var item = { - t: schema, - p: item_p, + s: schema, + p: path, l: $$location, i: inlinedLocation }; @@ -2361,10 +2397,10 @@ function definitionToSchema(definition) { var $$location$1 = fieldNames[idx$1]; var inlinedLocation$1 = "\"" + $$location$1 + "\""; var schema$1 = definitionToSchema(definition[$$location$1]); - var item_p$1 = "[" + inlinedLocation$1 + "]"; + var item_p = "[" + inlinedLocation$1 + "]"; var item$1 = { - t: schema$1, - p: item_p$1, + s: schema$1, + p: item_p, l: $$location$1, i: inlinedLocation$1 }; @@ -2513,7 +2549,7 @@ function internalInline(schema, maybeVariant, param) { case "Object" : var items = literal.items; inlinedSchema = items.length !== 0 ? "S.object(s =>\n {\n " + items.map(function (item) { - return item.i + ": s.field(" + item.i + ", " + internalInline(item.t, undefined, undefined) + ")"; + return item.i + ": s.field(" + item.i + ", " + internalInline(item.s, undefined, undefined) + ")"; }).join(",\n ") + ",\n }\n)" : "S.object(_ => ())"; break; case "Tuple" : @@ -2529,25 +2565,25 @@ function internalInline(schema, maybeVariant, param) { break; case 1 : var i1 = tupleSchemas[0]; - inlinedSchema = "S.tuple1(" + internalInline(i1.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple1(" + internalInline(i1.s, undefined, undefined) + ")"; break; case 2 : var i1$1 = tupleSchemas[0]; var i2 = tupleSchemas[1]; - inlinedSchema = "S.tuple2(" + internalInline(i1$1.t, undefined, undefined) + ", " + internalInline(i2.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple2(" + internalInline(i1$1.s, undefined, undefined) + ", " + internalInline(i2.s, undefined, undefined) + ")"; break; case 3 : var i1$2 = tupleSchemas[0]; var i2$1 = tupleSchemas[1]; var i3 = tupleSchemas[2]; - inlinedSchema = "S.tuple3(" + internalInline(i1$2.t, undefined, undefined) + ", " + internalInline(i2$1.t, undefined, undefined) + ", " + internalInline(i3.t, undefined, undefined) + ")"; + inlinedSchema = "S.tuple3(" + internalInline(i1$2.s, undefined, undefined) + ", " + internalInline(i2$1.s, undefined, undefined) + ", " + internalInline(i3.s, undefined, undefined) + ")"; break; } } if (exit === 1) { inlinedSchema = "S.tuple(s => (" + tupleSchemas.map(function (item, idx) { - return "s.item(" + idx + ", " + internalInline(item.t, undefined, undefined) + ")"; + return "s.item(" + idx + ", " + internalInline(item.s, undefined, undefined) + ")"; }).join(", ") + "))"; } break; diff --git a/src/S_Core.res b/src/S_Core.res index c8165d3..0e45db2 100644 --- a/src/S_Core.res +++ b/src/S_Core.res @@ -278,7 +278,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t, @@ -2243,6 +2243,13 @@ module Object = { let getItems = (schema): array => (schema->classify->Obj.magic)["items"] let getOutputDefinition = schema => (schema->classify->Obj.magic)["definition"] + type outputItem = + | ExitNode + | Object({path: Path.t}) + | Tuple({path: Path.t}) + | Target({path: Path.t, item: item}) + | Constant({path: Path.t, value: unknown}) + module BuildCtx = { // type inputKind = | @as(0) Any | @as(1) Object | @as(2) Tuple @unboxed @@ -2286,7 +2293,7 @@ module Object = { let toOutputVal = (b, ~ctx: t, ~outputDefinition) => { let syncOutput = { - let rec definitionToValue = (definition: Definition.t) => { + let rec definitionToValue = (definition: Definition.t, ~outputPath) => { if definition->Definition.isNode { switch ctx.outputs->Stdlib.WeakMap.get( definition->Definition.toEmbededItem->Stdlib.Option.unsafeUnwrap, @@ -2301,7 +2308,10 @@ module Object = { for idx in 0 to keys->Js.Array2.length - 1 { let key = keys->Js.Array2.unsafe_get(idx) let definition = node->Js.Dict.unsafeGet(key) - let output = definition->definitionToValue + let output = + definition->definitionToValue( + ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + ) if idx !== 0 { codeRef := codeRef.contents ++ "," } @@ -2320,7 +2330,7 @@ module Object = { } outputDefinition ->(Obj.magic: unknown => Definition.t) - ->definitionToValue + ->definitionToValue(~outputPath=Path.empty) } if ctx.asyncOutputs === None { @@ -2334,6 +2344,71 @@ module Object = { ) } } + + let toOutputVal2 = (b, ~ctx: t, ~outputItems: array) => { + // let syncOutput = { + // let rec definitionToValue = (definition: Definition.t, ~outputPath) => { + // if definition->Definition.isNode { + // switch ctx.outputs->Stdlib.WeakMap.get( + // definition->Definition.toEmbededItem->Stdlib.Option.unsafeUnwrap, + // ) { + // | Some(val) => b->B.Val.inline(val) + // | None => { + // let node = definition->Definition.toNode + // let isArray = Stdlib.Array.isArray(node) + // let keys = node->Js.Dict.keys + + // let codeRef = ref(isArray ? "[" : "{") + // for idx in 0 to keys->Js.Array2.length - 1 { + // let key = keys->Js.Array2.unsafe_get(idx) + // let definition = node->Js.Dict.unsafeGet(key) + // let output = + // definition->definitionToValue( + // ~outputPath=Path.concat(outputPath, Path.fromLocation(key)), + // ) + // if idx !== 0 { + // codeRef := codeRef.contents ++ "," + // } + // codeRef := + // codeRef.contents ++ ( + // isArray ? output : `${key->Stdlib.Inlined.Value.fromString}:${output}` + // ) + // } + // codeRef.contents ++ (isArray ? "]" : "}") + // } + // } + // } else { + // let constant = definition->Definition.toConstant + // b->B.embed(constant) + // } + // } + // outputDefinition + // ->(Obj.magic: unknown => Definition.t) + // ->definitionToValue(~outputPath=Path.empty) + // } + + let syncOutput = ref("") + let currentPath = ref(Path.empty) + + for idx in 0 to outputItems->Js.Array2.length - 1 { + let outputItem = outputItems->Js.Array2.unsafe_get(idx) + switch outputItem { + | Constant({path, value}) => b->B.embed(value) + | ExitNode => () + } + } + + if ctx.asyncOutputs === None { + b->B.val(syncOutput.contents) + } else { + let asyncOutputs = ctx.asyncOutputs->(Obj.magic: asyncOutputs => array) + b->B.asyncVal( + `Promise.all([${asyncOutputs + ->Js.Array2.map(val => b->B.Val.inline(val)) + ->Js.Array2.toString}]).then(a=>(${syncOutput.contents}))`, + ) + } + } } let typeFilter = (_b, ~inputVar) => `!${inputVar}||${inputVar}.constructor!==Object` @@ -2589,7 +2664,33 @@ module Object = { } let definition: unknown = definer(item->proxify)->Obj.magic - // let outputs = + let outputItems: array = [] + + let rec traverseDefinition = (definition: Definition.t, ~path) => { + if definition->Definition.isNode { + switch definition->Definition.toEmbededItem { + | Some(item) => outputItems->Js.Array2.push(Target({path, item}))->ignore + | None => { + let node = definition->Definition.toNode + let isArray = Stdlib.Array.isArray(node) + outputItems + ->Js.Array2.push(isArray ? Tuple({path: path}) : Object({path: path})) + ->ignore + let keys = node->Js.Dict.keys + for idx in 0 to keys->Js.Array2.length - 1 { + let key = keys->Js.Array2.unsafe_get(idx) + let definition = node->Js.Dict.unsafeGet(key) + definition->traverseDefinition(~path=Path.concat(path, Path.fromLocation(key))) + } + outputItems->Js.Array2.push(ExitNode)->ignore + } + } + } else { + let constant = definition->Definition.toConstant + outputItems->Js.Array2.push(Constant({path, value: constant}))->ignore + } + } + definition->Obj.magic->traverseDefinition(~path=Path.empty) makeSchema( ~name=schema.name, @@ -3482,11 +3583,12 @@ module Schema = { let location = idx->Js.Int.toString let inlinedLocation = `"${location}"` let schema = node->Js.Array2.unsafe_get(idx)->definitionToSchema + let path = inlinedLocation->Path.fromInlinedLocation let item: item = { schema, location, inlinedLocation, - path: inlinedLocation->Path.fromInlinedLocation, + path, } items->Js.Array2.unsafe_set(idx, item) if !isTransformed.contents && schema !== schema.reverse() { diff --git a/src/S_Core.resi b/src/S_Core.resi index 9b1c344..0a07d0b 100644 --- a/src/S_Core.resi +++ b/src/S_Core.resi @@ -55,7 +55,7 @@ and tagged = | Dict(t) | JSON({validated: bool}) and item = { - @as("t") + @as("s") schema: schema, @as("p") path: Path.t,