Java >> Java Program >  >> Java

ANTLR och webben:ett enkelt exempel

ANTLR på webben:varför?

Jag började skriva mina första program på MS-DOS. Så jag är väldigt van vid att ha mina verktyg installerade på min maskin. Men under 2016 är webben överallt och därför kan våra språk behövas även där.

Möjliga scenarier:

  • ANTLR också på webben:
    • användare kan vilja komma åt och möjligen till mindre ändringar av filer skrivna i en DSL även från webben, samtidigt som de fortsätter att använda sina fettklienter för komplexa uppgifter.
  • ANTLR endast på webben:
    • du har att göra med domänexperter som är ovilliga att installera IDE, så de föredrar att ha någon webbapplikation där de kan skriva sina DSL-program.
    • du vill erbjuda en enkel DSL för att specificera frågor som ska köras direkt i webbläsaren.

I det första fallet kan du generera din ANTLR-parser med hjälp av ett Java-mål och ett Javascript-mål, medan du i det andra bara kunde rikta dig till JavaScript

Ett enkelt exempel:en Att göra-lista

Den DSL vi kommer att använda i det här exemplet kommer att vara superenkel:den representerar en att göra-lista, där varje att göra-objekt finns på en separat rad och startas av en asterisk.

Ett exempel på en giltig inmatning:

* do this
* do that
 
* do something else after an empty line

Och det här är vår grammatik:

grammar todo;
 
elements
    : (element|emptyLine)* EOF
    ;
 
element
    : '*' ( ' ' | '\t' )* CONTENT NL+
    ;
 
emptyLine
    : NL
    ;
 
NL
    : '\r' | '\n' 
    ;
 
CONTENT
    : [a-zA-Z0-9_][a-zA-Z0-9_ \t]*
    ;

Använda ANTLR Javascript-målet

Du skulle behöva installera ANTLR-verktyget för att generera Javascript-koden för vår parser. Istället för att manuellt ladda ner ANTLR och dess beroenden kan du använda ett enkelt Gradle-skript. Det är också mycket enkelt att uppdatera den version av ANTLR du använder.

apply plugin: 'java'
 
repositories {
    jcenter()
}
 
dependencies {
    runtime 'org.antlr:antlr4:4.5.2'
}
 
task generateParser(type:JavaExec) {
   main = 'org.antlr.v4.Tool'
   classpath = sourceSets.main.runtimeClasspath
   args = ['-Dlanguage=JavaScript', 'todo.g4', '-o', 'static/generated-parser']
}

Du kan nu generera din parser genom att köra:

gradle generateParser

Ok, den här var lätt.

Anropar parsern

Tyvärr fungerar inte JS-biblioteken vi använder när vi bara öppnar lokala filer:det betyder att vi även för vårt lilla exempel måste använda HTTP. Vår webbserver kommer bara att behöva servera ett gäng statiska filer. För att göra detta valde jag att skriva en superenkel applikation i kolv. Det finns miljontals alternativ för att visa statiska filer så välj den du föredrar. Jag kommer inte att beskriva hur man servar statiska filer genom kolven här men koden är tillgänglig på GitHub och om du har problem med det kan du lägga till en kommentar till det här inlägget för att låta mig veta.

Våra statiska filer kommer att innehålla:

  • den genererade parsern vi fick genom att köra gradle generateParser
  • Antlr4 JS runtime
  • JS-biblioteket require.js
  • HTML och CSS

Du kan hämta Antlr4 JS runtime härifrån. För att undvika att behöva importera tiotals filer manuellt kommer vi att använda require.js. Du kan få smaken eller require.js vi behöver härifrån.

Vi kommer att lägga till ett textområde och en knapp. När användaren klickar på knappen kommer vi att analysera innehållet i textområdet. Enkelt, eller hur?

Detta är HTML-koden för detta designmästerverk:

<div id="inputs">
<textarea id="code">
* play with antlr4
* write a tutorial
</textarea>
<br/>
<button id="parse">Parse</button>
</div>

Först och främst, importera require.js:

<script type="text/javascript" src="lib/require.js"></script>

Förresten, vi använder inte jquery, jag vet att detta kan vara chockerande.

Bra, nu måste vi anropa parsern

<script type="text/javascript">
var antlr4 = require('antlr4/index');
var TodoLexer = require('generated-parser/todoLexer');
var TodoParser = require('generated-parser/todoParser');
document.getElementById("parse").addEventListener("click", function(){
    var input = document.getElementById("code").value;
    var chars = new antlr4.InputStream(input);
    var lexer = new TodoLexer.todoLexer(chars);
    var tokens  = new antlr4.CommonTokenStream(lexer);
    var parser = new TodoParser.todoParser(tokens);
    parser.buildParseTrees = true;
    var tree = parser.elements();
    console.log("Parsed: "+ tree);
});
</script>

Coolt, nu är vår kod tolkad men vi gör ingenting med den. Visst kan vi aktivera utvecklarkonsolen i webbläsaren och skriva ut lite information om trädet för att verifiera att det fungerar och för att bekanta oss med strukturen för trädet ANTLR återvänder.

Visa resultat

Om vi ​​byggde någon form av TODO-applikation kanske vi på något sätt vill representera informationen som användaren infogade via DSL.

Låt oss få något sånt här:

För att göra det behöver vi i princip lägga till funktionen updateTree som navigerar i trädet som returneras av ANTLR och bygger några DOM-noder för att representera dess innehåll

<script type="text/javascript">
var updateTree = function(tree, ruleNames) {
    var container = document.getElementById("tree");
    while (container.hasChildNodes()) {
        container.removeChild(container.lastChild);
    }
 
    for (var i = 0; i < tree.children.length; i++) {
        var child = tree.children[i];
        var nodeType = ruleNames[child.ruleIndex];
        if (nodeType == "element") {
            var newElement = document.createElement("div");
            newElement.className = "todoElement";
            var newElementText = document.createTextNode(child.children[2].getText());
            newElement.appendChild(newElementText);
            container.appendChild(newElement);
        }
    }
};
 
var antlr4 = require('antlr4/index');
var TodoLexer = require('generated-parser/todoLexer');
var TodoParser = require('generated-parser/todoParser');
document.getElementById("parse").addEventListener("click", function(){
    var input = document.getElementById("code").value;
    var chars = new antlr4.InputStream(input);
    var lexer = new TodoLexer.todoLexer(chars);
    var tokens  = new antlr4.CommonTokenStream(lexer);
    var parser = new TodoParser.todoParser(tokens);
    parser.buildParseTrees = true;
    var tree = parser.elements();
    console.log("Parsed: "+ tree);
    updateTree(tree, parser.ruleNames);
});
</script>

Här har du!

Kod

Om det inte är första gången du läser den här bloggen kommer du att misstänka att någon kod kommer. Som vanligt finns koden på GitHub: https://github.com/ftomassetti/antlr-web-example

Nästa steg

Nästa steg är att utföra felhantering:vi vill fånga upp fel och peka dem till användarna. Sedan kanske vi vill lägga till syntaxmarkering genom att till exempel använda ACE. Det här verkar vara en bra utgångspunkt:

  • https://github.com/antlr/antlr4/blob/master/doc/ace-javascript-target.md

Jag tror verkligen att enkla textbaserade DSL:er kan bidra till att göra flera applikationer mycket kraftfullare. Det är dock inte enkelt att skapa en trevlig redigeringsupplevelse på webben. Jag skulle vilja ägna lite mer tid åt att leka med det här.

Java-tagg