entlib
Version:
Entry API Wrapper
428 lines (418 loc) • 20.7 kB
text/typescript
import { MultiStatementSkeleton } from "./Statements"
export class SyntaxTree {
public type: SyntaxCode
public content: (SyntaxTree | string)[]
constructor(type: SyntaxCode, content: (SyntaxTree | string)[]) {
this.type = type
this.content = content
}
}
export enum IndicatorType {
EVENT,
DEFAULT
}
export enum SyntaxCode {
BLOCK,
CONDITION,
PARAM,
TEXT,
DROPDOWN,
STATEMENT
}
export interface BlockSyntax {
template: string
color: string
outerLine: string
skeleton: string
statements: any[]
params: any[]
events: { [key: string]: Function[] }
def: {
params: any[]
type: string
},
paramsKeyMap: { [key: string]: number }
statementsKeyMap: { [key: string]: number }
class: string
func: Function
event?: string
}
export class Parser {
public parse(template: string, isParameter: boolean = false): SyntaxTree {
let templateType: SyntaxCode
let tree: SyntaxTree = null!
let retIndex: number = 0
let passCount: number = 0
let openedBracket: number = 1
let isDefaultValue: boolean = false
try {
template.split('').forEach((char, i, arr) => {
if (passCount > 0) return passCount--
if (i == 0) {
if (char == '[') templateType = isParameter ? SyntaxCode.DROPDOWN : SyntaxCode.BLOCK
else if (char == '(') templateType = SyntaxCode.PARAM
else if (char == '<') templateType = SyntaxCode.CONDITION
else if (char == '{') templateType = SyntaxCode.STATEMENT
else templateType = SyntaxCode.TEXT
tree = new SyntaxTree(templateType, [])
if (templateType == SyntaxCode.TEXT) tree.content.push('') && (tree.content[tree.content.length - 1] += char)
return
}
if (isParameter && ['[', '(', '<', '{'].includes(char)) openedBracket++
if ([']', ')', '>', '}'].includes(char)) openedBracket--
if (!isParameter && openedBracket < 1 && arr.length - 1 != i) throw new SyntaxError('Invalid Syntax: Multiple-Root-Brackets are not allowed.')
if (((arr.length - 1 == i) || (isParameter && [']', ')', '>', '}'].includes(char) && openedBracket == 0)) && templateType != SyntaxCode.TEXT) {
if ((templateType == SyntaxCode.BLOCK || templateType == SyntaxCode.DROPDOWN) && char != ']') throw new SyntaxError('Invalid Syntax: Required \']\'')
if (templateType == SyntaxCode.PARAM && char != ')') throw new SyntaxError('Invalid Syntax: Required \')\'')
if (templateType == SyntaxCode.CONDITION && char != '>') throw new SyntaxError('Invalid Syntax: Required \'>\'')
if (templateType == SyntaxCode.STATEMENT && char != '}') throw new SyntaxError('Invalid Syntax: Required \'}\'')
retIndex = i
throw 'break'
}
if (isParameter || templateType == SyntaxCode.TEXT ? true : !['[', '(', '<', '{'].includes(char)) {
if (typeof tree.content[tree.content.length - 1] != 'string') tree.content.push('')
if (isParameter) {
if (char == ':' && templateType != SyntaxCode.STATEMENT) {
tree.content.push('')
isDefaultValue = true
} else if (isDefaultValue ? char != ' ' : true) {
if (templateType != SyntaxCode.DROPDOWN) {
tree.content[tree.content.length - 1] += char
} else {
if (isDefaultValue) {
tree.content[tree.content.length - 1] = (char == '@').toString()
tree.content.push(char == '@' ? '' : char)
} else {
if (tree.content[1] == 'true' && char != '@') {
tree.content[tree.content.length - 1] += char
} else {
if (char == '=' || char == ',') tree.content.push('')
else tree.content[tree.content.length - 1] += char
}
}
}
isDefaultValue = false
}
} else {
tree.content[tree.content.length - 1] += char
}
} else if (!isParameter) {
const parsed = <any> this.parse(template.substring(i), true)
passCount = parsed.len
tree.content.push(parsed.tree)
}
})
} catch (e) {
if (e != 'break') throw e
}
if (isParameter) return <any> { tree, len: retIndex }
else return <any> tree
}
public syntaxTreeToBlock({ name, className, color, outline, action, darkenColor, indicator, indicatorType = IndicatorType.DEFAULT, event }: {
name: string
className: string
color: string
outline: string,
action: Function
darkenColor: string
indicator?: string
indicatorType?: IndicatorType
event?: string
}, tree: SyntaxTree) {
switch (tree.type) {
case SyntaxCode.TEXT: {
const base: BlockSyntax = {
template: '%1',
color: 'transparent',
outerLine: 'transparent',
skeleton: 'basic_text',
statements: [],
params: [{
type: 'Text',
text: tree.content[0],
color: typeof EntryStatic != 'undefined' ? EntryStatic.colorSet.common.TEXT : '#333',
class: 'bold',
align: 'center'
}],
events: {},
def: {
params: [null],
type: name
},
paramsKeyMap: {},
statementsKeyMap: {},
class: className,
func: action
}
return { base, neededDynamicDropdown: [], customSkeletons: {} }
}
case SyntaxCode.CONDITION:
case SyntaxCode.PARAM:
case SyntaxCode.BLOCK: {
const base: BlockSyntax = {
template: '',
color,
outerLine: outline,
skeleton: event ? 'basic_event' : ({
[SyntaxCode.BLOCK]: 'basic',
[SyntaxCode.CONDITION]: 'basic_boolean_field',
[SyntaxCode.PARAM]: 'basic_string_field'
})[tree.type],
statements: [],
params: [],
events: {},
def: {
params: [],
type: name
},
paramsKeyMap: {},
statementsKeyMap: {},
class: className,
func: action
}
if (event) base.event = event
const neededDynamicDropdown: string[] = []
const customSkeletons: { [key: string]: MultiStatementSkeleton } = {}
let paramCount = 0
let lineBreakCount = 0
let indicatorGenerated = false
if (indicator && indicatorType == IndicatorType.EVENT) {
base.template += `%${++paramCount}`
base.params.push({
type: 'Indicator',
img: indicator,
size: 14,
position: { x: 0, y: -2 }
})
base.def.params.push(null)
}
tree.content.forEach(content => {
if (typeof content == 'string') {
base.template += content
} else {
base.template += `%${++paramCount}`
if (content.content.length >= 2) {
if (content.type == SyntaxCode.PARAM) {
base.params.push({
type: 'Block',
accept: 'string'
})
base.def.params.push({
type: 'text',
params: [content.content[1]]
})
base.paramsKeyMap[(<string> content.content[0]).toUpperCase().trim()] = paramCount - 1
} else if (content.type == SyntaxCode.CONDITION) {
base.params.push({
type: 'Block',
accept: 'boolean'
})
base.def.params.push({
type: (<string> content.content[1]).toLowerCase().trim() == 'true' ? 'True' : 'False',
})
base.paramsKeyMap[(<string> content.content[0]).toUpperCase().trim()] = paramCount - 1
} else if (content.type == SyntaxCode.DROPDOWN) {
const dropdown = {
key: (<string> content.content[0]).trim(),
isDynamic: content.content[1] == 'true',
map: <[string, string][]>(content.content[1] != 'true' ? content.content.slice(2).reduce((acc, cur, i, arr) => {
if (!(acc[acc.length - 1] instanceof Array)) acc.push([])
acc[acc.length - 1].push(<string> cur)
if (i % 2 == 1 && i != arr.length - 1) acc.push([])
return acc
}, <string[][]>[]).map(l => {
const arr = [l[0], l[1]]
const temp = arr[0]
arr[0] = arr[1]
arr[1] = temp
return arr
}) : []),
menuName: content.content[1] == 'true' ? content.content[2] as string : null
}
if (!dropdown.isDynamic) {
base.params.push({
type: "Dropdown",
options: dropdown.map,
fontSize: 11,
value: dropdown.map[0][1],
bgColor: darkenColor,
arrowColor: '#ffffff'
})
base.def.params.push(null)
} else {
const menuName = <string> dropdown.menuName?.toLowerCase()
base.params.push({
type: "DropdownDynamic",
menuName,
fontSize: 11,
value: null,
bgColor: darkenColor,
arrowColor: '#ffffff'
})
if (![
"sprites",
"allSprites",
"spritesWithMouse",
"spritesWithSelf",
"textBoxWithSelf",
"collision",
"pictures",
"messages",
"variables",
"lists",
"tables",
"scenes",
"sounds",
"clone",
"objectSequence",
"fonts"
].includes(menuName)) neededDynamicDropdown.push(menuName)
base.def.params.push(null)
}
base.paramsKeyMap[dropdown.key.toUpperCase()] = paramCount - 1
}
} else if (content.content.length == 1) {
if (content.type == SyntaxCode.PARAM) {
base.params.push({
type: 'Block',
accept: 'string'
})
base.def.params.push({
type: 'text',
params: [content.content[0]]
})
} else if (content.type == SyntaxCode.CONDITION) {
base.params.push({
type: 'Block',
accept: 'boolean'
})
base.def.params.push({
type: (<string> content.content[0]).toLowerCase().trim() == 'true' ? 'True' : 'False',
})
} else if (content.type == SyntaxCode.STATEMENT) {
const statementCount = tree.content.filter((content: any) => content.type == SyntaxCode.STATEMENT).length
if (statementCount > 1 && lineBreakCount < statementCount - 1) {
if (!indicatorGenerated) {
base.template += ` %${++paramCount}`
base.params.push({
type: 'Indicator',
img: indicator,
size: 11
})
base.def.params.push(null)
base.statementsKeyMap[(content.content[0] as string).toUpperCase().trim()] = paramCount - 2
indicatorGenerated = true
} else {
base.statementsKeyMap[(content.content[0] as string).toUpperCase().trim()] = paramCount - 1
}
base.params.push({
type: 'LineBreak'
})
lineBreakCount++
} else {
base.template = base.template.slice(0, (1 + paramCount.toString().length) * -1)
paramCount--
if (!indicatorGenerated) {
base.template += ` %${++paramCount}`
base.params.push({
type: 'Indicator',
img: indicator,
size: 11
})
base.def.params.push(null)
base.statementsKeyMap[(content.content[0] as string).toUpperCase().trim()] = paramCount - 2
indicatorGenerated = true
} else {
base.statementsKeyMap[(content.content[0] as string).toUpperCase().trim()] = paramCount - 1
}
}
if (statementCount == 1) base.skeleton = 'basic_loop'
else if (statementCount == 2) base.skeleton = 'basic_double_loop'
base.statements.push({ accept: 'basic' })
}
}
}
})
const statementCount = tree.content.filter((content: any) => content.type == SyntaxCode.STATEMENT).length
if (indicator && indicatorType == IndicatorType.DEFAULT && statementCount == 0 && tree.type == SyntaxCode.BLOCK) {
base.template += ` %${++paramCount}`
base.params.push({
type: 'Indicator',
img: indicator,
size: 11
})
base.def.params.push(null)
}
if (statementCount > 2) {
const id = Math.random().toString(36).substr(2, 4)
base.skeleton = id
customSkeletons[id] = new MultiStatementSkeleton(statementCount)
}
return { base, neededDynamicDropdown, customSkeletons }
}
default: {
const base: BlockSyntax = {
template: '%1',
color: 'transparent',
outerLine: 'transparent',
skeleton: 'basic_text',
statements: [],
params: [{
type: 'Text',
text: 'Error',
color: '#ff0000',
class: 'bold',
align: 'center'
}],
events: {},
def: {
params: [null],
type: name
},
paramsKeyMap: {},
statementsKeyMap: {},
class: className,
func: action
}
return { base, neededDynamicDropdown: [], customSkeletons: {} }
}
}
}
public parseButton({ textColor, text, align, action, name, className }: {
textColor: string
text: string
align: 'center' | 'right' | 'left'
action: Function
name: string
className: string
}) {
const base: BlockSyntax = {
template: '%1',
color: '#eee',
skeleton: 'basic_button',
params: [{
type: 'Text',
text,
align,
color: textColor
}],
events: { mousedown: [action] },
def: {
params: [null],
type: name
},
func: null!,
class: null!,
outerLine: null!,
statements: null!,
paramsKeyMap: null!,
statementsKeyMap: null!
}
return {
base, neededDynamicDropdown: [], customSkeletons: {}
}
}
public static parse(template: string) {
return new Parser().parse(template)
}
}