🤔 Para Refletir : "Seu jogo nao precisa ser o melhor ou o mais popular, apenas precisa fazer o dia de alguem melhor" - codingkitsune

Schach - Parsing

Colaboração Colaboração
Quod erat demonstrandum
Postagens
337
Bravecoins
1.156
lsbasi_part7_astprecedence_02.png


Schach - Parsing | v1.1.0 | por Brandt

28S2WCW.png


Introdução

Este plugin é uma biblioteca de processamento de texto. Ele não adiciona nenhuma funcionalidade por si só, apenas funções que podem ser utilizadas por outros plugins para processar e utilizar entradas genéricas do usuário.


Instruções

Leia a documentação do plugin para detalhes das funções disponíveis.

Um exemplo rápido de uso:

Javascript:
const { expression, evaluate } = Schach.Parsing.Arithmetic;

const { parsed: expr } = expression().run("#x^4 - log2(#y)")
evaluate(expr, { variables: { x: 5, y: 32 } }) // retorna 5^4 - log2(32) = 625 - 5 = 620


Download: Github


Código Fonte:
Github


Licença:
Zlib
 
Última edição:
Achei interessante, mas poderia me dar um exemplo concreto de quando esse plugin poderia ser útil de forma concreta? Desculpe se a pergunta é amadora demais, mas eu sou meio lento pra entender esse tipo de coisa.
 
Achei interessante, mas poderia me dar um exemplo concreto de quando esse plugin poderia ser útil de forma concreta? Desculpe se a pergunta é amadora demais, mas eu sou meio lento pra entender esse tipo de coisa.

Ah sim, acabei postando meio com pressa então não tive muito tempo de explicar. Acho que é uma dúvida normal quando não se tem muito contato com o tema (que não é tão acessível também, infelizmente).

Basicamente, um Parser (ou, se quiser, um Analisador Sintático) é um processador de texto. A ideia é que você jogue um texto nele, e ele converta aquele input em uma representação mais adequada para manipulação. Um exemplo clássico, que botei até na imagem do tópico, é processamento de expressões aritméticas:

Parsing-example.png

A ideia aí é que o parser pega essa string "1 + 2 * 3", que não temos como usar pra calcular o resultado (até temos, mas mais sobre isso depois) e converte numa árvore (que costuma ser chamada de AST, ou Abstract Syntax Tree/Árvore Abstrata de Sintaxe), que é bem simples de usar (basta aplicar recursão).

Para fins de demonstração (e porque costuma ser útil) eu implementei um parser aritmético no plugin também:
Javascript:
const { expression } = Schach.Parsing.Arithmetic;

const { parsed: expr } = expression().run("1 + 2 * 3")
console.log(expr);

A saída desse script é a seguinte:
Javascript:
{
    "type": "operator",
    "operator": "+",
    "left": {
        "type": "number",
        "value": 1
    },
    "right": {
        "type": "operator",
        "operator": "*",
        "left": {
            "type": "number",
            "value": 2
        },
        "right": {
            "type": "number",
            "value": 3
        }
    }
}

E podemos avaliar o resultado com outra função:
Javascript:
const { evaluate } = Schach.Parsing.Arithmetic;

console.log(evaluate(expr));

E como esperado, a resposta é 7.

Sem o plugin, a única alternativa para isso é a função eval. Embora seja mais poderosa do que esse parser (porque internamente ela usa outro parser mais cheio de coisa: o do javascript) ela tem algumas desvantagens. Por exemplo, está limitada à sintaxe do Javascript e não nos permite acesso à representação intermediária (a AST).

No fim, avaliar expressões direto a partir da AST é mais rápido até do que usar o eval, porque não precisamos fazer a fase de parsing novamente.

Além disso, quando nos livramos da sintaxe do Javascript, temos bem mais liberdade de manipular a sintaxe como quisermos. Por exemplo, suponha que queremos pegar uma equação digitada pelo jogador na forma <expressão> = <expressão>. Tudo que precisamos é definir um parser assim:

