Auswertung von Ausdrücken in Java (Auswertung von Ausdrücken in Java), Lektion, Seite 722855
https://www.purl.org/stefan_ram/pub/auswerten_java (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Auswertung von Ausdrücken in Java

Zum Interpretieren oder übersetzen eines Ausdrucks (Terms) wird

dieser in lexikalische Einheiten (Token, Wörter) zerlegt, die

dann von einem syntaktischen Zerleger in ein "Symbol"

(Syntaxbaum) strukturiert werden. Dieses Symbol kann dann

ausgewertet oder in eine andere Darstellung übersetzt werden.

Die syntaktische Zerlegung entspricht den Regeln für den Aufbau

eines Ausdrucks, die oft durch sogenannte "Produktionsregeln" der

EBNF (erweiterte Backus-Nauer-Form) gegeben werden, die

manchmal linksrekursiv sind.

Beim Schreiben eines syntaktischen Analysators bereiten diese

linksrekursiven Produktionsregeln oft Kopfzerbrechen, denn es ist

nicht klar, wie hier eine unbeschränkte Rekursion vermieden

werden kann. Die Lösung ist es, sie in rechtsrekursive

Produktionsregeln umzuschreiben:

Die Addition mit einem binären zwischengestellten Operator ist

beispielsweise linksassoziativ. Allerdings ist es technisch

einfacher, rechtsassoziativ zu analysieren. Daher analysiert

man das ganze rechtsassoziativ und realisiert das Analysat

linksassoziativ.

Eine gewuenschte linksassoziative Grammatik lautet

beispielsweise:

<numeral> ::= '2' | '4' | '5'.

<expression> ::= <numeral> | <expression> '+' <numeral>.

Startsymbol: <expression>.

Zum Analysieren mit einem rekursiv absteigenden Analysator

verwendet man aber lieber folgende Grammatik:

<numeral> ::= '2' | '4' | '5'.

<expression> ::= <numeral>[ '+' <expression> ].

Startsymbol: <expression>.

Das kann man auch iterativ schreiben:

<numeral> ::= '2' | '4' | '5'.

<expression> ::= <numeral>{ '+' <numeral> }.

Startsymbol: <expression>.

Dabei baut man aber ein Analysat im Sinne der ersten

Grammatik auf. Beispielcode hierzu:

class Scan

{ final static java.lang.String source = "5+4+2)";

static int pos = 0;

static char get(){ return source.charAt( pos++ ); }}

class Parse

{

static int numeral(){ return Scan.get() - '0'; }

static int expression(){ int result = numeral();

while( '+' == Scan.get() )result += numeral();

return result; }}

public final class Start

{

public static void main( final java.lang.String[] args )

{ java.lang.System.out.println( Parse.expression() ); }}

Um nun noch Ausdrücke mit höherem Vorrange lesen zu

können, kann die Grammatik erweitert werden.

<numeral> ::= '2' | '4' | '5'.

<product> ::= <numeral> | <product> '*' <numeral>.

<sum> ::= <product> | <sum> '+' <product>.

Startsymbol: <sum>.

In iterativer Schreibweise:

<numeral> ::= '2' | '4' | '5'.

<product> ::= <numeral>{ '*' <numeral> }.

<sum> ::= <product>{ '+' <product> }.

Startsymbol: <sum>.

In Java:

class Scan

{ final static java.lang.String source = "5+4*2)";

static int pos = 0;

static char get( final boolean advance )

{ return source.charAt( advance ? pos++ : pos ); }}

class Parse

{

static int numeral(){ return Scan.get( true ) - '0'; }

static int product(){ int result = numeral();

while( '*' == Scan.get( false )){ Scan.get( true ); result *= numeral(); }

return result; }

static int sum(){ int result = product();

while( '+' == Scan.get( true ))result += product();

return result; }}

public final class Start

{

public static void main( final java.lang.String[] args )

{ java.lang.System.out.println( Parse.sum() ); }}

Übungsaufgaben

- Welche Ausgabe erzeugen die obigen Programme jeweils?

- Erweitern Sie die letzte Grammatik und das letzte

Programm um die Subtraktion.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

um die Division.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

so, daß auch Zahlen mit mehreren Ziffern akzeptiert

werden.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

so, daß in den Termen auch Leerräume (Leerzeichen)

in der üblichen Weise verwendet werden können.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

so, daß auch eingeklammerte Terme akzeptiert werden.

("(2+4)*5)" sollte das Ergebnis "30" liefern.)

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

so, daß auch das Vorzeichen "-" erkannt wird.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe

so, daß weitere Operatoren und Methoden akzeptiert

werden.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe so,

daß sinnvolle Fehlermeldungen für alle möglichen

Eingaben, die nicht den Regeln der Eingabesprache

entsprechen, erzeugt werden.

- Erweitern Sie das Ergebnis der letzten Übungsaufgabe so,

daß die Fehlermeldungen, die Stelle anzeigen, an der

der Fehler bemerkt wurde. Es sollen auch Ausdrücke

akzeptiert werden, die über mehrere Zeilen gehen und

bei Fehlern die Nummer der Zeile mit dem Fehler

angezeigt werden.

Neben den rekursiv absteigenden Zergliederern gibt es auch

noch die Zweikellerzergliederer. Einen solchen habe ich in

meinen kleinen BASIC-Interpretierer gebaut:

https://www.purl.org/stefan_ram/java/RamBasic657.java

