package org.textmapper.lapg.ui.settings; import java.io.CharArrayReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.textmapper.lapg.ui.settings.SettingsLexer.ErrorReporter; import org.textmapper.lapg.ui.settings.SettingsParser.ParseException; public class SettingsTree<T> { private final TextSource source; private final T root; private final List<SettingsProblem> errors; public SettingsTree(TextSource source, T root, List<SettingsProblem> errors) { this.source = source; this.root = root; this.errors = errors; } public TextSource getSource() { return source; } public T getRoot() { return root; } public List<SettingsProblem> getErrors() { return errors; } public boolean hasErrors() { return errors.size() > 0; } public static SettingsTree<AstInput> parse(TextSource source) { final List<SettingsProblem> list = new ArrayList<SettingsProblem>(); ErrorReporter reporter = new ErrorReporter() { public void error(int start, int end, int line, String s) { list.add(new SettingsProblem(KIND_ERROR, start, end, s, null)); } }; try { SettingsLexer lexer = new SettingsLexer(source.getStream(), reporter); lexer.setLine(source.getInitialLine()); SettingsParser parser = new SettingsParser(reporter); AstInput result = parser.parse(lexer); return new SettingsTree<AstInput>(source, result, list); } catch (ParseException ex) { /* not parsed */ } catch (IOException ex) { list.add(new SettingsProblem(KIND_FATAL, 0, 0, "I/O problem: " + ex.getMessage(), ex)); } return new SettingsTree<AstInput>(source, null, list); } public static final int KIND_FATAL = 0; public static final int KIND_ERROR = 1; public static final int KIND_WARN = 2; public static final String PARSER_SOURCE = "parser"; public static class SettingsProblem extends Exception { private static final long serialVersionUID = 1L; private final int kind; private final int offset; private final int endoffset; public SettingsProblem(int kind, int offset, int endoffset, String message, Throwable cause) { super(message, cause); this.kind = kind; this.offset = offset; this.endoffset = endoffset; } public int getKind() { return kind; } public int getOffset() { return offset; } public int getEndOffset() { return endoffset; } public String getSource() { return PARSER_SOURCE; } } public static class TextSource { private final String file; private final int initialLine; private final char[] contents; private int[] lineoffset; public TextSource(String file, char[] contents, int initialLine) { this.file = file; this.initialLine = initialLine; this.contents = contents; } public String getFile() { return file; } public int getInitialLine() { return initialLine; } public Reader getStream() { return new CharArrayReader(contents); } public String getLocation(int offset) { return file + "," + lineForOffset(offset); } public String getText(int start, int end) { if (start < 0 || start > contents.length || end > contents.length || start > end) { return ""; } return new String(contents, start, end - start); } public int lineForOffset(int offset) { if (lineoffset == null) { lineoffset = getLineOffsets(contents); } int line = Arrays.binarySearch(lineoffset, offset); return initialLine + (line >= 0 ? line : -line - 2); } public char[] getContents() { return contents; } } private static int[] getLineOffsets(char[] contents) { int size = 1; for (int i = 0; i < contents.length; i++) { if (contents[i] == '\n') { size++; } else if (contents[i] == '\r') { if (i + 1 < contents.length && contents[i + 1] == '\n') { i++; } size++; } } int[] result = new int[size]; result[0] = 0; int e = 1; for (int i = 0; i < contents.length; i++) { if (contents[i] == '\n') { result[e++] = i + 1; } else if (contents[i] == '\r') { if (i + 1 < contents.length && contents[i + 1] == '\n') { i++; } result[e++] = i + 1; } } if (e != size) { throw new IllegalStateException(); } return result; } }