Código:
const { expression } = Schach.Parsing.Arithmetic;
const { spaces, char } = Schach.Parsing.Text;

const equation =
    expression()
    .thenDrop(spaces())
    .thenDrop(char('='))
    .thenDrop(spaces())
    .zip(expression())
    .map(([left, right]) => ({ type: 'equation', left, right }));

E temos uma árvore representando a equação. Com isso daria pra simplificar a equação, usar ela pra aproximar um gráfico numericamente, etc.

Saindo um pouco da aritmética, dá também pra fazer um parser de algo parecido com XML, por exemplo:

Javascript:
const { Parser, pure, many, many1 } = Schach.Parsing;
const { number } = Schach.Parsing.Arithmetic;
const { char, predicate, spaces, string } = Schach.Parsing.Text;

const ESCAPE = {
    'b': "\b",
    'f': "\f",
    'n': "\n",
    'r': "\r",
    't': "\t",
    'v': "\v",
    "'": "'",
    '"': '"',
    '\\': '\\'
};

const escapeSequence = char("\\")
    .dropThen(
        predicate(c => String.fromCodePoint(c) in ESCAPE, 'valid escape character')
        .map(String.fromCodePoint)
        .map(c => ESCAPE[c]));

const stringValue = char('"')
    .dropThen(
        many(
            escapeSequence
            .or(
                predicate(c => c != '"'.codePointAt(0) && c != '\\'.codePointAt(0), 'not " or \\')
                .map(String.fromCodePoint))))
    .thenDrop(char('"'))
    .map(l => l.join(''));

const value = stringValue.or(number())
    .mapError(({ actual }) => ({ actual, expected: "string or number" }));

const property = () =>
    many1(
        predicate(c => c != 32 && c != '='.codePointAt(0), 'neither space or =')
        .map(String.fromCodePoint))
    .map(l => l.join(''))
    .thenDrop(spaces())
    .thenDrop(char('='))
    .thenDrop(spaces())
    .zip(value)
    .map(([name, value]) => ({ name, value }));

const openTag =
    char('<')
    .dropThen(
        many1(
            predicate(
                c => c != 32 && c != '>'.codePointAt(0), 'neither space or >')
            .map(String.fromCodePoint))
        .map(l => l.join('')))
    .thenDrop(spaces())
    .zip(many(property().thenDrop(spaces())))
    .thenDrop(char('>'))
    .map(([tag, props]) => ({ tag, props }));

const closeTag = (tag) => string(`</${tag}>`);

const myXML = Parser.of(() => {
    const tag =
        openTag.flatMap(({ tag, props }) =>
            myXML().or(pure(undefined))
            .map(content => ({ tag, props, content }))
            .thenDrop(closeTag(tag)));
    return many1(tag.or(value));
});

Aí se rodarmos o parser numa string:

Javascript:
myXML().run('<abc><def ghi="asd">123</def></abc>')

Temos a seguinte saída:
Javascript:
[{
    "tag": "abc",
    "props": [],
    "content": [{
        "tag": "def",
        "props": [{
            "name": "ghi",
            "value": "asd"
        }],
        "content": [123]
    }]
}]

Pode parecer meio confuso no começo, mas são menos de 70 linhas para um parser quase completo de XML. A maioria dos componentes aí poderia ser reutilizado também (por exemplo, o stringValue é uma string JSON).

Creio que com isso dá pra imaginar a utilidade de algo assim para ler configurações e coisas do tipo em um plugin. No geral, é uma ferramenta bem mais poderosa que uma Regex por exemplo e bem mais controlada e segura que um eval da vida.
 
Última edição:
Cara... se eu tivesse tido um professor tão bom qianto você nas minhas aulas de estrututa de dados eu não estaria fazendo essa pergunta que julgo eu deve ser bem amadora. Gostei desse teu plugin e vou pensar numa forma de aproveitar isso de alguma forma já que volta e meia eu me canso da fakta de praticidade de manipular as fórmulas para calculo do dano no rpg maker.
Obrigado pela explicação e mais uma vez por disponobilizar esse recurso.
 
Voltar
Topo