Mein Zergliederer ist insofern recht »konservativ« als

er einem alten Verfahren von Bauer folgt:

Sequential Formula Translation

K. Samelson and F. L. Bauer

Communications of the ACM

Volume 3, Issue 2 (February 1960)

Pages: 76 - 83

ISSN:0001-0782

Den Algorithmus habe ich etwas abgewandelt in der Klasse

»Enroller« notiert:

class Enroller

{ public static void enrollOperator

( final OperatorSymbol self,

final OperatorStack operatorStack,

final DesignationStack designationStack )

{ while

( operatorStack.containsAnOperator() &&

operatorStack.top().leftPriority() > self.rightPriority() )

{ OperatorSymbol operator = operatorStack.pop();

operator.reduce( operatorStack, designationStack ); }

operatorStack.accept( self ); }

public static void enrollDesignation

( final Designation self,

final OperatorStack operatorStack,

final DesignationStack designationStack )

{ designationStack.accept( self ); }}

Wenn man sich die Parameterdeklarationen und die langen

Bezeichner wegdenkt, ist dieser Code wirklich winzig.

In Worten:

Ich verwende zwei Keller: einen Operatorkeller und einen

Wertekeller (»designationStack«). Dann wird das jeweils nächste

Symbol aus der Eingabe hinzugefügt:

- wenn es ein Operator ist, dann:

- Solange der Operator auf dem Operatorkeller höheren

Vorrang hat, wende ihn auf seine Argumente

auf dem Wertekeller an (»reduce«) und lege das Ergebnis

dann wieder auf den Wertekeller. (»reduce« ist für

jeden Operator anders, für »+« ersetzt es

beispielsweise die beiden oberen Werte durch ihre Summe.)

- Lege den neuen Operator auf den Operatorkeller

- wenn es ein Operand ist, dann

- Lege ihn auf dem Argumentkeller

Das Verfahren von Samelson und Bauer wird oft benutzt

und gelehrt, aber die obige Quelle wird dabei selten

angegeben.

Glossar:

"NLR grammar" = "non left-recursive grammar"

pre-order traversal = (node, left, right)

Quellen:

JEP - Java Mathematical Expression Parser

http://www.singularsys.com/jep/

Steven Metsker: Building Parsers with Java.

Addison-Wesley 2001, ISBN 0201719622.

A.W. Appel: Modern Compiler Implementation in Java.

Cambridge University Press 1998, ISBN 0521586542.

Niklaus Wirth: Grundlagen und Techniken des Compilerbaus.

ISBN 3486243748

http://www.oberon.ethz.ch/WirthPubl/CBEAll.pdf

Aho, Sethi, Ullman: Compilerbau, Tl. 1. Oldenbourg 1999

ISBN 3486252941

Aho, Sethi, Ullman: Compilerbau, Tl. 2. Oldenbourg 1999

ISBN 3486252666

Steven Metsker

Building Parsers with Java

Addison-Wesley 2001

http://www.awprofessional.com/title/0201719622

Compiler-Compiler JACCIE

http://www2-data.informatik.unibw-muenchen.de/Research/Tools/JACCIE/downloadEN/download.html

Implementing a scripting engine

http://www.flipcode.com/articles/scripting_issue01.shtml

Ein Einführung in den Compilerbau mit modernen Standardwerkzeugen:

http://staff.polito.it/silvano.rivoira/HowToWriteYourOwnCompiler.htm

Quellen zu speziellen Einzelthemen:

Quellen zu BNF:

Erklärung einer Form der EBNF

https://www.purl.org/stefan_ram/pub/bnf-ebnf

Die ursprüngliche BNF findet sich AFAIK in

Naur, Peter (ed.), "Revised Report on the Algorithmic Language ALGOL 60.",

Communications of the ACM, Vol. 3 No.5, pp. 299-314, May 1960.

Eine aktuelle EBNF-Variante in:

ISO/IEC 14977:1996(E), Information technology: Syntactic metalanguage ISO/I

EC 14977 : 1996(E), EXTENDED BNF

oder:

Crocker, Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 2234

, November 1997.

Quellen und BNF zu PL/0:

http://www.inf.hs-zigr.de/~wagenkn/TI/Berechenbarkeit/rekabstieg.html

http://www.246.dk/pl0.html

http://www.inf.hs-zigr.de/~wagenkn/TI/Berechenbarkeit/rekabstieg.html

Ein PL/0-Übersetzer in Pascal:

http://www.246.dk/pl0.html

Zu PL/0 (mit PL/0-Synax [sic!]):

http://www.inf.hs-zigr.de/~wagenkn/TI/Berechenbarkeit/rekabstieg.html

Einführung mit PL/0:

http://www.htw-dresden.de/~s2536/Compiler/Vorlesung_CI.pdf

http://www.htw-dresden.de/~s2536/Compiler/Vorlesung_CI.pdf

Guide to the PL/0 Compiler, http://www.cs.rochester.edu/courses/254/PLzero/guide.pdf

PL0/E - Pascal-ähnliche Programmiersprache (executables only), http://www.s-direktnet.de/homepages/neumann/Data/pl0/pl0.zip

Parboiled is an open-source Java library released under an Apache License. It provides support for defining PEG parsers directly in Java source code.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722855 stefan_ram:722855 Auswertung von Ausdrücken in Java Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722855, slrprddef722855, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/auswerten_java