You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Parser.java 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.calc;
  23. import java.text.ParseException;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Collections;
  27. import java.util.Comparator;
  28. import java.util.List;
  29. /**
  30. * The parser takes the output from a {@link Lexer} and applies precdence rules
  31. * to build the tokens into a tree.
  32. *
  33. * @author chris
  34. */
  35. public class Parser {
  36. /** The lexer whose output will be parsed. */
  37. protected final Lexer lexer;
  38. /** A list of token types sorted by their precendece. */
  39. protected static final List<TokenType> TOKENS_BY_PRECEDENCE;
  40. static {
  41. TOKENS_BY_PRECEDENCE = new ArrayList<TokenType>(Arrays.asList(TokenType.values()));
  42. Collections.sort(TOKENS_BY_PRECEDENCE, new TokenTypePrecedenceComparator());
  43. }
  44. /**
  45. * Creates a new parser for the specified lexer.
  46. *
  47. * @param lexer The lexer whose output should be parsed
  48. */
  49. public Parser(final Lexer lexer) {
  50. this.lexer = lexer;
  51. }
  52. /**
  53. * Parses the output of this parser's lexer, and returns a {@link TreeToken}
  54. * representing the parsed formula.
  55. *
  56. * @return A token tree corresponding to the lexer's token output
  57. * @throws ParseException If the lexer encounters a parse error, or if an
  58. * error occurs while parsing the lexer's output (such as a non-sensical
  59. * formula such as one involving a mis-matched bracket).
  60. */
  61. public TreeToken parse() throws ParseException {
  62. final List<TreeToken> tokens = new ArrayList<TreeToken>();
  63. for (Token token : lexer.tokenise()) {
  64. tokens.add(new TreeToken(token));
  65. }
  66. return parse(tokens);
  67. }
  68. /**
  69. * Parses the specified tokens into a tree.
  70. *
  71. * @param tokens The tokens to be parsed
  72. * @return A single tree containing all of the specified tokens
  73. * @throws ParseException If the tokens contain mismatched brackets
  74. */
  75. protected TreeToken parse(final List<TreeToken> tokens) throws ParseException {
  76. while (tokens.size() > 1) {
  77. for (TokenType type : TOKENS_BY_PRECEDENCE) {
  78. final int offset = findTokenType(tokens, type);
  79. if (offset > -1) {
  80. switch (type.getArity()) {
  81. case HIDDEN:
  82. parseHiddenOperator(tokens, offset);
  83. break;
  84. case BINARY:
  85. parseBinaryOperator(tokens, offset);
  86. break;
  87. case UNARY:
  88. parseUnaryOperator(tokens, offset);
  89. break;
  90. case NULLARY:
  91. parseNullaryOperator(tokens, offset);
  92. break;
  93. }
  94. break;
  95. }
  96. }
  97. }
  98. return tokens.get(0);
  99. }
  100. /**
  101. * Parses an operator that takes no operands.
  102. *
  103. * @param tokens The supply of tokens from which the operator will be parsed
  104. * @param offset The offset at which the operator occurs
  105. * @throws ParseException If the operator is a bracket and that bracket is
  106. * mismatched
  107. */
  108. protected void parseNullaryOperator(final List<TreeToken> tokens, final int offset)
  109. throws ParseException {
  110. if (tokens.get(offset).getToken().getType() == TokenType.BRACKET_CLOSE
  111. || tokens.get(offset).getToken().getType() == TokenType.BRACKET_OPEN) {
  112. parseBracket(tokens, offset);
  113. } else {
  114. parseNumber(tokens, offset);
  115. }
  116. }
  117. /**
  118. * Parses a bracket operator.
  119. *
  120. * @param tokens The supply of tokens from which the operator will be parsed
  121. * @param offset The offset at which the operator occurs
  122. * @throws ParseException If the operator is a bracket and that bracket is
  123. * mismatched
  124. */
  125. protected void parseBracket(final List<TreeToken> tokens, final int offset)
  126. throws ParseException {
  127. final List<TreeToken> stack = new ArrayList<TreeToken>();
  128. for (int i = offset - 1; i > 0; i--) {
  129. if (tokens.get(i).getToken().getType() == TokenType.BRACKET_OPEN
  130. && !tokens.get(i).isProcessed()) {
  131. tokens.add(i, parse(stack));
  132. tokens.get(i).setProcessed();
  133. tokens.remove(i + 1);
  134. tokens.remove(i + 1);
  135. return;
  136. } else {
  137. stack.add(0, tokens.get(i));
  138. tokens.remove(i);
  139. }
  140. }
  141. throw new ParseException("Couldn't find matching opening bracket", offset);
  142. }
  143. /**
  144. * Parses an operator that takes two operands.
  145. *
  146. * @param tokens The supply of tokens from which the operator will be parsed
  147. * @param offset The offset at which the operator occurs
  148. */
  149. protected void parseBinaryOperator(final List<TreeToken> tokens, final int offset) {
  150. tokens.get(offset).addChild(tokens.get(offset - 1));
  151. tokens.get(offset).addChild(tokens.get(offset + 1));
  152. tokens.get(offset).setProcessed();
  153. tokens.remove(offset + 1);
  154. tokens.remove(offset - 1);
  155. }
  156. /**
  157. * Parses an operator that takes one operand.
  158. *
  159. * @param tokens The supply of tokens from which the operator will be parsed
  160. * @param offset The offset at which the operator occurs
  161. */
  162. protected void parseUnaryOperator(final List<TreeToken> tokens, final int offset) {
  163. tokens.get(offset).addChild(tokens.get(offset + 1));
  164. tokens.get(offset).setProcessed();
  165. tokens.remove(offset + 1);
  166. }
  167. /**
  168. * Parses an operator that does not actually correspond to a piece of the
  169. * input (such as the START and END operators).
  170. *
  171. * @param tokens The supply of tokens from which the operator will be parsed
  172. * @param offset The offset at which the operator occurs
  173. */
  174. protected void parseHiddenOperator(final List<TreeToken> tokens, final int offset) {
  175. tokens.remove(offset);
  176. }
  177. /**
  178. * Parses a number.
  179. *
  180. * @param tokens The supply of tokens from which the operator will be parsed
  181. * @param offset The offset at which the operator occurs
  182. */
  183. protected void parseNumber(final List<TreeToken> tokens, final int offset) {
  184. tokens.get(offset).setProcessed();
  185. }
  186. /**
  187. * Retrieves the offset of the first token within the input list that has
  188. * a type corresponding to the specified {@link TokenType}.
  189. *
  190. * @param tokens The tokens to be searched
  191. * @param type The desired token type
  192. * @return The index of the first token with that type, or -1 if none found
  193. */
  194. protected static int findTokenType(final List<TreeToken> tokens, final TokenType type) {
  195. for (int i = 0; i < tokens.size(); i++) {
  196. if (tokens.get(i).getToken().getType() == type && !tokens.get(i).isProcessed()) {
  197. return i;
  198. }
  199. }
  200. return -1;
  201. }
  202. /**
  203. * A class which compares token types based on their precendence.
  204. */
  205. protected static class TokenTypePrecedenceComparator implements Comparator<TokenType> {
  206. /** {@inheritDoc} */
  207. @Override
  208. public int compare(final TokenType o1, final TokenType o2) {
  209. return o2.getPrecedence() - o1.getPrecedence();
  210. }
  211. }
  212. }