org.rascalmpl.library.Prelude.java Source code

Java tutorial

Introduction

Here is the source code for org.rascalmpl.library.Prelude.java

Source

/*******************************************************************************
 * Copyright (c) 2009-2013 CWI
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   * Paul Klint - Paul.Klint@cwi.nl - CWI
 *   * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
 *   * Arnold Lankamp - Arnold.Lankamp@cwi.nl
 *   * Davy Landman - Davy.Landman@cwi.nl
 *   * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI
*******************************************************************************/

/*******************************************************************************
 * 
 * Warning this file is an experiment to determine the effect of collecting all
 * classes used by the Prelude in a single class. Overall effect seems to be circa 10%
 * reduction of import time.
 * 
 * Do not edit/change this code, but use the original code instead.
 * 
 */
package org.rascalmpl.library;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang.CharSetUtils;
import org.apache.commons.lang.WordUtils;
import org.eclipse.imp.pdb.facts.IBool;
import org.eclipse.imp.pdb.facts.IConstructor;
import org.eclipse.imp.pdb.facts.IDateTime;
import org.eclipse.imp.pdb.facts.IInteger;
import org.eclipse.imp.pdb.facts.IList;
import org.eclipse.imp.pdb.facts.IListWriter;
import org.eclipse.imp.pdb.facts.IMap;
import org.eclipse.imp.pdb.facts.IMapWriter;
import org.eclipse.imp.pdb.facts.INode;
import org.eclipse.imp.pdb.facts.IRational;
import org.eclipse.imp.pdb.facts.ISet;
import org.eclipse.imp.pdb.facts.ISetWriter;
import org.eclipse.imp.pdb.facts.ISourceLocation;
import org.eclipse.imp.pdb.facts.IString;
import org.eclipse.imp.pdb.facts.ITuple;
import org.eclipse.imp.pdb.facts.IValue;
import org.eclipse.imp.pdb.facts.IValueFactory;
import org.eclipse.imp.pdb.facts.exceptions.FactTypeUseException;
import org.eclipse.imp.pdb.facts.io.ATermReader;
import org.eclipse.imp.pdb.facts.io.BinaryValueReader;
import org.eclipse.imp.pdb.facts.io.BinaryValueWriter;
import org.eclipse.imp.pdb.facts.io.StandardTextReader;
import org.eclipse.imp.pdb.facts.io.StandardTextWriter;
import org.eclipse.imp.pdb.facts.type.Type;
import org.eclipse.imp.pdb.facts.type.TypeFactory;
import org.eclipse.imp.pdb.facts.type.TypeStore;
import org.rascalmpl.interpreter.IEvaluatorContext;
import org.rascalmpl.interpreter.TypeReifier;
import org.rascalmpl.interpreter.control_exceptions.Throw;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.interpreter.result.ICallableValue;
import org.rascalmpl.interpreter.staticErrors.UndeclaredNonTerminal;
import org.rascalmpl.interpreter.types.FunctionType;
import org.rascalmpl.interpreter.types.NonTerminalType;
import org.rascalmpl.interpreter.types.OverloadedFunctionType;
import org.rascalmpl.interpreter.types.ReifiedType;
import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory;
import org.rascalmpl.parser.gtd.IGTD;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.parser.gtd.exception.UndeclaredNonTerminalException;
import org.rascalmpl.unicode.UnicodeDetector;
import org.rascalmpl.unicode.UnicodeOutputStreamWriter;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.uptr.Factory;
import org.rascalmpl.values.uptr.ProductionAdapter;
import org.rascalmpl.values.uptr.SymbolAdapter;
import org.rascalmpl.values.uptr.TreeAdapter;
import org.rascalmpl.values.uptr.visitors.TreeVisitor;

import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;

public class Prelude {
    private final TypeFactory types;
    private final IValueFactory values;
    private final Random random;

    public Prelude(IValueFactory values) {
        super();

        this.values = values;
        this.types = TypeFactory.getInstance();
        this.tr = new TypeReifier(values);
        random = new Random();
    }
    /*   // Only here for test purposes: 
       public IValue f1(IInteger x, IInteger y){
          return values.integer(x.intValue() + y.intValue());
       }
           
       public IValue f2(IInteger x, IList LS, IString y, IBool z){
          return values.string("x : " + x.intValue() + ", LS = " + LS + ", y ; " + y + ", z : " + z);
       }
    */
    /*
     * Boolean
     */

    public IValue arbBool() // get an arbitrary boolean value.}
    {
        return values.bool(random.nextInt(2) == 1);
    }

    /*
     * DateTime
     */
    public IValue now()
    //@doc{Get the current datetime.}
    {
        return values.datetime(Calendar.getInstance().getTimeInMillis());
    }

    public IValue createDate(IInteger year, IInteger month, IInteger day)
    //@doc{Create a new date.}
    {
        return values.date(year.intValue(), month.intValue(), day.intValue());
    }

    public IValue createTime(IInteger hour, IInteger minute, IInteger second, IInteger millisecond)
    //@doc{Create a new time.}
    {
        return values.time(hour.intValue(), minute.intValue(), second.intValue(), millisecond.intValue());
    }

    public IValue createTime(IInteger hour, IInteger minute, IInteger second, IInteger millisecond,
            IInteger timezoneHourOffset, IInteger timezoneMinuteOffset)
    //@doc{Create a new time with the given numeric timezone offset.}
    {
        return values.time(hour.intValue(), minute.intValue(), second.intValue(), millisecond.intValue(),
                timezoneHourOffset.intValue(), timezoneMinuteOffset.intValue());
    }

