public static final Term FALSE = new Term("false"); public static final TermList NULL_LIST = new TermList(); public static final Term TRUE = new Term("true"); public static Term newAtom(final String text) {
  if (".".equals(text)) {
    return LIST_FUNCTOR;
  } else {
    return new Term(text);
  }
} public static TermLong newLong(final String text) {
  return new TermLong(text);
} public static TermStruct newStruct(final Term functor) {
  return new TermStruct(functor);
} public class Term {

  private final String text;

  Term(final String text) {
    this.text = requireNonNull(text);
  }

  public int getPriority() {
    return 0;
  }

  public String getText() {
    return this.text;
  }

  public TermType getTermType() {
    return ATOM;
  }

  public boolean dryUnifyTo(Term term) {
    if (this == term) {
      return true;
    }

    switch (term.getTermType()) {
      case VAR: {
        term = ((TermVar) term).getValue();
        return term == null || this.dryUnifyTo(term);
      }
      case ATOM: {
        return term.getClass() == Term.class && getText().equals(term.getText());
      }
      case STRUCT: {
        final TermStruct thatStruct = (TermStruct) term;
        return thatStruct.getArity() == 0 && getText().equals(thatStruct.getFunctor().getText());
      }
      default:
        return false;
    }
  }

  public Map<String, TermVar> allNamedVarsAsMap() {
    return this.variables()
        .collect(toMap(TermVar::getText, e -> e, (v1, v2) -> v2));
  }

  public Map<String, Term> allNamedVarValuesAsMap() {
    return this.variables()
        .filter(v -> !v.isFree())
        .collect(toMap(TermVar::getText, e -> e.<Term>findNonVarOrDefault(e), (v1, v2) -> v2));
  }

  public boolean isGround() {
    return true;
  }

  @SuppressWarnings("unchecked")
  public <T extends Term> T findNonVarOrSame() {
    return (T) this;
  }

  @SuppressWarnings("unchecked")
  public <T extends Term> T findNonVarOrDefault(final T term) {
    return (T) this;
  }

  public Stream<Term> stream() {
    return Stream.of(this);
  }

  public Stream<TermVar> variables() {
    return Stream.empty();
  }

  public String toSrcString() {
    return String.format("'%s'", escapeSrc(getText()));
  }

  @Override
  public String toString() {
    return toSrcString();
  }

  @Override
  public int hashCode() {
    if (text == null) {
      return super.hashCode();
    } else {
      return text.hashCode();
    }
  }

  public Number toNumber() {
    throw new ProlTypeErrorException("numeric", "NonNumeric term", this);
  }

  public String getSignature() {
    return getText() + "/0";
  }

  public String forWrite() {
    return getText();
  }

  public boolean unifyTo(final Term other) {
    if (this == other) {
      return true;
    }

    boolean result = false;

    switch (other.getTermType()) {
      case VAR: {
        final TermVar var = (TermVar) other;
        final Term value = var.getValue();
        if (value == null) {
          result = ((TermVar) other).setValue(this);
        } else {
          result = unifyTo(value);
        }
      }
      break;
      case ATOM: {
        result = other.getClass() == Term.class && getText().equals(other.getText());
      }
      break;
      case STRUCT: {
        final TermStruct thatStruct = (TermStruct) other;
        result = thatStruct.getArity() == 0 && getText().equals(thatStruct.getFunctor().getText());
      }
      break;
    }

    return result;
  }

  @Override
  public boolean equals(final Object obj) {
    if (obj == null) {
      return false;
    }

    if (this == obj) {
      return true;
    }

    final Class<?> objclass = obj.getClass();

    if (objclass == TermLong.class || obj.getClass() == Term.class) {

      final Term other = (Term) obj;

      if (hashCode() != other.hashCode()) {
        return false;
      }
      return this.text.equals(other.text);
    } else {
      return false;
    }
  }

  protected void doArrangeVars(final Map<String, TermVar> variables) {
  }

  public final void arrangeVariablesInsideTerms(final Term termTwo) {
    final Map<String, TermVar> varMap = new HashMap<>();
    this.doArrangeVars(varMap);
    termTwo.doArrangeVars(varMap);
  }

  public int getTextLength() {
    return this.text == null ? 0 : this.text.length();
  }

  public boolean hasVariableWithName(final String name) {
    return false;
  }

  public Term makeClone() {
    return this;
  }

  protected Term doMakeClone(final Map<Integer, TermVar> vars) {
    return this;
  }

  public final Term makeCloneAndVarBound() {
    return this.makeCloneAndVarBound(new HashMap<>());
  }

  protected Term makeCloneAndVarBound(final Map<Integer, TermVar> vars) {
    return this;
  }

} public enum TermType {
  ATOM,
  STRUCT,
  VAR,
  OPERATOR,
  OPERATORS,
  LIST
} public final class Terms {
  public static final Term TRUE = new Term("true");
  public static final Term FALSE = new Term("false");
  public static final TermLong INT_ONE = new TermLong(1L);
  public static final TermLong INT_ZERO = new TermLong(0L);
  public static final TermLong INT_MINUS_ONE = new TermLong(-1L);
  public static final Term LIST_FUNCTOR = new Term(".");
  public static final TermList NULL_LIST = new TermList();

  public static Term newAtom(final String text) {
    if (".".equals(text)) {
      return LIST_FUNCTOR;
    } else {
      return new Term(text);
    }
  }

  public static TermDouble newDouble(final String text) {
    return new TermDouble(text);
  }

  public static TermDouble newDouble(final double value) {
    return new TermDouble(value);
  }

  public static TermLong newLong(final String text) {
    return new TermLong(text);
  }

  public static TermLong newLong(final long value) {
    if (value == 0L) {
      return INT_ZERO;
    } else if (value == 1L) {
      return INT_ONE;
    } else if (value == -1L) {
      return INT_MINUS_ONE;
    } else {
      return new TermLong(value);
    }
  }

  public static TermList newList(final Term term) {
    return new TermList(term);
  }

  public static TermList newList(final Term head, final Term tail) {
    return new TermList(head, tail);
  }

  public static TermVar newVar(final String name) {
    return new TermVar(name);
  }

  public static TermVar newVar() {
    return new TermVar();
  }

  public static TermStruct newStruct(final Term functor) {
    return new TermStruct(functor);
  }

  public static TermStruct newStruct(final String functor, final Term[] elements) {
    return new TermStruct(functor, elements);
  }

  public static TermStruct newStruct(final Term functor, final Term[] elements) {
    return new TermStruct(functor, elements);
  }

  public static TermStruct newStruct(final Term functor, final Term[] elements,
                                     final PredicateInvoker processor) {
    return new TermStruct(functor, elements, processor);
  }

  private static Term convert(final JProlContext context, final PrologTerm term,
                              final Map<String, TermVar> vars) {
    switch (term.getType()) {
      case ATOM: {
        if (term instanceof PrologNumeric) {
          if (term instanceof PrologFloat) {
            return newDouble(((PrologFloat) term).getFloatValue().doubleValue());
          } else {
            return newLong(((PrologInt) term).getIntValue().longValue());
          }
        } else {
          final String text = term.getText();
          if (context.hasZeroArityPredicateForName(text)) {
            final TermStruct result = newStruct(text, new Term[0]);
            result.setPredicateProcessor(context.findProcessor(result));
            return result;
          } else {
            return newAtom(text);
          }
        }
      }
      case OPERATOR: {
        return context.getKnowledgeBase().findOperatorForName(context, term.getText())
            .getForTypePrecisely(((Op) term).getAssoc());
      }
      case LIST: {
        final PrologList list = (PrologList) term;
        return list.isEmpty() ? NULL_LIST :
            newList(convert(context, list.getHead(), vars), convert(context, list.getTail(), vars));
      }
      case STRUCT: {
        final PrologStruct struct = (PrologStruct) term;

        if (struct.isBlock()) {
          return convert(context, struct.getTermAt(0), vars);
        } else {
          final TermStruct result;
          final int arity = struct.getArity();
          if (arity == 0) {
            result = newStruct(convert(context, struct.getFunctor(), vars));
          } else {
            final Term[] terms = new Term[arity];
            for (int i = 0; i < arity; i++) {
              terms[i] = convert(context, struct.getTermAt(i), vars);
            }
            result = newStruct(convert(context, struct.getFunctor(), vars), terms);
          }
          result.setPredicateProcessor(context.findProcessor(result));
          return result;
        }
      }
      case VAR: {
        if (((PrologVar) term).isAnonymous()) {
          return newVar();
        } else {
          return vars.computeIfAbsent(term.getText(), Terms::newVar);
        }
      }
      default:
        throw new IllegalArgumentException("Unexpected parsed prolog term: " + term);
    }
  }

  public static Term fromParsed(final JProlContext context, final PrologTerm term) {
    if (term.getType() == com.igormaznitsa.prologparser.terms.TermType.ATOM
        || term.getType() == com.igormaznitsa.prologparser.terms.TermType.OPERATOR) {
      return convert(context, term, null);
    } else {
      return convert(context, term, new HashMap<>());
    }
  }

} public final class Utils {

  public static final TermOperator SIGNATURE_OPERATOR = new TermOperator(400, OpAssoc.YFX, "/");

  private Utils() {
  }

  public static TermList toCharCodeList(final Term term) {
    final String text = term.getText();

    if (text == null || text.isEmpty()) {
      return Terms.NULL_LIST;
    } else {
      final TermList result = createOrAppendToList(null, newLong(text.charAt(0)));
      TermList current = result;
      for (int i = 1; i < text.length(); i++) {
        current = createOrAppendToList(current, newLong(text.charAt(i)));
      }
      return result;
    }
  }

  public static int getJvmBitness() {
    String data = System.getProperty("sun.arch.data.model","");
    if (data.contains("32")) return 32;
    if (data.contains("64")) return 64;
    return 64;
  }
  
  public static TermList toCharList(final Term term) {
    final String text = term.getText();
    final int len = text.length();
    if (len == 0) {
      return Terms.NULL_LIST;
    }

    final StringBuilder buff = new StringBuilder(1);

    TermList resultList = null;
    TermList curList = null;

    for (int li = 0; li < len; li++) {
      buff.append(text.charAt(li));
      final Term newAtom = Terms.newAtom(buff.toString());
      buff.setLength(0);
      if (li == 0) {
        resultList = newList(newAtom);
        curList = resultList;
      } else {
        curList = createOrAppendToList(curList, newAtom);
      }
    }

    return resultList;
  }


  public static Throwable[] extractErrors(final List<CompletableFuture<Term>> futures) {
    return futures.stream().filter(CompletableFuture::isCompletedExceptionally).map(x -> {
      try {
        x.join();
      } catch (Throwable e) {
        if (!(e instanceof CancellationException)) {
          return e;
        }
      }
      return null;
    }).filter(Objects::nonNull).toArray(Throwable[]::new);
  }

  public static <T> CloseableIterator<T> makeCloseableIterator(final Iterator<T> iterator,
                                                               final Runnable onClose) {
    return new CloseableIterator<T>() {
      private final Iterator<T> wrapped = iterator;

      @Override
      public void close() {
        if (onClose != null) {
          onClose.run();
        }
      }

      @Override
      public boolean hasNext() {
        return this.wrapped.hasNext();
      }

      @Override
      public T next() {
        return this.wrapped.next();
      }
    };
  }

  public static void writeAsUtf8(final File file, final CharSequence seq) throws IOException {
    try (Writer writer = new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream(file, false), StandardCharsets.UTF_8))) {
      writer.write(seq.toString());
      writer.flush();
    }
  }

  public static String readAsUtf8(final File file) throws IOException {
    try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
      final StringBuilder buffer =
          new StringBuilder((int) file.length() < 0 ? 16384 : (int) file.length());
      while (!Thread.currentThread().isInterrupted()) {
        final int chr = reader.read();
        if (chr < 0) {
          break;
        }
        buffer.append((char) chr);
      }
      return buffer.toString();
    }
  }

  public static TermList createOrAppendToList(final TermList nullableList, final Term term) {
    final TermList newList = Terms.newList(term);

    if (nullableList != null && !nullableList.isNullList()) {
      newList.setTail(nullableList.getTail());
      nullableList.setTail(newList);
    }

    return newList;
  }

  public static String escapeSrc(final String string) {

    if (string.length() == 0) {
      return string;
    }

    final StringBuilder builder = new StringBuilder(string.length());

    for (int li = 0; li < string.length(); li++) {
      final char curChar = string.charAt(li);

      switch (curChar) {
        case '\\':
          builder.append("\\\\");
          break;
        case '\'':
          builder.append("\\'");
          break;
        case '\"':
          builder.append("\\\"");
          break;
        case '\n':
          builder.append("\\n");
          break;
        case '\f':
          builder.append("\\f");
          break;
        case '\r':
          builder.append("\\r");
          break;
        case '\t':
          builder.append("\\t");
          break;
        case '_': {
          builder.append('_');
        }
        break;
        case '%':
        case '.':
        default: {
          builder.append(curChar);
        }
        break;
      }
    }

    return builder.toString();
  }

  public static String extractPredicateSignatureFromStructure(final Term term) {
    final TermStruct struct = term.findNonVarOrSame();

    if (struct.getArity() != 2) {
      return null;
    }
    final Term left = struct.getElement(0).findNonVarOrDefault(null);
    final Term right = struct.getElement(1).findNonVarOrDefault(null);

    if (right instanceof TermLong && left.getTermType() == ATOM) {
      return left.getText() + '/' + right.getText();
    }
    return null;
  }

  public static String normalizeSignature(final String signature) {
    if (signature == null) {
      return null;
    }
    String sig = signature.trim();

    if (sig.length() > 0 && sig.charAt(0) == '\'') {
      sig = sig.substring(1);
      final int lastIndex = sig.lastIndexOf('/');
      if (lastIndex < 0) {
        sig = null;
      } else {
        final String arity = sig.substring(lastIndex + 1).trim();
        String name = sig.substring(0, lastIndex - 1).trim();
        if (name.length() > 0 && name.charAt(name.length() - 1) == '\'') {
          name = name.substring(0, name.length() - 1);
          sig = name + '/' + arity;
        } else {
          sig = null;
        }
      }
    }
    return sig;
  }

  public static String validateSignature(final String signature) {
    final String[] parsed =
        Objects.requireNonNull(signature, "Null signature not allowed").split("/");
    if (parsed.length == 2) {
      String str = parsed[0].trim();
      boolean quoted = false;
      if (str.length() != 0) {
        if (str.charAt(0) == '\'') {
          if (str.length() > 1 && str.charAt(str.length() - 1) == '\'') {
            str = str.substring(1, str.length() - 1);
            if (str.length() == 0) {
              return null;
            }
            quoted = true;
          } else {
            // wrong name, it must not contain '\'' as the only symbol
            return null;
          }
        }

        final char firstChar = str.charAt(0);
        if (!quoted && (Character.isDigit(firstChar) || Character.isUpperCase(firstChar) ||
            Character.isWhitespace(firstChar) || firstChar == '.')) {
          return null;
        }

        // ok. the first part is ok, check the second part
        final int arity;
        try {
          arity = Integer.parseInt(parsed[1].trim());
          if (arity < 0) {
            throw new NumberFormatException("Negate number is not supported as arity");
          }
        } catch (NumberFormatException ex) {
          return null;
        }

        final StringBuilder builder = new StringBuilder(signature.length());

        if (quoted) {
          builder.append('\'').append(str).append('\'').append('/').append(arity);
        } else {
          builder.append(str).append('/').append(arity);
        }

        return builder.toString();
      }
    }
    return null;
  }

}
import static com.igormaznitsa.jprol.data.Terms.FALSE;
import static com.igormaznitsa.jprol.data.Terms.NULL_LIST;
import static com.igormaznitsa.jprol.data.Terms.TRUE;
import static com.igormaznitsa.jprol.data.Terms.newAtom;
import static com.igormaznitsa.jprol.data.Terms.newLong;
import static com.igormaznitsa.jprol.data.Terms.newStruct;
import static java.util.Arrays.stream;
import com.igormaznitsa.jprol.data.Term;
import com.igormaznitsa.jprol.data.TermType;
import com.igormaznitsa.jprol.data.Terms;
import com.igormaznitsa.jprol.utils.Utils;
import java.util.Locale;
import java.util.Optional;
package com.igormaznitsa.jprol.logic;




