hackmud-script-manager
Version:
Script manager for game hackmud, with minification, TypeScript support, and player script type definition generation.
108 lines (107 loc) • 3.8 kB
JavaScript
import babelGenerator from "@babel/generator"
import { parse } from "@babel/parser"
import babelTraverse from "@babel/traverse"
import t from "@babel/types"
import { assert } from "@samual/lib/assert"
import { spliceString } from "@samual/lib/spliceString"
import { resolve } from "import-meta-resolve"
const { default: traverse } = babelTraverse,
{ default: generate } = babelGenerator
async function preprocess(code, { uniqueId = "00000000000" } = {}) {
assert(/^\w{11}$/.test(uniqueId), "src/processScript/preprocess.ts:23:36")
const sourceCode = code
let lengthBefore, file, program
do {
lengthBefore = code.length
code = code
.replace(/^\s+/, "")
.replace(/^\/\/.*/, "")
.replace(/^\/\*[\s\S]*?\*\//, "")
} while (code.length != lengthBefore)
code = code.replace(/^function\s*\(/, "export default function (")
for (;;) {
let error
try {
file = parse(code, {
plugins: [
"typescript",
["decorators", { decoratorsBeforeExport: !0 }],
"doExpressions",
"functionBind",
"functionSent",
"partialApplication",
["pipelineOperator", { proposal: "hack", topicToken: "%" }],
"throwExpressions",
["recordAndTuple", { syntaxType: "hash" }],
"classProperties",
"classPrivateProperties",
"classPrivateMethods",
"logicalAssignment",
"numericSeparator",
"nullishCoalescingOperator",
"optionalChaining",
"optionalCatchBinding",
"objectRestSpread"
],
sourceType: "module"
})
break
} catch (error_) {
assert(error_ instanceof SyntaxError, "src/processScript/preprocess.ts:67:42")
error = error_
}
if ("BABEL_PARSER_SYNTAX_ERROR" != error.code || "PrivateInExpectedIn" != error.reasonCode) {
console.log(/.+/.exec(code.slice(error.pos))?.[0])
throw error
}
const codeSlice = code.slice(error.pos)
let match
if ((match = /^#[0-4fhmln]s\.scripts\.quine\(\)/.exec(codeSlice)))
code = spliceString(code, JSON.stringify(sourceCode), error.pos, match[0].length)
else if ((match = /^#[0-4fhmln]?s\./.exec(codeSlice))) code = spliceString(code, "$", error.pos, 1)
else if ((match = /^#D[^\w$]/.exec(codeSlice))) code = spliceString(code, "$", error.pos, 1)
else if ((match = /^#FMCL/.exec(codeSlice)))
code = spliceString(code, `$${uniqueId}$FMCL$`, error.pos, match[0].length)
else if ((match = /^#G/.exec(codeSlice)))
code = spliceString(code, `$${uniqueId}$GLOBAL$`, error.pos, match[0].length)
else {
if (!(match = /^#db\./.exec(codeSlice))) throw error
code = spliceString(code, "$", error.pos, 1)
}
}
traverse(file, {
Program(path) {
program = path
path.skip()
}
})
const needRecord = program.scope.hasGlobal("Record"),
needTuple = program.scope.hasGlobal("Tuple")
;(needRecord || needTuple) &&
file.program.body.unshift(
t.importDeclaration(
needRecord
? needTuple
? [
t.importSpecifier(t.identifier("Record"), t.identifier("Record")),
t.importSpecifier(t.identifier("Tuple"), t.identifier("Tuple"))
]
: [t.importSpecifier(t.identifier("Record"), t.identifier("Record"))]
: [t.importSpecifier(t.identifier("Tuple"), t.identifier("Tuple"))],
t.stringLiteral("@bloomberg/record-tuple-polyfill")
)
)
program.scope.hasGlobal("Proxy") &&
file.program.body.unshift(
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier("Proxy"))],
t.stringLiteral(resolve("proxy-polyfill/src/proxy.js", import.meta.url).slice(7))
)
)
if (1 == program.node.body.length && "FunctionDeclaration" == program.node.body[0].type)
throw Error(
"Scripts that only contain a single function declaration are no longer supported.\nPrefix the function declaration with `export default`."
)
return { code: generate(file).code }
}
export { preprocess }