Arvutiteaduse instituut
  1. Kursused
  2. 2017/18 kevad
  3. Automaadid, keeled ja translaatorid (LTAT.03.006)
EN
Logi sisse

Automaadid, keeled ja translaatorid 2017/18 kevad

  • Üldinfo
  1. Õppekorraldus
  2. Eksam
  3. Reeglid
  4. Töövahendid
  5. Projekt
  • Kava
  1. Soojendus
  2. Regulaaravaldised
  3. Olekumasinad
  4. Lõplikud automaadid
  5. Avaldise struktuur
  6. Grammatikad ja lekser
  7. Käsitsi parsimine
  8. ANTLR intro
  9. AST loomine
    1. ANTLRi parsepuu
    2. AST klassid
    3. Kodutöö (Visitor*)
  10. Interpretaator
  11. Semantiline analüüs
  12. Kompilaator
  • Moodle
  • Bitbucket
  • Fleep!

Parse-puu läbimine Visitoriga

OOP-i eksperdid harilikult krimpsutavad nina instanceof-i kasutamise peale. Paremaks peetakse seda, kui iga alamklassi isend (nt. puu tipp tüübiga KorrutamineJagamineContext) leiab ise tee selle õige koodini, mis tema jaoks on mõeldud. Keerukus tuleb sellest, et me ei taha (ja tegelikult ei saagi) genereeritud Context klassidesse oma meetodeid lisada. Probleemi lahendamiseks on välja mõeldud üsna komplitseeritud skeem, mida inglise keeles nimetakse visitor pattern.

ANTLR tuleb OOPi puristidele vastu ja pakub välja mugava võimaluse korraldada parse-puu läbimist selle skeemi põhjal. Nimelt genereerib ta soovi korral koos parseriga veel ühe liidese ja ühe klassi, mis realiseerivad selle skeemi raamistiku. AKTK puhul on nendeks AktkVisitor ja AktkBaseVisitor. Nende genereerimiseks on juba build.gradle faili lisatud generateGrammarSource reegli juurde vastav käsureaparameeter.

Vaatame kõigepealt liidest AktkVisitor (võta see enda IDE-s lahti). Nagu näed, on siin meetod iga grammatika reegli ja iga sildi kohta. Meetod saab argumendiks vastava tüübiga parse-puu tipu ning peab ka midagi tagastama. Liidese päisest on näha, et tagastustüüp T ei ole mitte mingi konkreetne tüüp, vaid tüübiparameeter, seega on võimalik liidese realiseerimisel ise öelda, mis tüüpi asju me tahame puu läbimisel tagastada. Näiteks avaldise väärtustamise juures määraksime me selleks tüübiks Integer.

Puu "käsitsi" läbimise näite juures ilmnes, et meil ei olnud vaja käsitleda kõiki võimalikke parse-puu tipuklasse -- piisas kui tegime midagi sisulist ainult antud ülesannet puudutavate tipuklasside puhul. Vahetippude puhul lihtsalt liikusime allapoole. Liidese AktkVisitori implementeerimisel nõuavad Java reeglid aga kõigi meetodite realiseerimist. Selleks, et ka siin programmeerijate vaeva vähendada, genereeris ANTLR koos selle liidesega ka ühe klassi, AktkBaseVisitor, mis implementeerib antud liidese selliselt, et iga tiputüübi juures delegeeritakse töö alluvatele, kui neid on. Seega, kui me tahame teha midagi spetsiifilist vaid üksikute tiputüüpide puhul, siis ei ole meil mõtet mitte implementeerida liidest AktkVisitor, vaid luua alamklass klassile AktkBaseVisitor, kus vaid teatud meetodid on ülekaetud.

Kui me tahaksime eespool mainitud lihtsa avaldise väärtustamise ülesannet realiseerida visitoriga, siis sobiks meile selline klass:

import org.antlr.v4.runtime.tree.ParseTree;

import week9.AktkBaseVisitor;
import week9.AktkParser.*;

