猫史档案馆


BH喵L解释器 - 用html来快速编写GUI

用户:NomenNomen查看:5 回复:7 评论:5 创建时间:2024-01-06T09:33:38


这里是没有开场白的Nomen,介绍懒得写了,总之就是解释html并且渲染到GUI上,直接看代码吧

// 简单的例子:
// 由于客户端特性,我们无法在客户端储存敏感的H喵L数据,所以我们需要从服务端获取

// SERVER index.js
world.onPlayerJoin(({ entity }) => {
    remoteChannel.sendClientEvent(entity, "<box height=200 width=300 scale=20% x=20 y=40><text>awaawaawaawa</text></box>")
})

// CLIENT clientIndex.js
remoteChannel.events.on("client", (arg) => {
    let nodes = parser(lexer(arg)); // 进行解析
    render(ui, nodes); // 挂载到ui上并且渲染
})


/*
语法应该是这样的
<标签名></闭合标签(如果有)>
以及属性语法
<标签名 key=value>
key可以是以下内容


x
x偏移

y
y偏移

height/h
高度

width/w
宽度

name/id
名字

scale/resize
缩放

backgroundOpacity/background-opacity
背景透明度

zIndex/z-index/z-Index
图层位置

autoResize/auto-resize
自动缩放方式:'NONE' | 'X' | 'Y' | 'XY'

visible
可见性

textContent/text/text-content
文字内容(通常只要直接包含在标签内部就会自动设置)

textFontSize/text-font-size/font-size/text-size
文字尺寸

textXAlignment/text-align-x/textXAlign
x对齐:'Center' | 'Left' | 'Right'

textYAlignment/text-align-y/textYAlign
y对其:'Center' | 'Top' | 'Bottom'

image/src/image-src
图片链接

imageOpacity/opacity/img-oct
图片透明度

backgroundColor/background-color/bc
背景颜色,例如:<box background-color=rgb(11,45,14)></box>

textColor/text-color/tc/color
文字颜色,例如:<text tc=rgb(11,45,14)></text>
*/
const TokenTypes = {
    OPERATOR: "operator",
    STRING: "string",
    TEXT: "text"
}
const AtomTypes = {
    NODE: "node",
    ATTRIBUTES: "attributes",
    TEXT: "text",
    FUNCTION: "function"
}

