keine ahnung ob du dich schon mal mit lex/yacc (flex/bison von gnu) beschäftigt hast
anscheinend nicht
was meinst du mit Parser? finde dazu nichts aussagekräftiges bei Google :/
aber ich würde dir trotzdem raten das nicht mit regex zu lösen, das bekommst du nie unter kontrolle.
wenn du dir das
calculator beispiel hier ansiehst, ist das schon fast 90% deiner gesuchten lösung.
die syntax ist nicht schwer zu verstehen. alles zw.
%lex
und
/lex
ist für den
scanner der rest ist der
parser
im scanner legst du die
token fest die der parser in seinen regeln verwenden darf.
die token selbst werden wieder über regex definiert, kompliziertere sachen benötigst du vermutlich nicht.
der grundsätzliche aufbau des parser teils sieht so aus:
Bison 1.875
im calculator-bsp kommen als erstes die beiden
Bison Declarations Operator Precedence und
Start-Symbol
dann kommen die Grammar rules. die sind im beispiel doch ganz übersichtlich. die
regeln selbst sind eine form von
bnf wie man sie z.b. aus der js-spec in ähnlicher weise kennt.
in geschweiften klammern {} stehen unter den regeln die
Actions das ist js-code der ausgeführt wird, wenn eine regel matched.
$$ ist in den actions sozusagen der returnwert, $1 bis $n sind die werte welche hinter den teilregeln stehen welche die regel definieren und yytext ist der value welcher hinter einem token steht.
im beispiel:
Code:
e
: e '+' e
{$$ = $1+$3;}
| e '-' e
{$$ = $1-$3;}
| e '*' e
{$$ = $1*$3;}
| e '/' e
{$$ = $1/$3;}
| e '^' e
{$$ = Math.pow($1, $3);}
| '-' e %prec UMINUS
{$$ = -$2;}
| '(' e ')'
{$$ = $2;}
| NUMBER
{$$ = Number(yytext);}
| E
{$$ = Math.E;}
| PI
{$$ = Math.PI;}
;
die regel e besteht z.b. aus PI. PI ist im scanner als token definiert worden welcher aus dem string "PI" besteht. e wird dann der wert Math.PI in der action zugewiesen(Math.PI wird auf den stack gelegt)
mit E ist es das gleiche.
e kann aber auch aus dem token NUMBER bestehen. NUMBER wurde im scanner definiert als regex
[0-9]+("."[0-9]+)?\b
. 123 wird vom scanner also als NUMBER identifiziert und dem parser übergeben.
die action hinter NUMBER weist e wieder den wert zu. diesmal ist das token aber keine konstante, sondern variabel. der string "123" ,welcher vom scanner also als NUMBER identifiziert wurde, ist am parser gespeichert und über yytext zugreifbar. dieser wird in eine zahl konvertiert und auf den stack gelegt.
angenommen es wird der String "(PI)" geparst, dann trifft als erstes die regel
PI
welche e den wert Math.PI zuweist und dann (die regeln können rekursiv sein) die regel
'(' e ')'
hier wird e der wert der 2. teilregel zugewiesen. die regel e ist ja definiert als token '(' gefolgt von der teilregel e gefolgt von dem token ')'. (die token '(' und ')' hätte ich LEFTBRACE und RIGHTBRACE genannt, hier heißen sie eben '(' und ')' nicht verwechseln mit strings, das sind tokennamen)
die teilregel e wurde ja im vorhergehenden schritt abgearbeitet und ihr der wert Math.PI zugewiesen. auf dem stack liegt also token '(', MATH.PI und token ')' mit $2 kann auf den wert der teilregel zugegriffen werden und dieser wird wieder e zugewiesen.
der rest sollte jetzt klar sein.
den parser kannst du dier generieren lassen und runterladen. z.b. als test.js. das kannst du einfach über script-tags in dein html einbinden und dann mit
var result = parser.parse("text")
deinen text parsen lassen.
du brauchst jetzt noch deine felder {00RyWNRMT1}
da würde ich die token '{', '}' und IDENTIFIER definieren, damit PI und E nicht als IDENTIFIER identifiziert werden, muss PI und E vor dem token IDENTIFIER stehen.
dann kannst du eine neue regel fields einfügen
Code:
fields : '{' IDENTIFIER '}'
{
$$ = getIdentifierValue($IDENTIFIER);
};
du brauchst noch eine funktion getIdentifierValue welche dir den wert sucht. übergeben wird der name(bei "{00RyWNRMT1}" also "00RyWNRMT1").
dafür fügst du diese am anfang der datei zw. %{ und %} als Prologue ein.
Code:
%{
var test = {
aaa: 7,
bbb: 3
};
function getIdentifierValue(name)
{
return test[name];
}
%}
als beispiel lese ich hier das objekt test, wo du die werte herbekommst musst du wissen.
die regel fields musst du jetzt noch als teilregel von e einfügen
Code:
e
: e '+' e
{$$ = $1+$3;}
| e '-' e
{$$ = $1-$3;}
| e '*' e
{$$ = $1*$3;}
| e '/' e
{$$ = $1/$3;}
| e '^' e
{$$ = Math.pow($1, $3);}
| '-' e %prec UMINUS
{$$ = -$2;}
| '(' e ')'
{$$ = $2;}
| NUMBER
{$$ = Number(yytext);}
| fields
{$$ = $1;}
| E
{$$ = Math.E;}
| PI
{$$ = Math.PI;}
;
und schon ist das auch erledigt
das ganze sieht dann vollständig so aus (yytext habe ich mal noch durch die $-entsprechung ersetzt, das ist am anfang vielleicht einfacher wenn man nicht 10 verschiedene schreibweisen benutzt, sondern nur eine)
Code:
%{
var test = {
aaa: 7,
bbb: 3
};
function getIdentifierValue(name)
{
return Number(test[name]);
}
%}
/* description: Parses end executes mathematical expressions. */
/* lexical grammar */
%lex
%%
\s+ /* skip whitespace */
"PI" return 'PI'
"E" return 'E'
[0-9]+("."[0-9]+)?\b return 'NUMBER'
[a-zA-Z_][a-zA-Z0-9_]* return 'IDENTIFIER'
"*" return '*'
"/" return '/'
"-" return '-'
"+" return '+'
"^" return '^'
"(" return '('
")" return ')'
"{" return '{'
"}" return '}'
<<EOF>> return 'EOF'
. return 'INVALID'
/lex
/* operator associations and precedence */
%left '+' '-'
%left '*' '/'
%left '^'
%left UMINUS
%start expressions
%% /* language grammar */
expressions
: e EOF
{return $1;}
;
e
: e '+' e
{$$ = $1+$3;}
| e '-' e
{$$ = $1-$3;}
| e '*' e
{$$ = $1*$3;}
| e '/' e
{$$ = $1/$3;}
| e '^' e
{$$ = Math.pow($1, $3);}
| '-' e %prec UMINUS
{$$ = -$2;}
| '(' e ')'
{$$ = $2;}
| NUMBER
{$$ = Number($1);}
| fields
{$$ = $1;}
| E
{$$ = Math.E;}
| PI
{$$ = Math.PI;}
;
fields : '{' IDENTIFIER '}'
{$$ = getIdentifierValue($2);};
das kannst du
hier eingeben, generieren und downloaden.
testen geht dort leider nicht gleich, da der dynamisch generierte parser die eigenen funktionen nicht kennt.
aber mit dem runtergeladenem statischen parser sollte das so problemlos funktionieren:
Code:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="test.js"></script>
<script src="jquery.js"></script>
<script>
$(document).ready(function () {
$("button").click(function () {
try {
var result = parser.parse($("#source").val())
$("#out").html(result);
} catch (e) {
$("#loading").html(String(e));
}
});
});
</script>
</head>
<body>
<h2>Test Your Parser</h2>
<textarea id="source" rows="8" cols="80">(-{aaa}+5)*PI^2</textarea><br/>
<button>Parse</button>
<pre id="out"></pre>
<div id="loading"></div>
</body>
</html>