Chris@2: /* Chris@2: Copyright (C) 2001, 2006 by Simon Dixon Chris@2: Chris@2: This program is free software; you can redistribute it and/or modify Chris@2: it under the terms of the GNU General Public License as published by Chris@2: the Free Software Foundation; either version 2 of the License, or Chris@2: (at your option) any later version. Chris@2: Chris@2: This program is distributed in the hope that it will be useful, Chris@2: but WITHOUT ANY WARRANTY; without even the implied warranty of Chris@2: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Chris@2: GNU General Public License for more details. Chris@2: Chris@2: You should have received a copy of the GNU General Public License along Chris@2: with this program (the file gpl.txt); if not, download it from Chris@2: http://www.gnu.org/licenses/gpl.txt or write to the Chris@2: Free Software Foundation, Inc., Chris@2: 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Chris@2: */ Chris@2: Chris@2: package at.ofai.music.util; Chris@2: Chris@2: /** A simple parser for Prolog-type notation, but only handling the subset of Chris@2: * Prolog used in "match" files. Chris@2: */ Chris@2: public class Matcher { Chris@2: Chris@2: /** The unparsed part of the current line of text */ Chris@2: protected String s; Chris@2: Chris@2: /** The constructor is initialised with the input line of text for parsing*/ Chris@2: public Matcher(String data) { s = data; } Chris@2: Chris@2: /** Reinitialise the parser with a new line of input */ Chris@2: public void set(String data) { s = data; } Chris@2: Chris@2: /** Return the unparsed part of the input line */ Chris@2: public String get() { return s; } Chris@2: Chris@2: /** Returns true if there is input data remaining */ Chris@2: public boolean hasData() { Chris@2: return (s != null) && (s.length() > 0); Chris@2: } // hasData() Chris@2: Chris@2: /** Matches a String with the unparsed input data. Chris@2: * If the complete String occurs at the beginning of the unparsed data, Chris@2: * the unparsed data is advanced to the end of the String; otherwise Chris@2: * the data is left unchanged. Chris@2: * Chris@2: * @param m the String to match Chris@2: * @return true if m matches the beginning of the unparsed data Chris@2: */ Chris@2: public boolean matchString(String m) { Chris@2: if (s.startsWith(m)) { Chris@2: s = s.substring(m.length()); Chris@2: return true; Chris@2: } Chris@2: return false; Chris@2: } // matchString() Chris@2: Chris@2: /** Skips input up to and including the next instance of a given character. Chris@2: * It is an error for the character not to occur in the data. Chris@2: * @param c the character to skip to Chris@2: */ Chris@2: public void skip(char c) { Chris@2: int index = s.indexOf(c); Chris@2: if (index >= 0) Chris@2: s = s.substring(index + 1); Chris@2: else Chris@2: throw new RuntimeException("Parse error in skip(), expecting " + c); Chris@2: } // skip() Chris@2: Chris@2: /** Removes whitespace from the beginning and end of the line. Chris@2: */ Chris@2: public void trimSpace() { Chris@2: s = s.trim(); Chris@2: } // trimSpace() Chris@2: Chris@2: /** Returns and consumes the next character of unparsed data. */ Chris@2: public char getChar() { Chris@2: char c = s.charAt(0); Chris@2: s = s.substring(1); Chris@2: return c; Chris@2: } // getChar() Chris@2: Chris@2: /** Returns and consumes an int value from the head of the unparsed data. */ Chris@2: public int getInt() { Chris@2: int sz = 0; Chris@2: trimSpace(); Chris@2: while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) || Chris@2: ((sz==0) && (s.charAt(sz) == '-')))) Chris@2: sz++; Chris@2: int val = Integer.parseInt(s.substring(0, sz)); Chris@2: s = s.substring(sz); Chris@2: return val; Chris@2: } // getInt() Chris@2: Chris@2: /** Returns and consumes a double value, with two limitations: Chris@2: * 1) exponents are ignored e.g. 5.4e-3 is read as 5.4; Chris@2: * 2) a value terminated by a 2nd "." causes an Exception to be thrown Chris@2: */ Chris@2: public double getDouble() { Chris@2: int sz = 0; Chris@2: trimSpace(); Chris@2: while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) || Chris@2: ((sz==0)&&(s.charAt(sz) == '-')) || (s.charAt(sz) == '.'))) Chris@2: sz++; Chris@2: double val = Double.parseDouble(s.substring(0, sz)); Chris@2: s = s.substring(sz); Chris@2: return val; Chris@2: } // getDouble() Chris@2: Chris@2: /** Returns and consumes a string terminated by the first comma, Chris@2: * parenthesis, bracket or brace. Equivalent to getString(false). Chris@2: */ Chris@2: public String getString() { Chris@2: return getString(false); Chris@2: } // getString() Chris@2: Chris@2: /** Chris@2: * Returns and consumes a string terminated by various punctuation symbols. Chris@2: * Terminators include: '(', '[', '{', ',', '}', ']' and ')'. Chris@2: * An Exception is thrown if no terminator is found. Chris@2: * Chris@2: * @param extraPunctuation Specifies whether '-' and '.' are terminators Chris@2: */ Chris@2: public String getString(boolean extraPunctuation) { Chris@2: char[] stoppers = {'(','[','{',',','}',']',')','-','.'}; Chris@2: int index1 = s.indexOf(stoppers[0]); Chris@2: for (int i = 1; i < stoppers.length - (extraPunctuation? 0:2); i++) { Chris@2: int index2 = s.indexOf(stoppers[i]); Chris@2: if (index1 >= 0) { Chris@2: if ((index2 >= 0) && (index1 > index2)) Chris@2: index1 = index2; Chris@2: } else Chris@2: index1 = index2; Chris@2: } Chris@2: if (index1 < 0) Chris@2: throw new RuntimeException("getString(): no terminator: " + s); Chris@2: String val = s.substring(0, index1); Chris@2: s = s.substring(index1); Chris@2: return val; Chris@2: } // getString() Chris@2: Chris@2: /** Returns and consumes a comma-separated list of terms, surrounded by a Chris@2: * matching set of parentheses, brackets or braces. Chris@2: * The list may have any number of levels of recursion. Chris@2: * @return The return value is a linked list of the terms Chris@2: * (which themselves may be lists or String values) Chris@2: */ Chris@2: public ListTerm getList() { Chris@2: if ("([{".indexOf(s.charAt(0)) >= 0) Chris@2: return new ListTerm(getChar()); Chris@2: return null; Chris@2: } // getList() Chris@2: Chris@2: /** Returns and consumes a Prolog-style predicate, consisting of a functor Chris@2: * followed by an optional list of arguments in parentheses. Chris@2: */ Chris@2: public Predicate getPredicate() { Chris@2: return new Predicate(); Chris@2: } // getPredicate() Chris@2: Chris@2: class Predicate { Chris@2: Chris@2: String head; Chris@2: ListTerm args; Chris@2: Chris@2: protected Predicate() { Chris@2: head = getString(true); Chris@2: args = getList(); Chris@2: } Chris@2: Chris@2: public Object arg(int index) { Chris@2: ListTerm t = args; Chris@2: for (int i = 0; i < index; i++) Chris@2: t = t.next; Chris@2: return t.term; Chris@2: } // arg Chris@2: Chris@2: public String toString() { Chris@2: return (args == null)? head: head + args; Chris@2: } Chris@2: Chris@2: } // inner class Predicate Chris@2: Chris@2: class ListTerm { Chris@2: Chris@2: Object term; Chris@2: ListTerm next; Chris@2: char opener, closer; Chris@2: Chris@2: protected ListTerm(char c) { Chris@2: opener = c; Chris@2: term = null; Chris@2: next = null; Chris@2: if (hasData()) { Chris@2: switch(s.charAt(0)) { Chris@2: case '(': Chris@2: case '[': Chris@2: case '{': Chris@2: term = new ListTerm(getChar()); Chris@2: break; Chris@2: default: Chris@2: term = getString(); Chris@2: break; Chris@2: } Chris@2: } Chris@2: if (hasData()) { Chris@2: closer = getChar(); Chris@2: switch(closer) { Chris@2: case ')': Chris@2: if (opener == '(') Chris@2: return; Chris@2: break; Chris@2: case ']': Chris@2: if (opener == '[') Chris@2: return; Chris@2: break; Chris@2: case '}': Chris@2: if (opener == '{') Chris@2: return; Chris@2: break; Chris@2: case ',': Chris@2: next = new ListTerm(opener); Chris@2: return; Chris@2: } Chris@2: } Chris@2: throw new RuntimeException("Parse error in ListTerm(): " + s); Chris@2: } // constructor Chris@2: Chris@2: public String toString() { Chris@2: String s = "" + opener; Chris@2: for (ListTerm ptr = this; ptr != null; ptr = ptr.next) Chris@2: s += ptr.term.toString() + ptr.closer; Chris@2: return s; Chris@2: } // toString() Chris@2: Chris@2: } // inner class ListTerm Chris@2: Chris@2: } // class Matcher Chris@2: