Skip to content

Commit

Permalink
optimizer v3
Browse files Browse the repository at this point in the history
  • Loading branch information
Uzlopak committed Jul 27, 2023
1 parent 17bb4c2 commit c7c04bd
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 78 deletions.
57 changes: 29 additions & 28 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 @@ -504,8 +505,8 @@ function buildObject (context, location) {
}

let functionCode = `
// ${schemaRef}
function ${functionName} (input) {
// ${schemaRef}
`

functionCode += `
Expand Down Expand Up @@ -549,8 +550,8 @@ function buildArray (context, location) {
}

let functionCode = `
// ${schemaRef}
function ${functionName} (obj) {
// ${schemaRef}
`

functionCode += `
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
98 changes: 98 additions & 0 deletions lib/optimize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'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) {
let code = raw.split('\n')
code = optimizeJsonConcat(code)
code = optimizeLetJson(code)
code = optimizeDirectReturn(code)
code = optimizeReturnEmptyString(code)
code = optimizeDirectAssignWrappedFns(code)
return code.join('\n')
}

function optimizeJsonConcat (code) {
const optimizedJsonConcat = []

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)
optimizedJsonConcat.pop() // Remove the previous entry
optimizedJsonConcat.push(mergedEntry)
} else {
optimizedJsonConcat.push(code[i])
}
}

return optimizedJsonConcat
}

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

function optimizeDirectReturn(code) {
const optimizedDirectReturnCode = []
for (let i = 0; i < code.length; i++) {
if (i > 0 && returnJsonRE.test(code[i]) && letJsonRE.test(code[i - 1])) {
const mergedEntry = code[i].slice(0, code[i].indexOf('return') + 6) + code[i - 1].substring(code[i - 1].indexOf('let json =') + 10)
optimizedDirectReturnCode.pop() // Remove the previous entry
optimizedDirectReturnCode.push(mergedEntry)
} else {
optimizedDirectReturnCode.push(code[i])
}
}
return optimizedDirectReturnCode
}

function optimizeReturnEmptyString(code) {
for (let i = 0; i < code.length; i++) {
if (returnEmptyStringRE.test(code[i])) {
code[i] = code[i].replace('return \'\' +', 'return')
}
}
return code
}

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

return optimizedDirectAssignFns
}
module.exports = optimize
Loading

0 comments on commit c7c04bd

Please sign in to comment.