Chris@2
|
1 /*
|
Chris@2
|
2 Copyright (C) 2001, 2006 by Simon Dixon
|
Chris@2
|
3
|
Chris@2
|
4 This program is free software; you can redistribute it and/or modify
|
Chris@2
|
5 it under the terms of the GNU General Public License as published by
|
Chris@2
|
6 the Free Software Foundation; either version 2 of the License, or
|
Chris@2
|
7 (at your option) any later version.
|
Chris@2
|
8
|
Chris@2
|
9 This program is distributed in the hope that it will be useful,
|
Chris@2
|
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Chris@2
|
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Chris@2
|
12 GNU General Public License for more details.
|
Chris@2
|
13
|
Chris@2
|
14 You should have received a copy of the GNU General Public License along
|
Chris@2
|
15 with this program (the file gpl.txt); if not, download it from
|
Chris@2
|
16 http://www.gnu.org/licenses/gpl.txt or write to the
|
Chris@2
|
17 Free Software Foundation, Inc.,
|
Chris@2
|
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
Chris@2
|
19 */
|
Chris@2
|
20
|
Chris@2
|
21 package at.ofai.music.util;
|
Chris@2
|
22
|
Chris@2
|
23 /** A simple parser for Prolog-type notation, but only handling the subset of
|
Chris@2
|
24 * Prolog used in "match" files.
|
Chris@2
|
25 */
|
Chris@2
|
26 public class Matcher {
|
Chris@2
|
27
|
Chris@2
|
28 /** The unparsed part of the current line of text */
|
Chris@2
|
29 protected String s;
|
Chris@2
|
30
|
Chris@2
|
31 /** The constructor is initialised with the input line of text for parsing*/
|
Chris@2
|
32 public Matcher(String data) { s = data; }
|
Chris@2
|
33
|
Chris@2
|
34 /** Reinitialise the parser with a new line of input */
|
Chris@2
|
35 public void set(String data) { s = data; }
|
Chris@2
|
36
|
Chris@2
|
37 /** Return the unparsed part of the input line */
|
Chris@2
|
38 public String get() { return s; }
|
Chris@2
|
39
|
Chris@2
|
40 /** Returns true if there is input data remaining */
|
Chris@2
|
41 public boolean hasData() {
|
Chris@2
|
42 return (s != null) && (s.length() > 0);
|
Chris@2
|
43 } // hasData()
|
Chris@2
|
44
|
Chris@2
|
45 /** Matches a String with the unparsed input data.
|
Chris@2
|
46 * If the complete String occurs at the beginning of the unparsed data,
|
Chris@2
|
47 * the unparsed data is advanced to the end of the String; otherwise
|
Chris@2
|
48 * the data is left unchanged.
|
Chris@2
|
49 *
|
Chris@2
|
50 * @param m the String to match
|
Chris@2
|
51 * @return true if m matches the beginning of the unparsed data
|
Chris@2
|
52 */
|
Chris@2
|
53 public boolean matchString(String m) {
|
Chris@2
|
54 if (s.startsWith(m)) {
|
Chris@2
|
55 s = s.substring(m.length());
|
Chris@2
|
56 return true;
|
Chris@2
|
57 }
|
Chris@2
|
58 return false;
|
Chris@2
|
59 } // matchString()
|
Chris@2
|
60
|
Chris@2
|
61 /** Skips input up to and including the next instance of a given character.
|
Chris@2
|
62 * It is an error for the character not to occur in the data.
|
Chris@2
|
63 * @param c the character to skip to
|
Chris@2
|
64 */
|
Chris@2
|
65 public void skip(char c) {
|
Chris@2
|
66 int index = s.indexOf(c);
|
Chris@2
|
67 if (index >= 0)
|
Chris@2
|
68 s = s.substring(index + 1);
|
Chris@2
|
69 else
|
Chris@2
|
70 throw new RuntimeException("Parse error in skip(), expecting " + c);
|
Chris@2
|
71 } // skip()
|
Chris@2
|
72
|
Chris@2
|
73 /** Removes whitespace from the beginning and end of the line.
|
Chris@2
|
74 */
|
Chris@2
|
75 public void trimSpace() {
|
Chris@2
|
76 s = s.trim();
|
Chris@2
|
77 } // trimSpace()
|
Chris@2
|
78
|
Chris@2
|
79 /** Returns and consumes the next character of unparsed data. */
|
Chris@2
|
80 public char getChar() {
|
Chris@2
|
81 char c = s.charAt(0);
|
Chris@2
|
82 s = s.substring(1);
|
Chris@2
|
83 return c;
|
Chris@2
|
84 } // getChar()
|
Chris@2
|
85
|
Chris@2
|
86 /** Returns and consumes an int value from the head of the unparsed data. */
|
Chris@2
|
87 public int getInt() {
|
Chris@2
|
88 int sz = 0;
|
Chris@2
|
89 trimSpace();
|
Chris@2
|
90 while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) ||
|
Chris@2
|
91 ((sz==0) && (s.charAt(sz) == '-'))))
|
Chris@2
|
92 sz++;
|
Chris@2
|
93 int val = Integer.parseInt(s.substring(0, sz));
|
Chris@2
|
94 s = s.substring(sz);
|
Chris@2
|
95 return val;
|
Chris@2
|
96 } // getInt()
|
Chris@2
|
97
|
Chris@2
|
98 /** Returns and consumes a double value, with two limitations:
|
Chris@2
|
99 * 1) exponents are ignored e.g. 5.4e-3 is read as 5.4;
|
Chris@2
|
100 * 2) a value terminated by a 2nd "." causes an Exception to be thrown
|
Chris@2
|
101 */
|
Chris@2
|
102 public double getDouble() {
|
Chris@2
|
103 int sz = 0;
|
Chris@2
|
104 trimSpace();
|
Chris@2
|
105 while ((sz < s.length()) && (Character.isDigit(s.charAt(sz)) ||
|
Chris@2
|
106 ((sz==0)&&(s.charAt(sz) == '-')) || (s.charAt(sz) == '.')))
|
Chris@2
|
107 sz++;
|
Chris@2
|
108 double val = Double.parseDouble(s.substring(0, sz));
|
Chris@2
|
109 s = s.substring(sz);
|
Chris@2
|
110 return val;
|
Chris@2
|
111 } // getDouble()
|
Chris@2
|
112
|
Chris@2
|
113 /** Returns and consumes a string terminated by the first comma,
|
Chris@2
|
114 * parenthesis, bracket or brace. Equivalent to getString(false).
|
Chris@2
|
115 */
|
Chris@2
|
116 public String getString() {
|
Chris@2
|
117 return getString(false);
|
Chris@2
|
118 } // getString()
|
Chris@2
|
119
|
Chris@2
|
120 /**
|
Chris@2
|
121 * Returns and consumes a string terminated by various punctuation symbols.
|
Chris@2
|
122 * Terminators include: '(', '[', '{', ',', '}', ']' and ')'.
|
Chris@2
|
123 * An Exception is thrown if no terminator is found.
|
Chris@2
|
124 *
|
Chris@2
|
125 * @param extraPunctuation Specifies whether '-' and '.' are terminators
|
Chris@2
|
126 */
|
Chris@2
|
127 public String getString(boolean extraPunctuation) {
|
Chris@2
|
128 char[] stoppers = {'(','[','{',',','}',']',')','-','.'};
|
Chris@2
|
129 int index1 = s.indexOf(stoppers[0]);
|
Chris@2
|
130 for (int i = 1; i < stoppers.length - (extraPunctuation? 0:2); i++) {
|
Chris@2
|
131 int index2 = s.indexOf(stoppers[i]);
|
Chris@2
|
132 if (index1 >= 0) {
|
Chris@2
|
133 if ((index2 >= 0) && (index1 > index2))
|
Chris@2
|
134 index1 = index2;
|
Chris@2
|
135 } else
|
Chris@2
|
136 index1 = index2;
|
Chris@2
|
137 }
|
Chris@2
|
138 if (index1 < 0)
|
Chris@2
|
139 throw new RuntimeException("getString(): no terminator: " + s);
|
Chris@2
|
140 String val = s.substring(0, index1);
|
Chris@2
|
141 s = s.substring(index1);
|
Chris@2
|
142 return val;
|
Chris@2
|
143 } // getString()
|
Chris@2
|
144
|
Chris@2
|
145 /** Returns and consumes a comma-separated list of terms, surrounded by a
|
Chris@2
|
146 * matching set of parentheses, brackets or braces.
|
Chris@2
|
147 * The list may have any number of levels of recursion.
|
Chris@2
|
148 * @return The return value is a linked list of the terms
|
Chris@2
|
149 * (which themselves may be lists or String values)
|
Chris@2
|
150 */
|
Chris@2
|
151 public ListTerm getList() {
|
Chris@2
|
152 if ("([{".indexOf(s.charAt(0)) >= 0)
|
Chris@2
|
153 return new ListTerm(getChar());
|
Chris@2
|
154 return null;
|
Chris@2
|
155 } // getList()
|
Chris@2
|
156
|
Chris@2
|
157 /** Returns and consumes a Prolog-style predicate, consisting of a functor
|
Chris@2
|
158 * followed by an optional list of arguments in parentheses.
|
Chris@2
|
159 */
|
Chris@2
|
160 public Predicate getPredicate() {
|
Chris@2
|
161 return new Predicate();
|
Chris@2
|
162 } // getPredicate()
|
Chris@2
|
163
|
Chris@2
|
164 class Predicate {
|
Chris@2
|
165
|
Chris@2
|
166 String head;
|
Chris@2
|
167 ListTerm args;
|
Chris@2
|
168
|
Chris@2
|
169 protected Predicate() {
|
Chris@2
|
170 head = getString(true);
|
Chris@2
|
171 args = getList();
|
Chris@2
|
172 }
|
Chris@2
|
173
|
Chris@2
|
174 public Object arg(int index) {
|
Chris@2
|
175 ListTerm t = args;
|
Chris@2
|
176 for (int i = 0; i < index; i++)
|
Chris@2
|
177 t = t.next;
|
Chris@2
|
178 return t.term;
|
Chris@2
|
179 } // arg
|
Chris@2
|
180
|
Chris@2
|
181 public String toString() {
|
Chris@2
|
182 return (args == null)? head: head + args;
|
Chris@2
|
183 }
|
Chris@2
|
184
|
Chris@2
|
185 } // inner class Predicate
|
Chris@2
|
186
|
Chris@2
|
187 class ListTerm {
|
Chris@2
|
188
|
Chris@2
|
189 Object term;
|
Chris@2
|
190 ListTerm next;
|
Chris@2
|
191 char opener, closer;
|
Chris@2
|
192
|
Chris@2
|
193 protected ListTerm(char c) {
|
Chris@2
|
194 opener = c;
|
Chris@2
|
195 term = null;
|
Chris@2
|
196 next = null;
|
Chris@2
|
197 if (hasData()) {
|
Chris@2
|
198 switch(s.charAt(0)) {
|
Chris@2
|
199 case '(':
|
Chris@2
|
200 case '[':
|
Chris@2
|
201 case '{':
|
Chris@2
|
202 term = new ListTerm(getChar());
|
Chris@2
|
203 break;
|
Chris@2
|
204 default:
|
Chris@2
|
205 term = getString();
|
Chris@2
|
206 break;
|
Chris@2
|
207 }
|
Chris@2
|
208 }
|
Chris@2
|
209 if (hasData()) {
|
Chris@2
|
210 closer = getChar();
|
Chris@2
|
211 switch(closer) {
|
Chris@2
|
212 case ')':
|
Chris@2
|
213 if (opener == '(')
|
Chris@2
|
214 return;
|
Chris@2
|
215 break;
|
Chris@2
|
216 case ']':
|
Chris@2
|
217 if (opener == '[')
|
Chris@2
|
218 return;
|
Chris@2
|
219 break;
|
Chris@2
|
220 case '}':
|
Chris@2
|
221 if (opener == '{')
|
Chris@2
|
222 return;
|
Chris@2
|
223 break;
|
Chris@2
|
224 case ',':
|
Chris@2
|
225 next = new ListTerm(opener);
|
Chris@2
|
226 return;
|
Chris@2
|
227 }
|
Chris@2
|
228 }
|
Chris@2
|
229 throw new RuntimeException("Parse error in ListTerm(): " + s);
|
Chris@2
|
230 } // constructor
|
Chris@2
|
231
|
Chris@2
|
232 public String toString() {
|
Chris@2
|
233 String s = "" + opener;
|
Chris@2
|
234 for (ListTerm ptr = this; ptr != null; ptr = ptr.next)
|
Chris@2
|
235 s += ptr.term.toString() + ptr.closer;
|
Chris@2
|
236 return s;
|
Chris@2
|
237 } // toString()
|
Chris@2
|
238
|
Chris@2
|
239 } // inner class ListTerm
|
Chris@2
|
240
|
Chris@2
|
241 } // class Matcher
|
Chris@2
|
242
|