Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add optimizer #633

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 29 additions & 31 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 @@ -120,21 +133,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 @@ -143,7 +143,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 @@ -264,7 +265,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 @@ -279,13 +280,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 @@ -541,10 +542,7 @@ function buildObject (context, location) {
schemaRef = schemaRef.replace(context.rootSchemaId, '')
}

let functionCode = `
`

functionCode += `
const functionCode = `
// ${schemaRef}
function ${functionName} (input) {
const obj = ${toJSON('input')}
Expand Down Expand Up @@ -581,8 +579,8 @@ function buildArray (context, location) {
}

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

functionCode += `
Expand Down Expand Up @@ -775,21 +773,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 lastEntry = optimizedJsonConcat.pop()
const mergedEntry = lastEntry + ' +' + code[i].substring(code[i].indexOf('json +=') + 7)
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