Package com.lastcalc.parsers

Source Code of com.lastcalc.parsers.UserDefinedParserParser

/*******************************************************************************
* LastCalc - The last calculator you'll ever need
* Copyright (C) 2011, 2012 Uprizer Labs LLC
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE.  See the GNU Affero General Public License for more
* details.
******************************************************************************/
package com.lastcalc.parsers;

import java.util.*;
import java.util.Map.Entry;

import com.google.common.collect.*;

import org.jscience.mathematics.number.Number;
import org.jscience.physics.amount.Amount;

import com.lastcalc.TokenList;
import com.lastcalc.parsers.PreParser.ListWithTail;
import com.lastcalc.parsers.PreParser.MapWithTail;


public class UserDefinedParserParser extends Parser {

  private static final long serialVersionUID = -6964937711038633291L;
  private static TokenList template;

  static {
    template = TokenList.createD((Lists.<Object> newArrayList("=", "is", "means")));
  }

  @Override
  public ParseResult parse(final TokenList tokens, final int templatePos, final ParserContext context) {

    // Identify the beginning and end of the UDPP
    final int start = PreParser.findEdgeOrObjectBackwards(tokens, templatePos, null);
    final int end = PreParser.findEdgeOrObjectForwards(tokens, templatePos, null) + 1;

    final TokenList before = tokens.subList(start, templatePos);

    if (before.isEmpty())
      return ParseResult.fail();

    final TokenList after = PreParser.flatten(tokens.subList(templatePos + 1, end));

    if (after.isEmpty())
      return ParseResult.fail();

    final Set<String> variables = Sets.newHashSet();

    for (final Object o : PreParser.flatten(before)) {
      if (o instanceof String && !PreParser.reserved.contains(o)
          && Character.isUpperCase(((String) o).charAt(0))) {
        variables.add((String) o);
      }
    }

    final UserDefinedParser udp = new UserDefinedParser(before, after, variables);

    return ParseResult.success(
        tokens.replaceWithTokens(Math.max(0, start - 1), Math.min(tokens.size(), end + 1), udp));
  }

  public static class BindException extends Exception {
    private static final long serialVersionUID = -2021162162489202197L;

    public BindException(final String err) {
      super(err);
    }
  }

  @SuppressWarnings("unchecked")
  protected static void bind(final Object from, final Object to, final Set<String> variables,
      final Map<String, Object> bound)
          throws BindException {
    if (from.equals(to) || from.equals("?"))
      return;
    if (from instanceof String && variables.contains(from)) {
            if (bound.containsKey(from) && !bound.get(from).equals(to)) {
                throw new BindException("Variable "+from+" is already bound to "+bound.get(from)+" which isn't the same as "+to);
            }
      bound.put((String) from, to);
    } else if (from instanceof Iterable && to instanceof Iterable) {
      final Iterator<Object> fromL = ((Iterable<Object>) from).iterator();
      final Iterator<Object> toL = ((Iterable<Object>) to).iterator();
      while (fromL.hasNext() && toL.hasNext()) {
        bind(fromL.next(), toL.next(), variables, bound);
      }
      if (fromL.hasNext() || toL.hasNext())
        throw new BindException("Iterables are not of the same size");
    } else if (from instanceof MapWithTail && to instanceof Map) {
      final MapWithTail fromMWT = (MapWithTail) from;
      final Map<Object, Object> toM = (Map<Object, Object>) to;
      final Map<Object, Object> tail = Maps.newLinkedHashMap(toM);
      if (toM.size() < fromMWT.map.size())
        throw new BindException("Map must be at least size of head of MWT");
      for (final Entry<Object, Object> e : fromMWT.map.entrySet()) {
        if (!variables.contains(e.getValue()))
          throw new BindException("Map value '"+e.getValue()+" is not a variable");
        if (variables.contains(e.getKey()) && !bound.containsKey(e.getKey())) {
          // The key is a variable, bind to any value and remove from
          // tail
          final Map.Entry<Object, Object> nextEntry = tail.entrySet().iterator().next();
          bound.put((String) e.getKey(), nextEntry.getKey());
          bind(e.getValue(), nextEntry.getValue(), variables, bound);
          tail.remove(nextEntry.getKey());
        } else {
          final Object aKey = bound.containsKey(e.getKey()) ? bound.get(e.getKey()) : e.getKey();
          final Object value = toM.get(aKey);
          if (value == null)
            throw new BindException("Map doesn't contain key '" + e.getKey() + "'");
          bind(e.getValue(), value, variables, bound);
          tail.remove(aKey);
        }
      }
      bind(fromMWT.tail, tail, variables, bound);
    } else if (from instanceof Map && to instanceof Map) {
      final Map<Object, Object> fromM = (Map<Object, Object>) from;
      final Map<Object, Object> toM = (Map<Object, Object>) to;
      final Map<Object, Object> tail = Maps.newLinkedHashMap(toM);
      if (toM.size() != fromM.size())
        throw new BindException("Maps are not the same size");
      for (final Entry<Object, Object> e : fromM.entrySet()) {
        if (!variables.contains(e.getValue()))
          throw new BindException("Map value '" + e.getValue() + " is not a variable");
        if (variables.contains(e.getKey()) && !bound.containsKey(e.getKey())) {
          // The key is a variable, bind to any value and remove from
          // tail
          final Map.Entry<Object, Object> nextEntry = tail.entrySet().iterator().next();
          bound.put((String) e.getKey(), nextEntry.getKey());
          bind(e.getValue(), nextEntry.getValue(), variables, bound);
          tail.remove(nextEntry.getKey());
        } else {
          final Object aKey = bound.containsKey(e.getKey()) ? bound.get(e.getKey()) : e.getKey();
          final Object value = toM.get(aKey);
          if (value == null)
            throw new BindException("Map doesn't contain key '" + e.getKey() + "'");
          bind(e.getValue(), value, variables, bound);
          tail.remove(e.getKey());
        }
      }
    } else if (from instanceof ListWithTail && to instanceof List) {
      final ListWithTail fromLWT = (ListWithTail) from;
      final List<Object> toList = (List<Object>) to;
      if (toList.size() < fromLWT.list.size())
        throw new BindException("List must be at least size of head of LWT");
      if (toList.isEmpty())
        throw new BindException("Can't bind list with tail to empty list");
      final LinkedList<Object> tail = Lists.newLinkedList(toList);
      for (int x = 0; x < fromLWT.list.size(); x++) {
        bind(fromLWT.list.get(x), toList.get(x), variables, bound);
        tail.removeFirst();
      }
      bind(fromLWT.tail, tail, variables, bound);
    } else
      throw new BindException("Don't know how to bind " + from + ":" + from.getClass().getSimpleName() + " to "
          + to + ":" + to.getClass().getSimpleName());
  }

