/** * Copyright 2010-2017 Evgeny Gryaznov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package org.textmapper.idea.lang.templates.parser; import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiBuilder.Marker; import com.intellij.lang.PsiParser; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.textmapper.idea.lang.templates.lexer.LtplElementType; import org.textmapper.templates.ast.TemplatesLexer; import org.textmapper.templates.ast.TemplatesLexer.ErrorReporter; import org.textmapper.templates.ast.TemplatesLexer.Span; import org.textmapper.templates.ast.TemplatesLexer.Tokens; import org.textmapper.templates.ast.TemplatesParser; import org.textmapper.templates.ast.TemplatesParser.ParseException; import org.textmapper.templates.ast.TemplatesParser.Nonterminals; import java.io.IOException; import java.io.Reader; import java.util.HashMap; import java.util.Map; import java.util.Stack; /** * evgeny, 3/3/12 */ public class LtplParser implements PsiParser { private static final Map<Integer, IElementType> types = initTypes(); private static Map<Integer, IElementType> initTypes() { Map<Integer, IElementType> result = new HashMap<>(); result.put(Nonterminals.syntax_problem, TokenType.ERROR_ELEMENT); for (IElementType t : LtplElementTypes.allElements) { result.put(((LtplElementType) t).getSymbol(), t); } for (int expr : LtplElementTypes.allExpressions) { result.put(expr, LtplElementTypes.EXPRESSION); } return result; } private static IElementType reduceType(int token, int rule) { IElementType type = types.get(token); if (type != null) { return type; } return null; } @NotNull public ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) { final PsiBuilder.Marker file = builder.mark(); parseBundle(builder); file.done(root); return builder.getTreeBuilt(); } @NotNull public ASTNode parseBody(IElementType root, PsiBuilder builder) { final PsiBuilder.Marker file = builder.mark(); parseBody(builder); file.done(root); return builder.getTreeBuilt(); } private void parseBundle(PsiBuilder builder) { Marker grammar = builder.mark(); LtplParserEx parser = new LtplParserEx(builder); try { parser.parseInput(new LtplLexerEx(builder)); } catch (IOException e) { /* cannot happen */ } catch (ParseException e) { /* syntax error, ok */ } boolean cannotRecover = !parser.markers.isEmpty(); while (!parser.markers.isEmpty()) { parser.markers.pop().drop(); } while (!builder.eof()) { builder.advanceLexer(); } grammar.done(LtplElementTypes.BUNDLE); } private void parseBody(PsiBuilder builder) { Marker grammar = builder.mark(); LtplParserEx parser = new LtplParserEx(builder); try { parser.parseBody(new LtplLexerEx(builder)); } catch (IOException e) { /* cannot happen */ } catch (ParseException e) { /* syntax error, ok */ } boolean cannotRecover = !parser.markers.isEmpty(); while (!parser.markers.isEmpty()) { parser.markers.pop().drop(); } while (!builder.eof()) { builder.advanceLexer(); } grammar.done(LtplElementTypes.TEMPLATE_BODY); } private static class LtplParserEx extends TemplatesParser { private final PsiBuilder myBuilder; private final Stack<Marker> markers = new Stack<>(); public LtplParserEx(PsiBuilder builder) { super((message, line, offset, endoffset) -> { // ignore, errors are reported as syntax_problem productions }); myBuilder = builder; } private Marker mark() { Marker m = myBuilder.mark(); markers.push(m); return m; } private Marker clone(Marker inner) { Marker outer = inner.precede(); free(inner, false); markers.push(outer); return outer; } private void free(Marker m, boolean drop) { assert m == markers.pop(); if (drop) { m.drop(); } } private void drop(Span sym) { if (sym.value != null) { Marker m = (Marker) sym.value; free(m, true); sym.value = null; } } @Override protected void shift() throws IOException { Marker marker = tmNext.symbol != Tokens.eoi ? mark() : null; super.shift(); tmStack[tmHead].value = marker; } @Override protected void applyRule(Span left, int ruleIndex, int ruleLength) { for (int i = 0; i < ruleLength - 1; i++) { drop(tmStack[tmHead - i]); } if (ruleLength > 0) { tmStack[tmHead - (ruleLength - 1)].value = null; } Marker m = (Marker) left.value; if (m != null) { IElementType elementType = reduceType(left.symbol, ruleIndex); if (elementType != null) { left.value = clone(m); if (left.symbol == Nonterminals.syntax_problem) { m.error("syntax error"); } else { m.done(elementType); } } } if (left.symbol == Nonterminals.input || left.symbol == Nonterminals.body) { drop(left); } } @Override protected boolean restore() { boolean restored = super.restore(); if (restored) { /* restored after syntax error - mark the location */ tmStack[tmHead].value = mark(); } return restored; } @Override protected void dispose(Span sym) { drop(sym); } @Override protected void cleanup(Span sym) { assert sym.value == null; } } private static class LtplLexerEx extends TemplatesLexer { private final PsiBuilder myBuilder; private Span next; public LtplLexerEx(PsiBuilder builder) throws IOException { super(null, null); myBuilder = builder; } @Override public Span next() throws IOException { return nextInternal(); } @Override public void reset(CharSequence input) throws IOException { } private Span nextInternal() { if (next != null && !myBuilder.eof()) { myBuilder.advanceLexer(); while (!myBuilder.eof() && myBuilder.getTokenType() == TokenType.BAD_CHARACTER) { myBuilder.advanceLexer(); } } next = new Span(); if (myBuilder.eof()) { next.symbol = Tokens.eoi; } else { LtplElementType tokenType = (LtplElementType) myBuilder.getTokenType(); next.symbol = tokenType.getSymbol(); } return next; } } }