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:
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.