  public static class UserDefinedParser extends Parser {
    private static final long serialVersionUID = -4928516936219533258L;

    public final TokenList after;

    private final TokenList template;

    private final TokenList before;

    public final Set<String> variables;

    public UserDefinedParser(final TokenList before, final TokenList after, final Set<String> variables) {
      this.before = before;
      this.after = after;
      this.variables = variables;
      final List<Object> tpl = Lists.newArrayList();

      for (final Object o : before) {
        if (variables.contains(o)) {
          final String var = (String) o;
          if (var.endsWith("List")) {
            tpl.add(List.class);
          } else if (var.endsWith("Map")) {
            tpl.add(Map.class);
          } else if (var.endsWith("Num") || var.endsWith("Number")) {
            tpl.add(Number.class);
          } else if (var.endsWith("Bool") || var.endsWith("Boolean")) {
            tpl.add(Boolean.class);
          } else if (var.endsWith("Amount")) {
            tpl.add(Amount.class);
          } else if (var.endsWith("Fun") || var.endsWith("Function")) {
            tpl.add(UserDefinedParser.class);
            tpl.add(Amount.class);
          } else {
            tpl.add(Object.class);
          }
        } else if (o instanceof String) {
          tpl.add(o);
        } else if (o instanceof MapWithTail || o instanceof Map) {
          tpl.add(Map.class);
        } else if (o instanceof ListWithTail || o instanceof List) {
          tpl.add(List.class);
        } else {
          tpl.add(o.getClass());
        }
      }
      template = TokenList.create(tpl);

    }

    @Override
    public ParseResult parse(final TokenList tokens, final int templatePos, final ParserContext context) {
      if (tokens.get(PreParser.findEdgeOrObjectBackwards(tokens, templatePos, "then")).equals("then"))
        return ParseResult.fail();

      final List<Object> result = Lists.newArrayListWithCapacity(after.size() + 2);
      final boolean useBrackets = tokens.size() > template.size();
      if (useBrackets) {
        result.add("(");
      }
      final TokenList input = tokens.subList(templatePos, templatePos + template.size());
      final Map<String, Object> varMap = Maps.newHashMapWithExpectedSize(variables.size());
      try {
        bind(before, input, variables, varMap);
        for (final Object o : after) {
          final Object val = varMap.get(o);
          if (val != null) {
            result.add(val);
          } else {
            result.add(o);
          }
        }
      } catch (final BindException e) {
        return ParseResult.fail();
      }
      if (useBrackets) {
        result.add(")");
      }
      final TokenList resultTL = TokenList.create(result);
      final TokenList flattened = PreParser.flatten(resultTL);
      return ParseResult.success(
          tokens.replaceWithTokenList(templatePos, templatePos + template.size(), flattened),
          Math.min(0, -flattened.size())
          );
    }

    @Override
    public TokenList getTemplate() {
      return template;
    }

    @Override
    public String toString() {
      return before + " = " + after;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((after == null) ? 0 : after.hashCode());
      result = prime * result + ((before == null) ? 0 : before.hashCode());
      return result;
    }

    @Override
    public boolean equals(final Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof UserDefinedParser))
        return false;
      final UserDefinedParser other = (UserDefinedParser) obj;
      if (after == null) {
        if (other.after != null)
          return false;
      } else if (!after.equals(other.after))
        return false;
      if (before == null) {
        if (other.before != null)
          return false;
      } else if (!before.equals(other.before))
        return false;
      return true;
    }

    public boolean hasVariables() {
      return !variables.isEmpty();
    }
  }

  @Override
  public TokenList getTemplate() {
    return template;
  }

  @Override
  public int hashCode() {
    return "UserDefinedParserParser".hashCode();
  }

  @Override
  public boolean equals(final Object obj) {
    return obj instanceof UserDefinedParserParser;
  }

}
TOP

Related Classes of com.lastcalc.parsers.UserDefinedParserParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.