001/*******************************************************************************
002 * Crown Copyright (c) 2006 - 2014, Copyright (c) 2006 - 2014 Kestral Computing P/L.
003 * All rights reserved. This program and the accompanying materials
004 * are made available under the terms of the Eclipse Public License v1.0
005 * which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v10.html
007 * 
008 * Contributors:
009 *    Kestral Computing P/L - initial implementation
010 *******************************************************************************/
011
012package org.hl7.fhir.utilities.ucum;
013
014import org.hl7.fhir.exceptions.UcumException;
015
016public class ExpressionParser {
017
018        private UcumModel model;
019        
020        /**
021         * @param model
022         */
023        public ExpressionParser(UcumModel model) {
024                super();
025                this.model = model;
026        }
027
028        public Term parse(String code) throws UcumException  {
029                Lexer lexer = new Lexer(code);
030                Term res = parseTerm(lexer, true);
031                if (!lexer.finished())
032                        throw new UcumException("Expression was not parsed completely. Syntax Error?");
033                return res;
034        }
035        
036        private Term parseTerm(Lexer lexer, boolean first) throws UcumException  {
037                Term res = new Term();
038                if (first && lexer.getType() == TokenType.NONE) {
039                        res.setComp(new Factor(1));
040                } else if (lexer.getType() == TokenType.SOLIDUS) {
041                        res.setOp(Operator.DIVISION);
042                        lexer.consume();
043                        res.setTerm(parseTerm(lexer, false));
044                } else {
045                  if (lexer.getType() == TokenType.ANNOTATION) {
046         res.setComp(new Factor(1)); // still lose the annotation
047         lexer.consume();
048                } else
049         res.setComp(parseComp(lexer));
050                        if (lexer.getType() != TokenType.NONE && lexer.getType() != TokenType.CLOSE) {
051                                if (lexer.getType() == TokenType.SOLIDUS) {
052                                        res.setOp(Operator.DIVISION);
053                                        lexer.consume();
054                                } else if (lexer.getType() == TokenType.PERIOD) {
055                                        res.setOp(Operator.MULTIPLICATION);
056                                        lexer.consume();
057                                } else if (lexer.getType() == TokenType.ANNOTATION)
058                                        res.setOp(Operator.MULTIPLICATION); // implicit
059                                else
060                                        lexer.error("Expected '/' or '.'");
061                                res.setTerm(parseTerm(lexer, false));
062                        }
063                } 
064                return res;
065        }
066
067        private Component parseComp(Lexer lexer) throws UcumException  {
068                if (lexer.getType() == TokenType.NUMBER) { 
069                        Factor fact = new Factor(lexer.getTokenAsInt());
070                        lexer.consume();
071                        return fact;
072                } else if (lexer.getType() == TokenType.SYMBOL)
073                        return parseSymbol(lexer);
074                else if  (lexer.getType() == TokenType.NONE)
075                        lexer.error("unexpected end of expression looking for a symbol or a number");
076                else if (lexer.getType() == TokenType.OPEN) {
077      lexer.consume();
078      Term res = parseTerm(lexer, true);
079      if (lexer.getType() == TokenType.CLOSE) 
080        lexer.consume();
081      else
082        lexer.error("Unexpected Token Type '"+lexer.getType().toString()+"' looking for a close bracket");
083      return res;
084                } else 
085                        lexer.error("unexpected token looking for a symbol or a number");
086                return null; // we never get to here
087        }
088
089        private Component parseSymbol(Lexer lexer) throws UcumException  {
090                Symbol symbol = new Symbol(); 
091                String sym = lexer.getToken();
092                
093                // now, can we pick a prefix that leaves behind a metric unit?
094                Prefix selected = null;
095                Unit unit = null;
096                for (Prefix prefix : model.getPrefixes()) {
097                        if (sym.startsWith(prefix.getCode())) {
098                                unit = model.getUnit(sym.substring(prefix.getCode().length()));
099                                if (unit != null && (unit.getKind() == ConceptKind.BASEUNIT || ((DefinedUnit) unit).isMetric())) {
100                                        selected = prefix;
101                                        break;
102                                };                              
103                        }
104                }
105
106                if (selected != null) {
107                        symbol.setPrefix(selected);
108                        symbol.setUnit(unit);
109                } else {
110                        unit = model.getUnit(sym);
111                        if (unit != null) 
112                                symbol.setUnit(unit);
113                        else if (!sym.equals("1"))
114                                lexer.error("The unit '"+sym+"' is unknown");
115                }
116                
117                lexer.consume();
118                if (lexer.getType() == TokenType.NUMBER) {
119                        symbol.setExponent(lexer.getTokenAsInt());
120                        lexer.consume();
121                } else
122                        symbol.setExponent(1);
123
124                return symbol;
125        }
126}