Skip to content

Commit

Permalink
add optimizer v2
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Jul 27, 2023
1 parent 17bb4c2 commit 592b3e7
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 92 deletions.
53 changes: 27 additions & 26 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Serializer = require('./lib/serializer')
const Validator = require('./lib/validator')
const RefResolver = require('./lib/ref-resolver')
const Location = require('./lib/location')
const optimize = require('./lib/optimize')

let largeArraySize = 2e4
let largeArrayMechanism = 'default'
Expand All @@ -27,6 +28,18 @@ const validLargeArrayMechanisms = [
'json-stringify'
]

const serializerFns = `
const {
asString,
asInteger,
asNumber,
asBoolean,
asDateTime,
asDate,
asTime,
} = serializer
`

const addComma = '!addComma && (addComma = true) || (json += \',\')'

function isValidSchema (schema, name) {
Expand Down Expand Up @@ -119,21 +132,8 @@ function build (schema, options) {
const location = new Location(schema, context.rootSchemaId)
const code = buildValue(context, location, 'input')

let contextFunctionCode

// If we have only the invocation of the 'anonymous0' function, we would
// basically just wrap the 'anonymous0' function in the 'main' function and
// and the overhead of the intermediate variable 'json'. We can avoid the
// wrapping and the unnecessary memory allocation by aliasing 'anonymous0' to
// 'main'
if (code === 'json += anonymous0(input)') {
contextFunctionCode = `
${context.functions.join('\n')}
const main = anonymous0
return main
`
} else {
contextFunctionCode = `
let contextFunctionCode = `
${serializerFns}
function main (input) {
let json = ''
${code}
Expand All @@ -142,7 +142,8 @@ function build (schema, options) {
${context.functions.join('\n')}
return main
`
}

contextFunctionCode = optimize(contextFunctionCode)

const serializer = new Serializer(options)
const validator = new Validator(options.ajv)
Expand Down Expand Up @@ -263,7 +264,7 @@ function buildExtraObjectPropertiesSerializer (context, location) {
code += `
if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {
${addComma}
json += serializer.asString(key) + ':'
json += asString(key) + ':'
${buildValue(context, propertyLocation, 'value')}
continue
}
Expand All @@ -278,13 +279,13 @@ function buildExtraObjectPropertiesSerializer (context, location) {
if (additionalPropertiesSchema === true) {
code += `
${addComma}
json += serializer.asString(key) + ':' + JSON.stringify(value)
json += asString(key) + ':' + JSON.stringify(value)
`
} else {
const propertyLocation = location.getPropertyLocation('additionalProperties')
code += `
${addComma}
json += serializer.asString(key) + ':'
json += asString(key) + ':'
${buildValue(context, propertyLocation, 'value')}
`
}
Expand Down Expand Up @@ -743,21 +744,21 @@ function buildSingleTypeSerializer (context, location, input) {
return 'json += \'null\''
case 'string': {
if (schema.format === 'date-time') {
return `json += serializer.asDateTime(${input})`
return `json += asDateTime(${input})`
} else if (schema.format === 'date') {
return `json += serializer.asDate(${input})`
return `json += asDate(${input})`
} else if (schema.format === 'time') {
return `json += serializer.asTime(${input})`
return `json += asTime(${input})`
} else {
return `json += serializer.asString(${input})`
return `json += asString(${input})`
}
}
case 'integer':
return `json += serializer.asInteger(${input})`
return `json += asInteger(${input})`
case 'number':
return `json += serializer.asNumber(${input})`
return `json += asNumber(${input})`
case 'boolean':
return `json += serializer.asBoolean(${input})`
return `json += asBoolean(${input})`
case 'object': {
const funcName = buildObject(context, location)
return `json += ${funcName}(${input})`
Expand Down
89 changes: 89 additions & 0 deletions lib/optimize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict'

const returnFnRE = /^\s+return ([.a-zA-Z0-9]+)\(\w+\)$/
const fnRE = /^\s*function\s+/
const fnNameRE = /^\s+function ([a-zA-Z0-9_]+) \(input\) {$/
const jsonConcatRE = /^\s*json\s*\+=/
const letJsonRE = /^\s*let json =/
const returnJsonRE = /^\s*return json\s*$/
const returnEmptyStringRE = /^\s*return '' \+/
const closingCurlyBracketRE = /^\s*}\s*$/
/**
* @param {Array<string>} code
* @returns {Array<string>}
*/
function optimize (raw) {
const code = raw.split('\n')
/**
* @type {Array<string>}
*/
const dedupedLevel1 = []

for (let i = 0; i < code.length; i++) {
if (i > 0 && jsonConcatRE.test(code[i]) && jsonConcatRE.test(code[i - 1])) {
const mergedEntry = code[i - 1] + ' +' + code[i].substring(code[i].indexOf('json +=') + 7)
dedupedLevel1.pop() // Remove the previous entry
dedupedLevel1.push(mergedEntry)
} else {
dedupedLevel1.push(code[i])
}
}

/**
* @type {Array<string>}
*/
const dedupedLevel2 = []
for (let i = 0; i < dedupedLevel1.length; i++) {
if (i > 0 && jsonConcatRE.test(dedupedLevel1[i]) && letJsonRE.test(dedupedLevel1[i - 1])) {
const mergedEntry = dedupedLevel1[i - 1] + ' +' + dedupedLevel1[i].substring(dedupedLevel1[i].indexOf('json +=') + 7)
dedupedLevel2.pop() // Remove the previous entry
dedupedLevel2.push(mergedEntry)
} else {
dedupedLevel2.push(dedupedLevel1[i])
}
}

/**
* @type {Array<string>}
*/
const dedupedLevel3 = []
for (let i = 0; i < dedupedLevel2.length; i++) {
if (i > 0 && returnJsonRE.test(dedupedLevel2[i]) && letJsonRE.test(dedupedLevel2[i - 1])) {
const mergedEntry = dedupedLevel2[i].slice(0, dedupedLevel2[i].indexOf('return') + 6) + dedupedLevel2[i - 1].substring(dedupedLevel2[i - 1].indexOf('let json =') + 10)
dedupedLevel3.pop() // Remove the previous entry
dedupedLevel3.push(mergedEntry)
} else {
dedupedLevel3.push(dedupedLevel2[i])
}
}

/**
* @type {Array<string>}
*/
for (let i = 0; i < dedupedLevel3.length; i++) {
if (returnEmptyStringRE.test(dedupedLevel3[i])) {
dedupedLevel3[i] = dedupedLevel3[i].replace('return \'\' +', 'return')
}
}

const dedupedLevel4 = []
for (let i = 0; i < dedupedLevel3.length; i++) {
if (
fnRE.test(dedupedLevel3[i]) &&
returnFnRE.test(dedupedLevel3[i + 1]) &&
closingCurlyBracketRE.test(dedupedLevel3[i + 2])
) {
const serializerFnName = dedupedLevel3[i + 1].match(returnFnRE)[1]
const fnName = dedupedLevel3[i].match(fnNameRE)[1]
const whitespace = dedupedLevel3[i].slice(0, dedupedLevel3[i].indexOf('f'))
dedupedLevel4[i] = `${whitespace}const ${fnName} = ${serializerFnName}`
i += 2
} else {
dedupedLevel4.push(dedupedLevel3[i])
}
}

return dedupedLevel4.join('\n')
}

module.exports = optimize
Loading

0 comments on commit 592b3e7

Please sign in to comment.