public class AktkEvaluationVisitor extends AktkBaseVisitor<Integer> {
	@Override
	public Integer visitArvuliteraal(ArvuliteraalContext tree) {
		return Integer.parseInt(tree.getText());
	}

	@Override
	public Integer visitLiitmineLahutamine(LiitmineLahutamineContext tree) {
		return arvutaTehe (
				tree.getChild(0), 
				tree.getChild(1).getText(),
				tree.getChild(2)
		);
	}

	@Override
	public Integer visitKorrutamineJagamine(KorrutamineJagamineContext tree) {
		return arvutaTehe (
				tree.getChild(0), 
				tree.getChild(1).getText(),
				tree.getChild(2)
		);
	}

	private Integer arvutaTehe(ParseTree leftChild, String operator, 
			ParseTree rightChild) {

		// Väärtustan rekursiivselt.
		// visit on spets. meetod, mis organiseerib õige visit-meetodi
		// väljakutsumise vastavalt argumendi tüübile.
		int leftValue  = this.visit(leftChild);
		int rightValue = this.visit(rightChild); 

		// väärtustan terve operatsiooni
		switch (operator) {
		case "+": return leftValue + rightValue;
		case "-": return leftValue - rightValue;
		case "*": return leftValue * rightValue;
		case "/": return leftValue / rightValue;
		case "%": return leftValue % rightValue;
		default : throw new RuntimeException("Tundmatu operaator");
		}
	}
}

Nagu näha, on need juhtumid, mida me enne eristasime instanceof kasutamise abil, on nüüd saanud endale eraldi meetodid. Kuna visitor pattern ei lubanud meil kokku võtta juhtumeid LiitmineLahutamine ning KorrutamineJagamine, siis on kummagi jaoks küll eraldi meetod, aga mõlemad delegeerivad põhitöö samale abimeetodile. Selles variandis ei ole enam eraldi käsitletud vahetippe, sest nende käsitlemiseks piisab ülemklassis defineeritud meetoditest.

Selle klassi kasutamiseks piisab järgmisest koodist:

	private static int evaluateWithVisitor(ParseTree tree) {
		AktkVisitor<Integer> visitor = new AktkEvaluationVisitor();
		return tree.accept(visitor);
	}

Võimaliku lahenduse algus

Visitoriga variant

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.antlr.v4.runtime.tree.ParseTree;

import week9.ast.*;
import week9.AktkBaseVisitor;
import week9.AktkParser.*;

public class AktkAstCreationVisitor extends AktkBaseVisitor<AstNode> {
	@Override
	public AstNode visitFunktsiooniValjakutse(FunktsiooniValjakutseContext ctx) {
		// NB! siin tuleb kontrollida, kuidas näeb välja 
		//   - ilma argumentideta 
		//   - ühe argumendiga
		//   - mitme argumendiga 
		// funktsiooni väljakutse parse-puu
		String funktsiooniNimi = ctx.getChild(0).getText();

		List<Expression> argumendid = new ArrayList<Expression>();

		// Kui väljakutsel on 3 alluvat, siis need on funktsiooni nimi, alustav
		// sulg ja lõpetav sulg.

		if (ctx.getChildCount() > 3) {
			// argumentavaldised on paarisarvulise indeksiga lapsed
			for (int i=2; i < ctx.getChildCount(); i += 2) {
				argumendid.add((Expression)this.visit(ctx.getChild(i)));
			}
		}

		return new FunctionCall(funktsiooniNimi, argumendid);
	}


	@Override
	public AstNode visitOmistamine(OmistamineContext ctx) {
		return new Assignment(____________, _____________);
	}

	@Override
	public AstNode visitLauseteJada(LauseteJadaContext ctx) {
		// Siin on tähtis järgi uurida, kuidas näib välja mitme lausega 
		// lauseteJada parse-puu.

		List<Statement> laused = new ArrayList<Statement>();

		____________________________________;
		____________________________________;
		____________________________________;
		____________________________________;
		____________________________________;
		____________________________________;
		____________________________________;

		return new Block(laused);
	}
}

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused