Disabled external gits

This commit is contained in:
2022-04-07 18:43:21 +02:00
parent 182267a8cb
commit 88cb3426ad
1067 changed files with 102374 additions and 6 deletions

View File

@@ -0,0 +1,44 @@
#+OPTIONS: toc:nil author:nil
#+TITLE: The L₃ compiler
* Introduction
This directory contains the source code of the L₃ compiler, written in Scala. All interactions with the compiler should be done through [[http://www.scala-sbt.org/][sbt]], a Scala build tool.
~Sbt~ can either be run in interactive mode, by simply typing ~sbt~ and then entering commands at the prompt, or in batch mode. The following sections use batch mode for illustration, but in practice interactive mode is often to be preferred as it avoids repeated startup of ~sbt~ itself.
* Compiling
To compile the compiler, use the ~compile~ command:
: $ sbt compile
(the dollar sign ~$~ represents the shell prompt and should not be typed).
* Testing
To test the compiler (and compile it beforehand, if necessary), use the ~test~ command:
: $ sbt test
* Running
To run the compiler (and compile it beforehand, if necessary), use the ~run~ command, followed by arguments for the compiler, e.g.:
: $ sbt 'run ../library/lib.l3m ../examples/queens.l3'
The compiler accepts a list of files to compile as arguments. These files can have one of the following extensions:
- ~.l3~ :: A normal source file, containing L₃ code.
- ~.l3m~ :: A module file, containing a list of other files, which must also be either source files (with a ~.l3~ extension) or other module files (with a ~.l3m~ extension).
Modules are expanded recursively, until only ~.l3~ files remain. Then, duplicate file names are removed, with only the first occurrence kept. Finally, this list of files is fed to the compiler.
As an example, assume that the file ~lib.l3m~ references ~characters.l3m~ and ~integers.l3m~, and that ~characters.l3m~ references ~characters.l3~ while ~integers.l3m~ references both ~characters.l3m~ and ~integers.l3~. Then, a command line consisting of ~lib.l3m~ and ~helloworld.l3~ is expanded as follows:
1. ~lib.l3m~ ~helloworld.l3~ (original command line),
2. ~characters.l3m~ ~integers.l3m~ ~helloworld.l3~ (expansion of ~lib.l3m~),
3. ~characters.l3~ ~characters.l3m~ ~integers.l3~ ~helloworld.l3~ (expansion of ~characters.l3m~ and ~integers.l3m~),
4. ~characters.l3~ ~characters.l3~ ~integers.l3~ ~helloworld.l3~ (expansion of the second ~characters.l3m~),
5. ~characters.l3~ ~integers.l3~ ~helloworld.l3~ (removal of duplicates).

View File

@@ -0,0 +1,661 @@
#!/usr/bin/env bash
#
# A more capable sbt runner, coincidentally also called sbt.
# Author: Paul Phillips <paulp@improving.org>
# https://github.com/paulp/sbt-extras
#
# Generated from http://www.opensource.org/licenses/bsd-license.php
# Copyright (c) 2011, Paul Phillips. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set -o pipefail
declare -r sbt_release_version="1.4.7"
declare -r sbt_unreleased_version="1.4.7"
declare -r latest_213="2.13.4"
declare -r latest_212="2.12.12"
declare -r latest_211="2.11.12"
declare -r latest_210="2.10.7"
declare -r latest_29="2.9.3"
declare -r latest_28="2.8.2"
declare -r buildProps="project/build.properties"
declare -r sbt_launch_ivy_release_repo="https://repo.typesafe.com/typesafe/ivy-releases"
declare -r sbt_launch_ivy_snapshot_repo="https://repo.scala-sbt.org/scalasbt/ivy-snapshots"
declare -r sbt_launch_mvn_release_repo="https://repo1.maven.org/maven2"
declare -r sbt_launch_mvn_snapshot_repo="https://repo.scala-sbt.org/scalasbt/maven-snapshots"
declare -r default_jvm_opts_common="-Xms512m -Xss2m -XX:MaxInlineLevel=18"
declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy -Dsbt.coursier.home=project/.coursier"
declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new
declare sbt_explicit_version
declare verbose noshare batch trace_level
declare java_cmd="java"
declare sbt_launch_dir="$HOME/.sbt/launchers"
declare sbt_launch_repo
# pull -J and -D options to give to java.
declare -a java_args scalac_args sbt_commands residual_args
# args to jvm/sbt via files or environment variables
declare -a extra_jvm_opts extra_sbt_opts
echoerr() { echo >&2 "$@"; }
vlog() { [[ -n "$verbose" ]] && echoerr "$@"; }
die() {
echo "Aborting: $*"
exit 1
}
setTrapExit() {
# save stty and trap exit, to ensure echo is re-enabled if we are interrupted.
SBT_STTY="$(stty -g 2>/dev/null)"
export SBT_STTY
# restore stty settings (echo in particular)
onSbtRunnerExit() {
[ -t 0 ] || return
vlog ""
vlog "restoring stty: $SBT_STTY"
stty "$SBT_STTY"
}
vlog "saving stty: $SBT_STTY"
trap onSbtRunnerExit EXIT
}
# this seems to cover the bases on OSX, and someone will
# have to tell me about the others.
get_script_path() {
local path="$1"
[[ -L "$path" ]] || {
echo "$path"
return
}
local -r target="$(readlink "$path")"
if [[ "${target:0:1}" == "/" ]]; then
echo "$target"
else
echo "${path%/*}/$target"
fi
}
script_path="$(get_script_path "${BASH_SOURCE[0]}")"
declare -r script_path
script_name="${script_path##*/}"
declare -r script_name
init_default_option_file() {
local overriding_var="${!1}"
local default_file="$2"
if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then
local envvar_file="${BASH_REMATCH[1]}"
if [[ -r "$envvar_file" ]]; then
default_file="$envvar_file"
fi
fi
echo "$default_file"
}
sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)"
sbtx_opts_file="$(init_default_option_file SBTX_OPTS .sbtxopts)"
jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)"
build_props_sbt() {
[[ -r "$buildProps" ]] &&
grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }'
}
set_sbt_version() {
sbt_version="${sbt_explicit_version:-$(build_props_sbt)}"
[[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version
export sbt_version
}
url_base() {
local version="$1"
case "$version" in
0.7.*) echo "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/simple-build-tool" ;;
0.10.*) echo "$sbt_launch_ivy_release_repo" ;;
0.11.[12]) echo "$sbt_launch_ivy_release_repo" ;;
0.*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss"
echo "$sbt_launch_ivy_snapshot_repo" ;;
0.*) echo "$sbt_launch_ivy_release_repo" ;;
*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmddThhMMss"
echo "$sbt_launch_mvn_snapshot_repo" ;;
*) echo "$sbt_launch_mvn_release_repo" ;;
esac
}
make_url() {
local version="$1"
local base="${sbt_launch_repo:-$(url_base "$version")}"
case "$version" in
0.7.*) echo "$base/sbt-launch-0.7.7.jar" ;;
0.10.*) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
0.11.[12]) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
0.*) echo "$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;;
*) echo "$base/org/scala-sbt/sbt-launch/$version/sbt-launch.jar" ;;
esac
}
addJava() {
vlog "[addJava] arg = '$1'"
java_args+=("$1")
}
addSbt() {
vlog "[addSbt] arg = '$1'"
sbt_commands+=("$1")
}
addScalac() {
vlog "[addScalac] arg = '$1'"
scalac_args+=("$1")
}
addResidual() {
vlog "[residual] arg = '$1'"
residual_args+=("$1")
}
addResolver() { addSbt "set resolvers += $1"; }
addDebugger() { addJava "-Xdebug" && addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"; }
setThisBuild() {
vlog "[addBuild] args = '$*'"
local key="$1" && shift
addSbt "set $key in ThisBuild := $*"
}
setScalaVersion() {
[[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")'
addSbt "++ $1"
}
setJavaHome() {
java_cmd="$1/bin/java"
setThisBuild javaHome "_root_.scala.Some(file(\"$1\"))"
export JAVA_HOME="$1"
export JDK_HOME="$1"
export PATH="$JAVA_HOME/bin:$PATH"
}
getJavaVersion() {
local -r str=$("$1" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d '"')
# java -version on java8 says 1.8.x
# but on 9 and 10 it's 9.x.y and 10.x.y.
if [[ "$str" =~ ^1\.([0-9]+)(\..*)?$ ]]; then
echo "${BASH_REMATCH[1]}"
elif [[ "$str" =~ ^([0-9]+)(\..*)?$ ]]; then
echo "${BASH_REMATCH[1]}"
elif [[ -n "$str" ]]; then
echoerr "Can't parse java version from: $str"
fi
}
checkJava() {
# Warn if there is a Java version mismatch between PATH and JAVA_HOME/JDK_HOME
[[ -n "$JAVA_HOME" && -e "$JAVA_HOME/bin/java" ]] && java="$JAVA_HOME/bin/java"
[[ -n "$JDK_HOME" && -e "$JDK_HOME/lib/tools.jar" ]] && java="$JDK_HOME/bin/java"
if [[ -n "$java" ]]; then
pathJavaVersion=$(getJavaVersion java)
homeJavaVersion=$(getJavaVersion "$java")
if [[ "$pathJavaVersion" != "$homeJavaVersion" ]]; then
echoerr "Warning: Java version mismatch between PATH and JAVA_HOME/JDK_HOME, sbt will use the one in PATH"
echoerr " Either: fix your PATH, remove JAVA_HOME/JDK_HOME or use -java-home"
echoerr " java version from PATH: $pathJavaVersion"
echoerr " java version from JAVA_HOME/JDK_HOME: $homeJavaVersion"
fi
fi
}
java_version() {
local -r version=$(getJavaVersion "$java_cmd")
vlog "Detected Java version: $version"
echo "$version"
}
is_apple_silicon() { [[ "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ]]; }
# MaxPermSize critical on pre-8 JVMs but incurs noisy warning on 8+
default_jvm_opts() {
local -r v="$(java_version)"
if [[ $v -ge 10 ]]; then
if is_apple_silicon; then
# As of Dec 2020, JVM for Apple Silicon (M1) doesn't support JVMCI
echo "$default_jvm_opts_common"
else
echo "$default_jvm_opts_common -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler"
fi
elif [[ $v -ge 8 ]]; then
echo "$default_jvm_opts_common"
else
echo "-XX:MaxPermSize=384m $default_jvm_opts_common"
fi
}
execRunner() {
# print the arguments one to a line, quoting any containing spaces
vlog "# Executing command line:" && {
for arg; do
if [[ -n "$arg" ]]; then
if printf "%s\n" "$arg" | grep -q ' '; then
printf >&2 "\"%s\"\n" "$arg"
else
printf >&2 "%s\n" "$arg"
fi
fi
done
vlog ""
}
setTrapExit
if [[ -n "$batch" ]]; then
"$@" </dev/null
else
"$@"
fi
}
jar_url() { make_url "$1"; }
is_cygwin() { [[ "$(uname -a)" == "CYGWIN"* ]]; }
jar_file() {
is_cygwin &&
cygpath -w "$sbt_launch_dir/$1/sbt-launch.jar" ||
echo "$sbt_launch_dir/$1/sbt-launch.jar"
}
download_url() {
local url="$1"
local jar="$2"
mkdir -p "${jar%/*}" && {
if command -v curl >/dev/null 2>&1; then
curl --fail --silent --location "$url" --output "$jar"
elif command -v wget >/dev/null 2>&1; then
wget -q -O "$jar" "$url"
fi
} && [[ -r "$jar" ]]
}
acquire_sbt_jar() {
{
sbt_jar="$(jar_file "$sbt_version")"
[[ -r "$sbt_jar" ]]
} || {
sbt_jar="$HOME/.ivy2/local/org.scala-sbt/sbt-launch/$sbt_version/jars/sbt-launch.jar"
[[ -r "$sbt_jar" ]]
} || {
sbt_jar="$(jar_file "$sbt_version")"
jar_url="$(make_url "$sbt_version")"
echoerr "Downloading sbt launcher for ${sbt_version}:"
echoerr " From ${jar_url}"
echoerr " To ${sbt_jar}"
download_url "${jar_url}" "${sbt_jar}"
case "${sbt_version}" in
0.*)
vlog "SBT versions < 1.0 do not have published MD5 checksums, skipping check"
echo ""
;;
*) verify_sbt_jar "${sbt_jar}" ;;
esac
}
}
verify_sbt_jar() {
local jar="${1}"
local md5="${jar}.md5"
md5url="$(make_url "${sbt_version}").md5"
echoerr "Downloading sbt launcher ${sbt_version} md5 hash:"
echoerr " From ${md5url}"
echoerr " To ${md5}"
download_url "${md5url}" "${md5}" >/dev/null 2>&1
if command -v md5sum >/dev/null 2>&1; then
if echo "$(cat "${md5}") ${jar}" | md5sum -c -; then
rm -rf "${md5}"
return 0
else
echoerr "Checksum does not match"
return 1
fi
elif command -v md5 >/dev/null 2>&1; then
if [ "$(md5 -q "${jar}")" == "$(cat "${md5}")" ]; then
rm -rf "${md5}"
return 0
else
echoerr "Checksum does not match"
return 1
fi
elif command -v openssl >/dev/null 2>&1; then
if [ "$(openssl md5 -r "${jar}" | awk '{print $1}')" == "$(cat "${md5}")" ]; then
rm -rf "${md5}"
return 0
else
echoerr "Checksum does not match"
return 1
fi
else
echoerr "Could not find an MD5 command"
return 1
fi
}
usage() {
set_sbt_version
cat <<EOM
Usage: $script_name [options]
Note that options which are passed along to sbt begin with -- whereas
options to this runner use a single dash. Any sbt command can be scheduled
to run first by prefixing the command with --, so --warn, --error and so on
are not special.
-h | -help print this message
-v verbose operation (this runner is chattier)
-d, -w, -q aliases for --debug, --warn, --error (q means quiet)
-x debug this script
-trace <level> display stack traces with a max of <level> frames (default: -1, traces suppressed)
-debug-inc enable debugging log for the incremental compiler
-no-colors disable ANSI color codes
-sbt-create start sbt even if current directory contains no sbt project
-sbt-dir <path> path to global settings/plugins directory (default: ~/.sbt/<version>)
-sbt-boot <path> path to shared boot directory (default: ~/.sbt/boot in 0.11+)
-ivy <path> path to local Ivy repository (default: ~/.ivy2)
-no-share use all local caches; no sharing
-offline put sbt in offline mode
-jvm-debug <port> Turn on JVM debugging, open at the given port.
-batch Disable interactive mode
-prompt <expr> Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted
-script <file> Run the specified file as a scala script
# sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version)
-sbt-version <version> use the specified version of sbt (default: $sbt_release_version)
-sbt-force-latest force the use of the latest release of sbt: $sbt_release_version
-sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version
-sbt-jar <path> use the specified jar as the sbt launcher
-sbt-launch-dir <path> directory to hold sbt launchers (default: $sbt_launch_dir)
-sbt-launch-repo <url> repo url for downloading sbt launcher jar (default: $(url_base "$sbt_version"))
# scala version (default: as chosen by sbt)
-28 use $latest_28
-29 use $latest_29
-210 use $latest_210
-211 use $latest_211
-212 use $latest_212
-213 use $latest_213
-scala-home <path> use the scala build at the specified directory
-scala-version <version> use the specified version of scala
-binary-version <version> use the specified scala version when searching for dependencies
# java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
-java-home <path> alternate JAVA_HOME
# passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution
# The default set is used if JVM_OPTS is unset and no -jvm-opts file is found
<default> $(default_jvm_opts)
JVM_OPTS environment variable holding either the jvm args directly, or
the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts')
Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument.
-jvm-opts <path> file containing jvm args (if not given, .jvmopts in project root is used if present)
-Dkey=val pass -Dkey=val directly to the jvm
-J-X pass option -X directly to the jvm (-J is stripped)
# passing options to sbt, OR to this runner
SBT_OPTS environment variable holding either the sbt args directly, or
the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts')
Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument.
-sbt-opts <path> file containing sbt args (if not given, .sbtopts in project root is used if present)
-S-X add -X to sbt's scalacOptions (-S is stripped)
# passing options exclusively to this runner
SBTX_OPTS environment variable holding either the sbt-extras args directly, or
the reference to a file containing sbt-extras args if given path is prepended by '@' (e.g. '@/etc/sbtxopts')
Note: "@"-file is overridden by local '.sbtxopts' or '-sbtx-opts' argument.
-sbtx-opts <path> file containing sbt-extras args (if not given, .sbtxopts in project root is used if present)
EOM
exit 0
}
process_args() {
require_arg() {
local type="$1"
local opt="$2"
local arg="$3"
if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
die "$opt requires <$type> argument"
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h | -help) usage ;;
-v) verbose=true && shift ;;
-d) addSbt "--debug" && shift ;;
-w) addSbt "--warn" && shift ;;
-q) addSbt "--error" && shift ;;
-x) shift ;; # currently unused
-trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;;
-debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;;
-no-colors) addJava "-Dsbt.log.noformat=true" && addJava "-Dsbt.color=false" && shift ;;
-sbt-create) sbt_create=true && shift ;;
-sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;;
-sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;;
-ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;;
-no-share) noshare=true && shift ;;
-offline) addSbt "set offline in Global := true" && shift ;;
-jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;;
-batch) batch=true && shift ;;
-prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;;
-script) require_arg file "$1" "$2" && sbt_script="$2" && addJava "-Dsbt.main.class=sbt.ScriptMain" && shift 2 ;;
-sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;;
-sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;;
-sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;;
-sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;;
-sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;;
-sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;;
-28) setScalaVersion "$latest_28" && shift ;;
-29) setScalaVersion "$latest_29" && shift ;;
-210) setScalaVersion "$latest_210" && shift ;;
-211) setScalaVersion "$latest_211" && shift ;;
-212) setScalaVersion "$latest_212" && shift ;;
-213) setScalaVersion "$latest_213" && shift ;;
-scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;;
-binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;;
-scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "_root_.scala.Some(file(\"$2\"))" && shift 2 ;;
-java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;;
-sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;;
-sbtx-opts) require_arg path "$1" "$2" && sbtx_opts_file="$2" && shift 2 ;;
-jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;;
-D*) addJava "$1" && shift ;;
-J*) addJava "${1:2}" && shift ;;
-S*) addScalac "${1:2}" && shift ;;
new) sbt_new=true && : ${sbt_explicit_version:=$sbt_release_version} && addResidual "$1" && shift ;;
*) addResidual "$1" && shift ;;
esac
done
}
# process the direct command line arguments
process_args "$@"
# skip #-styled comments and blank lines
readConfigFile() {
local end=false
until $end; do
read -r || end=true
[[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo "$REPLY"
done <"$1"
}
# if there are file/environment sbt_opts, process again so we
# can supply args to this runner
if [[ -r "$sbt_opts_file" ]]; then
vlog "Using sbt options defined in file $sbt_opts_file"
while read -r opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file")
elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then
vlog "Using sbt options defined in variable \$SBT_OPTS"
IFS=" " read -r -a extra_sbt_opts <<<"$SBT_OPTS"
else
vlog "No extra sbt options have been defined"
fi
# if there are file/environment sbtx_opts, process again so we
# can supply args to this runner
if [[ -r "$sbtx_opts_file" ]]; then
vlog "Using sbt options defined in file $sbtx_opts_file"
while read -r opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbtx_opts_file")
elif [[ -n "$SBTX_OPTS" && ! ("$SBTX_OPTS" =~ ^@.*) ]]; then
vlog "Using sbt options defined in variable \$SBTX_OPTS"
IFS=" " read -r -a extra_sbt_opts <<<"$SBTX_OPTS"
else
vlog "No extra sbt options have been defined"
fi
[[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}"
# reset "$@" to the residual args
set -- "${residual_args[@]}"
argumentCount=$#
# set sbt version
set_sbt_version
checkJava
# only exists in 0.12+
setTraceLevel() {
case "$sbt_version" in
"0.7."* | "0.10."* | "0.11."*) echoerr "Cannot set trace level in sbt version $sbt_version" ;;
*) setThisBuild traceLevel "$trace_level" ;;
esac
}
# set scalacOptions if we were given any -S opts
[[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[*]}\""
[[ -n "$sbt_explicit_version" && -z "$sbt_new" ]] && addJava "-Dsbt.version=$sbt_explicit_version"
vlog "Detected sbt version $sbt_version"
if [[ -n "$sbt_script" ]]; then
residual_args=("$sbt_script" "${residual_args[@]}")
else
# no args - alert them there's stuff in here
((argumentCount > 0)) || {
vlog "Starting $script_name: invoke with -help for other options"
residual_args=(shell)
}
fi
# verify this is an sbt dir, -create was given or user attempts to run a scala script
[[ -r ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_script" || -n "$sbt_new" ]] || {
cat <<EOM
$(pwd) doesn't appear to be an sbt project.
If you want to start sbt anyway, run:
$0 -sbt-create
EOM
exit 1
}
# pick up completion if present; todo
# shellcheck disable=SC1091
[[ -r .sbt_completion.sh ]] && source .sbt_completion.sh
# directory to store sbt launchers
[[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir"
[[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)"
# no jar? download it.
[[ -r "$sbt_jar" ]] || acquire_sbt_jar || {
# still no jar? uh-oh.
echo "Could not download and verify the launcher. Obtain the jar manually and place it at $sbt_jar"
exit 1
}
if [[ -n "$noshare" ]]; then
for opt in ${noshare_opts}; do
addJava "$opt"
done
else
case "$sbt_version" in
"0.7."* | "0.10."* | "0.11."* | "0.12."*)
[[ -n "$sbt_dir" ]] || {
sbt_dir="$HOME/.sbt/$sbt_version"
vlog "Using $sbt_dir as sbt dir, -sbt-dir to override."
}
;;
esac
if [[ -n "$sbt_dir" ]]; then
addJava "-Dsbt.global.base=$sbt_dir"
fi
fi
if [[ -r "$jvm_opts_file" ]]; then
vlog "Using jvm options defined in file $jvm_opts_file"
while read -r opt; do extra_jvm_opts+=("$opt"); done < <(readConfigFile "$jvm_opts_file")
elif [[ -n "$JVM_OPTS" && ! ("$JVM_OPTS" =~ ^@.*) ]]; then
vlog "Using jvm options defined in \$JVM_OPTS variable"
IFS=" " read -r -a extra_jvm_opts <<<"$JVM_OPTS"
else
vlog "Using default jvm options"
IFS=" " read -r -a extra_jvm_opts <<<"$( default_jvm_opts)"
fi
# traceLevel is 0.12+
[[ -n "$trace_level" ]] && setTraceLevel
execRunner "$java_cmd" \
"${extra_jvm_opts[@]}" \
"${java_args[@]}" \
-jar "$sbt_jar" \
"${sbt_commands[@]}" \
"${residual_args[@]}"

View File

@@ -0,0 +1,42 @@
ThisBuild / organization := "ch.epfl"
ThisBuild / version := "2021"
ThisBuild / scalaVersion := "2.13.4"
val javaMemOptions = Seq("-Xss32M", "-Xms128M")
lazy val root = (project in file("."))
// Enable packaging of the L3 compiler so that it can be run without SBT.
// See documentation at https://www.scala-sbt.org/sbt-native-packager/
// Among the tasks added by this plugin, the most useful are:
// - "stage" to create the scripts locally in target/universal/stage/bin,
// - "dist" to create a Zip archive in target/universal.
.enablePlugins(JavaAppPackaging)
.settings(
name := "l3c",
scalacOptions ++= Seq("-feature",
"-deprecation",
"-unchecked",
"-encoding", "utf-8"),
// Main configuration
Compile / scalaSource := baseDirectory.value / "src",
libraryDependencies ++= Seq(
"com.lihaoyi" %% "fastparse" % "2.3.1",
"org.typelevel" %% "paiges-core" % "0.4.0"),
fork := true,
javaOptions ++= javaMemOptions,
run / connectInput := true,
run / outputStrategy := Some(StdoutOutput),
// Test configuration
Test / scalaSource := baseDirectory.value / "test",
libraryDependencies += "com.lihaoyi" %% "utest" % "0.7.7" % "test",
testFrameworks += new TestFramework("utest.runner.Framework"),
// Packaging configuration (sbt-native-packager)
Compile / packageDoc / mappings := Seq(),
Universal / javaOptions ++= javaMemOptions.map("-J" + _))

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager">
<output url="file://output" />
<output-test url="file://test" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.bsp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="scala-sdk-2.13.5" level="project" />
</component>
</module>

View File

@@ -0,0 +1 @@
sbt.version=1.4.7

View File

@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.0")

View File

@@ -0,0 +1,11 @@
package l3
/**
* Predefined tags for blocks.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object BlockTag extends Enumeration(200) {
val String, RegisterFrame, Function = Value
}

View File

@@ -0,0 +1,161 @@
package l3
import scala.collection.mutable.{ Map => MutableMap }
import SymbolicCL3TreeModule._
import IO._
import l3.L3Primitive._
/**
* A tree-based interpreter for the CL₃ language.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object CL3Interpreter extends (Tree => TerminalPhaseResult) {
def apply(program: Tree): TerminalPhaseResult =
try {
eval(program)(Map.empty)
Right(0, None)
} catch {
case e: EvalHlt =>
Right((e.retCode, None))
case e: EvalErr =>
val Seq(m1, ms @ _*) = e.messages
Left((m1 +: ms.reverse).mkString("\n"))
}
// Values
private sealed trait Value {
override def toString(): String = this match {
case BlockV(t, c) => s"<$t>[${c mkString ","}]"
case IntV(i) => i.toString
case CharV(c) => s"'${new String(Array(c), 0, 1)}'"
case BoolV(b) => if (b) "#t" else "#f"
case UnitV => "#u"
case FunctionV(_, _, _) => "<function>"
}
}
private case class BlockV(tag: L3BlockTag, contents: Array[Value])
extends Value
private case class IntV(i: L3Int) extends Value
private case class CharV(c: L3Char) extends Value
private case class BoolV(b: Boolean) extends Value
private case object UnitV extends Value
private case class FunctionV(args: Seq[Symbol], body: Tree, env: Env)
extends Value
// Environment
private type Env = PartialFunction[Symbol, Value]
// Error/halt handling (termination)
private class EvalErr(val messages: Seq[String]) extends Exception()
private class EvalHlt(val retCode: Int) extends Exception()
private def error(pos: Position, msg: String): Nothing =
throw new EvalErr(Seq(msg, s" at $pos"))
private def halt(r: Int): Nothing =
throw new EvalHlt(r)
private def validIndex(a: Array[Value], i: L3Int): Boolean =
0 <= i.toInt && i.toInt < a.length
private final def eval(tree: Tree)(implicit env: Env): Value = tree match {
case Let(bdgs, body) =>
eval(body)(Map(bdgs map { case (n, e) => n -> eval(e) } : _*) orElse env)
case LetRec(funs, body) =>
val recEnv = MutableMap[Symbol, Value]()
val env1 = recEnv orElse env
for (Fun(name, args, body) <- funs)
recEnv(name) = BlockV(l3.BlockTag.Function.id,
Array(FunctionV(args, body, env1)))
eval(body)(env1)
case If(cond, thenE, elseE) =>
eval(cond) match {
case BoolV(false) => eval(elseE)
case _ => eval(thenE)
}
case App(fun, args) =>
eval(fun) match {
case BlockV(_, Array(FunctionV(cArgs, cBody, cEnv))) =>
if (args.length != cArgs.length)
error(tree.pos,
s"expected ${cArgs.length} arguments, got ${args.length}")
try {
eval(cBody)(Map(cArgs zip (args map eval) : _*) orElse cEnv)
} catch {
case e: EvalErr =>
throw new EvalErr(e.messages :+ s" at ${fun.pos}")
}
case _ => error(fun.pos, "function value expected")
}
case Prim(p, args) => (p, args map eval) match {
case (BlockAlloc(t), Seq(IntV(i))) =>
BlockV(t, Array.fill(i.toInt)(UnitV))
case (BlockP, Seq(BlockV(_, _))) => BoolV(true)
case (BlockP, Seq(_)) => BoolV(false)
case (BlockTag, Seq(BlockV(t, _))) => IntV(L3Int(t))
case (BlockLength, Seq(BlockV(_, c))) => IntV(L3Int(c.length))
case (BlockGet, Seq(BlockV(_, v), IntV(i))) if (validIndex(v, i)) =>
v(i.toInt)
case (BlockSet, Seq(BlockV(_, v), IntV(i), o)) if (validIndex(v, i)) =>
v(i.toInt) = o; UnitV
case (IntP, Seq(IntV(_))) => BoolV(true)
case (IntP, Seq(_)) => BoolV(false)
case (IntAdd, Seq(IntV(v1), IntV(v2))) => IntV(v1 + v2)
case (IntSub, Seq(IntV(v1), IntV(v2))) => IntV(v1 - v2)
case (IntMul, Seq(IntV(v1), IntV(v2))) => IntV(v1 * v2)
case (IntDiv, Seq(IntV(v1), IntV(v2))) => IntV(v1 / v2)
case (IntMod, Seq(IntV(v1), IntV(v2))) => IntV(v1 % v2)
case (IntShiftLeft, Seq(IntV(v1), IntV(v2))) => IntV(v1 << v2)
case (IntShiftRight, Seq(IntV(v1), IntV(v2))) => IntV(v1 >> v2)
case (IntBitwiseAnd, Seq(IntV(v1), IntV(v2))) => IntV(v1 & v2)
case (IntBitwiseOr, Seq(IntV(v1), IntV(v2))) => IntV(v1 | v2)
case (IntBitwiseXOr, Seq(IntV(v1), IntV(v2))) => IntV(v1 ^ v2)
case (IntLt, Seq(IntV(v1), IntV(v2))) => BoolV(v1 < v2)
case (IntLe, Seq(IntV(v1), IntV(v2))) => BoolV(v1 <= v2)
case (Eq, Seq(v1, v2)) => BoolV(v1 == v2)
case (IntToChar, Seq(IntV(i))) if Character.isValidCodePoint(i.toInt) =>
CharV(i.toInt)
case (CharP, Seq(CharV(_))) => BoolV(true)
case (CharP, Seq(_)) => BoolV(false)
case (ByteRead, Seq()) => IntV(L3Int(readByte()))
case (ByteWrite, Seq(IntV(c))) => writeByte(c.toInt); UnitV
case (CharToInt, Seq(CharV(c))) => IntV(L3Int(c))
case (BoolP, Seq(BoolV(_))) => BoolV(true)
case (BoolP, Seq(_)) => BoolV(false)
case (UnitP, Seq(UnitV)) => BoolV(true)
case (UnitP, Seq(_)) => BoolV(false)
case (p, vs) =>
error(tree.pos,
s"""cannot apply primitive $p to values ${vs.mkString(", ")}""")
}
case Halt(arg) => eval(arg) match {
case IntV(c) => halt(c.toInt)
case c => error(tree.pos, s"halt with code $c")
}
case Ident(n) => env(n)
case Lit(IntLit(i)) => IntV(i)
case Lit(CharLit(c)) => CharV(c)
case Lit(BooleanLit(b)) => BoolV(b)
case Lit(UnitLit) => UnitV
}
}

View File

@@ -0,0 +1,21 @@
package l3
/**
* Literal values for the CL₃ language.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
sealed trait CL3Literal {
override def toString: String = this match {
case IntLit(i) => i.toString
case CharLit(c) => "'"+ (new String(Character.toChars(c))) +"'"
case BooleanLit(v) => if (v) "#t" else "#f"
case UnitLit => "#u"
}
}
case class IntLit(value: L3Int) extends CL3Literal
case class CharLit(value: L3Char) extends CL3Literal
case class BooleanLit(value: Boolean) extends CL3Literal
case object UnitLit extends CL3Literal

View File

@@ -0,0 +1,83 @@
package l3
import l3.{ NominalCL3TreeModule => N }
import l3.{ SymbolicCL3TreeModule => S }
/**
* Name analysis for the CL₃ language. Translates a tree in which
* identifiers are simple strings into one in which identifiers are
* symbols (i.e. globally-unique names).
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object CL3NameAnalyzer extends (N.Tree => Either[String, S.Tree]) {
def apply(tree: N.Tree): Either[String, S.Tree] =
try {
Right(rewrite(tree)(Map.empty))
} catch {
case NameAnalysisError(msg) =>
Left(msg)
}
private type Env = Map[String, Symbol]
private final case class NameAnalysisError(msg: String) extends Exception(msg)
private def error(msg: String)(implicit pos: Position): Nothing =
throw new NameAnalysisError(s"$pos: $msg")
private def rewrite(tree: N.Tree)(implicit env: Env): S.Tree = {
implicit val pos = tree.pos
tree match {
case N.Let(bdgs, body) =>
val syms = checkUnique(bdgs map (_._1)) map Symbol.fresh
S.Let(syms zip (bdgs map { b => rewrite(b._2) }),
rewrite(body)(augmented(env, syms)))
case N.LetRec(funs, body) =>
val syms = checkUnique(funs map (_.name)) map Symbol.fresh
val env1 = augmented(env, syms)
S.LetRec((syms zip funs) map {case (s,f) => rewriteF(s, f , env1)},
rewrite(body)(env1))
case N.If(cond, thenE, elseE) =>
S.If(rewrite(cond), rewrite(thenE), rewrite(elseE))
case N.App(N.Ident(fun), args) if env contains altName(fun, args.length)=>
S.App(S.Ident(env(altName(fun, args.length))), args map rewrite)
case N.App(fun, args) =>
S.App(rewrite(fun), args map rewrite)
case N.Prim(p, args) if L3Primitive.isDefinedAt(p, args.length) =>
S.Prim(L3Primitive(p), args map rewrite)
case N.Halt(arg) =>
S.Halt(rewrite(arg))
case N.Ident(name) if env contains name =>
S.Ident(env(name))
case N.Lit(value) =>
S.Lit(value)
case N.Prim(p, _) if L3Primitive isDefinedAt p =>
error(s"incorrect number of arguments for @$p")
case N.Prim(p, _) =>
error(s"unknown primitive $p")
case N.Ident(name) =>
error(s"unknown identifier $name")
}
}
private def rewriteF(funSym: Symbol, fun: N.Fun, env: Env): S.Fun = {
implicit val pos = fun.pos
val argsSyms = checkUnique(fun.args) map Symbol.fresh
S.Fun(funSym, argsSyms, rewrite(fun.body)(augmented(env, argsSyms)))
}
private def checkUnique(names: Seq[String])
(implicit pos: Position): Seq[String] = {
for (n <- names diff names.distinct)
error(s"repeated definition of $n")
names
}
private def altName(name: String, arity: Int): String =
s"$name@$arity"
private def augmented(env: Env, symbols: Seq[Symbol]): Env =
env ++ (symbols map { s => (s.name, s) })
}

View File

@@ -0,0 +1,195 @@
package l3
import l3.CL3NameAnalyzer.NameAnalysisError
import l3.{SymbolicCL3TreeModule => CL3, SymbolicCPSTreeModule => CPS}
import l3.{L3Primitive => L3}
object CL3ToCPSTranslator extends (CL3.Tree => CPS.Tree) {
private final case class CPSTranslationError(msg: String) extends Exception(msg)
private def error(msg: String)(implicit pos: Position): Nothing =
throw CPSTranslationError(s"$pos: $msg")
def freshName(s: String): Symbol = Symbol.fresh(s)
def apply(tree: CL3.Tree): CPS.Tree = translateTree(tree, atom => {
CPS.Halt(CPS.AtomL(IntLit(L3Int(0))))
})
private def translateArgsToSeq(args: Seq[CL3.Tree], finalMapper: Seq[CPS.Atom] => CPS.Tree, translatedArgs: Seq[CPS.Atom] = Seq()): CPS.Tree = {
if (args.isEmpty) finalMapper(translatedArgs)
else translateTree(args.head, vn => translateArgsToSeq(args.tail, finalMapper, translatedArgs :+ vn))
}
def translateTree(tree: CL3.Tree, partialTerm: CPS.Atom => CPS.Tree): CPS.Tree = {
implicit val position: Position = tree.pos
// As I understand the thing:
// We get a tree and a "partialTerm"
// This partial term is a term awaiting to be filled with a value (atom)
// [A program is described as a "halt" awaiting to be filled with its return code]
// In each term, we must "translate".
// - If we reach a CPS.Tree, return it.
// - If we reach a CPS.Atom, return partialTerm(atom)
tree match {
case CL3.Let(bindings, body) =>
def translateLet(bindings: Seq[(CL3.Name, CL3.Tree)]): CPS.Tree = {
if (bindings.isEmpty) translateTree(body, partialTerm)
else {
val (n1, e1) = bindings.head
val letp = (v1: CPS.Atom) => CPS.LetP(n1, L3.Id, Seq(v1), translateLet(bindings.tail))
translateTree(e1, letp)
}
}
translateLet(bindings)
case CL3.LetRec(functions, body) =>
def translateFunc(func: CL3.Fun): CPS.Fun = {
val c = Symbol.fresh("letrec_c")
CPS.Fun(func.name, c, func.args, tail(func.body, c))
}
CPS.LetF(functions.map(translateFunc), translateTree(body, partialTerm))
case CL3.If(cnd, thenE, elseE) =>
cnd match {
case CL3.Prim(prim: L3TestPrimitive, args) =>
val (c, r, contTrue, contFalse) = (freshName("iff_c"), freshName("iff_r"), freshName("iff_ct"), freshName("iff_cf"))
// The final body, containing the translated primitive and a reference to the continuations
val translatedIfBody = cond(cnd, contTrue, contFalse) // translateArgsToSeq(args, translatedArgs => CPS.If(prim, translatedArgs, contTrue, contFalse))
// The third letC, containing the else
val elseBody = CPS.LetC(Seq(CPS.Cnt(contFalse, Seq(), tail(elseE, c))), translatedIfBody)
// The second letC, containing the then
val thenBody = CPS.LetC(Seq(CPS.Cnt(contTrue, Seq(), tail(thenE, c))), elseBody)
// Finally, the final expression, with the return context (the thing that is waiting for if results)
CPS.LetC(Seq(CPS.Cnt(c, Seq(r), partialTerm(CPS.AtomN(r)))), thenBody)
case _ =>
val remappedIf = CL3.If(CL3.Prim(L3Primitive.Eq, Seq(cnd, CL3.Lit(BooleanLit(false)))), elseE, thenE)
translateTree(remappedIf, partialTerm)
}
case CL3.App(func, args) =>
val cName = Symbol.fresh("app_c")
val rName = Symbol.fresh("app_r")
// Code to apply to plug the returned function
val plugFunc = (funcV: CPS.Atom) => {
translateArgsToSeq(args, translatedArgs => CPS.AppF(funcV, cName, translatedArgs))
}
val cnt = CPS.Cnt(cName, Seq(rName), partialTerm(CPS.AtomN(rName)))
CPS.LetC(Seq(cnt), translateTree(func, plugFunc))
case cnd @ CL3.Prim(_: L3TestPrimitive, _) =>
// Boolean primitives
// <=> remapped as CL3 If for translation
translateTree(CL3.If(cnd, CL3.Lit(BooleanLit(true)), CL3.Lit(BooleanLit(false))), partialTerm)
case CL3.Prim(prim: L3ValuePrimitive, args) =>
// Other primitives
// We should map arguments and return the value
// map to:
/*
[[arg1]](\v1
[[arg2]](\v2 ...
(let_p ((FRESH_NAME (prim) (v1 v2 ...)) C[FRESH_NAME])
*/
val primName = Symbol.fresh("prim_r")
translateArgsToSeq(args, res => CPS.LetP(primName, prim, res, partialTerm(CPS.AtomN(primName))))
case CL3.Halt(arg) => translateTree(arg, res => CPS.Halt(res))
case CL3.Ident(name) => partialTerm(CPS.AtomN(name))
case CL3.Lit(value) => partialTerm(CPS.AtomL(value))
}
}
/**
* Used to replace a "\v (app_c c v)" translation
*
* For basic values, it's equivalent.
* @param tree
* @param continuation
* @return
*/
def tail(tree: CL3.Tree, continuation: CL3.Name): CPS.Tree = {
implicit val position: Position = tree.pos
tree match {
/*
// No opti possible.
case CL3.Let(bindings, body) =>
case CL3.If(cond, thenE, elseE) =>
case cnd @ CL3.Prim(_: L3TestPrimitive, _) =>
case CL3.Prim(prim: L3ValuePrimitive, args) =>
case CL3.App(func, args) if args.nonEmpty =>
case CL3.Halt(arg) =>
*/
// Trivial cases covered by default :)
case CL3.Ident(name) => CPS.AppC(continuation, Seq(CPS.AtomN(name)))
case CL3.Lit(value) => CPS.AppC(continuation, Seq(CPS.AtomL(value)))
// course example
// if a function application takes no arg, we can simply apply the continuation to the function directly
// FIXME Commented out for now: causes some tests to fail.
//case CL3.App(func, args) => tail(func, continuation)
case CL3.App(func, args) if args.isEmpty => translateTree(func, f => CPS.AppF(f, continuation, Seq()))
// By default, run the "naive" unoptimized alg: use non-tail and wrap the result
case default =>
translateTree(default, res => CPS.AppC(continuation, Seq(res)))
}
}
def cond(t: CL3.Tree, ct: CL3.Name, cf: CL3.Name): CPS.Tree = {
def get_ct_cf(v: Boolean): CL3.Name = {
if(v) ct
else cf
}
t match {
case CL3.App(func, args) if args.isEmpty => // call with no args
val cName = Symbol.fresh("app_c")
translateTree(func, t => CPS.AppF(t, cName, Seq()))
case CL3.Lit(BooleanLit(t)) => CPS.AppC(get_ct_cf(t), Seq())
case CL3.Lit(_) => CPS.AppC(ct, Seq())
case CL3.Let(Seq(), body) => cond(body, ct, cf)
case CL3.Halt(arg) => translateTree(arg , res => CPS.Halt(res))
case CL3.If(cnd, CL3.Lit(BooleanLit(t)), CL3.Lit(BooleanLit(e))) => //if(cnd) X else X
cond(cnd,get_ct_cf(t),get_ct_cf(e))
case CL3.If(cnd, CL3.Lit(tt), CL3.Lit(ft)) => // (Lit != false) => true
cond(cnd, get_ct_cf(tt != BooleanLit(false)), get_ct_cf(ft != BooleanLit(false)))
case CL3.If(cnd, CL3.Lit(BooleanLit(t)), elseE) => //if(cnd) X else E
val primName = Symbol.fresh("if_e")
CPS.LetC(Seq(CPS.Cnt(primName, Seq(), cond(elseE,ct,cf))), cond(cnd,get_ct_cf(t),primName))
case CL3.If(cnd, thenE, CL3.Lit(BooleanLit(t))) => //if(cnd) T else X
val primName = Symbol.fresh("if_t")
CPS.LetC(Seq(CPS.Cnt(primName, Seq(), cond(thenE,ct,cf))), cond(cnd,primName,get_ct_cf(t)))
case CL3.If(cnd, thenE, elseE) => // if(cnd) T else E
val primNameT = Symbol.fresh("if_t")
val primNameE = Symbol.fresh("if_e")
CPS.LetC(Seq(CPS.Cnt(primNameT, Seq(), cond(thenE,ct,cf)),CPS.Cnt(primNameE, Seq(), cond(elseE, ct,cf))), cond(cnd,primNameT,primNameE))
//FIXME: this case might be useless ?
case CL3.Prim(prim: L3TestPrimitive, args) =>
translateArgsToSeq(args, translatedArgs => CPS.If(prim, translatedArgs, ct, cf))
case default =>
translateTree(default , t => CPS.If(L3Primitive.Eq,Seq(t,CPS.AtomL(BooleanLit(true))),ct,cf))
}
}
}

View File

@@ -0,0 +1,51 @@
package l3
/**
* A module for CL₃ trees.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
trait CL3TreeModule {
type Name
type Primitive
sealed abstract class Tree(val pos: Position)
case class Let(bindings: Seq[(Name, Tree)], body: Tree)
(implicit pos: Position) extends Tree(pos)
case class LetRec(functions: Seq[Fun], body: Tree)
(implicit pos: Position) extends Tree(pos)
case class If(cond: Tree, thenE: Tree, elseE: Tree)
(implicit pos: Position) extends Tree(pos)
case class App(fun: Tree, args: Seq[Tree])
(implicit pos: Position) extends Tree(pos)
case class Prim(prim: Primitive, args: Seq[Tree])
(implicit pos: Position) extends Tree(pos)
case class Halt(arg: Tree)
(implicit pos: Position) extends Tree(pos)
case class Ident(name: Name)
(implicit pos: Position) extends Tree(pos)
case class Lit(value: CL3Literal)
(implicit pos: Position) extends Tree(pos)
case class Fun(name: Name, args: Seq[Name], body: Tree)
(implicit val pos: Position)
}
/**
* Module for trees after parsing: names and primitives are
* represented as strings.
*/
object NominalCL3TreeModule extends CL3TreeModule {
type Name = String
type Primitive = String
}
/**
* Module for trees after name analysis: names are represented as
* symbols (globally-unique names) and primitives as objects.
*/
object SymbolicCL3TreeModule extends CL3TreeModule {
type Name = Symbol
type Primitive = L3Primitive
}

View File

@@ -0,0 +1,40 @@
package l3
import org.typelevel.paiges.Doc
class CL3TreeFormatter[T <: CL3TreeModule](treeModule: T)
extends Formatter[T#Tree] {
import Formatter.par, treeModule._
def toDoc(tree: T#Tree): Doc = (tree: @unchecked) match {
case Let(bdgs, body) =>
val bdgsDoc =
par(1, bdgs map { case (n, v) => par(1, Doc.str(n), toDoc(v)) })
par("let", 2, bdgsDoc, toDoc(body))
case LetRec(funs, body) =>
def funToDoc(fun: T#Fun): Doc =
(Doc.str(fun.name)
/ par("fun", 2, par(1, fun.args map Doc.str), toDoc(fun.body)))
val funsDoc = par(1, funs map { f => par(1, funToDoc(f)) })
par("letrec", 2, funsDoc, toDoc(body))
case If(c, t, e) =>
par("if", 2, toDoc(c), toDoc(t), toDoc(e))
case App(fun, args) =>
par(1, (fun +: args) map toDoc)
case Halt(arg) =>
par("halt", 2, toDoc(arg))
case Prim(prim, args) =>
par(1, Doc.text(s"@$prim") +: (args map toDoc))
case Ident(name) =>
Doc.str(name)
case Lit(l) =>
Doc.str(l)
}
}
object CL3TreeFormatter {
implicit object NominalCL3TreeFormatter
extends CL3TreeFormatter(NominalCL3TreeModule)
implicit object SymbolicCL3TreeFormatter
extends CL3TreeFormatter(SymbolicCL3TreeModule)
}

View File

@@ -0,0 +1,248 @@
package l3
import scala.annotation.tailrec
import scala.collection.mutable.{ Map => MutableMap }
import IO._
/**
* A tree-based interpreter for the CPS languages.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
sealed abstract class CPSInterpreter[M <: CPSTreeModule](
protected val treeModule: M,
log: M#Tree => Unit = { _ : M#Tree => () }) {
import treeModule._
def apply(tree: Tree): TerminalPhaseResult =
Right((eval(tree, emptyEnv), None))
protected sealed trait Value
protected case class FunV(retC: Name, args: Seq[Name], body: Tree, env: Env)
extends Value
protected case class CntV(args: Seq[Name], body: Tree, env: Env)
extends Value
protected type Env = PartialFunction[Name, Value]
protected val emptyEnv: Env = Map.empty
@tailrec
private def eval(tree: Tree, env: Env): Int = {
def resolve(a: Atom): Value = a match {
case AtomN(n) => env(n)
case AtomL(l) => evalLit(l)
}
log(tree)
(tree: @unchecked) match {
case LetP(name, prim, args, body) =>
eval(body, Map(name->evalValuePrim(prim, args map resolve)) orElse env)
case LetC(cnts, body) =>
val recEnv = MutableMap[Name, Value]()
val env1 = recEnv orElse env
for (Cnt(name, args, body) <- cnts)
recEnv(name) = CntV(args, body, env1)
eval(body, env1)
case LetF(funs, body) =>
val recEnv = MutableMap[Name, Value]()
val env1 = recEnv orElse env
for (Fun(name, retC, args, body) <- funs)
recEnv(name) = wrapFunV(FunV(retC, args, body, env1))
eval(body, env1)
case AppC(cnt, args) =>
val CntV(cArgs, cBody, cEnv) = env(cnt)
assume(cArgs.length == args.length)
eval(cBody, (cArgs zip (args map resolve)).toMap orElse cEnv)
case AppF(fun, retC, args) =>
val FunV(fRetC, fArgs, fBody, fEnv) = unwrapFunV(resolve(fun))
assume(fArgs.length == args.length)
val rArgs = args map resolve
val env1 = ((fRetC +: fArgs) zip (env(retC) +: rArgs)).toMap orElse fEnv
eval(fBody, env1)
case If(cond, args, thenC, elseC) =>
val cnt = if (evalTestPrim(cond, args map resolve)) thenC else elseC
val cntV = env(cnt).asInstanceOf[CntV]
eval(cntV.body, cntV.env)
case Halt(name) =>
extractInt(resolve(name))
}
}
protected def extractInt(v: Value): Int
protected def wrapFunV(funV: FunV): Value
protected def unwrapFunV(v: Value): FunV
protected def evalLit(l: Literal): Value
protected def evalValuePrim(p: ValuePrimitive, args: Seq[Value]): Value
protected def evalTestPrim(p: TestPrimitive, args: Seq[Value]): Boolean
}
object CPSInterpreterHigh extends CPSInterpreter(SymbolicCPSTreeModule)
with (SymbolicCPSTreeModule.Tree => TerminalPhaseResult) {
import treeModule._
import L3Primitive._
private case class BlockV(tag: L3BlockTag, contents: Array[Value])
extends Value
private case class IntV(value: L3Int) extends Value
private case class CharV(value: L3Char) extends Value
private case class BooleanV(value: Boolean) extends Value
private case object UnitV extends Value
protected def extractInt(v: Value): Int = v match { case IntV(i) => i.toInt }
protected def wrapFunV(funV: FunV): Value =
BlockV(l3.BlockTag.Function.id, Array(funV))
protected def unwrapFunV(v: Value): FunV = v match {
case BlockV(id, Array(funV: FunV)) if id == l3.BlockTag.Function.id => funV
}
protected def evalLit(l: Literal): Value = l match {
case IntLit(i) => IntV(i)
case CharLit(c) => CharV(c)
case BooleanLit(b) => BooleanV(b)
case UnitLit => UnitV
}
protected def evalValuePrim(p: ValuePrimitive, args: Seq[Value]): Value =
(p, args) match {
case (BlockAlloc(t), Seq(IntV(i))) =>
BlockV(t, Array.fill(i.toInt)(UnitV))
case (BlockTag, Seq(BlockV(t, _))) => IntV(L3Int(t))
case (BlockLength, Seq(BlockV(_, c))) => IntV(L3Int(c.length))
case (BlockGet, Seq(BlockV(_, v), IntV(i))) => v(i.toInt)
case (BlockSet, Seq(BlockV(_, v), IntV(i), o)) => v(i.toInt) = o; UnitV
case (IntAdd, Seq(IntV(v1), IntV(v2))) => IntV(v1 + v2)
case (IntSub, Seq(IntV(v1), IntV(v2))) => IntV(v1 - v2)
case (IntMul, Seq(IntV(v1), IntV(v2))) => IntV(v1 * v2)
case (IntDiv, Seq(IntV(v1), IntV(v2))) => IntV(v1 / v2)
case (IntMod, Seq(IntV(v1), IntV(v2))) => IntV(v1 % v2)
case (IntToChar, Seq(IntV(v))) => CharV(v.toInt)
case (IntShiftLeft, Seq(IntV(v1), IntV(v2))) => IntV(v1 << v2)
case (IntShiftRight, Seq(IntV(v1), IntV(v2))) => IntV(v1 >> v2)
case (IntBitwiseAnd, Seq(IntV(v1), IntV(v2))) => IntV(v1 & v2)
case (IntBitwiseOr, Seq(IntV(v1), IntV(v2))) => IntV(v1 | v2)
case (IntBitwiseXOr, Seq(IntV(v1), IntV(v2))) => IntV(v1 ^ v2)
case (ByteRead, Seq()) => IntV(L3Int(readByte()))
case (ByteWrite, Seq(IntV(c))) => writeByte(c.toInt); UnitV
case (CharToInt, Seq(CharV(c))) => IntV(L3Int(c))
case (Id, Seq(v)) => v
}
protected def evalTestPrim(p: TestPrimitive, args: Seq[Value]): Boolean =
(p, args) match {
case (BlockP, Seq(BlockV(_, _))) => true
case (BlockP, Seq(_)) => false
case (IntP, Seq(IntV(_))) => true
case (IntP, Seq(_)) => false
case (IntLt, Seq(IntV(v1), IntV(v2))) => v1 < v2
case (IntLe, Seq(IntV(v1), IntV(v2))) => v1 <= v2
case (CharP, Seq(CharV(_))) => true
case (CharP, Seq(_)) => false
case (BoolP, Seq(BooleanV(_))) => true
case (BoolP, Seq(_)) => false
case (UnitP, Seq(UnitV)) => true
case (UnitP, Seq(_)) => false
case (Eq, Seq(v1, v2)) => v1 == v2
}
}
class CPSInterpreterLow(log: SymbolicCPSTreeModuleLow.Tree => Unit)
extends CPSInterpreter(SymbolicCPSTreeModuleLow, log)
with (SymbolicCPSTreeModuleLow.Tree => TerminalPhaseResult) {
import treeModule._
import CPSValuePrimitive._
import CPSTestPrimitive._
import scala.language.implicitConversions
protected case class BlockV(addr: Bits32,
tag: L3BlockTag,
contents: Array[Value])
extends Value
protected case class BitsV(value: Bits32) extends Value
private var nextBlockAddr = 0
protected def allocBlock(tag: L3BlockTag, contents: Array[Value]): BlockV = {
val block = BlockV(nextBlockAddr, tag, contents)
nextBlockAddr += 4
block
}
private implicit def valueToBits(v: Value): Bits32 = v match {
case BlockV(addr, _, _) => addr
case BitsV(value) => value
case _: FunV | _: CntV => sys.error(s"cannot convert $v to bits")
}
protected def extractInt(v: Value): Int = v match { case BitsV(i) => i }
protected def wrapFunV(funV: FunV): Value = funV
protected def unwrapFunV(v: Value): FunV = v.asInstanceOf[FunV]
protected def evalLit(l: Literal): Value = BitsV(l)
protected def evalValuePrim(p: ValuePrimitive, args: Seq[Value]): Value =
(p, args) match {
case (Add, Seq(v1, v2)) => BitsV(v1 + v2)
case (Sub, Seq(v1, v2)) => BitsV(v1 - v2)
case (Mul, Seq(v1, v2)) => BitsV(v1 * v2)
case (Div, Seq(v1, v2)) => BitsV(v1 / v2)
case (Mod, Seq(v1, v2)) => BitsV(v1 % v2)
case (ShiftLeft, Seq(v1, v2)) => BitsV(v1 << v2)
case (ShiftRight, Seq(v1, v2)) => BitsV(v1 >> v2)
case (And, Seq(v1, v2)) => BitsV(v1 & v2)
case (Or, Seq(v1, v2)) => BitsV(v1 | v2)
case (XOr, Seq(v1, v2)) => BitsV(v1 ^ v2)
case (ByteRead, Seq()) => BitsV(readByte())
case (ByteWrite, Seq(c)) => writeByte(c); BitsV(0)
case (BlockAlloc(t), Seq(BitsV(s))) =>
allocBlock(t, Array.fill(s)(BitsV(0)))
case (BlockTag, Seq(BlockV(_, t, _))) => BitsV(t)
case (BlockLength, Seq(BlockV(_, _, c))) => BitsV(c.length)
case (BlockGet, Seq(BlockV(_, _, c), BitsV(i))) => c(i)
case (BlockSet, Seq(BlockV(_, _, c), BitsV(i), v)) =>
c(i) = v; BitsV(0)
case (Id, Seq(o)) => o
}
protected def evalTestPrim(p: TestPrimitive, args: Seq[Value]): Boolean =
(p, args) match {
case (Lt, Seq(v1, v2)) => v1 < v2
case (Le, Seq(v1, v2)) => v1 <= v2
case (Eq, Seq(v1, v2)) => v1 == v2
}
}
object CPSInterpreterLow extends CPSInterpreterLow(_ => ())
object CPSInterpreterLowNoCC extends CPSInterpreterLow(_ => ()) {
override protected def wrapFunV(funV: FunV): Value =
allocBlock(BlockTag.Function.id, Array(funV))
override protected def unwrapFunV(v: Value): FunV = v match {
case BlockV(_, _, Array(funV: FunV)) => funV
}
}

View File

@@ -0,0 +1,53 @@
package l3
/**
* A class for value-producing primitives.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
sealed abstract class CPSValuePrimitive(val name: String) {
override def toString: String = name
}
object CPSValuePrimitive {
case object Add extends CPSValuePrimitive("+")
case object Sub extends CPSValuePrimitive("-")
case object Mul extends CPSValuePrimitive("*")
case object Div extends CPSValuePrimitive("/")
case object Mod extends CPSValuePrimitive("%")
case object ShiftLeft extends CPSValuePrimitive("shift-left")
case object ShiftRight extends CPSValuePrimitive("shift-right")
case object And extends CPSValuePrimitive("and")
case object Or extends CPSValuePrimitive("or")
case object XOr extends CPSValuePrimitive("xor")
case object ByteRead extends CPSValuePrimitive("byte-read")
case object ByteWrite extends CPSValuePrimitive("byte-write")
case class BlockAlloc(tag: L3BlockTag)
extends CPSValuePrimitive(s"block-alloc-${tag}")
case object BlockTag extends CPSValuePrimitive("block-tag")
case object BlockLength extends CPSValuePrimitive("block-length")
case object BlockGet extends CPSValuePrimitive("block-get")
case object BlockSet extends CPSValuePrimitive("block-set!")
case object Id extends CPSValuePrimitive("id")
}
/**
* A class for testing primitives.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
sealed abstract class CPSTestPrimitive(val name: String) {
override def toString: String = name
}
object CPSTestPrimitive {
case object Lt extends CPSTestPrimitive("<")
case object Le extends CPSTestPrimitive("<=")
case object Eq extends CPSTestPrimitive("=")
}

View File

@@ -0,0 +1,66 @@
package l3
/**
* A module for CPS trees.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
trait CPSTreeModule {
type Name
type Literal
type ValuePrimitive
type TestPrimitive
sealed trait Atom extends Product {
def asName: Option[Name]
def asLiteral: Option[Literal]
}
case class AtomN(n: Name) extends Atom {
override def asName: Option[Name] = Some(n)
override def asLiteral: Option[Literal] = None
override def toString: String = n.toString
}
case class AtomL(l: Literal) extends Atom {
override def asName: Option[Name] = None
override def asLiteral: Option[Literal] = Some(l)
override def toString: String = l.toString
}
sealed trait Tree
case class LetP(name: Name, prim: ValuePrimitive, args: Seq[Atom], body:Tree)
extends Tree
case class LetC(cnts: Seq[Cnt], body: Tree) extends Tree
case class LetF(funs: Seq[Fun], body: Tree) extends Tree
case class AppC(cnt: Name, args: Seq[Atom]) extends Tree
case class AppF(fun: Atom, retC: Name, args: Seq[Atom]) extends Tree
case class If(cond: TestPrimitive, args: Seq[Atom], thenC: Name, elseC: Name)
extends Tree
case class Halt(arg: Atom) extends Tree
case class Cnt(name: Name, args: Seq[Name], body: Tree)
case class Fun(name: Name, retC: Name, args: Seq[Name], body: Tree)
}
/**
* Module for "high-level" CPS trees: the full L3 literals and
* primitives are available.
*/
object SymbolicCPSTreeModule extends CPSTreeModule {
type Name = Symbol
type Literal = CL3Literal
type ValuePrimitive = L3ValuePrimitive
type TestPrimitive = L3TestPrimitive
}
/**
* Module for "low-level" CPS trees: the only literal values are
* integers, and the primitives work on integers and/or pointers to
* heap-allocated blocks.
*/
object SymbolicCPSTreeModuleLow extends CPSTreeModule {
type Name = Symbol
type Literal = Bits32
type ValuePrimitive = CPSValuePrimitive
type TestPrimitive = CPSTestPrimitive
}

View File

@@ -0,0 +1,91 @@
package l3
/**
* Tree checker for CPS languages. Verifies that:
* 1. names are globally unique (no name is bound more than once),
* 2. names are used in their scope.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
abstract class CPSTreeChecker[T <: CPSTreeModule](treeModule: T)
extends (T#Tree => Unit) {
import treeModule._
def apply(t: T#Tree): Unit = {
val allNames = scala.collection.mutable.Set[Name]()
def recordUniqueName(n: Name): Unit = {
if (allNames contains n)
error(s"Name ${n} is bound more than once (not globally unique).")
else
allNames += n
}
def checkName(n: Name, env: Set[Name]): Unit = {
if (! (env contains n))
error(s"Name ${n} is unbound.")
}
def checkAtom(a: Atom, env: Set[Name]): Unit = a match {
case AtomN(n) => checkName(n, env)
case _ =>
}
def checkT(t: T#Tree, cEnv: Set[Name], vEnv: Set[Name]): Unit =
(t: @unchecked) match {
case LetP(name, _, args, body) =>
recordUniqueName(name)
args.foreach(checkAtom(_, vEnv))
checkT(body, cEnv, vEnv + name)
case LetC(cnts, body) =>
val cEnv1 = cEnv ++ (cnts map (_.name))
cnts.foreach(checkC(_, cEnv1, vEnv))
checkT(body, cEnv1, vEnv)
case LetF(funs, body) =>
val vEnv1 = vEnv ++ (funs map (_.name))
funs.foreach(checkF(_, vEnv1))
checkT(body, cEnv, vEnv1)
case AppC(cnt, args) =>
checkName(cnt, cEnv)
args.foreach(checkAtom(_, vEnv))
case AppF(fun, retC, args) =>
checkAtom(fun, vEnv)
checkName(retC, cEnv)
args.foreach(checkAtom(_, vEnv))
case If(_, args, thenC, elseC) =>
args.foreach(checkAtom(_, vEnv))
checkName(thenC, cEnv)
checkName(elseC, cEnv)
case Halt(arg) =>
checkAtom(arg, vEnv)
}
def checkC(cnt: Cnt, cEnv: Set[Name], vEnv: Set[Name]): Unit = {
recordUniqueName(cnt.name)
cnt.args.foreach(recordUniqueName)
checkT(cnt.body, cEnv, vEnv ++ cnt.args)
}
def checkF(fun: Fun, vEnv: Set[Name]): Unit = {
recordUniqueName(fun.name)
recordUniqueName(fun.retC)
fun.args.foreach(recordUniqueName)
checkT(fun.body, Set(fun.retC), vEnv ++ fun.args)
}
checkT(t, Set(), Set())
}
private def error(msg: String): Unit = {
Console.println(s"Error: ${msg}")
}
}
object CPSTreeChecker {
implicit object SymbolicCPSTreeChecker
extends CPSTreeChecker(SymbolicCPSTreeModule)
implicit object SymbolicCPSTreeLowChecker
extends CPSTreeChecker(SymbolicCPSTreeModuleLow)
}

View File

@@ -0,0 +1,55 @@
package l3
import org.typelevel.paiges.Doc
class CPSTreeFormatter[T <: CPSTreeModule](treeModule: T)
extends Formatter[T#Tree] {
import Formatter.par, treeModule._
def toDoc(tree: T#Tree): Doc = {
def pullLets(tree: T#Tree): (Seq[(T#Name, Doc)], Doc) = tree match {
case LetP(name, prim, args, body) =>
val (bdgs, bodyDoc) = pullLets(body)
val pDoc = par(1, Doc.text(s"@$prim") +: (args map Doc.str))
((name, pDoc) +: bdgs, bodyDoc)
case LetC(cnts, body) =>
val (bdgs, bodyDoc) = pullLets(body)
def toBdg(c: Cnt): (T#Name, Doc) =
(c.name,
par("cnt", 2, par(1, c.args map Doc.str), toDoc(c.body)))
((cnts map toBdg) ++ bdgs, bodyDoc)
case LetF(funs, body) =>
val (bdgs, bodyDoc) = pullLets(body)
def toBdg(f: Fun): (T#Name, Doc) =
(f.name,
par("fun", 2, par(1, (f.retC +: f.args) map Doc.str), toDoc(f.body)))
((funs map toBdg) ++ bdgs, bodyDoc)
case other =>
(Seq(), toDoc(other))
}
(tree: @unchecked) match {
case LetP(_, _, _, _) | LetC(_, _) | LetF(_, _) =>
val (bdgs, bodyDoc) = pullLets(tree)
val tag = if (bdgs.length > 1) "let*" else "let"
val bdgsDoc = par(1, bdgs map (b => par(1, Doc.str(b._1), b._2)))
par(tag, 2, bdgsDoc, bodyDoc)
case AppF(fun, retC, args) =>
par(1, (fun +: retC +: args) map Doc.str)
case AppC(cont, args) =>
par(1, (cont +: args) map Doc.str)
case If(p, args, thenC, elseC) =>
val pDoc = par(1, Doc.text(s"@$p") +: (args map Doc.str))
par("if", 2, pDoc, Doc.str(thenC), Doc.str(elseC))
case Halt(arg) =>
par("halt", 2, Doc.str(arg))
}
}
}
object CPSTreeFormatter {
implicit object SymbolicCPSTreeFormatter
extends CPSTreeFormatter(SymbolicCPSTreeModule)
implicit object SymbolicCPSTreeLowFormatter
extends CPSTreeFormatter(SymbolicCPSTreeModuleLow)
}

View File

@@ -0,0 +1,236 @@
package l3
import l3.{CPSTestPrimitive => CTP, CPSValuePrimitive => CVP, L3Primitive => L3P, SymbolicCPSTreeModule => High, SymbolicCPSTreeModuleLow => Low}
import scala.language.implicitConversions
object CPSValueRepresenter extends (High.Tree => Low.Tree) {
// Implicit conversion from Int to AtomL to make life easier (but code may get harder to read...)
implicit def intToAtomL(int: Int): Low.AtomL = Low.AtomL(int)
def apply(v1: High.Tree): Low.Tree = v1 match {
case High.LetP(name, prim, args, body) =>
// Note: to represent integers, add operations have been replaced by (faster) OR operations
val p = freshName("p") // Not necessarily needed
val atomP = Low.AtomN(p)
val argsT = args.map(translateAtom)
/**
* Convert an int to its internal representation (* 2 + 1)
*
* @param target the variable in which the representation must be written
* @param source the variable in which the int currently is
* @param rest the tree to set as the body of the final let
*/
def toIntRepr(target: Low.Name, source: Low.AtomN = atomP, rest: Low.Tree = this (body)): Low.LetP = {
val p2 = freshName("fromInt")
Low.LetP(p2, CVP.ShiftLeft, Seq(source, 1),
Low.LetP(target, CVP.Or, Seq(Low.AtomN(p2), 1), rest)
)
}
/**
* Convert an internal representation of int to an actual int (>> 2)
*
* @param source the variable containing the internal representation
* @param target the variable in which to put the extracted int, by default a new variable
* @param rest a function taking the new variable name and producing the tree to set in the final let
*/
def fromIntRepr(source: Low.Atom, target: Low.Name = freshName("toInt"))(rest: Low.AtomN => Low.Tree): Low.LetP = {
Low.LetP(target, CVP.ShiftRight, Seq(source, 1), rest(Low.AtomN(target)))
}
prim match {
// Block Primitives
case L3P.BlockAlloc(tag) =>
fromIntRepr(argsT.head)(intHolder => Low.LetP(name, CVP.BlockAlloc(tag), Seq(intHolder), this (body)))
case L3P.BlockTag => Low.LetP(p, CVP.BlockTag, Seq(argsT.head), toIntRepr(name))
case L3P.BlockLength => Low.LetP(p, CVP.BlockLength, Seq(argsT.head), toIntRepr(name))
case L3P.BlockGet =>
fromIntRepr(argsT(1))(field => Low.LetP(name, CVP.BlockGet, Seq(argsT.head, field), this (body)))
case L3P.BlockSet =>
fromIntRepr(argsT(1))(field => Low.LetP(name, CVP.BlockSet, Seq(argsT.head, field, argsT(2)), this (body)))
// Int Primitives
case L3P.IntAdd =>
Low.LetP(p, CVP.Add, argsT,
Low.LetP(name, CVP.Sub, Seq(atomP, 1), this (body))
)
case L3P.IntSub =>
Low.LetP(p, CVP.Sub, argsT,
Low.LetP(name, CVP.Or, Seq(atomP, 1), this (body))
)
case L3P.IntMul =>
Low.LetP(p, CVP.Sub, Seq(argsT.head, 1),
withFreshAtomN("p")((q, atomQ) => Low.LetP(q, CVP.ShiftRight, Seq(argsT(1), 1),
withFreshAtomN("p")((r, atomR) => Low.LetP(r, CVP.Mul, Seq(atomP, atomQ),
Low.LetP(name, CVP.Or, Seq(atomR, 1), this (body))
))
))
)
case L3P.IntDiv =>
fromIntRepr(argsT.head)(top =>
fromIntRepr(argsT(1))(bottom =>
withFreshAtomN("p")((p, quot) => Low.LetP(p, CVP.Div, Seq(top, bottom),
withFreshAtomN("p")((p, shifted) => Low.LetP(p, CVP.ShiftLeft, Seq(quot, 1),
Low.LetP(name, CVP.Or, Seq(shifted, 1), this (body))
))
))
)
)
case L3P.IntMod =>
fromIntRepr(argsT.head)(top =>
fromIntRepr(argsT(1))(bottom =>
withFreshAtomN("p")((p, q)=> Low.LetP(p, CVP.Mod, Seq(top, bottom),
withFreshAtomN("p")((p, s)=> Low.LetP(p, CVP.ShiftLeft, Seq(q, 1),
Low.LetP(name, CVP.Or, Seq(s, 1), this(body))
))
))
)
)
case L3P.IntShiftRight =>
fromIntRepr(argsT(1))(offset =>
Low.LetP(p, CVP.ShiftRight, Seq(argsT.head, offset),
Low.LetP(name, CVP.Or, Seq(atomP, 1), this (body))
)
)
case L3P.IntShiftLeft =>
fromIntRepr(argsT(1))(offset =>
withFreshAtomN("p")((xored, xoredAtom) => Low.LetP(xored, CVP.XOr, Seq(argsT.head, 1), // set last bit to 0
Low.LetP(p, CVP.ShiftLeft, Seq(xoredAtom, offset),
Low.LetP(name, CVP.Or, Seq(atomP, 1), this (body)) // set back last bit to 1
)
))
)
case L3P.IntBitwiseAnd => Low.LetP(name, CVP.And, argsT, this (body))
case L3P.IntBitwiseOr => Low.LetP(name, CVP.Or, argsT, this (body))
case L3P.IntBitwiseXOr =>
Low.LetP(p, CVP.XOr, argsT,
Low.LetP(name, CVP.Or, Seq(atomP, 1), this (body)) // reset the last bit to 1
)
// Byte Primitives
case L3P.ByteRead => Low.LetP(p, CVP.ByteRead, Seq(), toIntRepr(name)) // read byte and convert to int
case L3P.ByteWrite =>
fromIntRepr(argsT.head)(int =>
Low.LetP(name, CVP.ByteWrite, Seq(int), this (body)) // convert from int and write
)
// Type conversions
case L3P.IntToChar =>
Low.LetP(p, CVP.ShiftLeft, Seq(argsT.head, 2),
Low.LetP(name, CVP.Or, Seq(atomP, 2), this (body))
)
case L3P.CharToInt =>
Low.LetP(name, CVP.ShiftRight, Seq(argsT.head, 2), this (body))
// ID
case L3P.Id => Low.LetP(name, CVP.Id, argsT, this (body))
}
case High.LetC(cnts, body) =>
val lowCnts = cnts.map { case High.Cnt(name, args, cntBody) => Low.Cnt(name, args, this (cntBody)) }
Low.LetC(lowCnts, this (body))
case High.LetF(fun, body) =>
val lowFuns = fun.map { case High.Fun(name, retC, args, body) => Low.Fun(name, retC, args, this (body)) }
Low.LetF(lowFuns, this (body))
case High.AppC(cnt, args) => Low.AppC(cnt, args.map(translateAtom))
case High.AppF(fun, retC, args) => Low.AppF(translateAtom(fun), retC, args.map(translateAtom))
case High.If(cnd, args, thenn, elsee) =>
val p = freshName("p") // Not necessarily needed
val atomP = Low.AtomN(p)
val argsT = args.map(translateAtom)
cnd match {
case L3P.IntLt => Low.If(CTP.Lt, argsT, thenn, elsee)
case L3P.IntLe => Low.If(CTP.Le, argsT, thenn, elsee)
case L3P.Eq => Low.If(CTP.Eq, argsT, thenn, elsee)
// Typechecks: int? block? char?...
case typeCheck =>
val mv = TypesMagicValues.condMapping(typeCheck)
Low.LetP(p, CVP.And, Seq(argsT.head, Low.AtomL(mv.mask)),
Low.If(CTP.Eq, Seq(atomP, Low.AtomL(mv.magicValue)), thenn, elsee)
)
}
case High.Halt(arg) => Low.Halt(translateAtom(arg))
}
def withFreshAtomN[T](s: String)(body: (Symbol, Low.AtomN) => T): T = {
val (n, a) = freshAtomN(s)
body(n, a)
}
def freshAtomN(s: String): (Symbol, Low.AtomN) = {
val name = freshName(s)
(name, Low.AtomN(name))
}
def freshName(s: String): Symbol = Symbol.fresh(s)
def translateAtom(v1: High.Atom): Low.Atom = {
import TypesMagicValues._
v1 match {
case High.AtomN(name) => Low.AtomN(name)
case High.AtomL(IntLit(v)) => Low.AtomL((v.toInt << 1) | Integer.magicValue)
case High.AtomL(CharLit(v)) => Low.AtomL((v << 3) | Character.magicValue) // 6 == 0b0110
case High.AtomL(BooleanLit(v)) =>
val booleanBase = Boolean.magicValue
Low.AtomL {
if (v) 0x10 | booleanBase // 0x10 = 0b0001 0000
else booleanBase
}
case High.AtomL(UnitLit) => Low.AtomL(Unit.magicValue) // 2 == 0b0010
}
}
private object TypesMagicValues {
val condMapping: Map[L3TestPrimitive, TypeMagicValues] = Map(
L3P.BlockP -> Block,
L3P.IntP -> Integer,
L3P.CharP -> Character,
L3P.BoolP -> Boolean,
L3P.UnitP -> Unit
)
/**
* Stores constants representing a type
*
* @param magicValue the last significant bits identifying this type
* @param mask the mask to extract the LSB identifying this type
*/
sealed class TypeMagicValues(val magicValue: Int, val mask: Int)
case object Integer extends TypeMagicValues(1, 1)
case object Block extends TypeMagicValues(0, 3)
case object Character extends TypeMagicValues(6, 7)
case object Boolean extends TypeMagicValues(10, 15)
case object Unit extends TypeMagicValues(2, 15)
}
}

View File

@@ -0,0 +1,24 @@
package l3
import org.typelevel.paiges.Doc
/**
* Utility methods for formatting.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
trait Formatter[-T] {
def toDoc(value: T): Doc
}
object Formatter {
def par(nest: Int, ds: Iterable[Doc]): Doc =
(Doc.char('(') + Doc.intercalate(Doc.line, ds).nested(nest) + Doc.char(')'))
.grouped
def par(nest: Int, d1: Doc): Doc = par(nest, Seq(d1))
def par(nest: Int, d1: Doc, d2: Doc): Doc = par(nest, Seq(d1, d2))
def par(tag: String, nest: Int, d1: Doc, ds: Doc*): Doc =
par(nest, (Doc.text(tag) space d1.aligned) +: ds)
}

View File

@@ -0,0 +1,17 @@
package l3
/**
* Helper module for IO functions in L₃ and intermediate languages.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object IO {
def readByte(): Int =
System.in.read()
def writeByte(c: Int): Unit = {
System.out.write(c)
System.out.flush()
}
}

View File

@@ -0,0 +1,83 @@
package l3
import java.io.IOException
import java.nio.file.Path
import java.nio.file.Files.newBufferedReader
import scala.util.Using.{resource => using}
import scala.collection.mutable.ArrayBuffer
/**
* File reading for L₃ (both modules and source files).
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object L3FileReader {
def readFilesExpandingModules(base: Path, pathNames: Seq[String])
: Either[String, (String, Int => Position)] =
try {
Right(readFiles(base, expandModules(base, pathNames)))
} catch {
case e: IOException => Left(e.getMessage)
}
private def expandModules(base: Path, pathNames: Seq[String]): Seq[Path] = {
def readModule(modulePath: Path): Seq[String] = {
using(newBufferedReader(modulePath)) { moduleReader =>
Iterator.continually(moduleReader.readLine)
.takeWhile (_ != null)
.map (_.trim)
.filterNot { s => (s startsWith ";") || s.isEmpty }
.toList
}
}
def expand(base: Path, pathNames: Seq[String]): Seq[Path] = {
val basePath = base.toAbsolutePath.normalize
pathNames flatMap { pn =>
val p = basePath.resolve(pn).normalize
if (p.getFileName.toString endsWith ".l3m")
expandModules(p.getParent, readModule(p))
else
Seq(p)
}
}
expand(base, pathNames).distinct
}
private def readFiles(basePath: Path,
paths: Seq[Path]): (String, Int=>Position) = {
def indexToPosition(indices: Array[Int],
fileLines: Int => Option[(String, Int)])
(index: Int): Position = {
val p = {
val p0 = java.util.Arrays.binarySearch(indices, index)
// FIXME: use code-points count to get column number, not char count!
if (p0 < 0) (-p0 - 2) else p0
}
fileLines(p)
.map { case (f, l) => new FilePosition(f, l, index - indices(p)) }
.getOrElse(UnknownPosition)
}
val progB = new StringBuilder()
val indicesB = ArrayBuffer.empty[Int]
val fileLinesB = ArrayBuffer.empty[(String, Int)]
for (path <- paths) {
val relPath = basePath relativize path
using(newBufferedReader(path)) { fileReader =>
Iterator.continually(fileReader.readLine)
.takeWhile(_ != null)
.zipWithIndex
.foreach { case (line, lineIndex) =>
val index = progB.size
progB ++= line; progB += '\n'
indicesB += index
fileLinesB += ((relPath.toString, lineIndex + 1))
}
}
}
(progB.result(), indexToPosition(indicesB.toArray, fileLinesB.toArray.lift))
}
}

View File

@@ -0,0 +1,47 @@
package l3
final class L3Int private(private val v: Int) extends AnyVal {
def toInt: Int = v
def +(that: L3Int): L3Int = L3Int.ofIntClipped(this.v + that.v)
def -(that: L3Int): L3Int = L3Int.ofIntClipped(this.v - that.v)
def *(that: L3Int): L3Int = L3Int.ofIntClipped(this.v * that.v)
def /(that: L3Int): L3Int = L3Int.ofIntClipped(this.v / that.v)
def %(that: L3Int): L3Int = L3Int.ofIntClipped(this.v % that.v)
def &(that: L3Int): L3Int = L3Int.ofIntClipped(this.v & that.v)
def |(that: L3Int): L3Int = L3Int.ofIntClipped(this.v | that.v)
def ^(that: L3Int): L3Int = L3Int.ofIntClipped(this.v ^ that.v)
def <<(that: L3Int): L3Int = L3Int.ofIntClipped(this.v << that.v)
def >>(that: L3Int): L3Int = L3Int.ofIntClipped(this.v >> that.v)
def <(that: L3Int): Boolean = this.v < that.v
def <=(that: L3Int): Boolean = this.v <= that.v
def >(that: L3Int): Boolean = this.v > that.v
def >=(that: L3Int): Boolean = this.v >= that.v
override def toString: String = v.toString
}
object L3Int {
private def ofIntClipped(v: Int): L3Int =
L3Int((v << 1) >> 1)
def canConvertFromInt(i: Int): Boolean =
fitsInNSignedBits(L3_INT_BITS)(i)
def canConvertFromIntUnsigned(i: Int): Boolean =
fitsInNUnsignedBits(L3_INT_BITS)(i)
def ofIntUnsigned(v: Int): L3Int = {
require(canConvertFromIntUnsigned(v))
new L3Int((v << 1) >> 1)
}
def apply(v: Int): L3Int = {
require(canConvertFromInt(v))
new L3Int(v)
}
def unapply(v: L3Int): Option[Int] =
Some(v.toInt)
}

View File

@@ -0,0 +1,254 @@
package l3
import fastparse._
import NominalCL3TreeModule._
/**
* Parsing (including lexical analysis) for the L₃ language.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
object L3Parser {
def parse(programText: String,
indexToPosition: Int => Position): Either[String, Tree] = {
val parser = new S(indexToPosition)
fastparse.parse(programText, parser.program(_)) match {
case Parsed.Success(program, _) =>
Right(program)
case Parsed.Failure(lp, index, _) =>
Left(s"${indexToPosition(index)}: parse error (expected: $lp)")
}
}
// Lexical analysis (for which whitespace is significant)
private class L(indexToPosition: Int => Position) {
import NoWhitespace._
private implicit val indexToPositionView = indexToPosition
// Literals
private def sign[_: P] = P(CharIn("+\\-"))
private def prefix2[_: P] = IgnoreCase("#b")
private def prefix16[_: P] = IgnoreCase("#x")
private def digit2[_: P] = CharIn("0-1")
private def digit10[_: P] = CharIn("0-9")
private def digit16[_: P] = CharIn("0-9a-fA-F")
private def unicodeChar[_: P] = P(
CharPred(!Character.isHighSurrogate(_))
| (CharPred(Character.isHighSurrogate)
~ CharPred(Character.isLowSurrogate)))
private def integer2[_: P] = P(
(prefix2 ~/ digit2.rep(1).!)
.map { Integer.parseInt(_, 2) }
.filter { L3Int.canConvertFromIntUnsigned(_) }
.map { L3Int.ofIntUnsigned(_) })
private def integer16[_: P] = P(
(prefix16 ~/ digit16.rep(1).!)
.map { Integer.parseInt(_, 16) }
.filter { L3Int.canConvertFromIntUnsigned(_) }
.map { L3Int.ofIntUnsigned(_) })
private def integer10[_: P] = P(
(sign.? ~ digit10 ~/ digit10.rep).!
.map { Integer.parseInt(_, 10) }
.filter { L3Int.canConvertFromInt(_) })
.map { L3Int(_) }
private def integer[_: P] = P(
(Index ~ (integer2 | integer10 | integer16))
.map { case (i, v) => Lit(IntLit(v))(i) })
private def string[_: P] = P(
(Index ~ "\"" ~/ CharPred(c => c != '\n' && c != '"').rep.! ~ "\"")
.map { case (i, s) => sStringLit(s)(i) })
private def char[_: P] = P(
(Index ~ "'" ~/ unicodeChar.! ~ "'")
.map { case (i, c) => Lit(CharLit(c.codePointAt(0)))(i) })
private def bool[_: P] = P(
(Index ~ StringIn("#t", "#f").!)
.map { case (i, v) => Lit(BooleanLit(v == "#t"))(i) })
private def unit[_: P] = P(
(Index ~ "#u")
.map { case i => Lit(UnitLit)(i) })
def literal[_: P] = P(integer | string | char | bool | unit)
// Identifiers
private def identStart[_: P] = P(CharIn("|!%&*+\\-./:<=>?^_~a-zA-Z"))
private def identCont[_: P] = P(identStart | digit10)
private def identSuffix[_: P] = P("@" ~ digit10.rep(1))
def identStr[_: P] = P(
(identStart ~/ identCont.rep ~/ identSuffix.?).!)
def identifier[_: P] = P(
(Index ~ identStr).map { case (i, n) => Ident(n)(i) })
// Keywords
def kDef[_: P] = P("def" ~ !identCont)
def kDefrec[_: P] = P("defrec" ~ !identCont)
def kFun[_: P] = P("fun" ~ !identCont)
def kLet[_: P] = P("let" ~ !identCont)
def kLet_*[_: P] = P("let*" ~ !identCont)
def kLetrec[_: P] = P("letrec" ~ !identCont)
def kRec[_: P] = P("rec" ~ !identCont)
def kBegin[_: P] = P("begin" ~ !identCont)
def kCond[_: P] = P("cond" ~ !identCont)
def kIf[_: P] = P("if" ~ !identCont)
def kAnd[_: P] = P("and" ~ !identCont)
def kOr[_: P] = P("or" ~ !identCont)
def kNot[_: P] = P("not" ~ !identCont)
def kHalt[_: P] = P("halt" ~ !identCont)
def kPrim[_: P] = P("@")
}
// Syntactic analysis (for which whitespace and comments are ignored)
private class S(indexToPosition: Int => Position) {
val lexer = new L(indexToPosition)
import lexer._
private implicit val whitespace = { implicit ctx: ParsingRun[_] =>
import NoWhitespace._
(CharIn(" \t\n\r")
| (";" ~ CharPred(c => c != '\n' && c != '\r').rep)).rep
}
private implicit val indexToPositionView = indexToPosition
def program[_: P]: P[Tree] =
P("" ~ topExpr ~ End) // The initial "" allows leading whitespace
private def topExpr[_: P]: P[Tree] = P(defP | defrecP | exprP)
private def defP[_: P] = P(
(iPar(kDef ~ identStr ~ expr) ~ topExpr)
.map { case (i, (n, v), p) => Let(Seq((n, v)), p)(i) })
private def defrecP[_: P] = P(
(iPar(kDefrec ~ identStr ~ anonFun) ~ topExpr)
.map { case (i, (n, (a, b)), p) =>
LetRec(Seq(Fun(n, a, b)(i)), p)(i) })
private def exprP[_: P] = P(
(ix(expr ~ topExpr.?))
.map { case (i, (e, p)) => sBegin(e +: p.toSeq)(i) })
private def expr[_: P]: P[Tree] = P(
fun | let | let_* | letrec | rec | begin | cond | if_ | and | or | not
| halt | app | prim | literal | identifier)
private def exprs[_: P] = expr.rep
private def iExprs[_: P] = ix(exprs)
private def exprs1[_: P] = expr.rep(1)
private def iExprs1[_: P] = ix(exprs1)
private def anonFun[_: P] = P(
par("fun" ~ par(identStr.rep) ~ iExprs1)
.map { case (a, (i, e)) => (a, sBegin(e)(i)) })
private def funDef[_: P] = P(
iPar(identStr ~ anonFun)
.map { case (i, (n, (a, e))) => Fun(n, a, e)(i) })
private def binding[_: P] = P(
par(identStr ~ expr)
.map { case (i, e) => (i, e) })
private def bindings[_: P] = P(
par(binding.rep))
private def fun[_: P] = P(
ix(anonFun)
.map { case (i, (a, e)) => sFun(a, e)(i) })
private def let[_: P] = P(
iPar(kLet ~/ bindings ~ iExprs1)
.map { case (i1, (b, (i2, e))) => Let(b, sBegin(e)(i2))(i1) })
private def let_*[_: P] = P(
iPar(kLet_* ~/ bindings ~ iExprs1)
.map { case (i1, (b, (i2, e))) => sLet_*(b, sBegin(e)(i2))(i1) })
private def letrec[_: P]= P(
iPar(kLetrec ~/ par(funDef.rep) ~ iExprs1)
.map { case (i1, (f, (i2, e))) => LetRec(f, sBegin(e)(i2))(i1) })
private def rec[_: P] = P(
iPar(kRec ~/ identStr ~ bindings ~ iExprs1)
.map { case (i1, (n, b, (i2, e))) => sRec(n, b, sBegin(e)(i2))(i1) })
private def begin[_: P] = P(
iPar(kBegin ~/ exprs1)
.map { case (i, e) => sBegin(e)(i) })
private def cond[_: P] = P(
iPar(kCond ~/ par(expr ~ exprs1).rep(1))
.map { case (i, a) => sCond(a)(i) })
private def if_[_: P] = P(
iPar(kIf ~ expr ~ expr ~ expr.?)
.map { case (i, (c, t, f)) =>
If(c, t, f.getOrElse(Lit(UnitLit)(i)))(i) })
private def and[_: P] = P(
iPar(kAnd ~/ expr.rep(2))
.map { case (i, es) => sAnd(es)(i) })
private def or[_: P] = P(
iPar(kOr ~/ expr.rep(2))
.map { case (i, es) => sOr(es)(i) })
private def not[_: P] = P(
iPar(kNot ~/ expr)
.map { case (i, e) => sNot(e)(i) })
private def app[_: P] = P(
iPar(expr ~ exprs)
.map { case (i, (e, es)) => App(e, es)(i) })
private def prim[_: P] = P(
iPar(kPrim ~/ identStr ~ exprs)
.map { case (i, (p, es)) => Prim(p, es)(i) })
private def halt[_: P] = P(
iPar(kHalt ~/ expr)
.map { case (i, e) => Halt(e)(i) })
private def par[T, _: P](b: =>P[T]): P[T] = P("(" ~ b ~ ")")
private def ix[T, _: P](b: =>P[T]): P[(Int, T)] = Index ~ b
private def iPar[T, _: P](b: =>P[T]): P[(Int, T)] = ix(par(b))
}
// Syntactic sugar translation.
private var freshCounter = 0
private def freshName(prefix: String): String = {
freshCounter += 1
prefix + "$" + freshCounter
}
private def sFun(args: Seq[String], body: Tree)
(implicit p: Position): Tree = {
val fName = freshName("fun")
LetRec(Seq(Fun(fName, args, body)), Ident(fName))
}
private def sLet_*(bdgs: Seq[(String,Tree)], body: Tree)
(implicit p: Position): Tree =
bdgs.foldRight(body)((b, t) => Let(Seq(b), t))
private def sBegin(exprs: Seq[Tree])(implicit p: Position): Tree =
exprs reduceRight { (e1, e2) => Let(Seq((freshName("begin"), e1)), e2) }
private def sRec(name: String, bdgs: Seq[(String, Tree)], body: Tree)
(implicit p: Position) =
LetRec(Seq(Fun(name, bdgs map { _._1 }, body)),
App(Ident(name), bdgs map { _._2 }))
private def sAnd(es: Seq[Tree])(implicit p: Position): Tree =
es reduceRight { If(_, _, Lit(BooleanLit(false))) }
private def sOr(es: Seq[Tree])(implicit p: Position): Tree = {
es reduceRight { (e, r) =>
val en = freshName("or")
Let(Seq((en, e)), If(Ident(en), Ident(en), r))
}
}
private def sNot(e: Tree)(implicit p: Position): Tree =
If(e, Lit(BooleanLit(false)), Lit(BooleanLit(true)))
private def sCond(clses: Seq[(Tree, Seq[Tree])])(implicit p: Position): Tree =
clses.foldRight(Lit(UnitLit) : Tree){ case ((c, t), e) =>
If(c, sBegin(t), e) }
private def sStringLit(s: String)(implicit p: Position): Tree = {
val b = freshName("string")
val cs = codePoints(s)
Let(Seq((b, Prim("block-alloc-"+ BlockTag.String.id,
Seq(Lit(IntLit(L3Int(cs.length))))))),
sBegin((cs.zipWithIndex map {case (c, i) =>
Prim("block-set!",
Seq(Ident(b), Lit(IntLit(L3Int(i))), Lit(CharLit(c)))) })
:+ Ident(b)))
}
private def codePoints(chars: Seq[Char]): Seq[L3Char] = chars match {
case Seq(h, l, r @ _*) if (Character.isSurrogatePair(h, l)) =>
Character.toCodePoint(h, l) +: codePoints(r)
case Seq(c, r @ _*) =>
c.toInt +: codePoints(r)
case Seq() =>
Seq()
}
}

View File

@@ -0,0 +1,92 @@
package l3
/**
* A class for L₃ primitives.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
sealed abstract class L3Primitive(val name: String, val arity: Int) {
override def toString: String = name
}
sealed abstract class L3ValuePrimitive(name: String, arity: Int)
extends L3Primitive(name, arity)
sealed abstract class L3TestPrimitive(name: String, arity: Int)
extends L3Primitive(name, arity)
object L3Primitive {
// Primitives on blocks
case class BlockAlloc(tag: L3BlockTag)
extends L3ValuePrimitive(s"block-alloc-${tag}", 1)
case object BlockP extends L3TestPrimitive("block?", 1)
case object BlockTag extends L3ValuePrimitive("block-tag", 1)
case object BlockLength extends L3ValuePrimitive("block-length", 1)
case object BlockGet extends L3ValuePrimitive("block-get", 2)
case object BlockSet extends L3ValuePrimitive("block-set!", 3)
// Primitives on integers
case object IntP extends L3TestPrimitive("int?", 1)
case object IntAdd extends L3ValuePrimitive("+", 2)
case object IntSub extends L3ValuePrimitive("-", 2)
case object IntMul extends L3ValuePrimitive("*", 2)
case object IntDiv extends L3ValuePrimitive("/", 2)
case object IntMod extends L3ValuePrimitive("%", 2)
case object IntShiftLeft extends L3ValuePrimitive("shift-left", 2)
case object IntShiftRight extends L3ValuePrimitive("shift-right", 2)
case object IntBitwiseAnd extends L3ValuePrimitive("and", 2)
case object IntBitwiseOr extends L3ValuePrimitive("or", 2)
case object IntBitwiseXOr extends L3ValuePrimitive("xor", 2)
case object IntLt extends L3TestPrimitive("<", 2)
case object IntLe extends L3TestPrimitive("<=", 2)
case object ByteRead extends L3ValuePrimitive("byte-read", 0)
case object ByteWrite extends L3ValuePrimitive("byte-write", 1)
case object IntToChar extends L3ValuePrimitive("int->char", 1)
// Primitives on characters
case object CharP extends L3TestPrimitive("char?", 1)
case object CharToInt extends L3ValuePrimitive("char->int", 1)
// Primitives on booleans
case object BoolP extends L3TestPrimitive("bool?", 1)
// Primitives on unit
case object UnitP extends L3TestPrimitive("unit?", 1)
// Primitives on arbitrary values
case object Eq extends L3TestPrimitive("=", 2)
case object Id extends L3ValuePrimitive("id", 1)
def isDefinedAt(name: String): Boolean =
byName isDefinedAt name
def isDefinedAt(name: String, arity: Int): Boolean =
(byName isDefinedAt name) && (byName(name).arity == arity)
def apply(name: String): L3Primitive =
byName(name)
private val blockAllocators = for (i <- 0 to 200) yield BlockAlloc(i)
// Note: private primitives (id and block-alloc-n for n > 200) are ommitted
// on purpose from this map, as they are not meant to be used by user code.
private val byName: Map[String, L3Primitive] =
Map((Seq(BlockP, BlockTag, BlockLength, BlockGet, BlockSet,
IntP, IntAdd, IntSub, IntMul, IntDiv, IntMod,
IntShiftLeft, IntShiftRight,
IntBitwiseAnd, IntBitwiseOr, IntBitwiseXOr,
IntLt, IntLe, Eq, IntToChar,
CharP, ByteRead, ByteWrite, CharToInt,
BoolP,
UnitP) ++ blockAllocators)
map { p => (p.name, p) } : _*)
}

View File

@@ -0,0 +1,58 @@
package l3
import java.io.PrintWriter
import java.nio.file.{ Files, Paths }
import l3.SymbolicCL3TreeModule.Tree
import CL3TreeFormatter._ // Implicits required for CL3 tree printing
import CPSTreeFormatter._ // Implicits required for CPS tree printing
import CPSTreeChecker._ // Implicits required for CPS tree checking
object Main {
def main(args: Array[String]): Unit = {
val backEnd: Tree => TerminalPhaseResult = (
CL3ToCPSTranslator
andThen CPSValueRepresenter
andThen treePrinter("---------- After value representation")
andThen treeChecker
andThen CPSInterpreterLowNoCC
)
val basePath = Paths.get(".").toAbsolutePath
Either.cond(! args.isEmpty, args.toIndexedSeq, "no input file given")
.flatMap(L3FileReader.readFilesExpandingModules(basePath, _))
.flatMap(p => L3Parser.parse(p._1, p._2))
.flatMap(CL3NameAnalyzer)
.flatMap(backEnd) match {
case Right((retCode, maybeMsg)) =>
maybeMsg foreach println
sys.exit(retCode)
case Left(errMsg) =>
println(s"Error: $errMsg")
sys.exit(1)
}
}
private lazy val outPrintWriter =
new PrintWriter(System.out, true)
private def treeChecker[T <: CPSTreeModule](implicit c: CPSTreeChecker[T]) =
passThrough(c)
private def treePrinter[T](msg: String)(implicit f: Formatter[T]): T => T =
passThrough { tree =>
outPrintWriter.println(msg)
f.toDoc(tree).writeTo(80, outPrintWriter)
outPrintWriter.println()
}
private def seqPrinter[T](msg: String): Seq[T] => Seq[T] =
passThrough { program =>
outPrintWriter.println(msg)
program foreach outPrintWriter.println
}
private def passThrough[T](f: T => Unit): T => T =
{ t: T => f(t); t }
}

View File

@@ -0,0 +1,12 @@
package l3
sealed trait Position
final class FilePosition(fileName: String, line: Int, column: Int)
extends Position {
override def toString: String = s"$fileName:$line:$column"
}
object UnknownPosition extends Position {
override def toString: String = "<unknown position>"
}

View File

@@ -0,0 +1,32 @@
package l3
/**
* A class for symbols, i.e. globally-unique names.
*
* @author Michel Schinz <Michel.Schinz@epfl.ch>
*/
final class Symbol(val name: String, idProvider: => Int) {
private[this] lazy val id =
idProvider
def copy(): Symbol =
new Symbol(name, idProvider)
override def toString: String =
if (id == 0) name else s"${name}_${id}"
}
object Symbol {
private[this] val counters = scala.collection.mutable.HashMap[String,Int]()
def fresh(name: String): Symbol = {
def id: Int = {
val id = counters.getOrElse(name, 0)
counters.put(name, id + 1)
id
}
new Symbol(name, id)
}
}

View File

@@ -0,0 +1,49 @@
package object l3 {
type TerminalPhaseResult = Either[String, (Int, Option[String])]
type L3BlockTag = Int
type L3Char = Int
// A 32-bit integer (which could contain a pointer, a tagged value,
// or an untagged value).
type Bits32 = Int
val L3_INT_BITS = java.lang.Integer.SIZE - 1
// Bit twiddling
def bitsToIntMSBF(bits: Int*): Int =
bits.foldLeft(0) { (v, b) => (v << 1) | b }
def fitsInNSignedBits(bits: Int)(value: Int): Boolean = {
require(0 <= bits && bits < Integer.SIZE)
val value1 = value >> (bits - 1)
value1 == 0 || value1 == -1
}
def fitsInNUnsignedBits(bits: Int)(value: Int): Boolean = {
require(0 <= bits && bits < Integer.SIZE)
(value >>> bits) == 0
}
// Substitutions
type Subst[T] = Map[T, T]
def emptySubst[T]: Subst[T] =
Map.empty[T, T].withDefault(identity)
def subst[T](from: T, to: T): Subst[T] =
emptySubst[T] + (from -> to)
def subst[T](from: Seq[T], to: Seq[T]): Subst[T] =
emptySubst[T] ++ (from zip to)
// Fixed point computation
private def fixedPoint[T](start: T, maxIt: Option[Int])(f: T=>T): T = {
val approx = LazyList.iterate(start, maxIt getOrElse Integer.MAX_VALUE)(f)
val (improv, stable) = ((approx zip approx.tail) span (p => p._1 != p._2))
if (improv.isEmpty) stable.head._1 else improv.last._2
}
private[l3] def fixedPoint[T](start: T)(f: T=>T): T =
fixedPoint(start, None)(f)
private[l3] def fixedPoint[T](start: T, maxIt: Int)(f: T=>T): T =
fixedPoint(start, Some(maxIt))(f)
}

View File

@@ -0,0 +1,59 @@
package l3
import java.io.{ ByteArrayInputStream }
import java.nio.file.{ Files, Paths, Path }
import java.nio.charset.StandardCharsets.UTF_8
import scala.util.Using.{resource => using}
import utest._
import SymbolicCL3TreeModule.Tree
trait ExamplesTests {
val backEnd: Tree => TerminalPhaseResult
def compileAndRun(fileName: String, input: String): Either[String, String] = {
using(new ByteArrayInputStream(input.getBytes(UTF_8))) { inS =>
val in0 = System.in
try {
System.setIn(inS)
L3Tester.compileAndRun(backEnd)(Seq(fileName))
} finally {
System.setIn(in0)
}
}
}
def readFile(fileName: String): String =
new String(Files.readAllBytes(Paths.get(fileName)), UTF_8)
def testExpectedOutput(implicit path: utest.framework.TestPath) = {
val testName = path.value.last
val input = readFile(s"../tests/${testName}.in")
val expectedOut = readFile(s"../tests/${testName}.out")
assertMatch(compileAndRun(s"../examples/${testName}.l3m", input)) {
case Right(s: String) if s == expectedOut =>
}
}
val tests = Tests {
// Note: sudoku is too slow to be included here
test("bignums") { testExpectedOutput }
test("maze") { testExpectedOutput }
test("queens") { testExpectedOutput }
test("unimaze") { testExpectedOutput }
}
}
object ExamplesTests1 extends TestSuite with ExamplesTests {
val backEnd = L3Tester.backEnd1
}
object ExamplesTests2 extends TestSuite with ExamplesTests {
val backEnd = L3Tester.backEnd2
}
object ExamplesTests3 extends TestSuite with ExamplesTests {
val backEnd = L3Tester.backEnd3
}

View File

@@ -0,0 +1,58 @@
package l3
import java.io.{ ByteArrayOutputStream, PrintStream }
import java.nio.file.{ Paths }
import scala.util.Using.{resource => using}
import SymbolicCL3TreeModule.Tree
object L3Tester {
def compile[T](backEnd: Tree => Either[String, T])
(inFileNames: Seq[String]): Either[String, T] = {
val basePath = Paths.get(".").toAbsolutePath.normalize
Right(inFileNames)
.flatMap(L3FileReader.readFilesExpandingModules(basePath, _))
.flatMap(p => L3Parser.parse(p._1, p._2))
.flatMap(CL3NameAnalyzer)
.flatMap(backEnd)
}
def compileNoFail[T](backEnd: Tree => T)
(inFileNames: Seq[String]): Either[String, T] =
compile(t => Right(backEnd(t)))(inFileNames)
def compileAndRun(backEnd: Tree => TerminalPhaseResult)
(inFileNames: Seq[String]): Either[String, String] = {
def outputCapturingBackend(t: Tree): Either[String, String] = {
val outBS = new ByteArrayOutputStream()
using(new PrintStream(outBS)) { outPS =>
val out0 = System.out
try {
System.setOut(outPS)
backEnd(t)
.flatMap(_ => Right(outBS.toString("UTF-8")))
} finally {
System.setOut(out0)
}
}
}
compile(outputCapturingBackend(_))(inFileNames)
}
val backEnd1 = (
CL3Interpreter
)
val backEnd2 = (
CL3ToCPSTranslator
andThen CPSInterpreterHigh
)
val backEnd3 = (
CL3ToCPSTranslator
andThen CPSValueRepresenter
andThen CPSInterpreterLowNoCC
)
}

View File

@@ -0,0 +1,95 @@
package l3
import utest._
import SymbolicCL3TreeModule.Tree
trait SyntheticTests {
val backEnd: Tree => TerminalPhaseResult
def compileAndRun(inFileNames: String*): Either[String, String] =
L3Tester.compileAndRun(backEnd)(inFileNames)
// Validate the output of a self-validating test.
def isValidTestResult(s: String): Boolean =
! s.isEmpty && (s == ((s(0) +: ('A' to s(0))).mkString))
def testSelfValidatingOutput(implicit path: utest.framework.TestPath) = {
val testFileName = "../tests/" + path.value.last.split(" ")(0) + ".l3"
assertMatch(compileAndRun(testFileName)) {
case Right(s: String) if isValidTestResult(s) =>
}
}
val tests = Tests {
test("primitives") {
// Block primitives
test("prim-blockp (@block?)"){ testSelfValidatingOutput }
test("prim-block-alloc (@block-alloc-0)"){ testSelfValidatingOutput }
test("prim-block-tag (@block-tag)"){ testSelfValidatingOutput }
test("prim-block-length (@block-length)"){ testSelfValidatingOutput }
test("prim-block-get-set (@block-[sg]et,)"){ testSelfValidatingOutput }
// Integer primitives
test("prim-intp (@int?)"){ testSelfValidatingOutput }
test("prim-add (@+)"){ testSelfValidatingOutput }
test("prim-sub (@-)"){ testSelfValidatingOutput }
test("prim-mul (@*)"){ testSelfValidatingOutput }
test("prim-div (@/)"){ testSelfValidatingOutput }
test("prim-mod (@%)"){ testSelfValidatingOutput }
test("prim-shift-left (@shift-left)"){ testSelfValidatingOutput }
test("prim-shift-right (@shift-right)"){ testSelfValidatingOutput }
test("prim-and (@and)"){ testSelfValidatingOutput }
test("prim-or (@or)"){ testSelfValidatingOutput }
test("prim-xor (@xor)"){ testSelfValidatingOutput }
test("prim-lt (@<)"){ testSelfValidatingOutput }
test("prim-le (@<=)"){ testSelfValidatingOutput }
test("prim-int-to-char (@int->char)"){ testSelfValidatingOutput }
// Character primitives
test("prim-charp (@char?)"){ testSelfValidatingOutput }
test("prim-char-to-int (@char->int)"){ testSelfValidatingOutput }
// Boolean primitives
test("prim-boolp (@bool?)"){ testSelfValidatingOutput }
// Unit primitives
test("prim-unitp (@unit?)"){ testSelfValidatingOutput }
// Primitives on arbitrary values
test("prim-eq (@=)"){ testSelfValidatingOutput }
}
test("expressions") {
test("expr-let (let …)"){ testSelfValidatingOutput }
test("expr-lets (let* …)"){ testSelfValidatingOutput }
test("expr-letrec (letrec …)"){ testSelfValidatingOutput }
test("expr-rec (rec …)"){ testSelfValidatingOutput }
test("expr-fun (fun …)"){ testSelfValidatingOutput }
test("expr-begin (begin …)"){ testSelfValidatingOutput }
test("expr-if (if …)"){ testSelfValidatingOutput }
test("expr-cond (cond …)"){ testSelfValidatingOutput }
test("expr-and (and …)"){ testSelfValidatingOutput }
test("expr-or (or …)"){ testSelfValidatingOutput }
test("expr-not (not …)"){ testSelfValidatingOutput }
}
test("statements") {
test("stmt-def (def …)"){ testSelfValidatingOutput }
test("stmt-defrec (defrec …)"){ testSelfValidatingOutput }
test("stmt-halt (halt …)"){ testSelfValidatingOutput }
}
}
}
object SyntheticTests1 extends TestSuite with SyntheticTests {
val backEnd = L3Tester.backEnd1
}
object SyntheticTests2 extends TestSuite with SyntheticTests {
val backEnd = L3Tester.backEnd2
}
object SyntheticTests3 extends TestSuite with SyntheticTests {
val backEnd = L3Tester.backEnd3
}