feat: add molang package

1. upgrade bukkit chat
2. fix config update error

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
2022-02-12 16:29:40 +08:00
parent 5ed61829e1
commit 6816e51239
73 changed files with 3103 additions and 129 deletions

View File

@@ -0,0 +1,93 @@
import { Parser } from '../parser/parse'
import { Token } from '../parser/../tokenizer/token'
import { IPrefixParselet } from '../parser/parselets/prefix'
import { Expression, IExpression } from '../parser/expression'
import { StringExpression } from '../parser/expressions/string'
import { StatementExpression } from '../parser/expressions/statement'
import { CustomMoLangParser } from './main'
import { GroupExpression } from '../parser/expressions/group'
export class CustomFunctionParselet implements IPrefixParselet {
constructor(public precedence = 0) {}
parse(parser: Parser, token: Token) {
parser.consume('LEFT_PARENT')
if (parser.match('RIGHT_PARENT'))
throw new Error(`function() called without arguments`)
let args: string[] = []
let functionBody: IExpression | undefined
let functionName: string | undefined
do {
const expr = parser.parseExpression()
if (expr instanceof StringExpression) {
if (!functionName) functionName = <string>expr.eval()
else args.push(<string>expr.eval())
} else if (
expr instanceof StatementExpression ||
expr instanceof GroupExpression
) {
functionBody = expr
} else {
throw new Error(
`Unexpected expresion: found "${expr.constructor.name}"`
)
}
} while (parser.match('COMMA'))
parser.consume('RIGHT_PARENT')
if (!functionName)
throw new Error(
`Missing function() name (argument 1); found "${functionName}"`
)
if (!functionBody)
throw new Error(
`Missing function() body (argument ${args.length + 2})`
)
return new CustomFunctionExpression(
(<CustomMoLangParser>parser).functions,
functionName,
args,
functionBody
)
}
}
class CustomFunctionExpression extends Expression {
type = 'CustomFunctionExpression'
constructor(
functions: Map<string, [string[], string]>,
functionName: string,
args: string[],
protected functionBody: IExpression
) {
super()
functions.set(functionName, [
args,
functionBody instanceof GroupExpression
? functionBody.allExpressions[0].toString()
: functionBody.toString(),
])
}
get allExpressions() {
return [this.functionBody]
}
setExpressionAt(_: number, expr: IExpression) {
this.functionBody = expr
}
get isReturn() {
// Scopes inside of functions may use return statements
return false
}
isStatic() {
return true
}
eval() {
return 0
}
}

View File