function lexer(input) {
    const tokens = [];
    let current = 0;
    function flowText() {
        let char = input[current], value = [];
        while (/[^"<>&\x00-\x1F\x7F/=(), ]|\r?\n/i.test(char)) {
            value.push(char);
            char = input[++current];
        }
        return value.join("");
    }
    function flowString() {
        let char = input[current], value = [];
        while (char !== "\"") {
            if (input[current] === undefined) {
                throw new SyntaxError(`双引号没有被闭合`);
            }
            value.push(char);
            char = input[++current];
        }
        if (input[current] === "\"") {
            current++;
        }
        return value.join("");
    }
    function lText(length) {
        let value = [];
        for (let i = 0; i < length; i++) {
            value.push(input[current + i]);
        }
        return value.join("");
    }
    while (current < input.length) {
        let char = input[current];

        if (/\s/.test(char)) {
            current++;
            continue;
        }
        if (char === "<" && [1, 2, 3].map(v => input[current + v]).join("") === "!--") {
            current += 4;
            while (lText(2) !== "--" && input[current + 2] === ">") {
                current++;
            }
            current += 3;
            continue;
        }
        if (["<", ">", "=", "/", "(", ",", ")"].includes(char)) {
            tokens.push({ type: TokenTypes.OPERATOR, value: char });
            current++;
            continue;
        }
        if (char === "\"") {
            current++;
            tokens.push({ type: TokenTypes.STRING, value: flowString() });
            continue;
        }
        if (/[^"<>&\x00-\x1F\x7F/= ]|\r?\n/i.test(char)) {
            tokens.push({ type: TokenTypes.TEXT, value: flowText() });
            continue;
        }
        if (char === "&") {
            let parseMap = {
                "&amp": "&",
                "&lt": "<",
                "&gt": ">",
                "&quot": "\""
            }
            let l = Object.keys(parseMap).find(v => lText(v.length) === v);
            if (l) {
                current += l.length;
                tokens.push({ type: TokenTypes.TEXT, value: parseMap[l] });
                continue;
            }
        }

        throw new SyntaxError(`无效或意外的字符: ${char}`);
    }
    return tokens;
}

function parser(tokens) {
    let current = 0;
    function wrapNode(arg = {}) {
        let res = {
            type: "",
            tag: "",
            value: null,
            attributes: {},
            children: [],
            args: []
        }
        Object.assign(res, arg);
        return res;
    }
    function is(token, type, value) {
        return token.type === type && token.value === value;
    }
    function walk({ parent, expected }) {
        let token = tokens[current];

        if (is(token, TokenTypes.OPERATOR, "<")) {
            current++;
            let node = wrapNode();
            if (is(tokens[current], TokenTypes.OPERATOR, "/")) {
                current++;
                if (tokens[current]?.type === TokenTypes.TEXT) {
                    node.tag = tokens[current++].value;
                    node.type = AtomTypes.NODE;
                } else {
                    throw new SyntaxError(`预期应该是标签名,实际得到: ${tokens[current]?.value}`);
                }
                if (is(tokens[current], TokenTypes.OPERATOR, ">")) {
                    current++;
                    return node;
                } else {
                    throw new SyntaxError(`你应该闭合标签`)
                }
            }
            if (tokens[current]?.type === TokenTypes.TEXT) {
                node.tag = tokens[current++].value;
                node.type = AtomTypes.NODE;
            } else {
                throw new SyntaxError(`预期应该是标签名,实际得到: ${tokens[current]?.value}`);
            }
            if (is(tokens[current], TokenTypes.OPERATOR, "/" && is(tokens[current + 1], TokenTypes.OPERATOR, "/"))) {
                current += 2;
                return node;
            }
            if (!is(tokens[current], TokenTypes.OPERATOR, ">")) {
                while (tokens[current].type === TokenTypes.TEXT) {
                    let attr = walk({ expected: AtomTypes.ATTRIBUTES });
                    if (attr && attr.type === AtomTypes.ATTRIBUTES) {
                        node.attributes[attr.attributes[0]] = attr.attributes[1];
                    } else if (attr && attr.type === AtomTypes.FUNCTION) {
                        node.attributes[attr.attributes[0]] = attr
                    }
                }
                if (is(tokens[current], TokenTypes.OPERATOR, "/") && is(tokens[current + 1], TokenTypes.OPERATOR, ">")) {
                    current += 2;
                    return node;
                }
            }
            if (is(tokens[current], TokenTypes.OPERATOR, ">")) {
                current++;
                while (!is(tokens[current], TokenTypes.OPERATOR, "<") ||
                    (!is(tokens[current + 1], TokenTypes.OPERATOR, "/")) ||
                    (!is(tokens[current + 2], TokenTypes.TEXT, node.tag))
                ) {
                    if (tokens[current] === undefined) {
                        throw new SyntaxError(`严格来讲你应该闭合标签,但实际没有`);
                    }
                    let res = walk({ parent: node });
                    if (res.type === AtomTypes.TEXT) {
                        if (node.attributes.textContent === undefined) node.attributes.textContent = "";
                        node.attributes.textContent = node.attributes.textContent + res.value;
                    } else if (res.type === AtomTypes.NODE) {
                        node.children.push(res);
                    }
                }
                if (is(tokens[current + 3], TokenTypes.OPERATOR, ">")) {
                    current += 4;
                    return node;
                } else {
                    throw new SyntaxError(`没有闭合的标签`);
                }
            }
            throw new SyntaxError(`意料之外的行为:${JSON.stringify(tokens[current])}`);
        }
        if (token.type === TokenTypes.TEXT) {
            let node = wrapNode();
            current++;
            if (is(tokens[current], TokenTypes.OPERATOR, "=")) {
                node.attributes[0] = token.value;
                current++;
                if (tokens[current]?.type === TokenTypes.TEXT && is(tokens[current + 1], TokenTypes.OPERATOR, "(")) {
                    node.type = AtomTypes.FUNCTION;
                    node.value = tokens[current].value;
                    current += 2;
                    let args = [];
                    while (!is(tokens[current], TokenTypes.OPERATOR, ")")) {
                        if (tokens[current] === undefined) throw new SyntaxError(`未闭合的括号`);
                        if (tokens[current].type !== TokenTypes.OPERATOR) {
                            args.push(tokens[current]);
                            current++;
                        } else {
                            throw new SyntaxError(`意外的值: ${JSON.stringify(tokens[current])}`);
                        }
                        if (is(tokens[current], TokenTypes.OPERATOR, ",")) {
                            current++;
                        } else if (is(tokens[current], TokenTypes.OPERATOR, ")")) {
                            break;
                        }
                    }
                    current++;
                    node.args = args;
                    return node;
                }
                node.type = AtomTypes.ATTRIBUTES;
                if (tokens[current]?.type !== TokenTypes.STRING && tokens[current]?.type !== TokenTypes.TEXT) throw new SyntaxError(`预期应该是字段值,实际得到${tokens[current]?.value}`);
                node.attributes[1] = tokens[current].value;
                current++;
                return node;
            } else if (expected === AtomTypes.ATTRIBUTES) {
                node.type = AtomTypes.ATTRIBUTES;
                node.attributes[0] = token.value;
                return node;
            } else {
                node.type = AtomTypes.TEXT;
                node.value = token.value;
                return node;
            }
        }

        throw new SyntaxError(`预期之外的Token: ${JSON.stringify(token)}`);
    }

    let body = wrapNode({
        type: AtomTypes.NODE,
        tag: "body",
    });
    while (current < tokens.length) {
        let node = walk({ parent: body });
        if (node) body.children.push(node);
    }
    return body;
}

function render(ui, nodes) {
    const UiMap = {
        "box": UiBox,
        "text": UiText,
        "img": UiImage
    }
    const functions = {
        "rgb": (r, g, b) => (Vec3.create({ r, g, b }))
    }
    function exec(parent, node) {
        if (node === null) return null;
        if (node.type === AtomTypes.FUNCTION) {
            return functions[node.value]?.(...node.args.map(a => (Number(a.value) == a.value) ? parseFloat(a.value) : a.value))
        } else if (node.type === AtomTypes.NODE) {
            if (node.tag === "body") {
                node.children.forEach(e => exec(ui, e));
                return null;
            }
            /**@type {UiBox|UiText|UiImage} */
            let uinode = UiMap[node.tag].create();
            if (parent) uinode.parent = parent;
            Object.keys(node.attributes).forEach(k => {
                let attr = node.attributes[k];
                switch (k) {
                    case "x":
                        uinode.position.offset.copy({ x: attr, y: uinode.position.offset.y });
                        break;
                    case "y":
                        uinode.position.offset.copy({ x: uinode.position.offset.x, y: attr });
                        break;
                    case "height":
                    case "h":
                        uinode.size.offset.copy({ x: uinode.size.offset.x, y: attr });
                        break;
                    case "width":
                    case "w":
                        uinode.size.offset.copy({ x: attr, y: uinode.size.offset.y });
                        break;
                    case "name":
                    case "id":
                        uinode.name = attr;
                        break;
                    case "scale":
                    case "resize":
                        uinode.uiScale.scale = attr;
                        break;
                    case "backgroundOpacity":
                    case "background-opacity":
                        if (attr.endsWith("%")) attr = parseFloat(attr.slice(0, attr.length - 1)) / 100;
                        uinode.backgroundOpacity = parseFloat(attr);
                        break;
                    case "zIndex":
                    case "z-index":
                    case "z-Index":
                        uinode.zIndex = parseInt(attr);
                        break;
                    case "autoResize":
                    case "auto-resize":
                        uinode.autoResize = attr;
                        break;
                    case "visible":
                        uinode.visible = attr === "false" ? false : (attr === "true" ? true : false);
                        break;
                    case "textContent":
                    case "text":
                    case "text-content":
                        uinode.textContent = attr;
                        break;
                    case "textFontSize":
                    case "text-font-size":
                    case "font-size":
                    case "text-size":
                        uinode.textFontSize = parseFloat(attr);
                        break;
                    case "textXAlignment":
                    case "text-align-x":
                    case "textXAlign":
                        uinode.textXAlignment = attr;
                        break;
                    case "textYAlignment":
                    case "text-align-y":
                    case "textYAlign":
                        uinode.textYAlignment = attr;
                        break;
                    case "image":
                    case "src":
                    case "image-src":
                        uinode.image = attr;
                        break;
                    case "imageOpacity":
                    case "opacity":
                    case "img-oct":
                        if (attr.endsWith("%")) attr = parseFloat(attr.slice(0, attr.length - 1)) / 100;
                        uinode.imageOpacity = parseFloat(attr);
                        break;
                    case "backgroundColor":
                    case "background-color":
                    case "bc":
                        if (attr && attr.type === AtomTypes.FUNCTION) uinode.backgroundColor.copy(exec(parent, attr));
                        else uinode.backgroundColor.copy(attr);
                        break;
                    case "textColor":
                    case "text-color":
                    case "color":
                    case "tc":
                        if (attr && attr.type === AtomTypes.FUNCTION) uinode.textColor.copy(exec(parent, attr));
                        else uinode.textColor.copy(attr);
                        break;
                }
            })
            node.children.forEach(e => exec(ui, e));
            return null;
        }
    }
    exec(ui, nodes);
}


回复

上一页1 页 / 共 1下一页
NomenNomen

标签一共三种

box, text, img

点赞0


评论


神岛吉吉喵神岛吉吉喵

前排!

点赞5


评论


七式草莓七式草莓

你用JSON更好

点赞1


评论


摆烂才是王道摆烂才是王道

看了半天完全看不懂【本人啥也不会】

点赞0


评论


86135clc86135clc

应该是xml

点赞1


评论


LZ编编编编程猫LZ编编编编程猫

太强了

点赞2


评论


小苏打_小苏打_

经典HT-ML被屏蔽

点赞0


评论