public enum JProlSystemFlag {
  ADDRESS_BIT(true, Terms.newAtom("address_bit"), Terms.newLong(Utils.getJvmBitness())),
  ALLOW_VARIABLE_NAME_AS_FUNCTOR(true, Terms.newAtom("allow_variable_name_as_functor"), FALSE),
  OS(true, Terms.newAtom("os"), Terms.newAtom(System.getProperty("os.name", "unknown"))),
  ARCH(true, Terms.newAtom("arch"), Terms.newAtom(System.getProperty("os.arch", "unknown"))),
  BOUNDED(true, Terms.newAtom("bounded"), TRUE),
  DEBUG(false, Terms.newAtom("debug"), FALSE),
  DIALECT(true, Terms.newAtom("dialect"), Terms.newAtom("jprol")),
  ENCODING(false, Terms.newAtom("encoding"), Terms.newAtom("UTF-8")),
  GC(true, Terms.newAtom("gc"), TRUE),
  MAX_ARITY(true, Terms.newAtom("max_arity"), Terms.newLong(Integer.MAX_VALUE)),
  MAX_INTEGER(true, Terms.newAtom("max_integer"), Terms.newLong(Long.MAX_VALUE)),
  MIN_INTEGER(true, Terms.newAtom("min_integer"), Terms.newLong(Long.MIN_VALUE)),
  CPU_COUNT(true, Terms.newAtom("cpu_count"),
      Terms.newLong(Runtime.getRuntime().availableProcessors())),
  UNKNOWN(false, Terms.newAtom("unknown"), UndefinedPredicateBehavior.ERROR.getTerm()),
  HOME(true, Terms.newAtom("home"), Terms.newAtom(System.getProperty("user.home", ""))),
  VERIFY(false, Terms.newAtom("verify"), TRUE),
  VERSION_DATA(true, Terms.newAtom("version_data"),
      newStruct(newAtom("jprol"), new Term[] {newLong(2), newLong(0), newLong(1), NULL_LIST}));