/**
* 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.syntax.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.syntax.lexer.TMElementType;
import org.textmapper.idea.lang.syntax.lexer.TMTemplatesElementType;
import org.textmapper.tool.parser.TMLexer;
import org.textmapper.tool.parser.TMLexer.ErrorReporter;
import org.textmapper.tool.parser.TMLexer.Span;
import org.textmapper.tool.parser.TMLexer.Tokens;
import org.textmapper.tool.parser.TMParser;
import org.textmapper.tool.parser.TMParser.ParseException;
import org.textmapper.tool.parser.TMParser.Nonterminals;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
public class TMPsiParser implements PsiParser {
private static final Map<Integer, IElementType> types = initTypes();
private static Map<Integer, IElementType> initTypes() {
Map<Integer, IElementType> result = new HashMap<>();
for (IElementType t : TextmapperElementTypes.allElements) {
int symbol = ((TMElementType) t).getSymbol();
if (symbol >= 0) {
result.put(symbol, t);
}
}
result.put(Nonterminals.syntax_problem, TokenType.ERROR_ELEMENT);
return result;
}
private static IElementType reduceType(int token) {
return types.get(token);
}
@NotNull
public ASTNode parse(IElementType root, PsiBuilder builder) {
final PsiBuilder.Marker file = builder.mark();
parseGrammar(builder);
file.done(root);
return builder.getTreeBuilt();
}
private void parseGrammar(PsiBuilder builder) {
Marker grammar = builder.mark();
TMParserEx parser = new TMParserEx(builder);
try {
parser.parseInput(new TMLexerEx(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();
}
if (cannotRecover) {
parser.mark().error(" syntax error");
}
while (!builder.eof()) {
builder.advanceLexer();
}
grammar.done(TextmapperElementTypes.GRAMMAR);
}
private static class TMParserEx extends TMParser {
private final PsiBuilder myBuilder;
private final Stack<Marker> markers = new Stack<>();
public TMParserEx(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) {
Marker top = markers.pop();
assert m == top;
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) {
Span leftmost = null;
for (int i = 0; i < ruleLength; i++) {
if (tmStack[tmHead - i].value == null) continue;
if (leftmost != null) {
drop(leftmost);
}
leftmost = tmStack[tmHead - i];
}
PsiBuilder.Marker m = null;
if (leftmost != null) {
m = (PsiBuilder.Marker) leftmost.value;
leftmost.value = null;
}
left.value = m;
if (m != null) {
IElementType elementType = reduceType(left.symbol);
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) {
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 TMLexerEx extends TMLexer {
private final PsiBuilder myBuilder;
private Span next;
public TMLexerEx(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 if (myBuilder.getTokenType() instanceof TMTemplatesElementType) {
TMTemplatesElementType tokenType = (TMTemplatesElementType) myBuilder.getTokenType();
next.symbol = tokenType.getSymbol();
} else {
TMElementType tokenType = (TMElementType) myBuilder.getTokenType();
next.symbol = tokenType.getSymbol();
if (next.symbol == Nonterminals.command) {
// temp hack
return nextInternal();
}
}
return next;
}
}
}