    public IValue createDateTime(IInteger year, IInteger month, IInteger day, IInteger hour, IInteger minute,
            IInteger second, IInteger millisecond)
    //@doc{Create a new datetime.}
    {
        return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(),
                minute.intValue(), second.intValue(), millisecond.intValue());
    }

    public IValue createDateTime(IInteger year, IInteger month, IInteger day, IInteger hour, IInteger minute,
            IInteger second, IInteger millisecond, IInteger timezoneHourOffset, IInteger timezoneMinuteOffset)
    //@doc{Create a new datetime with the given numeric timezone offset.}
    {
        return values.datetime(year.intValue(), month.intValue(), day.intValue(), hour.intValue(),
                minute.intValue(), second.intValue(), millisecond.intValue(), timezoneHourOffset.intValue(),
                timezoneMinuteOffset.intValue());
    }

    public IValue joinDateAndTime(IDateTime date, IDateTime time)
    //@doc{Create a new datetime by combining a date and a time.}
    {
        return values.datetime(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth(), time.getHourOfDay(),
                time.getMinuteOfHour(), time.getSecondOfMinute(), time.getMillisecondsOfSecond(),
                time.getTimezoneOffsetHours(), time.getTimezoneOffsetMinutes());
    }

    public IValue splitDateTime(IDateTime dt)
    //@doc{Split an existing datetime into a tuple with the date and the time.}
    {
        return values.tuple(values.date(dt.getYear(), dt.getMonthOfYear(), dt.getDayOfMonth()),
                values.time(dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute(),
                        dt.getMillisecondsOfSecond(), dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes()));
    }

    public IValue incrementYears(IDateTime dt, IInteger n)
    //@doc{Increment the years by a given amount.}
    {
        return incrementDate(dt, Calendar.YEAR, "years", n);
    }

    public IValue incrementMonths(IDateTime dt, IInteger n)
    //@doc{Increment the months by a given amount.}
    {
        return incrementDate(dt, Calendar.MONTH, "months", n);
    }

    public IValue incrementDays(IDateTime dt, IInteger n)
    //@doc{Increment the days by a given amount.}
    {
        return incrementDate(dt, Calendar.DAY_OF_MONTH, "days", n);
    }

    private String getTZString(int hourOffset, int minuteOffset) {
        String tzString = "GMT" + ((hourOffset < 0 || (0 == hourOffset && minuteOffset < 0)) ? "-" : "+")
                + String.format("%02d", hourOffset >= 0 ? hourOffset : hourOffset * -1)
                + String.format("%02d", minuteOffset >= 0 ? minuteOffset : minuteOffset * -1);
        return tzString;
    }

    private final int millisInAMinute = 1000 * 60;
    private final int millisInAnHour = millisInAMinute * 60;

    private IValue incrementDTField(IDateTime dt, int field, IInteger amount) {
        Calendar cal = null;

        cal = dateTimeToCalendar(dt);

        // Make sure lenient is true, since this allows wrapping of fields. For
        // instance, if you have $2012-05-15, and subtract 15 months, this is
        // an error if lenient is false, but gives $2012-02-15 (as expected)
        // if lenient is true.
        cal.setLenient(true);

        cal.add(field, amount.intValue());

        // Turn the calendar back into a date, time, or datetime value
        if (dt.isDate()) {
            return calendarToDate(cal);
        } else {
            if (dt.isTime()) {
                return calendarToTime(cal);
            } else {
                return calendarToDateTime(cal);
            }
        }
    }

    private IValue calendarToDateTime(Calendar cal) {
        int timezoneHours = cal.get(Calendar.ZONE_OFFSET) / millisInAnHour;
        int timezoneMinutes = cal.get(Calendar.ZONE_OFFSET) % millisInAnHour / millisInAMinute;
        return createDateTime(values.integer(cal.get(Calendar.YEAR)), values.integer(cal.get(Calendar.MONTH) + 1),
                values.integer(cal.get(Calendar.DAY_OF_MONTH)), values.integer(cal.get(Calendar.HOUR_OF_DAY)),
                values.integer(cal.get(Calendar.MINUTE)), values.integer(cal.get(Calendar.SECOND)),
                values.integer(cal.get(Calendar.MILLISECOND)), values.integer(timezoneHours),
                values.integer(timezoneMinutes));
    }

    private IValue calendarToTime(Calendar cal) {
        int timezoneHours = cal.get(Calendar.ZONE_OFFSET) / millisInAnHour;
        int timezoneMinutes = cal.get(Calendar.ZONE_OFFSET) % millisInAnHour / millisInAMinute;
        return createTime(values.integer(cal.get(Calendar.HOUR_OF_DAY)), values.integer(cal.get(Calendar.MINUTE)),
                values.integer(cal.get(Calendar.SECOND)), values.integer(cal.get(Calendar.MILLISECOND)),
                values.integer(timezoneHours), values.integer(timezoneMinutes));
    }

    private IValue calendarToDate(Calendar cal) {
        return createDate(values.integer(cal.get(Calendar.YEAR)), values.integer(cal.get(Calendar.MONTH) + 1),
                values.integer(cal.get(Calendar.DAY_OF_MONTH)));
    }

    private Calendar dateTimeToCalendar(IDateTime dt) {
        Calendar cal;
        if (dt.isDate()) {
            cal = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
            cal.set(dt.getYear(), dt.getMonthOfYear() - 1, dt.getDayOfMonth());
        } else {
            cal = Calendar.getInstance(
                    TimeZone.getTimeZone(getTZString(dt.getTimezoneOffsetHours(), dt.getTimezoneOffsetMinutes())),
                    Locale.getDefault());
            if (dt.isTime()) {
                cal.set(1970, 0, 1, dt.getHourOfDay(), dt.getMinuteOfHour(), dt.getSecondOfMinute());
            } else {
                cal.set(dt.getYear(), dt.getMonthOfYear() - 1, dt.getDayOfMonth(), dt.getHourOfDay(),
                        dt.getMinuteOfHour(), dt.getSecondOfMinute());
            }
            cal.set(Calendar.MILLISECOND, dt.getMillisecondsOfSecond());
        }
        return cal;
    }

    private IValue incrementTime(IDateTime dt, int field, String fieldName, IInteger amount) {
        if (dt.isDate())
            throw RuntimeExceptionFactory.invalidUseOfDateException(
                    "Cannot increment the " + fieldName + " on a date value.", null, null);

        return incrementDTField(dt, field, amount);
    }

    private IValue incrementDate(IDateTime dt, int field, String fieldName, IInteger amount) {
        if (dt.isTime())
            throw RuntimeExceptionFactory.invalidUseOfDateException(
                    "Cannot increment the " + fieldName + " on a time value.", null, null);

        return incrementDTField(dt, field, amount);
    }

    public IValue incrementHours(IDateTime dt, IInteger n)
    //@doc{Increment the hours by a given amount.}
    {
        return incrementTime(dt, Calendar.HOUR_OF_DAY, "hours", n);
    }

    public IValue incrementMinutes(IDateTime dt, IInteger n)
    //@doc{Increment the minutes by a given amount.}
    {
        return incrementTime(dt, Calendar.MINUTE, "minutes", n);
    }

    public IValue incrementSeconds(IDateTime dt, IInteger n)
    //@doc{Increment the seconds by a given amount.}
    {
        return incrementTime(dt, Calendar.SECOND, "seconds", n);
    }

    public IValue incrementMilliseconds(IDateTime dt, IInteger n)
    //@doc{Increment the milliseconds by a given amount.}
    {
        return incrementTime(dt, Calendar.MILLISECOND, "milliseconds", n);
    }

    public IValue decrementYears(IDateTime dt, IInteger n)
    //@doc{Decrement the years by a given amount.}
    {
        return incrementDate(dt, Calendar.YEAR, "years", n.negate());
    }

    public IValue decrementMonths(IDateTime dt, IInteger n)
    //@doc{Decrement the months by a given amount.}
    {
        return incrementDate(dt, Calendar.MONTH, "months", n.negate());
    }

    public IValue decrementDays(IDateTime dt, IInteger n)
    //@doc{Decrement the days by a given amount.}
    {
        return incrementDate(dt, Calendar.DAY_OF_MONTH, "days", n.negate());
    }

    public IValue decrementHours(IDateTime dt, IInteger n)
    //@doc{Decrement the hours by a given amount.}
    {
        return incrementTime(dt, Calendar.HOUR_OF_DAY, "hours", n.negate());
    }

    public IValue decrementMinutes(IDateTime dt, IInteger n)
    //@doc{Decrement the minutes by a given amount.}
    {
        return incrementTime(dt, Calendar.MINUTE, "minutes", n.negate());
    }

    public IValue decrementSeconds(IDateTime dt, IInteger n)
    //@doc{Decrement the seconds by a given amount.}
    {
        return incrementTime(dt, Calendar.SECOND, "seconds", n.negate());
    }

    public IValue decrementMilliseconds(IDateTime dt, IInteger n)
    //@doc{Decrement the milliseconds by a given amount.}
    {
        return incrementTime(dt, Calendar.MILLISECOND, "milliseconds", n.negate());
    }

    public IValue createDurationInternal(IDateTime dStart, IDateTime dEnd) {
        // dStart and dEnd both have to be dates, times, or datetimes
        Calendar startCal = Calendar.getInstance();
        startCal.setTimeInMillis(dStart.getInstant());
        Calendar endCal = Calendar.getInstance();
        endCal.setTimeInMillis(dEnd.getInstant());

        IValue duration = null;
        if (dStart.isDate()) {
            if (dEnd.isDate()) {
                duration = values.tuple(values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)),
                        values.integer(0), values.integer(0), values.integer(0), values.integer(0));
            } else if (dEnd.isTime()) {
                throw RuntimeExceptionFactory.invalidUseOfTimeException(
                        "Cannot determine the duration between a date with no time and a time with no date.", null,
                        null);
            } else {
                throw RuntimeExceptionFactory.invalidUseOfDateTimeException(
                        "Cannot determine the duration between a date with no time and a datetime.", null, null);
            }
        } else if (dStart.isTime()) {
            if (dEnd.isTime()) {
                duration = values.tuple(values.integer(0), values.integer(0), values.integer(0),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND)));
            } else if (dEnd.isDate()) {
                throw RuntimeExceptionFactory.invalidUseOfDateException(
                        "Cannot determine the duration between a time with no date and a date with no time.", null,
                        null);
            } else {
                throw RuntimeExceptionFactory.invalidUseOfDateTimeException(
                        "Cannot determine the duration between a time with no date and a datetime.", null, null);
            }
        } else {
            if (dEnd.isDateTime()) {
                duration = values.tuple(values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.YEAR)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MONTH)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.HOUR_OF_DAY)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MINUTE)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.SECOND)),
                        values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.MILLISECOND)));
            } else if (dEnd.isDate()) {
                throw RuntimeExceptionFactory.invalidUseOfDateException(
                        "Cannot determine the duration between a datetime and a date with no time.", null, null);
            } else {
                throw RuntimeExceptionFactory.invalidUseOfTimeException(
                        "Cannot determine the duration between a datetime and a time with no date.", null, null);
            }
        }
        return duration;
    }

    public IValue parseDate(IString inputDate, IString formatString)
    //@doc{Parse an input date given as a string using the given format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue());
            fmt.parse(inputDate.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            return values.date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue()
                    + " using format string: " + formatString.getValue(), null, null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue()
                    + " using format string: " + formatString.getValue(), null, null);
        }
    }

    public IValue parseDateInLocale(IString inputDate, IString formatString, IString locale)
    //@doc{Parse an input date given as a string using a specific locale and format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(),
                    new Locale(locale.getValue()));
            fmt.parse(inputDate.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            return values.date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue()
                    + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue(), null,
                    null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputDate.getValue()
                    + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue(), null,
                    null);
        }
    }

    public IValue parseTime(IString inputTime, IString formatString)
    //@doc{Parse an input time given as a string using the given format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue());
            fmt.parse(inputTime.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            // The value for zone offset comes back in milliseconds. The number of
            // hours is thus milliseconds / 1000 (to get to seconds) / 60 (to get to minutes)
            // / 60 (to get to hours). Minutes is this except for the last division,
            // but then we use mod 60 since this gives us total # of minutes, including
            // the hours we have already computed.
            int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
            int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60;
            return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND),
                    cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes);
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue()
                    + " using format string: " + formatString.getValue(), null, null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input date: " + inputTime.getValue()
                    + " using format string: " + formatString.getValue(), null, null);
        }
    }

    public IValue parseTimeInLocale(IString inputTime, IString formatString, IString locale)
    //@doc{Parse an input time given as a string using a specific locale and format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(),
                    new Locale(locale.getValue()));
            fmt.parse(inputTime.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            // The value for zone offset comes back in milliseconds. The number of
            // hours is thus milliseconds / 1000 (to get to seconds) / 60 (to get to minutes)
            // / 60 (to get to hours). Minutes is this except for the last division,
            // but then we use mod 60 since this gives us total # of minutes, including
            // the hours we have already computed.
            int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
            int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60;
            return values.time(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND),
                    cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes);
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue()
                    + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue(), null,
                    null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input time: " + inputTime.getValue()
                    + " using format string: " + formatString.getValue() + " in locale: " + locale.getValue(), null,
                    null);
        }
    }

    public IString printSymbol(IConstructor symbol, IBool withLayout) {
        return values.string(SymbolAdapter.toString(symbol, withLayout.getValue()));
    }

    public IValue parseDateTime(IString inputDateTime, IString formatString)
    //@doc{Parse an input datetime given as a string using the given format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue());
            fmt.setLenient(false);
            fmt.parse(inputDateTime.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
            int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60;
            return values.datetime(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
                    cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE),
                    cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes);
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: "
                    + inputDateTime.getValue() + " using format string: " + formatString.getValue(), null, null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError("Cannot parse input datetime: "
                    + inputDateTime.getValue() + " using format string: " + formatString.getValue(), null, null);
        }
    }

    public IValue parseDateTimeInLocale(IString inputDateTime, IString formatString, IString locale)
    //@doc{Parse an input datetime given as a string using a specific locale and format string}
    {
        try {
            java.text.SimpleDateFormat fmt = new java.text.SimpleDateFormat(formatString.getValue(),
                    new Locale(locale.getValue()));
            fmt.parse(inputDateTime.getValue());
            java.util.Calendar cal = fmt.getCalendar();
            int zoneHours = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
            int zoneMinutes = (cal.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60;
            return values.datetime(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
                    cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE),
                    cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND), zoneHours, zoneMinutes);
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimeParsingError(
                    "Cannot parse input datetime: " + inputDateTime.getValue() + " using format string: "
                            + formatString.getValue() + " in locale: " + locale.getValue(),
                    null, null);
        } catch (ParseException e) {
            throw RuntimeExceptionFactory.dateTimeParsingError(
                    "Cannot parse input datetime: " + inputDateTime.getValue() + " using format string: "
                            + formatString.getValue() + " in locale: " + locale.getValue(),
                    null, null);
        }
    }

    private Calendar getCalendarForDate(IDateTime inputDate) {
        if (inputDate.isDate() || inputDate.isDateTime()) {
            Calendar cal = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
            cal.setLenient(false);
            cal.set(inputDate.getYear(), inputDate.getMonthOfYear() - 1, inputDate.getDayOfMonth());
            return cal;
        } else {
            throw new IllegalArgumentException("Cannot get date for a datetime that only represents the time");
        }
    }

    private Calendar getCalendarForTime(IDateTime inputTime) {
        if (inputTime.isTime() || inputTime.isDateTime()) {
            Calendar cal = Calendar.getInstance(
                    TimeZone.getTimeZone(
                            getTZString(inputTime.getTimezoneOffsetHours(), inputTime.getTimezoneOffsetMinutes())),
                    Locale.getDefault());
            cal.setLenient(false);
            cal.set(Calendar.HOUR_OF_DAY, inputTime.getHourOfDay());
            cal.set(Calendar.MINUTE, inputTime.getMinuteOfHour());
            cal.set(Calendar.SECOND, inputTime.getSecondOfMinute());
            cal.set(Calendar.MILLISECOND, inputTime.getMillisecondsOfSecond());
            return cal;
        } else {
            throw new IllegalArgumentException("Cannot get time for a datetime that only represents the date");
        }
    }

    private Calendar getCalendarForDateTime(IDateTime inputDateTime) {
        if (inputDateTime.isDateTime()) {
            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(
                    getTZString(inputDateTime.getTimezoneOffsetHours(), inputDateTime.getTimezoneOffsetMinutes())),
                    Locale.getDefault());
            cal.setLenient(false);
            cal.set(inputDateTime.getYear(), inputDateTime.getMonthOfYear() - 1, inputDateTime.getDayOfMonth(),
                    inputDateTime.getHourOfDay(), inputDateTime.getMinuteOfHour(),
                    inputDateTime.getSecondOfMinute());
            cal.set(Calendar.MILLISECOND, inputDateTime.getMillisecondsOfSecond());
            return cal;
        } else {
            throw new IllegalArgumentException(
                    "Cannot get date and time for a datetime that only represents the date or the time");
        }
    }

    public IValue printDate(IDateTime inputDate, IString formatString)
    //@doc{Print an input date using the given format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue());
            Calendar cal = getCalendarForDate(inputDate);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory
                    .dateTimePrintingError("Cannot print time with format " + formatString.getValue(), null, null);
        }
    }

    public IValue printDate(IDateTime inputDate)
    //@doc{Print an input date using a default format string}
    {
        SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd");
        Calendar cal = getCalendarForDate(inputDate);
        sd.setCalendar(cal);
        return values.string(sd.format(cal.getTime()));
    }

    public IValue printDateInLocale(IDateTime inputDate, IString formatString, IString locale)
    //@doc{Print an input date using a specific locale and format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(), new ULocale(locale.getValue()));
            Calendar cal = getCalendarForDate(inputDate);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time with format "
                    + formatString.getValue() + ", in locale: " + locale.getValue(), null, null);
        }
    }

    public IValue printDateInLocale(IDateTime inputDate, IString locale)
    //@doc{Print an input date using a specific locale and a default format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd", new ULocale(locale.getValue()));
            Calendar cal = getCalendarForDate(inputDate);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time in locale: " + locale.getValue(),
                    null, null);
        }
    }

    public IValue printTime(IDateTime inputTime, IString formatString)
    //@doc{Print an input time using the given format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue());
            Calendar cal = getCalendarForTime(inputTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory
                    .dateTimePrintingError("Cannot print time with format: " + formatString.getValue(), null, null);
        }
    }

    public IValue printTime(IDateTime inputTime)
    //@doc{Print an input time using a default format string}
    {
        SimpleDateFormat sd = new SimpleDateFormat("HH:mm:ss.SSSZ");
        Calendar cal = getCalendarForTime(inputTime);
        sd.setCalendar(cal);
        return values.string(sd.format(cal.getTime()));
    }

    public IValue printTimeInLocale(IDateTime inputTime, IString formatString, IString locale)
    //@doc{Print an input time using a specific locale and format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(), new ULocale(locale.getValue()));
            Calendar cal = getCalendarForTime(inputTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time in locale: " + locale.getValue(),
                    null, null);
        }
    }

    public IValue printTimeInLocale(IDateTime inputTime, IString locale)
    //@doc{Print an input time using a specific locale and a default format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat("HH:mm:ss.SSSZ", new ULocale(locale.getValue()));
            Calendar cal = getCalendarForTime(inputTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print time in locale: " + locale.getValue(),
                    null, null);
        }
    }

    public IValue printDateTime(IDateTime inputDateTime, IString formatString)
    //@doc{Print an input datetime using the given format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue());
            Calendar cal = getCalendarForDateTime(inputDateTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError(
                    "Cannot print datetime using format string: " + formatString.getValue(), null, null);
        }
    }

    public IValue printDateTime(IDateTime inputDateTime)
    //@doc{Print an input datetime using a default format string}
    {
        SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
        Calendar cal = getCalendarForDateTime(inputDateTime);
        sd.setCalendar(cal);
        return values.string(sd.format(cal.getTime()));
    }

    public IValue printDateTimeInLocale(IDateTime inputDateTime, IString formatString, IString locale)
    //@doc{Print an input datetime using a specific locale and format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat(formatString.getValue(), new ULocale(locale.getValue()));
            Calendar cal = getCalendarForDateTime(inputDateTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory.dateTimePrintingError("Cannot print datetime using format string: "
                    + formatString.getValue() + " in locale: " + locale.getValue(), null, null);
        }
    }

    public IValue printDateTimeInLocale(IDateTime inputDateTime, IString locale)
    //@doc{Print an input datetime using a specific locale and a default format string}
    {
        try {
            SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", new ULocale(locale.getValue()));
            Calendar cal = getCalendarForDateTime(inputDateTime);
            sd.setCalendar(cal);
            return values.string(sd.format(cal.getTime()));
        } catch (IllegalArgumentException iae) {
            throw RuntimeExceptionFactory
                    .dateTimePrintingError("Cannot print datetime in locale: " + locale.getValue(), null, null);
        }
    }

    public IValue daysDiff(IDateTime dtStart, IDateTime dtEnd)
    //@doc{Increment the years by a given amount.}
    {
        if (!(dtStart.isTime() || dtEnd.isTime())) {
            Calendar startCal = Calendar.getInstance();
            startCal.setTimeInMillis(dtStart.getInstant());
            Calendar endCal = Calendar.getInstance();
            endCal.setTimeInMillis(dtEnd.getInstant());

            return values.integer(startCal.fieldDifference(endCal.getTime(), Calendar.DAY_OF_MONTH));
        }
        throw RuntimeExceptionFactory.invalidUseOfTimeException("Both inputs must include dates.", null, null);
    }

    /*
    * Graph
    */

    private HashMap<IValue, Distance> distance;
    private HashMap<IValue, IValue> pred;
    private HashSet<IValue> settled;
    private PriorityQueue<IValue> Q;
    private int MAXDISTANCE = 10000;

    private HashMap<IValue, LinkedList<IValue>> adjacencyList;

    private void buildAdjacencyListAndDistance(ISet G) {
        adjacencyList = new HashMap<IValue, LinkedList<IValue>>();
        distance = new HashMap<IValue, Distance>();

        for (IValue v : G) {
            ITuple tup = (ITuple) v;
            IValue from = tup.get(0);
            IValue to = tup.get(1);

            if (distance.get(from) == null)
                distance.put(from, new Distance(MAXDISTANCE));
            if (distance.get(to) == null)
                distance.put(to, new Distance(MAXDISTANCE));

            LinkedList<IValue> adjacencies = adjacencyList.get(from);
            if (adjacencies == null)
                adjacencies = new LinkedList<IValue>();
            adjacencies.add(to);
            adjacencyList.put(from, adjacencies);
        }
    }

    public IValue shortestPathPair(ISet G, IValue From, IValue To) {
        buildAdjacencyListAndDistance(G);
        distance.put(From, new Distance(0));

        pred = new HashMap<IValue, IValue>();
        settled = new HashSet<IValue>();
        Q = new PriorityQueue<IValue>(G.size(), new NodeComparator(distance));
        Q.add(From);

        while (!Q.isEmpty()) {
            IValue u = Q.remove();
            if (u.isEqual(To))
                return extractPath(From, u);
            settled.add(u);
            relaxNeighbours(u);
        }
        return values.list();
    }

    private void relaxNeighbours(IValue u) {
        LinkedList<IValue> adjacencies = adjacencyList.get(u);
        if (adjacencies != null) {
            for (IValue v : adjacencyList.get(u)) {
                if (!settled.contains(v)) {
                    Distance dv = distance.get(v);
                    Distance du = distance.get(u);
                    if (dv.intval > du.intval + 1) { // 1 is default weight of each edge
                        dv.intval = du.intval + 1;
                        pred.put(v, u);
                        Q.add(v);
                    }
                }
            }
        }
    }

    private IList extractPath(IValue start, IValue u) {
        IListWriter w = values.listWriter();

        if (!start.isEqual(u)) {
            w.insert(u);
            while (!pred.get(u).isEqual(start)) {
                u = pred.get(u);
                w.insert(u);
            }
            // TODO Check if a path was found at all; it could be that we just hit the root of the graph.
        }
        w.insert(start);
        return w.done();
    }

    public void print(IValue arg, IEvaluatorContext eval) {
        PrintWriter currentOutStream = eval.getStdOut();

        try {
            if (arg.getType().isString()) {
                currentOutStream.print(((IString) arg).getValue().toString());
            } else if (arg.getType().isSubtypeOf(Factory.Tree)) {
                currentOutStream.print(TreeAdapter.yield((IConstructor) arg));
            } else if (arg.getType().isSubtypeOf(Factory.Type)) {
                currentOutStream
                        .print(SymbolAdapter.toString((IConstructor) ((IConstructor) arg).get("symbol"), false));
            } else {
                currentOutStream.print(arg.toString());
            }
        } finally {
            currentOutStream.flush();
        }
    }

    public void iprint(IValue arg, IEvaluatorContext eval) {
        StandardTextWriter w = new StandardTextWriter(true, 2);

        try {
            w.write(arg, eval.getStdOut());
        } catch (IOException e) {
            RuntimeExceptionFactory.io(values.string("Could not print indented value"), eval.getCurrentAST(),
                    eval.getStackTrace());
        } finally {
            eval.getStdOut().flush();
        }
    }

    public void iprintToFile(ISourceLocation sloc, IValue arg, IEvaluatorContext eval) {
        StandardTextWriter w = new StandardTextWriter(true, 2);
        StringWriter sw = new StringWriter();

        try {
            w.write(arg, sw);
            writeFile(sloc, values.list(values.string(sw.toString())), eval);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), eval.getCurrentAST(), null);
        }
    }

    public void iprintln(IValue arg, IEvaluatorContext eval) {
        StandardTextWriter w = new StandardTextWriter(true, 2);

        try {
            w.write(arg, eval.getStdOut());
            eval.getStdOut().println();
        } catch (IOException e) {
            RuntimeExceptionFactory.io(values.string("Could not print indented value"), eval.getCurrentAST(),
                    eval.getStackTrace());
        } finally {
            eval.getStdOut().flush();
        }
    }

    public void println(IEvaluatorContext eval) {
        eval.getStdOut().println();
        eval.getStdOut().flush();
    }

    public void println(IValue arg, IEvaluatorContext eval) {
        PrintWriter currentOutStream = eval.getStdOut();

        try {
            if (arg.getType().isString()) {
                currentOutStream.print(((IString) arg).getValue());
            } else if (arg.getType().isSubtypeOf(Factory.Tree)) {
                currentOutStream.print(TreeAdapter.yield((IConstructor) arg));
            } else if (arg.getType().isSubtypeOf(Factory.Type)) {
                currentOutStream
                        .print(SymbolAdapter.toString((IConstructor) ((IConstructor) arg).get("symbol"), false));
            } else {
                currentOutStream.print(arg.toString());
            }
            currentOutStream.println();
        } finally {
            currentOutStream.flush();
        }
    }

    public void rprintln(IValue arg, IEvaluatorContext eval) {
        PrintWriter currentOutStream = eval.getStdOut();

        try {
            currentOutStream.print(arg.toString());
            currentOutStream.println();
        } finally {
            currentOutStream.flush();
        }
    }

    public void rprint(IValue arg, IEvaluatorContext eval) {
        PrintWriter currentOutStream = eval.getStdOut();

        try {
            currentOutStream.print(arg.toString());
        } finally {
            currentOutStream.flush();
        }
    }

    @Deprecated
    public IValue readFile(IString filename) {
        IListWriter w = values.listWriter(types.stringType());

        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(filename.getValue()));
            java.lang.String line;

            do {
                line = in.readLine();
                if (line != null) {
                    w.append(values.string(line));
                }
            } while (line != null);
        } catch (FileNotFoundException fnfex) {
            throw RuntimeExceptionFactory.pathNotFound(values.sourceLocation(filename.getValue()), null, null);
        } catch (IOException ioex) {
            throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
                }
            }
        }
        return w.done();
    }

    public IValue exists(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        return values.bool(ctx.getResolverRegistry().exists(sloc.getURI()));
    }

    public IValue lastModified(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        try {
            return values.datetime(ctx.getResolverRegistry().lastModified(sloc.getURI()));
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, null, null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(),
                    ctx.getStackTrace());
        }
    }

    public IValue isDirectory(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        return values.bool(ctx.getResolverRegistry().isDirectory(sloc.getURI()));
    }

    public IValue isFile(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        return values.bool(ctx.getResolverRegistry().isFile(sloc.getURI()));
    }

    public void mkDirectory(ISourceLocation sloc, IEvaluatorContext ctx) throws IOException {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        ctx.getResolverRegistry().mkDirectory(sloc.getURI());
    }

    public IValue listEntries(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        try {
            java.lang.String[] entries = ctx.getResolverRegistry().listEntries(sloc.getURI());
            IListWriter w = values.listWriter();
            for (java.lang.String entry : entries) {
                w.append(values.string(entry));
            }
            return w.done();
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(),
                    ctx.getStackTrace());
        }
    }

    public ISet charsets() {
        ISetWriter w = values.setWriter();
        for (String s : Charset.availableCharsets().keySet()) {
            w.insert(values.string(s));
        }
        return w.done();
    }

    public IValue readFile(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        Reader reader = null;

        try {
            Charset c = ctx.getResolverRegistry().getCharset(sloc.getURI());
            if (c != null) {
                return readFileEnc(sloc, values.string(c.name()), ctx);
            }
            sloc = ctx.getHeap().resolveSourceLocation(sloc);
            reader = ctx.getResolverRegistry().getCharacterReader(sloc.getURI());
            return consumeInputStream(sloc, reader, ctx);
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                }
            }
        }
    }

    public IValue readFileEnc(ISourceLocation sloc, IString charset, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        try {
            return consumeInputStream(sloc,
                    ctx.getResolverRegistry().getCharacterReader(sloc.getURI(), charset.getValue()), ctx);
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        }
    }

    private IValue consumeInputStream(ISourceLocation sloc, Reader in, IEvaluatorContext ctx) {
        StringBuilder result = new StringBuilder(1024 * 1024);
        try {
            char[] buf = new char[4096];
            int count;

            while ((count = in.read(buf)) != -1) {
                result.append(new java.lang.String(buf, 0, count));
            }

            java.lang.String str = result.toString();

            if (sloc.hasOffsetLength() && sloc.getOffset() != -1) {
                str = str.substring(sloc.getOffset(), sloc.getOffset() + sloc.getLength());
            }

            return values.string(str);
        } catch (FileNotFoundException fnfex) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException ioex) {
            throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
                }
            }
        }
    }

    public IValue md5HashFile(ISourceLocation sloc, IEvaluatorContext ctx) {
        StringBuilder result = new StringBuilder(1024 * 1024);

        InputStream in = null;
        try {
            in = ctx.getResolverRegistry().getInputStream(sloc.getURI());
            MessageDigest md = MessageDigest.getInstance("MD5");
            in = new DigestInputStream(in, md);
            byte[] buf = new byte[4096];
            int count;

            while ((count = in.read(buf)) != -1) {
                result.append(new java.lang.String(buf, 0, count));
            }

            return values.string(new String(md.digest()));
        } catch (FileNotFoundException fnfex) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException ioex) {
            throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
        } catch (NoSuchAlgorithmException e) {
            throw RuntimeExceptionFactory.io(values.string("Cannot load MD5 digest algorithm"), ctx.getCurrentAST(),
                    null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
                }
            }
        }
    }

    public void writeFile(ISourceLocation sloc, IList V, IEvaluatorContext ctx) {
        writeFile(sloc, V, false, ctx);
    }

    public void writeFileEnc(ISourceLocation sloc, IString charset, IList V, IEvaluatorContext ctx) {
        writeFileEnc(sloc, charset, V, false, ctx);
    }

    private void writeFile(ISourceLocation sloc, IList V, boolean append, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        IString charset = values.string("UTF8");
        if (append) {
            // in case the file already has a encoding, we have to correctly append that.
            InputStream in = null;
            Charset detected = null;
            try {
                detected = ctx.getResolverRegistry().getCharset(sloc.getURI());
                if (detected == null) {
                    in = ctx.getResolverRegistry().getInputStream(sloc.getURI());
                    detected = UnicodeDetector.estimateCharset(in);
                }
            } catch (FileNotFoundException fnfex) {
                throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
            } catch (IOException e) {
                throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
                    }
                }
            }
            if (detected != null)
                charset = values.string(detected.name());
            else {
                charset = values.string(Charset.defaultCharset().name());
            }
        }
        writeFileEnc(sloc, charset, V, append, ctx);
    }

    public IBool canEncode(IString charset) {
        return values.bool(Charset.forName(charset.getValue()).canEncode());
    }

    private void writeFileEnc(ISourceLocation sloc, IString charset, IList V, boolean append,
            IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        OutputStreamWriter out = null;

        if (!Charset.forName(charset.getValue()).canEncode()) {
            throw RuntimeExceptionFactory.illegalArgument(charset, null, null);
        }

        try {
            out = new UnicodeOutputStreamWriter(ctx.getResolverRegistry().getOutputStream(sloc.getURI(), append),
                    charset.getValue(), append);

            for (IValue elem : V) {
                if (elem.getType().isString()) {
                    out.append(((IString) elem).getValue());
                } else if (elem.getType().isSubtypeOf(Factory.Tree)) {
                    out.append(TreeAdapter.yield((IConstructor) elem));
                } else {
                    out.append(elem.toString());
                }
            }
        } catch (FileNotFoundException fnfex) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException ioex) {
            throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
                }
            }
        }

        return;
    }

    public void appendToFile(ISourceLocation sloc, IList V, IEvaluatorContext ctx) {
        writeFile(sloc, V, true, ctx);
    }

    public void appendToFileEnc(ISourceLocation sloc, IString charset, IList V, IEvaluatorContext ctx) {
        writeFileEnc(sloc, charset, V, true, ctx);
    }

    public IList readFileLines(ISourceLocation sloc, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);
        Reader reader = null;

        try {
            Charset detected = ctx.getResolverRegistry().getCharset(sloc.getURI());
            if (detected != null) {
                return readFileLinesEnc(sloc, values.string(detected.name()), ctx);
            }
            reader = ctx.getResolverRegistry().getCharacterReader(sloc.getURI());
            return consumeInputStreamLines(sloc, reader, ctx);
        } catch (MalformedURLException e) {
            throw RuntimeExceptionFactory.malformedURI(sloc.toString(), null, null);
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // forgot about it
                }
            }
        }
    }

    public IList readFileLinesEnc(ISourceLocation sloc, IString charset, IEvaluatorContext ctx) {
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        try {
            return consumeInputStreamLines(sloc,
                    ctx.getResolverRegistry().getCharacterReader(sloc.getURI(), charset.getValue()), ctx);
        } catch (MalformedURLException e) {
            throw RuntimeExceptionFactory.malformedURI(sloc.toString(), null, null);
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
        }
    }

    private IList consumeInputStreamLines(ISourceLocation sloc, Reader stream, IEvaluatorContext ctx) {
        IListWriter w = values.listWriter();

        BufferedReader in = null;
        try {
            in = new BufferedReader(stream);
            java.lang.String line;

            int i = 0;
            //         int offset = sloc.getOffset();
            int beginLine = sloc.hasLineColumn() ? sloc.getBeginLine() : -1;
            int beginColumn = sloc.hasLineColumn() ? sloc.getBeginColumn() : -1;
            int endLine = sloc.hasLineColumn() ? sloc.getEndLine() : -1;
            int endColumn = sloc.hasLineColumn() ? sloc.getEndColumn() : -1;

            do {
                line = in.readLine();
                i++;
                if (line != null) {
                    if (!sloc.hasOffsetLength()) {
                        w.append(values.string(line));
                    } else {
                        if (!sloc.hasLineColumn()) {
                            endColumn = line.length();
                        }
                        if (i == beginLine) {
                            if (i == endLine) {
                                w.append(values.string(line.substring(beginColumn, endColumn)));
                            } else {
                                w.append(values.string(line.substring(beginColumn)));
                            }
                        } else if (i > beginLine) {
                            if (i == endLine) {
                                w.append(values.string(line.substring(0, endColumn)));
                            } else if (i < endLine) {
                                w.append(values.string(line));
                            }
                        }
                    }
                }
            } while (line != null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
                }
            }
        }

        return w.done();
    }

    public IList readFileBytes(ISourceLocation sloc, IEvaluatorContext ctx) {
        IListWriter w = values.listWriter();
        sloc = ctx.getHeap().resolveSourceLocation(sloc);

        BufferedInputStream in = null;
        try {
            InputStream stream = ctx.getResolverRegistry().getInputStream(sloc.getURI());
            in = new BufferedInputStream(stream);
            int read;
            final int size = 256;
            byte bytes[] = new byte[size];

            do {
                read = in.read(bytes);
                for (int i = 0; i < read; i++) {
                    w.append(values.integer(bytes[i] & 0xff));
                }
            } while (read != -1);
        } catch (FileNotFoundException e) {
            throw RuntimeExceptionFactory.pathNotFound(sloc, ctx.getCurrentAST(), null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), ctx.getCurrentAST(), null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), ctx.getCurrentAST(), null);
                }
            }
        }

        return w.done();
    }

    public IString createLink(IString title, IString target) {
        return values.string("\uE007[" + title.getValue().replaceAll("\\]", "_") + "](" + target.getValue() + ")");
    }

    /*
     * List
     */

    private WeakReference<IList> indexes;

    /**
     * A mini class to wrap a lessThan function
     */
    private class Less {
        private final ICallableValue less;

        Less(ICallableValue less) {
            this.less = less;
        }

        public boolean less(IValue x, IValue y) {
            return ((IBool) less.call(new Type[] { x.getType(), y.getType() }, new IValue[] { x, y }, null)
                    .getValue()).getValue();
        }
    }

    private class Sorting {
        private final IValue[] array;
        private final int size;
        private final Less less;

        private void swap(int i, int j) {
            IValue tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }

        public Sorting(IValue[] array, Less less) {
            this.array = array;
            this.size = array.length;
            this.less = less;
        }

        /**
         * @throws IllegalArgument if comparator is illegal (i.e., if pivot equals pivot)
         */
        public Sorting sort() {
            if (size == 0) {
                return this;
            }
            if (less.less(array[0], array[0])) {
                throw RuntimeExceptionFactory.illegalArgument(less.less, null, null,
                        "Bad comparator: Did you use less-or-equals instead of less-than?");
            }
            sort(0, size - 1);

            return this;
        }

        public Sorting shuffle() {
            for (int i = 0; i < size; i++) {
                swap(i, i + (int) (Math.random() * (size - i)));
            }
            return this;
        }

        private void sort(int low, int high) {
            IValue pivot = array[low + (high - low) / 2];
            int oldLow = low;
            int oldHigh = high;

            while (low < high) {
                for (; less.less(array[low], pivot); low++)
                    ;
                for (; less.less(pivot, array[high]); high--)
                    ;

                if (low <= high) {
                    swap(low, high);
                    low++;
                    high--;
                }
            }

            if (oldLow < high)
                sort(oldLow, high);
            if (low < oldHigh)
                sort(low, oldHigh);
        }
    }

    public IValue elementAt(IList lst, IInteger index) {
        if (lst.length() == 0)
            throw RuntimeExceptionFactory.emptyList(null, null);
        try {
            int i = index.intValue();
            if (index.intValue() < 0)
                i = i + lst.length();
            return lst.get(i);
        } catch (IndexOutOfBoundsException e) {
            throw RuntimeExceptionFactory.indexOutOfBounds(index, null, null);
        }
    }

    public IList sort(IList l, IValue cmpv) {
        IValue[] tmpArr = new IValue[l.length()];
        for (int i = 0; i < l.length(); i++) {
            tmpArr[i] = l.get(i);
        }

        // we randomly swap some elements to make worst case complexity unlikely
        new Sorting(tmpArr, new Less((ICallableValue) cmpv)).shuffle().sort();

        IListWriter writer = values.listWriter();
        writer.append(tmpArr);
        return writer.done();
    }

    public IList sort(ISet l, IValue cmpv) {
        IValue[] tmpArr = new IValue[l.size()];
        int i = 0;

        // we assume that the set is reasonably randomly ordered, such
        // that the worst case of quicksort is unlikely
        for (IValue elem : l) {
            tmpArr[i++] = elem;
        }

        new Sorting(tmpArr, new Less((ICallableValue) cmpv)).sort();

        IListWriter writer = values.listWriter();
        for (IValue v : tmpArr) {
            writer.append(v);
        }

        return writer.done();
    }

    private IList makeUpTill(int from, int len) {
        IListWriter writer = values.listWriter();
        for (int i = from; i < len; i++) {
            writer.append(values.integer(i));
        }
        return writer.done();
    }

    public IValue delete(IList lst, IInteger n)
    // @doc{delete -- delete nth element from list}
    {
        try {
            return lst.delete(n.intValue());
        } catch (IndexOutOfBoundsException e) {
            throw RuntimeExceptionFactory.indexOutOfBounds(n, null, null);
        }
    }

    public IValue domain(IList lst)
    //@doc{domain -- a list of all legal index values for a list}
    {
        ISetWriter w = values.setWriter();
        int len = lst.length();
        for (int i = 0; i < len; i++) {
            w.insert(values.integer(i));
        }
        return w.done();
    }

    public IValue head(IList lst)
    // @doc{head -- get the first element of a list}
    {
        if (lst.length() > 0) {
            return lst.get(0);
        }

        throw RuntimeExceptionFactory.emptyList(null, null);
    }

    public IValue head(IList lst, IInteger n) throws IndexOutOfBoundsException
    // @doc{head -- get the first n elements of a list}
    {
        try {
            return lst.sublist(0, n.intValue());
        } catch (IndexOutOfBoundsException e) {
            IInteger end = values.integer(n.intValue() - 1);
            throw RuntimeExceptionFactory.indexOutOfBounds(end, null, null);
        }
    }

    public IValue getOneFrom(IList lst)
    //@doc{getOneFrom -- get an arbitrary element from a list}
    {
        int n = lst.length();
        if (n > 0) {
            return lst.get(random.nextInt(n));
        }

        throw RuntimeExceptionFactory.emptyList(null, null);
    }

    public IValue insertAt(IList lst, IInteger n, IValue elm) throws IndexOutOfBoundsException
    //@doc{insertAt -- add an element at a specific position in a list}
    {
        IListWriter w = values.listWriter();

        int k = n.intValue();
        if (k >= 0 && k <= lst.length()) {
            if (k == lst.length()) {
                w.insert(elm);
            }
            for (int i = lst.length() - 1; i >= 0; i--) {
                w.insert(lst.get(i));
                if (i == k) {
                    w.insert(elm);
                }
            }
            return w.done();
        }

        throw RuntimeExceptionFactory.indexOutOfBounds(n, null, null);
    }

    public IValue isEmpty(IList lst)
    //@doc{isEmpty -- is list empty?}
    {
        return values.bool(lst.length() == 0);
    }

    public IValue reverse(IList lst)
    //@doc{reverse -- elements of a list in reverse order}
    {
        return lst.reverse();
    }

    public IValue size(IList lst)
    //@doc{size -- number of elements in a list}
    {
        return values.integer(lst.length());
    }

    public IValue slice(IList lst, IInteger start, IInteger len)
    //@doc{slice -- sublist from start of length len}
    {
        try {
            return lst.sublist(start.intValue(), len.intValue());
        } catch (IndexOutOfBoundsException e) {
            IInteger end = values.integer(start.intValue() + len.intValue());
            throw RuntimeExceptionFactory.indexOutOfBounds(end, null, null);
        }
    }

    public IValue tail(IList lst)
    //@doc{tail -- all but the first element of a list}
    {
        try {
            return lst.sublist(1, lst.length() - 1);
        } catch (IndexOutOfBoundsException e) {
            throw RuntimeExceptionFactory.emptyList(null, null);
        }
    }

    public IValue tail(IList lst, IInteger len)
    //@doc{tail -- last n elements of a list}
    {
        int lenVal = len.intValue();
        int lstLen = lst.length();

        try {
            return lst.sublist(lstLen - lenVal, lenVal);
        } catch (IndexOutOfBoundsException e) {
            IInteger end = values.integer(lenVal - lstLen);
            throw RuntimeExceptionFactory.indexOutOfBounds(end, null, null);
        }
    }

    public IValue take(IInteger len, IList lst) {
        //@doc{take -- take n elements of from front of a list}
        int lenVal = len.intValue();
        int lstLen = lst.length();
        if (lenVal >= lstLen) {
            return lst;
        } else {
            return lst.sublist(0, lenVal);
        }
    }

    public IValue drop(IInteger len, IList lst) {
        //@doc{drop -- remove n elements of from front of a list}
        int lenVal = len.intValue();
        int lstLen = lst.length();
        if (lenVal >= lstLen) {
            return values.list();
        } else {
            return lst.sublist(lenVal, lstLen - lenVal);
        }
    }

    public IValue upTill(IInteger ni) {
        //@doc{Returns the list 0..n, this is slightly faster than [0,1..n], since the returned values are shared}
        int n = ni.intValue();
        if (indexes == null || indexes.get() == null) {
            IList l = makeUpTill(0, n);
            indexes = new WeakReference<IList>(l);
            return indexes.get();
        } else {
            IList l = indexes.get(); // strong ref
            if (l == null || n >= l.length()) {
                l = makeUpTill(0, n);
                indexes = new WeakReference<IList>(l);
                return l;
            }
            return l.sublist(0, n);
        }
    }

    public IValue prefix(IList lst) {
        //@doc{Return all but the last element of a list}
        int lstLen = lst.length();
        if (lstLen <= 1) {
            return values.list();
        } else {
            return lst.sublist(0, lstLen - 1);
        }
    }

    public IValue takeOneFrom(IList lst)
    //@doc{takeOneFrom -- remove an arbitrary element from a list, returns the element and the modified list}
    {
        int n = lst.length();

        if (n > 0) {
            int k = random.nextInt(n);
            IValue pick = lst.get(0);
            IListWriter w = values.listWriter();

            for (int i = n - 1; i >= 0; i--) {
                if (i == k) {
                    pick = lst.get(i);
                } else {
                    w.insert(lst.get(i));
                }
            }
            return values.tuple(pick, w.done());
        }

        throw RuntimeExceptionFactory.emptyList(null, null);
    }

    private class IValueWrap {
        private final IValue ori;

        public IValueWrap(IValue ori) {
            this.ori = ori;
        }

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

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj instanceof IValueWrap) {
                return ori.isEqual(((IValueWrap) obj).ori);
            }
            return false;
        }

        public IValue getValue() {
            return ori;
        }
    }

    public IMap toMap(IList lst)
    // @doc{toMap -- convert a list of tuples to a map; first value in old tuples is associated with a set of second values}
    {
        Map<IValueWrap, ISetWriter> hm = new HashMap<IValueWrap, ISetWriter>();

        for (IValue v : lst) {
            ITuple t = (ITuple) v;
            IValueWrap key = new IValueWrap(t.get(0));
            IValue val = t.get(1);
            ISetWriter wValSet = hm.get(key);
            if (wValSet == null) {
                wValSet = values.setWriter();
                hm.put(key, wValSet);
            }
            wValSet.insert(val);
        }

        IMapWriter w = values.mapWriter();
        for (IValueWrap v : hm.keySet()) {
            w.put(v.getValue(), hm.get(v).done());
        }
        return w.done();
    }

    public IValue toMapUnique(IList lst)
    //@doc{toMapUnique -- convert a list of tuples to a map; result should be a map}
    {
        if (lst.length() == 0) {
            return values.mapWriter().done();
        }

        IMapWriter w = values.mapWriter();
        Set<IValueWrap> seenKeys = new HashSet<IValueWrap>();
        for (IValue v : lst) {
            ITuple t = (ITuple) v;
            IValueWrap key = new IValueWrap(t.get(0));
            if (seenKeys.contains(key))
                throw RuntimeExceptionFactory.MultipleKey(key.getValue(), null, null);
            seenKeys.add(key);
            w.put(key.getValue(), t.get(1));
        }
        return w.done();
    }

    public IValue toSet(IList lst)
    //@doc{toSet -- convert a list to a set}
    {
        ISetWriter w = values.setWriter();

        for (IValue v : lst) {
            w.insert(v);
        }

        return w.done();
    }

    public IValue toString(IList lst)
    //@doc{toString -- convert a list to a string}
    {
        return values.string(lst.toString());
    }

    /*
     * Map
     */

    public IValue domain(IMap M)
    //@doc{domain -- return the domain (keys) of a map}

    {
        ISetWriter w = values.setWriter();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            w.insert(entry.getKey());
        }
        return w.done();
    }

    public IValue getOneFrom(IMap m)
    //@doc{getOneFrom -- return arbitrary key of a map}
    {
        int i = 0;
        int sz = m.size();
        if (sz == 0) {
            throw RuntimeExceptionFactory.emptyMap(null, null);
        }
        int k = random.nextInt(sz);
        Iterator<Entry<IValue, IValue>> iter = m.entryIterator();

        while (iter.hasNext()) {
            if (i == k) {
                return (iter.next()).getKey();
            }
            iter.next();
            i++;
        }
        return null;
    }

    public IValue invertUnique(IMap M)
    //@doc{invertUnique -- return map with key and value inverted; values are unique}
    {
        IMapWriter w = values.mapWriter();
        HashSet<IValue> seenValues = new HashSet<IValue>();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            IValue key = entry.getKey();
            IValue val = entry.getValue();
            if (seenValues.contains(val))
                throw RuntimeExceptionFactory.MultipleKey(val, null, null);
            seenValues.add(val);
            w.put(val, key);
        }
        return w.done();
    }

    public IValue invert(IMap M)
    //@doc{invert -- return map with key and value inverted; values are not unique and are collected in a set}
    {
        HashMap<IValue, ISetWriter> hm = new HashMap<IValue, ISetWriter>();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            IValue key = entry.getKey();
            IValue val = entry.getValue();
            ISetWriter wKeySet = hm.get(val);
            if (wKeySet == null) {
                wKeySet = values.setWriter();
                hm.put(val, wKeySet);
            }
            wKeySet.insert(key);
        }

        IMapWriter w = values.mapWriter();

        iter = M.entryIterator();
        for (IValue v : hm.keySet()) {
            w.put(v, hm.get(v).done());
        }
        return w.done();
    }

    public IValue isEmpty(IMap M)
    //@doc{isEmpty -- is map empty?}
    {
        return values.bool(M.size() == 0);
    }

    public IValue range(IMap M)
    //@doc{range -- return the range (values) of a map}
    {
        ISetWriter w = values.setWriter();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            w.insert(entry.getValue());
        }
        return w.done();
    }

    public IValue size(IMap M) {
        return values.integer(M.size());
    }

    public IValue toList(IMap M)
    //@doc{toList -- convert a map to a list}
    {
        IListWriter w = values.listWriter();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            w.insert(values.tuple(entry.getKey(), entry.getValue()));
        }
        return w.done();
    }

    public IValue toRel(IMap M)
    //@doc{toRel -- convert a map to a relation}
    {
        ISetWriter w = values.setWriter();
        Iterator<Entry<IValue, IValue>> iter = M.entryIterator();
        while (iter.hasNext()) {
            Entry<IValue, IValue> entry = iter.next();
            w.insert(values.tuple(entry.getKey(), entry.getValue()));
        }
        return w.done();
    }

    public IValue toString(IMap M) {
        return values.string(M.toString());
    }

    /*
     * Node
     */

    public IValue arity(INode T)
    //@doc{arity -- number of children of a node}
    {
        return values.integer(T.arity());
    }

    public IValue getChildren(INode T)
    //@doc{getChildren -- get the children of a node}
    {
        IListWriter w = values.listWriter();

        for (IValue v : T.getChildren()) {
            w.append(v);
        }
        return w.done();
    }

    public IValue getName(INode T)
    //@doc{getName -- get the function name of a node}
    {
        return values.string(T.getName());
    }

    public IValue makeNode(IString N, IList V)
    //@doc{makeNode -- create a node given its function name and arguments}
    {
        IList argList = V;
        IValue args[] = new IValue[argList.length()];
        int i = 0;
        for (IValue v : argList) {
            args[i++] = v;
        }
        return values.node(N.getValue(), args);
    }

    public IValue readATermFromFile(IString fileName) {
        //@doc{readATermFromFile -- read an ATerm from a named file}
        ATermReader atr = new ATermReader();
        try {
            FileInputStream stream = new FileInputStream(fileName.getValue());
            IValue result = atr.read(values, stream);
            stream.close();
            return result;
        } catch (FactTypeUseException e) {
            e.printStackTrace();
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } catch (IOException e) {
            e.printStackTrace();
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);

        }
    }

    public IValue toString(INode T)
    //@doc{toString -- convert a node to a string}
    {
        return values.string(T.toString());
    }

    public IMap getAnnotations(INode node) {
        java.util.Map<java.lang.String, IValue> map = node.asAnnotatable().getAnnotations();
        IMapWriter w = values.mapWriter();

        for (Entry<java.lang.String, IValue> entry : map.entrySet()) {
            w.put(values.string(entry.getKey()), entry.getValue());
        }

        return w.done();
    }

    public INode setAnnotations(INode node, IMap annotations) {
        java.util.Map<java.lang.String, IValue> map = new HashMap<java.lang.String, IValue>();

        for (IValue key : annotations) {
            IValue value = annotations.get(key);
            map.put(((IString) key).getValue(), value);
        }

        return node.asAnnotatable().setAnnotations(map);
    }

    public INode delAnnotations(INode node) {
        return node.asAnnotatable().removeAnnotations();
    }

    public INode delAnnotation(INode node, IString label) {
        return node.asAnnotatable().removeAnnotation(label.getValue());
    }

    /*
     * ParseTree
     */

    private final TypeReifier tr;

    public IValue parse(IValue start, ISourceLocation input, IEvaluatorContext ctx) {
        return parse(start, values.mapWriter().done(), input, ctx);
    }

    public IValue parse(IValue start, IMap robust, ISourceLocation input, IEvaluatorContext ctx) {
        Type reified = start.getType();
        IConstructor startSort = checkPreconditions(start, reified);

        try {
            IConstructor pt = ctx.getEvaluator().parseObject(ctx.getEvaluator().getMonitor(), startSort, robust,
                    input.getURI());

            if (TreeAdapter.isAppl(pt)) {
                if (SymbolAdapter.isStart(TreeAdapter.getType(pt))) {
                    pt = (IConstructor) TreeAdapter.getArgs(pt).get(1);
                }
            }
            return pt;
        } catch (ParseError pe) {
            ISourceLocation errorLoc = values.sourceLocation(pe.getLocation(), pe.getOffset(), pe.getLength(),
                    pe.getBeginLine() + 1, pe.getEndLine() + 1, pe.getBeginColumn(), pe.getEndColumn());
            throw RuntimeExceptionFactory.parseError(errorLoc, ctx.getCurrentAST(), ctx.getStackTrace());
        } catch (UndeclaredNonTerminalException e) {
            throw new UndeclaredNonTerminal(e.getName(), e.getClassName(), ctx.getCurrentAST());
        }
    }

    public IValue parse(IValue start, IString input, IEvaluatorContext ctx) {
        return parse(start, values.mapWriter().done(), input, ctx);
    }

    public IValue parse(IValue start, IMap robust, IString input, IEvaluatorContext ctx) {
        Type reified = start.getType();
        IConstructor startSort = checkPreconditions(start, reified);
        try {
            IConstructor pt = ctx.getEvaluator().parseObject(ctx.getEvaluator().getMonitor(), startSort, robust,
                    input.getValue());

            if (TreeAdapter.isAppl(pt)) {
                if (SymbolAdapter.isStart(TreeAdapter.getType(pt))) {
                    pt = (IConstructor) TreeAdapter.getArgs(pt).get(1);
                }
            }

            return pt;
        } catch (ParseError pe) {
            ISourceLocation errorLoc = values.sourceLocation(pe.getLocation(), pe.getOffset(), pe.getLength(),
                    pe.getBeginLine() + 1, pe.getEndLine() + 1, pe.getBeginColumn(), pe.getEndColumn());
            throw RuntimeExceptionFactory.parseError(errorLoc, null, null);
        } catch (UndeclaredNonTerminalException e) {
            throw new UndeclaredNonTerminal(e.getName(), e.getClassName(), ctx.getCurrentAST());
        }
    }

    public IValue parse(IValue start, IString input, ISourceLocation loc, IEvaluatorContext ctx) {
        return parse(start, values.mapWriter().done(), input, loc, ctx);
    }

    public IValue parse(IValue start, IMap robust, IString input, ISourceLocation loc, IEvaluatorContext ctx) {
        Type reified = start.getType();
        IConstructor startSort = checkPreconditions(start, reified);
        try {
            IConstructor pt = ctx.getEvaluator().parseObject(ctx.getEvaluator().getMonitor(), startSort, robust,
                    input.getValue(), loc);

            if (TreeAdapter.isAppl(pt)) {
                if (SymbolAdapter.isStart(TreeAdapter.getType(pt))) {
                    pt = (IConstructor) TreeAdapter.getArgs(pt).get(1);
                }
            }

            return pt;
        } catch (ParseError pe) {
            ISourceLocation errorLoc = values.sourceLocation(pe.getLocation(), pe.getOffset(), pe.getLength(),
                    pe.getBeginLine(), pe.getEndLine(), pe.getBeginColumn(), pe.getEndColumn());
            throw RuntimeExceptionFactory.parseError(errorLoc, null, null);
        } catch (UndeclaredNonTerminalException e) {
            throw new UndeclaredNonTerminal(e.getName(), e.getClassName(), ctx.getCurrentAST());
        }
    }

    public IString saveParser(ISourceLocation outFile, IEvaluatorContext ctx) {

        IGTD<IConstructor, IConstructor, ISourceLocation> parser = org.rascalmpl.semantics.dynamic.Import.getParser(
                ctx.getEvaluator(), (ModuleEnvironment) ctx.getCurrentEnvt().getRoot(), URIUtil.invalidURI(),
                false);
        Class<IGTD<IConstructor, IConstructor, ISourceLocation>> parserClass = (Class<IGTD<IConstructor, IConstructor, ISourceLocation>>) parser
                .getClass();

        try (OutputStream outStream = ctx.getResolverRegistry().getOutputStream(outFile.getURI(), false)) {
            ctx.getEvaluator().getParserGenerator().saveToJar(parserClass, outStream);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(
                    ctx.getValueFactory().string("Unable to save to output file '" + outFile.getURI() + "'"),
                    ctx.getCurrentAST(), ctx.getStackTrace());
        }
        return ctx.getValueFactory().string(parserClass.getName());

    }

    public IString unparse(IConstructor tree) {
        return values.string(TreeAdapter.yield(tree));
    }

    private IConstructor makeConstructor(Type returnType, String name, IEvaluatorContext ctx, IValue... args) {
        IValue value = ctx.getEvaluator().call(returnType.getName(), name, args);
        Type type = value.getType();
        if (type.isAbstractData()) {
            return (IConstructor) value;
        }
        throw RuntimeExceptionFactory
                .implodeError("Calling of constructor " + name + " did not return a constructor", null, null);
    }

    private java.lang.String unescapedConsName(IConstructor tree) {
        java.lang.String x = TreeAdapter.getConstructorName(tree);
        if (x != null) {
            x = x.replaceAll("\\\\", "");
        }
        return x;
    }

    private Set<Type> findConstructors(Type type, java.lang.String constructorName, int arity, TypeStore store) {
        Set<Type> constructors = new HashSet<Type>();

        for (Type constructor : store.lookupConstructor(type, constructorName)) {
            if (constructor.getArity() == arity)
                constructors.add(constructor);
        }

        return constructors;
    }

    //   private Type findConstructor(Type type, java.lang.String constructorName, int arity,  TypeStore store) {
    //      for (Type candidate: store.lookupConstructor(type, constructorName)) {
    //         // It finds the first with suitable arity, so this is inaccurate
    //         // if there are overloaded constructors with the same arity
    //         if (arity == candidate.getArity()) {
    //            return candidate;
    //         }
    //      }
    //      return null;
    //   }

    public IValue implode(IValue reifiedType, IConstructor tree, IEvaluatorContext ctx) {
        TypeStore store = new TypeStore();
        Type type = tr.valueToType((IConstructor) reifiedType, store);
        try {
            IValue result = implode(store, type, tree, false, ctx);
            if (isUntypedNodeType(type) && !type.isTop() && (TreeAdapter.isList(tree) || TreeAdapter.isOpt(tree))) {
                // Ensure the result is actually a node, even though
                // the tree given to implode is a list.
                result = values.node("", result);
            }
            return result;
        } catch (Backtrack b) {
            throw b.exception;
        }
    }

    @SuppressWarnings("serial")
    private static class Backtrack extends RuntimeException {
        Throw exception;

        public Backtrack(Throw exception) {
            this.exception = exception;
        }

        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }
    }

    private IValue[] implodeArgs(TypeStore store, Type type, IList args, IEvaluatorContext ctx) {
        int length = args.length();
        IValue implodedArgs[] = new IValue[length];
        for (int i = 0; i < length; i++) {
            Type argType = isUntypedNodeType(type) ? type : type.getFieldType(i);
            implodedArgs[i] = implode(store, argType, (IConstructor) args.get(i), false, ctx);
        }
        return implodedArgs;
    }

    private IValue implode(TypeStore store, Type type, IConstructor tree, boolean splicing, IEvaluatorContext ctx) {

        // always yield if expected type is str, except if regular 
        if (type.isString() && !splicing) {
            return values.string(TreeAdapter.yield(tree));
        }

        if (SymbolAdapter.isStartSort(TreeAdapter.getType(tree))) {
            IList args = TreeAdapter.getArgs(tree);
            IConstructor before = (IConstructor) args.get(0);
            IConstructor ast = (IConstructor) args.get(1);
            IConstructor after = (IConstructor) args.get(2);
            IValue result = implode(store, type, ast, splicing, ctx);
            if (result.getType().isNode()) {
                IMapWriter comments = values.mapWriter();
                comments.putAll((IMap) ((INode) result).asAnnotatable().getAnnotation("comments"));
                IList beforeComments = extractComments(before);
                if (!beforeComments.isEmpty()) {
                    comments.put(values.integer(-1), beforeComments);
                }
                IList afterComments = extractComments(after);
                if (!afterComments.isEmpty()) {
                    comments.put(values.integer(((INode) result).arity()), afterComments);
                }
                result = ((INode) result).asAnnotatable().setAnnotation("comments", comments.done());
            }
            return result;
        }

        if (TreeAdapter.isLexical(tree)) {
            java.lang.String constructorName = unescapedConsName(tree);
            java.lang.String yield = TreeAdapter.yield(tree);
            if (constructorName != null) {
                // make a single argument constructor  with yield as argument
                // if there is a singleton constructor with a str argument
                if (!type.isAbstractData() && !isUntypedNodeType(type)) {
                    throw RuntimeExceptionFactory.illegalArgument(tree, null, null, "Constructor ("
                            + constructorName + ") should match with abstract data type and not with " + type);
                }

                if (isUntypedNodeType(type)) {
                    return values.node(constructorName, values.string(yield));
                }

                Set<Type> conses = findConstructors(type, constructorName, 1, store);
                Iterator<Type> iter = conses.iterator();
                while (iter.hasNext()) {
                    try {
                        @SuppressWarnings("unused")
                        Type cons = iter.next();
                        ISourceLocation loc = TreeAdapter.getLocation(tree);
                        IConstructor ast = makeConstructor(type, constructorName, ctx, values.string(yield));
                        return ast.asAnnotatable().setAnnotation("location", loc);
                    } catch (Backtrack b) {
                        continue;
                    }
                }
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                        "Cannot find a constructor " + type));
            }
            if (type.isInteger()) {
                return values.integer(yield);
            }
            if (type.isReal()) {
                return values.real(yield);
            }
            if (type.isBool()) {
                if (yield.equals("true")) {
                    return values.bool(true);
                }
                if (yield.equals("false")) {
                    return values.bool(false);
                }
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                        "Bool type does not match with " + yield));
            }
            if (type.isString() || isUntypedNodeType(type)) {
                // NB: in "node space" all lexicals become strings
                return values.string(yield);
            }

            throw RuntimeExceptionFactory.illegalArgument(tree, null, null, "Missing lexical constructor");
        }

        //Set implementation added here by Jurgen at 19/07/12 16:45
        if (TreeAdapter.isList(tree)) {
            if (type.isList() || splicing || isUntypedNodeType(type)) {
                // if in node space, we also make a list; 
                // NB: this breaks type safety if the top-level tree
                // is itself a list.

                Type elementType = type;
                if (!splicing && !isUntypedNodeType(type)) {
                    elementType = type.getElementType();
                }
                IListWriter w = values.listWriter();
                for (IValue arg : TreeAdapter.getListASTArgs(tree)) {
                    w.append(implode(store, elementType, (IConstructor) arg, false, ctx));
                }
                return w.done();
            } else if (type.isSet()) {
                Type elementType = splicing ? type : type.getElementType();
                ISetWriter w = values.setWriter();
                for (IValue arg : TreeAdapter.getListASTArgs(tree)) {
                    w.insert(implode(store, elementType, (IConstructor) arg, false, ctx));
                }
                return w.done();
            } else {
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                        "Cannot match list with " + type));
            }
        }
        //Changes end here

        if (TreeAdapter.isOpt(tree) && type.isBool()) {
            IList args = TreeAdapter.getArgs(tree);
            if (args.isEmpty()) {
                return values.bool(false);
            }
            return values.bool(true);
        }

        if (TreeAdapter.isOpt(tree)) {
            if (!type.isList() && !isUntypedNodeType(type)) {
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                        "Optional should match with a list and not " + type));
            }
            Type elementType = isUntypedNodeType(type) ? type : type.getElementType();
            IListWriter w = values.listWriter();
            for (IValue arg : TreeAdapter.getASTArgs(tree)) {
                IValue implodedArg = implode(store, elementType, (IConstructor) arg, true, ctx);
                if (implodedArg instanceof IList) {
                    // splicing
                    for (IValue nextArg : (IList) implodedArg) {
                        w.append(nextArg);
                    }
                } else {
                    w.append(implodedArg);
                }
                // opts should have one argument (if any at all)
                break;
            }
            return w.done();
        }

        if (TreeAdapter.isAmb(tree)) {
            if (!type.isSet()) {
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                        "Ambiguous node should match with set and not " + type));
            }
            Type elementType = type.getElementType();
            ISetWriter w = values.setWriter();
            for (IValue arg : TreeAdapter.getAlternatives(tree)) {
                w.insert(implode(store, elementType, (IConstructor) arg, false, ctx));
            }
            return w.done();
        }

        if (ProductionAdapter.hasAttribute(TreeAdapter.getProduction(tree), Factory.Attribute_Bracket)) {
            return implode(store, type, (IConstructor) TreeAdapter.getASTArgs(tree).get(0), false, ctx);
        }

        if (TreeAdapter.isAppl(tree)) {
            IList args = TreeAdapter.getASTArgs(tree);

            int j = 0;
            IMapWriter cw = values.mapWriter();
            IListWriter aw = values.listWriter();
            for (IValue kid : TreeAdapter.getArgs(tree)) {
                if (TreeAdapter.isLayout((IConstructor) kid)) {
                    IList cts = extractComments((IConstructor) kid);
                    if (!cts.isEmpty()) {
                        cw.put(values.integer(j), cts);
                    }
                    j++;
                } else if (!TreeAdapter.isLiteral((IConstructor) kid)
                        && !TreeAdapter.isCILiteral((IConstructor) kid)
                        && !TreeAdapter.isEmpty((IConstructor) kid)) {
                    aw.append(kid);
                }
            }
            args = aw.done();
            int length = args.length();
            IMap comments = cw.done();

            //         // this could be optimized.
            //         i = 0;
            //         int length = args.length();
            //         while (i < length) {
            //            if (TreeAdapter.isEmpty((IConstructor) args.get(i))) {
            //               length--;
            //               args = args.delete(i);
            //            }
            //            else {
            //               i++;
            //            }
            //         }

            java.lang.String constructorName = unescapedConsName(tree);

            if (constructorName == null) {
                if (length == 1) {
                    // jump over injection
                    return implode(store, type, (IConstructor) args.get(0), splicing, ctx);
                }

                // make a tuple if we're in node space
                if (isUntypedNodeType(type)) {
                    return values.tuple(implodeArgs(store, type, args, ctx));
                }

                if (!type.isTuple()) {
                    throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null,
                            "Constructor does not match with " + type));
                }

                if (length != type.getArity()) {
                    throw new Backtrack(RuntimeExceptionFactory.arityMismatch(type.getArity(), length, null, null));
                }

                return values.tuple(implodeArgs(store, type, args, ctx));
            }

            // if in node space, make untyped nodes
            if (isUntypedNodeType(type)) {
                INode ast = values.node(constructorName, implodeArgs(store, type, args, ctx));
                return ast.asAnnotatable().setAnnotation("location", TreeAdapter.getLocation(tree)).asAnnotatable()
                        .setAnnotation("comments", comments);
            }

            // make a typed constructor
            if (!type.isAbstractData()) {
                throw new Backtrack(RuntimeExceptionFactory.illegalArgument(tree, null, null, "Constructor ("
                        + constructorName + ") should match with abstract data type and not with " + type));
            }

            Set<Type> conses = findConstructors(type, constructorName, length, store);
            Iterator<Type> iter = conses.iterator();
            while (iter.hasNext()) {
                try {
                    Type cons = iter.next();
                    ISourceLocation loc = TreeAdapter.getLocation(tree);
                    IValue[] implodedArgs = implodeArgs(store, cons, args, ctx);
                    IConstructor ast = makeConstructor(type, constructorName, ctx, implodedArgs);
                    return ast.asAnnotatable().setAnnotation("location", loc).asAnnotatable()
                            .setAnnotation("comments", comments);
                } catch (Backtrack b) {
                    continue;
                }
            }

        }

        throw new Backtrack(
                RuntimeExceptionFactory.illegalArgument(tree, null, null, "Cannot find a constructor for " + type));
    }

    private IList extractComments(IConstructor layout) {
        final IListWriter comments = values.listWriter();
        TreeVisitor<RuntimeException> visitor = new TreeVisitor<RuntimeException>() {

            @Override
            public IConstructor visitTreeAppl(IConstructor arg) {
                if (TreeAdapter.isComment(arg)) {
                    comments.append(values.string(TreeAdapter.yield(arg)));
                } else {
                    for (IValue t : TreeAdapter.getArgs(arg)) {
                        t.accept(this);
                    }
                }
                return arg;
            }

            @Override
            public IConstructor visitTreeAmb(IConstructor arg) {
                return arg;
            }

            @Override
            public IConstructor visitTreeChar(IConstructor arg) {
                return arg;
            }

            @Override
            public IConstructor visitTreeCycle(IConstructor arg) {
                return arg;
            }

        };

        layout.accept(visitor);
        return comments.done();
    }

    private boolean isUntypedNodeType(Type type) {
        return (type.isNode() && !type.isConstructor() && !type.isAbstractData()) || type.isTop();
    }

    private static IConstructor checkPreconditions(IValue start, Type reified) {
        if (!(reified instanceof ReifiedType)) {
            throw RuntimeExceptionFactory.illegalArgument(start, null, null,
                    "A reified type is required instead of " + reified);
        }

        Type nt = reified.getTypeParameters().getFieldType(0);

        if (!(nt instanceof NonTerminalType)) {
            throw RuntimeExceptionFactory.illegalArgument(start, null, null,
                    "A non-terminal type is required instead of  " + nt);
        }

        IConstructor symbol = ((NonTerminalType) nt).getSymbol();

        return symbol;
    }

    /*
     * Rational
     */

    public IValue numerator(IRational n) {
        return n.numerator();
    }

    public IValue denominator(IRational n) {
        return n.denominator();
    }

    public IValue remainder(IRational n) {
        return n.remainder();
    }

    /*
     * Relation
     */

    /*
     * Set
     */
    public IValue getOneFrom(ISet st)
    // @doc{getOneFrom -- pick a random element from a set}
    {
        int sz = st.size();

        if (sz == 0) {
            throw RuntimeExceptionFactory.emptySet(null, null);
        }
        int k = random.nextInt(sz);
        int i = 0;

        for (IValue v : st) {
            if (i == k) {
                return v;
            }
            i++;
        }

        throw RuntimeExceptionFactory.emptySet(null, null);
    }

    public IValue isEmpty(ISet st)
    //@doc{isEmpty -- is set empty?}
    {
        return values.bool(st.size() == 0);
    }

    public IValue size(ISet st)
    // @doc{size -- number of elements in a set}
    {
        return values.integer(st.size());
    }

    public IMap index(ISet s) {
        Map<IValue, ISetWriter> map = new HashMap<IValue, ISetWriter>(s.size());

        for (IValue t : s) {
            ITuple tuple = (ITuple) t;
            IValue key = tuple.get(0);
            IValue value = tuple.get(1);

            ISetWriter writer = map.get(key);
            if (writer == null) {
                writer = values.setWriter();
                map.put(key, writer);
            }
            writer.insert(value);
        }

        IMapWriter mapWriter = values.mapWriter();
        for (IValue key : map.keySet()) {
            mapWriter.put(key, map.get(key).done());
        }

        return mapWriter.done();
    }

    public IMap index(IList s) {
        Map<IValue, ISetWriter> map = new HashMap<IValue, ISetWriter>(s.length());

        for (IValue t : s) {
            ITuple tuple = (ITuple) t;
            IValue key = tuple.get(0);
            IValue value = tuple.get(1);

            ISetWriter writer = map.get(key);
            if (writer == null) {
                writer = values.setWriter();
                map.put(key, writer);
            }
            writer.insert(value);
        }

        IMapWriter mapWriter = values.mapWriter();
        for (IValue key : map.keySet()) {
            mapWriter.put(key, map.get(key).done());
        }

        return mapWriter.done();
    }

    public IValue takeOneFrom(ISet st)
    // @doc{takeOneFrom -- remove an arbitrary element from a set,
    //      returns the element and the modified set}
    {
        int n = st.size();

        if (n > 0) {
            int i = 0;
            int k = random.nextInt(n);
            IValue pick = null;
            ISetWriter w = values.setWriter();

            for (IValue v : st) {
                if (i == k) {
                    pick = v;
                } else {
                    w.insert(v);
                }
                i++;
            }
            return values.tuple(pick, w.done());
        }
        throw RuntimeExceptionFactory.emptySet(null, null);
    }

    public IValue toList(ISet st)
    // @doc{toList -- convert a set to a list}
    {
        IListWriter w = values.listWriter();

        for (IValue v : st) {
            w.insert(v);
        }

        return w.done();
    }

    public IValue toMap(ISet st)
    // @doc{toMap -- convert a set of tuples to a map; value in old map is associated with a set of keys in old map}
    {
        Map<IValueWrap, ISetWriter> hm = new HashMap<IValueWrap, ISetWriter>();

        for (IValue v : st) {
            ITuple t = (ITuple) v;
            IValueWrap key = new IValueWrap(t.get(0));
            IValue val = t.get(1);
            ISetWriter wValSet = hm.get(key);
            if (wValSet == null) {
                wValSet = values.setWriter();
                hm.put(key, wValSet);
            }
            wValSet.insert(val);
        }

        IMapWriter w = values.mapWriter();
        for (IValueWrap v : hm.keySet()) {
            w.put(v.getValue(), hm.get(v).done());
        }
        return w.done();
    }

    public IValue toMapUnique(ISet st)
    // @doc{toMapUnique -- convert a set of tuples to a map; keys are unique}
    {
        IMapWriter w = values.mapWriter();
        HashSet<IValueWrap> seenKeys = new HashSet<IValueWrap>();

        for (IValue v : st) {
            ITuple t = (ITuple) v;
            IValueWrap key = new IValueWrap(t.get(0));
            IValue val = t.get(1);
            if (seenKeys.contains(key)) {
                throw RuntimeExceptionFactory.MultipleKey(key.getValue(), null, null);
            }
            seenKeys.add(key);
            w.put(key.getValue(), val);
        }
        return w.done();
    }

    public IValue toString(ISet st)
    // @doc{toString -- convert a set to a string}
    {
        return values.string(st.toString());
    }

    /*
     * String
     */

    public IBool isValidCharacter(IInteger i) {
        return values.bool(Character.isValidCodePoint(i.intValue()));
    }

    public IValue stringChar(IInteger i) {
        int intValue = i.intValue();
        if (Character.isValidCodePoint(intValue)) {
            return values.string(intValue);
        } else {
            throw RuntimeExceptionFactory.illegalArgument(i, null, null);
        }
    }

    public IValue stringChars(IList lst) {
        int[] chars = new int[lst.length()];

        for (int i = 0; i < lst.length(); i++) {
            chars[i] = ((IInteger) lst.get(i)).intValue();
            if (!Character.isValidCodePoint(chars[i])) {
                throw RuntimeExceptionFactory.illegalArgument(values.integer(chars[i]), null, null);
            }
        }

        return values.string(chars);
    }

    public IValue charAt(IString s, IInteger i) throws IndexOutOfBoundsException
    //@doc{charAt -- return the character at position i in string s.}
    {
        try {
            return values.integer(s.charAt(i.intValue()));
        } catch (IndexOutOfBoundsException e) {
            throw RuntimeExceptionFactory.indexOutOfBounds(i, null, null);
        }
    }

    public IValue endsWith(IString s, IString suffix)
    //@doc{endWith -- returns true if string s ends with given string suffix.}
    {
        return values.bool(s.getValue().endsWith(suffix.getValue()));
    }

    public IString trim(IString s) {
        return values.string(s.getValue().trim());
    }

    public IString squeeze(IString src, IString charSet) {
        //@{http://commons.apache.org/lang/api-2.6/index.html?org/apache/commons/lang/text/package-summary.html}
        String s = CharSetUtils.squeeze(src.getValue(), charSet.getValue());
        return values.string(s);
    }

    public IString capitalize(IString src) {
        return values.string(WordUtils.capitalize(src.getValue()));
    }

    public IString uncapitalize(IString src) {
        return values.string(WordUtils.uncapitalize(src.getValue()));
    }

    public IList split(IString sep, IString src) {
        String[] lst = src.getValue().split(Pattern.quote(sep.getValue()));
        IListWriter lw = values.listWriter();
        for (String s : lst) {
            lw.append(values.string(s));
        }
        return lw.done();
    }

    public IString wrap(IString src, IInteger wrapLength) {
        String s = WordUtils.wrap(src.getValue(), wrapLength.intValue());
        return values.string(s);
    }

    public IValue format(IString s, IString dir, IInteger n, IString pad)
    //@doc{format -- return string of length n, with s placed according to dir (left/center/right) and padded with pad}
    {
        StringBuffer res = new StringBuffer();
        int sLen = s.getValue().length();
        int nVal = n.intValue();
        if (sLen > nVal) {
            return s;
        }
        int padLen = pad.getValue().length();
        java.lang.String dirVal = dir.getValue();
        int start;

        if (dirVal.equals("right"))
            start = nVal - sLen;
        else if (dirVal.equals("center"))
            start = (nVal - sLen) / 2;
        else
            start = 0;

        int i = 0;
        while (i < start) {
            if (i + padLen < start) {
                res.append(pad.getValue());
                i += padLen;
            } else {
                res.append(pad.getValue().substring(0, start - i));
                i += start - i;
            }
        }
        res.append(s.getValue());
        i = start + sLen;
        while (i < nVal) {
            if (i + padLen < nVal) {
                res.append(pad.getValue());
                i += padLen;
            } else {
                res.append(pad.getValue().substring(0, nVal - i));
                i += nVal - i;
            }
        }
        return values.string(res.toString());
    }

    public IValue isEmpty(IString s)
    //@doc{isEmpty -- is string empty?}
    {
        return values.bool(s.getValue().length() == 0);
    }

    public IValue reverse(IString s)
    //@doc{reverse -- return string with all characters in reverse order.}
    {
        return s.reverse();
    }

    public IValue size(IString s)
    //@doc{size -- return the length of string s.}
    {
        return values.integer(s.length());
    }

    public IValue startsWith(IString s, IString prefix)
    //@doc{startsWith -- return true if string s starts with the string prefix.}
    {
        return values.bool(s.getValue().startsWith(prefix.getValue()));
    }

    public IValue substring(IString s, IInteger begin) {
        try {
            return s.substring(begin.intValue());
        } catch (IndexOutOfBoundsException e) {
            throw RuntimeExceptionFactory.indexOutOfBounds(begin, null, null);
        }
    }

    public IValue substring(IString s, IInteger begin, IInteger end) {
        try {
            return s.substring(begin.intValue(), end.intValue());
        } catch (IndexOutOfBoundsException e) {
            int bval = begin.intValue();
            IInteger culprit = (bval < 0 || bval >= s.length()) ? begin : end;
            throw RuntimeExceptionFactory.indexOutOfBounds(culprit, null, null);
        }

    }

    public IValue toInt(IString s)
    //@doc{toInt -- convert a string s to integer}
    {
        try {
            java.lang.String sval = s.getValue();
            boolean isNegative = false;
            int radix = 10;

            if (sval.equals("0")) {
                return values.integer(0);
            }

            if (sval.startsWith("-")) {
                isNegative = true;
                sval = sval.substring(1);
            }
            if (sval.startsWith("0x") || sval.startsWith("0X")) {
                radix = 16;
                sval = sval.substring(2);
            } else if (sval.startsWith("0")) {
                radix = 8;
                sval = sval.substring(1);
            }
            BigInteger bi = new BigInteger(isNegative ? "-" + sval : sval, radix);
            return values.integer(bi.toString());
        } catch (NumberFormatException e) {
            throw RuntimeExceptionFactory.illegalArgument(null, null);
        }
    }

    public IValue toInt(IString s, IInteger r) {
        try {
            java.lang.String sval = s.getValue();
            boolean isNegative = false;
            int radix = r.intValue();

            if (sval.equals("0")) {
                return values.integer(0);
            }

            if (sval.startsWith("-")) {
                isNegative = true;
                sval = sval.substring(1);
            }
            BigInteger bi = new BigInteger(isNegative ? "-" + sval : sval, radix);
            return values.integer(bi.toString());
        } catch (NumberFormatException e) {
            throw RuntimeExceptionFactory.illegalArgument(null, null);
        }
    }

    public IValue toReal(IString s)
    //@doc{toReal -- convert a string s to a real}
    {
        try {
            return values.real(s.getValue());
        } catch (NumberFormatException e) {
            throw RuntimeExceptionFactory.illegalArgument(null, null);
        }
    }

    public IValue toReal(IRational s)
    //@doc{toReal -- convert a string s to a real}
    {
        return s.toReal();
    }

    public IValue toLowerCase(IString s)
    //@doc{toLowerCase -- convert all characters in string s to lowercase.}
    {
        return values.string(s.getValue().toLowerCase());
    }

    public IValue toUpperCase(IString s)
    //@doc{toUpperCase -- convert all characters in string s to uppercase.}
    {
        return values.string(s.getValue().toUpperCase());
    }

    private boolean match(char[] subject, int i, char[] pattern) {
        if (i + pattern.length > subject.length)
            return false;
        for (int k = 0; k < pattern.length; k++) {
            if (subject[i] != pattern[k])
                return false;
            i++;
        }
        return true;
    }

    public IValue replaceAll(IString str, IString find, IString replacement) {
        StringBuilder b = new StringBuilder(str.getValue().length() * 2);
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();
        char[] replChars = replacement.getValue().toCharArray();

        int i = 0;
        boolean matched = false;
        while (i < input.length) {
            if (match(input, i, findChars)) {
                matched = true;
                b.append(replChars);
                i += findChars.length;
            } else {
                b.append(input[i]);
                i++;
            }
        }
        return (!matched) ? str : values.string(b.toString());
    }

    public IValue replaceFirst(IString str, IString find, IString replacement) {
        StringBuilder b = new StringBuilder(str.getValue().length() * 2);
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();
        char[] replChars = replacement.getValue().toCharArray();

        int i = 0;
        boolean matched = false;
        while (i < input.length) {
            if (!matched && match(input, i, findChars)) {
                matched = true;
                b.append(replChars);
                i += findChars.length;

            } else {
                b.append(input[i]);
                i++;
            }
        }
        return (!matched) ? str : values.string(b.toString());
    }

    public IValue replaceLast(IString str, IString find, IString replacement) {
        StringBuilder b = new StringBuilder(str.getValue().length() * 2);
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();
        char[] replChars = replacement.getValue().toCharArray();

        int i = input.length - findChars.length;
        while (i >= 0) {
            if (match(input, i, findChars)) {
                for (int j = 0; j < i; j++)
                    b.append(input[j]);
                b.append(replChars);
                for (int j = i + findChars.length; j < input.length; j++)
                    b.append(input[j]);
                return values.string(b.toString());
            }
            i--;
        }
        return str;
    }

    public IValue escape(IString str, IMap substitutions) {
        StringBuilder b = new StringBuilder(str.getValue().length() * 2);
        char[] input = str.getValue().toCharArray();

        for (char c : input) {
            IString sub = (IString) substitutions.get(values.string(Character.toString(c)));
            if (sub != null) {
                b.append(sub.getValue());
            } else {
                b.append(c);
            }
        }
        return values.string(b.toString());
    }

    public IValue contains(IString str, IString find) {
        return values.bool(str.getValue().indexOf(find.getValue()) >= 0);
    }

    public IValue findAll(IString str, IString find) {
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();
        IListWriter w = values.listWriter();

        for (int i = 0; i <= input.length - findChars.length; i++) {
            if (match(input, i, findChars)) {
                w.append(values.integer(i));
            }
        }
        return w.done();
    }

    public IValue findFirst(IString str, IString find) {
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();

        for (int i = 0; i <= input.length - findChars.length; i++) {
            if (match(input, i, findChars)) {
                return values.integer(i);
            }
        }
        return values.integer(-1);
    }

    public IValue findLast(IString str, IString find) {
        char[] input = str.getValue().toCharArray();
        char[] findChars = find.getValue().toCharArray();

        for (int i = input.length - findChars.length; i >= 0; i--) {
            if (match(input, i, findChars)) {
                return values.integer(i);
            }
        }
        return values.integer(-1);
    }

    /*
     *  !!EXPERIMENTAL!!
     * Tuple
     */

    public IList fieldsOf(IValue v) {
        if (!v.getType().isTuple())
            throw RuntimeExceptionFactory.illegalArgument(v, null, null, "argument of type tuple is required");
        ITuple tp = (ITuple) v;
        Type tt = tp.getType();
        int a = tt.getArity();
        IListWriter w = values.listWriter();
        for (int i = 0; i < a; i++) {
            String fname = tt.getFieldName(i);
            if (fname == null)
                fname = "";
            w.append(values.string(fname));
        }
        return w.done();
    }

    /*
     * ValueIO
     */

    public IValue readBinaryValueFile(IValue type, ISourceLocation loc, IEvaluatorContext ctx) {

        //      TypeStore store = ctx.getCurrentEnvt().getStore();
        TypeStore store = new TypeStore();
        ModuleEnvironment pt = ctx.getHeap().getModule("ParseTree");
        if (pt != null) {
            store.importStore(pt.getStore());
        }
        Type start = tr.valueToType((IConstructor) type, store);
        loc = ctx.getHeap().resolveSourceLocation(loc);

        InputStream in = null;
        try {
            in = new BufferedInputStream(ctx.getResolverRegistry().getInputStream(loc.getURI()));
            return new BinaryValueReader().read(values, store, start, in);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } catch (Exception e) {
            e.printStackTrace();
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
                }
            }
        }
    }

    public IValue readTextValueFile(IValue type, ISourceLocation loc, IEvaluatorContext ctx) {
        loc = ctx.getHeap().resolveSourceLocation(loc);

        TypeStore store = ctx.getCurrentEnvt().getStore();
        Type start = tr.valueToType((IConstructor) type, store);

        InputStream in = null;
        try {
            in = new BufferedInputStream(ctx.getResolverRegistry().getInputStream(loc.getURI()));
            return new StandardTextReader().read(values, store, start, new InputStreamReader(in, "UTF8"));
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
                }
            }
        }
    }

    public IValue readTextValueString(IValue type, IString input, IEvaluatorContext ctx) {
        //      TypeStore store = ctx.getCurrentEnvt().getStore();
        TypeStore store = new TypeStore();
        ModuleEnvironment pt = ctx.getHeap().getModule("ParseTree");
        if (pt != null) {
            store.importStore(pt.getStore());
        }
        Type start = tr.valueToType((IConstructor) type, store);

        StringReader in = new StringReader(input.getValue());
        try {
            return new StandardTextReader().read(values, store, start, in);
        } catch (FactTypeUseException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        }
    }

    public void writeBinaryValueFile(ISourceLocation loc, IValue value, IEvaluatorContext ctx) {
        loc = ctx.getHeap().resolveSourceLocation(loc);

        OutputStream out = null;
        try {
            out = ctx.getResolverRegistry().getOutputStream(loc.getURI(), false);
            new BinaryValueWriter().write(value, out);
        } catch (IOException ioex) {
            throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
                }
            }
        }
    }

    public void writeTextValueFile(ISourceLocation loc, IValue value, IEvaluatorContext ctx) {
        loc = ctx.getHeap().resolveSourceLocation(loc);

        OutputStream out = null;
        try {
            out = ctx.getResolverRegistry().getOutputStream(loc.getURI(), false);
            new StandardTextWriter().write(value, new OutputStreamWriter(out, "UTF8"));
        } catch (IOException e) {
            throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null);
        } finally {
            if (out != null) {
                try {
                    out.flush();
                    out.close();
                } catch (IOException ioex) {
                    throw RuntimeExceptionFactory.io(values.string(ioex.getMessage()), null, null);
                }
            }
        }
    }

    public IBool rexpMatch(IString s, IString re) {
        if (Pattern.matches(re.getValue(), s.getValue()))
            return values.bool(true);
        else
            return values.bool(false);
    }

    public IList getTraversalContext(IEvaluatorContext ctx) {
        return ctx.getEvaluator().__getCurrentTraversalEvaluator().getContext();
    }
}

// Utilities used by Graph
//TODO: Why is this code in the library? This should be done in pure Rascal.

class Distance {
    public int intval;

    Distance(int n) {
        intval = n;
    }
}

class NodeComparator implements Comparator<IValue> {
    private final HashMap<IValue, Distance> distance;

    NodeComparator(HashMap<IValue, Distance> distance) {
        this.distance = distance;
    }

    public int compare(IValue arg0, IValue arg1) {
        int d0 = distance.get(arg0).intval;
        int d1 = distance.get(arg1).intval;

        return d0 < d1 ? -1 : ((d0 == d1) ? 0 : 1);
    }
}