@@ -0,0 +1,229 @@
import { ExecutionEnvironment } from '../env/env'
import { IParserConfig } from '../main'
import { MoLangParser } from '../parser/molang'
import { Tokenizer } from '../tokenizer/Tokenizer'
import { CustomFunctionParselet } from './function'
import { MoLang } from '../MoLang'
import { StatementExpression } from '../parser/expressions/statement'
import { transformStatement } from './transformStatement'
import { NameExpression } from '../parser/expressions/name'
import { ReturnExpression } from '../parser/expressions/return'
import { GenericOperatorExpression } from '../parser/expressions/genericOperator'
import { TernaryExpression } from '../parser/expressions/ternary'
import { IExpression } from '../parser/expression'
import { VoidExpression } from '../parser/expressions/void'
import { GroupExpression } from '../parser/expressions/group'
export class CustomMoLangParser extends MoLangParser {
public readonly functions = new Map<string, [string[], string]>()
constructor(config: Partial<IParserConfig>) {
super(config)
this.registerPrefix('FUNCTION', new CustomFunctionParselet())
}
reset() {
this.functions.clear()
}
}
export class CustomMoLang {
protected parser: CustomMoLangParser
constructor(env: any) {
this.parser = new CustomMoLangParser({
useCache: false,
useOptimizer: true,
useAgressiveStaticOptimizer: true,
keepGroups: true,
earlyReturnsSkipParsing: false,
earlyReturnsSkipTokenization: false,
})
this.parser.setExecutionEnvironment(
new ExecutionEnvironment(this.parser, env)
)
this.parser.setTokenizer(new Tokenizer(new Set(['function'])))
}
get functions() {
return this.parser.functions
}
parse(expression: string) {
this.parser.init(expression)
const abstractSyntaxTree = this.parser.parseExpression()
return abstractSyntaxTree
}
transform(source: string) {
const molang = new MoLang(
{},
{
useCache: false,
keepGroups: true,
useOptimizer: true,
useAgressiveStaticOptimizer: true,
earlyReturnsSkipParsing: true,
earlyReturnsSkipTokenization: false,
}
)
let totalScoped = 0
let ast = molang.parse(source)
let isComplexExpression = false
if (ast instanceof StatementExpression) {
isComplexExpression = true
}
let containsComplexExpressions = false
ast = ast.walk((expr: any) => {
// Only run code on function expressions which start with "f." or "function."
if (
expr.type !== 'FunctionExpression' ||
(!expr.name.name.startsWith?.('f.') &&
!expr.name.name.startsWith?.('function.'))
)
return
const nameExpr = expr.name
const functionName = nameExpr.name.replace(/(f|function)\./g, '')
const argValues = expr.args
let [args, functionBody] = this.functions.get(functionName) ?? []
if (!functionBody || !args) return
// Insert argument values
functionBody = functionBody.replace(
/(a|arg)\.(\w+)/g,
(match, prefix, argName) => {
const val =
argValues[args!.indexOf(argName)]?.toString() ?? '0'
return val.replace(/(t|temp)\./, 'outer_temp.')
}
)
let funcAst = transformStatement(molang.parse(functionBody))
if (funcAst instanceof StatementExpression) {
funcAst = molang.parse(`({${functionBody}}+t.return_value)`)
containsComplexExpressions = true
}
const varNameMap = new Map<string, string>()
funcAst = funcAst.walk((expr) => {
if (expr instanceof NameExpression) {
const fullName = expr.toString()
// Remove "a."/"t."/etc. from var name
let tmp = fullName.split('.')
const varType = tmp.shift()
const varName = tmp.join('.')
if (varType === 't' || varType === 'temp') {
// Scope temp./t. variables to functions
let newName = varNameMap.get(fullName)
if (!newName) {
newName = `t.__scvar${totalScoped++}`
varNameMap.set(fullName, newName)
}
expr.setName(newName)
} else if (varType === 'outer_temp') {
expr.setName(`t.${varName}`)
}
return undefined
} else if (expr instanceof ReturnExpression) {
const nameExpr = new NameExpression(
molang.getParser().executionEnv,
't.return_value'
)
const returnValExpr = expr.allExpressions[0]
return new GenericOperatorExpression(
nameExpr,
returnValExpr,
'=',
() => {
nameExpr.setPointer(returnValExpr.eval())
}
)
} else if (expr instanceof StatementExpression) {
// Make early returns work correctly by adjusting ternary statements which contain return statements
const expressions: IExpression[] = []
for (let i = 0; i < expr.allExpressions.length; i++) {
const currExpr = expr.allExpressions[i]
if (
currExpr instanceof TernaryExpression &&
currExpr.hasReturn
) {
handleTernary(
currExpr,
expr.allExpressions.slice(i + 1)
)
expressions.push(currExpr)
break
} else if (currExpr.isReturn) {
expressions.push(currExpr)
break
}
expressions.push(currExpr)
}
return new StatementExpression(expressions)
}
})
return funcAst
})
const finalAst = molang.parse(ast.toString())
molang.resolveStatic(finalAst)
return !isComplexExpression && containsComplexExpressions
? `return ${finalAst.toString()};`
: finalAst.toString()
}
reset() {
this.functions.clear()
}
}
function handleTernary(
returnTernary: TernaryExpression,
currentExpressions: IExpression[]
) {
// If & else branch end with return statements -> we can omit everything after the ternary
if (returnTernary.isReturn) return
const notReturningBranchIndex = returnTernary.allExpressions[2].isReturn
? 1
: 2
const notReturningBranch =
returnTernary.allExpressions[notReturningBranchIndex]
if (!(notReturningBranch instanceof VoidExpression)) {
if (
notReturningBranch instanceof GroupExpression &&
notReturningBranch.allExpressions[0] instanceof StatementExpression
) {
currentExpressions.unshift(...notReturningBranch.allExpressions)
} else {
currentExpressions.unshift(notReturningBranch)
}
}
if (currentExpressions.length > 0)
returnTernary.setExpressionAt(
notReturningBranchIndex,
new GroupExpression(
new StatementExpression(currentExpressions),
'{}'
)
)
}

View File

@@ -0,0 +1,19 @@
import { IExpression } from '../parser/expression'
import { GroupExpression } from '../parser/expressions/group'
import { ReturnExpression } from '../parser/expressions/return'
import { StatementExpression } from '../parser/expressions/statement'
export function transformStatement(expression: IExpression) {
if (expression instanceof ReturnExpression)
return new GroupExpression(expression.allExpressions[0], '()')
if (!(expression instanceof StatementExpression)) return expression
if (expression.allExpressions.length > 1) return expression
// Only one statement, test whether it is a return statement
const expr = expression.allExpressions[0]
if (expr instanceof ReturnExpression) {
return new GroupExpression(expr.allExpressions[0], '()')
} else {
return expression
}
}