/******************************************************************************* * Copyright (c) 2008 xored software, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * xored software, Inc. - initial API and Implementation (Andrei Sobolev) *******************************************************************************/ package org.eclipse.dltk.tcl.internal.parser.raw; import java.text.ParseException; import java.util.List; import java.util.regex.Pattern; import org.eclipse.dltk.tcl.parser.ITclErrorConstants; import org.eclipse.dltk.tcl.parser.ITclErrorReporter; public class SimpleTclParser { private ITclErrorReporter reporter = null; public void setProblemReporter(ITclErrorReporter reporter) { this.reporter = reporter; } public SimpleTclParser() { this(0); } public SimpleTclParser(int sourceOffset) { this.sourceOffset = sourceOffset; } private boolean skipComments = true; private final int sourceOffset; /** * Report an error. Should return true parser should continue work, or * false, if it should stop. * * @param error * @return */ public boolean handleError(ErrorDescription error) { if (this.reporter != null) { this.reporter.report(0, error.getMessage(), null, sourceOffset + error.getPosition(), sourceOffset + error.getEnd() + 1, ITclErrorConstants.ERROR); } return true; } private static final Pattern MAGIC_SUBSTITUTE = Pattern .compile("\\\\" + "\\r*\\n\\s*"); //$NON-NLS-1$ //$NON-NLS-2$ public static String magicSubstitute(String src) { int i = 0; for (;;) { i = src.indexOf('\\', i); if (i >= 0) { ++i; if (i < src.length()) { char c = src.charAt(i); if (c == '\r' || c == '\n') { return MAGIC_SUBSTITUTE.matcher(src).replaceAll(" "); //$NON-NLS-1$ } ++i; continue; } } break; } return src; } public ISubstitution getCVB(ICodeScanner input) throws TclParseException { if (CommandSubstitution.iAm(input)) return new CommandSubstitution(); if (VariableSubstitution.iAm(input)) return new VariableSubstitution(); if (NormalBackslashSubstitution.iAm(input)) return new NormalBackslashSubstitution(); if (MagicBackslashSubstitution.iAm(input)) return new MagicBackslashSubstitution(); return null; } private TclCommand nextCommand(ICodeScanner input, boolean nest, final TclWordBuffer wordBuffer) throws TclParseException { TclCommand cmd = new TclCommand(); cmd.setStart(input.getPosition()); wordBuffer.reset(); while (true) { int ch = input.read(); boolean eof = (ch == ICodeScanner.EOF); if (eof && cmd.isEmpty() && wordBuffer.isEmpty()) { return STOP_EOF; } if (TclTextUtils.isTrueWhitespace(ch) || eof) { final TclWord word = wordBuffer.buildWord(); if (word != null) { validateWord(word); cmd.addWord(word); } if (eof) break; else continue; } else { input.unread(); if (wordBuffer.getState() != TclWordBuffer.State.CONTENT) { wordBuffer.setStart(input.getPosition()); } } if (wordBuffer.isEmpty() && BracesSubstitution.iAm(input)) { BracesSubstitution s = new BracesSubstitution(); s.readMe(input, this); wordBuffer.add(s); continue; } if (wordBuffer.isEmpty() && QuotesSubstitution.iAm(input)) { QuotesSubstitution s = new QuotesSubstitution(); s.readMe(input, this); wordBuffer.add(s); continue; } if (cmd.isEmpty() && wordBuffer.isEmpty()) { if (ch == '#' && skipComments) { input.read(); TclTextUtils.runToLineEnd(input); return null; } if (ch == ']' && nest) { input.read(); return STOP; } } else { if (ch == ']' && nest) { final TclWord word = wordBuffer.buildWord(); if (word != null) { validateWord(word); cmd.addWord(word); } break; } } ISubstitution s = this.getCVB(input); if (s != null) { s.readMe(input, this); if (s instanceof MagicBackslashSubstitution) { TclWord word = wordBuffer.buildWord(); if (word != null) { // XXX setEnd() is called in addWord() too word .setEnd(((MagicBackslashSubstitution) s) .getStart() - 1); validateWord(word); cmd.addWord(word); } } else { wordBuffer.add(s); } continue; } boolean cmdEnd = false; switch (ch) { case '\r': input.read(); int c1 = input.read(); if (c1 == '\n') { cmdEnd = true; } else if (c1 == ICodeScanner.EOF) { cmdEnd = true; } else { input.unread(); wordBuffer.add((char) ch); } break; case '\n': input.read(); cmdEnd = true; break; case ';': input.read(); cmdEnd = true; break; default: input.read(); // if (!TclTextUtils.isWhitespace(ch)) wordBuffer.add((char) ch); } if (cmdEnd) { final TclWord word = wordBuffer.buildWord(); if (word != null) { validateWord(word); cmd.addWord(word); } break; } } int wordsSize = cmd.getWords().size(); if (wordsSize > 0) { TclWord w = cmd.getWords().get(wordsSize - 1); cmd.setEnd(w.getEnd()); } else cmd.setEnd(cmd.getStart()); return cmd; } private void validateWord(final TclWord word) { final List<Object> contents = word.getContents(); if (contents.size() > 1) { final Object first = word.getContents().get(0); if (first instanceof IWordSubstitution) { handleError(new ErrorDescription( first instanceof QuotesSubstitution ? Messages.SimpleTclParser_ExtraCharactersAfterCloseQuote : Messages.SimpleTclParser_ExtraCharactersAfterCloseBrace, ((TclElement) first).getEnd() + 1, word.getStart() + word.length(), ErrorDescription.ERROR)); } } } public interface IEOFHandler { void handle(); } private static final TclCommand STOP = new TclCommand(); private static final TclCommand STOP_EOF = new TclCommand(); /** * Parses input. If nest is <code>true</code> treats ] command as end. * * @param input * @param nest * @throws ParseException */ public TclScript parse(ICodeScanner input, boolean nest, IEOFHandler handler) throws TclParseException { final TclWordBuffer wordBuffer = new TclWordBuffer(); TclScript script = new TclScript(); script.setStart(input.getPosition()); while (true) { TclCommand cmd = nextCommand(input, nest, wordBuffer); if (cmd == STOP) { break; } else if (cmd == STOP_EOF) { if (handler != null) { handler.handle(); } break; } if (cmd == null || cmd.getWords().size() == 0) continue; script.addCommand(cmd); } script.setEnd(input.getPosition() - 1); return script; } public TclScript parse(String content) throws TclParseException { ICodeScanner scanner = new CodeScanner(content); TclScript script = parse(scanner, false, null); return script; } public static TclScript staticParse(String content) throws TclParseException { SimpleTclParser parser = new SimpleTclParser(); return parser.parse(content); } /** * @since 2.0 */ public boolean isSkipComments() { return skipComments; } /** * @since 2.0 */ public void setSkipComments(boolean skipComments) { this.skipComments = skipComments; } }