feat: add molang package

1. upgrade bukkit chat
2. fix config update error

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

View File

@@ -0,0 +1,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'
}
}

View 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,
}
}
}

View 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',
])