diff --git a/.gitignore b/.gitignore index efcfd96883..614a268924 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .sconsign.dblite +.sconf_temp/ +config.log *.inc *.o *.os @@ -9,14 +11,35 @@ *.dylib *.dll *.gc* +*.pyc *.neonx +*.neond bin/ errors.txt +config.py +test_grammar external/IntelRDFPMathLib20U1/ external/utf8/ external/libffi-3.2.1/ external/PDCurses-3.4/ external/easysid-version-1.0/ +external/hash-library/ +external/pcre2-10.10/ +external/curl-7.41.0/ +external/sqlite-amalgamation-3080803/ +external/zlib-1.2.8/ +external/bzip2-1.0.6/ +external/xz-5.2.1/ +external/minijson_writer-master/ +external/NaturalDocs/ +external/pyparsing-2.0.3/ +external/minizip11/ +external/SDL2-2.0.3/ +external/libsodium-1.0.5/ +external/libressl-2.2.4/ + +external/etc +external/include external/lib external/share diff --git a/.neonpath b/.neonpath new file mode 100644 index 0000000000..c3af857904 --- /dev/null +++ b/.neonpath @@ -0,0 +1 @@ +lib/ diff --git a/.travis.yml b/.travis.yml index e1c5b9f2aa..a4f7c09d6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,11 @@ language: cpp +os: + - linux + - osx compiler: - gcc - clang +before_install: + - which scons || brew install scons script: scons +sudo: false diff --git a/README.md b/README.md index 6ed4425938..c65cb3e248 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ False This happens because `0.2` cannot be repesented exactly in binary floating point. -To resolve this problem, Neon uses the [decimal64](https://en.wikipedia.org/wiki/Decimal64_floating-point_format) floating point type, which matches the base 10 that humans use to read and write numbers. +To resolve this problem, Neon uses the [decimal128](https://en.wikipedia.org/wiki/Decimal128_floating-point_format) floating point type, which matches the base 10 that humans use to read and write numbers. ### Writing division expressions such as `5 / 2` and not expecting integer division @@ -92,7 +92,7 @@ Beginners rightly assume that `c` will be `2.5` as the result of the division. However, the C language definition states that `/` will be *integer* division if both operands are integers. So, the result in `c` is `2`. -To resolve this problem, the only number type in Neon is decimal64 floating point (called `Number`). +To resolve this problem, the only number type in Neon is decimal128 floating point (called `Number`). In contexts such as array indexing where integers are expected, values are checked for the presence of a fractional part before trying to use them. @@ -111,11 +111,15 @@ In many common systems languages (eg. C, C++, Java, C#), a pointer may hold a "n To resolve this problem, Neon introduces the idea of a "valid" pointer. A valid pointer is one that has been checked against `NIL` (the null reference) using a special form of the `IF` statement. The resulting valid pointer can be dereferenced without causing a null pointer exception. - VAR node: POINTER TO Node - - IF VALID p := node THEN - print(p.value) - END IF + TYPE Node IS RECORD + value: String + END RECORD + + FUNCTION output(node: POINTER TO Node) + IF VALID node AS p THEN + print(p->value) + END IF + END FUNCTION ### Unintended empty loop with `while (condition);` @@ -126,6 +130,7 @@ In C and derived languages, sometimes a loop or conditional is mistakenly writte while (x < 5); { printf("%d\n", x); + x++; } ``` @@ -133,9 +138,12 @@ The trailing `;` on the `while` statement is in fact an empty loop body and the To resolve this problem, Neon requires an explicitly terminated block in every compound statement: - WHILE x < 5 - print(x) - END + VAR x: Number := 0 + + WHILE x < 5 DO + print("\(x)") + INC x + END WHILE ### Writing `if a == b or c` (in Python) to test whether `a` is equal to either `b` or `c` diff --git a/SConscript-libffi b/SConscript-libffi deleted file mode 100644 index 7789c9410c..0000000000 --- a/SConscript-libffi +++ /dev/null @@ -1,80 +0,0 @@ -import os -import sys -import tarfile - -def find_tool(name): - justname = os.path.basename(name) - for p in env["ENV"]["PATH"].split(";"): - fn = os.path.join(p, justname) - if os.access(fn, os.F_OK): - return fn - fn = os.path.join(p, name) - if os.access(fn, os.F_OK): - return fn - print >>sys.stderr, "could not find tool:", name - sys.exit(1) - -def subs(target, source, env): - with open(target[0].path, "w") as outf, open(source[0].path) as inf: - outf.write(inf.read() - .replace("@VERSION@", "3.2.1") - .replace("@TARGET@", "X86_WIN64") - .replace("@HAVE_LONG_DOUBLE@", "HAVE_LONG_DOUBLE") - .replace("@HAVE_LONG_DOUBLE_VARIANT@", "HAVE_LONG_DOUBLE_VARIANT") - .replace("@FFI_EXEC_TRAMPOLINE_TABLE@", "FFI_EXEC_TRAMPOLINE_TABLE") - ) - -def fix_target(target, source, env): - with open(target[0].path, "w") as outf, open(source[0].path) as inf: - for s in inf: - if s.startswith("#define FFI_TARGET_HAS_COMPLEX_TYPE"): - s = "//" + s - outf.write(s) - -def sub_config(target, source, env): - with open(target[0].path, "w") as outf, open(source[0].path) as inf: - for s in inf: - if s.startswith("#undef"): - if s.split()[1] in [ - "HAVE_MEMCPY", - "FFI_NO_RAW_API", - ]: - s = "#define {} 1".format(s.split()[1]) - outf.write(s) - -def remove_short(target, source, env): - with open(target[0].path, "w") as outf, open(source[0].path) as inf: - outf.write(inf.read().replace("SHORT", "")) - -Import("env") - -if sys.platform == "win32": - env.Command("external/libffi-3.2.1/include/ffi.h.in", "external/libffi-3.2.1.tar.gz", lambda target, source, env: tarfile.open(source[0].path).extractall("external")) - env.Append(CPPDEFINES=["FFI_BUILDING"]) - ffienv = env.Clone() - ffienv.Append(CPPPATH=["external/libffi-3.2.1/x86-win64/include"]) - ffi_h = ffienv.Command("external/libffi-3.2.1/x86-win64/include/ffi.h", "external/libffi-3.2.1/include/ffi.h.in", subs) - ffitarget_h = ffienv.Command("external/libffi-3.2.1/x86-win64/include/ffitarget.h", "external/libffi-3.2.1/src/x86/ffitarget.h", fix_target) - fficommon_h = ffienv.Command("external/libffi-3.2.1/x86-win64/include/ffi_common.h", "external/libffi-3.2.1/include/ffi_common.h", Copy("$TARGET", "$SOURCE")) - fficonfig_h = ffienv.Command("external/libffi-3.2.1/x86-win64/include/fficonfig.h", "external/libffi-3.2.1/fficonfig.h.in", sub_config) - win64_p = ffienv.Command("external/libffi-3.2.1/x86-win64/win64.p", "external/libffi-3.2.1/src/x86/win64.S", "cl /EP /I external/libffi-3.2.1/x86-win64/include $SOURCE >$TARGET") - win64_asm = ffienv.Command("external/libffi-3.2.1/x86-win64/win62.asm", win64_p, remove_short) - objects = [ - ffienv.Object("external/libffi-3.2.1/x86-win64/closures.obj", "external/libffi-3.2.1/src/closures.c"), - ffienv.Object("external/libffi-3.2.1/x86-win64/ffi.obj", "external/libffi-3.2.1/src/x86/ffi.c"), - ffienv.Object("external/libffi-3.2.1/x86-win64/prep_cif.obj", "external/libffi-3.2.1/src/prep_cif.c"), - ffienv.Object("external/libffi-3.2.1/x86-win64/types.obj", "external/libffi-3.2.1/src/types.c"), - ] - for o in objects: - ffienv.Depends(o, [ffi_h, ffitarget_h, fficommon_h, fficonfig_h]) - libffi = ffienv.Library("external/libffi-3.2.1/x86-win64/libffi.lib", objects + [ - ffienv.Command("external/libffi-3.2.1/x86-win64/win64.obj", win64_asm, "\"{}\" /c /Cx /Fo$TARGET $SOURCE".format(find_tool("x86_amd64/ml64.exe"))), - ]) - ffienv.Install("external/lib/libffi-3.2.1/include/", [ffi_h, ffitarget_h, fficommon_h, fficonfig_h]) - libffi = ffienv.Install("external/lib/", libffi) -else: - env.Command("external/libffi-3.2.1/configure", "external/libffi-3.2.1.tar.gz", lambda target, source, env: tarfile.open(source[0].path).extractall("external")) - env.Command("external/libffi-3.2.1/Makefile", "external/libffi-3.2.1/configure", "cd external/libffi-3.2.1 && ./configure --prefix=`pwd`/..") - libffi = env.Command("external/lib/libffi.a", "external/libffi-3.2.1/Makefile", "cd external/libffi-3.2.1 && make && make install") - -Return(["libffi"]) diff --git a/SConstruct b/SConstruct index 7a28ba5815..b5f58f92f7 100644 --- a/SConstruct +++ b/SConstruct @@ -1,9 +1,24 @@ +import distutils.spawn +import operator import os +import re +import shutil +import subprocess import sys import tarfile import zipfile from SCons.Script.SConscript import SConsEnvironment +# Compatibility function for Python 2.6. +if not hasattr(subprocess, "check_output"): + def check_output(args): + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if p.returncode != 0: + raise subprocess.CalledProcessError(args) + return out + subprocess.check_output = check_output + # Assume a UTF-8 capable terminal. os.putenv("PYTHONIOENCODING", "UTF-8") @@ -11,36 +26,83 @@ coverage = ARGUMENTS.get("coverage", 0) # This is needed on OS X because clang has a bug where this isn't included automatically. coverage_lib = (["/Library/Developer/CommandLineTools/usr/lib/clang/6.0/lib/darwin/libclang_rt.profile_osx.a"] if coverage else []) +default_release = 0 +if GetOption("clean"): + try: + os.remove("config.py") + except OSError: + pass + release = default_release +else: + release = int(ARGUMENTS.get("release", default_release)) + stored_release = None + try: + with open("config.py") as config: + for s in config: + a = s.strip().split("=") + if a[0] == "release": + stored_release = int(a[1]) + except IOError: + pass + if stored_release is not None: + if release != stored_release: + print >>sys.stderr, "Requested release flag ({}) different from last build ({}).".format(release, stored_release) + print >>sys.stderr, "Run 'scons -c' first." + sys.exit(1) + else: + with open("config.py", "w") as config: + print >>config, "release={}".format(release) + +# Check for any files that accidentally contain \r\n. Only do this +# on non-windows platforms, because windows users may set Git to +# use crlf line endings. +if sys.platform != "nt": + for subdir in ["contrib", "gh-pages", "lib", "samples", "scripts", "src", "t", "tests", "tools"]: + for path, files, dirs in os.walk(subdir): + for fn in files: + if fn.endswith((".cpp", ".neon", ".txt", ".md")): + with open(os.path.join(path, fn), "rb") as f: + data = f.read() + assert "\r\n" not in data, fn + env = Environment() -env["ENV"]["PROCESSOR_ARCHITECURE"] = os.getenv("PROCESSOR_ARCHITECTURE") +env["RELEASE"] = release + +env["ENV"]["PROCESSOR_ARCHITECTURE"] = os.getenv("PROCESSOR_ARCHITECTURE") env["ENV"]["PROCESSOR_ARCHITEW6432"] = os.getenv("PROCESSOR_ARCHITEW6432") -env.Command("external/IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "external/IntelRDFPMathLib20U1.tar.gz", lambda target, source, env: tarfile.open(source[0].path).extractall("external")) -if sys.platform == "win32": - libbid = env.Command("external/IntelRDFPMathLib20U1/LIBRARY/libbid.lib", "external/IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "cd external/IntelRDFPMathLib20U1/LIBRARY && nmake -fmakefile.mak CC=cl GLOBAL_RND=1 GLOBAL_FLAGS=1") -else: - libbid = env.Command("external/IntelRDFPMathLib20U1/LIBRARY/libbid.a", "external/IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "cd external/IntelRDFPMathLib20U1/LIBRARY && make CC=gcc GLOBAL_RND=1 GLOBAL_FLAGS=1") +# Add path of Python itself to shell PATH. +env["ENV"]["PATH"] = env["ENV"]["PATH"] + os.pathsep + os.path.dirname(sys.executable) -libffi = SConscript("SConscript-libffi", exports=["env"]) +def add_external(target): + env.Depends("external", target) + return target -if sys.platform == "win32": - env.Command("external/PDCurses-3.4/win32/vcwin32.mak", "external/PDCurses-3.4.tar.gz", lambda target, source, env: tarfile.open(source[0].path).extractall("external")) - libs_curses = [env.Command("external/PDCurses-3.4/win32/pdcurses.lib", "external/PDCurses-3.4/win32/vcwin32.mak", "cd external/PDCurses-3.4/win32 && nmake -fvcwin32.mak WIDE=Y UTF8=Y")] - libs_curses.extend(["advapi32", "user32"]) -else: - libs_curses = ["ncurses"] +add_external(SConscript("external/SConscript-libutf8", exports=["env"])) +libbid = add_external(SConscript("external/SConscript-libbid", exports=["env"])) +libffi = add_external(SConscript("external/SConscript-libffi", exports=["env"])) +libs_curses = add_external(SConscript("external/SConscript-libcurses", exports=["env"])) +libpcre = add_external(SConscript("external/SConscript-libpcre", exports=["env"])) +libcurl = add_external(SConscript("external/SConscript-libcurl", exports=["env"])) +libeasysid = add_external(SConscript("external/SConscript-libeasysid", exports=["env"])) +libhash = add_external(SConscript("external/SConscript-libhash", exports=["env"])) +libsqlite = add_external(SConscript("external/SConscript-libsqlite", exports=["env"])) +libz = add_external(SConscript("external/SConscript-libz", exports=["env"])) +libbz2 = add_external(SConscript("external/SConscript-libbz2", exports=["env"])) +liblzma = add_external(SConscript("external/SConscript-liblzma", exports=["env"])) +libminizip = add_external(SConscript("external/SConscript-libminizip", exports=["env"])) +libsdl = add_external(SConscript("external/SConscript-libsdl", exports=["env"])) +libsodium = add_external(SConscript("external/SConscript-libsodium", exports=["env"])) +libssl = add_external(SConscript("external/SConscript-libssl", exports=["env"])) +add_external(SConscript("external/SConscript-minijson", exports=["env"])) +add_external(SConscript("external/SConscript-pyparsing", exports=["env"])) -env.Command("external/utf8/source/utf8.h", "external/utf8_v2_3_4.zip", lambda target, source, env: zipfile.ZipFile(source[0].path).extractall("external/utf8")) +env.Depends(libcurl, libssl) -env.Command("external/easysid-version-1.0/SConstruct", "external/easysid-version-1.0.tar.gz", lambda target, source, env: tarfile.open(source[0].path).extractall("external")) -libeasysid = env.Command("external/easysid-version-1.0/libeasysid"+env["SHLIBSUFFIX"], "external/easysid-version-1.0/SConstruct", "cd external/easysid-version-1.0 && " + sys.executable + " " + sys.argv[0]) +SConscript("external/SConscript-naturaldocs") env.Append(CPPPATH=[ - "external/IntelRDFPMathLib20U1/LIBRARY/src", - "external/utf8/source", - "external/lib/libffi-3.2.1/include", - "external/PDCurses-3.4", "src", ]) if sys.platform == "win32": @@ -49,18 +111,45 @@ if sys.platform == "win32": "/W4", "/WX", ]) + if not env["RELEASE"]: + env.Append(CXXFLAGS=[ + "/MTd", + "/Zi", + ]) else: env.Append(CXXFLAGS=[ "-std=c++0x", "-Wall", "-Wextra", "-Weffc++", + #"-Wold-style-cast", # Enable this temporarily to check, but it breaks with gcc and #defines with C casts in standard headers. "-Werror", - "-g", ]) -env.Append(LIBS=[libbid, libffi] + libs_curses) + if not env["RELEASE"]: + env.Append(CXXFLAGS=[ + "-g", + ]) +env.Prepend(LIBS=[x for x in [libbid, libffi, libpcre, libcurl, libhash, libsqlite, libminizip, libz, libbz2, liblzma, libsdl, libsodium, libssl] if x]) +env.Append(LIBS=libs_curses) if os.name == "posix": env.Append(LIBS=["dl"]) +if sys.platform.startswith("linux"): + env.Append(LIBS=["rt"]) + +if "g++" in env.subst("$CXX"): + # This adds -Doverride= for GCC earlier than 4.7. + # (GCC does not support 'override' before 4.7, but + # it supports everything else we need.) + try: + ver = subprocess.check_output([env.subst("$CXX"), "--version"]) + if ver.startswith("g++"): + ver = ver.split("\n")[0] + ver = re.sub(r"\(.*?\)", "", ver) + ver = float(re.search(r"(\d+\.\d+)\.", ver).group(1)) + if ver < 4.7: + env.Append(CXXFLAGS=["-Doverride="]) + except Exception as x: + pass if coverage: env.Append(CXXFLAGS=[ @@ -68,28 +157,100 @@ if coverage: ]) rtl_const = [ - "lib/math_const.cpp", + "lib/curses_const.cpp", + "lib/sdl_const.cpp", + "lib/sodium_const.cpp", ] -rtl = rtl_const + [ +if os.name == "posix": + rtl_const.extend([ + "lib/file_const_posix.cpp", + ]) +elif os.name == "nt": + rtl_const.extend([ + "lib/file_const_win32.cpp", + ]) +else: + print "Unsupported platform:", os.name + sys.exit(1) + +rtl_cpp = rtl_const + [ "lib/bitwise.cpp", + "lib/compress.cpp", "lib/curses.cpp", + "lib/datetime.cpp", + "lib/debugger.cpp", "lib/global.cpp", + "lib/file.cpp", + "lib/hash.cpp", + "lib/http.cpp", + "lib/io.cpp", "lib/math.cpp", + "lib/net.cpp", + "lib/os.cpp", "lib/random.cpp", + "lib/runtime.cpp", + "lib/regex.cpp", + "lib/sdl.cpp", + "lib/sodium.cpp", + "lib/sqlite.cpp", + "lib/string.cpp", "lib/sys.cpp", "lib/time.cpp", ] -env.Command(["src/thunks.inc", "src/functions_compile.inc", "src/functions_exec.inc"], [rtl, "scripts/make_thunks.py"], sys.executable + " scripts/make_thunks.py " + " ".join(rtl)) +env.Depends("lib/http.cpp", libcurl) + +rtl_neon = [ + "lib/bitwise.neon", + "lib/compress.neon", + "lib/curses.neon", + "lib/datetime.neon", + "lib/debugger.neon", + "lib/file.neon", + "lib/global.neon", + "lib/hash.neon", + "lib/http.neon", + "lib/io.neon", + "lib/math.neon", + "lib/mmap.neon", + "lib/net.neon", + "lib/os.neon", + "lib/random.neon", + "lib/runtime.neon", + "lib/regex.neon", + "lib/sdl.neon", + "lib/sodium.neon", + "lib/sqlite.neon", + "lib/string.neon", + "lib/sys.neon", + "lib/time.neon", +] if os.name == "posix": - rtl.extend([ + rtl_cpp.extend([ + "lib/file_posix.cpp", + "lib/mmap_posix.cpp", + "lib/os_posix.cpp", "lib/time_posix.cpp", ]) + if sys.platform.startswith("darwin"): + rtl_cpp.extend([ + "lib/time_darwin.cpp", + ]) + elif sys.platform.startswith("linux"): + rtl_cpp.extend([ + "lib/time_linux.cpp", + ]) + else: + print >>sys.stderr, "Unsupported platform:", sys.platform + sys.exit(1) rtl_platform = "src/rtl_posix.cpp" elif os.name == "nt": - rtl.extend([ + rtl_cpp.extend([ + "lib/file_win32.cpp", + "lib/mmap_win32.cpp", + "lib/os_win32.cpp", "lib/time_win32.cpp", ]) rtl_platform = "src/rtl_win32.cpp" @@ -97,7 +258,10 @@ else: print "Unsupported platform:", os.name sys.exit(1) +env.Command(["src/thunks.inc", "src/functions_compile.inc", "src/functions_exec.inc", "src/enums.inc", "src/exceptions.inc", "src/constants_compile.inc"], [rtl_neon, "scripts/make_thunks.py"], sys.executable + " scripts/make_thunks.py " + " ".join(rtl_neon)) + neon = env.Program("bin/neon", [ + "src/analyzer.cpp", "src/ast.cpp", "src/bytecode.cpp", "src/cell.cpp", @@ -105,43 +269,77 @@ neon = env.Program("bin/neon", [ "src/debuginfo.cpp", "src/disassembler.cpp", "src/exec.cpp", + "src/format.cpp", + "src/httpserver.cpp", + "src/intrinsic.cpp", "src/lexer.cpp", "src/main.cpp", "src/number.cpp", "src/parser.cpp", + "src/pt_dump.cpp", "src/rtl_compile.cpp", "src/rtl_exec.cpp", - rtl, + rtl_cpp, rtl_platform, + "src/support.cpp", + "src/support_compiler.cpp", "src/util.cpp", ] + coverage_lib, ) neonc = env.Program("bin/neonc", [ + "src/analyzer.cpp", "src/ast.cpp", "src/bytecode.cpp", "src/compiler.cpp", "src/debuginfo.cpp", "src/disassembler.cpp", + "src/format.cpp", + "src/intrinsic.cpp", "src/lexer.cpp", + "src/neonc.cpp", "src/number.cpp", "src/parser.cpp", + "src/pt_dump.cpp", "src/rtl_compile.cpp", rtl_const, - "src/neonc.cpp", + "src/support.cpp", + "src/support_compiler.cpp", "src/util.cpp", ] + coverage_lib, ) neonx = env.Program("bin/neonx", [ + "src/bundle.cpp", + "src/bytecode.cpp", + "src/cell.cpp", + "src/exec.cpp", + "src/format.cpp", + "src/httpserver.cpp", + "src/intrinsic.cpp", + "src/neonx.cpp", + "src/number.cpp", + "src/rtl_exec.cpp", + rtl_cpp, + rtl_platform, + "src/support.cpp", +] + coverage_lib, +) + +neonstub = env.Program("bin/neonstub", [ + "src/bundle.cpp", "src/bytecode.cpp", "src/cell.cpp", "src/exec.cpp", + "src/format.cpp", + "src/httpserver.cpp", + "src/intrinsic.cpp", + "src/neonstub.cpp", "src/number.cpp", "src/rtl_exec.cpp", - rtl, + rtl_cpp, rtl_platform, - "src/neonx.cpp", + "src/support.cpp", ] + coverage_lib, ) @@ -151,10 +349,18 @@ neondis = env.Program("bin/neondis", [ "src/disassembler.cpp", "src/neondis.cpp", "src/number.cpp", + # The following are just to support internal_error() + "src/lexer.cpp", "src/util.cpp", ] + coverage_lib, ) +neonbind = env.Program("bin/neonbind", [ + "src/bytecode.cpp", + "src/neonbind.cpp", + "src/support.cpp", +]) + env.Depends("src/number.h", libbid) env.Depends("src/exec.cpp", libffi) @@ -184,6 +390,13 @@ env.UnitTest("bin/test_lexer", [ ] + coverage_lib, ) +env.UnitTest("bin/test_format", [ + "tests/test_format.cpp", + "src/format.cpp", + "src/number.cpp", +] + coverage_lib, +) + env.Program("bin/fuzz_lexer", [ "tests/fuzz_lexer.cpp", "src/lexer.cpp", @@ -194,9 +407,13 @@ env.Program("bin/fuzz_lexer", [ env.Program("bin/fuzz_parser", [ "tests/fuzz_parser.cpp", + "src/analyzer.cpp", "src/ast.cpp", + "src/bytecode.cpp", "src/compiler.cpp", "src/lexer.cpp", + "src/format.cpp", + "src/intrinsic.cpp", "src/number.cpp", "src/parser.cpp", "src/rtl_compile.cpp", @@ -210,15 +427,44 @@ if sys.platform == "win32": else: test_ffi = env.SharedLibrary("bin/test_ffi", "tests/test_ffi.c") -tests = env.Command("tests_normal", [neon, "scripts/run_test.py", Glob("t/*")], sys.executable + " scripts/run_test.py t") +tests = env.Command("tests_normal", [neon, "scripts/run_test.py", Glob("t/*.neon")], sys.executable + " scripts/run_test.py t") +tests = env.Command("tests_helium", [neon, "scripts/run_test.py", Glob("t/*.neon")], sys.executable + " scripts/run_test.py --runner \"" + sys.executable + " tools/helium.py\" t") env.Depends(tests, test_ffi) -env.Command("tests_error", [neon, "scripts/run_test.py", "src/errors.txt", Glob("t/errors/*")], sys.executable + " scripts/run_test.py --errors t/errors") +testenv = env.Clone() +testenv["ENV"]["NEONPATH"] = "t/" +testenv.Command("tests_error", [neon, "scripts/run_test.py", "src/errors.txt", Glob("t/errors/*")], sys.executable + " scripts/run_test.py --errors t/errors") env.Command("tests_number", test_number_to_string, test_number_to_string[0].path) -for sample in Glob("samples/*.neon"): - env.Command(sample.path+"x", [sample, neonc], neonc[0].abspath + " $SOURCE") -env.Command("tests_2", ["samples/hello.neonx", neonx], neonx[0].abspath + " $SOURCE") +samples = [] +for path, dirs, files in os.walk("."): + if "t" not in path.split(os.sep): + samples.extend(os.path.join(path, x) for x in files if x.endswith(".neon") and x != "global.neon") +for sample in samples: + env.Command(sample+"x", [sample, neonc], neonc[0].abspath + " $SOURCE") +env.Command("tests_2", ["samples/hello/hello.neonx", neonx], neonx[0].abspath + " $SOURCE") + +env.Command("test_grammar", ["contrib/grammar/neon.ebnf", "src/parser.cpp"], sys.executable + " contrib/grammar/test-grammar.py lib/*.neon {} t/*.neon t/errors/N3*.neon >$TARGET".format(" ".join(x for x in reduce(operator.add, ([os.path.join(path, x) for x in files] for path, dirs, files in os.walk("samples"))) if x.endswith(".neon")))) +env.Command("test_grammar_random", "contrib/grammar/neon.ebnf", sys.executable + " contrib/grammar/test-random.py") +env.Command("contrib/grammar/neon.w3c.ebnf", ["contrib/grammar/neon.ebnf", "contrib/grammar/ebnf_w3c.neon", neon], neon[0].path + " contrib/grammar/ebnf_w3c.neon <$SOURCE >$TARGET") + +env.Command("test_doc", None, sys.executable + " scripts/test_doc.py") if os.name == "posix": - env.Command("samples/hello", "samples/hello.neon", "echo '#!/usr/bin/env neon' | cat - $SOURCE >$TARGET && chmod +x $TARGET") - env.Command("tests_script", "samples/hello", "env PATH=bin samples/hello") + env.Command("tmp/hello", "samples/hello/hello.neon", "echo '#!/usr/bin/env neon' | cat - $SOURCE >$TARGET && chmod +x $TARGET") + env.Command("tests_script", "tmp/hello", "env PATH=bin tmp/hello") + +hello_neb = env.Command("tmp/hello.neb", ["samples/hello/hello.neonx", neonbind], "{} $TARGET $SOURCE".format(neonbind[0].path)) +env.Command("test_hello_neb", [hello_neb, neonx], "{} $SOURCE".format(neonx[0].path)) + +hello_exe = env.Command("tmp/hello.exe", ["samples/hello/hello.neonx", neonbind, neonstub], "{} -e $TARGET $SOURCE".format(neonbind[0].path)) +env.Command("test_hello_exe", hello_exe, hello_exe[0].path) +cal_exe = env.Command("tmp/cal.exe", ["samples/cal/cal.neonx", neonbind, neonstub], "{} -e $TARGET $SOURCE".format(neonbind[0].path)) +env.Command("test_cal", cal_exe, cal_exe[0].path) + +# Need to find where perl actually is, in case it's not in +# one of the paths supplied by scons by default (for example, +# on Windows with the GitHub command prompt). +perl = distutils.spawn.find_executable("perl") +if perl: + env.Command("docs", None, perl + " external/NaturalDocs/NaturalDocs -i lib -o HTML gh-pages/html -p lib/nd.proj -ro") + env.Command("docs_samples", None, perl + " external/NaturalDocs/NaturalDocs -i samples -o HTML gh-pages/samples -p samples/nd.proj -ro") diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..99ed54a343 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,28 @@ +version: 1.0.{build} +branches: + only: + - master +os: Visual Studio 2013 +configuration: Release +platform: x64 +clone_folder: C:\projects\neon-lang\ +install: +- ps: >- + if (-not (Test-Path C:\Python27)){ + Start-FileDownload "https://www.python.org/ftp/python/2.7.9/python-2.7.9.amd64.msi" -FileName python-2.7.9.amd64.msi + Start-Process "python-2.7.9.amd64.msi" -ArgumentList "/quiet" -Wait + } +- ps: >- + if (-not (Test-Path C:\Python27\Lib\site-packages\scons-2.4.1)){ + Start-FileDownload "http://downloads.sourceforge.net/project/scons/scons/2.4.1/scons-2.4.1.zip" -FileName "scons-2.4.1.zip" + 7z x -y scons-2.4.1.zip + New-Item C:\SCons -ItemType Directory + Move-Item .\scons-2.4.1 C:\SCons\scons-2.4.1 + Start-Process "python.exe" -ArgumentList "c:\SCons\scons-2.4.1\setup.py" -Wait + } +cache: +- C:\projects\neon-lang +build_script: +- cmd: CD C:\projects\neon-lang +- cmd: dir +- cmd: C:\Python27\Scripts\scons.py --directory=c:\projects\neon-lang diff --git a/contrib/editors/Notepad++/Neon.xml b/contrib/editors/Notepad++/Neon.xml index e09b3b6b19..c9dfed27ce 100644 --- a/contrib/editors/Notepad++/Neon.xml +++ b/contrib/editors/Notepad++/Neon.xml @@ -2,7 +2,7 @@ - + 00 01 02 03%| 04|% @@ -11,54 +11,54 @@ A B C D E F a b c d e f - + # - < > <= >= ^ = + - / * := : ( ) - MOD AND NOT OR # .. + < > <= >= ^ = + - / * := : ( ) & + MOD AND NOT OR # - + %| - - CASE CONST DECLARE DO ELSE ELSEIF END ENUM EXIT EXTERNAL FOR FUNCTION IF IMPORT LOOP NEW NEXT POINTER RECORD REPEAT RETURN STEP THEN TO TYPE UNTIL VALID VAR WHEN WHILE EXCEPTION TRY - RAISE AND NOT OR - FALSE Boolean TRUE Nothing Number String NIL - IN OUT INOUT + |% + AS BEGIN CASE CONSTANT DEC DECLARE DO ELSE ELSIF EMBED END ENUM EXCEPTION EXIT EXPORT EXTERNAL FOR FOREACH FUNCTION IF IMPORT INC INDEX IS LET LOOP MAIN NATIVE NEW NEXT OF POINTER PRIVATE RECORD REPEAT RETURN STEP THEN TO TRY TYPE UNTIL VALID VAR WHEN WHILE + AND ASSERT NOT OR RAISE _ + Boolean Bytes FALSE HEXBYTES NIL Number POINTER String TO TRUE + DEFAULT IN INOUT OUT Array Dictionary - - - - 00%| 01 02|% 03[ 04 05] 06{ 07 08} 09# 10 11# 12% 13 14((EOL)) 15TODO 15BUG 15FIX 15HACK 16 17((EOL)) 17((EOL)) 17((EOL)) 17((EOL)) 18" 19\ 20" 21 22 23 + .append .extend .resize .size .slice .splice .keys .length .substring .toString .toArray .fromArray .toArray + chr concat format input max min num ord print str strb + FIRST LAST + 00%| 01 02|% 03[ 04 05] 06{ 07 08} 09 10 11 12% 13 14((EOL)) 15TODO 15BUG 15FIX 15HACK 16 17((EOL)) 17((EOL)) 17((EOL)) 17((EOL)) 18" 19((EOL)) 20" 21@@" 22 23"@@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/editors/kedit/neon.kld b/contrib/editors/kedit/neon.kld index 37b617211a..eebdc058ea 100644 --- a/contrib/editors/kedit/neon.kld +++ b/contrib/editors/kedit/neon.kld @@ -1,5 +1,5 @@ * neon.kld - KEDIT Language Definition for the Neon Programming Language. -* Version 1.3 - December, 2014 +* Version 1.6 - December, 2015 :case respect @@ -18,32 +18,54 @@ [ ] < > { } + " " + \( ) + @@ @@ :keyword * Language Keywords + AS + ASSERT + BEGIN CASE - CONST + CONSTANT + DEC DECLARE + DEFAULT DO ELSE - ELSEIF + ELSIF + EMBED END ENUM + EXCEPTION EXIT + EXPORT EXTERNAL FOR + FOREACH FUNCTION + HEXBYTES IF IMPORT + INC + INDEX + IS + LET LOOP + MAIN + NATIVE NEW NEXT + OF POINTER + PRIVATE RECORD REPEAT RETURN STEP THEN TO + TRY TYPE UNTIL VALID @@ -51,8 +73,6 @@ WHEN WHILE * Exception Handling - EXCEPTION - TRY RAISE alternate 1 * Parameter Control IN alternate 4 @@ -65,19 +85,27 @@ * Data Types and Storage containers Array alternate 6 Boolean alternate 2 + Bytes alternate 2 Dictionary alternate 6 - Nothing alternate 2 Number alternate 2 String alternate 2 * NIL is actually a unique value literal, used only with pointer operations. NIL alternate 2 + FIRST alternate 2 + INDEX alternate 2 + LAST alternate 2 + OF alternate 2 * Boolean literals FALSE alternate 2 TRUE alternate 2 +* Library Keywords + IMPORT alternate 3 + EXTERNAL alternate 3 + EXPORT alternate 3 + NATIVE alternate 3 :postcompare text MOD alternate 1 - text .. alternate 1 text # alternate 1 text := alternate 1 text < alternate 1 @@ -93,4 +121,6 @@ text , alternate 1 text : alternate 1 text . alternate 1 + text -> alternate 1 + text MAIN alternate 3 diff --git a/contrib/grammar/.gitignore b/contrib/grammar/.gitignore new file mode 100644 index 0000000000..cd234e72e7 --- /dev/null +++ b/contrib/grammar/.gitignore @@ -0,0 +1 @@ +neon.w3c.ebnf diff --git a/contrib/grammar/ebnf.py b/contrib/grammar/ebnf.py new file mode 100644 index 0000000000..51fcd317a9 --- /dev/null +++ b/contrib/grammar/ebnf.py @@ -0,0 +1,151 @@ +# This module tries to implement ISO 14977 standard with pyparsing. +# pyparsing version 1.1 or greater is required. + +# ISO 14977 standardize The Extended Backus-Naur Form(EBNF) syntax. +# You can read a final draft version here: +# http://www.cl.cam.ac.uk/~mgk25/iso-ebnf.html + + +from pyparsing import * + + +all_names = ''' +integer +meta_identifier +terminal_string +optional_sequence +repeated_sequence +grouped_sequence +syntactic_primary +syntactic_factor +syntactic_term +single_definition +definitions_list +syntax_rule +syntax +'''.split() + + +integer = Word(nums) +meta_identifier = Word(alphas, alphanums + '_') +terminal_string = Suppress("'") + CharsNotIn("'") + Suppress("'") ^ \ + Suppress('"') + CharsNotIn('"') + Suppress('"') +definitions_list = Forward() +optional_sequence = Suppress('[') + definitions_list + Suppress(']') +repeated_sequence = Suppress('{') + definitions_list + Suppress('}') +grouped_sequence = Suppress('(') + definitions_list + Suppress(')') +syntactic_primary = optional_sequence ^ repeated_sequence ^ \ + grouped_sequence ^ meta_identifier ^ terminal_string +syntactic_factor = Optional(integer + Suppress('*')) + syntactic_primary +syntactic_term = syntactic_factor + Optional(Suppress('-') + syntactic_factor) +single_definition = delimitedList(syntactic_term, ',') +definitions_list << delimitedList(single_definition, '|') +syntax_rule = meta_identifier + Suppress('=') + definitions_list + \ + Suppress(';') + +ebnfComment = ( "(*" + + ZeroOrMore( CharsNotIn("*") | ( "*" + ~Literal(")") ) ) + + "*)" ).streamline().setName("ebnfComment") + +syntax = OneOrMore(syntax_rule) +syntax.ignore(ebnfComment) + + +def do_integer(str, loc, toks): + return int(toks[0]) + +def do_meta_identifier(str, loc, toks): + if toks[0] in symbol_table: + return symbol_table[toks[0]] + else: + forward_count.value += 1 + symbol_table[toks[0]] = Forward() + return symbol_table[toks[0]] + +def do_terminal_string(str, loc, toks): + return Keyword(toks[0]) if toks[0].isalpha() else Literal(toks[0]) + +def do_optional_sequence(str, loc, toks): + return Optional(toks[0]) + +def do_repeated_sequence(str, loc, toks): + return ZeroOrMore(toks[0]) + +def do_grouped_sequence(str, loc, toks): + return Group(toks[0]) + +def do_syntactic_primary(str, loc, toks): + return toks[0] + +def do_syntactic_factor(str, loc, toks): + if len(toks) == 2: + # integer * syntactic_primary + return And([toks[1]] * toks[0]) + else: + # syntactic_primary + return [ toks[0] ] + +def do_syntactic_term(str, loc, toks): + if len(toks) == 2: + # syntactic_factor - syntactic_factor + return NotAny(toks[1]) + toks[0] + else: + # syntactic_factor + return [ toks[0] ] + +def do_single_definition(str, loc, toks): + toks = toks.asList() + if len(toks) > 1: + # syntactic_term , syntactic_term , ... + return And(toks) + else: + # syntactic_term + return [ toks[0] ] + +def do_definitions_list(str, loc, toks): + toks = toks.asList() + if len(toks) > 1: + # single_definition | single_definition | ... + return Or(toks) + else: + # single_definition + return [ toks[0] ] + +def do_syntax_rule(str, loc, toks): + # meta_identifier = definitions_list ; + assert toks[0].expr is None, "Duplicate definition" + forward_count.value -= 1 + toks[0] << toks[1] + return [ toks[0] ] + +def do_syntax(str, loc, toks): + # syntax_rule syntax_rule ... + return symbol_table + + + +symbol_table = {} +class forward_count: + pass +forward_count.value = 0 +for name in all_names: + expr = vars()[name] + action = vars()['do_' + name] + expr.setName(name) + expr.setParseAction(action) + #~ expr.setDebug() + + +def parse(ebnf, given_table={}): + symbol_table.clear() + symbol_table.update(given_table) + forward_count.value = 0 + table = syntax.parseString(ebnf, parseAll=True)[0] + if forward_count.value != 0: + print [k for k, v in symbol_table.items() if isinstance(v, Forward) and not v.expr] + assert forward_count.value == 0, "Missing definition" + for name in table: + expr = table[name] + expr.setName(name) + #~ expr.setDebug() + return table diff --git a/contrib/grammar/ebnf_w3c.neon b/contrib/grammar/ebnf_w3c.neon new file mode 100644 index 0000000000..0dd6b103a9 --- /dev/null +++ b/contrib/grammar/ebnf_w3c.neon @@ -0,0 +1,67 @@ +%| + | This program converts the neon.ebnf grammar from EBNF format to + | the W3C dialect of EBNF used by http://www.bottlecaps.de/rr/ui. + | It is quick and dirty and is not a general solution. + |% + +IMPORT string + +LOOP + VAR s: String + TRY + s := input("") + + % Skip over comment block at top. + IF s[1] = "*" THEN + NEXT LOOP + END IF + + % Remove semicolons from the end of each production. + IF s[LAST] = ";" THEN + s := s[FIRST TO LAST-1] + END IF + + % Convert = to ::=. + LET equals: Number := string.find(s, " =") + IF equals >= 0 THEN + s[equals+1] := "::=" + END IF + + % Remove commas separating items. + LOOP + LET comma: Number := string.find(s, ", ") + IF comma < 0 THEN + EXIT LOOP + END IF + s[comma] := "" + END LOOP + + % Convert [...] to (...)?. + LOOP + LET bracket: Number := string.find(s, " [") + IF bracket < 0 THEN + EXIT LOOP + END IF + LET rbracket: Number := string.find(s, "]") + ASSERT rbracket > bracket + s[bracket+1] := "(" + s[rbracket] := ")?" + END LOOP + + % Convert {...} to (...)*. + LOOP + LET brace: Number := string.find(s, " {") + IF brace < 0 THEN + EXIT LOOP + END IF + LET rbrace: Number := string.find(s, "}") + ASSERT rbrace > brace + s[brace+1] := "(" + s[rbrace] := ")*" + END LOOP + + EXCEPTION EndOfFileException DO + EXIT LOOP + END TRY + print(s) +END LOOP diff --git a/contrib/grammar/grammar.py b/contrib/grammar/grammar.py new file mode 100644 index 0000000000..06f24c2aac --- /dev/null +++ b/contrib/grammar/grammar.py @@ -0,0 +1,18 @@ +from pyparsing import * +import ebnf + +ParserElement.enablePackrat() + +singleRawString = QuotedString(quoteChar='@"', endQuoteChar='"') +doubleRawString = QuotedString(quoteChar='@@"', endQuoteChar='"@@', multiline=True) +rawString = singleRawString | doubleRawString + +table = { + # Non-greedy trick from http://pyparsing.wikispaces.com/share/view/178079 + "Identifier": ~Literal('END') + Word(alphas, alphanums + "_"), + "Number": Word(nums, nums + "_e.") ^ ("0b" + Word("01_")) ^ ("0o" + Word("01234567_")) ^ ("0x" + Word("0123456789abcdefABCDEF_")) ^ ("0#" + Word(nums) + "#" + Word(alphanums + "_")), + "StringLiteral": Regex(r'"(?:[^"\r\n\\]|(?:\\\((?:[^")]|"[^"]*")*\))|(?:\\.))*"') | rawString, + "restOfLine": restOfLine, +} + +parsers = ebnf.parse(open("contrib/grammar/neon.ebnf").read(), table) diff --git a/contrib/grammar/neon.ebnf b/contrib/grammar/neon.ebnf new file mode 100644 index 0000000000..c589aafd20 --- /dev/null +++ b/contrib/grammar/neon.ebnf @@ -0,0 +1,180 @@ +(* + * The following EBNF grammar for Neon is for informative purposes only. + * This grammar file is not used by the compiler; the real grammar is + * implemented by the recursive descent parser. + * + * However, this grammar is tested against the recursive descent parser + * using two methods: + * + * 1. Ensure that all the sample programs and test suite are matched by + * this grammar (test-grammar.py). + * 2. Generate random statements using this grammar and ensure that they + * do not trigger parse errors (test-random.py). + * + * Additionally, this grammar is converted to a form suitable for input + * into http://www.bottlecaps.de/rr/ui by ebnf_w3c.neon. + *) + +Program = [Shebang], {Statement}; +Shebang = '#!', restOfLine; + +Statement = + ImportDeclaration | + TypeDeclaration | + ConstantDeclaration | + NativeConstantDeclaration | + VariableDeclaration | + NativeVariableDeclaration | + LetDeclaration | + FunctionDeclaration | + ExternalFunctionDeclaration | + NativeFunctionDeclaration | + ExceptionDeclaration | + ExportDeclaration | + MainBlock | + AssertStatement | + AssignmentStatement | + CaseStatement | + ExitStatement | + ExpressionStatement | + ForStatement | + ForeachStatement | + IfStatement | + IncrementStatement | + LoopStatement | + NextStatement | + RaiseStatement | + RepeatStatement | + ReturnStatement | + TryStatement | + WhileStatement; + +ImportDeclaration = 'IMPORT', (Identifier, ['.', Identifier], ['ALIAS', Identifier] | StringLiteral, 'ALIAS', Identifier); + +TypeDeclaration = 'TYPE', Identifier, 'IS', Type; + +Type = ParameterisedType | RecordType | EnumType | PointerType | FunctionPointerType | (Identifier, ['.', Identifier]); + +ParameterisedType = ('Array' | 'Dictionary'), '<', Type, '>'; + +RecordType = 'RECORD', { ['PRIVATE'], Identifier, ':', Type}, 'END', 'RECORD'; + +EnumType = 'ENUM', {Identifier}, 'END', 'ENUM'; + +PointerType = 'POINTER', 'TO', Type; + +FunctionPointerType = 'FUNCTION', FunctionParameterList; + +ConstantDeclaration = 'CONSTANT', Identifier, ':', Type, ':=', Expression; + +NativeConstantDeclaration = 'DECLARE', 'NATIVE', 'CONSTANT', Identifier, ':', Type; + +VariableDeclaration = 'VAR', Identifier, {',', Identifier}, ':', Type, [':=', Expression]; + +NativeVariableDeclaration = 'DECLARE', 'NATIVE', 'VAR', Identifier, ':', Type; + +LetDeclaration = 'LET', Identifier, ':', Type, ':=', Expression; + +FunctionDeclaration = 'FUNCTION', FunctionHeader, {Statement}, 'END', 'FUNCTION'; + +ExternalFunctionDeclaration = 'EXTERNAL', 'FUNCTION', FunctionHeader, DictionaryLiteral, 'END', 'FUNCTION'; + +NativeFunctionDeclaration = 'DECLARE', 'NATIVE', 'FUNCTION', FunctionHeader; + +FunctionHeader = Identifier, ['.', Identifier], FunctionParameterList; + +FunctionParameterList = '(', [FunctionParameter, {',', FunctionParameter}], ')', [':', Type]; + +FunctionParameter = ['IN' | 'OUT' | 'INOUT'], Identifier, {',', Identifier}, ':', Type, ['DEFAULT', Expression]; + +ExceptionDeclaration = 'DECLARE', 'EXCEPTION', Identifier; + +ExportDeclaration = 'EXPORT', Identifier; + +MainBlock = 'BEGIN', 'MAIN', {Statement}, 'END', 'MAIN'; + +AssertStatement = 'ASSERT', Expression, {',', Expression}; + +AssignmentStatement = (CompoundExpression | '_'), ':=', Expression; + +CaseStatement = 'CASE', Expression, {'WHEN', WhenCondition, {',', WhenCondition}, 'DO', {Statement}}, ['WHEN', 'OTHERS', 'DO', {Statement}], 'END', 'CASE'; + +WhenCondition = (('=' | '#' | '<' | '>' | '<=' | '>='), Expression) | (Expression, ['TO', Expression]); + +ExitStatement = 'EXIT', ('FUNCTION' | 'WHILE' | 'FOR' | 'FOREACH' | 'LOOP' | 'REPEAT'); + +ExpressionStatement = Identifier, CompoundExpressionTail, {CompoundExpressionTail}; + +ForStatement = 'FOR', Identifier, ':=', Expression, 'TO', Expression, ['STEP', Expression], 'DO', {Statement}, 'END', 'FOR'; + +ForeachStatement = 'FOREACH', Identifier, 'OF', Expression, ['INDEX', Identifier], 'DO', {Statement}, 'END', 'FOREACH'; + +IfStatement = 'IF', IfExpression, 'THEN', {Statement}, {'ELSIF', IfExpression, 'THEN', {Statement}}, ['ELSE', {Statement}], 'END', 'IF'; + +IncrementStatement = ('INC' | 'DEC'), Expression; + +IfExpression = Expression | ('VALID', (Identifier | (Expression, 'AS', Identifier)), {',', (Identifier | (Expression, 'AS', Identifier))}); + +LoopStatement = 'LOOP', {Statement}, 'END', 'LOOP'; + +NextStatement = 'NEXT', ('WHILE' | 'FOR' | 'FOREACH' | 'LOOP' | 'REPEAT'); + +RaiseStatement = 'RAISE', Identifier, ['.', Identifier], ['(', Expression, {',', Expression}, ')']; + +RepeatStatement = 'REPEAT', {Statement}, 'UNTIL', Expression; + +ReturnStatement = 'RETURN', Expression; + +TryStatement = 'TRY', {Statement}, {'EXCEPTION', Identifier, ['.', Identifier], 'DO', {Statement}}, 'END', 'TRY'; + +WhileStatement = 'WHILE', Expression, 'DO', {Statement}, 'END', 'WHILE'; + +Expression = ConditionalExpression; + +ConditionalExpression = ('IF', Expression, 'THEN', Expression, 'ELSE', Expression) | DisjunctionExpression; + +DisjunctionExpression = ConjunctionExpression, {'OR', ConjunctionExpression}; + +ConjunctionExpression = MembershipExpression, {'AND', MembershipExpression}; + +MembershipExpression = ComparisonExpression, [('IN' | 'NOT', 'IN'), ComparisonExpression]; + +ComparisonExpression = AdditionExpression, {('=' | '#' | '<' | '>' | '<=' | '>='), AdditionExpression}; + +AdditionExpression = MultiplicationExpression, {('+' | '-' | '&'), MultiplicationExpression}; + +MultiplicationExpression = ExponentiationExpression, {('*' | '/' | 'MOD'), ExponentiationExpression}; + +ExponentiationExpression = Atom, {'^', Atom}; + +Atom = + ('(', Expression, ')') | + ArrayLiteral | ArrayRangeLiteral | + DictionaryLiteral | + ('FALSE' | 'TRUE') | + Number | + StringLiteral | + 'EMBED', StringLiteral | + 'HEXBYTES', StringLiteral | + (('+' | '-' | 'NOT'), Atom) | + ('NEW', Type) | + 'NIL' | + CompoundExpression; + +ArrayLiteral = '[', [Expression, {',', Expression}, [',']], ']'; + +ArrayRangeLiteral = '[', Expression, 'TO', Expression, ['STEP', Expression], ']'; + +DictionaryLiteral = '{', {StringLiteral, ':', Expression, [',']}, '}'; + +CompoundExpression = Identifier, {CompoundExpressionTail}; + +CompoundExpressionTail = + ('[', ArrayIndexExpression, {',', ArrayIndexExpression}, ']') | + ('(', [FunctionArgument, {',', FunctionArgument}], ')') | + ('.', Identifier) | + ('->', Identifier); + +FunctionArgument = ['IN' | 'INOUT' | 'OUT'], ((Expression | '_') | (Identifier, 'WITH', (Expression | '_'))); + +ArrayIndexExpression = Expression | (Expression, 'TO', Expression); diff --git a/contrib/grammar/test-grammar.py b/contrib/grammar/test-grammar.py new file mode 100644 index 0000000000..112d0327ff --- /dev/null +++ b/contrib/grammar/test-grammar.py @@ -0,0 +1,51 @@ +import glob +import sys + +from pyparsing import * + +import grammar + +KnownParseFailures = [ # Reason: + "t/assign2.neon", # + "t/comments.neon", # nested block comments + "t/lexer-raw.neon", # unicode + "t/lexer-unicode.neon", # unicode + "t/os-test.neon", # double-at raw string with embedded quote at end + "t/unicode-source.neon", # unicode + "t/utf8-invalid.neon", # invalid utf-8 + "t/errors/N3060.neon", # + "t/errors/N3061.neon", # +] + +# blockComment is similar to cStyleComment form pyparsing +blockComment = Regex(r"%\|(?:[^|]*\|+)+?%") +lineComment = "%" + restOfLine +comments = blockComment | lineComment + +#grammar.parsers["Program"].setDebug() +#grammar.parsers["Statement"].setDebug() +#grammar.parsers["EnumType"].setDebug() +#grammar.parsers["Type"].setDebug() +#grammar.parsers["ForeachStatement"].setDebug() +#ParserElement.verbose_stacktrace = True + +parser = grammar.parsers["Program"] +parser.ignore(comments) +for fn in reduce(lambda x, y: x + y, [glob.glob(x) for x in sys.argv[1:]]): + fn = fn.replace("\\", "/") + print(fn) + if "%!" in open(fn).read(): + print("skipped, failure") + if fn in KnownParseFailures: + print "Unneeded known failure:", fn + continue + try: + parser.parseFile(fn, parseAll=True) + if fn in KnownParseFailures: + print "Incorrect known failure:", fn + except ParseException, e: + if fn in KnownParseFailures: + print "Known failure:", e + else: + print >>sys.stderr, "Failure parsing:", fn + raise diff --git a/contrib/grammar/test-random.py b/contrib/grammar/test-random.py new file mode 100644 index 0000000000..b68b29461f --- /dev/null +++ b/contrib/grammar/test-random.py @@ -0,0 +1,77 @@ +import random +import re +import subprocess +import sys + +from pyparsing import * + +import grammar + +def random_ident(node): + while True: + s = random.choice(node.initCharsOrig) + "".join(random.sample(node.bodyCharsOrig, random.randint(2, 9))) + if re.match(r"[A-Z]+$", s): + continue + return s + +class Generator: + def __init__(self): + self.tokens = [] + self.depth = 0 + + def eval(self, node): + self.depth += 1 + if hasattr(node, "name") and node.name == "Statement": + self.tokens.append("\n" + " "*self.depth) + if hasattr(node, "name") and node.name == "Expression" and (random.uniform(0, 1) >= 0.05 or self.depth > 5): + ident = grammar.table["Identifier"].exprs[1] + self.tokens.append(random_ident(ident)) + elif node is grammar.table["Number"]: + self.tokens.append("12345") + elif isinstance(node, And): + for e in node.exprs: + self.eval(e) + elif isinstance(node, Forward): + self.eval(node.expr) + elif isinstance(node, Group): + self.eval(node.expr) + elif isinstance(node, (Keyword, Literal)): + self.tokens.append(node.match) + elif isinstance(node, MatchFirst): + self.eval(random.choice(node.exprs)) + elif isinstance(node, NotAny): + pass + elif isinstance(node, Optional): + if random.choice([False, True]): + self.eval(node.expr) + elif isinstance(node, Or): + self.eval(random.choice(node.exprs)) + elif isinstance(node, QuotedString): + self.tokens.append('"foo"') + elif isinstance(node, Regex): + #self.tokens.append("/"+node.pattern+"/") + self.tokens.append('"foo"') + elif isinstance(node, Word): + self.tokens.append(random_ident(node)) + elif isinstance(node, ZeroOrMore): + while random.choice([False, True]): + self.eval(node.expr) + else: + assert False, type(node) + self.depth -= 1 + +sys.setrecursionlimit(5000) +for _ in range(100): + g = Generator() + g.eval(grammar.parsers["Statement"]) + source = " ".join(map(str, g.tokens)) + #print source + p = subprocess.Popen(["bin/neonc", "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate(source) + p.wait() + if "Error N1" in err or "Error N2" in err: + with open("tmp/random.out", "w") as f: + f.write(source) + print err + print "Randomly generated source saved in tmp/random.out" + sys.exit(1) diff --git a/doc/language.md b/doc/language.md deleted file mode 100644 index 2634496d43..0000000000 --- a/doc/language.md +++ /dev/null @@ -1,246 +0,0 @@ -# Neon Language - -## Lexical Structure - -Neon source code is encoded in UTF-8. - -### Comments - -The comment lead-in character is `%`. -A single line comment is a `%` followed by arbitrary text to the end of the line. -A block comment is introduced with `%|` and ends with `|%`. -Block comments may span multiple lines. -Block comments may be nested. - -### Keywords - -* `AND` -* `ARRAY` -* `CASE` -* `CONST` -* `DECLARE` -* `DICTIONARY` -* `DO` -* `ELSE` -* `ELSIF` -* `ENUM` -* `END` -* `EXCEPTION` -* `EXIT` -* `EXTERNAL` -* `FALSE` -* `FOR` -* `FUNCTION` -* `IF` -* `IN` -* `INOUT` -* `IMPORT` -* `LOOP` -* `MOD` -* `NEXT` -* `NEW` -* `NIL` -* `NOT` -* `OR` -* `OUT` -* `POINTER` -* `RAISE` -* `RECORD` -* `REPEAT` -* `RETURN` -* `STEP` -* `THEN` -* `TO` -* `TRUE` -* `TRY` -* `TYPE` -* `UNTIL` -* `VAR` -* `VALID` -* `WHEN` -* `WHILE` - -### Identifiers - -An Identifier is a letter followed by any number of letters or digits. - -### Numbers - -Literal numbers are in base 10 by default. -Numbers may contain a fractional portion following a decimal point `.`. -Numbers may have an exponent following `e` or `E`. - -Numbers may be specified in a variety of bases: - -* Binary preceded by `0b` -* Octal preceded by `0o` -* Hexadecimal preceded by `0x` -* Any base from 2 to 36 preceded by `0#n#` where `n` is the base - -### Strings - -Strings are sequences of characters surrounded by double quotes. - -## Types - -### Boolean - -Boolean values can take on two values, `FALSE` or `TRUE`. - -### Number - -Number values are 64-bit decimal floating point. -The valid magnitude range of numbers are (in addition to zero): - -* Minimum: 1.000000000000000e-383 -* Maximum: 9.999999999999999e384 - -### String - -String values are sequences of Unicode characters. - -### Enumeration - -Enumerations are declared with the `ENUM` keyword. - -### Record - -Records are declared with the `RECORD` keyword. - -### Array - -Arrays are variable size sequences of values indexed by nonnegative integers. -Arrays are declared using the syntax `Array` where `Type` represents the type of elements. - -### Dictionary - -Dictionaries are an associative map which pairs a unique `String` with a value of some type. -Dictionaries are declared using the syntax `Dictionary` where `Type` represents the type of elements. - -### Pointers - -Pointers are addresses of dynamically allocated records. -The `NEW` keyword allocates a new record of a given type and returns a pointer to it. -Pointers may have the value `NIL` that does not point to any object. -To use (dereference) a pointer, it must first be checked for vaildity (not `NIL`) using the `IF VALID` construct. - -## Expressions - -### Boolean Operators - -* `=` -* `#` -* `AND` -* `OR` - -### Numeric Operators - -* `+` -* `-` -* `*` -* `/` -* `MOD` -* `^` -* `=` -* `#` -* `<` -* `>` -* `<=` -* `>=` - -### String Operators - -* `+` -* `=` -* `#` -* `<` -* `>` -* `<=` -* `>=` - -### Operator Precedence - -The operator precedence is as follows, highest to lowest: - -* `^` exponentiation -* `*` `/` `MOD` multiplication, division, modulo -* `+` `-` addition, subtraction -* `<` `=` `>` comparison -* `IN` membership -* `AND` conjunction -* `OR` disjunction -* `IF` conditional - -## Declarations - -### Types - -User defined types are introduced with the `RECORD` keyword: - - VAR r: RECORD - size: Number - colour: String - END RECORD - -Types may be assigned names using the `TYPE` keyword: - - TYPE Widget := RECORD - size: Number - colour: String - END RECORD - - VAR r: Widget - -### Constants - -Constants are defined using the `CONST` keyword. - - CONST Pi: Number := 3.141592653589793 - CONST Sky: String := "blue" - -The value assigned to a constant must be able to be evaluated at compile time. -This may be an expression: - - CONST Pi2: Number := Pi ^ 2 - CONST Ocean: "Deep " + Sky - -### Variables - -Variables are declared using the `VAR` keyword: - - VAR count: Number - VAR colour: String - -Variables declared outside a function are *global* variables. -Variables declared inside a function are visible only from within that function. - -### Functions - -Functions are declared using the `FUNCTION` keyword: - - FUNCTION square(x: Number): Number - RETURN x ^ 2 - END FUNCTION - -## Statements - -* `:=` (assignment) -* Function call -* `BREAK` -* `CASE` -* `EXIT` -* `FOR` -* `IF` -* `LOOP` -* `NEXT` -* `RAISE` -* `REPEAT` -* `RETURN` -* `TRY` -* `WHILE` - -## Functions - -## Standard Library - -## Grammar diff --git a/external/NaturalDocs-1.52.zip b/external/NaturalDocs-1.52.zip new file mode 100644 index 0000000000..fbf4a9c6a8 Binary files /dev/null and b/external/NaturalDocs-1.52.zip differ diff --git a/external/SConscript-libbid b/external/SConscript-libbid new file mode 100644 index 0000000000..586ad491d8 --- /dev/null +++ b/external/SConscript-libbid @@ -0,0 +1,27 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +bidenv = Environment() + +if GetOption("clean"): + shutil.rmtree("IntelRDFPMathLib20U1", ignore_errors=True) +elif not os.path.exists("IntelRDFPMathLib20U1/LIBRARY/makefile.mak"): + tarfile.open("IntelRDFPMathLib20U1.tar.gz").extractall(".") + if not env["RELEASE"]: + if sys.platform == "win32": + makefile = open("IntelRDFPMathLib20U1/LIBRARY/makefile.mak").read() + makefile = makefile.replace("DEBUG=/Od /Zi", "DEBUG=/Od /Zi /MTd") + open("IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "w").write(makefile) + +if sys.platform == "win32": + libbid = bidenv.Command("IntelRDFPMathLib20U1/LIBRARY/libbid.lib", "IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "cd external/IntelRDFPMathLib20U1/LIBRARY && nmake -fmakefile.mak CC=cl GLOBAL_RND=1 GLOBAL_FLAGS=1 DBG=1") +else: + libbid = bidenv.Command("IntelRDFPMathLib20U1/LIBRARY/libbid.a", "IntelRDFPMathLib20U1/LIBRARY/makefile.mak", "cd external/IntelRDFPMathLib20U1/LIBRARY && make CC=gcc GLOBAL_RND=1 GLOBAL_FLAGS=1") + +env.Append(CPPPATH=["external/IntelRDFPMathLib20U1/LIBRARY/src"]) + +Return(["libbid"]) diff --git a/external/SConscript-libbz2 b/external/SConscript-libbz2 new file mode 100644 index 0000000000..481ebfb17c --- /dev/null +++ b/external/SConscript-libbz2 @@ -0,0 +1,38 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libbz2 = None + +bz2env = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + bz2env.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("bzip2-1.0.6", ignore_errors=True) +elif not os.path.exists("bzip2-1.0.6/Makefile"): + tarfile.open("bzip2-1.0.6.tar.gz").extractall(".") + +if sys.platform == "win32": + libbz2 = bz2env.Library("bzip2-1.0.6/libbz2.lib", [ + "bzip2-1.0.6/blocksort.c", + "bzip2-1.0.6/bzlib.c", + "bzip2-1.0.6/compress.c", + "bzip2-1.0.6/crctable.c", + "bzip2-1.0.6/decompress.c", + "bzip2-1.0.6/huffman.c", + "bzip2-1.0.6/randtable.c", + ]) + env.Append(CPPPATH=["external/bzip2-1.0.6"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("bz2", "bzlib.h", "C++"): + libbz2 = bz2env.Command("bzip2-1.0.6/libbz2.a", "bzip2-1.0.6/Makefile", "cd external/bzip2-1.0.6 && make") + env.Append(CPPPATH=["external/bzip2-1.0.6"]) + conf.Finish() + +Return(["libbz2"]) diff --git a/external/SConscript-libcurl b/external/SConscript-libcurl new file mode 100644 index 0000000000..3c5e15f7c3 --- /dev/null +++ b/external/SConscript-libcurl @@ -0,0 +1,79 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libcurl = None + +curlenv = Environment() + +if GetOption("clean"): + shutil.rmtree("curl-7.41.0", ignore_errors=True) +elif not os.path.exists("curl-7.41.0/configure"): + tarfile.open("curl-7.41.0.tar.gz").extractall(".") + if sys.platform == "win32": + config_win32 = open("curl-7.41.0/lib/config-win32.h").read() + config_win32 = "#define HTTP_ONLY\n" + config_win32 + config_win32 = "#define USE_WINDOWS_SSPI\n" + config_win32 + config_win32 = "#define USE_SCHANNEL\n" + config_win32 + open("curl-7.41.0/lib/config-win32.h", "w").write(config_win32) + +if sys.platform == "win32": + env.Append(CPPFLAGS=["-DCURL_STATICLIB"]) + curlenv.Append(CPPFLAGS=["-DCURL_STATICLIB"]) + libname = "libcurl" if env["RELEASE"] else "libcurld" + cfgname = "release" if env["RELEASE"] else "debug" + libcurl = curlenv.Command("curl-7.41.0/lib/{}.lib".format(libname), "curl-7.41.0/winbuild/Makefile.vc", "cd external/curl-7.41.0/lib && nmake /f Makefile.vc10 CFG={} MACHINE=x64 RTLIBCFG=static".format(cfgname)) + env.Append(CPPPATH=["external/curl-7.41.0/include"]) + env.Append(LIBS=["ws2_32"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("curl", "curl/curl.h", "C++"): + libcurl = curlenv.Command("curl-7.41.0/lib/.libs/libcurl.a", "curl-7.41.0/configure", " ".join(["cd external/curl-7.41.0 &&", + "./configure", + "--disable-ares", + "--disable-ftp", + "--disable-file", + "--disable-ldap", + "--disable-ldaps", + "--disable-rtsp", + "--disable-proxy", + "--disable-dict", + "--disable-telnet", + "--disable-tftp", + "--disable-pop3", + "--disable-imap", + "--disable-smb", + "--disable-smtp", + "--disable-gopher", + "--disable-manual", + "--disable-ipv6", + "--disable-threaded-resolver", + "--disable-sspi", + "--disable-crypto-auth", + "--disable-ntlm-wb", + "--disable-tls-srp", + "--disable-unix-sockets", + "--disable-cookies", + "--disable-soname-bump", + "--without-winssl", + "--without-darwinssl", + "--without-gnutls", + "--without-polarssl", + "--without-cyassl", + "--without-nss", + "--without-axtls", + "--without-libmetalink", + "--without-libssh2", + "--without-librtmp", + "--without-winidn", + "--without-libidn", + "--without-nghttp2", + "--with-ssl=" + os.path.abspath("."), + "&& make"])) + env.Append(CPPPATH=["external/curl-7.41.0/include"]) + conf.Finish() + +Return(["libcurl"]) diff --git a/external/SConscript-libcurses b/external/SConscript-libcurses new file mode 100644 index 0000000000..ba6e5c2c75 --- /dev/null +++ b/external/SConscript-libcurses @@ -0,0 +1,32 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libs_curses = [] + +cursesenv = Environment() + +if sys.platform == "win32": + if GetOption("clean"): + shutil.rmtree("PDCurses-3.4", ignore_errors=True) + elif not os.path.exists("PDCurses-3.4/win32/vcwin32.mak"): + tarfile.open("PDCurses-3.4.tar.gz").extractall(".") + if not env["RELEASE"]: + makefile = open("PDCurses-3.4/win32/vcwin32.mak").read() + makefile = makefile.replace("= -Z7 -DPDCDEBUG", "= -Z7 -DPDCDEBUG -MTd") + open("PDCurses-3.4/win32/vcwin32.mak", "w").write(makefile) + + libs_curses = [cursesenv.Command("PDCurses-3.4/win32/pdcurses.lib", "PDCurses-3.4/win32/vcwin32.mak", "cd external/PDCurses-3.4/win32 && nmake -fvcwin32.mak WIDE=Y UTF8=Y DEBUG=Y")] + libs_curses.extend(["advapi32", "user32"]) + env.Append(CPPPATH=["external/PDCurses-3.4"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("ncurses", "curses.h", "C++"): + print "Could not find ncurses library" + Exit(1) + conf.Finish() + +Return(["libs_curses"]) diff --git a/external/SConscript-libeasysid b/external/SConscript-libeasysid new file mode 100644 index 0000000000..43af9ce598 --- /dev/null +++ b/external/SConscript-libeasysid @@ -0,0 +1,19 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +easysidenv = Environment() + +easysidenv["ENV"]["PROCESSOR_ARCHITECTURE"] = env["ENV"]["PROCESSOR_ARCHITECTURE"] +easysidenv["ENV"]["PROCESSOR_ARCHITEW6432"] = env["ENV"]["PROCESSOR_ARCHITEW6432"] + +if GetOption("clean"): + shutil.rmtree("easysid-version-1.0", ignore_errors=True) +elif not os.path.exists("easysid-version-1.0/SConstruct"): + tarfile.open("easysid-version-1.0.tar.gz").extractall(".") +libeasysid = easysidenv.Command("easysid-version-1.0/libeasysid"+easysidenv["SHLIBSUFFIX"], "easysid-version-1.0/SConstruct", "cd external/easysid-version-1.0 && " + sys.executable + " " + sys.argv[0]) + +Return(["libeasysid"]) diff --git a/external/SConscript-libffi b/external/SConscript-libffi new file mode 100644 index 0000000000..d51af3347e --- /dev/null +++ b/external/SConscript-libffi @@ -0,0 +1,95 @@ +import os +import shutil +import sys +import tarfile + +def find_tool(name): + justname = os.path.basename(name) + for p in env["ENV"]["PATH"].split(";"): + fn = os.path.join(p, justname) + if os.access(fn, os.F_OK): + return fn + fn = os.path.join(p, name) + if os.access(fn, os.F_OK): + return fn + print >>sys.stderr, "could not find tool:", name + sys.exit(1) + +def subs(target, source, env): + with open(target[0].path, "w") as outf: + with open(source[0].path) as inf: + outf.write(inf.read() + .replace("@VERSION@", "3.2.1") + .replace("@TARGET@", "X86_WIN64") + .replace("@HAVE_LONG_DOUBLE@", "HAVE_LONG_DOUBLE") + .replace("@HAVE_LONG_DOUBLE_VARIANT@", "HAVE_LONG_DOUBLE_VARIANT") + .replace("@FFI_EXEC_TRAMPOLINE_TABLE@", "FFI_EXEC_TRAMPOLINE_TABLE") + ) + +def fix_target(target, source, env): + with open(target[0].path, "w") as outf: + with open(source[0].path) as inf: + for s in inf: + if s.startswith("#define FFI_TARGET_HAS_COMPLEX_TYPE"): + s = "//" + s + outf.write(s) + +def sub_config(target, source, env): + with open(target[0].path, "w") as outf: + with open(source[0].path) as inf: + for s in inf: + if s.startswith("#undef"): + if s.split()[1] in [ + "HAVE_MEMCPY", + "FFI_NO_RAW_API", + ]: + s = "#define {} 1".format(s.split()[1]) + outf.write(s) + +def remove_short(target, source, env): + with open(target[0].path, "w") as outf: + with open(source[0].path) as inf: + outf.write(inf.read().replace("SHORT", "")) + +Import("env") + +ffienv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + ffienv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("libffi-3.2.1", ignore_errors=True) +elif not os.path.exists("libffi-3.2.1/configure"): + tarfile.open("libffi-3.2.1.tar.gz").extractall(".") + +if sys.platform == "win32": + env.Append(CPPDEFINES=["FFI_BUILDING"]) + ffienv.Append(CPPDEFINES=["FFI_BUILDING"]) + ffienv.Append(CPPPATH=["libffi-3.2.1/x86-win64/include"]) + ffi_h = ffienv.Command("libffi-3.2.1/x86-win64/include/ffi.h", "libffi-3.2.1/include/ffi.h.in", subs) + ffitarget_h = ffienv.Command("libffi-3.2.1/x86-win64/include/ffitarget.h", "libffi-3.2.1/src/x86/ffitarget.h", fix_target) + fficommon_h = ffienv.Command("libffi-3.2.1/x86-win64/include/ffi_common.h", "libffi-3.2.1/include/ffi_common.h", Copy("$TARGET", "$SOURCE")) + fficonfig_h = ffienv.Command("libffi-3.2.1/x86-win64/include/fficonfig.h", "libffi-3.2.1/fficonfig.h.in", sub_config) + win64_p = ffienv.Command("libffi-3.2.1/x86-win64/win64.p", "libffi-3.2.1/src/x86/win64.S", "cl /EP /I external/libffi-3.2.1/x86-win64/include $SOURCE >$TARGET") + win64_asm = ffienv.Command("libffi-3.2.1/x86-win64/win62.asm", win64_p, remove_short) + objects = [ + ffienv.Object("libffi-3.2.1/x86-win64/closures.obj", "libffi-3.2.1/src/closures.c"), + ffienv.Object("libffi-3.2.1/x86-win64/ffi.obj", "libffi-3.2.1/src/x86/ffi.c"), + ffienv.Object("libffi-3.2.1/x86-win64/prep_cif.obj", "libffi-3.2.1/src/prep_cif.c"), + ffienv.Object("libffi-3.2.1/x86-win64/types.obj", "libffi-3.2.1/src/types.c"), + ] + for o in objects: + ffienv.Depends(o, [ffi_h, ffitarget_h, fficommon_h, fficonfig_h]) + libffi = ffienv.Library("libffi-3.2.1/x86-win64/libffi.lib", objects + [ + ffienv.Command("libffi-3.2.1/x86-win64/win64.obj", win64_asm, "\"{}\" /c /Cx /Fo$TARGET $SOURCE".format(find_tool("x86_amd64/ml64.exe"))), + ]) + ffienv.Install("lib/libffi-3.2.1/include/", [ffi_h, ffitarget_h, fficommon_h, fficonfig_h]) + libffi = ffienv.Install("lib/", libffi) +else: + ffienv.Command("libffi-3.2.1/Makefile", "libffi-3.2.1/configure", "cd external/libffi-3.2.1 && ./configure --prefix=`pwd`/..") + libffi = ffienv.Command("lib/libffi.a", "libffi-3.2.1/Makefile", "cd external/libffi-3.2.1 && make && make install") + +env.Append(CPPPATH=["external/lib/libffi-3.2.1/include"]) + +Return(["libffi"]) diff --git a/external/SConscript-libhash b/external/SConscript-libhash new file mode 100644 index 0000000000..b42acd4aab --- /dev/null +++ b/external/SConscript-libhash @@ -0,0 +1,31 @@ +import os +import shutil +import subprocess +import sys +import zipfile + +Import("env") + +hashenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + hashenv.Append(CXXFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("hash-library", ignore_errors=True) +elif not os.path.exists("hash-library/sha256.cpp"): + zipfile.ZipFile("hash-library.zip").extractall("hash-library") + if sys.platform == "darwin": + subprocess.check_call("perl -n -i -e 'print unless //' hash-library/*.cpp", shell=True) + +libhash = hashenv.Library("hash-library/hash-library", [ + "hash-library/crc32.cpp", + "hash-library/md5.cpp", + "hash-library/sha1.cpp", + "hash-library/sha256.cpp", + "hash-library/sha3.cpp", +]) + +env.Append(CPPPATH=["external/hash-library"]) + +Return(["libhash"]) diff --git a/external/SConscript-liblzma b/external/SConscript-liblzma new file mode 100644 index 0000000000..0d6cf7cca3 --- /dev/null +++ b/external/SConscript-liblzma @@ -0,0 +1,108 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +liblzma = None + +lzmaenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + lzmaenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("xz-5.2.1", ignore_errors=True) +elif not os.path.exists("xz-5.2.1/configure"): + tarfile.open("xz-5.2.1.tar.gz").extractall(".") + +if sys.platform == "win32": + env.Append(CPPDEFINES=[ + "LZMA_API_STATIC", + ]) + lzmaenv.Append(CPPDEFINES=[ + "LZMA_API_STATIC", + ]) + lzmaenv.Append(CPPPATH=[ + "xz-5.2.1/src/common", + "xz-5.2.1/src/liblzma/api", + "xz-5.2.1/src/liblzma/check", + "xz-5.2.1/src/liblzma/common", + "xz-5.2.1/src/liblzma/delta", + "xz-5.2.1/src/liblzma/lz", + "xz-5.2.1/src/liblzma/lzma", + "xz-5.2.1/src/liblzma/rangecoder", + "xz-5.2.1/src/liblzma/simple", + "xz-5.2.1/windows", + ]) + lzmaenv.Append(CPPDEFINES=[ + ("DWORD", "unsigned long"), + "HAVE_CONFIG_H", + ]) + liblzma = lzmaenv.Library("xz-5.2.1/src/liblzma/liblzma.lib", [ + "xz-5.2.1/src/liblzma/check/check.c", + "xz-5.2.1/src/liblzma/check/crc32_fast.c", + "xz-5.2.1/src/liblzma/check/crc32_table.c", + "xz-5.2.1/src/liblzma/check/crc64_fast.c", + "xz-5.2.1/src/liblzma/check/crc64_table.c", + "xz-5.2.1/src/liblzma/check/sha256.c", + "xz-5.2.1/src/liblzma/delta/delta_common.c", + "xz-5.2.1/src/liblzma/delta/delta_decoder.c", + "xz-5.2.1/src/liblzma/delta/delta_encoder.c", + "xz-5.2.1/src/liblzma/common/block_header_decoder.c", + "xz-5.2.1/src/liblzma/common/block_header_encoder.c", + "xz-5.2.1/src/liblzma/common/block_buffer_encoder.c", + "xz-5.2.1/src/liblzma/common/block_decoder.c", + "xz-5.2.1/src/liblzma/common/block_util.c", + "xz-5.2.1/src/liblzma/common/common.c", + "xz-5.2.1/src/liblzma/common/easy_buffer_encoder.c", + "xz-5.2.1/src/liblzma/common/easy_preset.c", + "xz-5.2.1/src/liblzma/common/filter_common.c", + "xz-5.2.1/src/liblzma/common/filter_decoder.c", + "xz-5.2.1/src/liblzma/common/filter_encoder.c", + "xz-5.2.1/src/liblzma/common/filter_flags_decoder.c", + "xz-5.2.1/src/liblzma/common/filter_flags_encoder.c", + "xz-5.2.1/src/liblzma/common/index.c", + "xz-5.2.1/src/liblzma/common/index_encoder.c", + "xz-5.2.1/src/liblzma/common/index_hash.c", + "xz-5.2.1/src/liblzma/common/stream_buffer_decoder.c", + "xz-5.2.1/src/liblzma/common/stream_buffer_encoder.c", + "xz-5.2.1/src/liblzma/common/stream_decoder.c", + "xz-5.2.1/src/liblzma/common/stream_flags_common.c", + "xz-5.2.1/src/liblzma/common/stream_flags_decoder.c", + "xz-5.2.1/src/liblzma/common/stream_flags_encoder.c", + "xz-5.2.1/src/liblzma/common/vli_decoder.c", + "xz-5.2.1/src/liblzma/common/vli_encoder.c", + "xz-5.2.1/src/liblzma/common/vli_size.c", + "xz-5.2.1/src/liblzma/lz/lz_decoder.c", + "xz-5.2.1/src/liblzma/lz/lz_encoder.c", + "xz-5.2.1/src/liblzma/lz/lz_encoder_mf.c", + "xz-5.2.1/src/liblzma/lzma/fastpos_table.c", + "xz-5.2.1/src/liblzma/lzma/lzma_decoder.c", + "xz-5.2.1/src/liblzma/lzma/lzma_encoder.c", + "xz-5.2.1/src/liblzma/lzma/lzma_encoder_optimum_fast.c", + "xz-5.2.1/src/liblzma/lzma/lzma_encoder_optimum_normal.c", + "xz-5.2.1/src/liblzma/lzma/lzma_encoder_presets.c", + "xz-5.2.1/src/liblzma/lzma/lzma2_decoder.c", + "xz-5.2.1/src/liblzma/lzma/lzma2_encoder.c", + "xz-5.2.1/src/liblzma/rangecoder/price_table.c", + "xz-5.2.1/src/liblzma/simple/arm.c", + "xz-5.2.1/src/liblzma/simple/armthumb.c", + "xz-5.2.1/src/liblzma/simple/ia64.c", + "xz-5.2.1/src/liblzma/simple/powerpc.c", + "xz-5.2.1/src/liblzma/simple/simple_coder.c", + "xz-5.2.1/src/liblzma/simple/simple_decoder.c", + "xz-5.2.1/src/liblzma/simple/simple_encoder.c", + "xz-5.2.1/src/liblzma/simple/sparc.c", + "xz-5.2.1/src/liblzma/simple/x86.c", + ]) + env.Append(CPPPATH=["external/xz-5.2.1/src/liblzma/api"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("lzma", "lzma.h", "C++"): + liblzma = lzmaenv.Command("xz-5.2.1/src/liblzma/.libs/liblzma.a", "xz-5.2.1/configure", "cd external/xz-5.2.1 && ./configure && make") + env.Append(CPPPATH=["external/xz-5.2.1/src/liblzma/api"]) + conf.Finish() + +Return(["liblzma"]) diff --git a/external/SConscript-libminizip b/external/SConscript-libminizip new file mode 100644 index 0000000000..96b084f7ac --- /dev/null +++ b/external/SConscript-libminizip @@ -0,0 +1,40 @@ +import os +import shutil +import sys +import zipfile + +Import("env") + +libminizip = None + +libminizipenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + libminizipenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("minizip11", ignore_errors=True) +elif not os.path.exists("minizip11/Makefile"): + zipfile.ZipFile("unzip11.zip").extractall("minizip11") + +libminizipenv.Append(CPPPATH=["zlib-1.2.8"]) + +if sys.platform == "win32": + libminizip = libminizipenv.Library("minizip11/minizip.lib", [ + "minizip11/ioapi.c", + "minizip11/iowin32.c", + "minizip11/unzip.c", + "minizip11/zip.c", + ]) + env.Append(CPPPATH=["external/minizip11"]) +else: + if sys.platform.startswith("darwin"): + libminizipenv.Append(CPPFLAGS=["-Dfopen64=fopen", "-Dfseeko64=fseeko", "-Dftello64=ftello"]) + libminizip = libminizipenv.Library("minizip11/libminizip.a", [ + "minizip11/ioapi.c", + "minizip11/unzip.c", + "minizip11/zip.c", + ]) + env.Append(CPPPATH=["external/minizip11"]) + +Return(["libminizip"]) diff --git a/external/SConscript-libpcre b/external/SConscript-libpcre new file mode 100644 index 0000000000..9a41177ca5 --- /dev/null +++ b/external/SConscript-libpcre @@ -0,0 +1,63 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libpcre = None + +pcreenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + pcreenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("pcre2-10.10", ignore_errors=True) +elif not os.path.exists("pcre2-10.10/configure"): + tarfile.open("pcre2-10.10.tar.gz").extractall(".") + if sys.platform == "win32": + shutil.copyfile("pcre2-10.10/src/config.h.generic", "pcre2-10.10/src/config.h") + shutil.copyfile("pcre2-10.10/src/pcre2.h.generic", "pcre2-10.10/src/pcre2.h") + +if sys.platform == "win32": + pcreenv.Append(CPPFLAGS=[ + "-DHAVE_CONFIG_H", + "-DPCRE2_CODE_UNIT_WIDTH=8", + "-DPCRE2_STATIC", + ]) + pcreenv.Command("pcre2-10.10/src/pcre2_chartables.c", "pcre2-10.10/src/pcre2_chartables.c.dist", lambda target, source, env: shutil.copyfile(source[0].path, target[0].path)) + libpcre = pcreenv.Library("pcre2-10.10/pcre2.lib", [ + "pcre2-10.10/src/pcre2_auto_possess.c", + "pcre2-10.10/src/pcre2_chartables.c", + "pcre2-10.10/src/pcre2_compile.c", + "pcre2-10.10/src/pcre2_config.c", + "pcre2-10.10/src/pcre2_context.c", + "pcre2-10.10/src/pcre2_dfa_match.c", + "pcre2-10.10/src/pcre2_error.c", + "pcre2-10.10/src/pcre2_jit_compile.c", + "pcre2-10.10/src/pcre2_maketables.c", + "pcre2-10.10/src/pcre2_match.c", + "pcre2-10.10/src/pcre2_match_data.c", + "pcre2-10.10/src/pcre2_newline.c", + "pcre2-10.10/src/pcre2_ord2utf.c", + "pcre2-10.10/src/pcre2_pattern_info.c", + "pcre2-10.10/src/pcre2_serialize.c", + "pcre2-10.10/src/pcre2_string_utils.c", + "pcre2-10.10/src/pcre2_study.c", + "pcre2-10.10/src/pcre2_substitute.c", + "pcre2-10.10/src/pcre2_substring.c", + "pcre2-10.10/src/pcre2_tables.c", + "pcre2-10.10/src/pcre2_ucd.c", + "pcre2-10.10/src/pcre2_valid_utf.c", + "pcre2-10.10/src/pcre2_xclass.c", + ]) + env.Append(CPPPATH=["external/pcre2-10.10/src"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("pcre2", "pcre2.h", "C++"): + libpcre = pcreenv.Command("pcre2-10.10/.libs/libpcre2-8.a", "pcre2-10.10/configure", "cd external/pcre2-10.10 && ./configure && make") + env.Append(CPPPATH=["external/pcre2-10.10/src"]) + conf.Finish() + +Return(["libpcre"]) diff --git a/external/SConscript-libsdl b/external/SConscript-libsdl new file mode 100644 index 0000000000..82e9c210dc --- /dev/null +++ b/external/SConscript-libsdl @@ -0,0 +1,154 @@ +import os +import re +import shutil +import sys +import tarfile + +Import("env") + +libsdl = None + +libsdlenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + libsdlenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("SDL2-2.0.3", ignore_errors=True) +elif not os.path.exists("SDL2-2.0.3/configure"): + tarfile.open("SDL2-2.0.3.tar.gz").extractall(".") + if sys.platform == "win32": + config = open("SDL2-2.0.3/include/SDL_config_windows.h").read() + config = re.sub(r"SDL_AUDIO_DRIVER_XAUDIO2\s+1", "SDL_AUDIO_DRIVER_XAUDIO2 0", config) + open("SDL2-2.0.3/include/SDL_config_windows.h", "w").write(config) + dynapi = open("SDL2-2.0.3/src/dynapi/SDL_dynapi.h").read() + dynapi = re.sub(r"SDL_DYNAMIC_API\s+1", "SDL_DYNAMIC_API 0", dynapi) + open("SDL2-2.0.3/src/dynapi/SDL_dynapi.h", "w").write(dynapi) + +if sys.platform == "win32": + env.Append(LIBS=["gdi32", "imm32", "ole32", "oleaut32", "shell32", "version", "winmm"]) + libsdlenv.Append(CPPPATH=["SDL2-2.0.3/include"]) + libsdl = libsdlenv.Library("SDL2-2.0.3/sdl.lib", [ + "SDL2-2.0.3/src/SDL.c", + "SDL2-2.0.3/src/SDL_assert.c", + "SDL2-2.0.3/src/SDL_error.c", + "SDL2-2.0.3/src/SDL_hints.c", + "SDL2-2.0.3/src/SDL_log.c", + "SDL2-2.0.3/src/atomic/SDL_atomic.c", + "SDL2-2.0.3/src/atomic/SDL_spinlock.c", + "SDL2-2.0.3/src/audio/SDL_audio.c", + "SDL2-2.0.3/src/audio/SDL_audiocvt.c", + "SDL2-2.0.3/src/audio/SDL_audiotypecvt.c", + "SDL2-2.0.3/src/audio/SDL_mixer.c", + "SDL2-2.0.3/src/audio/directsound/SDL_directsound.c", + "SDL2-2.0.3/src/audio/disk/SDL_diskaudio.c", + "SDL2-2.0.3/src/audio/dummy/SDL_dummyaudio.c", + "SDL2-2.0.3/src/audio/winmm/SDL_winmm.c", + "SDL2-2.0.3/src/audio/xaudio2/SDL_xaudio2.c", + "SDL2-2.0.3/src/core/windows/SDL_windows.c", + "SDL2-2.0.3/src/cpuinfo/SDL_cpuinfo.c", + "SDL2-2.0.3/src/events/SDL_clipboardevents.c", + "SDL2-2.0.3/src/events/SDL_dropevents.c", + "SDL2-2.0.3/src/events/SDL_events.c", + "SDL2-2.0.3/src/events/SDL_gesture.c", + "SDL2-2.0.3/src/events/SDL_keyboard.c", + "SDL2-2.0.3/src/events/SDL_mouse.c", + "SDL2-2.0.3/src/events/SDL_quit.c", + "SDL2-2.0.3/src/events/SDL_touch.c", + "SDL2-2.0.3/src/events/SDL_windowevents.c", + "SDL2-2.0.3/src/file/SDL_rwops.c", + "SDL2-2.0.3/src/haptic/SDL_haptic.c", + "SDL2-2.0.3/src/haptic/windows/SDL_syshaptic.c", + "SDL2-2.0.3/src/joystick/SDL_gamecontroller.c", + "SDL2-2.0.3/src/joystick/SDL_joystick.c", + "SDL2-2.0.3/src/joystick/windows/SDL_dxjoystick.c", + "SDL2-2.0.3/src/libm/e_atan2.c", + "SDL2-2.0.3/src/libm/e_log.c", + "SDL2-2.0.3/src/libm/e_pow.c", + "SDL2-2.0.3/src/libm/e_rem_pio2.c", + "SDL2-2.0.3/src/libm/e_sqrt.c", + "SDL2-2.0.3/src/libm/k_cos.c", + "SDL2-2.0.3/src/libm/k_rem_pio2.c", + "SDL2-2.0.3/src/libm/k_sin.c", + "SDL2-2.0.3/src/libm/s_atan.c", + "SDL2-2.0.3/src/libm/s_copysign.c", + "SDL2-2.0.3/src/libm/s_cos.c", + "SDL2-2.0.3/src/libm/s_fabs.c", + "SDL2-2.0.3/src/libm/s_floor.c", + "SDL2-2.0.3/src/libm/s_scalbn.c", + "SDL2-2.0.3/src/libm/s_sin.c", + "SDL2-2.0.3/src/loadso/windows/SDL_sysloadso.c", + "SDL2-2.0.3/src/render/SDL_d3dmath.c", + "SDL2-2.0.3/src/render/SDL_render.c", + "SDL2-2.0.3/src/render/SDL_yuv_sw.c", + "SDL2-2.0.3/src/render/direct3d/SDL_render_d3d.c", + "SDL2-2.0.3/src/render/opengl/SDL_render_gl.c", + "SDL2-2.0.3/src/render/opengl/SDL_shaders_gl.c", + "SDL2-2.0.3/src/render/opengles2/SDL_render_gles2.c", + "SDL2-2.0.3/src/render/opengles2/SDL_shaders_gles2.c", + "SDL2-2.0.3/src/render/software/SDL_blendfillrect.c", + "SDL2-2.0.3/src/render/software/SDL_blendline.c", + "SDL2-2.0.3/src/render/software/SDL_blendpoint.c", + "SDL2-2.0.3/src/render/software/SDL_drawline.c", + "SDL2-2.0.3/src/render/software/SDL_drawpoint.c", + "SDL2-2.0.3/src/render/software/SDL_render_sw.c", + "SDL2-2.0.3/src/render/software/SDL_rotate.c", + "SDL2-2.0.3/src/stdlib/SDL_getenv.c", + "SDL2-2.0.3/src/stdlib/SDL_iconv.c", + "SDL2-2.0.3/src/stdlib/SDL_malloc.c", + "SDL2-2.0.3/src/stdlib/SDL_qsort.c", + "SDL2-2.0.3/src/stdlib/SDL_stdlib.c", + "SDL2-2.0.3/src/stdlib/SDL_string.c", + "SDL2-2.0.3/src/thread/SDL_thread.c", + "SDL2-2.0.3/src/thread/generic/SDL_syscond.c", + "SDL2-2.0.3/src/thread/windows/SDL_sysmutex.c", + "SDL2-2.0.3/src/thread/windows/SDL_syssem.c", + "SDL2-2.0.3/src/thread/windows/SDL_systhread.c", + "SDL2-2.0.3/src/thread/windows/SDL_systls.c", + "SDL2-2.0.3/src/timer/SDL_timer.c", + "SDL2-2.0.3/src/timer/windows/SDL_systimer.c", + "SDL2-2.0.3/src/video/SDL_blit.c", + "SDL2-2.0.3/src/video/SDL_blit_0.c", + "SDL2-2.0.3/src/video/SDL_blit_1.c", + "SDL2-2.0.3/src/video/SDL_blit_A.c", + "SDL2-2.0.3/src/video/SDL_blit_auto.c", + "SDL2-2.0.3/src/video/SDL_blit_copy.c", + "SDL2-2.0.3/src/video/SDL_blit_N.c", + "SDL2-2.0.3/src/video/SDL_blit_slow.c", + "SDL2-2.0.3/src/video/SDL_bmp.c", + "SDL2-2.0.3/src/video/SDL_egl.c", + "SDL2-2.0.3/src/video/SDL_fillrect.c", + "SDL2-2.0.3/src/video/SDL_pixels.c", + "SDL2-2.0.3/src/video/SDL_rect.c", + "SDL2-2.0.3/src/video/SDL_RLEaccel.c", + "SDL2-2.0.3/src/video/SDL_shape.c", + "SDL2-2.0.3/src/video/SDL_stretch.c", + "SDL2-2.0.3/src/video/SDL_surface.c", + "SDL2-2.0.3/src/video/SDL_video.c", + "SDL2-2.0.3/src/video/dummy/SDL_nullevents.c", + "SDL2-2.0.3/src/video/dummy/SDL_nullframebuffer.c", + "SDL2-2.0.3/src/video/dummy/SDL_nullvideo.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsclipboard.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsevents.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsframebuffer.c", + "SDL2-2.0.3/src/video/windows/SDL_windowskeyboard.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsmessagebox.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsmodes.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsmouse.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsopengl.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsopengles.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsshape.c", + "SDL2-2.0.3/src/video/windows/SDL_windowsvideo.c", + "SDL2-2.0.3/src/video/windows/SDL_windowswindow.c", + ]) +else: + libsdl = libsdlenv.Command("SDL2-2.0.3/build/.libs/libSDL2.a", "SDL2-2.0.3/configure", "cd external/SDL2-2.0.3 && ./configure && make") + if sys.platform.startswith("darwin"): + env.Append(LIBS=["iconv", "objc"]) + env.Append(FRAMEWORKS=["AppKit", "AudioUnit", "Carbon", "CoreAudio", "CoreFoundation", "CoreGraphics", "ForceFeedback", "IOKit"]) + else: + env.Append(LIBS="pthread") + +env.Append(CPPPATH=["external/SDL2-2.0.3/include"]) + +Return(["libsdl"]) diff --git a/external/SConscript-libsodium b/external/SConscript-libsodium new file mode 100644 index 0000000000..c87d5eba5e --- /dev/null +++ b/external/SConscript-libsodium @@ -0,0 +1,201 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libsodium = None + +sodiumenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + sodiumenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("libsodium-1.0.5", ignore_errors=True) +elif not os.path.exists("libsodium-1.0.5/configure"): + tarfile.open("libsodium-1.0.5.tar.gz").extractall(".") + if sys.platform == "win32": + version = open("libsodium-1.0.5/src/libsodium/include/sodium/version.h.in").read() + version = (version + .replace("@VERSION@", "1.0.5") + .replace("@SODIUM_LIBRARY_VERSION_MAJOR@", "7") + .replace("@SODIUM_LIBRARY_VERSION_MINOR@", "6") + ) + open("libsodium-1.0.5/src/libsodium/include/sodium/version.h", "w").write(version) + +if sys.platform == "win32": + sodiumenv.Append(CPPPATH=["libsodium-1.0.5/src/libsodium/include"]) + sodiumenv.Append(CPPPATH=["libsodium-1.0.5/src/libsodium/include/sodium"]) + sodiumenv.Append(CPPFLAGS=["-DSODIUM_STATIC"]) + sodiumenv.Append(CPPFLAGS=["-Dinline="]) + libsodium = sodiumenv.Library("libsodium-1.0.5/libsodium.lib", [ + "libsodium-1.0.5/src/libsodium/crypto_aead/aes256gcm/aesni/aead_aes256gcm_aesni.c", + "libsodium-1.0.5/src/libsodium/crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/crypto_auth.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha256/auth_hmacsha256_api.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha256/cp/hmac_hmacsha256.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha256/cp/verify_hmacsha256.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512/auth_hmacsha512_api.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512/cp/hmac_hmacsha512.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512/cp/verify_hmacsha512.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512256/auth_hmacsha512256_api.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c", + "libsodium-1.0.5/src/libsodium/crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c", + "libsodium-1.0.5/src/libsodium/crypto_box/crypto_box.c", + "libsodium-1.0.5/src/libsodium/crypto_box/crypto_box_easy.c", + "libsodium-1.0.5/src/libsodium/crypto_box/crypto_box_seal.c", + "libsodium-1.0.5/src/libsodium/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c", + "libsodium-1.0.5/src/libsodium/crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_core/hsalsa20/core_hsalsa20_api.c", + "libsodium-1.0.5/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa20/core_salsa20_api.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa20/ref/core_salsa20.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa2012/core_salsa2012_api.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa2012/ref/core_salsa2012.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa208/core_salsa208_api.c", + "libsodium-1.0.5/src/libsodium/crypto_core/salsa208/ref/core_salsa208.c", + "libsodium-1.0.5/src/libsodium/crypto_generichash/blake2/generichash_blake2_api.c", + "libsodium-1.0.5/src/libsodium/crypto_generichash/blake2/ref/blake2b-ref.c", + "libsodium-1.0.5/src/libsodium/crypto_generichash/blake2/ref/generichash_blake2b.c", + "libsodium-1.0.5/src/libsodium/crypto_generichash/crypto_generichash.c", + "libsodium-1.0.5/src/libsodium/crypto_hash/crypto_hash.c", + "libsodium-1.0.5/src/libsodium/crypto_hash/sha256/cp/hash_sha256.c", + "libsodium-1.0.5/src/libsodium/crypto_hash/sha256/hash_sha256_api.c", + "libsodium-1.0.5/src/libsodium/crypto_hash/sha512/cp/hash_sha512.c", + "libsodium-1.0.5/src/libsodium/crypto_hash/sha512/hash_sha512_api.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/crypto_onetimeauth.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/poly1305/donna/auth_poly1305_donna.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/poly1305/donna/verify_poly1305_donna.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305_api.c", + "libsodium-1.0.5/src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305_try.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c", + "libsodium-1.0.5/src/libsodium/crypto_pwhash/scryptsalsa208sha256/sse/pwhash_scryptsalsa208sha256_sse.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/crypto_scalarmult.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/donna_c64/base_curve25519_donna_c64.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/donna_c64/smult_curve25519_donna_c64.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/base_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_0_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_1_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_add_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_copy_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_cswap_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_frombytes_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_invert_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_mul121666_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_mul_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_sq_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_sub_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/fe_tobytes_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/ref10/scalarmult_curve25519_ref10.c", + "libsodium-1.0.5/src/libsodium/crypto_scalarmult/curve25519/scalarmult_curve25519_api.c", + "libsodium-1.0.5/src/libsodium/crypto_secretbox/crypto_secretbox.c", + "libsodium-1.0.5/src/libsodium/crypto_secretbox/crypto_secretbox_easy.c", + "libsodium-1.0.5/src/libsodium/crypto_secretbox/xsalsa20poly1305/ref/box_xsalsa20poly1305.c", + "libsodium-1.0.5/src/libsodium/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c", + "libsodium-1.0.5/src/libsodium/crypto_shorthash/crypto_shorthash.c", + "libsodium-1.0.5/src/libsodium/crypto_shorthash/siphash24/ref/shorthash_siphash24.c", + "libsodium-1.0.5/src/libsodium/crypto_shorthash/siphash24/shorthash_siphash24_api.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/crypto_sign.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_0.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_1.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_add.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_cmov.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_copy.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_frombytes.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_invert.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_isnegative.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_isnonzero.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_mul.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_neg.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_pow22523.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_sq.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_sq2.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_sub.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/fe_tobytes.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_add.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_double_scalarmult.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_frombytes.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_madd.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_msub.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p1p1_to_p2.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p1p1_to_p3.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p2_0.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p2_dbl.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p3_0.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p3_dbl.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p3_tobytes.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p3_to_cached.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_p3_to_p2.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_precomp_0.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_scalarmult_base.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_sub.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/ge_tobytes.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/keypair.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/open.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/sc_muladd.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/sc_reduce.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/ref10/sign.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/ed25519/sign_ed25519_api.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/edwards25519sha512batch/ref/fe25519_edwards25519sha512batch.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/edwards25519sha512batch/ref/ge25519_edwards25519sha512batch.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/edwards25519sha512batch/ref/sc25519_edwards25519sha512batch.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/edwards25519sha512batch/ref/sign_edwards25519sha512batch.c", + "libsodium-1.0.5/src/libsodium/crypto_sign/edwards25519sha512batch/sign_edwards25519sha512batch_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/afternm_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/beforenm_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/common_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/consts_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/int128_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/stream_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/portable/xor_afternm_aes128ctr.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/aes128ctr/stream_aes128ctr_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/chacha20/ref/stream_chacha20_ref.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/chacha20/stream_chacha20_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/crypto_stream.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa20/ref/stream_salsa20_ref.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa20/ref/xor_salsa20_ref.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa20/stream_salsa20_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa2012/ref/stream_salsa2012.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa2012/ref/xor_salsa2012.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa2012/stream_salsa2012_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa208/ref/stream_salsa208.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa208/ref/xor_salsa208.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/salsa208/stream_salsa208_api.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/xsalsa20/ref/stream_xsalsa20.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/xsalsa20/ref/xor_xsalsa20.c", + "libsodium-1.0.5/src/libsodium/crypto_stream/xsalsa20/stream_xsalsa20_api.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/16/ref/verify_16.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/16/verify_16_api.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/32/ref/verify_32.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/32/verify_32_api.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/64/ref/verify_64.c", + "libsodium-1.0.5/src/libsodium/crypto_verify/64/verify_64_api.c", + "libsodium-1.0.5/src/libsodium/randombytes/nativeclient/randombytes_nativeclient.c", + "libsodium-1.0.5/src/libsodium/randombytes/randombytes.c", + "libsodium-1.0.5/src/libsodium/randombytes/salsa20/randombytes_salsa20_random.c", + "libsodium-1.0.5/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c", + "libsodium-1.0.5/src/libsodium/sodium/core.c", + "libsodium-1.0.5/src/libsodium/sodium/runtime.c", + "libsodium-1.0.5/src/libsodium/sodium/utils.c", + "libsodium-1.0.5/src/libsodium/sodium/version.c", + ]) + env.Append(CPPPATH=["external/libsodium-1.0.5/src/libsodium/include"]) + env.Append(CPPFLAGS=["-DSODIUM_STATIC"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("sodium", "sodium.h", "C++"): + libsodium = sodiumenv.Command("libsodium-1.0.5/src/libsodium/.libs/libsodium.a", "libsodium-1.0.5/configure", "cd external/libsodium-1.0.5 && ./configure && make") + env.Append(CPPPATH=["external/libsodium-1.0.5/src/libsodium/include"]) + conf.Finish() + +Return(["libsodium"]) diff --git a/external/SConscript-libsqlite b/external/SConscript-libsqlite new file mode 100644 index 0000000000..71f6893a35 --- /dev/null +++ b/external/SConscript-libsqlite @@ -0,0 +1,23 @@ +import os +import shutil +import sys +import zipfile + +Import("env") + +sqliteenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + sqliteenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("sqlite-amalgamation-3080803", ignore_errors=True) +elif not os.path.exists("sqlite-amalgamation-3080803/sqlite3.c"): + zipfile.ZipFile("sqlite-amalgamation-3080803.zip").extractall(".") + +sqliteenv.Append(CPPFLAGS=["-DSQLITE_THREADSAFE=0"]) +libsqlite = sqliteenv.Library("sqlite-amalgamation-3080803/sqlite3.c") + +env.Append(CPPPATH=["external/sqlite-amalgamation-3080803"]) + +Return(["libsqlite"]) diff --git a/external/SConscript-libssl b/external/SConscript-libssl new file mode 100644 index 0000000000..94a47aa259 --- /dev/null +++ b/external/SConscript-libssl @@ -0,0 +1,29 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libssl = None + +sslenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + sslenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("libressl-2.2.4", ignore_errors=True) +elif not os.path.exists("libressl-2.2.4/configure"): + tarfile.open("libressl-2.2.4.tar.gz").extractall(".") + +if sys.platform == "win32": + pass +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("ressl", "crypto.h", "C++"): + libssl = sslenv.Command(["lib/libssl.a", "lib/libcrypto.a"], "libressl-2.2.4/configure", "cd external/libressl-2.2.4 && ./configure --prefix={} && make install".format(os.path.abspath("."))) + env.Append(CPPPATH=["external/include"]) + conf.Finish() + +Return(["libssl"]) diff --git a/external/SConscript-libutf8 b/external/SConscript-libutf8 new file mode 100644 index 0000000000..806b38a4dd --- /dev/null +++ b/external/SConscript-libutf8 @@ -0,0 +1,12 @@ +import os +import shutil +import zipfile + +Import("env") + +if GetOption("clean"): + shutil.rmtree("utf8", ignore_errors=True) +elif not os.path.exists("utf8/source/utf8.h"): + zipfile.ZipFile("utf8_v2_3_4.zip").extractall("utf8") + +env.Append(CPPPATH=["external/utf8/source"]) diff --git a/external/SConscript-libz b/external/SConscript-libz new file mode 100644 index 0000000000..708940279d --- /dev/null +++ b/external/SConscript-libz @@ -0,0 +1,41 @@ +import os +import shutil +import sys +import tarfile + +Import("env") + +libz = None + +libzenv = Environment() +if not env["RELEASE"]: + if sys.platform == "win32": + libzenv.Append(CFLAGS=["/MTd"]) + +if GetOption("clean"): + shutil.rmtree("zlib-1.2.8", ignore_errors=True) +elif not os.path.exists("zlib-1.2.8/configure"): + tarfile.open("zlib-1.2.8.tar.gz").extractall(".") + +if sys.platform == "win32": + libz = libzenv.Library("zlib-1.2.8/libz.lib", [ + "zlib-1.2.8/adler32.c", + "zlib-1.2.8/compress.c", + "zlib-1.2.8/crc32.c", + "zlib-1.2.8/deflate.c", + "zlib-1.2.8/inffast.c", + "zlib-1.2.8/inflate.c", + "zlib-1.2.8/inftrees.c", + "zlib-1.2.8/trees.c", + "zlib-1.2.8/uncompr.c", + "zlib-1.2.8/zutil.c", + ]) + env.Append(CPPPATH=["external/zlib-1.2.8"]) +else: + conf = Configure(env) + if not conf.CheckLibWithHeader("z", "zlib.h", "C++"): + libz = libzenv.Command("zlib-1.2.8/libz.a", "zlib-1.2.8/configure", "cd external/zlib-1.2.8 && ./configure --static && make") + env.Append(CPPPATH=["external/zlib-1.2.8"]) + conf.Finish() + +Return(["libz"]) diff --git a/external/SConscript-minijson b/external/SConscript-minijson new file mode 100644 index 0000000000..e906f1c854 --- /dev/null +++ b/external/SConscript-minijson @@ -0,0 +1,31 @@ +import os +import re +import shutil +import zipfile + +Import("env") + +if GetOption("clean"): + shutil.rmtree("minijson_writer-master", ignore_errors=True) +elif not os.path.exists("minijson_writer-master/minijson_writer.hpp"): + zipfile.ZipFile("minijson_writer-master.zip").extractall() + hpp = open("minijson_writer-master/minijson_writer.hpp").read() + + # Default Travis compilers are C++0x, and not quite officially C++11. + hpp, n = re.subn(r"(#define MJW_CPP11_SUPPORTED) .*", r"\1 1", hpp) + assert n == 1 + + # -Weffc++ requires these. + hpp, n = re.subn(r"(<0>\n{\n)", r"\1 virtual ~overload_rank() {}\n", hpp) + assert n == 1 + hpp, n = re.subn(r"(explicit writer\(.*?}\n)", r"""\1 + writer(const writer &rhs): m_array(rhs.m_array), m_status(rhs.m_status), m_stream(rhs.m_stream), m_configuration(rhs.m_configuration) {} + writer &operator=(const writer &rhs) = delete; + virtual ~writer() {}\n""", hpp, flags=re.DOTALL) + assert n == 1 + hpp, n = re.subn(r"(struct default_value_writer\n{\n)", r"\1 virtual ~default_value_writer() {}\n", hpp) + assert n == 1 + + open("minijson_writer-master/minijson_writer.hpp", "w").write(hpp) + +env.Append(CPPPATH=["external/minijson_writer-master"]) diff --git a/external/SConscript-naturaldocs b/external/SConscript-naturaldocs new file mode 100644 index 0000000000..26d4033cd6 --- /dev/null +++ b/external/SConscript-naturaldocs @@ -0,0 +1,8 @@ +import os +import shutil +import zipfile + +if GetOption("clean"): + shutil.rmtree("NaturalDocs", ignore_errors=True) +elif not os.path.exists("NaturalDocs/NaturalDocs"): + zipfile.ZipFile("NaturalDocs-1.52.zip").extractall("NaturalDocs") diff --git a/external/SConscript-pyparsing b/external/SConscript-pyparsing new file mode 100644 index 0000000000..5b53bf2cf1 --- /dev/null +++ b/external/SConscript-pyparsing @@ -0,0 +1,13 @@ +import os +import re +import shutil +import tarfile + +Import("env") + +if GetOption("clean"): + shutil.rmtree("pyparsing-2.0.3", ignore_errors=True) +elif not os.path.exists("pyparsing-2.0.3/pyparsing.py"): + tarfile.open("pyparsing-2.0.3.tar.gz").extractall(".") + +env["ENV"]["PYTHONPATH"] = (env["ENV"]["PYTHONPATH"] + os.pathsep if "PYTHONPATH" in env["ENV"] else "") + "external/pyparsing-2.0.3" diff --git a/external/SDL2-2.0.3.tar.gz b/external/SDL2-2.0.3.tar.gz new file mode 100644 index 0000000000..8be403d8a9 Binary files /dev/null and b/external/SDL2-2.0.3.tar.gz differ diff --git a/external/bzip2-1.0.6.tar.gz b/external/bzip2-1.0.6.tar.gz new file mode 100644 index 0000000000..e47e9034c4 Binary files /dev/null and b/external/bzip2-1.0.6.tar.gz differ diff --git a/external/curl-7.41.0.tar.gz b/external/curl-7.41.0.tar.gz new file mode 100644 index 0000000000..0aed6bdc86 Binary files /dev/null and b/external/curl-7.41.0.tar.gz differ diff --git a/external/hash-library.zip b/external/hash-library.zip new file mode 100644 index 0000000000..87fba1b4d9 Binary files /dev/null and b/external/hash-library.zip differ diff --git a/external/libressl-2.2.4.tar.gz b/external/libressl-2.2.4.tar.gz new file mode 100644 index 0000000000..1b7563e50e Binary files /dev/null and b/external/libressl-2.2.4.tar.gz differ diff --git a/external/libsodium-1.0.5.tar.gz b/external/libsodium-1.0.5.tar.gz new file mode 100644 index 0000000000..6dc653ceef Binary files /dev/null and b/external/libsodium-1.0.5.tar.gz differ diff --git a/external/minijson_writer-master.zip b/external/minijson_writer-master.zip new file mode 100644 index 0000000000..85b4b1058e Binary files /dev/null and b/external/minijson_writer-master.zip differ diff --git a/external/pcre2-10.10.tar.gz b/external/pcre2-10.10.tar.gz new file mode 100644 index 0000000000..5d38cb1167 Binary files /dev/null and b/external/pcre2-10.10.tar.gz differ diff --git a/external/pyparsing-2.0.3.tar.gz b/external/pyparsing-2.0.3.tar.gz new file mode 100644 index 0000000000..772ac63442 Binary files /dev/null and b/external/pyparsing-2.0.3.tar.gz differ diff --git a/external/sqlite-amalgamation-3080803.zip b/external/sqlite-amalgamation-3080803.zip new file mode 100644 index 0000000000..ad5505a7a7 Binary files /dev/null and b/external/sqlite-amalgamation-3080803.zip differ diff --git a/external/unzip11.zip b/external/unzip11.zip new file mode 100644 index 0000000000..fe55dc355c Binary files /dev/null and b/external/unzip11.zip differ diff --git a/external/xz-5.2.1.tar.gz b/external/xz-5.2.1.tar.gz new file mode 100644 index 0000000000..33788e95f4 Binary files /dev/null and b/external/xz-5.2.1.tar.gz differ diff --git a/external/zlib-1.2.8.tar.gz b/external/zlib-1.2.8.tar.gz new file mode 100644 index 0000000000..ed88885bd4 Binary files /dev/null and b/external/zlib-1.2.8.tar.gz differ diff --git a/gh-pages/.gitignore b/gh-pages/.gitignore new file mode 100644 index 0000000000..57510a2be4 --- /dev/null +++ b/gh-pages/.gitignore @@ -0,0 +1 @@ +_site/ diff --git a/gh-pages/CNAME b/gh-pages/CNAME new file mode 100644 index 0000000000..2582166470 --- /dev/null +++ b/gh-pages/CNAME @@ -0,0 +1 @@ +neon-lang.org diff --git a/gh-pages/_layouts/default.html b/gh-pages/_layouts/default.html new file mode 100644 index 0000000000..15f966137e --- /dev/null +++ b/gh-pages/_layouts/default.html @@ -0,0 +1,26 @@ + + + + {{ page.title }} + + + + +
+ + + {{ content }} +
+ + diff --git a/gh-pages/common-errors.md b/gh-pages/common-errors.md new file mode 100644 index 0000000000..27a2b442d4 --- /dev/null +++ b/gh-pages/common-errors.md @@ -0,0 +1,200 @@ +--- +layout: default +title: Common Errors +--- + +The following types of programming errors have been identified as frequently occurring among beginning programmers on Stack Overflow: + +* [Floating point errors due to binary floating point](#floating_point) +* [Writing division expressions such as `5 / 2` and not expecting integer division](#integer_division) +* [Writing `if (x = 0)` when `if (x == 0)` is intended](#assignment_equals) +* [Null pointer exceptions](#null_pointer) +* [Unintended empty loop with `while (condition);`](#empty_loop) +* [Writing `if a == b or c` (in Python) to test whether `a` is equal to either `b` or `c`](#logical_alternative) +* [Catching all exceptions](#catch_all) +* [Accidentally shadowing outer scope variables with inner declaration](#shadow_variables) +* [Returning a reference to a local variable (C++)](#return_reference) +* [Partial reading of typed user input using Java `Scanner` or C `scanf('%c')`](#partial_input) +* [Writing `^` to mean exponentiation in C or Java](#exponentiation_xor) +* [Forgetting to use the return value of a function](#return_value) + + + +## Floating point errors due to binary floating point + +Most languages use [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point) binary floating point for non-integer calculations. +Although this is well-defined, it is often counterintuitive for beginners. Consider the following Python session: + +{% highlight python %} +>>> a = 0.1 +>>> b = a + a + a +>>> b +0.3 +>>> b == 0.3 +False +{% endhighlight %} + +This happens because `0.2` cannot be repesented exactly in binary floating point. + +To resolve this problem, Neon uses the [decimal64](https://en.wikipedia.org/wiki/Decimal64_floating-point_format) floating point type, which matches the base 10 that humans use to read and write numbers. + + + +## Writing division expressions such as `5 / 2` and not expecting integer division + +Consider the following C statements: + +{% highlight c %} +int a = 5; +int b = 2; +double c = a / b; +{% endhighlight %} + +Beginners rightly assume that `c` will be `2.5` as the result of the division. +However, the C language definition states that `/` will be *integer* division if both operands are integers. +So, the result in `c` is `2`. + +To resolve this problem, the only number type in Neon is decimal64 floating point (called `Number`). +In contexts such as array indexing where integers are expected, values are checked for the presence of a fractional part before trying to use them. + + + +## Writing `if (x = 0)` when `if (x == 0)` is intended + +In C and derived languages, `x = 0` is an *expression* with the result 0. +Some compilers raise a warning if an expression like `(x = 0)` is used in a conditional statement, as it is not likely to be what is intended. +However, this is not prohibited by the language. + +To resolve this problem, the assignment operator in Neon is `:=` and assignment cannot appear within an expression. + + + +## Null pointer exceptions + +In many common systems languages (eg. C, C++, Java, C#), a pointer may hold a "null" value which is a runtime error to dereference. Tony Hoare has called the introduction of the null reference in ALGOL his ["billion-dollar mistake"](https://en.wikipedia.org/wiki/Tony_Hoare). + +To resolve this problem, Neon introduces the idea of a "valid" pointer. A valid pointer is one that has been checked against `NIL` (the null reference) using a special form of the `IF` statement. The resulting valid pointer can be dereferenced without causing a null pointer exception. + + TYPE Node IS RECORD + next: POINTER TO Node + value: String + END RECORD + + VAR node: POINTER TO Node := NIL + + IF VALID node AS p THEN + print(p->value) + END IF + + + +## Unintended empty loop with `while (condition);` + +In C and derived languages, sometimes a loop or conditional is mistakenly written as: + +{% highlight c %} +while (x < 5); +{ + printf("%d\n", x); +} +{% endhighlight %} + +The trailing `;` on the `while` statement is in fact an empty loop body and the loop is an infinite loop. + +To resolve this problem, Neon requires an explicitly terminated block in every compound statement: + + VAR x: Number := 0 + + WHILE x < 5 DO + print(x.toString()) + INC x + END WHILE + + + +## Writing `if a == b or c` (in Python) to test whether `a` is equal to either `b` or `c` + +In Python, beginners find it natural to write code like: + +{% highlight python %} +if name == "Jack" or "Jill": + ... +{% endhighlight %} + +This is valid because the `"Jill"` is automatically treated as a boolean expression (with value `True`) and combined with the `name == "Jack"` condition using the `or` operator. + +To resolve this problem, values in Neon cannot be automatically converted from one type to another (in particular, not to Boolean). + + + +## Catching all exceptions + +Languages with complex exception hierarchies (eg. C++, Java, C#, Python) allow the program to catch *all* types of exceptions using a construct such as `catch (...)` (C++) or `except:` (Python). +This generally has the unintended effect of masking exceptions that may not be among those expected by the programmer. + +Neon does not have an exception hierarchy, and the exception handling always uses explicitly named exceptions (there is no way to catch *all* types of exceptions). + + + +## Accidentally shadowing outer scope variables with inner declaration + +Most programming languages allow names declared in a nested scope to *shadow* names declared in an enclosing scope (such as the global scope). For example, in C: + +{% highlight c %} +int x; + +void f() { + int x; + x = 5; +} +{% endhighlight %} + +This can lead to confusion due to unexpectedly using the wrong variable. + +In Neon, it is an error for a declaration to shadow an outer declaration. + + + +## Returning a reference to a local variable (C++) + +In C++, it is possible to return a reference (or pointer) to a local variable: + +{% highlight c++ %} +int &foo(int x) { + int a = x * x; + return a; +} +{% endhighlight %} + +This is *undefined behaviour* because the reference returns to memory that has been deallocated as soon as the function returns. + +This is resolved in Neon by not having references. + + + +## Partial reading of typed user input using Java `Scanner` or C `scanf('%c')` + +The Java `Scanner` class and C `scanf` functions treat their input as a stream. +However, when used with interactive terminal input, they do not return a value until an *entire* line has been typed at the console. +This causes confusion when the user types more than what is expected and the remainder of what the user typed is held in the input buffer until the next time input is requested. +At that time, the contents of the buffer are used without waiting for user input. + +This is resolved in Neon by only providing line oriented input for text in traditional tty mode, or single character input via curses. + + + +## Writing `^` to mean exponentiation in C or Java + +Sometimes beginners expect the `^` operator to mean exponentiation (eg. `dist = sqrt(a^2 + b^2)`). +However, `^` means bitwise XOR in many languages, which is not expected. + +In Neon, `^` means exponentiation. + + + +## Forgetting to use the return value of a function + +Many programming languages permit a function that returns a value to be called without actually using the return value. +This is a frequent source of bugs because the return value may indicate an error condition, which is then ignored. + +In Neon, it is an error to call a function that returns a value without using that value in some way. diff --git a/gh-pages/css/screen.css b/gh-pages/css/screen.css new file mode 100644 index 0000000000..19ef18b05a --- /dev/null +++ b/gh-pages/css/screen.css @@ -0,0 +1,30 @@ +body { + background-color: white; + font-family: Georgia, "Liberation Serif", serif; +} + +#page { + max-width: 780px; + text-align: left; + margin: 2.5em auto 2em; +} + +#page .title { + text-align: center; + font-size: 300%; + font-family: sans-serif; + text-shadow: 0 0 0.5em red; +} + +#page .title a { + text-decoration: none; +} + +#page .links { + text-align: center; +} + +pre { + padding: 1em 2em 1em 2em; + background-color: #eeeeee; +} diff --git a/gh-pages/docs.md b/gh-pages/docs.md new file mode 100644 index 0000000000..f567bc8597 --- /dev/null +++ b/gh-pages/docs.md @@ -0,0 +1,14 @@ +--- +layout: default +title: Neon Programming Language Documentation +--- + +The Neon documentation is split into several parts. + +* [**Manifesto**](manifesto.html). Why does Neon exist? What problems does it try to solve? +* [**Tutorial**](tutorial.html). This is an introduction to the Neon language. +* [**Standard Library**](/html/). This describes every module in the Neon Standard Library. +* [**Overview for Experts**](overview.html). This is a very quick overview of the language for experienced programmers. +* [**Language Reference**](reference.html). This is a detailed reference describing every language feature. +* [**Grammar Diagrams**](grammar.xhtml). Detail of the language grammar using "railroad diagrams". +* [**Samples**](/samples/). This describes a number of sample programs written in Neon. diff --git a/gh-pages/download.md b/gh-pages/download.md new file mode 100644 index 0000000000..69eae52766 --- /dev/null +++ b/gh-pages/download.md @@ -0,0 +1,7 @@ +--- +layout: default +title: Download +--- + +There are no pre-built packages available yet. +However, you can download a [zip file of the current source tree](https://github.com/ghewgill/neon-lang/archive/master.zip) from GitHub. diff --git a/gh-pages/grammar.xhtml b/gh-pages/grammar.xhtml new file mode 100644 index 0000000000..c54090eafc --- /dev/null +++ b/gh-pages/grammar.xhtml @@ -0,0 +1,4448 @@ + + + + + + + + + + + Program: + + + + + + + + + + Shebang + + + + + Statement + + + + + + + Program ::= Shebang? Statement* + + no referencesShebang: + + + + + + + + + #! + + + + restOfLine + + + + + + + Shebang ::= '#!' restOfLine + + referenced by: + + Program + + Statement: + + + + + + + + + + ImportDeclaration + + + + + TypeDeclaration + + + + + ConstantDeclaration + + + + + NativeConstantDeclaration + + + + + VariableDeclaration + + + + + NativeVariableDeclaration + + + + + LetDeclaration + + + + + FunctionDeclaration + + + + + ExternalFunctionDeclaration + + + + + NativeFunctionDeclaration + + + + + ExceptionDeclaration + + + + + ExportDeclaration + + + + + MainBlock + + + + + AssertStatement + + + + + AssignmentStatement + + + + + CaseStatement + + + + + ExitStatement + + + + + ExpressionStatement + + + + + ForStatement + + + + + ForeachStatement + + + + + IfStatement + + + + + IncrementStatement + + + + + LoopStatement + + + + + NextStatement + + + + + RaiseStatement + + + + + RepeatStatement + + + + + ReturnStatement + + + + + TryStatement + + + + + WhileStatement + + + + + + + Statement + ::= ImportDeclaration + | TypeDeclaration + | ConstantDeclaration + | NativeConstantDeclaration + | VariableDeclaration + | NativeVariableDeclaration + | LetDeclaration + | FunctionDeclaration + | ExternalFunctionDeclaration + | NativeFunctionDeclaration + | ExceptionDeclaration + | ExportDeclaration + | MainBlock + | AssertStatement + | AssignmentStatement + | CaseStatement + | ExitStatement + | ExpressionStatement + | ForStatement + | ForeachStatement + | IfStatement + | IncrementStatement + | LoopStatement + | NextStatement + | RaiseStatement + | RepeatStatement + | ReturnStatement + | TryStatement + | WhileStatement + + referenced by: + + CaseStatement + ForStatement + ForeachStatement + FunctionDeclaration + IfStatement + LoopStatement + MainBlock + Program + RepeatStatement + TryStatement + WhileStatement + + ImportDeclaration: + + + + + + + + + IMPORT + + + + Identifier + + + + . + + + + Identifier + + + + ALIAS + + + + Identifier + + + + + StringLiteral + + + + ALIAS + + + + Identifier + + + + + + + ImportDeclaration + ::= 'IMPORT' ( Identifier ( '.' Identifier )? ( 'ALIAS' Identifier )? | StringLiteral 'ALIAS' Identifier ) + + referenced by: + + Statement + + TypeDeclaration: + + + + + + + + + TYPE + + + + Identifier + + + + IS + + + + Type + + + + + + + TypeDeclaration + ::= 'TYPE' Identifier 'IS' Type + + referenced by: + + Statement + + Type: + + + + + + + + + + ParameterisedType + + + + + RecordType + + + + + EnumType + + + + + PointerType + + + + + FunctionPointerType + + + + + Identifier + + + + . + + + + Identifier + + + + + + + Type ::= ParameterisedType + | RecordType + | EnumType + | PointerType + | FunctionPointerType + | Identifier ( '.' Identifier )? + + referenced by: + + Atom + ConstantDeclaration + FunctionParameter + FunctionParameterList + LetDeclaration + NativeConstantDeclaration + NativeVariableDeclaration + ParameterisedType + PointerType + RecordType + TypeDeclaration + VariableDeclaration + + ParameterisedType: + + + + + + + + + Array + + + Dictionary + + + < + + + + Type + + + + > + + + + + + ParameterisedType + ::= ( 'Array' | 'Dictionary' ) '<' Type '>' + + referenced by: + + Type + + RecordType: + + + + + + + + + RECORD + + + PRIVATE + + + + Identifier + + + + : + + + + Type + + + + END + + + RECORD + + + + + + RecordType + ::= 'RECORD' ( 'PRIVATE'? Identifier ':' Type )* 'END' 'RECORD' + + referenced by: + + Type + + EnumType: + + + + + + + + + ENUM + + + + Identifier + + + + END + + + ENUM + + + + + + EnumType ::= 'ENUM' Identifier* 'END' 'ENUM' + + referenced by: + + Type + + PointerType: + + + + + + + + + POINTER + + + TO + + + + Type + + + + + + + PointerType + ::= 'POINTER' 'TO' Type + + referenced by: + + Type + + FunctionPointerType: + + + + + + + + + FUNCTION + + + + FunctionParameterList + + + + + + + FunctionPointerType + ::= 'FUNCTION' FunctionParameterList + + referenced by: + + Type + + ConstantDeclaration: + + + + + + + + + CONSTANT + + + + Identifier + + + + : + + + + Type + + + + := + + + + Expression + + + + + + + ConstantDeclaration + ::= 'CONSTANT' Identifier ':' Type ':=' Expression + + referenced by: + + Statement + + NativeConstantDeclaration: + + + + + + + + + DECLARE + + + NATIVE + + + CONSTANT + + + + Identifier + + + + : + + + + Type + + + + + + + NativeConstantDeclaration + ::= 'DECLARE' 'NATIVE' 'CONSTANT' Identifier ':' Type + + referenced by: + + Statement + + VariableDeclaration: + + + + + + + + + VAR + + + + Identifier + + + + , + + + : + + + + Type + + + + := + + + + Expression + + + + + + + VariableDeclaration + ::= 'VAR' Identifier ( ',' Identifier )* ':' Type ( ':=' Expression )? + + referenced by: + + Statement + + NativeVariableDeclaration: + + + + + + + + + DECLARE + + + NATIVE + + + VAR + + + + Identifier + + + + : + + + + Type + + + + + + + NativeVariableDeclaration + ::= 'DECLARE' 'NATIVE' 'VAR' Identifier ':' Type + + referenced by: + + Statement + + LetDeclaration: + + + + + + + + + LET + + + + Identifier + + + + : + + + + Type + + + + := + + + + Expression + + + + + + + LetDeclaration + ::= 'LET' Identifier ':' Type ':=' Expression + + referenced by: + + Statement + + FunctionDeclaration: + + + + + + + + + FUNCTION + + + + FunctionHeader + + + + + Statement + + + + END + + + FUNCTION + + + + + + FunctionDeclaration + ::= 'FUNCTION' FunctionHeader Statement* 'END' 'FUNCTION' + + referenced by: + + Statement + + ExternalFunctionDeclaration: + + + + + + + + + EXTERNAL + + + FUNCTION + + + + FunctionHeader + + + + + DictionaryLiteral + + + + END + + + FUNCTION + + + + + + ExternalFunctionDeclaration + ::= 'EXTERNAL' 'FUNCTION' FunctionHeader DictionaryLiteral 'END' 'FUNCTION' + + referenced by: + + Statement + + NativeFunctionDeclaration: + + + + + + + + + DECLARE + + + NATIVE + + + FUNCTION + + + + FunctionHeader + + + + + + + NativeFunctionDeclaration + ::= 'DECLARE' 'NATIVE' 'FUNCTION' FunctionHeader + + referenced by: + + Statement + + FunctionHeader: + + + + + + + + + + Identifier + + + + . + + + + Identifier + + + + + FunctionParameterList + + + + + + + FunctionHeader + ::= Identifier ( '.' Identifier )? FunctionParameterList + + referenced by: + + ExternalFunctionDeclaration + FunctionDeclaration + NativeFunctionDeclaration + + FunctionParameterList: + + + + + + + + + ( + + + + FunctionParameter + + + + , + + + ) + + + : + + + + Type + + + + + + + FunctionParameterList + ::= '(' ( FunctionParameter ( ',' FunctionParameter )* )? ')' ( ':' Type )? + + referenced by: + + FunctionHeader + FunctionPointerType + + FunctionParameter: + + + + + + + + + IN + + + OUT + + + INOUT + + + + Identifier + + + + , + + + : + + + + Type + + + + DEFAULT + + + + Expression + + + + + + + FunctionParameter + ::= ( 'IN' | 'OUT' | 'INOUT' )? Identifier ( ',' Identifier )* ':' Type ( 'DEFAULT' Expression )? + + referenced by: + + FunctionParameterList + + ExceptionDeclaration: + + + + + + + + + DECLARE + + + EXCEPTION + + + + Identifier + + + + + + + ExceptionDeclaration + ::= 'DECLARE' 'EXCEPTION' Identifier + + referenced by: + + Statement + + ExportDeclaration: + + + + + + + + + EXPORT + + + + Identifier + + + + + + + ExportDeclaration + ::= 'EXPORT' Identifier + + referenced by: + + Statement + + MainBlock: + + + + + + + + + BEGIN + + + MAIN + + + + Statement + + + + END + + + MAIN + + + + + + MainBlock + ::= 'BEGIN' 'MAIN' Statement* 'END' 'MAIN' + + referenced by: + + Statement + + AssertStatement: + + + + + + + + + ASSERT + + + + Expression + + + + , + + + + + + AssertStatement + ::= 'ASSERT' Expression ( ',' Expression )* + + referenced by: + + Statement + + AssignmentStatement: + + + + + + + + + + CompoundExpression + + + + _ + + + := + + + + Expression + + + + + + + AssignmentStatement + ::= ( CompoundExpression | '_' ) ':=' Expression + + referenced by: + + Statement + + CaseStatement: + + + + + + + + + CASE + + + + Expression + + + + WHEN + + + + WhenCondition + + + + , + + + DO + + + + Statement + + + + WHEN + + + OTHERS + + + DO + + + + Statement + + + + END + + + CASE + + + + + + CaseStatement + ::= 'CASE' Expression ( 'WHEN' WhenCondition ( ',' WhenCondition )* 'DO' Statement* )* ( 'WHEN' 'OTHERS' 'DO' Statement* )? 'END' 'CASE' + + referenced by: + + Statement + + WhenCondition: + + + + + + + + + = + + + # + + + < + + + > + + + <= + + + >= + + + + Expression + + + + + Expression + + + + TO + + + + Expression + + + + + + + WhenCondition + ::= ( '=' | '#' | '<' | '>' | '<=' | '>=' ) Expression + | Expression ( 'TO' Expression )? + + referenced by: + + CaseStatement + + ExitStatement: + + + + + + + + + EXIT + + + FUNCTION + + + WHILE + + + FOR + + + FOREACH + + + LOOP + + + REPEAT + + + + + + ExitStatement + ::= 'EXIT' ( 'FUNCTION' | 'WHILE' | 'FOR' | 'FOREACH' | 'LOOP' | 'REPEAT' ) + + referenced by: + + Statement + + ExpressionStatement: + + + + + + + + + + Identifier + + + + + CompoundExpressionTail + + + + + + + ExpressionStatement + ::= Identifier CompoundExpressionTail CompoundExpressionTail* + + referenced by: + + Statement + + ForStatement: + + + + + + + + + FOR + + + + Identifier + + + + := + + + + Expression + + + + TO + + + + Expression + + + + STEP + + + + Expression + + + + DO + + + + Statement + + + + END + + + FOR + + + + + + ForStatement + ::= 'FOR' Identifier ':=' Expression 'TO' Expression ( 'STEP' Expression )? 'DO' Statement* 'END' 'FOR' + + referenced by: + + Statement + + ForeachStatement: + + + + + + + + + FOREACH + + + + Identifier + + + + OF + + + + Expression + + + + INDEX + + + + Identifier + + + + DO + + + + Statement + + + + END + + + FOREACH + + + + + + ForeachStatement + ::= 'FOREACH' Identifier 'OF' Expression ( 'INDEX' Identifier )? 'DO' Statement* 'END' 'FOREACH' + + referenced by: + + Statement + + IfStatement: + + + + + + + + + IF + + + + IfExpression + + + + THEN + + + + Statement + + + + ELSIF + + + ELSE + + + + Statement + + + + END + + + IF + + + + + + IfStatement + ::= 'IF' IfExpression 'THEN' Statement* ( 'ELSIF' IfExpression 'THEN' Statement* )* ( 'ELSE' Statement* )? 'END' 'IF' + + referenced by: + + Statement + + IncrementStatement: + + + + + + + + + INC + + + DEC + + + + Expression + + + + + + + IncrementStatement + ::= ( 'INC' | 'DEC' ) Expression + + referenced by: + + Statement + + IfExpression: + + + + + + + + + + Expression + + + + VALID + + + + Identifier + + + + + Expression + + + + AS + + + + Identifier + + + + , + + + + + + IfExpression + ::= Expression + | 'VALID' ( Identifier | Expression 'AS' Identifier ) ( ',' ( Identifier | Expression 'AS' Identifier ) )* + + referenced by: + + IfStatement + + LoopStatement: + + + + + + + + + LOOP + + + + Statement + + + + END + + + LOOP + + + + + + LoopStatement + ::= 'LOOP' Statement* 'END' 'LOOP' + + referenced by: + + Statement + + NextStatement: + + + + + + + + + NEXT + + + WHILE + + + FOR + + + FOREACH + + + LOOP + + + REPEAT + + + + + + NextStatement + ::= 'NEXT' ( 'WHILE' | 'FOR' | 'FOREACH' | 'LOOP' | 'REPEAT' ) + + referenced by: + + Statement + + RaiseStatement: + + + + + + + + + RAISE + + + + Identifier + + + + . + + + + Identifier + + + + ( + + + + Expression + + + + , + + + ) + + + + + + RaiseStatement + ::= 'RAISE' Identifier ( '.' Identifier )? ( '(' Expression ( ',' Expression )* ')' )? + + referenced by: + + Statement + + RepeatStatement: + + + + + + + + + REPEAT + + + + Statement + + + + UNTIL + + + + Expression + + + + + + + RepeatStatement + ::= 'REPEAT' Statement* 'UNTIL' Expression + + referenced by: + + Statement + + ReturnStatement: + + + + + + + + + RETURN + + + + Expression + + + + + + + ReturnStatement + ::= 'RETURN' Expression + + referenced by: + + Statement + + TryStatement: + + + + + + + + + TRY + + + + Statement + + + + DO + + + + Identifier + + + + . + + + + Identifier + + + + EXCEPTION + + + END + + + TRY + + + + + + TryStatement + ::= 'TRY' Statement* ( 'EXCEPTION' Identifier ( '.' Identifier )? 'DO' Statement* )* 'END' 'TRY' + + referenced by: + + Statement + + WhileStatement: + + + + + + + + + WHILE + + + + Expression + + + + DO + + + + Statement + + + + END + + + WHILE + + + + + + WhileStatement + ::= 'WHILE' Expression 'DO' Statement* 'END' 'WHILE' + + referenced by: + + Statement + + Expression: + + + + + + + + + + ConditionalExpression + + + + + + + Expression + ::= ConditionalExpression + + referenced by: + + ArrayIndexExpression + ArrayLiteral + ArrayRangeLiteral + AssertStatement + AssignmentStatement + Atom + CaseStatement + ConditionalExpression + ConstantDeclaration + DictionaryLiteral + ForStatement + ForeachStatement + FunctionArgument + FunctionParameter + IfExpression + IncrementStatement + LetDeclaration + RaiseStatement + RepeatStatement + ReturnStatement + VariableDeclaration + WhenCondition + WhileStatement + + ConditionalExpression: + + + + + + + + + IF + + + + Expression + + + + THEN + + + + Expression + + + + ELSE + + + + Expression + + + + + DisjunctionExpression + + + + + + + ConditionalExpression + ::= 'IF' Expression 'THEN' Expression 'ELSE' Expression + | DisjunctionExpression + + referenced by: + + Expression + + DisjunctionExpression: + + + + + + + + + + ConjunctionExpression + + + + OR + + + + + + DisjunctionExpression + ::= ConjunctionExpression ( 'OR' ConjunctionExpression )* + + referenced by: + + ConditionalExpression + + ConjunctionExpression: + + + + + + + + + + MembershipExpression + + + + AND + + + + + + ConjunctionExpression + ::= MembershipExpression ( 'AND' MembershipExpression )* + + referenced by: + + DisjunctionExpression + + MembershipExpression: + + + + + + + + + + ComparisonExpression + + + + IN + + + NOT + + + IN + + + + ComparisonExpression + + + + + + + MembershipExpression + ::= ComparisonExpression ( ( 'IN' | 'NOT' 'IN' ) ComparisonExpression )? + + referenced by: + + ConjunctionExpression + + ComparisonExpression: + + + + + + + + + + AdditionExpression + + + + = + + + # + + + < + + + > + + + <= + + + >= + + + + + + ComparisonExpression + ::= AdditionExpression ( ( '=' | '#' | '<' | '>' | '<=' | '>=' ) AdditionExpression )* + + referenced by: + + MembershipExpression + + AdditionExpression: + + + + + + + + + + MultiplicationExpression + + + + + + + + - + + + & + + + + + + AdditionExpression + ::= MultiplicationExpression ( ( '+' | '-' | '&' ) MultiplicationExpression )* + + referenced by: + + ComparisonExpression + + MultiplicationExpression: + + + + + + + + + + ExponentiationExpression + + + + * + + + / + + + MOD + + + + + + MultiplicationExpression + ::= ExponentiationExpression ( ( '*' | '/' | 'MOD' ) ExponentiationExpression )* + + referenced by: + + AdditionExpression + + ExponentiationExpression: + + + + + + + + + + Atom + + + + ^ + + + + + + ExponentiationExpression + ::= Atom ( '^' Atom )* + + referenced by: + + MultiplicationExpression + + Atom: + + + + + + + + + ( + + + + Expression + + + + ) + + + + ArrayLiteral + + + + + ArrayRangeLiteral + + + + + DictionaryLiteral + + + + FALSE + + + TRUE + + + + Number + + + + + StringLiteral + + + + EMBED + + + + StringLiteral + + + + HEXBYTES + + + + StringLiteral + + + + + + + + - + + + NOT + + + + Atom + + + + NEW + + + + Type + + + + NIL + + + + CompoundExpression + + + + + + + Atom ::= '(' Expression ')' + | ArrayLiteral + | ArrayRangeLiteral + | DictionaryLiteral + | ( 'FALSE' | 'TRUE' ) + | Number + | StringLiteral + | 'EMBED' StringLiteral + | 'HEXBYTES' StringLiteral + | ( '+' | '-' | 'NOT' ) Atom + | 'NEW' Type + | 'NIL' + | CompoundExpression + + referenced by: + + Atom + ExponentiationExpression + + ArrayLiteral: + + + + + + + + + [ + + + + Expression + + + + , + + + , + + + ] + + + + + + ArrayLiteral + ::= '[' ( Expression ( ',' Expression )* ','? )? ']' + + referenced by: + + Atom + + ArrayRangeLiteral: + + + + + + + + + [ + + + + Expression + + + + TO + + + + Expression + + + + STEP + + + + Expression + + + + ] + + + + + + ArrayRangeLiteral + ::= '[' Expression 'TO' Expression ( 'STEP' Expression )? ']' + + referenced by: + + Atom + + DictionaryLiteral: + + + + + + + + + { + + + + StringLiteral + + + + : + + + + Expression + + + + , + + + } + + + + + + DictionaryLiteral + ::= '{' ( StringLiteral ':' Expression ','? )* '}' + + referenced by: + + Atom + ExternalFunctionDeclaration + + CompoundExpression: + + + + + + + + + + Identifier + + + + + CompoundExpressionTail + + + + + + + CompoundExpression + ::= Identifier CompoundExpressionTail* + + referenced by: + + AssignmentStatement + Atom + + CompoundExpressionTail: + + + + + + + + + [ + + + + ArrayIndexExpression + + + + , + + + ] + + + ( + + + + FunctionArgument + + + + , + + + ) + + + . + + + + Identifier + + + + -> + + + + Identifier + + + + + + + CompoundExpressionTail + ::= '[' ArrayIndexExpression ( ',' ArrayIndexExpression )* ']' + | '(' ( FunctionArgument ( ',' FunctionArgument )* )? ')' + | '.' Identifier + | '->' Identifier + + referenced by: + + CompoundExpression + ExpressionStatement + + FunctionArgument: + + + + + + + + + IN + + + INOUT + + + OUT + + + + Expression + + + + _ + + + + Identifier + + + + WITH + + + + Expression + + + + _ + + + + + + FunctionArgument + ::= ( 'IN' | 'INOUT' | 'OUT' )? ( ( Expression | '_' ) | Identifier 'WITH' ( Expression | '_' ) ) + + referenced by: + + CompoundExpressionTail + + ArrayIndexExpression: + + + + + + + + + + Expression + + + + + Expression + + + + TO + + + + Expression + + + + + + + ArrayIndexExpression + ::= Expression + | Expression 'TO' Expression + + referenced by: + + CompoundExpressionTail + + + + + +   + + ... generated by Railroad Diagram Generator + + + + + + + + + R + R + + + + + + + diff --git a/gh-pages/html/.gitignore b/gh-pages/html/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/gh-pages/html/.gitignore @@ -0,0 +1 @@ +* diff --git a/gh-pages/index.md b/gh-pages/index.md new file mode 100644 index 0000000000..be099f5040 --- /dev/null +++ b/gh-pages/index.md @@ -0,0 +1,50 @@ +--- +layout: default +title: Neon Programming Language +--- + +Neon is a high-level, statically typed, imperative programming language intended for teaching and learning the craft of programming. +Its design borrows features from many [popular languages](motivation.html), yet carefully avoids [common errors](common-errors.html) encountered by beginning programmers. + +Neon is not hip (you probably won't like it). +It is imperative and not functional; it is statically and not dynamically typed; it is verbose and not terse; it is single-threaded and not multiprocessing; it is not compiled to native code. +You will not find monads, templates, virtual functions, currying, lambdas, immutability, type inference, or probably even your favourite language feature. + +Neon does, however, have features that can make learning programming fun. +Neon has [standard libraries](html/index.html) for graphics, sound, text mode interfaces (curses), networking, and more. +The graphics and sound libraries recall the early days of the Apple ][ and Commodore 64. +The curses interface provides interactive text mode capability. +The ability to link to external libraries and to call external functions opens up a wide variety of possibilities. + +There are a variety of [samples](samples/index.html) which demonstrate the capabilities of Neon. + +## What does Neon look like? + +Neon programs start executing at the first statement in the source file. +For example, a program can be as short as this: + + print("Hello, World.") + +Another well-known example is the classic "FizzBuzz" program: + + % For each integer from 1 to 100, print "Fizz" if the number + % is divisible by 3, or "Buzz" if the number is divisible + % by 5, or "FizzBuzz" if the number is divisible by both. + % Otherwise, print the number itself. + + FOR i := 1 TO 100 DO + IF i MOD 15 = 0 THEN + print("FizzBuzz") + ELSIF i MOD 3 = 0 THEN + print("Fizz") + ELSIF i MOD 5 = 0 THEN + print("Buzz") + ELSE + print("\(i)") + END IF + END FOR + +Neon syntax is not sensitive to whitespace, and does not have statement separators. +Neon source code is case sensitive, but there are no requirements on the case of user-defined identifiers. + +Fork me on GitHub diff --git a/gh-pages/manifesto.md b/gh-pages/manifesto.md new file mode 100644 index 0000000000..bcd81a40d0 --- /dev/null +++ b/gh-pages/manifesto.md @@ -0,0 +1,46 @@ +--- +layout: default +title: Neon Manifesto +--- + +Programming languages today are full of features, and there is a trend toward brevity. +Brevity is highly expressive, for an expert who understands the notation. +However, for a new learner or a casual user, brevity promotes unreadable code. + +## Principles of Design + +* Prefer keywords over punctuation +* Good error messages +* Avoid implicit behaviour +* Explicit declarations +* Single meaning per keyword/operator +* Principle of least surprise +* Easy lookup + +### Prefer keywords over punctuation + +Punctuation is important, but it should be used sparingly. + +### Good error messages + +Error messages should explain what is wrong, with suggestions of ways to correct the problem. + +### Avoid implicit behaviour + +There will be no hidden, implicit, or automatic calls made to code that is not expressly visible in the source. + +### Explicit declarations + +Everything is declared, and identifiers from modules are always qualified. + +### Single meaning per keyword/operator + +To the greatest reasonable extent, keywords and operators only have one meaning. + +### Principle of least surprise + +This one is hard to quantify, but it's good. + +### Easy lookup + +By using keywords instead of punctuation, and by using explicit declarations, it is easy to look up unknown things in the documentation. diff --git a/gh-pages/motivation.md b/gh-pages/motivation.md new file mode 100644 index 0000000000..ddb7db00ac --- /dev/null +++ b/gh-pages/motivation.md @@ -0,0 +1,32 @@ +--- +layout: default +title: Motivation +--- + +The primary goal of Neon is to find out whether a useful programming language can avoid some of the common errors that beginners frequently encounter in other languages. + +After many years of participation on [Stack Overflow](http://stackoverflow.com), one starts to recognise the same kinds of problems that beginners encounter over and over. +Some of these errors are: + +* Floating point errors due to binary floating point +* Writing `if (x = 0)` when `if (x == 0)` is intended +* Null pointer exceptions +* Unintended empty loop with `while (condition);` +* Forgetting to use the return value of a function + +It is my opinion that these kinds of errors can be avoided by language design. +Neon is an experiment that attempts to show that this is the case. + +## Influences + +Many languages have influenced the design or specific features of Neon. Some are: + +- Pascal (`:=` for assignment, `VAR` keyword, nested functions) +- Modula-2 (upper case keywords) +- Ada (`IN`, `OUT`, `INOUT` parameter passing modes, arbitrary base notation) +- Python (standard library) +- C++ (`<...>` syntax for parameterised types) +- Prolog (`%` single line comment character) +- Scheme (`%|...|%` block comment, Scheme uses `#|...|#`) + +There is also a lot of *negative* influence, where the presence of a feature in a language has the potential to be misused (eg. `if (a = b)` in C). diff --git a/gh-pages/overview.md b/gh-pages/overview.md new file mode 100644 index 0000000000..5b64aa322e --- /dev/null +++ b/gh-pages/overview.md @@ -0,0 +1,241 @@ +--- +layout: default +title: Neon Overview +--- + +# Neon Overview + +The following is a brief description of Neon for experienced programmers. +There are plenty of examples, because experienced programmers know how to read code, and can pick up concepts more quickly by reading code than by reading a description of code. + +Neon is a statically typed imperative language, with roots in Pascal, Modula-2, Ada, and [others](motivation.html). +Program structure and modules are influenced by Python. + + % This sample program greets the user + % until an empty line is entered. + + LOOP + LET name: String := input("What is your name? ") + IF name = "" THEN + EXIT LOOP + END IF + print("Hello, \(name).") + END LOOP + +## General + +All identifiers are case sensitive. +Language defined keywords are all upper case. +Semicolons are not used. +Identifier scope is defined by program block structure. +Assignments have value semantics (deep copy). +Forward declarations are not required. +All variables must be explicitly initialised before use. + +## Types + +The scalar types are `Boolean` (`TRUE` or `FALSE`), `Number` (decimal floating point), `String` (Unicode text), `Bytes` (arbitrary blocks of bytes), and enumerations. +Aggregate types are `RECORD` (named fields), `Array` (arbitrary size vector), and `Dictionary` (map indexed by a `String` key). +Dynamic heap allocation is supported by a `POINTER` type. + + TYPE Colour IS ENUM + red + green + blue + END ENUM + + TYPE Person IS RECORD + name: String + eyes: Colour + END RECORD + + LET b: Boolean := TRUE + LET n: Number := 123.456 + LET s: String := "Hello world" + LET y: Bytes := HEXBYTES "00 01 02 03" + LET e: Colour := Colour.green + LET r: Person := Person("Alice", Colour.green) + LET a: Array := ["fork", "knife", "spoon"] + LET d: Dictionary := {"fork": 5, "knife": 6, "spoon": 1} + LET p: POINTER TO Person := NEW Person + +## Expressions + +There is a rich expression syntax including arithmetic, array slicing, conditionals, and string interpolation. + + LET x: Number := 5 + LET y: Number := (6 + x) / 2 + ASSERT y = 5.5 + + LET a: Array := ["fork", "knife", "spoon"] + ASSERT a[1 TO LAST] = ["knife", "spoon"] + + LET r: String := IF y < 5 THEN "small" ELSE "big" + ASSERT r = "big" + + LET t: String := "y is a \(r) value" + ASSERT t = "y is a big value" + +## Statements + +There are two variable declarations: `LET` (read-only value), and `VAR` (modifiable value). + + LET a: Number := 5 + + VAR b: Number + b := a + b := 6 + + print("\(a), \(b)") + +There are two conditional blocks: `CASE` (multiple branches), and `IF` (single test). + + FOR a := 0 TO 9 DO + VAR s: String + CASE a + WHEN < 2 DO + s := "less than two" + WHEN 2 DO + s := "two" + WHEN 3 TO 5 DO + s := "three to five" + WHEN 7, 9 DO + s := "seven or nine" + WHEN OTHERS DO + s := "something else" + END CASE + print("\(a) is \(s)") + END FOR + + IMPORT random + IF random.uint32() < 10 THEN + print("small") + END IF + +There are four kinds of loops: `FOR` (bounded iteration), `LOOP` (infinite loop), `REPEAT` (bottom-tested condition), and `WHILE` (top-tested condition). +The `EXIT` and `NEXT` statements branch out of the loop or to the next iteration, respectively. + + FOR i := 1 TO 10 DO + print("\(i)") + END FOR + + VAR a: Number := 1 + LOOP + print("\(a)") + IF a = 10 THEN + EXIT LOOP + END IF + INC a + END LOOP + + a := 1 + REPEAT + print("\(a)") + INC a + UNTIL a = 10 + + a := 1 + WHILE a <= 10 DO + print("\(a)") + INC a + END WHILE + +The exception handling statements are `TRY` (introduces a new handling scope), and `RAISE` to raise an exception. + + DECLARE EXCEPTION PrinterOutOfPaperException + + FUNCTION printFile(name: String) + % Save the trees, don't print anything. + RAISE PrinterOutOfPaperException + END FUNCTION + + TRY + printFile("hello.txt") + EXCEPTION PrinterOutOfPaperException DO + print("Sorry, out of paper.") + END TRY + +The `ASSERT` statement is used to check program invariants. +Execution stops with a diagnostic dump if the condition is not satisfied. + + FUNCTION setRatio(percent: Number) + ASSERT 0 <= percent <= 100 + % ... use percent value + END FUNCTION + +## Functions + +Functions may or may not return a value. +If a function returns a value, then the return value cannot be silently ignored by the caller. +Function parameters can be `IN` (default), `OUT` (passed back to caller), or `INOUT` (references caller value). + + IMPORT string + + FUNCTION func(name: String, OUT result: String, INOUT count: Number) + result := string.upper(name) + INC count + END FUNCTION + + VAR uname: String + VAR n: Number := 0 + + % The parameter mode (if not IN) must be explicitly indicated + % on the function call. + func("charlie", OUT uname, INOUT n) + + % The caller may choose to pass parameters in a different + % order using the WITH keyword. + func("charlie", INOUT count WITH n, OUT result WITH uname) + + ASSERT uname = "CHARLIE" + ASSERT n = 2 + +## Methods + +Records may have methods attached to them, to be called with the usual method syntax. + + TYPE Rectangle IS RECORD + width: Number + height: Number + END RECORD + + FUNCTION Rectangle.area(self: Rectangle): Number + RETURN self.width * self.height + END FUNCTION + + FUNCTION Rectangle.expand(INOUT self: Rectangle, edge: Number) + self.width := self.width + 2 * edge + self.height := self.height + 2 * edge + END FUNCTION + + LET r: Rectangle := Rectangle(4, 5) + ASSERT r.area() = 20 + r.expand(1) + ASSERT r.area() = 42 + +## Pointers + +Pointers can only point to records. +Pointers are declared with `POINTER TO` and allocated with `NEW`. + + TYPE Person IS RECORD + name: String + age: Number + END RECORD + + LET p: POINTER TO Person := NEW Person + p->name := "Alice" + p->age := 23 + +Pointers must be checked for validity (non-NIL) before they can be used using the `IF VALID` block. + + TYPE Person IS RECORD + name: String + age: Number + END RECORD + + FUNCTION incrementAge(p: POINTER TO Person) + IF VALID p THEN + INC p->age + END IF + END FUNCTION diff --git a/gh-pages/reference.md b/gh-pages/reference.md new file mode 100644 index 0000000000..b0bf22fe5a --- /dev/null +++ b/gh-pages/reference.md @@ -0,0 +1,1204 @@ +--- +layout: default +title: Neon Programming Language Reference +--- + +# Neon Programming Language Reference + +This document is intended as detailed reference material. +It is not intended as an introduction to the language. + +
    +
  1. Lexical Structure +
      +
    1. Comments
    2. +
    3. Keywords
    4. +
    5. Identifiers
    6. +
    7. Numbers
    8. +
    9. Strings
    10. +
  2. +
  3. Types +
      +
    1. Boolean
    2. +
    3. Number
    4. +
    5. String
    6. +
    7. Bytes
    8. +
    9. Enumeration
    10. +
    11. Record
    12. +
    13. Array
    14. +
    15. Dictionary
    16. +
    17. Pointer
    18. +
  4. +
  5. Expressions +
      +
    1. Literal Values
    2. +
    3. Boolean Operators
    4. +
    5. Numeric Operators
    6. +
    7. String Operators
    8. +
    9. Array Operator
    10. +
    11. Dictionary Operator
    12. +
    13. Pointer Operator
    14. +
    15. Operator Precedence
    16. +
    17. Array Subscripts
    18. +
    19. Expression Substitution
    20. +
  6. +
  7. Statements +
      +
    1. Assignment
    2. +
    3. Function Call
    4. +
    5. CASE
    6. +
    7. EXIT
    8. +
    9. FOR
    10. +
    11. IF
    12. +
    13. LET
    14. +
    15. LOOP
    16. +
    17. NEXT
    18. +
    19. RAISE
    20. +
    21. REPEAT
    22. +
    23. RETURN
    24. +
    25. TRY
    26. +
    27. WHILE
    28. +
  8. +
  9. Functions +
      +
    1. Parameter Modes
    2. +
    3. Default Parameter Value
    4. +
    5. Named Parameters
    6. +
    7. External Functions
    8. +
  10. +
  11. Modules +
      +
    1. Export
    2. +
    3. Import
    4. +
    5. Module Path
    6. +
  12. +
  13. Standard Library +
      +
    1. bigint
    2. +
    3. bitwise
    4. +
    5. cformat
    6. +
    7. complex
    8. +
    9. compress
    10. +
    11. curses
    12. +
    13. datetime
    14. +
    15. file
    16. +
    17. hash
    18. +
    19. http
    20. +
    21. io
    22. +
    23. json
    24. +
    25. math
    26. +
    27. net
    28. +
    29. os
    30. +
    31. random
    32. +
    33. regex
    34. +
    35. sqlite
    36. +
    37. string
    38. +
    39. struct
    40. +
    41. sys
    42. +
    43. time
    44. +
    45. variant
    46. +
    47. xml
    48. +
  14. +
  15. Grammar
  16. +
+ + + +## Lexical Structure + +Neon source code is encoded in UTF-8. +All keywords and identifiers are case sensitive. + + + +### Comments + +The comment lead-in character is `%`. +A single line comment is a `%` followed by arbitrary text to the end of the line. +A block comment is introduced with `%|` and ends with `|%`. +Block comments may span multiple lines. +Block comments may be nested. + +Example: + + % The following line prints some text. + print("Hello, World.") + + %| This comment spans multiple + lines of text until the comment + closing characters. |% + + + +### Keywords + +All words that consist of only uppercase letters are reserved for keywords. +The following keywords are defined by the language. + +| Keyword | Description | +| ------- | ----------- | +| `ALIAS` | used in `IMPORT` to optionally rename a module | +| `AND` | logical conjunction | +| `AS` | names a tested expression in `IF VALID` statement | +| `ASSERT` | assert that an expression is true, used for diagnostics | +| `Array` | generic array type | +| `BEGIN` | used in `BEGIN MAIN` to indicate a program entry point | +| `CASE` | multiple value matching from a range | +| `CONSTANT` | constant declaration | +| `DECLARE` | exception and forward function declaration | +| `DEFAULT` | default value for function parameter | +| `DEC` | decrement a `Number` variable | +| `Dictionary` | generic dictionary type | +| `DO` | used in `CASE`, `FOR`, and `WHILE` statements | +| `ELSE` | alternative condition in `IF` statement | +| `ELSIF` | alternative and test in `IF` statement | +| `EMBED` | include an external file directly into the compiled code | +| `ENUM` | enumeration type declaration | +| `END` | end of most kinds of blocks of code | +| `EXCEPTION` | exception declaration and handling | +| `EXIT` | early exit from loops | +| `EXPORT` | export identifier from module | +| `EXTERNAL` | external function declaration | +| `FALSE` | boolean constant | +| `FIRST` | indicates first value in array subscript | +| `FOR` | loop with a sequential control variable | +| `FOREACH` | loop over an array of values | +| `FUNCTION` | definition of subprogram | +| `HEXBYTES` | literal `Bytes` value | +| `IF` | conditional test and branch | +| `IN` | function parameter passing mode; aggregate membership test | +| `INC` | increment a `Number` variable | +| `INDEX` | used in `FOREACH` statement for counting values | +| `INOUT` | function parameter passing mode | +| `IMPORT` | access code in another module | +| `IS` | used in a `TYPE` declaration | +| `LAST` | indicates last value in array subscript | +| `LET` | assignment to read-only value | +| `LOOP` | generic loop | +| `MAIN` | used in `BEGIN MAIN` to indicate a program entry point | +| `MOD` | arithmetic modulus | +| `NATIVE` | declares a predefined function in the standard library | +| `NEXT` | early skip to next loop iteration | +| `NEW` | dynamic memory allocation | +| `NIL` | pointer value constant | +| `NOT` | logical negation | +| `OF` | used in `FOREACH` statement | +| `OR` | logical disjunction | +| `OUT` | function parameter passing mode | +| `OTHERS` | alternative condition in a `CASE` statement | +| `POINTER` | pointer type declaration | +| `PRIVATE` | private record field | +| `RAISE` | initiate exception search | +| `RECORD` | named aggregate type declaration | +| `REPEAT` | bottom-tested loop | +| `RETURN` | early exit and value return from function | +| `STEP` | used in `FOR` loop for increment value | +| `THEN` | used in `IF` statement | +| `TO` | used in `FOR` loop; part of pointer declaration | +| `TRUE` | boolean constant | +| `TRY` | start of exception-checked block | +| `TYPE` | define named type | +| `UNTIL` | used at end of `REPEAT` loop | +| `VAR` | variable declaration | +| `VALID` | used in `IF VALID` pointer test statement | +| `WHEN` | used in `CASE` statement | +| `WHILE` | top-tested loop | +| `WITH` | parameter specification for named parameters | + + + +### Identifiers + +An identifier is a letter followed by any number of letters, digits, or underscore. +Identifiers which consist of all uppercase letters are reserved for [keywords](#lexical-keywords). + + + +### Numbers + +Literal numbers are in base 10 by default. + +Numbers may be specified in a variety of bases: + +* Binary preceded by `0b` +* Octal preceded by `0o` +* Hexadecimal preceded by `0x` +* Any base from 2 to 36 preceded by `0#n#` where `n` is the base + +For base 10 numbers, they may contain a fractional portion following a decimal point `.`. +Additionally, they may have an exponent following `e` or `E`. + + + +### Strings + +Strings are sequences of Unicode characters surrounded by double quotes. +The only special character within a string is the backslash, used for character escapes. +The allowed character escapes are: + +| Escape | Replacement | Description | +| ------ | ----------- | ----------- | +| `\"` | " | double quote | +| `\\` | \ | backslash | +| `\b` | chr(8) | backspace | +| `\f` | chr(11) | form feed | +| `\n` | chr(10) | newline | +| `\r` | chr(13) | carriage return | +| `\t` | chr(9) | tab | +| `\uXXXX` | chr(XXXX) | Unicode character XXXX (where XXXX is a 4-digit hex number) | +| `\()` | expression | see expression substitution | + +Example: + + VAR s: String + + s := "Hello, World" + +Literal strings may need to contain backslashes (such as when used for regular expressions). +Instead of using normal double-quoted strings, there are two varieties of "raw strings". +The first can contain any character except `"`: + + CONSTANT s: String := @"This string contains backslash (\) characters" + +The second type of string uses arbitrary delimiters so that any literal string can be included in source. +The simplest form is: + + CONSTANT s: String := @@"This string contains backslashes (\) and "quotes"."@@ + +If there is a need to include the sequence `"@@` within the string, an arbitrary identifier may appear between the `@` at the start and end of the stiring. +For example: + + CONSTANT s: String := @raw@"A raw string example is @@"like this"@@."@raw@ + + + +## Types + +Neon is statically and strongly typed. +Every value has a definite type, and there are no automatic conversions between types. + + + +### Boolean + +Boolean values can take on two values, `FALSE` or `TRUE`. + +Example: + + LET b: Boolean := TRUE + + + +### Number + +Number values are 128-bit decimal floating point (specifically, [decimal128](https://en.wikipedia.org/wiki/Decimal128_floating-point_format)). +The valid magnitude range of numbers are (in addition to zero): + +* Minimum: 1.000000000000000000000000000000000e-6143 +* Maximum: 9.999999999999999999999999999999999e6144 + +Example: + + LET n: Number := 2.997924580e+8 + + + +### String + +String values are sequences of Unicode code points. + + + +### Bytes + +Bytes values are sequences of 8-bit bytes. +Values of this type are used for buffers when doing file and network I/O, for example. + + + +### Enumeration + +Enumeration values are one of a set of valid values defined in the `ENUM` definition. + +Example: + + TYPE Colour IS ENUM + red + green + blue + END ENUM + + LET e: Colour := Colour.green + + + +### Record + +Records are aggregate types that contain named elements with independent types. + +Example: + + TYPE Item IS RECORD + name: String + size: Number + END RECORD + + VAR r: Item + + r.name := "Widget" + r.size := 5 + +Records may have associated functions called methods, which can be called using a typical method call syntax. + +Example: + + TYPE Cart IS RECORD + apples: Number + oranges: Number + END RECORD + + FUNCTION Cart.totalFruit(self: Cart): Number + RETURN self.apples + self.oranges + END FUNCTION + + VAR c: Cart := Cart() + c.apples := 5 + c.oranges := 6 + print(str(c.totalFruit())) + +Record fields may be marked `PRIVATE`, which means that only code within associated methods may access that field. + +Example: + + TYPE Cart IS RECORD + apples: Number + oranges: Number + PRIVATE nuts: Number + END RECORD + + + +### Array + +Arrays are variable size sequences of values indexed by nonnegative integers. +Arrays are dynamically sized as needed. + +Example: + + VAR a: Array + + a[0] := "Hello" + a[1] := "World" + + + +### Dictionary + +Dictionaries are an associative map which pairs a unique `String` with a value of some type. + +Example: + + VAR d: Dictionary + + d["gold"] := 1 + d["silver"] := 2 + d["bronze"] := 3 + + + +### Pointers + +Pointers are addresses of dynamically allocated records. +The `NEW` keyword allocates a new record of a given type and returns a pointer to it. +Pointers may have the value `NIL` that does not point to any object. +To use (dereference) a pointer, it must first be checked for validity (not `NIL`) using the `IF VALID` construct. + +Example: + + TYPE Item IS RECORD + name: String + size: Number + END RECORD + + VAR item: POINTER TO Item + + item := NEW Item + IF VALID item AS p THEN + p->name := "Widget" + p->size := 5 + END IF + + + +## Expressions + +Expressions are combinations of operands and operators. +Operands are values in themselves, which may be expressions surrounded by `( )`. +Operators are logical, arithmetic, or string and the valid operators depend on the types of the operands. + + + +### Literal Values + +Literal values can be individual lexical elements such as identifiers, numbers, and strings. + +Literal arrays are sequences of comma-separated values surrounded by brackets `[ ]`. + +Example: + + LET a: Array := [1, 2, 3] + +Literal dictionaries are sequences of comma-separated name/value pairs surrounded by braces `{ }`. + +Example: + + LET d: Dictionary := { + "one": 1, + "two": 2, + "three": 3 + } + +For convenience, both literal arrays and dictionaries accept a trailing comma after the final element. + + + +### Boolean Operators + +The following operators take two boolean values. + +| Operator | Description | +| -------- | ----------- | +| `=` | equality | +| `#` | inequality | +| `AND` | logical conjunction | +| `OR` | logical disjunction | + + + +### Numeric Operators + +The following operators take two number values. + +| Operator | Description | +| -------- | ----------- | +| `+` | addition | +| `-` | subtraction | +| `*` | multiplication | +| `/` | division | +| `MOD` | modulo (remainder) | +| `^` | exponentiation | +| `=` | equality | +| `#` | inequality | +| `<` | less than | +| `>` | greater than | +| `<=` | less than or equal | +| `>=` | greater than or equal | + + + +### String Operators + +The following operators take two string values. + +| Operator | Description | +| -------- | ----------- | +| `&` | concatenation | +| `=` | equality | +| `#` | inequality | +| `<` | lexicographical less than | +| `>` | lexicographical greater than | +| `<=` | lexicographical less than or equal | +| `>=` | lexicographical greater than or equal | + + + +### Array Operators + +| Operator | Description | +| -------- | ----------- | +| `IN` | membership test (*O(n)* complexity) | + + + +### Dictionary Operators + +| Operator | Description | +| -------- | ----------- | +| `IN` | membership test (*O(log n)* complexity) | + + + +### Pointer Operator + +| Operator | Description | +| -------- | ----------- | +| `->` | pointer dereference | + + + +### Operator Precedence + +The operator precedence is as follows, highest to lowest: + +| Operator | Description | +| -------- | ----------- | +| `(` `)` | subexpression | +| `^` | exponentiation | +| `*` `/` `MOD` | multiplication, division, modulo | +| `+` `-` `&` | addition, subtraction, concatenation | +| `<` `=` `>` | comparison | +| `IN` | membership | +| `AND` | conjunction | +| `OR` | disjunction | +| `IF` | conditional | + + + +### Array Subscripts + +Array subscripts are normally integers greater than or equal to zero: + + LET a: Array := ["foo", "bar", "baz"] + print(a[0]) + print(a[2]) + +Two special values may be used, `FIRST` and `LAST`: + + LET a: Array := ["foo", "bar", "baz"] + print(a[FIRST]) + print(a[LAST]) + +`FIRST` always means the same as `0` and is provided for completeness. +`LAST` refers to the index of the last element of the array. + +Array slices are also possible using the `TO` keyword. +Both indexes are inclusive. + + LET a: Array := ["foo", "bar", "baz"] + LET b: Array := a[0 TO 1] + LET c: Array := a[LAST-1 TO LAST] + +In the above example, `b` contains `["foo", "bar"]` and `c` contains `["bar", "baz"]`. + + + +### Expression Substitution + +Literal strings may contain embedded expressions surrounded by the special escape `\( )`. +These expressions are evaluated at run time. +The type of the embedded expression must have a `.toString()` method which will be called automatically to convert the result to a string. + +Example: + + LET a: Array := ["one", "two", "three"] + FOR i := 0 TO 2 DO + print("i is \(i) and the array element is \(a[i])") + END FOR + +[TODO: formatting specifiers] + +## Declarations + +### Types + +User defined types are introduced with the `RECORD` keyword: + + VAR r: RECORD + size: Number + colour: String + END RECORD + +Types may be assigned names using the `TYPE` keyword: + + TYPE Widget IS RECORD + size: Number + colour: String + END RECORD + + VAR r: Widget + +### Constants + +Constants are defined using the `CONSTANT` keyword. + + CONSTANT Pi: Number := 3.141592653589793 + CONSTANT Sky: String := "blue" + +The value assigned to a constant must be able to be evaluated at compile time. +This may be an expression: + + CONSTANT Pi: Number := 3.141592653589793 + CONSTANT Pi2: Number := Pi ^ 2 + +### Variables + +Variables are declared using the `VAR` keyword: + + VAR count: Number + VAR colour: String + +Variables declared outside a function are *global* variables. +Variables declared inside a function are visible only from within that function. + +Read-only values (therefore not actually *variables*) are declared with the `LET` keyword: + + IMPORT os + + LET path: String := os.getenv("PATH") + + + +## Statements + + + +### Assignment + +Assignment evaluates the expression on the right hand side and assigns it to the storage location identified on the left hand side. + +Example: + + VAR a: Number + + a := 5 + + + +### Function Call + +A function call statement is the same as a function call in an expression, except that the function cannot return a value. +See Functions for complete information about function calls. + + + +### `CASE` + +The `CASE` statement selects one of a number of alternative code paths based on the value of an expression. + +Example: + + VAR x: Number := 0 + + CASE x + WHEN < 2 DO + print("less than two") + WHEN 2 DO + print("is two") + WHEN 3 TO 5 DO + print("three to five") + WHEN 7, 9 DO + print("seven or nine") + WHEN OTHERS DO + print("is something else") + END CASE + +The `CASE` statement expression may be of type `Number`, `String`, or an enumeration. +The possible kinds of `WHEN` clauses are: + +| Form | Meaning | +| ---- | ------- | +| `WHEN ` | equality match | +| `WHEN ` | relational operator comparison (one of `=`, `#`, `<`, `>`, `<=`, `>=`) | +| `WHEN TO ` | range check (both endpoints inclusive) | + +More than one of the above forms may be included in a `WHEN` clause, separated by commas. +The values of `WHEN` clauses must not overlap. +The optional `WHEN OTHERS` clause is executed when no other `WHEN` clauses match. + + + +### `EXIT` + +The `EXIT` statement has five different forms: + +| Form | Description | +| ---- | ----------- | +| `EXIT FOR` | stop iteration of the nearest enclosing `FOR` loop | +| `EXIT FOREACH` | stop iteration of the nearest enclosing `FOREACH` loop | +| `EXIT FUNCTION` | immediately return from a function (only for functions that do not return a value) | +| `EXIT LOOP` | stop iteration of the nearest enclosing `LOOP` loop | +| `EXIT REPEAT` | stop iteration of the nearest enclosing `REPEAT` loop | +| `EXIT WHILE` | stop iteration of the nearest enclosing `WHILE` loop | + + + +### `FOR` + +The `FOR` loop iterates a numeric variable over a range of values. +The loop control variable is implicitly a `Number` and must not be already declared outside the `FOR` statement. + +Example: + + FOR i := 1 TO 10 STEP 2 DO + print("i is \(i)") + END FOR + +The above example is equivalent to: + + VAR i: Number + + i := 1 + WHILE i <= 10 DO + print("i is \(i)") + i := i + 2 + END WHILE + +The exception is that in the `FOR` loop, the value of `i` cannot be modified. + +The `STEP` value is optional and defaults to 1. +It may be any number, including fractional values, except 0. +It must, however, be a compile time constant. + + + +### `IF` + +The `IF` statement tests a condition of type `Boolean` and executes one of two alternatives. + +Example: + + VAR x: Number := 0 + + IF x < 10 THEN + print("x is less than 10") + ELSE + print("not less than 10") + END IF + +The `ELSE` clause is optional. + +Additional alternatives may be introduced with the `ELSIF` clause: + + VAR x: Number := 0 + + IF x < 10 THEN + print("x is less than 10") + ELSIF x < 20 THEN + print("x is less than 20") + ELSE + print("not less than 20") + END IF + +The `IF VALID` form is used to test a pointer value to check whether it is `NIL`, and capture the pointer value in a new variable for use within the `IF VALID` block. + + TYPE Record IS RECORD + name: String + END RECORD + + VAR p: POINTER TO Record := NIL + + IF VALID p AS q THEN + print(q->name) + END IF + + + +### `LET` + +The `LET` statement introduces a new read-only variable and assigns a value (which can be an arbitrary expression, evaluated at run time). + +Example: + + FUNCTION five(): Number + RETURN 5 + END FUNCTION + + LET ten: Number := 2 * five() + + + +### `LOOP` + +The `LOOP` statement begins a loop with no specific exit condition. +There is normally an `EXIT LOOP` statement within the loop for a termination condition. + +Example: + + VAR i: Number := 0 + + LOOP + INC i + IF i >= 10 THEN + EXIT LOOP + END IF + print("i is \(i)") + END LOOP + + + +### `NEXT` + +The `NEXT` statement has four different forms: + +| Form | Description | +| ---- | ----------- | +| `NEXT FOR` | next iteration of the nearest enclosing `FOR` loop | +| `NEXT FOREACH` | next iteration of the nearest enclosing `FOREACH` loop | +| `NEXT LOOP` | next iteration of the nearest enclosing `LOOP` loop | +| `NEXT REPEAT` | next iteration of the nearest enclosing `REPEAT` loop | +| `NEXT WHILE` | next iteration of the nearest enclosing `WHILE` loop | + +When using `NEXT FOR`, the loop control variable is incremented (or decremented, according to the `STEP` value) before continuing to the next iteration. + +When using `NEXT REPEAT` or `NEXT WHILE`, the loop condition is tested before continuing to the next iteration. + + + +### `RAISE` + +The `RAISE` statement raises an exception. + +Example: + + DECLARE EXCEPTION InvalidWidgetSizeException + + VAR size: Number := 3 + IF size > 4 THEN + RAISE InvalidWidgetSizeException(size.toString()) + END IF + +The executor searches for an exception handler that can handle the given expression type, and execution resumes with the exception handler. +If no exception handler is found, the program terminates with a message and stack trace. + + + +### `REPEAT` + +The `REPEAT` statement begins a loop with a bottom-tested condition. +Execution always proceeds into the loop body at least once. + +Example: + + VAR x: Number := 0 + + REPEAT + print("x is \(x)") + INC x + UNTIL x = 10 + +The above loop will print the whole numbers 0 through 9. + + + +### `RETURN` + +The `RETURN` statement returns a value from a function. +The type of the expression in the `RETURN` statement must match the return type declared in the function header (which means that it is only valid to use `RETURN` for a function that actually returns a value). + +Example: + + FUNCTION square(x: Number): Number + RETURN x ^ 2 + END FUNCTION + + + +### `TRY` + +The `TRY` statement introduces a block that handles exceptions. +After entering a `TRY` block, any exception that happens within the block is checked against the `EXCEPTION` clauses. +If an exception matching a clause is raised, the corresponding exception handler starts running. + +Example: + + DECLARE EXCEPTION InvalidWidgetSizeException + + VAR size: Number := 5 + TRY + IF size > 4 THEN + RAISE InvalidWidgetSizeException(size.toString()) + END IF + EXCEPTION InvalidWidgetSizeException DO + print("Invalid size \(CURRENT_EXCEPTION.info)") + END TRY + + + +### `WHILE` + +The `WHILE` statement begins a loop with a top-tested condition. +The condition is tested before every loop iteration, including the first one. + +Example: + + VAR x: Number := 0 + + WHILE x < 10 DO + print("x is \(x)") + INC x + END WHILE + +The above loop will print the whole numbers 0 through 9. + + + +## Functions + +Functions are declared using the `FUNCTION` keyword: + + FUNCTION square(x: Number): Number + RETURN x ^ 2 + END FUNCTION + +The return type of a function (appearing after the `)`) is optional. +The `RETURN` statement is not permitted inside a function that does not return a value (use `EXIT FUNCTION` instead). + + + +### Parameter Modes + +Function parameters may be declared with a parameter mode: + +* `IN` - The function parameter is passed from the caller into the function, and may not be modified within the function. +* `INOUT` - A reference to the function argument is passed to the function, and the parameter may be modified within the function. +* `OUT` - No value is passed into the function, but any value assigned within the function is passed back to the caller. + +The default parameter mode is `IN`. +For `INOUT` and `OUT` parameters, the caller must supply an actual variable rather than the result of an expression. + +When calling a function with a parameter that has `INOUT` or `OUT` modes, the parameter mode must also be declared in the call. + + FUNCTION double(INOUT x: Number) + x := x * 2 + END FUNCTION + + VAR a: Number := 5 + double(INOUT a) + + + +### Default Parameter Value + +Function parameters may be declared with a default value. + +Example: + + FUNCTION add(INOUT x: Number, delta: Number DEFAULT 1) + x := x + delta + END FUNCTION + + VAR value: Number := 5 + add(INOUT value) + add(INOUT value, 2) + +When a parameter is not specified and it has a default value, the function call is executed as if the parameter were present and had the given value. +Default parameter values only apply to `IN` mode parameters. + + + +### Named Parameters + +When calling a function, function parameters may be named using the `WITH` keyword. + +Example: + + FUNCTION birthdayParty(name: String, balloons: Number, cake: String, clown: Boolean) + % ... + END FUNCTION + + birthdayParty(name WITH "Helen", balloons WITH 10, cake WITH "Chocolate", clown WITH TRUE) + +Parameters may be passed in order without using `WITH`, and then switch to using `WITH` for the remainder of the function call. +Each non-default parameter must be specified exactly once in the function call. + + + +## Modules + +Neon code can be divided into modules, which are separate source files. +A module named `example` would be found in a file called `example.neon`. + + + +Module source code must *export* identifiers before they can be used outside the module file. +This is done with the `EXPORT` declaration: + + EXPORT hello + + FUNCTION hello() + print("Hello world") + END FUNCTION + + + +In order to use an identifier exported by another module, the `IMPORT` declaration must be used. + +Example: + + IMPORT os + + print(os.getenv("PATH")) + +Individual identifiers may also be imported: + + IMPORT os.getenv + + print(getenv("PATH")) + + + +### Module Path + +When a module is imported, the compiler must be able to locate the code for the imported module. +The followed directories are searched in order: + +
    +
  1. Same directory as the importing source file
  2. +
  3. The current directory
  4. +
  5. The directories in the environment variable NEONPATH
  6. +
  7. The directories listed in the .neonpath file in the current directory
  8. +
+ + + +## Standard Library + + + +### bigint + +The `bigint` library provides functions for performing arithmetic with aribitrary precision integers. + + + +### bitwise + +The `bitwise` library provides functions for working with `Number` values treated as 32-bit binary integers. + + + +### cformat + +The `cformat` library provides formatting functions similar to the C `printf` family of functions. + + + +### complex + +The `complex` library provides functions for performing arithmetic on complex numbers (with a real and imaginary part). + + + +### compress + +The `compress` library provides functions for performing data compression using a variety of common algorithms (gzip, bzip2, lzma). + + + +### curses + +The `curses` library provides access to the "curses" terminal library for building textual user interfaces. + + + +### datetime + +The `datetime` library provides functions work working with calendar dates and times. + + + +### file + +The `file` library provides high level functions for filesystem access. + + + +### hash + +The `hash` library provides functions for calculating hashes using a number of different algorithms (CRC32, MD5, SHA1, SHA256, SHA3). + + + +### http + +The `http` library provides functions for interacting with remote servers using HTTP. + + + +### io + +The `io` library provides low-level functions for accessing files as streams of bytes. + + + +### json + +The `json` library provides functions for encoding and decoding data in JSON format. + + + +### math + +The `math` library provides trigonometric, trancendental, and other functions for real numbers. + + + +### net + +The `net` library provides functions for low-level socket communications. + + + +### os + +The `os` library provides low-level functions for working with the opering system environment. + + + +### random + +The `random` library provides functions for generating random numbers. + + + +### regex + +The `regex` library provides functions for performing text searches using regular expressions. + + + +### sqlite + +The `sqlite` library provides access to the SQLite database library. + + + +### string + +The `string` library provides functions for working with textual data in strings. + + + +### struct + +The `struct` library provides functions for packing and unpacking data into C-like structures. + + + +### sys + +The `sys` library provides low level functions for interacting with the Neon runtime environment. + + + +### time + +The `time` library provides functions for working with real (wall clock) time. + + + +### variant + +The `variant` library provides functions for working with data encapsulated in a generic `Variant` type. + + + +### xml + +The `xml` data provides functions for working with data in XML format. + + + +## Grammar + +[Neon grammar in "railroad diagrams"](grammar.xhtml). diff --git a/gh-pages/samples/.gitignore b/gh-pages/samples/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/gh-pages/samples/.gitignore @@ -0,0 +1 @@ +* diff --git a/gh-pages/tutorial.md b/gh-pages/tutorial.md new file mode 100644 index 0000000000..a446f30b65 --- /dev/null +++ b/gh-pages/tutorial.md @@ -0,0 +1,8 @@ +--- +layout: default +title: Neon Tutorial +--- + +# Neon Tutorial + +(to be written) diff --git a/lib/bigint.neon b/lib/bigint.neon new file mode 100644 index 0000000000..2deef77971 --- /dev/null +++ b/lib/bigint.neon @@ -0,0 +1,323 @@ +%| + | File: bigint + | + | Provides arbitrary precision integer support. + |% + +EXPORT BigInt +EXPORT Zero +EXPORT One +EXPORT Two +EXPORT make +EXPORT makeFromString +EXPORT abs +EXPORT add +EXPORT equals +EXPORT less +EXPORT mul +EXPORT neg +EXPORT sub + +IMPORT math + +CONSTANT Digits: Number := 6 +CONSTANT Base: Number := 1000000 % = 10^Digits, but avoid using ^ + +TYPE Comparison IS ENUM + less + equal + greater +END ENUM + +%| + | Type: BigInt + | + | An arbitrary precision integer type. This is an opaque type and users are not + | expected to poke around inside it. + |% +TYPE BigInt IS RECORD + sign: Number + digits: Array +END RECORD + +% TODO: Make these constants + +%| + | Constant: Zero + | + | The integer zero (0) as a BigInt. + |% +LET Zero: BigInt := BigInt(1, [0]) + +%| + | Constant: One + | + | The integer one (1) as a BigInt. + |% +LET One: BigInt := BigInt(1, [1]) + +%| + | Constant: Two + | + | The integer two (2) as a BigInt. + |% +LET Two: BigInt := BigInt(1, [2]) + +FUNCTION normalise(INOUT r: BigInt) + WHILE r.digits.size() >= 2 AND r.digits[LAST] = 0 DO + r.digits := r.digits[FIRST TO LAST-1] + END WHILE + IF r.digits = [0] THEN + r.sign := 1 + END IF +END FUNCTION + +%| + | Function: make + | + | Make a BigInt from a Number. + | + | Parameters: + | x - number to make into a BigInt + | + | Returns: + | The value of x as a BigInt. + | + | Description: + | This function discards any fractional part of the input value. + |% +FUNCTION make(x: Number): BigInt + VAR r: BigInt := BigInt() + r.sign := IF x < 0 THEN -1 ELSE 1 + VAR n: Number := math.floor(math.abs(x)) + r.digits[0] := 0 + VAR i: Number := 0 + WHILE n > 0 DO + r.digits[i] := n MOD Base + n := math.floor(n / Base) + INC i + END WHILE + RETURN r +END FUNCTION + +%| + | Function: makeFromString + | + | Make a BigInt from a string. + | + | Parameters: + | s - string to make into a BigInt + | + | Returns: + | The value of s as a BigInt. + | + | Description: + | This function expects that the string contains only decimal + | digits, optionally preceded by a minus sign (-). + |% +FUNCTION makeFromString(s: String): BigInt + VAR r: BigInt := BigInt() + VAR t: String := s + r.sign := 1 + IF t[0] = "-" THEN + r.sign := -1 + t := t[1 TO LAST] + END IF + VAR i: Number := 0 + VAR j: Number := t.length() - Digits + WHILE j >= 0 DO + r.digits[i] := num(t[j TO j+Digits-1]) + INC i + j := j - Digits + END WHILE + IF j > -Digits THEN + r.digits[i] := num(t[FIRST TO j+Digits-1]) + END IF + RETURN r +END FUNCTION + +%| + | Function: add + | + | Add two BigInt values and return the result. + |% +FUNCTION add(x, y: BigInt): BigInt + IF x.sign = y.sign THEN + VAR r: BigInt := BigInt() + r.sign := x.sign + VAR i: Number := 0 + VAR carry: Number := 0 + WHILE i < x.digits.size() OR i < y.digits.size() OR carry # 0 DO + r.digits[i] := (IF i < x.digits.size() THEN x.digits[i] ELSE 0) + (IF i < y.digits.size() THEN y.digits[i] ELSE 0) + carry + IF r.digits[i] >= Base THEN + r.digits[i] := r.digits[i] - Base + carry := 1 + ELSE + carry := 0 + END IF + INC i + END WHILE + RETURN r + ELSE + RETURN sub(x, neg(y)) + END IF +END FUNCTION + +%| + | Function: sub + | + | Subtract two BigInt values and return the result. + |% +FUNCTION sub(x, y: BigInt): BigInt + IF x.sign = y.sign THEN + IF NOT less(abs(x), abs(y)) THEN + VAR r: BigInt := BigInt() + r.sign := x.sign + VAR i: Number := 0 + VAR borrow: Number := 0 + WHILE i < x.digits.size() DO + r.digits[i] := (IF i < x.digits.size() THEN x.digits[i] ELSE 0) - (IF i < y.digits.size() THEN y.digits[i] ELSE 0) - borrow + IF r.digits[i] < 0 THEN + r.digits[i] := r.digits[i] + Base + borrow := 1 + ELSE + borrow := 0 + END IF + INC i + END WHILE + normalise(INOUT r) + RETURN r + ELSE + RETURN neg(sub(y, x)) + END IF + ELSE + RETURN add(x, neg(y)) + END IF +END FUNCTION + +%| + | Function: mul + | + | Multiply two BigInt values and return the result. + |% +FUNCTION mul(x, y: BigInt): BigInt + VAR r: BigInt := Zero + FOR i := 0 TO y.digits.size()-1 DO + VAR p: BigInt := Zero + VAR carry: Number := 0 + FOR j := 0 TO x.digits.size()-1 DO + LET z: Number := (IF j < x.digits.size() THEN x.digits[j] ELSE 0) * y.digits[i] + carry + p.digits[i+j] := z MOD Base + carry := math.floor(z / Base) + END FOR + IF carry # 0 THEN + p.digits[i+x.digits.size()] := carry + END IF + r := r.add(p) + END FOR + r.sign := x.sign * y.sign + normalise(INOUT r) + RETURN r +END FUNCTION + +%| + | Function: abs + | + | Return the absolute value of a BigInt value. + |% +FUNCTION abs(x: BigInt): BigInt + VAR r: BigInt := x + r.sign := 1 + RETURN r +END FUNCTION + +%| + | Function: neg + | + | Return the negation of a BigInt value. + |% +FUNCTION neg(x: BigInt): BigInt + VAR r: BigInt := x + r.sign := -r.sign + RETURN r +END FUNCTION + +%| + | Function: less + | + | Determine whether one BigInt value is less than another. Returns TRUE if x < y. + |% +FUNCTION less(x, y: BigInt): Boolean + IF x.sign # y.sign THEN + RETURN x.sign < y.sign + END IF + IF x.digits.size() # y.digits.size() THEN + RETURN x.sign*x.digits.size() < y.sign*y.digits.size() + END IF + FOR i := x.digits.size()-1 TO 0 STEP -1 DO + IF x.digits[i] # y.digits[i] THEN + RETURN x.sign*x.digits[i] < y.sign*y.digits[i] + END IF + END FOR + RETURN FALSE +END FUNCTION + +%| + | Function: equals + | + | Returns TRUE if x = y. + |% +FUNCTION equals(x, y: BigInt): Boolean + RETURN x.sign = y.sign AND x.digits = y.digits +END FUNCTION + +%| + | Function: BigInt.add + | + |% +FUNCTION BigInt.add(self, x: BigInt): BigInt + RETURN add(self, x) +END FUNCTION + +%| + | Function: BigInt.equals + | + |% +FUNCTION BigInt.equals(self, x: BigInt): Boolean + RETURN equals(self, x) +END FUNCTION + +%| + | Function: BigInt.mul + | + |% +FUNCTION BigInt.mul(self, x: BigInt): BigInt + RETURN mul(self, x) +END FUNCTION + +%| + | Function: BigInt.sub + | + |% +FUNCTION BigInt.sub(self, x: BigInt): BigInt + RETURN sub(self, x) +END FUNCTION + +%| + | Function: BigInt.toString + | + |% +FUNCTION BigInt.toString(self: BigInt): String + VAR s: String + FOR i := 0 TO self.digits.size()-1 DO + IF i < self.digits.size()-1 THEN + s := format(str(self.digits[i]), "0\(Digits)d") & s + ELSE + s := str(self.digits[i]) & s + END IF + END FOR + IF self.sign < 0 THEN + s := "-" & s + END IF + RETURN s +END FUNCTION diff --git a/lib/bitwise.cpp b/lib/bitwise.cpp index 086479a153..e0446a4428 100644 --- a/lib/bitwise.cpp +++ b/lib/bitwise.cpp @@ -1,96 +1,216 @@ +#include +#include + #include "number.h" +#include "rtl_exec.h" -namespace rtl { +template class traits {}; + +template <> class traits { +public: + static const unsigned int BITS = 32; + static const Number MIN; + static const Number MAX; + static Number to_number(int32_t x) { return number_from_sint32(x); } + static int32_t from_number(Number x) { return number_to_sint32(x); } +}; + +const Number traits::MIN = traits::to_number(std::numeric_limits::min()); +const Number traits::MAX = traits::to_number(std::numeric_limits::max()); + +template <> class traits { +public: + static const unsigned int BITS = 32; + static const Number MIN; + static const Number MAX; + static Number to_number(uint32_t x) { return number_from_uint32(x); } + static uint32_t from_number(Number x) { return number_to_uint32(x); } +}; + +const Number traits::MIN = traits::to_number(std::numeric_limits::min()); +const Number traits::MAX = traits::to_number(std::numeric_limits::max()); + +template <> class traits { +public: + static const unsigned int BITS = 64; + static const Number MIN; + static const Number MAX; + static Number to_number(int64_t x) { return number_from_sint64(x); } + static int64_t from_number(Number x) { return number_to_sint64(x); } +}; + +const Number traits::MIN = traits::to_number(std::numeric_limits::min()); +const Number traits::MAX = traits::to_number(std::numeric_limits::max()); + +template <> class traits { +public: + static const unsigned int BITS = 64; + static const Number MIN; + static const Number MAX; + static Number to_number(uint64_t x) { return number_from_uint64(x); } + static uint64_t from_number(Number x) { return number_to_uint64(x); } +}; + +const Number traits::MIN = traits::to_number(std::numeric_limits::min()); +const Number traits::MAX = traits::to_number(std::numeric_limits::max()); + +template void range_check(Number x) +{ + if (number_is_less(x, traits::MIN) || number_is_greater(x, traits::MAX)) { + throw RtlException(Exception_global$ValueRangeException, number_to_string(x)); + } + if (not number_is_integer(x)) { + throw RtlException(Exception_global$ValueRangeException, number_to_string(x)); + } +} -Number bitwise$and(Number x, Number y) +template Number bitwise_and(Number x, Number y) { - return number_from_uint32(number_to_uint32(x) & number_to_uint32(y)); + range_check(x); + range_check(y); + return traits::to_number(traits::from_number(x) & traits::from_number(y)); } -Number bitwise$extract(Number x, Number n, Number w) +template Number bitwise_extract(Number x, Number n, Number w) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { - return number_from_uint32(0); + range_check(x); + range_check(n); + range_check(w); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { + return traits::to_number(0); } - uint32_t v = number_to_uint32(w); - if (b + v > 32) { - v = 32 - b; + unsigned int v = number_to_uint32(w); + if (b + v > traits::BITS) { + v = traits::BITS - b; } - if (v == 32) { + if (v == traits::BITS) { return x; } - return number_from_uint32((number_to_uint32(x) >> b) & ((1 << v) - 1)); + return traits::to_number((traits::from_number(x) >> b) & (static_cast(1 << v) - 1)); } -bool bitwise$get(Number x, Number n) +template bool bitwise_get(Number x, Number n) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { + range_check(x); + range_check(n); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { return false; } - return (number_to_uint32(x) & (1 << b)) != 0; + return (traits::from_number(x) & static_cast(1 << b)) != 0; } -Number bitwise$not(Number x) +template Number bitwise_not(Number x) { - return number_from_uint32(~number_to_uint32(x)); + range_check(x); + return traits::to_number(~traits::from_number(x)); } -Number bitwise$or(Number x, Number y) +template Number bitwise_or(Number x, Number y) { - return number_from_uint32(number_to_uint32(x) | number_to_uint32(y)); + range_check(x); + range_check(y); + return traits::to_number(traits::from_number(x) | traits::from_number(y)); } -Number bitwise$replace(Number x, Number n, Number w, Number y) +template Number bitwise_replace(Number x, Number n, Number w, Number y) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { - return number_from_uint32(0); + range_check(x); + range_check(n); + range_check(w); + range_check(y); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { + return traits::to_number(0); } - uint32_t v = number_to_uint32(w); - if (b + v > 32) { - v = 32 - b; + unsigned int v = number_to_uint32(w); + if (b + v > traits::BITS) { + v = traits::BITS - b; } - uint32_t z = number_to_uint32(y); - uint32_t mask = v < 32 ? (1 << v) - 1 : ~0; - return number_from_uint32((number_to_uint32(x) & ~(mask << b)) | (z << b)); + T z = traits::from_number(y); + T mask = v < traits::BITS ? static_cast(1 << v) - 1 : ~0; + return traits::to_number((traits::from_number(x) & ~(mask << b)) | (z << b)); } -Number bitwise$set(Number x, Number n, bool v) +template Number bitwise_set(Number x, Number n, bool v) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { + range_check(x); + range_check(n); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { return x; } if (v) { - return number_from_uint32(number_to_uint32(x) | (1 << b)); + return traits::to_number(traits::from_number(x) | static_cast(1 << b)); } else { - return number_from_uint32(number_to_uint32(x) & ~(1 << b)); + return traits::to_number(traits::from_number(x) & ~static_cast(1 << b)); } } -Number bitwise$shift_left(Number x, Number n) +template Number bitwise_shift_left(Number x, Number n) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { - return number_from_uint32(0); + range_check(x); + range_check(n); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { + return traits::to_number(0); } - return number_from_uint32(number_to_uint32(x) << b); + return traits::to_number(traits::from_number(x) << b); } -Number bitwise$shift_right(Number x, Number n) +template Number bitwise_shift_right(Number x, Number n) { - uint32_t b = number_to_uint32(n); - if (b >= 32) { - return number_from_uint32(0); + range_check(x); + range_check(n); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { + return traits::to_number(0); } - return number_from_uint32(number_to_uint32(x) >> b); + return traits::to_number(traits::from_number(x) >> b); } -Number bitwise$xor(Number x, Number y) +template Number bitwise_shift_right_signed(Number x, Number n) { - return number_from_uint32(number_to_uint32(x) ^ number_to_uint32(y)); + range_check(x); + range_check(n); + unsigned int b = number_to_uint32(n); + if (b >= traits::BITS) { + return traits::to_number(0); + } + return number_from_sint32(number_to_sint32(x) >> b); } +template Number bitwise_xor(Number x, Number y) +{ + range_check(x); + range_check(y); + return traits::to_number(traits::from_number(x) ^ traits::from_number(y)); +} + +namespace rtl { + +Number bitwise$and32(Number x, Number y) { return bitwise_and(x, y); } +Number bitwise$and64(Number x, Number y) { return bitwise_and(x, y); } +Number bitwise$extract32(Number x, Number n, Number w) { return bitwise_extract(x, n, w); } +Number bitwise$extract64(Number x, Number n, Number w) { return bitwise_extract(x, n, w); } +bool bitwise$get32(Number x, Number n) { return bitwise_get(x, n); } +bool bitwise$get64(Number x, Number n) { return bitwise_get(x, n); } +Number bitwise$not32(Number x) { return bitwise_not(x); } +Number bitwise$not64(Number x) { return bitwise_not(x); } +Number bitwise$or32(Number x, Number y) { return bitwise_or(x, y); } +Number bitwise$or64(Number x, Number y) { return bitwise_or(x, y); } +Number bitwise$replace32(Number x, Number n, Number w, Number y) { return bitwise_replace(x, n, w, y); } +Number bitwise$replace64(Number x, Number n, Number w, Number y) { return bitwise_replace(x, n, w, y); } +Number bitwise$set32(Number x, Number n, bool v) { return bitwise_set(x, n, v); } +Number bitwise$set64(Number x, Number n, bool v) { return bitwise_set(x, n, v); } +Number bitwise$shiftLeft32(Number x, Number n) { return bitwise_shift_left(x, n); } +Number bitwise$shiftLeft64(Number x, Number n) { return bitwise_shift_left(x, n); } +Number bitwise$shiftRight32(Number x, Number n) { return bitwise_shift_right(x, n); } +Number bitwise$shiftRight64(Number x, Number n) { return bitwise_shift_right(x, n); } +Number bitwise$shiftRightSigned32(Number x, Number n) { return bitwise_shift_right_signed(x, n); } +Number bitwise$shiftRightSigned64(Number x, Number n) { return bitwise_shift_right_signed(x, n); } +Number bitwise$xor32(Number x, Number y) { return bitwise_xor(x, y); } +Number bitwise$xor64(Number x, Number y) { return bitwise_xor(x, y); } + } // namespace rtl diff --git a/lib/bitwise.neon b/lib/bitwise.neon new file mode 100644 index 0000000000..65ac7a1edf --- /dev/null +++ b/lib/bitwise.neon @@ -0,0 +1,407 @@ +%| + | File: bitwise + | + | Bitwise binary logical operations on 32-bit words. + |% + +%| + | Function: and32 + | + | Bitwise logical "and" of two 32-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 0 | + | > ---+---+---+ + | > 1 | 0 | 1 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION and32(x, y: Number): Number + +%| + | Function: and64 + | + | Bitwise logical "and" of two 64-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 0 | + | > ---+---+---+ + | > 1 | 0 | 1 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION and64(x, y: Number): Number + +%| + | Function: extract32 + | + | Extract a range of bits from a 32-bit word. + | + | Parameters: + | x - number to extract bits from + | n - bit position of the lowest order bit to extract + | w - width of bit range to extract + | + | Description: + | This function extracts a given range of bits. The bits + | in an word are numbered starting at 0 for the least + | significant bit. + | + | The function call + | + | > bitwise.extract(x, 5, 3) + | + | will extract the following bits from the word + | + | > 31 28 24 20 16 12 8 4 0 + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | + | > +-----+ + |% +DECLARE NATIVE FUNCTION extract32(x, n, w: Number): Number + +%| + | Function: extract64 + | + | Extract a range of bits from a 64-bit word. + | + | Parameters: + | x - number to extract bits from + | n - bit position of the lowest order bit to extract + | w - width of bit range to extract + | + | Description: + | This function extracts a given range of bits. The bits + | in an word are numbered starting at 0 for the least + | significant bit. + | + | The function call + | + | > bitwise.extract(x, 5, 3) + | + | will extract the following bits from the word + | + | > 31 28 24 20 16 12 8 4 0 + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | + | > +-----+ + |% +DECLARE NATIVE FUNCTION extract64(x, n, w: Number): Number + +%| + | Function: get32 + | + | Get a specific bit from a 32-bit word. + | + | Parameters: + | x - number to extract the bit from + | n - bit position of the bit to extract + | + | Description: + | This function returns a Boolean value indicting whether + | the indicated bit is set (TRUE) or not (FALSE). + |% +DECLARE NATIVE FUNCTION get32(x, n: Number): Boolean + +%| + | Function: get64 + | + | Get a specific bit from a 64-bit word. + | + | Parameters: + | x - number to extract the bit from + | n - bit position of the bit to extract + | + | Description: + | This function returns a Boolean value indicting whether + | the indicated bit is set (TRUE) or not (FALSE). + |% +DECLARE NATIVE FUNCTION get64(x, n: Number): Boolean + +%| + | Function: not32 + | + | Invert all bits in a 32-bit word. + |% +DECLARE NATIVE FUNCTION not32(x: Number): Number + +%| + | Function: not64 + | + | Invert all bits in a 64-bit word. + |% +DECLARE NATIVE FUNCTION not64(x: Number): Number + +%| + | Function: or32 + | + | Bitwise logical "or" of two 32-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 1 | + | > ---+---+---+ + | > 1 | 1 | 1 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION or32(x, y: Number): Number + +%| + | Function: or64 + | + | Bitwise logical "or" of two 64-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 1 | + | > ---+---+---+ + | > 1 | 1 | 1 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION or64(x, y: Number): Number + +%| + | Function: replace32 + | + | Replace a range of bits in a 32-bit word. + | + | Parameters: + | x - number to replace bits into + | n - bit position of the lowest order bit to replace + | w - width of bit range to replace + | y - new bits to replace + | + | Description: + | This function replaces a given range of bits with new bits. + | The bits in an word are numbered starting at 0 for the least + | significant bit. + | + | The function call + | + | > bitwise.replace(x, 5, 3, 7) + | + | will replace the following bits in the word + | + | > 31 28 24 20 16 12 8 4 0 + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | + | > +-----+ + | + | Each of the bits numbered 5, 6, and 7 would be set to 1 (from the + | input value 7). + |% +DECLARE NATIVE FUNCTION replace32(x, n, w, y: Number): Number + +%| + | Function: replace64 + | + | Replace a range of bits in a 64-bit word. + | + | Parameters: + | x - number to replace bits into + | n - bit position of the lowest order bit to replace + | w - width of bit range to replace + | y - new bits to replace + | + | Description: + | This function replaces a given range of bits with new bits. + | The bits in an word are numbered starting at 0 for the least + | significant bit. + | + | The function call + | + | > bitwise.replace(x, 5, 3, 7) + | + | will replace the following bits in the word + | + | > 31 28 24 20 16 12 8 4 0 + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | > +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | > | | + | > +-----+ + | + | Each of the bits numbered 5, 6, and 7 would be set to 1 (from the + | input value 7). + |% +DECLARE NATIVE FUNCTION replace64(x, n, w, y: Number): Number + +%| + | Function: set32 + | + | Set a specific bit in a 32-bit word. + | + | Parameters: + | x - number to set the bit + | n - bit position of the bit to set + | v - bit value to set, 0 (FALSE) or 1 (TRUE) + | + | Description: + | This function returns a new value with the given bit + | set according to the parameter v. + |% +DECLARE NATIVE FUNCTION set32(x, n: Number, v: Boolean): Number + +%| + | Function: set64 + | + | Set a specific bit in a 64-bit word. + | + | Parameters: + | x - number to set the bit + | n - bit position of the bit to set + | v - bit value to set, 0 (FALSE) or 1 (TRUE) + | + | Description: + | This function returns a new value with the given bit + | set according to the parameter v. + |% +DECLARE NATIVE FUNCTION set64(x, n: Number, v: Boolean): Number + +%| + | Function: shiftLeft32 + | + | Shift a 32-bit word left by a given number of bits. + | + | Parameters: + | x - word to shift + | n - number of bits to shift left + | + | Description: + | New bits shifted into the right hand side of the word are 0. + |% +DECLARE NATIVE FUNCTION shiftLeft32(x, n: Number): Number + +%| + | Function: shiftLeft64 + | + | Shift a 64-bit word left by a given number of bits. + | + | Parameters: + | x - word to shift + | n - number of bits to shift left + | + | Description: + | New bits shifted into the right hand side of the word are 0. + |% +DECLARE NATIVE FUNCTION shiftLeft64(x, n: Number): Number + +%| + | Function: shiftRight32 + | + | Shift a 32-bit word right by a given number of bits. + | + | Parameters: + | x - word to shift + | n - number of bits to shift right + | + | Description: + | New bits shifted into the left hand side of the word are 0. + |% +DECLARE NATIVE FUNCTION shiftRight32(x, n: Number): Number + +%| + | Function: shiftRight64 + | + | Shift a 64-bit word right by a given number of bits. + | + | Parameters: + | x - word to shift + | n - number of bits to shift right + | + | Description: + | New bits shifted into the left hand side of the word are 0. + |% +DECLARE NATIVE FUNCTION shiftRight64(x, n: Number): Number + +%| + | Function: shiftRightSigned32 + | + | Shift a 32-bit word right by a given number of bits, preserving the sign bit. + | + | Parameters: + | x - word to shift + | n - number of bits to shift right + | + | Description: + | New bits shifted into the left hand side of the word are the same as the original leftmost bit. + |% +DECLARE NATIVE FUNCTION shiftRightSigned32(x, n: Number): Number + +%| + | Function: shiftRightSigned64 + | + | Shift a 64-bit word right by a given number of bits, preserving the sign bit. + | + | Parameters: + | x - word to shift + | n - number of bits to shift right + | + | Description: + | New bits shifted into the left hand side of the word are the same as the original leftmost bit. + |% +DECLARE NATIVE FUNCTION shiftRightSigned64(x, n: Number): Number + +%| + | Function: xor32 + | + | Bitwise logical "exclusive-or" of two 32-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 1 | + | > ---+---+---+ + | > 1 | 1 | 0 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION xor32(x, y: Number): Number + +%| + | Function: xor64 + | + | Bitwise logical "exclusive-or" of two 64-bit words. + | + | For each bit in the inputs, the following truth table + | determines the output bit: + | + | > | 0 | 1 | <- x + | > ---+---+---+ + | > 0 | 0 | 1 | + | > ---+---+---+ + | > 1 | 1 | 0 | + | > ---+---+---+ + | > ^ + | > y + |% +DECLARE NATIVE FUNCTION xor64(x, y: Number): Number diff --git a/lib/cformat.neon b/lib/cformat.neon new file mode 100644 index 0000000000..ff30497b44 --- /dev/null +++ b/lib/cformat.neon @@ -0,0 +1,77 @@ +%| + | File: cformat + | + | Provide printf-like formatting similar to the C standard library. + |% + +EXPORT printf +EXPORT sprintf + +IMPORT variant.Variant + +%| + | Function: sprintf + | + | Given a format string and an array of parameters, format the + | string and return the result. + | + | Parameters: + | fmt - the format string + | value - the array of values to format + | + | Description: + | The available formatting codes are + | + | %d - decimal number + | %s - string + | + | Exceptions: + | - - if the wrong type is supplied for a parameter + | + | See Also: + | + |% +FUNCTION sprintf(fmt: String, value: Array): String + % TODO: This needs a lot more functionality, it is intended + % as a functional equivalent to the C sprintf function. + VAR r: String := "" + VAR a: Number := 0 + VAR i: Number := 0 + WHILE i < fmt.length() DO + IF fmt[i] = "%" THEN + INC i + CASE fmt[i] + WHEN "d" DO + r.append(value[a].getNumber().toString()) + INC a + WHEN "s" DO + r.append(value[a].getString()) + INC a + END CASE + ELSE + r.append(fmt[i]) + END IF + INC i + END WHILE + RETURN r +END FUNCTION + +%| + | Function: printf + | + | Given a format string and an array of parameters, format the + | string and print the result. + | + | Parameters: + | fmt - the format string + | value - the array of values to format + | + | Exceptions: + | - - if the wrong type is supplied for a parameter + | + | See Also: + | + |% +FUNCTION printf(fmt: String, value: Array) + print(sprintf(fmt, value)) +END FUNCTION diff --git a/lib/complex.neon b/lib/complex.neon new file mode 100644 index 0000000000..91e2b5ad35 --- /dev/null +++ b/lib/complex.neon @@ -0,0 +1,555 @@ +%| + | File: complex + | + | Mathematical functions for complex numbers. + |% + +EXPORT Complex +EXPORT make +EXPORT acos +EXPORT acosh +EXPORT abs +EXPORT add +EXPORT arg +EXPORT asin +EXPORT asinh +EXPORT atan +EXPORT atanh +EXPORT cos +EXPORT cosh +EXPORT div +EXPORT exp +EXPORT exp10 +EXPORT inv +EXPORT log +EXPORT log10 +EXPORT mul +EXPORT pow +EXPORT sin +EXPORT sinh +EXPORT sqrt +EXPORT sub +EXPORT square +EXPORT tan +EXPORT tanh +EXPORT Zero +EXPORT One +EXPORT i + +IMPORT math + +CONSTANT Max: Number := 9.999999999999999e384 + +%| + | Type: Complex + | + | Complex number type. + | + | Fields: + | re - real part + | im - imaginary part + |% +TYPE Complex IS RECORD + re: Number + im: Number +END RECORD + +LET LN10: Number := math.log(10) +CONSTANT Zero: Complex := Complex(0, 0) +CONSTANT One: Complex := Complex(1, 0) +CONSTANT i: Complex := Complex(0, 1) + +FUNCTION sign(x: Number): Number + IF x = 0 THEN + RETURN 0 + END IF + RETURN IF x > 0 THEN 1 ELSE -1 +END FUNCTION + +FUNCTION rsinh(x: Number): Number + RETURN (math.exp(x) - math.exp(-x)) / 2 +END FUNCTION + +FUNCTION rcosh(x: Number): Number + RETURN (math.exp(x) + math.exp(-x)) / 2 +END FUNCTION + +FUNCTION rtanh(x: Number): Number + RETURN (math.exp(2*x) - 1) / (math.exp(2*x) + 1) +END FUNCTION + +%| + | Function: make + | + | Make a complex number from real and imaginary parts. + | + | Parameters: + | re - real part + | im - imaginary part + |% +FUNCTION make(re: Number, im: Number): Complex + RETURN Complex(re, im) +END FUNCTION + +%| + | Function: acos + | + | Inverse cosine (arc cos). + | + | Reference: + | http://mathworld.wolfram.com/InverseCosine.html + |% +FUNCTION acos(a: Complex): Complex + LET s1: Complex := Complex(1 - a.re, -a.im).sqrt() + LET s2: Complex := Complex(1 + a.re, a.im).sqrt() + LET r1: Number := 2 * math.atan2(s1.re, s2.re) + VAR i1: Number := (s2.re*s1.im) - (s2.im*s1.re) + i1 := sign(i1) * math.log(math.abs(i1) + math.sqrt(i1*i1 + 1)) + RETURN Complex(r1, i1) +END FUNCTION + +%| + | Function: acosh + | + | Inverse hyperbolic cosine (arc cosh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicCosine.html + |% +FUNCTION acosh(a: Complex): Complex + LET s1: Complex := Complex(a.re - 1, a.im).sqrt() + LET s2: Complex := Complex(a.re + 1, a.im).sqrt() + VAR r1: Number := (s1.re*s2.re) + (s1.im*s2.im) + r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1*r1 + 1)) + LET i1: Number := 2 * math.atan2(s1.im, s2.re) + RETURN Complex(r1, i1) +END FUNCTION + +%| + | Function: abs + | + | Absolute value. + | + | Reference: + | http://mathworld.wolfram.com/AbsoluteValue.html + |% +FUNCTION abs(a: Complex): Number + RETURN math.sqrt(a.re^2 + a.im^2) +END FUNCTION + +%| + | Function: add + | + | Complex addition. + | + | Reference: + | http://mathworld.wolfram.com/ComplexAddition.html + |% +FUNCTION add(a, b: Complex): Complex + RETURN Complex(a.re + b.re, a.im + b.im) +END FUNCTION + +%| + | Function: arg + | + | Complex argument. + | + | Reference: + | http://mathworld.wolfram.com/ComplexArgument.html + |% +FUNCTION arg(a: Complex): Number + RETURN math.atan2(a.im, a.re) +END FUNCTION + +%| + | Function: asin + | + | Inverse sine (arc sin). + | + | Reference: + | http://mathworld.wolfram.com/InverseSine.html + |% +FUNCTION asin(a: Complex): Complex + LET s1: Complex := Complex(1 + a.re, a.im).sqrt() + LET s2: Complex := Complex(1 - a.re, -a.im).sqrt() + VAR r1: Number := (s1.re*s2.im) - (s2.re*s1.im) + r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1*r1 + 1)) + LET i1: Number := math.atan2(a.re, (s1.re*s2.re) - (s1.im*s2.im)) + RETURN Complex(i1, -r1) +END FUNCTION + +%| + | Function: asinh + | + | Inverse hyperbolic sine (arc sinh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicSine.html + |% +FUNCTION asinh(a: Complex): Complex + LET s1: Complex := Complex(1 + a.im, -a.re).sqrt() + LET s2: Complex := Complex(1 - a.im, a.re).sqrt() + VAR r1: Number := (s1.re * s2.im) - (s2.re * s1.im) + r1 := sign(r1) * math.log(math.abs(r1) + math.sqrt(r1 * r1 + 1)) + LET i1: Number := math.atan2(a.im, (s1.re * s2.re) - (s1.im * s2.im)) + RETURN Complex(r1, i1) +END FUNCTION + +%| + | Function: atan + | + | Inverse tangent (arc tan). + | + | Reference: + | http://mathworld.wolfram.com/InverseTangent.html + |% +FUNCTION atan(a: Complex): Complex + IF a.re = 0 AND math.abs(a.im) = 1 THEN + RETURN Complex(0, sign(a.im) * Max) + END IF + VAR rsign: Number := 1 + IF a.re = 0 AND math.abs(a.im) > 1 THEN + rsign := -1 + END IF + LET u: Complex := i.add(a).div(i.sub(a)) + LET w: Complex := u.log() + RETURN Complex(rsign * -w.im/2, w.re/2) +END FUNCTION + +%| + | Function: atanh + | + | Inverse hyperbolic tangent (arc tanh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicTangent.html + |% +FUNCTION atanh(a: Complex): Complex + IF a.im = 0 AND math.abs(a.re) = 1 THEN + RETURN Complex(sign(a.re) * Max, 0) + END IF + LET u: Complex := One.add(a).div(One.sub(a)).log() + RETURN Complex(u.re/2, u.im/2) +END FUNCTION + +%| + | Function: cos + | + | Cosine. + | + | Reference: + | http://mathworld.wolfram.com/Cosine.html + |% +FUNCTION cos(a: Complex): Complex + RETURN Complex(math.cos(a.re)*rcosh(a.im), -math.sin(a.re)*rsinh(a.im)) +END FUNCTION + +%| + | Function: cosh + | + | Hyperbolic cosine. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicCosine.html + |% +FUNCTION cosh(a: Complex): Complex + RETURN Complex(rcosh(a.re)*math.cos(a.im), rsinh(a.re)*math.sin(a.im)) +END FUNCTION + +%| + | Function: div + | + | Complex division. + | + | Reference: + | http://mathworld.wolfram.com/ComplexDivision.html + |% +FUNCTION div(a, b: Complex): Complex + LET d: Number := b.re^2 + b.im^2 + RETURN Complex((a.re*b.re + a.im*b.im) / d, (a.im*b.re - a.re*b.im) / d) +END FUNCTION + +%| + | Function: exp + | + | Complex exponentiation. + | + | Reference: + | http://mathworld.wolfram.com/ComplexExponentiation.html + |% +FUNCTION exp(a: Complex): Complex + LET r: Number := math.exp(a.re) + RETURN Complex(r * math.cos(a.im), r * math.sin(a.im)) +END FUNCTION + +%| + | Function: exp + | + | Complex exponentiation (base 10). + | + | Reference: + | http://mathworld.wolfram.com/ComplexExponentiation.html + |% +FUNCTION exp10(a: Complex): Complex + LET r: Number := 10^a.re + LET t: Number := a.im * LN10 + RETURN Complex(r * math.cos(t), r * math.sin(t)) +END FUNCTION + +%| + | Function: inv + | + | Complex inverse. + | + | Reference: + | http://mathworld.wolfram.com/MultiplicativeInverse.html + |% +FUNCTION inv(a: Complex): Complex + LET d: Number := a.re*a.re + a.im*a.im + RETURN Complex(a.re/d, -a.im/d) +END FUNCTION + +%| + | Function: log + | + | Complex logarithm. + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +FUNCTION log(a: Complex): Complex + RETURN Complex(math.log(a.re*a.re + a.im*a.im)/2, a.arg()) +END FUNCTION + +%| + | Function: log10 + | + | Complex logarithm (base 10). + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +FUNCTION log10(a: Complex): Complex + LET u: Complex := a.log() + RETURN Complex(u.re / LN10, u.im / LN10) +END FUNCTION + +%| + | Function: mul + | + | Complex multiplication. + | + | Reference: + | http://mathworld.wolfram.com/ComplexMultiplication.html + |% +FUNCTION mul(a, b: Complex): Complex + RETURN Complex(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re) +END FUNCTION + +%| + | Function: pow + | + | Complex power. + | + | Reference: + | http://mathworld.wolfram.com/Power.html + |% +FUNCTION pow(a, b: Complex): Complex + LET p: Number := a.arg() + LET m: Number := a.abs() + LET r: Number := m^b.re * math.exp(-b.im * p) + LET t: Number := b.re * p + b.im * math.log(m) + RETURN Complex(r * math.cos(t), r * math.sin(t)) +END FUNCTION + +%| + | Function: sin + | + | Sine. + | + | Reference: + | http://mathworld.wolfram.com/Sine.html + |% +FUNCTION sin(a: Complex): Complex + RETURN Complex(math.sin(a.re)*rcosh(a.im), math.cos(a.re)*rsinh(a.im)) +END FUNCTION + +%| + | Function: sinh + | + | Hyperbolic sine. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicSine.html + |% +FUNCTION sinh(a: Complex): Complex + RETURN Complex(rsinh(a.re)*math.cos(a.im), rcosh(a.re)*math.sin(a.im)) +END FUNCTION + +%| + | Function: sqrt + | + | Square root. + | + | Reference: + | http://mathworld.wolfram.com/SquareRoot.html + |% +FUNCTION sqrt(a: Complex): Complex + LET m: Number := a.abs() + RETURN Complex(math.sqrt((a.re + m) / 2), (IF a.im < 0 THEN -1 ELSE 1) * math.sqrt((-a.re + m) / 2)) +END FUNCTION + +%| + | Function: sub + | + | Complex subtraction. + | + | Reference: + | http://mathworld.wolfram.com/ComplexSubtraction.html + |% +FUNCTION sub(a, b: Complex): Complex + RETURN Complex(a.re - b.re, a.im - b.im) +END FUNCTION + +%| + | Function: square + | + | Complex square (a^2). + |% +FUNCTION square(a: Complex): Complex + RETURN Complex(a.re*a.re - a.im*a.im, 2*a.re*a.im) +END FUNCTION + +%| + | Function: tan + | + | Tangent. + | + | Reference: + | http://mathworld.wolfram.com/Tangent.html + |% +FUNCTION tan(a: Complex): Complex + LET u: Complex := Complex(math.tan(a.re), rtanh(a.im)) + RETURN u.div(Complex(1, -u.re*u.im)) +END FUNCTION + +%| + | Function: tanh + | + | Hyperbolic tangent. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicTangent.html + |% +FUNCTION tanh(a: Complex): Complex + LET u: Complex := Complex(rtanh(a.re), math.tan(a.im)) + RETURN u.div(Complex(1, u.re*u.im)) +END FUNCTION + +FUNCTION Complex.acos(self: Complex): Complex + RETURN acos(self) +END FUNCTION + +FUNCTION Complex.acosh(self: Complex): Complex + RETURN acosh(self) +END FUNCTION + +FUNCTION Complex.abs(self: Complex): Number + RETURN abs(self) +END FUNCTION + +FUNCTION Complex.add(self: Complex, a: Complex): Complex + RETURN add(self, a) +END FUNCTION + +FUNCTION Complex.arg(self: Complex): Number + RETURN arg(self) +END FUNCTION + +FUNCTION Complex.asin(self: Complex): Complex + RETURN asin(self) +END FUNCTION + +FUNCTION Complex.asinh(self: Complex): Complex + RETURN asinh(self) +END FUNCTION + +FUNCTION Complex.atan(self: Complex): Complex + RETURN atan(self) +END FUNCTION + +FUNCTION Complex.atanh(self: Complex): Complex + RETURN atanh(self) +END FUNCTION + +FUNCTION Complex.cos(self: Complex): Complex + RETURN cos(self) +END FUNCTION + +FUNCTION Complex.cosh(self: Complex): Complex + RETURN cosh(self) +END FUNCTION + +FUNCTION Complex.div(self: Complex, a: Complex): Complex + RETURN div(self, a) +END FUNCTION + +FUNCTION Complex.exp(self: Complex): Complex + RETURN exp(self) +END FUNCTION + +FUNCTION Complex.exp10(self: Complex): Complex + RETURN exp10(self) +END FUNCTION + +FUNCTION Complex.inv(self: Complex): Complex + RETURN inv(self) +END FUNCTION + +FUNCTION Complex.log(self: Complex): Complex + RETURN log(self) +END FUNCTION + +FUNCTION Complex.log10(self: Complex): Complex + RETURN log10(self) +END FUNCTION + +FUNCTION Complex.mul(self: Complex, a: Complex): Complex + RETURN mul(self, a) +END FUNCTION + +FUNCTION Complex.pow(self: Complex, a: Complex): Complex + RETURN pow(self, a) +END FUNCTION + +FUNCTION Complex.sin(self: Complex): Complex + RETURN sin(self) +END FUNCTION + +FUNCTION Complex.sinh(self: Complex): Complex + RETURN sinh(self) +END FUNCTION + +FUNCTION Complex.sqrt(self: Complex): Complex + RETURN sqrt(self) +END FUNCTION + +FUNCTION Complex.sub(self: Complex, a: Complex): Complex + RETURN sub(self, a) +END FUNCTION + +FUNCTION Complex.square(self: Complex): Complex + RETURN square(self) +END FUNCTION + +FUNCTION Complex.tan(self: Complex): Complex + RETURN tan(self) +END FUNCTION + +FUNCTION Complex.tanh(self: Complex): Complex + RETURN tanh(self) +END FUNCTION + +FUNCTION Complex.toString(self: Complex): String + RETURN "(\(self.re), \(self.im))" +END FUNCTION diff --git a/lib/compress.cpp b/lib/compress.cpp new file mode 100644 index 0000000000..84e6cc02ef --- /dev/null +++ b/lib/compress.cpp @@ -0,0 +1,119 @@ +#include + +#include +#include +#include + +namespace rtl { + +std::string compress$gzip(const std::string &input) +{ + std::string buf; + uLong destLen = compressBound(static_cast(input.length())); + buf.resize(destLen); + int r = compress(reinterpret_cast(const_cast(buf.data())), &destLen, reinterpret_cast(const_cast(input.data())), static_cast(input.length())); + if (r != Z_OK) { + fprintf(stderr, "gzip r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(destLen); + return buf; +} + +std::string compress$gunzip(const std::string &input) +{ + std::string buf; + uLong destLen = 12 * static_cast(input.length()); + int r; + for (;;) { + buf.resize(destLen); + r = uncompress(reinterpret_cast(const_cast(buf.data())), &destLen, reinterpret_cast(const_cast(input.data())), static_cast(input.length())); + if (r != Z_BUF_ERROR) { + break; + } + destLen *= 2; + } + if (r != Z_OK) { + fprintf(stderr, "gunzip r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(destLen); + return buf; +} + +std::string compress$bzip2(const std::string &input) +{ + std::string buf; + unsigned int destLen = static_cast(input.length() + input.length()/100 + 600); + buf.resize(destLen); + int r = BZ2_bzBuffToBuffCompress(const_cast(buf.data()), &destLen, const_cast(input.data()), static_cast(input.length()), 5, 0, 30); + if (r != BZ_OK) { + fprintf(stderr, "bzip2 r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(destLen); + return buf; +} + +std::string compress$bunzip2(const std::string &input) +{ + std::string buf; + unsigned int destLen = 20 * static_cast(input.length()); + int r; + for (;;) { + buf.resize(destLen); + r = BZ2_bzBuffToBuffDecompress(const_cast(buf.data()), &destLen, const_cast(input.data()), static_cast(input.length()), 0, 0); + if (r != BZ_OUTBUFF_FULL) { + break; + } + destLen *= 2; + } + if (r != BZ_OK) { + fprintf(stderr, "bunzip2 r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(destLen); + return buf; +} + +std::string compress$lzma(const std::string &input) +{ + std::string buf; + size_t destLen = input.length() + 1000; + buf.resize(destLen); + size_t destPos = 0; + int r = lzma_easy_buffer_encode(LZMA_PRESET_EXTREME, LZMA_CHECK_CRC64, NULL, reinterpret_cast(input.data()), input.length(), reinterpret_cast(const_cast(buf.data())), &destPos, destLen); + if (r != LZMA_OK) { + fprintf(stderr, "lzma r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(destPos); + return buf; +} + +std::string compress$unlzma(const std::string &input) +{ + std::string buf; + size_t destLen = 20 * input.length(); + int r; + size_t outPos; + for (;;) { + buf.resize(destLen); + uint64_t memlimit = UINT64_MAX; + size_t inPos = 0; + outPos = 0; + r = lzma_stream_buffer_decode(&memlimit, 0, NULL, reinterpret_cast(input.data()), &inPos, input.length(), reinterpret_cast(const_cast(buf.data())), &outPos, destLen); + if (r != LZMA_BUF_ERROR) { + break; + } + destLen *= 2; + } + if (r != LZMA_OK) { + fprintf(stderr, "unlzma r %d\n", r); + return std::string(); // TODO: exception + } + buf.resize(outPos); + return buf; +} + +} diff --git a/lib/compress.neon b/lib/compress.neon new file mode 100644 index 0000000000..d6be4b186a --- /dev/null +++ b/lib/compress.neon @@ -0,0 +1,53 @@ +%| + | File: compress + | + | Functions for in-memory compression using several different algorithms. + |% + +%| + | Function: gzip + | + | Compress an input string using the gzip algorithm, + | returning the compressed data as . + |% +DECLARE NATIVE FUNCTION gzip(input: String): Bytes + +%| + | Function: gunzip + | + | Decompress input bytes using the gunzip algorithm, + | returning the uncompressed data as a . + |% +DECLARE NATIVE FUNCTION gunzip(input: Bytes): String + +%| + | Function: bzip2 + | + | Compress an input string using the bzip2 algorithm, + | returning the compressed data as . + |% +DECLARE NATIVE FUNCTION bzip2(input: String): Bytes + +%| + | Function: bunzip2 + | + | Decompress input bytes using the bunzip2 algorithm, + | returning the uncompressed data as a . + |% +DECLARE NATIVE FUNCTION bunzip2(input: Bytes): String + +%| + | Function: lzma + | + | Compress an input string using the lzma algorithm, + | returning the compressed data as . + |% +DECLARE NATIVE FUNCTION lzma(input: String): Bytes + +%| + | Function: unlzma + | + | Decompress input bytes usingi the unlzma algorithm, + | returning the uncompressed data as a . + |% +DECLARE NATIVE FUNCTION unlzma(input: Bytes): String diff --git a/lib/curses.cpp b/lib/curses.cpp index 60f155d03f..208c32e417 100644 --- a/lib/curses.cpp +++ b/lib/curses.cpp @@ -31,16 +31,31 @@ static std::vector chtypes_from_numbers(const std::vector &chstr namespace rtl { -Number curses$COLS() +Number curses$COLOR_PAIR(Number n) +{ + return number_from_uint32(COLOR_PAIR(number_to_uint32(n))); +} + +Number curses$Cols() { return number_from_sint32(COLS); } -Number curses$LINES() +Number curses$KEY_F(Number c) +{ + return number_from_uint32(KEY_F(number_to_uint32(c))); +} + +Number curses$Lines() { return number_from_sint32(LINES); } +Number curses$PAIR_NUMBER(Number n) +{ + return number_from_uint32(PAIR_NUMBER(number_to_uint32(n))); +} + void curses$addch(Number ch) { addch(number_to_uint32(ch)); @@ -300,6 +315,11 @@ void curses$curs_set(Number visibility) curs_set(number_to_sint32(visibility)); } +void *curses$curscr() +{ + return curscr; +} + std::string curses$curses_version() { return curses_version(); diff --git a/lib/curses.neon b/lib/curses.neon new file mode 100644 index 0000000000..8ce25d36b0 --- /dev/null +++ b/lib/curses.neon @@ -0,0 +1,362 @@ +%| + | File: curses + | + | Curses library for text-oriented user interface support. + | + | Reference: + | https://en.wikipedia.org/wiki/Curses_%28programming_library%29 + |% + +EXPORT Window + +DECLARE NATIVE CONSTANT CURSES_ERR: Number +DECLARE NATIVE CONSTANT CURSES_OK: Number + +TYPE Window IS POINTER + +%|---------------------------------------------------------------------- + | + | Video Attributes + | + |% + +DECLARE NATIVE CONSTANT A_CHARTEXT : Number +DECLARE NATIVE CONSTANT A_NORMAL : Number +DECLARE NATIVE CONSTANT A_ALTCHARSET : Number +DECLARE NATIVE CONSTANT A_INVIS : Number +DECLARE NATIVE CONSTANT A_UNDERLINE : Number +DECLARE NATIVE CONSTANT A_REVERSE : Number +DECLARE NATIVE CONSTANT A_BLINK : Number +DECLARE NATIVE CONSTANT A_BOLD : Number +DECLARE NATIVE CONSTANT A_ATTRIBUTES : Number +DECLARE NATIVE CONSTANT A_COLOR : Number +DECLARE NATIVE CONSTANT A_PROTECT : Number + +%|---------------------------------------------------------------------- + | + | Color Constants + | + |% + +DECLARE NATIVE CONSTANT COLOR_BLACK : Number +DECLARE NATIVE CONSTANT COLOR_BLUE : Number +DECLARE NATIVE CONSTANT COLOR_GREEN : Number +DECLARE NATIVE CONSTANT COLOR_RED : Number +DECLARE NATIVE CONSTANT COLOR_CYAN : Number +DECLARE NATIVE CONSTANT COLOR_MAGENTA : Number +DECLARE NATIVE CONSTANT COLOR_YELLOW : Number +DECLARE NATIVE CONSTANT COLOR_WHITE : Number + +%|---------------------------------------------------------------------- + | + | Keyboard, and Key Definitions. + | + |% + +DECLARE NATIVE CONSTANT KEY_CODE_YES : Number % If get_wch() gives a key code + +DECLARE NATIVE CONSTANT KEY_BREAK : Number % Not on PC KBD +DECLARE NATIVE CONSTANT KEY_DOWN : Number % Down arrow key +DECLARE NATIVE CONSTANT KEY_UP : Number % Up arrow key +DECLARE NATIVE CONSTANT KEY_LEFT : Number % Left arrow key +DECLARE NATIVE CONSTANT KEY_RIGHT : Number % Right arrow key +DECLARE NATIVE CONSTANT KEY_HOME : Number % home key +DECLARE NATIVE CONSTANT KEY_BACKSPACE : Number % not on pc +DECLARE NATIVE CONSTANT KEY_F0 : Number % function keys; 64 reserved + +DECLARE NATIVE CONSTANT KEY_DL : Number % delete line +DECLARE NATIVE CONSTANT KEY_IL : Number % insert line +DECLARE NATIVE CONSTANT KEY_DC : Number % delete character +DECLARE NATIVE CONSTANT KEY_IC : Number % insert char or enter ins mode +DECLARE NATIVE CONSTANT KEY_EIC : Number % exit insert char mode +DECLARE NATIVE CONSTANT KEY_CLEAR : Number % clear screen +DECLARE NATIVE CONSTANT KEY_EOS : Number % clear to end of screen +DECLARE NATIVE CONSTANT KEY_EOL : Number % clear to end of line +DECLARE NATIVE CONSTANT KEY_SF : Number % scroll 1 line forward +DECLARE NATIVE CONSTANT KEY_SR : Number % scroll 1 line back (reverse) +DECLARE NATIVE CONSTANT KEY_NPAGE : Number % next page +DECLARE NATIVE CONSTANT KEY_PPAGE : Number % previous page +DECLARE NATIVE CONSTANT KEY_STAB : Number % set tab +DECLARE NATIVE CONSTANT KEY_CTAB : Number % clear tab +DECLARE NATIVE CONSTANT KEY_CATAB : Number % clear all tabs +DECLARE NATIVE CONSTANT KEY_ENTER : Number % enter or send (unreliable) +DECLARE NATIVE CONSTANT KEY_SRESET : Number % soft/reset (partial/unreliable) +DECLARE NATIVE CONSTANT KEY_RESET : Number % reset/hard reset (unreliable) +DECLARE NATIVE CONSTANT KEY_PRINT : Number % print/copy +DECLARE NATIVE CONSTANT KEY_LL : Number % home down/bottom (lower left) +DECLARE NATIVE CONSTANT KEY_SHELP : Number % short help +DECLARE NATIVE CONSTANT KEY_BTAB : Number % Back tab key +DECLARE NATIVE CONSTANT KEY_BEG : Number % beg(inning) key +DECLARE NATIVE CONSTANT KEY_CANCEL : Number % cancel key +DECLARE NATIVE CONSTANT KEY_CLOSE : Number % close key +DECLARE NATIVE CONSTANT KEY_COMMAND : Number % cmd (command) key +DECLARE NATIVE CONSTANT KEY_COPY : Number % copy key +DECLARE NATIVE CONSTANT KEY_CREATE : Number % create key +DECLARE NATIVE CONSTANT KEY_END : Number % end key +DECLARE NATIVE CONSTANT KEY_EXIT : Number % exit key +DECLARE NATIVE CONSTANT KEY_FIND : Number % find key +DECLARE NATIVE CONSTANT KEY_HELP : Number % help key +DECLARE NATIVE CONSTANT KEY_MARK : Number % mark key +DECLARE NATIVE CONSTANT KEY_MESSAGE : Number % message key +DECLARE NATIVE CONSTANT KEY_MOVE : Number % move key +DECLARE NATIVE CONSTANT KEY_NEXT : Number % next object key +DECLARE NATIVE CONSTANT KEY_OPEN : Number % open key +DECLARE NATIVE CONSTANT KEY_OPTIONS : Number % options key +DECLARE NATIVE CONSTANT KEY_PREVIOUS : Number % previous object key +DECLARE NATIVE CONSTANT KEY_REDO : Number % redo key +DECLARE NATIVE CONSTANT KEY_REFERENCE : Number % ref(erence) key +DECLARE NATIVE CONSTANT KEY_REFRESH : Number % refresh key +DECLARE NATIVE CONSTANT KEY_REPLACE : Number % replace key +DECLARE NATIVE CONSTANT KEY_RESTART : Number % restart key +DECLARE NATIVE CONSTANT KEY_RESUME : Number % resume key +DECLARE NATIVE CONSTANT KEY_SAVE : Number % save key +DECLARE NATIVE CONSTANT KEY_SBEG : Number % shifted beginning key +DECLARE NATIVE CONSTANT KEY_SCANCEL : Number % shifted cancel key +DECLARE NATIVE CONSTANT KEY_SCOMMAND : Number % shifted command key +DECLARE NATIVE CONSTANT KEY_SCOPY : Number % shifted copy key +DECLARE NATIVE CONSTANT KEY_SCREATE : Number % shifted create key +DECLARE NATIVE CONSTANT KEY_SDC : Number % shifted delete char key +DECLARE NATIVE CONSTANT KEY_SDL : Number % shifted delete line key +DECLARE NATIVE CONSTANT KEY_SELECT : Number % select key +DECLARE NATIVE CONSTANT KEY_SEND : Number % shifted end key +DECLARE NATIVE CONSTANT KEY_SEOL : Number % shifted clear line key +DECLARE NATIVE CONSTANT KEY_SEXIT : Number % shifted exit key +DECLARE NATIVE CONSTANT KEY_SFIND : Number % shifted find key +DECLARE NATIVE CONSTANT KEY_SHOME : Number % shifted home key +DECLARE NATIVE CONSTANT KEY_SIC : Number % shifted input key + +DECLARE NATIVE CONSTANT KEY_SLEFT : Number % shifted left arrow key +DECLARE NATIVE CONSTANT KEY_SMESSAGE : Number % shifted message key +DECLARE NATIVE CONSTANT KEY_SMOVE : Number % shifted move key +DECLARE NATIVE CONSTANT KEY_SNEXT : Number % shifted next key +DECLARE NATIVE CONSTANT KEY_SOPTIONS : Number % shifted options key +DECLARE NATIVE CONSTANT KEY_SPREVIOUS : Number % shifted prev key +DECLARE NATIVE CONSTANT KEY_SPRINT : Number % shifted print key +DECLARE NATIVE CONSTANT KEY_SREDO : Number % shifted redo key +DECLARE NATIVE CONSTANT KEY_SREPLACE : Number % shifted replace key +DECLARE NATIVE CONSTANT KEY_SRIGHT : Number % shifted right arrow +DECLARE NATIVE CONSTANT KEY_SRSUME : Number % shifted resume key +DECLARE NATIVE CONSTANT KEY_SSAVE : Number % shifted save key +DECLARE NATIVE CONSTANT KEY_SSUSPEND : Number % shifted suspend key +DECLARE NATIVE CONSTANT KEY_SUNDO : Number % shifted undo key +DECLARE NATIVE CONSTANT KEY_SUSPEND : Number % suspend key +DECLARE NATIVE CONSTANT KEY_UNDO : Number % undo key + +DECLARE NATIVE CONSTANT KEY_MIN : Number % Minimum curses key value +DECLARE NATIVE CONSTANT KEY_MAX : Number % Maximum curses key + +%|---------------------------------------------------------------------- + | + | Curses functions + | + |% + +DECLARE NATIVE FUNCTION COLOR_PAIR(n: Number): Number +DECLARE NATIVE FUNCTION Cols(): Number +DECLARE NATIVE FUNCTION KEY_F(n: Number): Number +DECLARE NATIVE FUNCTION Lines(): Number +DECLARE NATIVE FUNCTION PAIR_NUMBER(n: Number): Number + +DECLARE NATIVE FUNCTION addch(ch: Number) +DECLARE NATIVE FUNCTION waddch(win: Window, ch: Number) +DECLARE NATIVE FUNCTION mvaddch(y: Number, x: Number, ch: Number) +DECLARE NATIVE FUNCTION mvwaddch(win: Window, y: Number, x: Number, ch: Number) +DECLARE NATIVE FUNCTION addchstr(chstr: Array) +DECLARE NATIVE FUNCTION waddchstr(win: Window, chstr: Array) +DECLARE NATIVE FUNCTION mvaddchstr(y: Number, x: Number, chstr: Array) +DECLARE NATIVE FUNCTION mvwaddchstr(win: Window, y: Number, x: Number, chstr: Array) +DECLARE NATIVE FUNCTION addstr(str: String) +DECLARE NATIVE FUNCTION waddstr(win: Window, str: String) +DECLARE NATIVE FUNCTION mvaddstr(y: Number, x: Number, str: String) +DECLARE NATIVE FUNCTION mvwaddstr(win: Window, y: Number, x: Number, str: String) +DECLARE NATIVE FUNCTION attroff(attrs: Number) +DECLARE NATIVE FUNCTION wattroff(win: Window, attrs: Number) +DECLARE NATIVE FUNCTION attron(attrs: Number) +DECLARE NATIVE FUNCTION wattron(win: Window, attrs: Number) +DECLARE NATIVE FUNCTION attrset(attrs: Number) +DECLARE NATIVE FUNCTION wattrset(win: Window, attrs: Number) +DECLARE NATIVE FUNCTION baudrate(): Number +DECLARE NATIVE FUNCTION beep() +DECLARE NATIVE FUNCTION bkgd(ch: Number) +DECLARE NATIVE FUNCTION wbkgd(win: Window, ch: Number) +DECLARE NATIVE FUNCTION bkgdset(ch: Number) +DECLARE NATIVE FUNCTION wbkgdset(win: Window, ch: Number) +DECLARE NATIVE FUNCTION border(ls: Number, rs: Number, ts: Number, bs: Number, tl: Number, tr: Number, bl: Number, br: Number) +DECLARE NATIVE FUNCTION wborder(win: Window, ls: Number, rs: Number, ts: Number, bs: Number, tl: Number, tr: Number, bl: Number, br: Number) +DECLARE NATIVE FUNCTION box(win: Window, verch: Number, horch: Number) +DECLARE NATIVE FUNCTION can_change_color(): Boolean +DECLARE NATIVE FUNCTION cbreak() +DECLARE NATIVE FUNCTION nocbreak() +DECLARE NATIVE FUNCTION chgat(n: Number, attr: Number, color: Number, opts: POINTER) +DECLARE NATIVE FUNCTION wchgat(win: Window, n: Number, attr: Number, color: Number, opts: POINTER) +DECLARE NATIVE FUNCTION mvchgat(y: Number, x: Number, n: Number, attr: Number, color: Number, opts: POINTER) +DECLARE NATIVE FUNCTION mvwchgat(win: Window, y: Number, x: Number, n: Number, attr: Number, color: Number, opts: POINTER) +DECLARE NATIVE FUNCTION clear() +DECLARE NATIVE FUNCTION wclear(win: Window) +DECLARE NATIVE FUNCTION clearok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION clrtobot() +DECLARE NATIVE FUNCTION wclrtobot(win: Window) +DECLARE NATIVE FUNCTION clrtoeol() +DECLARE NATIVE FUNCTION wclrtoeol(win: Window) +DECLARE NATIVE FUNCTION color_content(color: Number): Array +DECLARE NATIVE FUNCTION color_set(color_pair_number: Number, opts: POINTER) +DECLARE NATIVE FUNCTION wcolor_set(win: Window, color_pair_number: Number, opts: POINTER) +DECLARE NATIVE FUNCTION copywin(srcwin: Window, dstwin: Window, sminrow: Number, smincol: Number, dminrow: Number, dmincol: Number, dmaxrow: Number, dmaxcol: Number, overlay: Boolean) +DECLARE NATIVE FUNCTION curs_set(visibility: Number) +DECLARE NATIVE FUNCTION curscr(): Window +DECLARE NATIVE FUNCTION curses_version(): String +DECLARE NATIVE FUNCTION def_prog_mode() +DECLARE NATIVE FUNCTION def_shell_mode() +DECLARE NATIVE FUNCTION delay_output(ms: Number) +DECLARE NATIVE FUNCTION delch() +DECLARE NATIVE FUNCTION wdelch(win: Window) +DECLARE NATIVE FUNCTION mvdelch(y: Number, x: Number) +DECLARE NATIVE FUNCTION mvwdelch(win: Window, y: Number, x: Number) +DECLARE NATIVE FUNCTION deleteln() +DECLARE NATIVE FUNCTION wdeleteln(win: Window) +DECLARE NATIVE FUNCTION delwin(win: Window) +DECLARE NATIVE FUNCTION derwin(orig: Window, nlines: Number, ncols: Number, begin_y: Number, begin_x: Number): Window +DECLARE NATIVE FUNCTION doupdate() +DECLARE NATIVE FUNCTION dupwin(win: Window): Window +DECLARE NATIVE FUNCTION echo() +DECLARE NATIVE FUNCTION noecho() +DECLARE NATIVE FUNCTION echochar(ch: Number) +DECLARE NATIVE FUNCTION wechochar(win: Window, ch: Number) +DECLARE NATIVE FUNCTION endwin() +DECLARE NATIVE FUNCTION erase() +DECLARE NATIVE FUNCTION werase(win: Window) +DECLARE NATIVE FUNCTION erasechar(): String +DECLARE NATIVE FUNCTION filter() +DECLARE NATIVE FUNCTION flash() +DECLARE NATIVE FUNCTION flushinp() +DECLARE NATIVE FUNCTION getbegx(win: Window): Number +DECLARE NATIVE FUNCTION getbegy(win: Window): Number +DECLARE NATIVE FUNCTION getbegyx(win: Window): Array +DECLARE NATIVE FUNCTION getbkgd(win: Window): Number +DECLARE NATIVE FUNCTION getch(): Number +DECLARE NATIVE FUNCTION wgetch(win: Window): Number +DECLARE NATIVE FUNCTION mvgetch(y: Number, x: Number): Number +DECLARE NATIVE FUNCTION mvwgetch(win: Window, y: Number, x: Number): Number +DECLARE NATIVE FUNCTION getcurx(win: Window): Number +DECLARE NATIVE FUNCTION getcury(win: Window): Number +DECLARE NATIVE FUNCTION getmaxx(win: Window): Number +DECLARE NATIVE FUNCTION getmaxy(win: Window): Number +DECLARE NATIVE FUNCTION getmaxyx(win: Window): Array +DECLARE NATIVE FUNCTION getnstr(n: Number): String +DECLARE NATIVE FUNCTION wgetnstr(win: Window, n: Number): String +DECLARE NATIVE FUNCTION mvgetnstr(y: Number, x: Number, n: Number): String +DECLARE NATIVE FUNCTION mvwgetnstr(win: Window, y: Number, x: Number, n: Number): String +DECLARE NATIVE FUNCTION getparx(win: Window): Number +DECLARE NATIVE FUNCTION getpary(win: Window): Number +DECLARE NATIVE FUNCTION getparyx(win: Window): Array +DECLARE NATIVE FUNCTION getsyx(): Array +DECLARE NATIVE FUNCTION getyx(win: Window): Array +DECLARE NATIVE FUNCTION halfdelay(tenths: Number) +DECLARE NATIVE FUNCTION has_colors(): Boolean +DECLARE NATIVE FUNCTION has_ic(): Boolean +DECLARE NATIVE FUNCTION has_il(): Boolean +DECLARE NATIVE FUNCTION has_key(ch: Number): Boolean +DECLARE NATIVE FUNCTION hline(ch: Number, n: Number) +DECLARE NATIVE FUNCTION whline(win: Window, ch: Number, n: Number) +DECLARE NATIVE FUNCTION mvhline(y: Number, x: Number, ch: Number, n: Number) +DECLARE NATIVE FUNCTION mvwhline(win: Window, y: Number, x: Number, ch: Number, n: Number) +DECLARE NATIVE FUNCTION idcok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION idlok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION immedok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION inch(): Number +DECLARE NATIVE FUNCTION winch(win: Window): Number +DECLARE NATIVE FUNCTION mvinch(y: Number, x: Number): Number +DECLARE NATIVE FUNCTION mvwinch(win: Window, y: Number, x: Number): Number +DECLARE NATIVE FUNCTION init_color(pair: Number, r: Number, g: Number, b: Number) +DECLARE NATIVE FUNCTION init_pair(pair: Number, f: Number, b: Number) +DECLARE NATIVE FUNCTION initscr() +DECLARE NATIVE FUNCTION intrflush(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION insch(ch: Number) +DECLARE NATIVE FUNCTION winsch(win: Window, ch: Number) +DECLARE NATIVE FUNCTION mvinsch(y: Number, x: Number, ch: Number) +DECLARE NATIVE FUNCTION mvwinsch(win: Window, y: Number, x: Number, ch: Number) +DECLARE NATIVE FUNCTION insdelln(n: Number) +DECLARE NATIVE FUNCTION winsdelln(win: Window, n: Number) +DECLARE NATIVE FUNCTION insertln() +DECLARE NATIVE FUNCTION winsertln(win: Window) +DECLARE NATIVE FUNCTION insstr(str: String) +DECLARE NATIVE FUNCTION winsstr(win: Window, str: String) +DECLARE NATIVE FUNCTION mvinsstr(y: Number, x: Number, str: String) +DECLARE NATIVE FUNCTION mvwinsstr(win: Window, y: Number, x: Number, str: String) +DECLARE NATIVE FUNCTION is_linetouched(win: Window, line: Number): Boolean +DECLARE NATIVE FUNCTION is_wintouched(win: Window): Boolean +DECLARE NATIVE FUNCTION isendwin(): Boolean +DECLARE NATIVE FUNCTION keyname(c: Number): String +DECLARE NATIVE FUNCTION keypad(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION killchar(): String +DECLARE NATIVE FUNCTION leaveok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION longname(): String +DECLARE NATIVE FUNCTION meta(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION move(y: Number, x: Number) +DECLARE NATIVE FUNCTION wmove(win: Window, y: Number, x: Number) +DECLARE NATIVE FUNCTION mvderwin(win: Window, par_y: Number, par_x: Number) +DECLARE NATIVE FUNCTION mvwin(win: Window, y: Number, x: Number) +DECLARE NATIVE FUNCTION napms(ms: Number) +DECLARE NATIVE FUNCTION newpad(nlines: Number, ncols: Number): Window +DECLARE NATIVE FUNCTION newwin(nlines: Number, ncols: Number, begin_y: Number, begin_x: Number): Window +DECLARE NATIVE FUNCTION nl() +DECLARE NATIVE FUNCTION nonl() +DECLARE NATIVE FUNCTION nodelay(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION noqiflush() +DECLARE NATIVE FUNCTION notimeout(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION overlay(srcwin: Window, dstwin: Window) +DECLARE NATIVE FUNCTION overwrite(srcwin: Window, dstwin: Window) +DECLARE NATIVE FUNCTION pair_content(color: Number): Array +DECLARE NATIVE FUNCTION pechochar(pad: Window, ch: Number) +DECLARE NATIVE FUNCTION pnoutrefresh(pad: Window, pminrow: Number, pmincol: Number, sminrow: Number, smincol: Number, smaxrow: Number, smaxcol: Number) +DECLARE NATIVE FUNCTION prefresh(pad: Window, pminrow: Number, pmincol: Number, sminrow: Number, smincol: Number, smaxrow: Number, smaxcol: Number) +DECLARE NATIVE FUNCTION qiflush() +DECLARE NATIVE FUNCTION raw() +DECLARE NATIVE FUNCTION noraw() +DECLARE NATIVE FUNCTION redrawwin(win: Window) +DECLARE NATIVE FUNCTION refresh() +DECLARE NATIVE FUNCTION wnoutrefresh(win: Window) +DECLARE NATIVE FUNCTION wrefresh(win: Window) +DECLARE NATIVE FUNCTION reset_prog_mode() +DECLARE NATIVE FUNCTION reset_shell_mode() +DECLARE NATIVE FUNCTION resetty() +DECLARE NATIVE FUNCTION savetty() +DECLARE NATIVE FUNCTION scr_dump(filename: String) +DECLARE NATIVE FUNCTION scr_init(filename: String) +DECLARE NATIVE FUNCTION scr_restore(filename: String) +DECLARE NATIVE FUNCTION scr_set(filename: String) +DECLARE NATIVE FUNCTION scroll(win: Window) +DECLARE NATIVE FUNCTION scrollok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION scrl(n: Number) +DECLARE NATIVE FUNCTION wscrl(win: Window, n: Number) +DECLARE NATIVE FUNCTION setscrreg(top: Number, bottom: Number) +DECLARE NATIVE FUNCTION wsetscrreg(win: Window, top: Number, bottom: Number) +DECLARE NATIVE FUNCTION setsyx(y: Number, x: Number) +DECLARE NATIVE FUNCTION standend() +DECLARE NATIVE FUNCTION wstandend(win: Window) +DECLARE NATIVE FUNCTION standout() +DECLARE NATIVE FUNCTION wstandout(win: Window) +DECLARE NATIVE FUNCTION start_color() +DECLARE NATIVE FUNCTION stdscr(): Window +DECLARE NATIVE FUNCTION subpad(orig: Window, nlines: Number, ncols: Number, begin_y: Number, begin_x: Number): Window +DECLARE NATIVE FUNCTION subwin(orig: Window, nlines: Number, ncols: Number, begin_y: Number, begin_x: Number): Window +DECLARE NATIVE FUNCTION syncok(win: Window, bf: Boolean) +DECLARE NATIVE FUNCTION termattrs(): Number +DECLARE NATIVE FUNCTION termname(): String +DECLARE NATIVE FUNCTION timeout(delay: Number) +DECLARE NATIVE FUNCTION wtimeout(win: Window, delay: Number) +DECLARE NATIVE FUNCTION touchline(win: Window, start: Number, count: Number) +DECLARE NATIVE FUNCTION touchwin(win: Window) +DECLARE NATIVE FUNCTION untouchwin(win: Window) +DECLARE NATIVE FUNCTION unctrl(c: Number): String +DECLARE NATIVE FUNCTION ungetch(ch: Number) +DECLARE NATIVE FUNCTION use_env(f: Boolean) +DECLARE NATIVE FUNCTION vline(ch: Number, n: Number) +DECLARE NATIVE FUNCTION wvline(win: Window, ch: Number, n: Number) +DECLARE NATIVE FUNCTION mvvline(y: Number, x: Number, ch: Number, n: Number) +DECLARE NATIVE FUNCTION mvwvline(win: Window, y: Number, x: Number, ch: Number, n: Number) +DECLARE NATIVE FUNCTION wcursyncup(win: Window) +DECLARE NATIVE FUNCTION wredrawln(win: Window, beg_line: Number, num_lines: Number) +DECLARE NATIVE FUNCTION wsyncdown(win: Window) +DECLARE NATIVE FUNCTION wsyncup(win: Window) +DECLARE NATIVE FUNCTION wtouchln(win: Window, y: Number, n: Number, changed: Boolean) diff --git a/lib/curses_const.cpp b/lib/curses_const.cpp new file mode 100644 index 0000000000..69ef7a6eb3 --- /dev/null +++ b/lib/curses_const.cpp @@ -0,0 +1,123 @@ +#include + +#include "number.h" + +namespace rtl { + +extern const Number curses$CURSES_ERR = number_from_sint32(ERR); +extern const Number curses$CURSES_OK = number_from_sint32(OK); + +extern const Number curses$A_CHARTEXT = number_from_uint32(A_CHARTEXT); +extern const Number curses$A_NORMAL = number_from_uint32(A_NORMAL); +extern const Number curses$A_ALTCHARSET = number_from_uint32(A_ALTCHARSET); +extern const Number curses$A_INVIS = number_from_uint32(A_INVIS); +extern const Number curses$A_UNDERLINE = number_from_uint32(A_UNDERLINE); +extern const Number curses$A_REVERSE = number_from_uint32(A_REVERSE); +extern const Number curses$A_BLINK = number_from_uint32(A_BLINK); +extern const Number curses$A_BOLD = number_from_uint32(A_BOLD); +extern const Number curses$A_ATTRIBUTES = number_from_uint64(A_ATTRIBUTES); +extern const Number curses$A_COLOR = number_from_uint32(A_COLOR); +extern const Number curses$A_PROTECT = number_from_uint32(A_PROTECT); + +extern const Number curses$COLOR_BLACK = number_from_uint32(COLOR_BLACK); +extern const Number curses$COLOR_RED = number_from_uint32(COLOR_RED); +extern const Number curses$COLOR_GREEN = number_from_uint32(COLOR_GREEN); +extern const Number curses$COLOR_BLUE = number_from_uint32(COLOR_BLUE); +extern const Number curses$COLOR_CYAN = number_from_uint32(COLOR_CYAN); +extern const Number curses$COLOR_MAGENTA = number_from_uint32(COLOR_MAGENTA); +extern const Number curses$COLOR_YELLOW = number_from_uint32(COLOR_YELLOW); +extern const Number curses$COLOR_WHITE = number_from_uint32(COLOR_WHITE); + +extern const Number curses$KEY_CODE_YES = number_from_uint32(KEY_CODE_YES); + +extern const Number curses$KEY_BREAK = number_from_uint32(KEY_BREAK); +extern const Number curses$KEY_DOWN = number_from_uint32(KEY_DOWN); +extern const Number curses$KEY_UP = number_from_uint32(KEY_UP); +extern const Number curses$KEY_LEFT = number_from_uint32(KEY_LEFT); +extern const Number curses$KEY_RIGHT = number_from_uint32(KEY_RIGHT); +extern const Number curses$KEY_HOME = number_from_uint32(KEY_HOME); +extern const Number curses$KEY_BACKSPACE = number_from_uint32(KEY_BACKSPACE); + +extern const Number curses$KEY_F0 = number_from_uint32(KEY_F0); +extern const Number curses$KEY_DL = number_from_uint32(KEY_DL); +extern const Number curses$KEY_IL = number_from_uint32(KEY_IL); +extern const Number curses$KEY_DC = number_from_uint32(KEY_DC); +extern const Number curses$KEY_IC = number_from_uint32(KEY_IC); +extern const Number curses$KEY_EIC = number_from_uint32(KEY_EIC); +extern const Number curses$KEY_CLEAR = number_from_uint32(KEY_CLEAR); +extern const Number curses$KEY_EOS = number_from_uint32(KEY_EOS); +extern const Number curses$KEY_EOL = number_from_uint32(KEY_EOL); +extern const Number curses$KEY_SF = number_from_uint32(KEY_SF); +extern const Number curses$KEY_SR = number_from_uint32(KEY_SR); +extern const Number curses$KEY_NPAGE = number_from_uint32(KEY_NPAGE); +extern const Number curses$KEY_PPAGE = number_from_uint32(KEY_PPAGE); +extern const Number curses$KEY_STAB = number_from_uint32(KEY_STAB); +extern const Number curses$KEY_CTAB = number_from_uint32(KEY_CTAB); +extern const Number curses$KEY_CATAB = number_from_uint32(KEY_CATAB); +extern const Number curses$KEY_ENTER = number_from_uint32(KEY_ENTER); +extern const Number curses$KEY_SRESET = number_from_uint32(KEY_SRESET); +extern const Number curses$KEY_RESET = number_from_uint32(KEY_RESET); +extern const Number curses$KEY_PRINT = number_from_uint32(KEY_PRINT); +extern const Number curses$KEY_LL = number_from_uint32(KEY_LL); +extern const Number curses$KEY_SHELP = number_from_uint32(KEY_SHELP); +extern const Number curses$KEY_BTAB = number_from_uint32(KEY_BTAB); +extern const Number curses$KEY_BEG = number_from_uint32(KEY_BEG); +extern const Number curses$KEY_CANCEL = number_from_uint32(KEY_CANCEL); +extern const Number curses$KEY_CLOSE = number_from_uint32(KEY_CLOSE); +extern const Number curses$KEY_COMMAND = number_from_uint32(KEY_COMMAND); +extern const Number curses$KEY_COPY = number_from_uint32(KEY_COPY); +extern const Number curses$KEY_CREATE = number_from_uint32(KEY_CREATE); +extern const Number curses$KEY_END = number_from_uint32(KEY_END); +extern const Number curses$KEY_EXIT = number_from_uint32(KEY_EXIT); +extern const Number curses$KEY_FIND = number_from_uint32(KEY_FIND); +extern const Number curses$KEY_HELP = number_from_uint32(KEY_HELP); +extern const Number curses$KEY_MARK = number_from_uint32(KEY_MARK); +extern const Number curses$KEY_MESSAGE = number_from_uint32(KEY_MESSAGE); +extern const Number curses$KEY_MOVE = number_from_uint32(KEY_MOVE); +extern const Number curses$KEY_NEXT = number_from_uint32(KEY_NEXT); +extern const Number curses$KEY_OPEN = number_from_uint32(KEY_OPEN); +extern const Number curses$KEY_OPTIONS = number_from_uint32(KEY_OPTIONS); +extern const Number curses$KEY_PREVIOUS = number_from_uint32(KEY_PREVIOUS); +extern const Number curses$KEY_REDO = number_from_uint32(KEY_REDO); +extern const Number curses$KEY_REFERENCE = number_from_uint32(KEY_REFERENCE); +extern const Number curses$KEY_REFRESH = number_from_uint32(KEY_REFRESH); +extern const Number curses$KEY_REPLACE = number_from_uint32(KEY_REPLACE); +extern const Number curses$KEY_RESTART = number_from_uint32(KEY_RESTART); +extern const Number curses$KEY_RESUME = number_from_uint32(KEY_RESUME); +extern const Number curses$KEY_SAVE = number_from_uint32(KEY_SAVE); + +extern const Number curses$KEY_SBEG = number_from_uint32(KEY_SBEG); +extern const Number curses$KEY_SCANCEL = number_from_uint32(KEY_SCANCEL); +extern const Number curses$KEY_SCOMMAND = number_from_uint32(KEY_SCOMMAND); +extern const Number curses$KEY_SCOPY = number_from_uint32(KEY_SCOPY); +extern const Number curses$KEY_SCREATE = number_from_uint32(KEY_SCREATE); +extern const Number curses$KEY_SDC = number_from_uint32(KEY_SDC); +extern const Number curses$KEY_SDL = number_from_uint32(KEY_SDL); +extern const Number curses$KEY_SELECT = number_from_uint32(KEY_SELECT); +extern const Number curses$KEY_SEND = number_from_uint32(KEY_SEND); +extern const Number curses$KEY_SEOL = number_from_uint32(KEY_SEOL); +extern const Number curses$KEY_SEXIT = number_from_uint32(KEY_SEXIT); +extern const Number curses$KEY_SFIND = number_from_uint32(KEY_SFIND); +extern const Number curses$KEY_SHOME = number_from_uint32(KEY_SHOME); +extern const Number curses$KEY_SIC = number_from_uint32(KEY_SIC); +extern const Number curses$KEY_SLEFT = number_from_uint32(KEY_SLEFT); +extern const Number curses$KEY_SMESSAGE = number_from_uint32(KEY_SMESSAGE); +extern const Number curses$KEY_SMOVE = number_from_uint32(KEY_SMOVE); +extern const Number curses$KEY_SNEXT = number_from_uint32(KEY_SNEXT); +extern const Number curses$KEY_SOPTIONS = number_from_uint32(KEY_SOPTIONS); +extern const Number curses$KEY_SPREVIOUS = number_from_uint32(KEY_SPREVIOUS); +extern const Number curses$KEY_SPRINT = number_from_uint32(KEY_SPRINT); +extern const Number curses$KEY_SREDO = number_from_uint32(KEY_SREDO); +extern const Number curses$KEY_SREPLACE = number_from_uint32(KEY_SREPLACE); +extern const Number curses$KEY_SRIGHT = number_from_uint32(KEY_SRIGHT); +extern const Number curses$KEY_SRSUME = number_from_uint32(KEY_SRSUME); +extern const Number curses$KEY_SSAVE = number_from_uint32(KEY_SSAVE); +extern const Number curses$KEY_SSUSPEND = number_from_uint32(KEY_SSUSPEND); +extern const Number curses$KEY_SUNDO = number_from_uint32(KEY_SUNDO); +extern const Number curses$KEY_SUSPEND = number_from_uint32(KEY_SUSPEND); +extern const Number curses$KEY_UNDO = number_from_uint32(KEY_UNDO); + +extern const Number curses$KEY_MIN = number_from_uint32(KEY_MIN); +extern const Number curses$KEY_MAX = number_from_uint32(KEY_MAX); + +} diff --git a/lib/datetime.cpp b/lib/datetime.cpp new file mode 100644 index 0000000000..cf539cb284 --- /dev/null +++ b/lib/datetime.cpp @@ -0,0 +1,41 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define timegm _mkgmtime +#endif +#include + +#include "cell.h" + +namespace rtl { + +Cell datetime$gmtime(Number t) +{ + time_t x = number_to_sint32(t); + struct tm *tm = gmtime(&x); + Cell r; + r.array_index_for_write(0) = Cell(number_from_uint32(tm->tm_sec)); + r.array_index_for_write(1) = Cell(number_from_uint32(tm->tm_min)); + r.array_index_for_write(2) = Cell(number_from_uint32(tm->tm_hour)); + r.array_index_for_write(3) = Cell(number_from_uint32(tm->tm_mday)); + r.array_index_for_write(4) = Cell(number_from_uint32(tm->tm_mon)); + r.array_index_for_write(5) = Cell(number_from_uint32(tm->tm_year)); + r.array_index_for_write(6) = Cell(number_from_uint32(tm->tm_wday)); + r.array_index_for_write(7) = Cell(number_from_uint32(tm->tm_yday)); + r.array_index_for_write(8) = Cell(number_from_uint32(tm->tm_isdst)); + r.array_index_for_write(10); + return r; +} + +Number datetime$timegm(Cell &t) +{ + struct tm tm; + tm.tm_sec = number_to_uint32(t.array_index_for_read(0).number()); + tm.tm_min = number_to_uint32(t.array_index_for_read(1).number()); + tm.tm_hour = number_to_uint32(t.array_index_for_read(2).number()); + tm.tm_mday = number_to_uint32(t.array_index_for_read(3).number()); + tm.tm_mon = number_to_uint32(t.array_index_for_read(4).number()); + tm.tm_year = number_to_uint32(t.array_index_for_read(5).number()); + return number_from_uint64(timegm(&tm)); +} + +} diff --git a/lib/datetime.neon b/lib/datetime.neon new file mode 100644 index 0000000000..e8ad6e5e30 --- /dev/null +++ b/lib/datetime.neon @@ -0,0 +1,351 @@ +%| + | File: datetime + | + | Functions for date and time manipulation. + |% + +EXPORT Instant +EXPORT DateTime +EXPORT Interval +EXPORT Period +EXPORT Duration +EXPORT now +EXPORT makeFromInstant +EXPORT makeFromParts + +EXPORT January +EXPORT February +EXPORT March +EXPORT April +EXPORT May +EXPORT June +EXPORT July +EXPORT August +EXPORT September +EXPORT October +EXPORT November +EXPORT December + +EXPORT Monday +EXPORT Tuesday +EXPORT Wednesday +EXPORT Thursday +EXPORT Friday +EXPORT Saturday +EXPORT Sunday + +IMPORT math +IMPORT time + +DECLARE EXCEPTION TodoException + +CONSTANT January : Number := 1 +CONSTANT February : Number := 2 +CONSTANT March : Number := 3 +CONSTANT April : Number := 4 +CONSTANT May : Number := 5 +CONSTANT June : Number := 6 +CONSTANT July : Number := 7 +CONSTANT August : Number := 8 +CONSTANT September: Number := 9 +CONSTANT October : Number := 10 +CONSTANT November : Number := 11 +CONSTANT December : Number := 12 + +CONSTANT Monday : Number := 1 +CONSTANT Tuesday : Number := 2 +CONSTANT Wednesday: Number := 3 +CONSTANT Thursday : Number := 4 +CONSTANT Friday : Number := 5 +CONSTANT Saturday : Number := 6 +CONSTANT Sunday : Number := 7 + +%| + | Type: Instant + | + | Represents a particular instant in global time. + |% +TYPE Instant IS Number + +%| + | Type: DateTime + | + | Represents a particular instant in global time, + | broken down to components in UTC. + | + | Fields: + | year - year + | month - month (1=January, 12=December) + | day - day + | weekday - weekday (1=Monday, 7=Sunday) + | hour - hour + | minute - minute + | second - second + |% +TYPE DateTime IS RECORD + instant: Instant + year: Number + month: Number + day: Number + weekday: Number + hour: Number + minute: Number + second: Number +END RECORD + +%| + | Type: Interval + | + | Represents a time period between a start and an end time. + | + | Fields: + | start - start + | end - end + |% +TYPE Interval IS RECORD + start: DateTime + end: DateTime +END RECORD + +%| + | Type: Duration + | + | Represents a specific amount of time measured in seconds. + |% +TYPE Duration IS Number + +%| + | Type: Period + | + | Represents an amount of time measured by components. + | + | Fields: + | years - years + | months - months + | days - days + | hours - hours + | minutes - minutes + | seconds - seconds + |% +TYPE Period IS RECORD + years: Number + months: Number + days: Number + hours: Number + minutes: Number + seconds: Number +END RECORD + +TYPE struct_tm IS RECORD + tm_sec: Number % seconds (0 - 60) + tm_min: Number % minutes (0 - 59) + tm_hour: Number % hours (0 - 23) + tm_mday: Number % day of month (1 - 31) + tm_mon: Number % month of year (0 - 11) + tm_year: Number % year - 1900 + tm_wday: Number % day of week (Sunday = 0) + tm_yday: Number % day of year (0 - 365) + tm_isdst: Number % is summer time in effect? +END RECORD + +DECLARE NATIVE FUNCTION gmtime(t: Number): struct_tm +DECLARE NATIVE FUNCTION timegm(tm: struct_tm): Number + +%| + | Function: now + | + | Return the current timestamp as a . + |% +FUNCTION now(): DateTime + RETURN makeFromInstant(time.now()) +END FUNCTION + +%| + | Function: makeFromInstant + | + | Convert a specific instant into a record. + |% +FUNCTION makeFromInstant(inst: Instant): DateTime + LET tm: struct_tm := gmtime(inst) + RETURN DateTime( + instant WITH inst, + year WITH 1900 + tm.tm_year, + month WITH 1 + tm.tm_mon, + day WITH tm.tm_mday, + weekday WITH 1 + (tm.tm_wday + 6) MOD 7, + hour WITH tm.tm_hour, + minute WITH tm.tm_min, + second WITH tm.tm_sec + ) +END FUNCTION + +%| + | Function: makeFromParts + | + | Given timestamp parts, return a representing the instant. + | This function must be called after filling in an empty DateTime. + |% +FUNCTION makeFromParts(dt: DateTime): DateTime + VAR tm: struct_tm := struct_tm() + tm.tm_sec := dt.second + tm.tm_min := dt.minute + tm.tm_hour := dt.hour + tm.tm_mday := dt.day + tm.tm_mon := dt.month - 1 + tm.tm_year := dt.year - 1900 + RETURN makeFromInstant(timegm(tm)) +END FUNCTION + +%| + | Function: makeFromString + | + | Make a DateTime from a string representation. + |% +FUNCTION makeFromString(s: String): DateTime + RAISE TodoException +END FUNCTION + +%| + | Function: DateTime.minusDuration + | + | Subtract a from a and return a new . + |% +FUNCTION DateTime.minusDuration(self: DateTime, duration: Duration): DateTime + RETURN makeFromInstant(self.instant - duration) +END FUNCTION + +%| + | Function: DateTime.minusPeriod + | + | Subtract a from a and return a new . + |% +FUNCTION DateTime.minusPeriod(self: DateTime, period: Period): DateTime + RAISE TodoException +END FUNCTION + +%| + | Function: DateTime.plusDuration + | + | Add a to a and return a new . + |% +FUNCTION DateTime.plusDuration(self: DateTime, duration: Duration): DateTime + RETURN makeFromInstant(self.instant + duration) +END FUNCTION + +%| + | Function: DateTime.plusPeriod + | + | Add a to a and return a new . + |% +FUNCTION DateTime.plusPeriod(self: DateTime, period: Period): DateTime + VAR dt: DateTime := self + dt.year := dt.year + period.years + LET m: Number := dt.month - 1 + period.months + dt.year := dt.year + math.floor(m / 12) + dt.month := 1 + (m MOD 12) + RETURN makeFromParts(dt).plusDuration(86400*period.days + 3600*period.hours + 60*period.minutes + period.seconds) +END FUNCTION + +%| + | Function: DateTime.toString + | + | Return an ISO 8601 formatted representation of a . + |% +FUNCTION DateTime.toString(self: DateTime): String + % TODO: add formatting codes for leading zeros when implemented + RETURN "\(self.year)-\(self.month)-\(self.day)T\(self.hour):\(self.minute):\(self.second)Z" +END FUNCTION + +%| + | Function: DateTime.withDate + | + | Return a new with the given year, month, and day based on + | an existing DateTime. + |% +FUNCTION DateTime.withDate(self: DateTime, year, month, day: Number): DateTime + VAR dt: DateTime := self + dt.year := year + dt.month := month + dt.day := day + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withYear + | + | Return a new with the given year based on an existing DateTime. + |% +FUNCTION DateTime.withYear(self: DateTime, year: Number): DateTime + VAR dt: DateTime := self + dt.year := year + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withMonth + | + | Return a new with the given month based on an existing DateTime. + |% +FUNCTION DateTime.withMonth(self: DateTime, month: Number): DateTime + VAR dt: DateTime := self + dt.month := month + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withDay + | + | Return a new with the given day based on an existing DateTime. + |% +FUNCTION DateTime.withDay(self: DateTime, day: Number): DateTime + VAR dt: DateTime := self + dt.day := day + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withTime + | + | Return a new with the given hour, minute, and second based on + | an existing DateTime. + |% +FUNCTION DateTime.withTime(self: DateTime, hour, minute, second: Number): DateTime + VAR dt: DateTime := self + dt.hour := hour + dt.minute := minute + dt.second := second + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withHour + | + | Return a new with the given hour based on an existing DateTime. + |% +FUNCTION DateTime.withHour(self: DateTime, hour: Number): DateTime + VAR dt: DateTime := self + dt.hour := hour + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withMinute + | + | Return a new with the given minute based on an existing DateTime. + |% +FUNCTION DateTime.withMinute(self: DateTime, minute: Number): DateTime + VAR dt: DateTime := self + dt.minute := minute + RETURN makeFromParts(dt) +END FUNCTION + +%| + | Function: DateTime.withSecond + | + | Return a new with the given second based on an existing DateTime. + |% +FUNCTION DateTime.withSecond(self: DateTime, second: Number): DateTime + VAR dt: DateTime := self + dt.second := second + RETURN makeFromParts(dt) +END FUNCTION diff --git a/lib/debugger.cpp b/lib/debugger.cpp new file mode 100644 index 0000000000..c8bcd3fc30 --- /dev/null +++ b/lib/debugger.cpp @@ -0,0 +1,15 @@ +#include "exec.h" + +namespace rtl { + +void debugger$breakpoint() +{ + executor_breakpoint(); +} + +void debugger$log(const std::string &message) +{ + executor_log(message); +} + +} diff --git a/lib/debugger.neon b/lib/debugger.neon new file mode 100644 index 0000000000..77fee0bb3d --- /dev/null +++ b/lib/debugger.neon @@ -0,0 +1,20 @@ +%| + | File: debugger + | + | Functions for interacting with an attached debugger. + | None of the functions in this module have any effect if a debugger is not attached. + |% + +%| + | Function: breakpoint + | + | Stop the execution as if a breakpoint were reached. + |% +DECLARE NATIVE FUNCTION breakpoint() + +%| + | Function: log + | + | Display a message to the debugger output. + |% +DECLARE NATIVE FUNCTION log(message: String) diff --git a/lib/encoding.neon b/lib/encoding.neon new file mode 100644 index 0000000000..9181befafa --- /dev/null +++ b/lib/encoding.neon @@ -0,0 +1,135 @@ +%| + | File: encoding + | + | Functions for encoding and decoding ASCII or Binary data in various formats. + |% +IMPORT bitwise + +EXPORT base64Encode +EXPORT base64Decode + +TYPE State IS ENUM + byte1 + byte2 + byte3 + byte4 +END ENUM + +CONSTANT Base64EncodeTable: String := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +LET Base64DecodeTable: Array := [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0x00 - 0x0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0x10 - 0x1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, % 0x20 - 0x2f + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, % 0x30 - 0x3f + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, % 0x40 - 0x4f + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, % 0x50 - 0x5f + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, % 0x60 - 0x6f + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, % 0x70 - 0x7f + % This table simply ignores these values. + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0x80 - 0x8f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0x90 - 0x9f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xa0 - 0xaf + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xb0 - 0xbf + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xc0 - 0xcf + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xd0 - 0xdf + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xe0 - 0xef + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, % 0xf0 - 0xff +] + +%| + | Function: base64Encode + | + | Encodes the provided Bytes into Base64 encoded data. + | + | Parameters: + | data - The binary data to be encoded to Base64. + | + |% +FUNCTION base64Encode(data: Bytes): String + VAR encoded: String := "" + VAR char1, char2: Number + VAR status: State := State.byte1 + + LET source: Array := data.toArray() + + FOREACH n OF source DO + CASE status + WHEN State.byte1 DO + encoded.append(Base64EncodeTable[bitwise.shiftRight32(n, 2)]) + char1 := bitwise.and32(bitwise.shiftLeft32(n, 4), 0b00110000) + status := State.byte2 + WHEN State.byte2 DO + encoded.append(Base64EncodeTable[bitwise.or32(char1, bitwise.shiftRight32(n, 4))]) + char2 := bitwise.and32(bitwise.shiftLeft32(n, 2), 0b00111100) + status := State.byte3 + WHEN State.byte3 DO + encoded.append(Base64EncodeTable[bitwise.or32(char2, bitwise.shiftRight32(n, 6))]) + encoded.append(Base64EncodeTable[bitwise.and32(n, 0b00111111)]) + status := State.byte1 + END CASE + END FOREACH + + CASE status + % In base64, when the data being encoded is NOT a multiple of 6 bits, + % needs to be padded. The following pads the output data and signals + % we've hit the end of the encoded data when decoding. + WHEN State.byte2 DO + encoded.append(Base64EncodeTable[char1]) + encoded.append("==") + WHEN State.byte3 DO + encoded.append(Base64EncodeTable[char2]) + encoded.append("=") + END CASE + RETURN encoded +END FUNCTION + +%| + | Function: base64Decode + | + | Decodes the provided Base64 string data back into binary data. + | + | Parameters: + | encoded - The Base64 encoded data string to be decoded back into + | its original form. + | + |% +FUNCTION base64Decode(encoded: String): Bytes + VAR char1, char2, char3: Number := 0 + VAR decoded: Array := [] + VAR state: State := State.byte1 + + FOR i := 0 TO encoded.length() - 1 DO + LET char: String := encoded[i] + IF char = "=" THEN + % If encoded[i] is the ASCII "=", then we've hit the end of the ACTUAL + % base64 encoded data, so we can break out now. + EXIT FOR + END IF + + LET bits: Number := Base64DecodeTable[ord(char)] + IF bits = -1 THEN + % Eat chars that are not part of the Base64 alphabet, + % including \r and \n, and whitespace. + NEXT FOR + END IF + + CASE state + WHEN State.byte1 DO + char1 := bits + state := State.byte2 + WHEN State.byte2 DO + char2 := bits + decoded.append(bitwise.and32(bitwise.or32(bitwise.shiftLeft32(char1, 2), bitwise.shiftRight32(char2, 4)), 0xFF)) + state := State.byte3 + WHEN State.byte3 DO + char3 := bits + decoded.append(bitwise.and32(bitwise.or32(bitwise.shiftLeft32(char2, 4), bitwise.shiftRight32(char3, 2)), 0xFF)) + state := State.byte4 + WHEN State.byte4 DO + decoded.append(bitwise.and32(bitwise.or32(bitwise.shiftLeft32(char3, 6), bits), 0xFF)) + state := State.byte1 + END CASE + END FOR + RETURN decoded.toBytes() +END FUNCTION diff --git a/lib/file.cpp b/lib/file.cpp new file mode 100644 index 0000000000..e909f4cb77 --- /dev/null +++ b/lib/file.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include "rtl_exec.h" + +namespace rtl { + +std::string file$readBytes(const std::string &filename) +{ + std::string r; + std::ifstream f(filename, std::ios::binary); + if (not f.is_open()) { + throw RtlException(Exception_file$FileOpenException, filename); + } + for (;;) { + char buf[16384]; + f.read(buf, sizeof(buf)); + std::streamsize n = f.gcount(); + if (n == 0) { + break; + } + r.append(std::string(buf, n)); + } + return r; +} + +std::vector file$readLines(const std::string &filename) +{ + std::vector r; + std::ifstream f(filename); + if (not f.is_open()) { + throw RtlException(Exception_file$FileOpenException, filename); + } + std::string s; + while (std::getline(f, s)) { + r.push_back(s); + } + return r; +} + +void file$writeBytes(const std::string &filename, const std::string &data) +{ + std::ofstream f(filename, std::ios::binary); + if (not f.is_open()) { + throw RtlException(Exception_file$FileOpenException, filename); + } + if (not f.write(data.c_str(), data.length())) { + throw RtlException(Exception_file$FileWriteException, filename); + } +} + +void file$writeLines(const std::string &filename, const std::vector &lines) +{ + std::ofstream f(filename, std::ios::out | std::ios::trunc); // Truncate the file every time we open it to write lines to it. + if (not f.is_open()) { + throw RtlException(Exception_file$FileOpenException, filename); + } + for (auto s: lines) { + f << s.str() << "\n"; // Write line, and line-ending for each element in the array. + if (f.fail()) { + // If the write fails for any reason, consider that a FileWriteError exception. + throw RtlException(Exception_file$FileWriteException, filename); + } + } +} + +} // namespace rtl diff --git a/lib/file.neon b/lib/file.neon new file mode 100644 index 0000000000..1ab9c6713a --- /dev/null +++ b/lib/file.neon @@ -0,0 +1,231 @@ +%| + | File: file + | + | Functions for working with files and directories. + |% + +EXPORT DirectoryExistsException +EXPORT FileException +EXPORT FileOpenException +EXPORT FileWriteException +EXPORT PathNotFoundException +EXPORT PermissionDeniedException +EXPORT pathJoin +EXPORT pathSplit +EXPORT removeDirectoryTree + +DECLARE NATIVE CONSTANT Separator: String + +%| + | Exception: DirectoryExistsException + | + | Indicates that a directory already exists when calling . + |% +DECLARE EXCEPTION DirectoryExistsException + +%| + | Exception: FileException + | + | Generic file error. + |% +DECLARE EXCEPTION FileException + +%| + | Exception: FileOpenException + | + | Indicates that an error occurred when opening a file. + |% +DECLARE EXCEPTION FileOpenException + +%| + | Exception: FileWriteException + | + | Indicates that an error occurred while writing to a file. + |% +DECLARE EXCEPTION FileWriteException + +%| + | Exception: PathNotFoundException + | + | Indicates that a path was not found. + |% +DECLARE EXCEPTION PathNotFoundException + +%| + | Exception: PermissionDeniedException + | + | Indicates that an operation was denied due to filesystem permisssions. + |% +DECLARE EXCEPTION PermissionDeniedException + +%| + | Function: copy + | + | Copy a file. + |% +DECLARE NATIVE FUNCTION copy(filename: String, destination: String) + +%| + | Function: delete + | + | Delete a file. This function does not raise an exception + | if the file does not exist. + | + | Exceptions: + | - - if the file permissions prevent the operation + |% +DECLARE NATIVE FUNCTION delete(filename: String) + +%| + | Function: exists + | + | Check to see whether a name exists in the filesystem. + | The name may refer to either a file or a directory. + |% +DECLARE NATIVE FUNCTION exists(filename: String): Boolean + +%| + | Function: files + | + | Given a path name, return an array containing the names of the files in that directory. + |% +DECLARE NATIVE FUNCTION files(path: String): Array + +%| + | Function: isDirectory + | + | Similar to , but only returns TRUE if the path actually is a directory. + |% +DECLARE NATIVE FUNCTION isDirectory(path: String): Boolean + +%| + | Function: mkdir + | + | Create a new directory with the given name. + | + | Exceptions: + | - - if the file permissions prevent the operation + | - - if the directory already exists + | - - if the path (not including the last component) does not exist + |% +DECLARE NATIVE FUNCTION mkdir(path: String) + +%| + | Function: pathJoin + | + | Join components of a path using the characters appropriate for the OS. + |% +FUNCTION pathJoin(first, second: String): String + IF first = "" THEN + RETURN second + END IF + IF second = "" THEN + RETURN first + END IF + IF second[FIRST] IN ["/", Separator] THEN + RETURN second + END IF + IF first[LAST] IN ["/", Separator] THEN + RETURN first & second + ELSE + RETURN first & Separator & second + END IF +END FUNCTION + +%| + | Function: pathSplit + | + | Split a path into directory and name parts. + |% +FUNCTION pathSplit(path: String, OUT directory, name: String) + FOR i := path.length() - 1 TO 0 STEP -1 DO + IF path[i] IN ["/", Separator] THEN + directory := path[FIRST TO i-1] + name := path[i+1 TO LAST] + EXIT FUNCTION + END IF + END FOR + directory := "" + name := path +END FUNCTION + +%| + | Function: readBytes + | + | Read the contents of a file into . + | + | Exceptions: + | - - if the file cannot be opened + |% +DECLARE NATIVE FUNCTION readBytes(filename: String): Bytes + +%| + | Function: readLines + | + | Read the lines of a file into an array of . + | + | Exceptions: + | - - if the file cannot be opened + |% +DECLARE NATIVE FUNCTION readLines(filename: String): Array + +%| + | Function: removeDirectoryTree + | + | Remove directory tree recursively. + |% +FUNCTION removeDirectoryTree(path: String) + LET names: Array := files(path) + FOREACH name OF names DO + IF name IN [".", ".."] THEN + NEXT FOREACH + END IF + LET pathname: String := pathJoin(path, name) + IF isDirectory(name) THEN + removeDirectoryTree(pathname) + ELSE + delete(pathname) + END IF + END FOREACH + removeEmptyDirectory(path) +END FUNCTION + +%| + | Function: removeEmptyDirectory + | + | Remove an empty directory. + |% +DECLARE NATIVE FUNCTION removeEmptyDirectory(path: String) + +%| + | Function: rename + | + | Rename a file. This function can also be used to move a file from + | one directory to another. + | + | Exceptions: + | - - if the file does not exist + |% +DECLARE NATIVE FUNCTION rename(oldname: String, newname: String) + +%| + | Function: writeBytes + | + | Write a complete file from data in . + | + | Exceptions: + | - - if the file cannot be opened + | - - if an error occurs during writing + |% +DECLARE NATIVE FUNCTION writeBytes(filename: String, data: Bytes) + +%| + | Function: writeLines + | + | Write a complete file from lines of text in an array. + | + | Exceptions: + | - - if the file cannot be opened + | - - if an error occurs during writing + |% +DECLARE NATIVE FUNCTION writeLines(filename: String, data: Array) diff --git a/lib/file_const_posix.cpp b/lib/file_const_posix.cpp new file mode 100644 index 0000000000..de03015046 --- /dev/null +++ b/lib/file_const_posix.cpp @@ -0,0 +1,7 @@ +#include + +namespace rtl { + +extern const std::string file$Separator = "/"; + +} // namespace rtl diff --git a/lib/file_const_win32.cpp b/lib/file_const_win32.cpp new file mode 100644 index 0000000000..e922284d87 --- /dev/null +++ b/lib/file_const_win32.cpp @@ -0,0 +1,7 @@ +#include + +namespace rtl { + +extern const std::string file$Separator = "\\"; + +} // namespace rtl diff --git a/lib/file_posix.cpp b/lib/file_posix.cpp new file mode 100644 index 0000000000..6c43030c45 --- /dev/null +++ b/lib/file_posix.cpp @@ -0,0 +1,147 @@ +#ifdef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl_exec.h" + +static void handle_error(int error, const std::string &path) +{ + switch (error) { + case EACCES: throw RtlException(Exception_file$PermissionDeniedException, path); + case EEXIST: throw RtlException(Exception_file$DirectoryExistsException, path); + case ENOENT: throw RtlException(Exception_file$PathNotFoundException, path); + default: + throw RtlException(Exception_file$FileException, path + ": " + strerror(error)); + } +} + +namespace rtl { + +void file$copy(const std::string &filename, const std::string &destination) +{ +#ifdef __APPLE__ + int r = copyfile(filename.c_str(), destination.c_str(), NULL, COPYFILE_ALL); + if (r != 0) { + handle_error(errno, filename); + } +#else + int sourcefd = open(filename.c_str(), O_RDONLY); + if (sourcefd < 0) { + handle_error(errno, filename); + } + struct stat statbuf; + int r = fstat(sourcefd, &statbuf); + if (r != 0) { + int error = errno; + close(sourcefd); + handle_error(error, filename); + } + int destfd = open(destination.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0); + if (destfd < 0) { + int error = errno; + close(sourcefd); + handle_error(error, destination); + } + char buf[BUFSIZ]; + for (;;) { + ssize_t n = read(sourcefd, buf, sizeof(buf)); + if (n < 0) { + int error = errno; + close(sourcefd); + close(destfd); + unlink(destination.c_str()); + handle_error(error, filename); + } else if (n == 0) { + break; + } + ssize_t written = write(destfd, buf, n); + if (written < n) { + int error = errno; + close(sourcefd); + close(destfd); + unlink(destination.c_str()); + handle_error(error, destination); + } + } + close(sourcefd); + fchmod(destfd, statbuf.st_mode); + fchown(destfd, statbuf.st_uid, statbuf.st_gid); + close(destfd); + struct utimbuf utimebuf; + utimebuf.actime = statbuf.st_atime; + utimebuf.modtime = statbuf.st_mtime; + utime(destination.c_str(), &utimebuf); +#endif +} + +void file$delete(const std::string &filename) +{ + int r = unlink(filename.c_str()); + if (r != 0) { + if (errno != ENOENT) { + handle_error(errno, filename); + } + } +} + +bool file$exists(const std::string &filename) +{ + return access(filename.c_str(), F_OK) == 0; +} + +std::vector file$files(const std::string &path) +{ + std::vector r; + DIR *d = opendir(path.c_str()); + if (d != NULL) { + for (;;) { + struct dirent *de = readdir(d); + if (de == NULL) { + break; + } + r.push_back(de->d_name); + } + closedir(d); + } + return r; +} + +bool file$isDirectory(const std::string &path) +{ + struct stat st; + return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFDIR) != 0; +} + +void file$mkdir(const std::string &path) +{ + int r = mkdir(path.c_str(), 0755); + if (r != 0) { + handle_error(errno, path); + } +} + +void file$removeEmptyDirectory(const std::string &path) +{ + int r = rmdir(path.c_str()); + if (r != 0) { + handle_error(errno, path); + } +} + +void file$rename(const std::string &oldname, const std::string &newname) +{ + int r = rename(oldname.c_str(), newname.c_str()); + if (r != 0) { + handle_error(errno, oldname); + } +} + +} // namespace rtl diff --git a/lib/file_win32.cpp b/lib/file_win32.cpp new file mode 100644 index 0000000000..544c11533a --- /dev/null +++ b/lib/file_win32.cpp @@ -0,0 +1,88 @@ +#include +#include +#include + +#include "rtl_exec.h" + +static void handle_error(DWORD error, const std::string &path) +{ + switch (error) { + case ERROR_ALREADY_EXISTS: throw RtlException(Exception_file$DirectoryExistsException, path); + case ERROR_ACCESS_DENIED: throw RtlException(Exception_file$PermissionDeniedException, path); + case ERROR_PATH_NOT_FOUND: throw RtlException(Exception_file$PathNotFoundException, path); + default: + throw RtlException(Exception_file$FileException, path + ": " + std::to_string(error)); + } +} + +namespace rtl { + +void file$copy(const std::string &filename, const std::string &destination) +{ + BOOL r = CopyFile(filename.c_str(), destination.c_str(), FALSE); + if (!r) { + handle_error(GetLastError(), filename); + } +} + +void file$delete(const std::string &filename) +{ + BOOL r = DeleteFile(filename.c_str()); + if (!r) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + handle_error(GetLastError(), filename); + } + } +} + +bool file$exists(const std::string &filename) +{ + return _access(filename.c_str(), 0) == 0; +} + +std::vector file$files(const std::string &path) +{ + std::vector r; + WIN32_FIND_DATA fd; + HANDLE ff = FindFirstFile((path + "\\*").c_str(), &fd); + if (ff != INVALID_HANDLE_VALUE) { + do { + r.push_back(fd.cFileName); + } while (FindNextFile(ff, &fd)); + FindClose(ff); + } + return r; +} + +bool file$isDirectory(const std::string &path) +{ + DWORD attr = GetFileAttributes(path.c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +void file$mkdir(const std::string &path) +{ + // TODO: Convert UTF-8 path to UCS-16 and call CreateDirectoryW. + BOOL r = CreateDirectoryA(path.c_str(), NULL); + if (!r) { + handle_error(GetLastError(), path); + } +} + +void file$removeEmptyDirectory(const std::string &path) +{ + BOOL r = RemoveDirectory(path.c_str()); + if (!r) { + handle_error(GetLastError(), path); + } +} + +void file$rename(const std::string &oldname, const std::string &newname) +{ + BOOL r = MoveFile(oldname.c_str(), newname.c_str()); + if (!r) { + handle_error(GetLastError(), oldname); + } +} + +} // namespace rtl diff --git a/lib/global.cpp b/lib/global.cpp index 2c01449cae..914256c6be 100644 --- a/lib/global.cpp +++ b/lib/global.cpp @@ -1,136 +1,304 @@ #include +#include #include #include #include "cell.h" +#include "format.h" #include "number.h" +#include "rtl_exec.h" namespace rtl { -namespace array { +void global$array__append(Cell *self, Cell &element) +{ + self->array_for_write().push_back(element); +} -Number size(Cell *self) +Cell global$array__concat(Cell &left, Cell &right) { - return number_from_uint64(self->array().size()); + std::vector a = left.array(); + const std::vector &b = right.array(); + std::copy(b.begin(), b.end(), std::back_inserter(a)); + return Cell(a); } -} // namespace array +void global$array__extend(Cell *self, Cell &elements) +{ + std::copy(elements.array().begin(), elements.array().end(), std::back_inserter(self->array_for_write())); +} -namespace boolean { +std::vector global$array__range(Number first, Number last, Number step) +{ + std::vector r; + if (number_is_zero(step)) { + throw RtlException(Exception_global$ValueRangeException, number_to_string(step)); + } + if (number_is_negative(step)) { + for (Number i = first; number_is_greater_equal(i, last); i = number_add(i, step)) { + r.push_back(i); + } + } else { + for (Number i = first; number_is_less_equal(i, last); i = number_add(i, step)) { + r.push_back(i); + } + } + return r; +} -std::string to_string(bool *self) +void global$array__resize(Cell *self, Number new_size) { - return *self ? "TRUE" : "FALSE"; + if (not number_is_integer(new_size)) { + throw RtlException(Exception_global$ArrayIndexException, number_to_string(new_size)); + } + self->array_for_write().resize(number_to_sint64(new_size)); } -} // namespace boolean +Number global$array__size(Cell &self) +{ + return number_from_uint64(self.array().size()); +} -namespace number { +Cell global$array__slice(Cell &a, Number first, bool first_from_end, Number last, bool last_from_end) +{ + const std::vector &array = a.array(); + int64_t fst = number_to_sint64(first); + int64_t lst = number_to_sint64(last); + if (first_from_end) { + fst += array.size() - 1; + } + if (fst < 0) fst = 0; + if (fst > static_cast(array.size())) fst = array.size(); + if (last_from_end) { + lst += array.size() - 1; + } + if (lst < -1) lst = -1; + if (lst >= static_cast(array.size())) lst = array.size() - 1; + std::vector r; + for (auto i = fst; i <= lst; i++) { + r.push_back(array[i]); + } + return Cell(r); +} -std::string to_string(Number *self) +Cell global$array__splice(Cell &b, Cell &a, Number first, bool first_from_end, Number last, bool last_from_end) { - return number_to_string(*self); + const std::vector &array = a.array(); + int64_t fst = number_to_sint64(first); + int64_t lst = number_to_sint64(last); + if (first_from_end) { + fst += array.size() - 1; + } + if (fst < 0) fst = 0; + if (fst > static_cast(array.size())) fst = array.size(); + if (last_from_end) { + lst += array.size() - 1; + } + if (lst < -1) lst = -1; + if (lst >= static_cast(array.size())) lst = array.size() - 1; + std::vector r; + for (auto i = 0; i < fst; i++) { + r.push_back(array[i]); + } + std::copy(b.array().begin(), b.array().end(), std::back_inserter(r)); + for (auto i = lst + 1; i < static_cast(array.size()); i++) { + r.push_back(array[i]); + } + return Cell(r); } -} // namespace number +std::string global$array__toBytes__number(const std::vector &a) +{ + std::string r; + r.reserve(a.size()); + for (auto x: a) { + uint64_t b = number_to_uint64(x); + if (b >= 256) { + throw RtlException(Exception_global$ByteOutOfRangeException, std::to_string(b)); + } + r.push_back(static_cast(b)); + } + return r; +} -namespace string { +std::string global$array__toString__number(const std::vector &a) +{ + std::string r = "["; + for (Number x: a) { + if (r.length() > 1) { + r.append(", "); + } + r.append(number_to_string(x)); + } + r.append("]"); + return r; +} -Number length(std::string *self) +std::string global$array__toString__string(const std::vector &a) { - return number_from_uint64(self->length()); + std::string r = "["; + for (utf8string x: a) { + if (r.length() > 1) { + r.append(", "); + } + // TODO: Escape embedded quotes as necessary. + r.append("\"" + x.str() + "\""); + } + r.append("]"); + return r; } -} // namespace string +std::string global$boolean__toString(bool self) +{ + return self ? "TRUE" : "FALSE"; +} -Number abs(Number x) +Number global$dictionary__size(Cell &self) { - return number_abs(x); + return number_from_uint64(self.dictionary().size()); } -std::string chr(Number x) +std::vector global$dictionary__keys(Cell &self) { - assert(number_is_integer(x)); - std::string r; - utf8::append(number_to_uint32(x), std::back_inserter(r)); + std::vector r; + for (auto d: self.dictionary()) { + r.push_back(d.first); + } return r; } -std::string concat(const std::string &a, const std::string &b) +std::string global$number__toString(Number self) { - return a + b; + return number_to_string(self); } -std::string input(const std::string &prompt) +void global$string__append(utf8string *self, const std::string &t) { - std::cout << prompt; - std::string r; - std::getline(std::cin, r); - return r; + self->append(t); } -Number max(Number a, Number b) +Number global$string__length(const std::string &self) { - if (number_is_greater(a, b)) { - return a; - } else { - return b; + return number_from_uint64(self.length()); +} + +std::string global$string__splice(const std::string &t, const std::string &s, Number first, bool first_from_end, Number last, bool last_from_end) +{ + // TODO: utf8 + int64_t f = number_to_sint64(first); + int64_t l = number_to_sint64(last); + if (first_from_end) { + f += s.size() - 1; } + if (last_from_end) { + l += s.size() - 1; + } + return s.substr(0, f) + t + s.substr(l + 1); } -Number min(Number a, Number b) +std::string global$string__substring(const std::string &t, Number first, bool first_from_end, Number last, bool last_from_end) { - if (number_is_less(a, b)) { - return a; - } else { - return b; + const utf8string &s = reinterpret_cast(t); + assert(number_is_integer(first)); + assert(number_is_integer(last)); + int64_t f = number_to_sint64(first); + int64_t l = number_to_sint64(last); + if (first_from_end) { + f += s.size() - 1; } + if (last_from_end) { + l += s.size() - 1; + } + size_t start = s.index(f); + size_t end = s.index(l + 1); + return s.substr(start, end-start); } -Number num(const std::string &s) +std::string global$string__toBytes(const std::string &self) { - return number_from_string(s); + return self; } -Number ord(const std::string &s) +std::string global$bytes__range(const std::string &t, Number first, bool first_from_end, Number last, bool last_from_end) { - assert(utf8::distance(s.begin(), s.end()) == 1); - auto it = s.begin(); - return number_from_uint32(utf8::next(it, s.end())); + assert(number_is_integer(first)); + assert(number_is_integer(last)); + int64_t f = number_to_sint64(first); + int64_t l = number_to_sint64(last); + if (first_from_end) { + f += t.size() - 1; + } + if (last_from_end) { + l += t.size() - 1; + } + return t.substr(f, l + 1 - f); } -void print(const std::string &s) +Number global$bytes__size(const std::string &self) { - std::cout << s << "\n"; + return number_from_uint64(self.length()); } -std::string splice(const std::string &t, const std::string &s, Number offset, Number length) +std::string global$bytes__splice(const std::string &t, const std::string &s, Number first, bool first_from_end, Number last, bool last_from_end) { - // TODO: utf8 - uint32_t o = number_to_uint32(offset); - return s.substr(0, o) + t + s.substr(o + number_to_uint32(length)); + int64_t f = number_to_sint64(first); + int64_t l = number_to_sint64(last); + if (first_from_end) { + f += s.size() - 1; + } + if (last_from_end) { + l += s.size() - 1; + } + return s.substr(0, f) + t + s.substr(l + 1); +} + +std::vector global$bytes__toArray(const std::string &self) +{ + std::vector r; + for (auto x: self) { + r.push_back(number_from_uint8(x)); + } + return r; +} + +std::string global$bytes__toString(const std::string &self) +{ + auto inv = utf8::find_invalid(self.begin(), self.end()); + if (inv != self.end()) { + throw RtlException(Exception_global$Utf8EncodingException, ""); + } + return self; } -std::string str(Number x) +std::string global$pointer__toString(void *p) { - return number_to_string(x); + return "(p)) + ">"; } -std::string strb(bool x) +std::string global$functionpointer__toString(Cell &p) { - return x ? "TRUE" : "FALSE"; + return ""; } -std::string substring(const std::string &s, Number offset, Number length) +std::string global$concatBytes(const std::string &a, const std::string &b) { - assert(number_is_integer(offset)); - assert(number_is_integer(length)); - auto start = s.begin(); - utf8::advance(start, number_to_uint32(offset), s.end()); - auto end = start; - utf8::advance(end, number_to_uint32(length), s.end()); - return std::string(start, end); + return a + b; +} + +std::string global$input(const std::string &prompt) +{ + std::cout << prompt; + std::string r; + if (not std::getline(std::cin, r)) { + throw RtlException(Exception_global$EndOfFileException, ""); + } + return r; +} + +void global$print(const std::string &s) +{ + std::cout << s << "\n"; } } // namespace rtl diff --git a/lib/global.neon b/lib/global.neon new file mode 100644 index 0000000000..f9eddda7a4 --- /dev/null +++ b/lib/global.neon @@ -0,0 +1,273 @@ +%| + | File: global + | + | Global names that are available everywhere. + |% + +%| + | Type: Boolean + | + | TRUE or FALSE. + |% + +%| + | Type: Number + | + | Floating point number with 34 decimal digits of precision. + |% + +%| + | Type: String + | + | Unicode string. + |% + +%| + | Type: Array + | + | Parameterised sequence type. Example + | + | > Array + |% + +%| + | Type: Dictionary + | + | Parameterised map type from to the given type. Example + | + | > Dictionary + |% + +%| + | Type: Bytes + | + | Bytes. + |% + +%| + | Exception: ArrayIndexException + | + | An array index is not valid or out of range. + |% +DECLARE EXCEPTION ArrayIndexException + +%| + | Exception: AssertFailedException + | + | Assert failed. + |% +DECLARE EXCEPTION AssertFailedException + +%| + | Exception: ByteOutOfRangeException + | + | A byte is out of range in toBytes(). + |% +DECLARE EXCEPTION ByteOutOfRangeException + +%| + | Exception: DictionaryIndexException + | + | A dictionary index value does not exist. + |% +DECLARE EXCEPTION DictionaryIndexException + +%| + | Exception: DivideByZeroException + | + | Divide by zero. + |% +DECLARE EXCEPTION DivideByZeroException + +%| + | Exception: EndOfFileException + | + | End of input file. + |% +DECLARE EXCEPTION EndOfFileException + +%| + | Exception: FormatException + | + | Format specifier not valid. + |% +DECLARE EXCEPTION FormatException + +%| + | Exception: InvalidFunctionException + | + | An invalid function pointer was called. + |% +DECLARE EXCEPTION InvalidFunctionException + +%| + | Exception: InvalidValueException + | + | An invalid value was passed to a library function. + |% +DECLARE EXCEPTION InvalidValueException + +%| + | Exception: FunctionNotFoundException + | + | Function not found. + |% +DECLARE EXCEPTION FunctionNotFoundException + +%| + | Exception: LibraryNotFoundException + | + | Library not found. + |% +DECLARE EXCEPTION LibraryNotFoundException + +%| + | Exception: StackOverflowException + | + | Call stack exceeded limit. + |% +DECLARE EXCEPTION StackOverflowException + +%| + | Exception: Utf8EncodingException + | + | Invalid UTF-8 encoding in Bytes.toString(). + |% +DECLARE EXCEPTION Utf8EncodingException + +%| + | Exception: ValueRangeException + | + | An input value is out of range. + |% +DECLARE EXCEPTION ValueRangeException + +DECLARE NATIVE FUNCTION array__append(INOUT self: Array, element: Cell) +DECLARE NATIVE FUNCTION array__concat(left: Array, right: Array): Array +DECLARE NATIVE FUNCTION array__extend(INOUT self: Array, elements: Array) +DECLARE NATIVE FUNCTION array__range(first: Number, last: Number, step: Number): Array +DECLARE NATIVE FUNCTION array__resize(INOUT self: Array, new_size: Number) +DECLARE NATIVE FUNCTION array__size(self: Array): Number +DECLARE NATIVE FUNCTION array__slice(a: Array, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): Array +DECLARE NATIVE FUNCTION array__splice(b: Array, a: Array, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): Array +DECLARE NATIVE FUNCTION array__toBytes__number(self: Array): Bytes +DECLARE NATIVE FUNCTION array__toString__number(self: Array): String +DECLARE NATIVE FUNCTION array__toString__string(self: Array): String +DECLARE NATIVE FUNCTION boolean__toString(self: Boolean): String +DECLARE NATIVE FUNCTION dictionary__size(self: Dictionary): Number +DECLARE NATIVE FUNCTION dictionary__keys(self: Dictionary): Array +DECLARE NATIVE FUNCTION number__toString(self: Number): String +DECLARE NATIVE FUNCTION string__append(INOUT self: String, t: String) +DECLARE NATIVE FUNCTION string__length(self: String): Number +DECLARE NATIVE FUNCTION string__splice(t: String, s: String, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): String +DECLARE NATIVE FUNCTION string__substring(s: String, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): String +DECLARE NATIVE FUNCTION string__toBytes(self: String): Bytes +DECLARE NATIVE FUNCTION bytes__range(s: String, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): Bytes +DECLARE NATIVE FUNCTION bytes__size(self: Bytes): Number +DECLARE NATIVE FUNCTION bytes__splice(t: String, s: String, first: Number, first_from_end: Boolean, last: Number, last_from_end: Boolean): Bytes +DECLARE NATIVE FUNCTION bytes__toArray(self: Bytes): Array +DECLARE NATIVE FUNCTION bytes__toString(self: Bytes): String +DECLARE NATIVE FUNCTION pointer__toString(self: POINTER): String +DECLARE NATIVE FUNCTION functionpointer__toString(self: Cell): String + +%| + | Function: chr + | + | Convert a number to the corresponding Unicode character. + |% +DECLARE NATIVE FUNCTION chr(x: Number): String + +%| + | Function: concat + | + | Concatenate two strings. Same as the & operator. + |% +DECLARE NATIVE FUNCTION concat(a: String, b: String): String + +%| + | Function: concatBytes + | + | Concatenate two bytes. Same as the & operator for . + |% +DECLARE NATIVE FUNCTION concatBytes(a: Bytes, b: Bytes): Bytes + +%| + | Function: format + | + | Format a string based on the formatting mini-language. + | + | TODO + |% +DECLARE NATIVE FUNCTION format(str: String, fmt: String): String + +%| + | Function: input + | + | Read a line of input from the terminal after printing a prompt. + |% +DECLARE NATIVE FUNCTION input(prompt: String): String + +%| + | Function: int + | + | Truncate a floating point number to the nearest integer whose + | absolute value is closest to zero. + |% +DECLARE NATIVE FUNCTION int(a: Number): Number + +%| + | Function: max + | + | Return the greater of two numbers. + |% +DECLARE NATIVE FUNCTION max(a: Number, b: Number): Number + +%| + | Function: min + | + | Return the lesser of two numbers. + |% +DECLARE NATIVE FUNCTION min(a: Number, b: Number): Number + +%| + | Function: num + | + | Convert a decimal number as a string to a number. + |% +DECLARE NATIVE FUNCTION num(s: String): Number + +%| + | Function: ord + | + | Return the Unicode value of a given character. + | The input string must be a single character. + |% +DECLARE NATIVE FUNCTION ord(s: String): Number + +%| + | Function: print + | + | Print a string to standard output followed by a newline. + |% +DECLARE NATIVE FUNCTION print(s: String) + +%| + | Function: str + | + | Convert a number to a decimal string. + |% +DECLARE NATIVE FUNCTION str(x: Number): String + +%| + | Function: strb + | + | Convert a boolean to a string, either TRUE or FALSE. + |% +DECLARE NATIVE FUNCTION strb(x: Boolean): String + +%| + | Function: substring + | + | Return a range of a string with the given offset and length. + |% +DECLARE NATIVE FUNCTION substring(s: String, offset: Number, length: Number): String diff --git a/lib/hash.cpp b/lib/hash.cpp new file mode 100644 index 0000000000..f55a780312 --- /dev/null +++ b/lib/hash.cpp @@ -0,0 +1,150 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "crc32.h" +#include "md5.h" +#include "sha1.h" +#include "sha256.h" +#include "sha3.h" + +#include "rtl_exec.h" + +inline char hex_digit(unsigned d) +{ + return static_cast(d < 10 ? '0' + d : 'a' + (d - 10)); +} + +static std::string to_hex(const std::string &hash) +{ + std::string r(2 * hash.length(), 'x'); + for (std::string::size_type i = 0; i < hash.length(); i++) { + unsigned char b = static_cast(hash[i]); + r[2*i] = hex_digit(b >> 4); + r[2*i+1] = hex_digit(b & 0xf); + } + return r; +} + +static std::string to_binary(const std::string &str) +{ + std::string r; + r.reserve(str.length()/2); + for (std::string::size_type i = 0; i < str.length(); i += 2) { + int c; + sscanf(&str[i], "%2x", &c); + r.push_back(static_cast(c)); + } + return r; +} + +namespace rtl { + +Number hash$crc32(const std::string &bytes) +{ + CRC32 crc32; + crc32(bytes.data(), bytes.length()); + unsigned char buf[CRC32::HashBytes]; + crc32.getHash(buf); + return number_from_uint32(0); +} + +Number hash$crc32Bytes(const std::string &bytes) +{ + return hash$crc32(bytes); +} + +std::string hash$md5Raw(const std::string &bytes) +{ + MD5 md5; + md5(bytes.data(), bytes.length()); + unsigned char buf[MD5::HashBytes]; + md5.getHash(buf); + return std::string(reinterpret_cast(buf), sizeof(buf)); +} + +std::string hash$md5(const std::string &bytes) +{ + return to_hex(hash$md5Raw(bytes)); +} + +std::string hash$md5Bytes(const std::string &bytes) +{ + return hash$md5(bytes); +} + +std::string hash$md5BytesRaw(const std::string &bytes) +{ + return hash$md5Raw(bytes); +} + +std::string hash$sha1Raw(const std::string &bytes) +{ + SHA1 sha1; + sha1(bytes.data(), bytes.length()); + unsigned char buf[SHA1::HashBytes]; + sha1.getHash(buf); + return std::string(reinterpret_cast(buf), sizeof(buf)); +} + +std::string hash$sha1(const std::string &bytes) +{ + return to_hex(hash$sha1Raw(bytes)); +} + +std::string hash$sha1Bytes(const std::string &bytes) +{ + return hash$sha1(bytes); +} + +std::string hash$sha1BytesRaw(const std::string &bytes) +{ + return hash$sha1Raw(bytes); +} + +std::string hash$sha256Raw(const std::string &bytes) +{ + SHA256 sha256; + sha256(bytes.data(), bytes.length()); + unsigned char buf[SHA256::HashBytes]; + sha256.getHash(buf); + return std::string(reinterpret_cast(buf), sizeof(buf)); +} + +std::string hash$sha256(const std::string &bytes) +{ + return to_hex(hash$sha256Raw(bytes)); +} + +std::string hash$sha256Bytes(const std::string &bytes) +{ + return hash$sha256(bytes); +} + +std::string hash$sha256BytesRaw(const std::string &bytes) +{ + return hash$sha256Raw(bytes); +} + +std::string hash$sha3(const std::string &bytes) +{ + SHA3 sha3; + return sha3(bytes.data(), bytes.length()); +} + +std::string hash$sha3Raw(const std::string &bytes) +{ + return to_binary(hash$sha3(bytes)); +} + +std::string hash$sha3Bytes(const std::string &bytes) +{ + return hash$sha3(bytes); +} + +std::string hash$sha3BytesRaw(const std::string &bytes) +{ + return hash$sha3Raw(bytes); +} + +} diff --git a/lib/hash.neon b/lib/hash.neon new file mode 100644 index 0000000000..534f53d230 --- /dev/null +++ b/lib/hash.neon @@ -0,0 +1,131 @@ +%| + | File: hash + | + | Functions for hashing data using a variety of algorithms. + |% + +%| + | Function: crc32 + | + | Return the CRC32 of a given . + |% +DECLARE NATIVE FUNCTION crc32(data: String): Number + +%| + | Function: crc32Bytes + | + | Return the CRC32 of given . + |% +DECLARE NATIVE FUNCTION crc32Bytes(data: Bytes): Number + +%| + | Function: md5 + | + | Return the MD5 hash of a given as a hex string. + |% +DECLARE NATIVE FUNCTION md5(data: String): String + +%| + | Function: md5Raw + | + | Return the MD5 hash of a given as raw . + |% +DECLARE NATIVE FUNCTION md5Raw(data: String): Bytes + +%| + | Function: md5Bytes + | + | Return the MD5 hash of given as a hex string. + |% +DECLARE NATIVE FUNCTION md5Bytes(data: Bytes): String + +%| + | Function: md5BytesRaw + | + | Return the MD5 hash of given as a raw . + |% +DECLARE NATIVE FUNCTION md5BytesRaw(data: Bytes): Bytes + +%| + | Function: sha1 + | + | Return the SHA1 hash of a given as a hex string. + |% +DECLARE NATIVE FUNCTION sha1(data: String): String + +%| + | Function: sha1Raw + | + | Return the SHA1 hash of a given as raw . + |% +DECLARE NATIVE FUNCTION sha1Raw(data: String): Bytes + +%| + | Function: sha1Bytes + | + | Return the SHA1 hash of given as a hex string. + |% +DECLARE NATIVE FUNCTION sha1Bytes(data: Bytes): String + +%| + | Function: sha1BytesRaw + | + | Return the SHA1 hash of given as a raw . + |% +DECLARE NATIVE FUNCTION sha1BytesRaw(data: Bytes): Bytes + +%| + | Function: sha256 + | + | Return the SHA256 hash of a given as a hex string. + |% +DECLARE NATIVE FUNCTION sha256(data: String): String + +%| + | Function: sha256Raw + | + | Return the SHA256 hash of a given as raw . + |% +DECLARE NATIVE FUNCTION sha256Raw(data: String): Bytes + +%| + | Function: sha256Bytes + | + | Return the SHA256 hash of given as a hex string. + |% +DECLARE NATIVE FUNCTION sha256Bytes(data: Bytes): String + +%| + | Function: sha256BytesRaw + | + | Return the SHA256 hash of given as a raw . + |% +DECLARE NATIVE FUNCTION sha256BytesRaw(data: Bytes): Bytes + +%| + | Function: sha3 + | + | Return the SHA3 hash of a given as a hex string. + |% +DECLARE NATIVE FUNCTION sha3(data: String): String + +%| + | Function: sha3Raw + | + | Return the SHA3 hash of a given as raw . + |% +DECLARE NATIVE FUNCTION sha3Raw(data: String): Bytes + +%| + | Function: sha3Bytes + | + | Return the SHA3 hash of given as a hex string. + |% +DECLARE NATIVE FUNCTION sha3Bytes(data: Bytes): String + +%| + | Function: sha3BytesRaw + | + | Return the SHA3 hash of given as a raw . + |% +DECLARE NATIVE FUNCTION sha3BytesRaw(data: Bytes): Bytes diff --git a/lib/html/.gitignore b/lib/html/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/lib/html/.gitignore @@ -0,0 +1 @@ +* diff --git a/lib/http.cpp b/lib/http.cpp new file mode 100644 index 0000000000..7f1eb879fc --- /dev/null +++ b/lib/http.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include + +#include "rtl_exec.h" +#include "utf8string.h" + +static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + std::vector *headers = reinterpret_cast *>(userdata); + std::string s(ptr, size*nmemb); + auto i = s.find_last_not_of("\r\n"); + headers->push_back(s.substr(0, i+1)); + return size*nmemb; +} + +static size_t data_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + std::string *data = reinterpret_cast(userdata); + data->append(std::string(ptr, size*nmemb)); + return size*nmemb; +} + +namespace rtl { + +std::string http$get(const std::string &url, std::vector *headers) +{ + std::string data; + headers->clear(); + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Neon/0.1"); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + char error[CURL_ERROR_SIZE]; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error); + CURLcode r = curl_easy_perform(curl); + if (r == CURLE_OK) { + long rc; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc); + //printf("rc %ld\n", rc); + } else { + curl_easy_cleanup(curl); + //fprintf(stderr, "curl %d error %s\n", r, error); + throw RtlException(Exception_http$HttpException, error, r); + } + curl_easy_cleanup(curl); + return data; +} + +std::string http$post(const std::string &url, const std::string &post_data, std::vector *headers) +{ + std::string data; + headers->clear(); + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "Neon/0.1"); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_data.length()); + char error[CURL_ERROR_SIZE]; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error); + CURLcode r = curl_easy_perform(curl); + if (r == CURLE_OK) { + long rc; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc); + //printf("rc %ld\n", rc); + } else { + curl_easy_cleanup(curl); + //fprintf(stderr, "curl %d error %s\n", r, error); + throw RtlException(Exception_http$HttpException, error, r); + } + curl_easy_cleanup(curl); + return data; +} + +} diff --git a/lib/http.neon b/lib/http.neon new file mode 100644 index 0000000000..c1708dc010 --- /dev/null +++ b/lib/http.neon @@ -0,0 +1,37 @@ +%| + | File: http + | + | Functions for making HTTP requests. + |% + +EXPORT HttpException + +%| + | Exception: HttpException + | + | Raised when an HTTP request returns anything other than 200 OK. + |% +DECLARE EXCEPTION HttpException + +%| + | Function: get + | + | Perform an HTTP GET operation with the specified url. + | + | Parameters: + | url - the url + | headers - the response headers from the server + |% +DECLARE NATIVE FUNCTION get(url: String, OUT headers: Array): Bytes + +%| + | Function: post + | + | Perform an HTTP POST operation with the specified url and post data. + | + | Parameters: + | url - the url + | post_data - the body of the POST request + | headers - the response headers from the server + |% +DECLARE NATIVE FUNCTION post(url: String, post_data: String, OUT headers: Array): Bytes diff --git a/lib/io.cpp b/lib/io.cpp new file mode 100644 index 0000000000..e025b4710d --- /dev/null +++ b/lib/io.cpp @@ -0,0 +1,131 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "cell.h" +#include "number.h" +#include "rtl_exec.h" + +#include "enums.inc" + +static FILE *check_file(void *pf) +{ + FILE *f = static_cast(pf); + if (f == NULL) { + throw RtlException(Exception_io$InvalidFileException, ""); + } + return f; +} + +namespace rtl { + +Cell io$stdin(reinterpret_cast(stdin)); +Cell io$stdout(reinterpret_cast(stdout)); +Cell io$stderr(reinterpret_cast(stderr)); + +void io$close(Cell **ppf) +{ + FILE *f = check_file(*ppf); + fclose(f); + *ppf = NULL; +} + +void io$fprint(void *pf, const std::string &s) +{ + FILE *f = check_file(pf); + fputs(s.c_str(), f); + fputs("\n", f); +} + +void *io$open(const std::string &name, Cell &mode) +{ + const char *m; + switch (number_to_uint32(mode.number())) { + case ENUM_Mode_read: m = "r"; break; + case ENUM_Mode_write: m = "w+"; break; + default: + return NULL; + } + return fopen(name.c_str(), m); +} + +std::string io$readBytes(void *pf, Number count) +{ + FILE *f = check_file(pf); + uint64_t ncount = number_to_uint64(count); + std::string r(ncount, 0); + size_t n = fread(const_cast(r.data()), 1, ncount, f); + r.resize(n); + return r; +} + +bool io$readLine(void *pf, utf8string *s) +{ + FILE *f = check_file(pf); + s->clear(); + for (;;) { + char buf[1024]; + if (fgets(buf, sizeof(buf), f) == NULL) { + return not s->empty(); + } + s->append(buf); + if (s->at(s->length()-1) == '\n') { + s->resize(s->length()-1); + return true; + } + } +} + +void io$seek(void *pf, Number offset, Cell &whence) +{ + FILE *f = check_file(pf); + int w; + switch (number_to_uint32(whence.number())) { + case ENUM_SeekBase_absolute: w = SEEK_SET; break; + case ENUM_SeekBase_relative: w = SEEK_CUR; break; + case ENUM_SeekBase_fromEnd: w = SEEK_END; break; + default: + return; + } + fseek(f, static_cast(number_to_sint64(offset)), w); +} + +Number io$tell(void *pf) +{ + FILE *f = check_file(pf); + return number_from_sint64(ftell(f)); +} + +void io$truncate(void *pf) +{ + FILE *f = check_file(pf); + long ofs = ftell(f); + #ifdef _WIN32 + _chsize(_fileno(f), ofs); + #else + ftruncate(fileno(f), ofs); + #endif +} + +void io$write(void *pf, const std::string &s) +{ + FILE *f = check_file(pf); + fputs(s.c_str(), f); +} + +void io$writeBytes(void *pf, const std::string &b) +{ + FILE *f = check_file(pf); + fwrite(b.data(), 1, b.size(), f); +} + +} diff --git a/lib/io.neon b/lib/io.neon new file mode 100644 index 0000000000..cc1dd7a56f --- /dev/null +++ b/lib/io.neon @@ -0,0 +1,147 @@ +%| + | File: io + | + | Functions for general purpose input and output with files. + |% + +EXPORT File +EXPORT Mode +EXPORT SeekBase + +%| + | Enumeration: Mode + | + | Mode to use when opening a file. + | + | Values: + | read - read only + | write - read or write + |% +TYPE Mode IS ENUM + read + write +END ENUM + +%| + | Enumeration: SeekBase + | + | Position to base seek position on. + | + | Values: + | absolute - set absolute position + | relative - set position relative to current + | fromEnd - set position relative to end + |% +TYPE SeekBase IS ENUM + absolute + relative + fromEnd +END ENUM + +%| + | Type: File + | + | Opaque type representing a file on disk. + |% +TYPE File IS POINTER + +%| + | Exception: InvalidFileException + | + | An invalid (NIL) file was used. + |% +DECLARE EXCEPTION InvalidFileException + +%| + | Variable: stdin + | + | The standard input file. + |% +DECLARE NATIVE VAR stdin: File + +%| + | Variable: stdout + | + | The standard output file. + |% +DECLARE NATIVE VAR stdout: File + +%| + | Variable: stderr + | + | The standard error file. + |% +DECLARE NATIVE VAR stderr: File + +%| + | Function: close + | + | Close a file. + |% +DECLARE NATIVE FUNCTION close(INOUT f: File) + +%| + | Function: fprint + | + | Print a string, followed by a newline, to a file. + |% +DECLARE NATIVE FUNCTION fprint(f: File, s: String) + +%| + | Function: open + | + | Open a file with the given name and mode. + |% +DECLARE NATIVE FUNCTION open(name: String, mode: Mode): File + +%| + | Function: readBytes + | + | Read bytes from a file. + |% +DECLARE NATIVE FUNCTION readBytes(f: File, count: Number): Bytes + +%| + | Function: readLine + | + | Read a line of text from a file. + | + | Returns: + | FALSE if there are no more lines in the file, otherwise TRUE. + |% +DECLARE NATIVE FUNCTION readLine(f: File, OUT s: String): Boolean + +%| + | Function: seek + | + | Seek to a specific position within a file. + |% +DECLARE NATIVE FUNCTION seek(f: File, offset: Number, whence: SeekBase DEFAULT SeekBase.absolute) + +%| + | Function: tell + | + | Return the current file pointer position. + |% +DECLARE NATIVE FUNCTION tell(f: File): Number + +%| + | Function: truncate + | + | Truncate the file at the current position. + |% +DECLARE NATIVE FUNCTION truncate(f: File) + +%| + | Function: write + | + | Write characters to a file. No newline is written. + |% +DECLARE NATIVE FUNCTION write(f: File, s: String) + +%| + | Function: writeBytes + | + | Write bytes to a file. + |% +DECLARE NATIVE FUNCTION writeBytes(f: File, b: Bytes) diff --git a/lib/json.neon b/lib/json.neon new file mode 100644 index 0000000000..417ba77e44 --- /dev/null +++ b/lib/json.neon @@ -0,0 +1,172 @@ +%| + | File: json + | + | Functions for reading and writing files in JSON format (). + |% + +EXPORT decode +EXPORT encode + +IMPORT variant +IMPORT variant.Type +IMPORT variant.Variant + +% TODO: This encoder and decoder does not handle all backslash +% character escapes yet. +% TODO: Also, this could probably be improved a lot by using regex. + +%| + | Exception: JsonFormatException + | + | Indicates that a formatting error was encountered when reading JSON. + |% +DECLARE EXCEPTION JsonFormatException + +FUNCTION skipWhitespace(json: String, INOUT index: Number) + WHILE index < json.length() AND (json[index] = " " OR json[index] = "\t" OR json[index] = "\r" OR json[index] = "\n") DO + INC index + END WHILE +END FUNCTION + +FUNCTION decodePart(json: String, INOUT index: Number): Variant + skipWhitespace(json, INOUT index) + CASE json[index] + WHEN "a" TO "z" DO + LET start: Number := index + WHILE index < json.length() AND "a" <= json[index] <= "z" DO + INC index + END WHILE + LET s: String := json[start TO index-1] + CASE s + WHEN "null" DO + RETURN variant.makeNull() + WHEN "false" DO + RETURN variant.makeBoolean(FALSE) + WHEN "true" DO + RETURN variant.makeBoolean(TRUE) + WHEN OTHERS DO + RAISE JsonFormatException("null or false or true expected") + END CASE + WHEN "-", "0" TO "9" DO + LET start: Number := index + WHILE index < json.length() AND ("0" <= json[index] <= "9" OR json[index] = "-" OR json[index] = "+" OR json[index] = "." OR json[index] = "e" OR json[index] = "E") DO + INC index + END WHILE + RETURN variant.makeNumber(num(json[start TO index-1])) + WHEN "\"" DO + INC index + VAR t: String := "" + LET start: Number := index + WHILE index < json.length() AND json[index] # "\"" DO + IF json[index] = "\\" THEN + % TODO: This just skips over the backslash. + % Need to add specific processing for control characters. + INC index + END IF + t.append(json[index]) + INC index + END WHILE + INC index + RETURN variant.makeString(t) + WHEN "[" DO + INC index + VAR a: Array := [] + LOOP + skipWhitespace(json, INOUT index) + IF json[index] = "]" THEN + EXIT LOOP + END IF + a.append(decodePart(json, INOUT index)) + skipWhitespace(json, INOUT index) + IF json[index] = "," THEN + INC index + ELSIF json[index] # "]" THEN + RAISE JsonFormatException(", or ] expected") + END IF + END LOOP + INC index + RETURN variant.makeArray(a) + WHEN "{" DO + INC index + VAR d: Dictionary := {} + LOOP + skipWhitespace(json, INOUT index) + IF json[index] = "}" THEN + EXIT LOOP + END IF + LET vkey: Variant := decodePart(json, INOUT index) + IF vkey.getType() # Type.string THEN + RAISE JsonFormatException("string key expected") + END IF + LET key: String := vkey.getString() + skipWhitespace(json, INOUT index) + IF json[index] # ":" THEN + RAISE JsonFormatException(": expected") + END IF + INC index + d[key] := decodePart(json, INOUT index) + skipWhitespace(json, INOUT index) + IF json[index] = "," THEN + INC index + ELSIF json[index] # "}" THEN + RAISE JsonFormatException(", or } expected") + END IF + END LOOP + INC index + RETURN variant.makeDictionary(d) + END CASE + RETURN variant.makeNull() +END FUNCTION + +%| + | Function: decode + | + | Decode JSON data in a string to a result in a . + |% +FUNCTION decode(json: String): Variant + VAR i: Number := 0 + RETURN decodePart(json, INOUT i) +END FUNCTION + +%| + | Function: encode + | + | Encode a value in a to JSON data in a string. + |% +FUNCTION encode(data: Variant): String + CASE data.getType() + WHEN Type.null DO + RETURN "null" + WHEN Type.boolean DO + RETURN IF data.getBoolean() THEN "true" ELSE "false" + WHEN Type.number DO + RETURN str(data.getNumber()) + WHEN Type.string DO + RETURN "\"" & data.getString() & "\"" + WHEN Type.array DO + VAR r: String := "[" + LET a: Array := data.getArray() + FOREACH x OF a DO + IF r.length() > 1 THEN + r.append(",") + END IF + r.append(encode(x)) + END FOREACH + r.append("]") + RETURN r + WHEN Type.dictionary DO + VAR r: String := "{" + LET d: Dictionary := data.getDictionary() + LET keys: Array := d.keys() + % TODO: This should just be d.keys(), see t/foreach-value.neon + FOREACH x OF keys DO + IF r.length() > 1 THEN + r.append(",") + END IF + r.append(encode(variant.makeString(x)) & ":" & encode(d[x])) + END FOREACH + r.append("}") + RETURN r + END CASE + RETURN "?unknown" +END FUNCTION diff --git a/lib/math.cpp b/lib/math.cpp index bf43755a79..72ea15400e 100644 --- a/lib/math.cpp +++ b/lib/math.cpp @@ -1,22 +1,56 @@ #include "number.h" +#include + +#include "rtl_exec.h" + namespace rtl { +Number math$abs(Number x) +{ + return number_abs(x); +} + Number math$acos(Number x) { return number_acos(x); } +Number math$acosh(Number x) +{ + return number_acosh(x); +} + Number math$asin(Number x) { return number_asin(x); } +Number math$asinh(Number x) +{ + return number_asinh(x); +} + Number math$atan(Number x) { return number_atan(x); } +Number math$atanh(Number x) +{ + return number_atanh(x); +} + +Number math$atan2(Number y, Number x) +{ + return number_atan2(y, x); +} + +Number math$cbrt(Number x) +{ + return number_cbrt(x); +} + Number math$ceil(Number x) { return number_ceil(x); @@ -27,26 +61,108 @@ Number math$cos(Number x) return number_cos(x); } +Number math$cosh(Number x) +{ + return number_cosh(x); +} + +Number math$erf(Number x) +{ + return number_erf(x); +} + +Number math$erfc(Number x) +{ + return number_erfc(x); +} + Number math$exp(Number x) { return number_exp(x); } +Number math$exp2(Number x) +{ + return number_exp2(x); +} + +Number math$expm1(Number x) +{ + return number_expm1(x); +} + Number math$floor(Number x) { return number_floor(x); } +Number math$frexp(Number x, Number *exp) +{ + int iexp; + Number r = number_frexp(x, &iexp); + *exp = number_from_sint32(iexp); + return r; +} + +Number math$hypot(Number x, Number y) +{ + return number_hypot(x, y); +} + +Number math$ldexp(Number x, Number exp) +{ + if (not number_is_integer(exp)) { + // TODO: more specific exception? + throw RtlException(Exception_global$ValueRangeException, number_to_string(exp)); + } + return number_ldexp(x, number_to_sint32(exp)); +} + +Number math$lgamma(Number x) +{ + return number_lgamma(x); +} + Number math$log(Number x) { return number_log(x); } +Number math$log10(Number x) +{ + return number_log10(x); +} + +Number math$log1p(Number x) +{ + return number_log1p(x); +} + +Number math$log2(Number x) +{ + return number_log2(x); +} + +Number math$nearbyint(Number x) +{ + return number_nearbyint(x); +} + +Number math$sign(Number x) +{ + return number_sign(x); +} + Number math$sin(Number x) { return number_sin(x); } +Number math$sinh(Number x) +{ + return number_sinh(x); +} + Number math$sqrt(Number x) { return number_sqrt(x); @@ -57,4 +173,19 @@ Number math$tan(Number x) return number_tan(x); } +Number math$tanh(Number x) +{ + return number_tanh(x); +} + +Number math$tgamma(Number x) +{ + return number_tgamma(x); +} + +Number math$trunc(Number x) +{ + return number_trunc(x); +} + } // namespace rtl diff --git a/lib/math.neon b/lib/math.neon new file mode 100644 index 0000000000..6079337e12 --- /dev/null +++ b/lib/math.neon @@ -0,0 +1,357 @@ +%| + | File: math + | + | Mathemtical functions for real numbers. + |% + +EXPORT Pi + +%| + | Constant: Pi + | + | The value of Pi to 34 significant digits. + |% +CONSTANT Pi: Number := 3.141592653589793238462643383279503 + +%| + | Function: abs + | + | Absolute value. + | + | Reference: + | http://mathworld.wolfram.com/AbsoluteValue.html + | + | Example: + | > ASSERT math.abs(-5) = 5 + |% +DECLARE NATIVE FUNCTION abs(x: Number): Number + +%| + | Function: acos + | + | Inverse cosine (arc cos). + | + | Reference: + | http://mathworld.wolfram.com/InverseCosine.html + |% +DECLARE NATIVE FUNCTION acos(x: Number): Number + +%| + | Function: acosh + | + | Inverse hyperbolic cosine (arc cosh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicCosine.html + |% +DECLARE NATIVE FUNCTION acosh(x: Number): Number + +%| + | Function: asin + | + | Inverse sine (arc sin). + | + | Reference: + | http://mathworld.wolfram.com/InverseSine.html + |% +DECLARE NATIVE FUNCTION asin(x: Number): Number + +%| + | Function: asinh + | + | Inverse hyperbolic sine (arc sinh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicSine.html + |% +DECLARE NATIVE FUNCTION asinh(x: Number): Number + +%| + | Function: atan + | + | Inverse tangent (arc tan). + | + | Reference: + | http://mathworld.wolfram.com/InverseTangent.html + |% +DECLARE NATIVE FUNCTION atan(x: Number): Number + +%| + | Function: atanh + | + | Inverse hyperbolic tangent (arc tanh). + | + | Reference: + | http://mathworld.wolfram.com/InverseHyperbolicTangent.html + |% +DECLARE NATIVE FUNCTION atanh(x: Number): Number + +%| + | Function: atan2 + | + | Inverse tangent (arc tan) of y/x. + | This two-argument form avoids problems where x is near 0. + | + | Reference: + | http://mathworld.wolfram.com/InverseTangent.html + |% +DECLARE NATIVE FUNCTION atan2(y, x: Number): Number + +%| + | Function: cbrt + | + | Cube root. + | + | Refrence: + | http://mathworld.wolfram.com/CubeRoot.html + |% +DECLARE NATIVE FUNCTION cbrt(x: Number): Number + +%| + | Function: ceil + | + | Ceiling function. + | + | Reference: + | http://mathworld.wolfram.com/CeilingFunction.html + |% +DECLARE NATIVE FUNCTION ceil(x: Number): Number + +%| + | Function: cos + | + | Cosine. + | + | Reference: + | http://mathworld.wolfram.com/Cosine.html + |% +DECLARE NATIVE FUNCTION cos(x: Number): Number + +%| + | Function: cosh + | + | Hyperbolic cosine. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicCosine.html + |% +DECLARE NATIVE FUNCTION cosh(x: Number): Number + +%| + | Function: erf + | + | Error function. + | + | Reference: + | http://mathworld.wolfram.com/Erf.html + |% +DECLARE NATIVE FUNCTION erf(x: Number): Number + +%| + | Function: erfc + | + | Complementary error function. + | + | Reference: + | http://mathworld.wolfram.com/Erfc.html + |% +DECLARE NATIVE FUNCTION erfc(x: Number): Number + +%| + | Function: exp + | + | Exponentiation. + | + | Reference: + | http://mathworld.wolfram.com/Exponentiation.html + |% +DECLARE NATIVE FUNCTION exp(x: Number): Number + +%| + | Function: exp2 + | + | Exponentiation (base 2). + | + | Reference: + | http://mathworld.wolfram.com/Exponentiation.html + |% +DECLARE NATIVE FUNCTION exp2(x: Number): Number + +%| + | Function: expm1 + | + | Calculates exp(x)-1.0. This function avoids numeric accuracy problems + | where x is close to zero. + | + | Reference: + | http://mathworld.wolfram.com/Exponentiation.html + |% +DECLARE NATIVE FUNCTION expm1(x: Number): Number + +%| + | Function: floor + | + | Floor function. + | + | Reference: + | http://mathworld.wolfram.com/FloorFunction.html + |% +DECLARE NATIVE FUNCTION floor(x: Number): Number + +%| + | Function: frexp + | + | Decomposes a floating point number into a normalised fraction and an integral power of 10. + |% +DECLARE NATIVE FUNCTION frexp(x: Number, OUT exp: Number): Number + +%| + | Function: hypot + | + | Hypotenuse. Calcuates sqrt(x^2 + y^2). + |% +DECLARE NATIVE FUNCTION hypot(x: Number, y: Number): Number + +%| + | Function: ldexp + | + | Multiplies a floating point value x by the number 10 raised to the exp power. + |% +DECLARE NATIVE FUNCTION ldexp(x: Number, exp: Number): Number + +%| + | Function: lgamma + | + | Log gamma function. + | + | Reference: + | http://mathworld.wolfram.com/LogGammaFunction.html + |% +DECLARE NATIVE FUNCTION lgamma(x: Number): Number + +%| + | Function: log + | + | Logarithm. + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +DECLARE NATIVE FUNCTION log(x: Number): Number + +%| + | Function: log10 + | + | Logarithm (base 10). + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +DECLARE NATIVE FUNCTION log10(x: Number): Number + +%| + | Function: log1p + | + | Computes the natural (base e) logarithm of 1+arg. This function is more precise than the expression log(1+arg) if arg is close to zero. + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +DECLARE NATIVE FUNCTION log1p(x: Number): Number + +%| + | Function: log2 + | + | Logarithm (base 2). + | + | Reference: + | http://mathworld.wolfram.com/Logarithm.html + |% +DECLARE NATIVE FUNCTION log2(x: Number): Number + +%| + | Function: nearbyint + | + | Returns an integer close to x by rounding. + |% +DECLARE NATIVE FUNCTION nearbyint(x: Number): Number + +%| + | Function: sign + | + | Returns -1 if x is negative, 0 if x is 0, or 1 if x is positive. + | + | Reference: + | http://mathworld.wolfram.com/Sign.html + |% +DECLARE NATIVE FUNCTION sign(x: Number): Number + +%| + | Function: sin + | + | Sine. + | + | Reference: + | http://mathworld.wolfram.com/Sine.html + |% +DECLARE NATIVE FUNCTION sin(x: Number): Number + +%| + | Function: sinh + | + | Hyperbolic sine. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicSine.html + |% +DECLARE NATIVE FUNCTION sinh(x: Number): Number + +%| + | Function: sqrt + | + | Square root. + | + | Reference: + | http://mathworld.wolfram.com/SquareRoot.html + |% +DECLARE NATIVE FUNCTION sqrt(x: Number): Number + +%| + | Function: tan + | + | Tangent. + | + | Reference: + | http://mathworld.wolfram.com/Tangent.html + |% +DECLARE NATIVE FUNCTION tan(x: Number): Number + +%| + | Function: tanh + | + | Hyperbolic tangent. + | + | Reference: + | http://mathworld.wolfram.com/HyperbolicTangent.html + |% +DECLARE NATIVE FUNCTION tanh(x: Number): Number + +%| + | Function: tgamma + | + | Gamma function. + | + | Reference: + | http://mathworld.wolfram.com/GammaFunction.html + |% +DECLARE NATIVE FUNCTION tgamma(x: Number): Number + +%| + | Function: trunc + | + | Truncate to integer (effectively, round toward zero). + | + | Reference: + | http://mathworld.wolfram.com/Truncate.html + |% +DECLARE NATIVE FUNCTION trunc(x: Number): Number diff --git a/lib/math_const.cpp b/lib/math_const.cpp deleted file mode 100644 index 5757354e9b..0000000000 --- a/lib/math_const.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "number.h" - -namespace rtl { - -extern const Number math$PI = number_from_string("3.141592653589793"); - -} // namespace rtl diff --git a/lib/mmap.neon b/lib/mmap.neon new file mode 100644 index 0000000000..63b9114a25 --- /dev/null +++ b/lib/mmap.neon @@ -0,0 +1,80 @@ +%| + | File: mmap + | + | Memory mapped file access. + |% + +EXPORT MemoryFile +EXPORT Mode + +%| + | Enumeration: Mode + | + | Mode to use when opening a file. + | + | Values: + | read - read only + | write - read or write + |% +TYPE Mode IS ENUM + read + write +END ENUM + +%| + | Type: MemoryFile + | + | Opaque type representing a memory mapped file. + |% +TYPE MemoryFile IS POINTER + +%| + | Exception: InvalidFileException + | + | An invalid (NIL) file was used. + |% +DECLARE EXCEPTION InvalidFileException + +%| + | Exception: OpenFileException + | + | An error occured while trying to open the file. + |% +DECLARE EXCEPTION OpenFileException + +%| + | Function: close + | + | Close a memory mapped file. + |% +DECLARE NATIVE FUNCTION close(INOUT f: MemoryFile) + +%| + | Function: open + | + | Open a memory mapped file. + | + | TODO: The only valid mode is currently Mode.read. + |% +DECLARE NATIVE FUNCTION open(name: String, mode: Mode): MemoryFile + +%| + | Function: read + | + | Read bytes from a memory mapped file at the given offset. + |% +DECLARE NATIVE FUNCTION read(f: MemoryFile, offset: Number, count: Number): Bytes + +%| + | Function: size + | + | Return the size of a memory mapped file. + |% +DECLARE NATIVE FUNCTION size(f: MemoryFile): Number + +%| + | Function: write + | + | Write bytes to a memory mapped file at the given offset. + |% +DECLARE NATIVE FUNCTION write(f: MemoryFile, offset: Number, data: Bytes) diff --git a/lib/mmap_posix.cpp b/lib/mmap_posix.cpp new file mode 100644 index 0000000000..371c8b04f5 --- /dev/null +++ b/lib/mmap_posix.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "cell.h" +#include "number.h" +#include "rtl_exec.h" + +class MemoryFile { +public: + int fd; + size_t len; + char *view; +}; + +static MemoryFile *check_file(void *pf) +{ + MemoryFile *f = static_cast(pf); + if (f == NULL) { + throw RtlException(Exception_mmap$InvalidFileException, ""); + } + return f; +} + +namespace rtl { + +void mmap$close(Cell **ppf) +{ + MemoryFile *f = check_file(*ppf); + munmap(f->view, f->len); + close(f->fd); + delete f; + *ppf = NULL; +} + +void *mmap$open(const std::string &name, Cell &) +{ + MemoryFile *f = new MemoryFile; + f->fd = open(name.c_str(), O_RDONLY); + if (f->fd < 0) { + int e = errno; + delete f; + throw RtlException(Exception_mmap$OpenFileException, "open: error (" + std::to_string(e) + ") " + strerror(e)); + } + struct stat st; + fstat(f->fd, &st); + f->len = st.st_size; + f->view = static_cast(mmap(NULL, f->len, PROT_READ, MAP_PRIVATE, f->fd, 0)); + if (f->view == MAP_FAILED) { + int e = errno; + close(f->fd); + delete f; + throw RtlException(Exception_mmap$OpenFileException, "mmap: error (" + std::to_string(e) + ") " + strerror(e)); + } + return f; +} + +std::string mmap$read(void *pf, Number offset, Number count) +{ + MemoryFile *f = check_file(pf); + uint64_t o = number_to_uint64(offset); + if (o >= f->len) { + return std::string(); + } + uint64_t c = number_to_uint64(count); + if (o + c > f->len) { + c = f->len - o; + } + return std::string(f->view + o, f->view + o + c); +} + +Number mmap$size(void *pf) +{ + MemoryFile *f = check_file(pf); + return number_from_uint64(f->len); +} + +void mmap$write(void *pf, Number offset, const std::string &data) +{ + MemoryFile *f = check_file(pf); + uint64_t o = number_to_uint64(offset); + if (o >= f->len) { + throw RtlException(Exception_global$ValueRangeException, ""); + } + if (o + data.length() > f->len) { + throw RtlException(Exception_global$ValueRangeException, ""); + } + memcpy(f->view + o, data.data(), data.length()); +} + +} diff --git a/lib/mmap_win32.cpp b/lib/mmap_win32.cpp new file mode 100644 index 0000000000..c81268271f --- /dev/null +++ b/lib/mmap_win32.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include "cell.h" +#include "number.h" +#include "rtl_exec.h" + +class MemoryFile { +public: + HANDLE file; + size_t len; + HANDLE map; + BYTE *view; +}; + +static MemoryFile *check_file(void *pf) +{ + MemoryFile *f = static_cast(pf); + if (f == NULL) { + throw RtlException(Exception_mmap$InvalidFileException, ""); + } + return f; +} + +namespace rtl { + +void mmap$close(Cell **ppf) +{ + MemoryFile *f = check_file(*ppf); + UnmapViewOfFile(f->view); + CloseHandle(f->map); + CloseHandle(f->file); + delete f; + *ppf = NULL; +} + +void *mmap$open(const std::string &name, Cell &) +{ + MemoryFile *f = new MemoryFile; + f->file = INVALID_HANDLE_VALUE; + if (not name.empty()) { + f->file = CreateFile(name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + if (f->file == INVALID_HANDLE_VALUE) { + DWORD e = GetLastError(); + delete f; + throw RtlException(Exception_mmap$OpenFileException, "CreateFile: error (" + std::to_string(e) + ")"); + } + } + LARGE_INTEGER size; + if (!GetFileSizeEx(f->file, &size)) { + DWORD e = GetLastError(); + CloseHandle(f->file); + delete f; + throw RtlException(Exception_mmap$OpenFileException, "GetFileSizeEx: error (" + std::to_string(e) + ")"); + } + f->len = size.QuadPart; + f->map = CreateFileMapping(f->file, NULL, PAGE_READONLY, 0, 0, NULL); + if (f->map == NULL) { + DWORD e = GetLastError(); + CloseHandle(f->file); + delete f; + throw RtlException(Exception_mmap$OpenFileException, "CreateFileMapping: error (" + std::to_string(e) + ")"); + } + f->view = reinterpret_cast(MapViewOfFile(f->map, FILE_MAP_READ, 0, 0, 0)); + if (f->view == NULL) { + DWORD e = GetLastError(); + CloseHandle(f->map); + CloseHandle(f->file); + delete f; + throw RtlException(Exception_mmap$OpenFileException, "MapViewOfFile: error (" + std::to_string(e) + ")"); + } + return f; +} + +std::string mmap$read(void *pf, Number offset, Number count) +{ + MemoryFile *f = check_file(pf); + uint64_t o = number_to_uint64(offset); + if (o >= f->len) { + return std::string(); + } + uint64_t c = number_to_uint64(count); + if (o + c > f->len) { + c = f->len - o; + } + return std::string(f->view + o, f->view + o + c); +} + +Number mmap$size(void *pf) +{ + MemoryFile *f = check_file(pf); + return number_from_uint64(f->len); +} + +void mmap$write(void *pf, Number offset, const std::string &data) +{ + MemoryFile *f = check_file(pf); + uint64_t o = number_to_uint64(offset); + if (o >= f->len) { + throw RtlException(Exception_global$ValueRangeException, ""); + } + if (o + data.length() > f->len) { + throw RtlException(Exception_global$ValueRangeException, ""); + } + memcpy(f->view + o, data.data(), data.length()); +} + +} diff --git a/lib/multiarray.neon b/lib/multiarray.neon new file mode 100644 index 0000000000..1048c1ff68 --- /dev/null +++ b/lib/multiarray.neon @@ -0,0 +1,175 @@ +%| + | File: multiarray + | + | Functions and types for working with multidimensional arrays. + |% + +EXPORT ArrayBoolean2D +EXPORT ArrayBoolean3D +EXPORT ArrayNumber2D +EXPORT ArrayNumber3D +EXPORT ArrayString2D +EXPORT ArrayString3D +EXPORT makeBoolean2D +EXPORT makeBoolean3D +EXPORT makeNumber2D +EXPORT makeNumber2DValue +EXPORT makeNumber3D +EXPORT makeString2D +EXPORT makeString3D + +%| + | Type: ArrayBoolean2D + | + | A two-dimensional array of . + |% +TYPE ArrayBoolean2D IS Array> + +%| + | Type: ArrayBoolean3D + | + | A three-dimensional array of . + |% +TYPE ArrayBoolean3D IS Array>> + +%| + | Type: ArrayNumber2D + | + | A two-dimensional array of . + |% +TYPE ArrayNumber2D IS Array> + +%| + | Type: ArrayNumber3D + | + | A three-dimensional array of . + |% +TYPE ArrayNumber3D IS Array>> + +%| + | Type: ArrayString2D + | + | A two-dimensional array of . + |% +TYPE ArrayString2D IS Array> + +%| + | Type: ArrayString3D + | + | A three-dimensional array of . + |% +TYPE ArrayString3D IS Array>> + +%| + | Function: makeBoolean2D + | + | Create a new with the given number of rows and columns. + |% +FUNCTION makeBoolean2D(rows, columns: Number): ArrayBoolean2D + VAR r: ArrayBoolean2D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeBoolean3D + | + | Create a new with the given number of rows, columns, and depth. + |% +FUNCTION makeBoolean3D(rows, columns, depth: Number): ArrayBoolean3D + VAR r: ArrayBoolean3D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + FOR j := 0 TO columns-1 DO + r[i][j].resize(depth) + END FOR + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeNumber2D + | + | Create a new with the given number of rows and columns. + |% +FUNCTION makeNumber2D(rows, columns: Number): ArrayNumber2D + VAR r: ArrayNumber2D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeNumber2DValue + | + | Create a new with the given number of rows and columns. + |% +FUNCTION makeNumber2DValue(rows, columns: Number, value: Number): ArrayNumber2D + % TODO: Make 'value' a parameter with a default value + % (depends on https://github.com/ghewgill/neon-lang/issues/45). + VAR r: ArrayNumber2D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + IF value # 0 THEN + FOR j := 0 TO columns-1 DO + r[i][j] := value + END FOR + END IF + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeNumber3D + | + | Create a new with the given number of rows, columns, and depth. + |% +FUNCTION makeNumber3D(rows, columns, depth: Number): ArrayNumber3D + VAR r: ArrayNumber3D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + FOR j := 0 TO columns-1 DO + r[i][j].resize(depth) + END FOR + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeString2D + | + | Create a new with the given number of rows and columns. + |% +FUNCTION makeString2D(rows, columns: Number): ArrayString2D + VAR r: ArrayString2D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + END FOR + RETURN r +END FUNCTION + +%| + | Function: makeString3D + | + | Create a new with the given number of rows, columns, and depth. + |% +FUNCTION makeString3D(rows, columns, depth: Number): ArrayString3D + VAR r: ArrayString3D := [] + r.resize(rows) + FOR i := 0 TO rows-1 DO + r[i].resize(columns) + FOR j := 0 TO columns-1 DO + r[i][j].resize(depth) + END FOR + END FOR + RETURN r +END FUNCTION diff --git a/lib/nd.proj/.gitignore b/lib/nd.proj/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/lib/nd.proj/.gitignore @@ -0,0 +1 @@ +* diff --git a/lib/nd.proj/Languages.txt b/lib/nd.proj/Languages.txt new file mode 100644 index 0000000000..9408291935 --- /dev/null +++ b/lib/nd.proj/Languages.txt @@ -0,0 +1,122 @@ +Format: 1.52 + +# This is the Natural Docs languages file for this project. If you change +# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change +# something for all your projects, edit the Languages.txt in Natural Docs' +# Config directory instead. + + +# You can prevent certain file extensions from being scanned like this: +# Ignore Extensions: [extension] [extension] ... + + +#------------------------------------------------------------------------------- +# SYNTAX: +# +# Unlike other Natural Docs configuration files, in this file all comments +# MUST be alone on a line. Some languages deal with the # character, so you +# cannot put comments on the same line as content. +# +# Also, all lists are separated with spaces, not commas, again because some +# languages may need to use them. +# +# Language: [name] +# Alter Language: [name] +# Defines a new language or alters an existing one. Its name can use any +# characters. If any of the properties below have an add/replace form, you +# must use that when using Alter Language. +# +# The language Shebang Script is special. It's entry is only used for +# extensions, and files with those extensions have their shebang (#!) lines +# read to determine the real language of the file. Extensionless files are +# always treated this way. +# +# The language Text File is also special. It's treated as one big comment +# so you can put Natural Docs content in them without special symbols. Also, +# if you don't specify a package separator, ignored prefixes, or enum value +# behavior, it will copy those settings from the language that is used most +# in the source tree. +# +# Extensions: [extension] [extension] ... +# [Add/Replace] Extensions: [extension] [extension] ... +# Defines the file extensions of the language's source files. You can +# redefine extensions found in the main languages file. You can use * to +# mean any undefined extension. +# +# Shebang Strings: [string] [string] ... +# [Add/Replace] Shebang Strings: [string] [string] ... +# Defines a list of strings that can appear in the shebang (#!) line to +# designate that it's part of the language. You can redefine strings found +# in the main languages file. +# +# Ignore Prefixes in Index: [prefix] [prefix] ... +# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ... +# +# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ... +# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ... +# Specifies prefixes that should be ignored when sorting symbols in an +# index. Can be specified in general or for a specific topic type. +# +#------------------------------------------------------------------------------ +# For basic language support only: +# +# Line Comments: [symbol] [symbol] ... +# Defines a space-separated list of symbols that are used for line comments, +# if any. +# +# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ... +# Defines a space-separated list of symbol pairs that are used for block +# comments, if any. +# +# Package Separator: [symbol] +# Defines the default package separator symbol. The default is a dot. +# +# [Topic Type] Prototype Enders: [symbol] [symbol] ... +# When defined, Natural Docs will attempt to get a prototype from the code +# immediately following the topic type. It stops when it reaches one of +# these symbols. Use \n for line breaks. +# +# Line Extender: [symbol] +# Defines the symbol that allows a prototype to span multiple lines if +# normally a line break would end it. +# +# Enum Values: [global|under type|under parent] +# Defines how enum values are referenced. The default is global. +# global - Values are always global, referenced as 'value'. +# under type - Values are under the enum type, referenced as +# 'package.enum.value'. +# under parent - Values are under the enum's parent, referenced as +# 'package.value'. +# +# Perl Package: [perl package] +# Specifies the Perl package used to fine-tune the language behavior in ways +# too complex to do in this file. +# +#------------------------------------------------------------------------------ +# For full language support only: +# +# Full Language Support: [perl package] +# Specifies the Perl package that has the parsing routines necessary for full +# language support. +# +#------------------------------------------------------------------------------- + +# The following languages are defined in the main file, if you'd like to alter +# them: +# +# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python, +# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile, +# ActionScript, ColdFusion, R, Fortran + +# If you add a language that you think would be useful to other developers +# and should be included in Natural Docs by default, please e-mail it to +# languages [at] naturaldocs [dot] org. + + +Language: Neon + + Extension: neon + Shebang String: neon + Line Comment: % + Block Comment: %| |% + Function Prototype Ender: \n diff --git a/lib/nd.proj/Menu.txt b/lib/nd.proj/Menu.txt new file mode 100644 index 0000000000..04d07e2d93 --- /dev/null +++ b/lib/nd.proj/Menu.txt @@ -0,0 +1,87 @@ +Format: 1.52 + + +Title: Neon +SubTitle: Standard Library + +# You can add a footer to your documentation like this: +# Footer: [text] +# If you want to add a copyright notice, this would be the place to do it. +Timestamp: Generated on day month, year +# m - One or two digit month. January is "1" +# mm - Always two digit month. January is "01" +# mon - Short month word. January is "Jan" +# month - Long month word. January is "January" +# d - One or two digit day. 1 is "1" +# dd - Always two digit day. 1 is "01" +# day - Day with letter extension. 1 is "1st" +# yy - Two digit year. 2006 is "06" +# yyyy - Four digit year. 2006 is "2006" +# year - Four digit year. 2006 is "2006" + + +# -------------------------------------------------------------------------- +# +# Cut and paste the lines below to change the order in which your files +# appear on the menu. Don't worry about adding or removing files, Natural +# Docs will take care of that. +# +# You can further organize the menu by grouping the entries. Add a +# "Group: [name] {" line to start a group, and add a "}" to end it. +# +# You can add text and web links to the menu by adding "Text: [text]" and +# "Link: [name] ([URL])" lines, respectively. +# +# The formatting and comments are auto-generated, so don't worry about +# neatness when editing the file. Natural Docs will clean it up the next +# time it is run. When working with groups, just deal with the braces and +# forget about the indentation and comments. +# +# -------------------------------------------------------------------------- + + +File: global (global.neon) +File: bigint (bigint.neon) +File: bitwise (bitwise.neon) +File: cformat (cformat.neon) +File: complex (complex.neon) +File: compress (compress.neon) +File: curses (curses.neon) +File: datetime (datetime.neon) +File: debugger (debugger.neon) +File: encoding (encoding.neon) +File: file (file.neon) +File: hash (hash.neon) +File: http (http.neon) +File: io (io.neon) +File: json (json.neon) +File: math (math.neon) +File: mmap (mmap.neon) +File: multiarray (multiarray.neon) +File: net (net.neon) +File: os (os.neon) +File: random (random.neon) +File: regex (regex.neon) +File: runtime (runtime.neon) +File: sdl (sdl.neon) +File: sodium (sodium.neon) +File: sqlite (sqlite.neon) +File: string (string.neon) +File: struct (struct.neon) +File: sys (sys.neon) +File: time (time.neon) +File: variant (variant.neon) +File: win32 (win32.neon) +File: xml (xml.neon) + +Group: Index { + + Constant Index: Constants + Index: Everything + Exception Index: Exceptions + File Index: Files + Function Index: Functions + Type Index: Types + Variable Index: Variables + } # Group: Index + diff --git a/lib/nd.proj/Topics.txt b/lib/nd.proj/Topics.txt new file mode 100644 index 0000000000..ea8f0a3bdc --- /dev/null +++ b/lib/nd.proj/Topics.txt @@ -0,0 +1,88 @@ +Format: 1.52 + +# This is the Natural Docs topics file for this project. If you change anything +# here, it will apply to THIS PROJECT ONLY. If you'd like to change something +# for all your projects, edit the Topics.txt in Natural Docs' Config directory +# instead. + + +# If you'd like to prevent keywords from being recognized by Natural Docs, you +# can do it like this: +# Ignore Keywords: [keyword], [keyword], ... +# +# Or you can use the list syntax like how they are defined: +# Ignore Keywords: +# [keyword] +# [keyword], [plural keyword] +# ... + + +#------------------------------------------------------------------------------- +# SYNTAX: +# +# Topic Type: [name] +# Alter Topic Type: [name] +# Creates a new topic type or alters one from the main file. Each type gets +# its own index and behavior settings. Its name can have letters, numbers, +# spaces, and these charaters: - / . ' +# +# Plural: [name] +# Sets the plural name of the topic type, if different. +# +# Keywords: +# [keyword] +# [keyword], [plural keyword] +# ... +# Defines or adds to the list of keywords for the topic type. They may only +# contain letters, numbers, and spaces and are not case sensitive. Plural +# keywords are used for list topics. You can redefine keywords found in the +# main topics file. +# +# Index: [yes|no] +# Whether the topics get their own index. Defaults to yes. Everything is +# included in the general index regardless of this setting. +# +# Scope: [normal|start|end|always global] +# How the topics affects scope. Defaults to normal. +# normal - Topics stay within the current scope. +# start - Topics start a new scope for all the topics beneath it, +# like class topics. +# end - Topics reset the scope back to global for all the topics +# beneath it. +# always global - Topics are defined as global, but do not change the scope +# for any other topics. +# +# Class Hierarchy: [yes|no] +# Whether the topics are part of the class hierarchy. Defaults to no. +# +# Page Title If First: [yes|no] +# Whether the topic's title becomes the page title if it's the first one in +# a file. Defaults to no. +# +# Break Lists: [yes|no] +# Whether list topics should be broken into individual topics in the output. +# Defaults to no. +# +# Can Group With: [type], [type], ... +# Defines a list of topic types that this one can possibly be grouped with. +# Defaults to none. +#------------------------------------------------------------------------------- + +# The following topics are defined in the main file, if you'd like to alter +# their behavior or add keywords: +# +# Generic, Class, Interface, Section, File, Group, Function, Variable, +# Property, Type, Constant, Enumeration, Event, Delegate, Macro, +# Database, Database Table, Database View, Database Index, Database +# Cursor, Database Trigger, Cookie, Build Target + +# If you add something that you think would be useful to other developers +# and should be included in Natural Docs by default, please e-mail it to +# topics [at] naturaldocs [dot] org. + + +Topic Type: Exception + + Plural: Exceptions + Keywords: + exception, exceptions diff --git a/lib/net.cpp b/lib/net.cpp new file mode 100644 index 0000000000..edca99b1f1 --- /dev/null +++ b/lib/net.cpp @@ -0,0 +1,188 @@ +#include + +#include "cell.h" +#include "rtl_exec.h" +#include "socketx.h" + +namespace rtl { + +Cell net$tcpSocket() +{ +#ifdef _WIN32 + static bool initialized = false; + if (!initialized) { + WSADATA wsa; + WSAStartup(MAKEWORD(1, 1), &wsa); + initialized = true; + } +#endif + Cell r; + SOCKET s = socket(PF_INET, SOCK_STREAM, 0); + r.array_index_for_write(0) = Cell(number_from_sint32(static_cast(s))); + return r; +} + +Cell net$socket_accept(Cell &handle) +{ + SOCKET s = number_to_sint32(handle.number()); + sockaddr_in sin; + socklen_t slen = sizeof(sin); + SOCKET r = accept(s, reinterpret_cast(&sin), &slen); + if (r < 0) { + perror("accept"); + return Cell(); + } + Cell client; + client.array_index_for_write(0) = Cell(number_from_sint32(static_cast(r))); + return client; +} + +void net$socket_close(Cell &handle) +{ + SOCKET s = number_to_sint32(handle.number()); + closesocket(s); +} + +void net$socket_connect(Cell &handle, const std::string &host, Number port) +{ + SOCKET s = number_to_sint32(handle.number()); + int p = number_to_sint32(port); + in_addr addr; + addr.s_addr = inet_addr(host.c_str()); + if (addr.s_addr == INADDR_NONE) { + struct hostent *he = gethostbyname(host.c_str()); + if (he == NULL) { + return; + } + addr = *reinterpret_cast(he->h_addr); + } + sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_addr = addr; + sin.sin_port = htons(static_cast(p)); + int r = connect(s, reinterpret_cast(&sin), sizeof(sin)); + if (r < 0) { + perror("connect"); + } +} + +void net$socket_listen(Cell &handle, Number port) +{ + SOCKET s = number_to_sint32(handle.number()); + int on = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(on), sizeof(on)); + int p = number_to_sint32(port); + sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(static_cast(p)); + int r = bind(s, reinterpret_cast(&sin), sizeof(sin)); + if (r < 0) { + perror("bind"); + return; + } + r = listen(s, 5); + if (r < 0) { + perror("listen"); + return; + } +} + +std::string net$socket_recv(Cell &handle, Number count) +{ + SOCKET s = number_to_sint32(handle.number()); + int n = number_to_sint32(count); + std::string buf(n, '\0'); + int r = recv(s, const_cast(buf.data()), n, 0); + if (r < 0) { + perror("recv"); + return ""; + } + buf.resize(r); + return buf; +} + +void net$socket_send(Cell &handle, const std::string &data) +{ + SOCKET s = number_to_sint32(handle.number()); + send(s, data.data(), static_cast(data.length()), 0); +} + +bool net$socket_select(Cell *read, Cell *write, Cell *error, Number timeout_seconds) +{ + fd_set rfds, wfds, efds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + int nfds = 0; + + std::vector &ra = read->array_for_write(); + for (auto s: ra) { + int fd = number_to_sint32(s.array_for_write()[0].number()); + FD_SET(fd, &rfds); + if (fd+1 > nfds) { + nfds = fd+1; + } + } + + std::vector &wa = write->array_for_write(); + for (auto s: wa) { + int fd = number_to_sint32(s.array_for_write()[0].number()); + FD_SET(fd, &wfds); + if (fd+1 > nfds) { + nfds = fd+1; + } + } + + std::vector &ea = error->array_for_write(); + for (auto s: ea) { + int fd = number_to_sint32(s.array_for_write()[0].number()); + FD_SET(fd, &efds); + if (fd+1 > nfds) { + nfds = fd+1; + } + } + + struct timeval actual_tv; + struct timeval *tv = NULL; + if (not number_is_negative(timeout_seconds)) { + actual_tv.tv_sec = number_to_sint32(number_trunc(timeout_seconds)); + actual_tv.tv_usec = number_to_sint32(number_modulo(number_multiply(timeout_seconds, number_from_sint32(1000000)), number_from_sint32(1000000))); + tv = &actual_tv; + } + int r = select(nfds, &rfds, &wfds, &efds, tv); + if (r < 0) { + throw RtlException(Exception_net$SocketException, ""); + } + if (r == 0) { + ra.clear(); + wa.clear(); + ea.clear(); + return false; + } + + for (auto i = ra.end(); i != ra.begin(); ) { + --i; + if (not FD_ISSET(number_to_sint32(i->array_for_write()[0].number()), &rfds)) { + ra.erase(i); + } + } + + for (auto i = wa.end(); i != wa.begin(); ) { + --i; + if (not FD_ISSET(number_to_sint32(i->array_for_write()[0].number()), &wfds)) { + wa.erase(i); + } + } + + for (auto i = ea.end(); i != ea.begin(); ) { + --i; + if (not FD_ISSET(number_to_sint32(i->array_for_write()[0].number()), &efds)) { + ea.erase(i); + } + } + + return true; +} + +} diff --git a/lib/net.neon b/lib/net.neon new file mode 100644 index 0000000000..a4826039e0 --- /dev/null +++ b/lib/net.neon @@ -0,0 +1,107 @@ +%| + | File: net + | + | Functions for working with network sockets. + |% + +EXPORT Socket +EXPORT SocketException +EXPORT select + +%| + | Exception: SocketException + | + | Indicates some kind of socket error. + |% +DECLARE EXCEPTION SocketException + +TYPE Handle IS Number + +%| + | Type: Socket + | + | Opaque type representing a network socket. + |% +TYPE Socket IS RECORD + PRIVATE handle: Handle +END RECORD + +%| + | Function: tcpSocket + | + | Create a new TCP/IP (stream) socket. + |% +DECLARE NATIVE FUNCTION tcpSocket(): Socket + +DECLARE NATIVE FUNCTION socket_accept(handle: Handle): Socket +DECLARE NATIVE FUNCTION socket_close(handle: Handle) +DECLARE NATIVE FUNCTION socket_connect(handle: Handle, host: String, port: Number) +DECLARE NATIVE FUNCTION socket_listen(handle: Handle, port: Number) +DECLARE NATIVE FUNCTION socket_recv(handle: Handle, count: Number): Bytes +DECLARE NATIVE FUNCTION socket_send(handle: Handle, data: Bytes) +DECLARE NATIVE FUNCTION socket_select(INOUT read, write, error: Array, timeout_seconds: Number): Boolean + +%| + | Function: select + | + | Select sockets with pending activity subject to an optional timeout. + |% +FUNCTION select(INOUT read, write, error: Array, timeout_seconds: Number): Boolean + % TODO: This function works around some problem with calling a predefined + % function with an Array parameter. + RETURN socket_select(INOUT read, INOUT write, INOUT error, timeout_seconds) +END FUNCTION + +%| + | Function: Socket.accept + | + | Accept an incoming connection request on a socket and returns a new socket. + |% +FUNCTION Socket.accept(self: Socket): Socket + RETURN socket_accept(self.handle) +END FUNCTION + +%| + | Function: Socket.close + | + | Close a socket. + |% +FUNCTION Socket.close(self: Socket) + socket_close(self.handle) +END FUNCTION + +%| + | Function: Socket.connect + | + | Connect a socket to a given host and port. + |% +FUNCTION Socket.connect(self: Socket, host: String, port: Number) + socket_connect(self.handle, host, port) +END FUNCTION + +%| + | Function: Socket.listen + | + | Listen for incoming connections on a specific port. + |% +FUNCTION Socket.listen(self: Socket, port: Number) + socket_listen(self.handle, port) +END FUNCTION + +%| + | Function: Socket.recv + | + | Receive (read) bytes from a socket. + |% +FUNCTION Socket.recv(self: Socket, count: Number): Bytes + RETURN socket_recv(self.handle, count) +END FUNCTION + +%| + | Function: Socket.send + | + | Send (write) bytes to a socket. + |% +FUNCTION Socket.send(self: Socket, data: Bytes) + socket_send(self.handle, data) +END FUNCTION diff --git a/lib/os.cpp b/lib/os.cpp new file mode 100644 index 0000000000..fa5bca6efd --- /dev/null +++ b/lib/os.cpp @@ -0,0 +1,33 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include +#include + +#include "number.h" + +namespace rtl { + +std::string os$getenv(const std::string &name) +{ + return getenv(name.c_str()); +} + +Number os$system(const std::string &command) +{ + std::string cmd = command; +#ifdef _WIN32 + // Terrible hack to change slashes to backslashes so cmd.exe isn't confused. + // Probably better handled by calling a lower level function than system(). + for (std::string::iterator i = cmd.begin(); not isspace(*i); ++i) { + if (*i == '/') { + *i = '\\'; + } + } +#endif + return number_from_sint32(system(cmd.c_str())); +} + +} diff --git a/lib/os.neon b/lib/os.neon new file mode 100644 index 0000000000..63ae39477b --- /dev/null +++ b/lib/os.neon @@ -0,0 +1,108 @@ +%| + | File: os + | + | Functions for operating system interfaces. + |% + +EXPORT Platform +EXPORT Process +EXPORT SystemException +EXPORT UnsupportedFunctionException + +%| + | Exception: SystemException + | + | Error from system. + |% +DECLARE EXCEPTION SystemException + +%| + | Exception: UnsupportedFunctionException + | + | Operation is unsupported on this platform. + |% +DECLARE EXCEPTION UnsupportedFunctionException + +%| + | Enumeration: Platform + | + | Platform type. + | + | Values: + | posix - posix + | win32 - win32 + |% +TYPE Platform IS ENUM + posix + win32 +END ENUM + +%| + | Type: Process + | + | Opaque type representing a process. + |% +TYPE Process IS POINTER + +%| + | Function: chdir + | + | Change the current process working directory to the given path. + |% +DECLARE NATIVE FUNCTION chdir(path: String) + +%| + | Function: getcwd + | + | Return the working directory of the current process. + |% +DECLARE NATIVE FUNCTION getcwd(): String + +%| + | Function: getenv + | + | Return the value of an environment variable in the current process. + |% +DECLARE NATIVE FUNCTION getenv(name: String): String + +%| + | Function: platform + | + | Return the running platform type. + |% +DECLARE NATIVE FUNCTION platform(): Platform + +%| + | Function: system + | + | Execute a system (shell) command. + |% +DECLARE NATIVE FUNCTION system(command: String): Number + +%| + | Function: fork + | + | Fork the current process (POSIX only). + |% +DECLARE NATIVE FUNCTION fork(OUT child: Process): Boolean + +%| + | Function: kill + | + | Kill a process. + |% +DECLARE NATIVE FUNCTION kill(process: Process) + +%| + | Function: spawn + | + | Create a new process with the given command line. + |% +DECLARE NATIVE FUNCTION spawn(command: String): Process + +%| + | Function: wait + | + | Wait for a process to terminate. + |% +DECLARE NATIVE FUNCTION wait(INOUT process: Process): Number diff --git a/lib/os_posix.cpp b/lib/os_posix.cpp new file mode 100644 index 0000000000..4a1f7166f9 --- /dev/null +++ b/lib/os_posix.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl_exec.h" + +#include "enums.inc" + +struct Process { + pid_t pid; +}; + +static std::vector g_children; + +void closer() +{ + for (auto p: g_children) { + kill(-p->pid, 9); + } +} + +namespace rtl { + +void os$chdir(const std::string &path) +{ + int r = chdir(path.c_str()); + if (r != 0) { + throw RtlException(Exception_file$PathNotFoundException, path); + } +} + +std::string os$getcwd() +{ + char buf[MAXPATHLEN]; + return getcwd(buf, sizeof(buf)); +} + +Cell os$platform() +{ + return Cell(number_from_uint32(ENUM_Platform_posix)); +} + +bool os$fork(Cell **process) +{ + Process **pp = reinterpret_cast(process); + *pp = NULL; + pid_t child = fork(); + if (child < 0) { + throw RtlException(Exception_os$SystemException, std::to_string(errno)); + } + if (child > 0) { + *pp = new Process; + (*pp)->pid = child; + } + return child > 0; +} + +void os$kill(void *process) +{ + Process *p = reinterpret_cast(process); + kill(-p->pid, SIGTERM); + p->pid = 0; + auto pi = std::find(g_children.begin(), g_children.end(), p); + if (pi != g_children.end()) { + g_children.erase(pi); + } + delete p; +} + +void *os$spawn(const std::string &command) +{ + static bool init_closer = false; + if (not init_closer) { + atexit(closer); + init_closer = true; + } + + Process *p = new Process; + p->pid = fork(); + if (p->pid == 0) { + setpgid(0, 0); + g_children.clear(); + for (int fd = 3; fd <= 256; fd++) { + close(fd); + } + execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL); + _exit(127); + } + g_children.push_back(p); + return p; +} + +Number os$wait(Cell **process) +{ + int r; + { + Process **pp = reinterpret_cast(process); + waitpid((*pp)->pid, &r, 0); + (*pp)->pid = 0; + auto pi = std::find(g_children.begin(), g_children.end(), *pp); + if (pi != g_children.end()) { + g_children.erase(pi); + } + delete *pp; + *pp = NULL; + } + if (WIFEXITED(r)) { + return number_from_uint8(WEXITSTATUS(r)); + } + return number_from_sint8(-1); +} + +} diff --git a/lib/os_win32.cpp b/lib/os_win32.cpp new file mode 100644 index 0000000000..6f826cc0ed --- /dev/null +++ b/lib/os_win32.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + +#include "rtl_exec.h" + +#include "enums.inc" + +struct Process { + HANDLE process; +}; + +namespace rtl { + +void os$chdir(const std::string &path) +{ + BOOL r = SetCurrentDirectory(path.c_str()); + if (not r) { + throw RtlException(Exception_file$PathNotFoundException, path); + } +} + +std::string os$getcwd() +{ + char buf[MAX_PATH]; + GetCurrentDirectory(sizeof(buf), buf); + return buf; +} + +Cell os$platform() +{ + return Cell(number_from_uint32(ENUM_Platform_win32)); +} + +bool os$fork(Cell **process) +{ + Process **pp = reinterpret_cast(process); + *pp = NULL; + throw RtlException(Exception_os$UnsupportedFunctionException, "os.fork"); +} + +void os$kill(void *process) +{ + Process *p = reinterpret_cast(process); + TerminateProcess(p->process, 1); + CloseHandle(p->process); + p->process = INVALID_HANDLE_VALUE; + delete p; +} + +void *os$spawn(const std::string &command) +{ + static HANDLE job = INVALID_HANDLE_VALUE; + if (job == INVALID_HANDLE_VALUE) { + job = CreateJobObject(NULL, NULL); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli; + ZeroMemory(&jeli, sizeof(jeli)); + jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)); + } + + Process *p = new Process; + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + BOOL r = CreateProcess( + NULL, + const_cast(command.c_str()), + NULL, + NULL, + FALSE, + 0, + NULL, + NULL, + &si, + &pi); + if (not r) { + throw RtlException(Exception_file$PathNotFoundException, command.c_str()); + } + AssignProcessToJobObject(job, pi.hProcess); + p->process = pi.hProcess; + CloseHandle(pi.hThread); + return p; +} + +Number os$wait(Cell **process) +{ + DWORD r; + { + Process **pp = reinterpret_cast(process); + WaitForSingleObject((*pp)->process, INFINITE); + GetExitCodeProcess((*pp)->process, &r); + CloseHandle((*pp)->process); + (*pp)->process = INVALID_HANDLE_VALUE; + delete *pp; + *pp = NULL; + } + return number_from_uint32(r); +} + +} diff --git a/lib/random.cpp b/lib/random.cpp index c54de69a00..68033d3867 100644 --- a/lib/random.cpp +++ b/lib/random.cpp @@ -2,7 +2,8 @@ #include -static std::mt19937 g_gen; +static std::random_device g_rd; +static std::mt19937 g_gen(g_rd()); namespace rtl { diff --git a/lib/random.neon b/lib/random.neon new file mode 100644 index 0000000000..7b7329bca8 --- /dev/null +++ b/lib/random.neon @@ -0,0 +1,12 @@ +%| + | File: random + | + | Functions for generating random numbers. + |% + +%| + | Function: uint32 + | + | Return a random 32-bit unsigned integer with a uniform distribution. + |% +DECLARE NATIVE FUNCTION uint32(): Number diff --git a/lib/regex.cpp b/lib/regex.cpp new file mode 100644 index 0000000000..53aaf6421a --- /dev/null +++ b/lib/regex.cpp @@ -0,0 +1,43 @@ +#include +#include + +#include +#include + +#define PCRE2_STATIC +#define PCRE2_CODE_UNIT_WIDTH 8 +#include + +namespace rtl { + +bool regex$search(const std::string &pattern, const std::string &subject, Cell *match) +{ + std::vector matches; + int errorcode; + PCRE2_SIZE erroroffset; + pcre2_code *code = pcre2_compile(reinterpret_cast(pattern.data()), pattern.length(), 0, &errorcode, &erroroffset, NULL); + pcre2_match_data *md = pcre2_match_data_create_from_pattern(code, NULL); + int r = pcre2_match(code, reinterpret_cast(subject.data()), subject.length(), 0, 0, md, NULL); + if (r <= 0) { + return false; + } + uint32_t n = pcre2_get_ovector_count(md); + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(md); + for (uint32_t i = 0; i < n*2; i += 2) { + std::vector g; + if (ovector[i] != PCRE2_UNSET) { + g.push_back(Cell(true)); + g.push_back(Cell(number_from_uint32(static_cast(ovector[i])))); + g.push_back(Cell(number_from_uint32(static_cast(ovector[i+1])))); + g.push_back(Cell(subject.substr(ovector[i], ovector[i+1]-ovector[i]))); + } else { + g.push_back(Cell(false)); + } + matches.push_back(Cell(g)); + } + pcre2_match_data_free(md); + *match = Cell(matches); + return true; +} + +} // namespace rtl diff --git a/lib/regex.neon b/lib/regex.neon new file mode 100644 index 0000000000..fd7ecf6be2 --- /dev/null +++ b/lib/regex.neon @@ -0,0 +1,38 @@ +%| + | File: regex + | + | Functions for using regular expressions for text searching. + |% + +EXPORT Match + +%| + | Type: Group + | + | Represents a matching group as part of a array. + | + | Fields: + | start - starting index of group + | end - ending index of group + | group - text of group + |% +TYPE Group IS RECORD + matched: Boolean + start: Number + end: Number + group: String +END RECORD + +%| + | Type: Match + | + | Represents the result of a successful regex match. + |% +TYPE Match IS Array + +%| + | Function: search + | + | Search a string for a given subject regex. + |% +DECLARE NATIVE FUNCTION search(pattern: String, subject: String, OUT match: Match): Boolean diff --git a/lib/runtime.cpp b/lib/runtime.cpp new file mode 100644 index 0000000000..27c47a47d2 --- /dev/null +++ b/lib/runtime.cpp @@ -0,0 +1,31 @@ +#include "exec.h" +#include "number.h" + +namespace rtl { + +void runtime$garbageCollect() +{ + executor_garbage_collect(); +} + +Number runtime$getAllocatedObjectCount() +{ + return number_from_uint64(executor_get_allocated_object_count()); +} + +bool runtime$moduleIsMain() +{ + return executor_module_is_main(); +} + +void runtime$setGarbageCollectionInterval(Number count) +{ + executor_set_garbage_collection_interval(number_to_uint64(count)); +} + +void runtime$setRecursionLimit(Number depth) +{ + executor_set_recursion_limit(number_to_uint64(depth)); +} + +} // namespace rtl diff --git a/lib/runtime.neon b/lib/runtime.neon new file mode 100644 index 0000000000..f71b1eeb1a --- /dev/null +++ b/lib/runtime.neon @@ -0,0 +1,49 @@ +%| + | File: runtime + | + | Functions that interact with the Neon runtime system. + |% + +%| + | Function: garbageCollect + | + | Force a garbage collection immediately. It is not normally necessary + | to call this function, since garbage collection is automatically run + | after a predetermined number of new allocations. + | Use to change the automatic behaviour. + |% +DECLARE NATIVE FUNCTION garbageCollect() + +%| + | Function: getAllocatedObjectCount + | + | Return the number of objects currently allocated. This includes + | objects that may be eligible for garbage collection. + |% +DECLARE NATIVE FUNCTION getAllocatedObjectCount(): Number + +%| + | Function: moduleIsMain + | + | Return TRUE if the calling module is being run as the main program. + | Return FALSE if the calling module has been imported with IMPORT. + |% +DECLARE NATIVE FUNCTION moduleIsMain(): Boolean + +%| + | Function: setGarbageCollectionInterval + | + | Set the interval that determines when garbage collection will be run. + | The count refers to the number of allocations. The default is 1000 + | allocations. Setting this value to 0 disables automatic garbage + | collection. + |% +DECLARE NATIVE FUNCTION setGarbageCollectionInterval(count: Number) + +%| + | Function: setRecursionLimit + | + | Set the maximum permitted call stack depth. The default limit is + | 1000. Exceeding this limit raises a exception. + |% +DECLARE NATIVE FUNCTION setRecursionLimit(depth: Number) diff --git a/lib/sdl.cpp b/lib/sdl.cpp new file mode 100644 index 0000000000..caaf7f5b79 --- /dev/null +++ b/lib/sdl.cpp @@ -0,0 +1,187 @@ +#include + +#include "cell.h" + +SDL_Rect *pack_Rect(SDL_Rect &out, Cell &in) +{ + if (in.array_index_for_write(4).boolean()) { + return NULL; + } + out.x = number_to_sint32(in.array_index_for_read(0).number()); + out.y = number_to_sint32(in.array_index_for_read(1).number()); + out.w = number_to_sint32(in.array_index_for_read(2).number()); + out.h = number_to_sint32(in.array_index_for_read(3).number()); + return &out; +} + +void unpack_Event(Cell *out, const SDL_Event &in) +{ + out->array_index_for_write(0) = Cell(number_from_uint32(in.type)); + switch (in.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: + out->array_index_for_write(1).array_index_for_write(0) = Cell(number_from_uint32(in.key.timestamp)); + out->array_index_for_write(1).array_index_for_write(1) = Cell(number_from_uint32(in.key.windowID)); + out->array_index_for_write(1).array_index_for_write(2) = Cell(number_from_uint8(in.key.state)); + out->array_index_for_write(1).array_index_for_write(3) = Cell(number_from_uint8(in.key.repeat)); + out->array_index_for_write(1).array_index_for_write(4).array_index_for_write(0) = Cell(number_from_uint32(in.key.keysym.scancode)); + out->array_index_for_write(1).array_index_for_write(4).array_index_for_write(1) = Cell(number_from_uint32(in.key.keysym.sym)); + out->array_index_for_write(1).array_index_for_write(4).array_index_for_write(2) = Cell(number_from_uint16(in.key.keysym.mod)); + break; + } +} + +namespace rtl { + +void *sdl$CreateRGBSurface(Number flags, Number width, Number height, Number depth, Number Rmask, Number Gmask, Number Bmask, Number Amask) +{ + return SDL_CreateRGBSurface( + number_to_uint32(flags), + number_to_sint32(width), + number_to_sint32(height), + number_to_sint32(depth), + number_to_uint32(Rmask), + number_to_uint32(Gmask), + number_to_uint32(Bmask), + number_to_uint32(Amask) + ); +} + +void *sdl$CreateRenderer(void *window, Number index, Number flags) +{ + return SDL_CreateRenderer(static_cast(window), number_to_sint32(index), number_to_uint32(flags)); +} + +void *sdl$CreateSoftwareRenderer(void *surface) +{ + return SDL_CreateSoftwareRenderer(static_cast(surface)); +} + +void *sdl$CreateTextureFromSurface(void *renderer, void *surface) +{ + return SDL_CreateTextureFromSurface(static_cast(renderer), static_cast(surface)); +} + +void *sdl$CreateWindow(const std::string &title, Number x, Number y, Number w, Number h, Number flags) +{ + return SDL_CreateWindow(title.c_str(), number_to_sint32(x), number_to_sint32(y), number_to_sint32(w), number_to_sint32(h), number_to_uint32(flags)); +} + +void sdl$Delay(Number ms) +{ + SDL_Delay(number_to_uint32(ms)); +} + +void sdl$DestroyRenderer(void *renderer) +{ + SDL_DestroyRenderer(static_cast(renderer)); +} + +void sdl$DestroyTexture(void *texture) +{ + SDL_DestroyTexture(static_cast(texture)); +} + +void sdl$DestroyWindow(void *window) +{ + SDL_DestroyWindow(static_cast(window)); +} + +void sdl$FreeSurface(void *surface) +{ + SDL_FreeSurface(static_cast(surface)); +} + +void sdl$Init(Number flags) +{ + SDL_Init(number_to_uint32(flags)); +} + +void *sdl$LoadBMP(const std::string &file) +{ + return SDL_LoadBMP(file.c_str()); +} + +bool sdl$PollEvent(Cell *event) +{ + SDL_Event e; + int r = SDL_PollEvent(&e); + if (r != 0) { + unpack_Event(event, e); + } + return r != 0; +} + +void sdl$Quit() +{ + SDL_Quit(); +} + +void sdl$RenderClear(void *renderer) +{ + SDL_RenderClear(static_cast(renderer)); +} + +void sdl$RenderCopy(void *renderer, void *texture, Cell &srcrect, Cell &dstrect) +{ + SDL_Rect src, dst; + SDL_RenderCopy(static_cast(renderer), static_cast(texture), pack_Rect(src, srcrect), pack_Rect(dst, dstrect)); +} + +void sdl$RenderDrawLine(void *renderer, Number x1, Number y1, Number x2, Number y2) +{ + SDL_RenderDrawLine( + static_cast(renderer), + number_to_sint32(x1), + number_to_sint32(y1), + number_to_sint32(x2), + number_to_sint32(y2) + ); +} + +void sdl$RenderDrawLines(void *renderer, Cell &points) +{ + std::vector p(points.array().size()); + for (size_t i = 0; i < points.array().size(); i++) { + p[i].x = number_to_sint32(points.array_index_for_read(i).array_index_for_read(0).number()); + p[i].y = number_to_sint32(points.array_index_for_read(i).array_index_for_read(1).number()); + } + SDL_RenderDrawLines( + static_cast(renderer), + p.data(), + static_cast(p.size()) + ); +} + +void sdl$RenderDrawPoint(void *renderer, Number x, Number y) +{ + SDL_RenderDrawPoint( + static_cast(renderer), + number_to_sint32(x), + number_to_sint32(y) + ); +} + +void sdl$RenderFillRect(void *renderer, Cell &rect) +{ + SDL_Rect r; + SDL_RenderFillRect(static_cast(renderer), pack_Rect(r, rect)); +} + +void sdl$RenderPresent(void *renderer) +{ + SDL_RenderPresent(static_cast(renderer)); +} + +void sdl$SetRenderDrawColor(void *renderer, Number r, Number g, Number b, Number a) +{ + SDL_SetRenderDrawColor( + static_cast(renderer), + number_to_uint8(r), + number_to_uint8(g), + number_to_uint8(b), + number_to_uint8(a) + ); +} + +} // namespace rtl diff --git a/lib/sdl.neon b/lib/sdl.neon new file mode 100644 index 0000000000..de6b8706b0 --- /dev/null +++ b/lib/sdl.neon @@ -0,0 +1,84 @@ +%| + | File: sdl + | + | Provides access to the Simple DirectMedia Layer (https://www.libsdl.org/). + |% + +EXPORT NullRect +EXPORT Event +EXPORT Point +EXPORT Rect +EXPORT Renderer +EXPORT Surface +EXPORT Texture +EXPORT Window + +TYPE Renderer IS POINTER +TYPE Surface IS POINTER +TYPE Texture IS POINTER +TYPE Window IS POINTER + +TYPE Event IS RECORD + type: Number + key: RECORD + timestamp: Number + windowID: Number + state: Number + repeat: Boolean + keysym: RECORD + scancode: Number + sym: Number + mod: Number + END RECORD + END RECORD +END RECORD + +TYPE Point IS RECORD + x: Number + y: Number +END RECORD + +TYPE Rect IS RECORD + x: Number + y: Number + w: Number + h: Number + PRIVATE null: Boolean +END RECORD + +CONSTANT NullRect: Rect := Rect(0, 0, 0, 0, TRUE) + +DECLARE NATIVE CONSTANT INIT_VIDEO: Number +DECLARE NATIVE CONSTANT RENDERER_ACCELERATED: Number +DECLARE NATIVE CONSTANT RENDERER_PRESENTVSYNC: Number +DECLARE NATIVE CONSTANT SDL_KEYDOWN: Number +DECLARE NATIVE CONSTANT SDL_KEYUP: Number +DECLARE NATIVE CONSTANT SDL_QUIT: Number +DECLARE NATIVE CONSTANT SDLK_LEFT: Number +DECLARE NATIVE CONSTANT SDLK_RIGHT: Number +DECLARE NATIVE CONSTANT SDLK_SPACE: Number +DECLARE NATIVE CONSTANT SDLK_UP: Number +DECLARE NATIVE CONSTANT WINDOW_SHOWN: Number + +DECLARE NATIVE FUNCTION CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask: Number): Surface +DECLARE NATIVE FUNCTION CreateRenderer(window: Window, index: Number, flags: Number): Renderer +DECLARE NATIVE FUNCTION CreateSoftwareRenderer(surface: Surface): Renderer +DECLARE NATIVE FUNCTION CreateTextureFromSurface(renderer: Renderer, surface: Surface): Texture +DECLARE NATIVE FUNCTION CreateWindow(title: String, x, y, w, h: Number, flags: Number): Window +DECLARE NATIVE FUNCTION Delay(ms: Number) +DECLARE NATIVE FUNCTION DestroyRenderer(renderer: Renderer) +DECLARE NATIVE FUNCTION DestroyTexture(texture: Texture) +DECLARE NATIVE FUNCTION DestroyWindow(window: Window) +DECLARE NATIVE FUNCTION FreeSurface(surface: Surface) +DECLARE NATIVE FUNCTION Init(flags: Number) +DECLARE NATIVE FUNCTION LoadBMP(file: String): Surface +DECLARE NATIVE FUNCTION PollEvent(OUT event: Event): Boolean +DECLARE NATIVE FUNCTION Quit() +DECLARE NATIVE FUNCTION RenderClear(renderer: Renderer) +DECLARE NATIVE FUNCTION RenderCopy(renderer: Renderer, texture: Texture, srcrect, dstrect: Rect) +DECLARE NATIVE FUNCTION RenderDrawLine(renderer: Renderer, x1, y1, x2, y2: Number) +DECLARE NATIVE FUNCTION RenderDrawLines(renderer: Renderer, points: Array>) +DECLARE NATIVE FUNCTION RenderDrawPoint(renderer: Renderer, x, y: Number) +DECLARE NATIVE FUNCTION RenderFillRect(renderer: Renderer, rect: Rect) +DECLARE NATIVE FUNCTION RenderPresent(renderer: Renderer) +DECLARE NATIVE FUNCTION SetRenderDrawColor(renderer: Renderer, r, g, b, a: Number) diff --git a/lib/sdl_const.cpp b/lib/sdl_const.cpp new file mode 100644 index 0000000000..15c03832fe --- /dev/null +++ b/lib/sdl_const.cpp @@ -0,0 +1,19 @@ +#include + +#include "number.h" + +namespace rtl { + +extern const Number sdl$INIT_VIDEO = number_from_uint32(SDL_INIT_VIDEO); +extern const Number sdl$RENDERER_ACCELERATED = number_from_uint32(SDL_RENDERER_ACCELERATED); +extern const Number sdl$RENDERER_PRESENTVSYNC = number_from_uint32(SDL_RENDERER_PRESENTVSYNC); +extern const Number sdl$SDL_KEYDOWN = number_from_uint32(SDL_KEYDOWN); +extern const Number sdl$SDL_KEYUP = number_from_uint32(SDL_KEYUP); +extern const Number sdl$SDL_QUIT = number_from_uint32(SDL_QUIT); +extern const Number sdl$SDLK_LEFT = number_from_uint32(SDLK_LEFT); +extern const Number sdl$SDLK_RIGHT = number_from_uint32(SDLK_RIGHT); +extern const Number sdl$SDLK_SPACE = number_from_uint32(SDLK_SPACE); +extern const Number sdl$SDLK_UP = number_from_uint32(SDLK_UP); +extern const Number sdl$WINDOW_SHOWN = number_from_uint32(SDL_WINDOW_SHOWN); + +} // namespace rtl diff --git a/lib/sodium.cpp b/lib/sodium.cpp new file mode 100644 index 0000000000..ac7327d0d2 --- /dev/null +++ b/lib/sodium.cpp @@ -0,0 +1,718 @@ +#include + +#include "rtl_exec.h" + +#ifdef _MSC_VER +#pragma warning(disable: 4324) +#endif +#include + +namespace rtl { + +void sodium$init() +{ + sodium_init(); +} + +std::string sodium$randombytes(Number size) +{ + unsigned long long isize = number_to_uint64(size); + if (isize == 0) { + return std::string(); + } + std::vector buf(isize); + randombytes(buf.data(), isize); + return std::string(reinterpret_cast(buf.data()), buf.size()); +} + +void sodium$randombytes_close() +{ + randombytes_close(); +} + +Number sodium$randombytes_random() +{ + return number_from_uint32(randombytes_random()); +} + +Number sodium$randombytes_uniform(Number upper_bound) +{ + return number_from_uint32(randombytes_uniform(number_to_uint32(upper_bound))); +} + +bool sodium$aead_aes256gcm_is_available() +{ + return crypto_aead_aes256gcm_is_available() != 0; +} + +typedef int (*crypto_hash_f)(unsigned char *, const unsigned char *, unsigned long long, const unsigned char *); +typedef int (*crypto_hash_verify_f)(const unsigned char *, const unsigned char *, unsigned long long, const unsigned char *); + +class AuthTraits_default { +public: + static const size_t KEYBYTES = crypto_auth_KEYBYTES; + static const size_t BYTES = crypto_auth_BYTES; + static const crypto_hash_f auth; + static const crypto_hash_verify_f verify; +}; +const crypto_hash_f AuthTraits_default::auth = crypto_auth; +const crypto_hash_verify_f AuthTraits_default::verify = crypto_auth_verify; + +class AuthTraits_hmacsha256 { +public: + static const size_t KEYBYTES = crypto_auth_hmacsha256_KEYBYTES; + static const size_t BYTES = crypto_auth_hmacsha256_BYTES; + static const crypto_hash_f auth; + static const crypto_hash_verify_f verify; +}; +const crypto_hash_f AuthTraits_hmacsha256::auth = crypto_auth_hmacsha256; +const crypto_hash_verify_f AuthTraits_hmacsha256::verify = crypto_auth_hmacsha256_verify; + +class AuthTraits_hmacsha512 { +public: + static const size_t KEYBYTES = crypto_auth_hmacsha512_KEYBYTES; + static const size_t BYTES = crypto_auth_hmacsha512_BYTES; + static const crypto_hash_f auth; + static const crypto_hash_verify_f verify; +}; +const crypto_hash_f AuthTraits_hmacsha512::auth = crypto_auth_hmacsha512; +const crypto_hash_verify_f AuthTraits_hmacsha512::verify = crypto_auth_hmacsha512_verify; + +template std::string crypto_auth_internal(const std::string &message, const std::string &key) +{ + if (key.size() != AuthTraits::KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + unsigned char mac[AuthTraits::BYTES]; + AuthTraits::auth( + mac, + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(mac), sizeof(mac)); +} + +template bool crypto_auth_verify_internal(const std::string &auth, const std::string &message, const std::string &key) +{ + if (auth.size() != AuthTraits::BYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "auth"); + } + if (key.size() != AuthTraits::KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + return AuthTraits::verify( + reinterpret_cast(auth.data()), + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(key.data()) + ) == 0; +} + +std::string sodium$auth(const std::string &message, const std::string &key) +{ + return crypto_auth_internal(message, key); +} + +bool sodium$auth_verify(const std::string &auth, const std::string &message, const std::string &key) +{ + return crypto_auth_verify_internal(auth, message, key); +} + +std::string sodium$auth_hmacsha256(const std::string &message, const std::string &key) +{ + return crypto_auth_internal(message, key); +} + +bool sodium$auth_hmacsha256_verify(const std::string &auth, const std::string &message, const std::string &key) +{ + return crypto_auth_verify_internal(auth, message, key); +} + +std::string sodium$auth_hmacsha512(const std::string &message, const std::string &key) +{ + return crypto_auth_internal(message, key); +} + +bool sodium$auth_hmacsha512_verify(const std::string &auth, const std::string &message, const std::string &key) +{ + return crypto_auth_verify_internal(auth, message, key); +} + +std::string sodium$aead_aes256gcm_encrypt(const std::string &message, const std::string &ad, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_aead_aes256gcm_NPUBBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_aead_aes256gcm_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector ciphertext(message.size() + crypto_aead_aes256gcm_ABYTES); + unsigned long long ciphertext_len; + crypto_aead_aes256gcm_encrypt( + ciphertext.data(), + &ciphertext_len, + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(ad.data()), + ad.size(), + NULL, + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(ciphertext.data()), ciphertext_len); +} + +std::string sodium$aead_aes256gcm_decrypt(const std::string &ciphertext, const std::string &ad, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_aead_aes256gcm_NPUBBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_aead_aes256gcm_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector decrypted(ciphertext.size()); + unsigned long long decrypted_len; + crypto_aead_aes256gcm_decrypt( + decrypted.data(), + &decrypted_len, + NULL, + reinterpret_cast(ciphertext.data()), + ciphertext.size(), + reinterpret_cast(ad.data()), + ad.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(decrypted.data()), decrypted_len); +} + +std::string sodium$box(const std::string &message, const std::string &nonce, const std::string &pk, const std::string &sk) +{ + if (nonce.size() != crypto_box_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (pk.size() != crypto_box_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "public key"); + } + if (sk.size() != crypto_box_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "secret key"); + } + unsigned long long ciphertext_len = crypto_box_MACBYTES + message.size(); + std::vector ciphertext(ciphertext_len); + crypto_box_easy( + ciphertext.data(), + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(pk.data()), + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(ciphertext.data()), ciphertext_len); +} + +void sodium$box_keypair(std::string *pk, std::string *sk) +{ + unsigned char pkbytes[crypto_box_PUBLICKEYBYTES]; + unsigned char skbytes[crypto_box_SECRETKEYBYTES]; + crypto_box_keypair(pkbytes, skbytes); + *pk = std::string(reinterpret_cast(pkbytes), sizeof(pkbytes)); + *sk = std::string(reinterpret_cast(skbytes), sizeof(skbytes)); +} + +std::string sodium$box_open(const std::string &ciphertext, const std::string &nonce, const std::string &pk, const std::string &sk) +{ + if (ciphertext.size() < crypto_box_MACBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "ciphertext"); + } + if (nonce.size() != crypto_box_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (pk.size() != crypto_box_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "public key"); + } + if (sk.size() != crypto_box_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "secret key"); + } + std::vector decrypted(ciphertext.size() - crypto_box_MACBYTES); + if (crypto_box_open_easy( + decrypted.data(), + reinterpret_cast(ciphertext.data()), + ciphertext.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(pk.data()), + reinterpret_cast(sk.data()) + ) != 0) { + throw RtlException(Exception_sodium$DecryptionFailedException, ""); + } + return std::string(reinterpret_cast(decrypted.data()), decrypted.size()); +} + +std::string sodium$box_seal(const std::string &message, const std::string &pk) +{ + if (pk.size() != crypto_box_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "public key"); + } + std::vector ciphertext(crypto_box_SEALBYTES + message.size()); + crypto_box_seal( + ciphertext.data(), + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(pk.data()) + ); + return std::string(reinterpret_cast(ciphertext.data()), ciphertext.size()); +} + +std::string sodium$box_seal_open(const std::string &ciphertext, const std::string &pk, const std::string &sk) +{ + if (ciphertext.size() < crypto_box_SEALBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "ciphertext"); + } + if (pk.size() != crypto_box_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "public key"); + } + if (sk.size() != crypto_box_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "secret key"); + } + std::vector decrypted(ciphertext.size() - crypto_box_SEALBYTES); + if (crypto_box_seal_open( + decrypted.data(), + reinterpret_cast(ciphertext.data()), + ciphertext.size(), + reinterpret_cast(pk.data()), + reinterpret_cast(sk.data()) + ) != 0) { + throw RtlException(Exception_sodium$DecryptionFailedException, ""); + } + return std::string(reinterpret_cast(decrypted.data()), decrypted.size()); +} + +void sodium$box_seed_keypair(std::string *pk, std::string *sk, const std::string &seed) +{ + if (seed.size() != crypto_box_SEEDBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "seed"); + } + unsigned char pkbytes[crypto_box_PUBLICKEYBYTES]; + unsigned char skbytes[crypto_box_SECRETKEYBYTES]; + crypto_box_seed_keypair(pkbytes, skbytes, reinterpret_cast(seed.data())); + *pk = std::string(reinterpret_cast(pkbytes), sizeof(pkbytes)); + *sk = std::string(reinterpret_cast(skbytes), sizeof(skbytes)); +} + +std::string sodium$generichash(Number outlen, const std::string &in, const std::string &key) +{ + size_t len = number_to_uint32(outlen); + if (len < 1 /*crypto_generichash_BYTES_MIN*/ || len > crypto_generichash_BYTES_MAX) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "outlen"); + } + if (key.size() > crypto_generichash_KEYBYTES_MAX) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(len); + crypto_generichash( + out.data(), + len, + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(key.data()), + key.size() + ); + return std::string(reinterpret_cast(out.data()), len); +} + +std::string sodium$hash(const std::string &in) +{ + std::vector out(crypto_hash_BYTES); + crypto_hash( + out.data(), + reinterpret_cast(in.data()), + in.size() + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$hash_sha256(const std::string &in) +{ + std::vector out(crypto_hash_sha256_BYTES); + crypto_hash_sha256( + out.data(), + reinterpret_cast(in.data()), + in.size() + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$onetimeauth(const std::string &in, const std::string &key) +{ + if (key.size() != crypto_onetimeauth_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(crypto_onetimeauth_BYTES); + crypto_onetimeauth( + out.data(), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +bool sodium$onetimeauth_verify(const std::string &auth, const std::string &in, const std::string &key) +{ + if (auth.size() != crypto_onetimeauth_BYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "auth"); + } + if (key.size() != crypto_onetimeauth_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + return crypto_onetimeauth_verify( + reinterpret_cast(auth.data()), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(key.data()) + ) == 0; +} + +std::string sodium$pwhash_scryptsalsa208sha256(Number outlen, const std::string &passwd, const std::string &salt, Number opslimit, Number memlimit) +{ + if (salt.size() != crypto_pwhash_scryptsalsa208sha256_SALTBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "salt"); + } + std::vector out(number_to_uint32(outlen)); + crypto_pwhash_scryptsalsa208sha256( + out.data(), + out.size(), + passwd.c_str(), + passwd.size(), + reinterpret_cast(salt.data()), + number_to_uint64(opslimit), + number_to_uint32(memlimit) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$pwhash_scryptsalsa208sha256_str(const std::string &passwd, Number opslimit, Number memlimit) +{ + std::vector out(crypto_pwhash_scryptsalsa208sha256_STRBYTES); + crypto_pwhash_scryptsalsa208sha256_str( + out.data(), + passwd.c_str(), + passwd.size(), + number_to_uint64(opslimit), + number_to_uint32(memlimit) + ); + return std::string(out.data(), out.size()); +} + +bool sodium$pwhash_scryptsalsa208sha256_str_verify(const std::string &str, const std::string &passwd) +{ + return crypto_pwhash_scryptsalsa208sha256_str_verify( + str.c_str(), + passwd.c_str(), + passwd.size() + ) == 0; +} + +std::string sodium$scalarmult(const std::string &sk, const std::string &pk) +{ + if (sk.size() != crypto_box_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + if (pk.size() != crypto_box_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "pk"); + } + std::vector out(crypto_scalarmult_BYTES); + crypto_scalarmult( + out.data(), + reinterpret_cast(sk.data()), + reinterpret_cast(pk.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$scalarmult_base(const std::string &sk) +{ + if (sk.size() != crypto_box_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + std::vector out(crypto_box_PUBLICKEYBYTES); + crypto_scalarmult_base( + out.data(), + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$secretbox(const std::string &message, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_secretbox_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_secretbox_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(crypto_secretbox_MACBYTES + message.size()); + crypto_secretbox_easy( + out.data(), + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$secretbox_open(const std::string &ciphertext, const std::string &nonce, const std::string &key) +{ + if (ciphertext.size() < crypto_secretbox_MACBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "ciphertext"); + } + if (nonce.size() != crypto_secretbox_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_secretbox_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(ciphertext.size() - crypto_secretbox_MACBYTES); + if (crypto_secretbox_open_easy( + out.data(), + reinterpret_cast(ciphertext.data()), + ciphertext.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ) != 0) { + throw RtlException(Exception_sodium$DecryptionFailedException, ""); + } + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$shorthash(const std::string &in, const std::string &key) +{ + if (key.size() != crypto_shorthash_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(crypto_shorthash_BYTES); + crypto_shorthash( + out.data(), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$sign(const std::string &message, const std::string &sk) +{ + if (sk.size() != crypto_sign_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + std::vector out(crypto_sign_BYTES + message.size()); + unsigned long long outlen; + crypto_sign( + out.data(), + &outlen, + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(out.data()), outlen); +} + +std::string sodium$sign_open(const std::string &signedmessage, const std::string &pk) +{ + if (signedmessage.size() < crypto_sign_BYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "signedmessage"); + } + if (pk.size() != crypto_sign_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "pk"); + } + std::vector out(signedmessage.size() - crypto_sign_BYTES); + unsigned long long outlen; + if (crypto_sign_open( + out.data(), + &outlen, + reinterpret_cast(signedmessage.data()), + signedmessage.size(), + reinterpret_cast(pk.data()) + ) != 0) { + throw RtlException(Exception_sodium$DecryptionFailedException, ""); + } + return std::string(reinterpret_cast(out.data()), outlen); +} + +std::string sodium$sign_detached(const std::string &message, const std::string &sk) +{ + if (sk.size() != crypto_sign_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + std::vector out(crypto_sign_BYTES); + unsigned long long outlen; + crypto_sign_detached( + out.data(), + &outlen, + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(out.data()), outlen); +} + +bool sodium$sign_verify_detached(const std::string &sig, const std::string &message, const std::string &pk) +{ + if (sig.size() != crypto_sign_BYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sig"); + } + if (pk.size() != crypto_sign_PUBLICKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "pk"); + } + return crypto_sign_verify_detached( + reinterpret_cast(sig.data()), + reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(pk.data()) + ) == 0; +} + +void sodium$sign_keypair(std::string *pk, std::string *sk) +{ + unsigned char pkbytes[crypto_sign_PUBLICKEYBYTES]; + unsigned char skbytes[crypto_sign_SECRETKEYBYTES]; + crypto_sign_keypair(pkbytes, skbytes); + *pk = std::string(reinterpret_cast(pkbytes), sizeof(pkbytes)); + *sk = std::string(reinterpret_cast(skbytes), sizeof(skbytes)); +} + +void sodium$sign_seed_keypair(std::string *pk, std::string *sk, const std::string &seed) +{ + if (seed.size() != crypto_sign_SEEDBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "seed"); + } + unsigned char pkbytes[crypto_sign_PUBLICKEYBYTES]; + unsigned char skbytes[crypto_sign_SECRETKEYBYTES]; + crypto_sign_seed_keypair(pkbytes, skbytes, reinterpret_cast(seed.data())); + *pk = std::string(reinterpret_cast(pkbytes), sizeof(pkbytes)); + *sk = std::string(reinterpret_cast(skbytes), sizeof(skbytes)); +} + +std::string sodium$sign_ed25519_sk_to_seed(const std::string &sk) +{ + if (sk.size() != crypto_sign_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + unsigned char seedbytes[crypto_sign_SEEDBYTES]; + crypto_sign_ed25519_sk_to_seed( + seedbytes, + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(seedbytes), sizeof(seedbytes)); +} + +std::string sodium$sign_ed25519_sk_to_pk(const std::string &sk) +{ + if (sk.size() != crypto_sign_SECRETKEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "sk"); + } + unsigned char pkbytes[crypto_sign_PUBLICKEYBYTES]; + crypto_sign_ed25519_sk_to_pk( + pkbytes, + reinterpret_cast(sk.data()) + ); + return std::string(reinterpret_cast(pkbytes), sizeof(pkbytes)); +} + +std::string sodium$stream(Number len, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_stream_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_stream_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(number_to_uint32(len)); + crypto_stream( + out.data(), + out.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$stream_xor(const std::string &in, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_stream_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_stream_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(in.size()); + crypto_stream_xor( + out.data(), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$stream_xsalsa20_xor_ic(const std::string &in, const std::string &nonce, Number ic, const std::string &key) +{ + if (nonce.size() != crypto_stream_xsalsa20_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_stream_xsalsa20_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(in.size()); + crypto_stream_xsalsa20_xor_ic( + out.data(), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(nonce.data()), + number_to_uint64(ic), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$stream_salsa20(Number len, const std::string &nonce, const std::string &key) +{ + if (nonce.size() != crypto_stream_salsa20_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_stream_salsa20_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(number_to_uint32(len)); + crypto_stream_salsa20( + out.data(), + out.size(), + reinterpret_cast(nonce.data()), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +std::string sodium$stream_salsa20_xor_ic(const std::string &in, const std::string &nonce, Number ic, const std::string &key) +{ + if (nonce.size() != crypto_stream_salsa20_NONCEBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "nonce"); + } + if (key.size() != crypto_stream_salsa20_KEYBYTES) { + throw RtlException(Exception_sodium$InvalidParameterLengthException, "key"); + } + std::vector out(in.size()); + crypto_stream_salsa20_xor_ic( + out.data(), + reinterpret_cast(in.data()), + in.size(), + reinterpret_cast(nonce.data()), + number_to_uint64(ic), + reinterpret_cast(key.data()) + ); + return std::string(reinterpret_cast(out.data()), out.size()); +} + +} diff --git a/lib/sodium.neon b/lib/sodium.neon new file mode 100644 index 0000000000..10e6c407de --- /dev/null +++ b/lib/sodium.neon @@ -0,0 +1,79 @@ +%| + | File: sodium + | + | Interface file for https://libsodium.org. + |% + +EXPORT DecryptionFailedException +EXPORT InvalidParameterLengthException + +DECLARE EXCEPTION DecryptionFailedException +DECLARE EXCEPTION InvalidParameterLengthException + +DECLARE NATIVE CONSTANT box_NONCEBYTES: Number +DECLARE NATIVE CONSTANT generichash_BYTES_MAX: Number +DECLARE NATIVE CONSTANT generichash_KEYBYTES_MAX: Number +DECLARE NATIVE CONSTANT secretbox_KEYBYTES: Number +DECLARE NATIVE CONSTANT secretbox_NONCEBYTES: Number +DECLARE NATIVE CONSTANT shorthash_KEYBYTES: Number +DECLARE NATIVE CONSTANT sign_BYTES: Number + +DECLARE NATIVE FUNCTION init() + +DECLARE NATIVE FUNCTION randombytes(size: Number): Bytes +DECLARE NATIVE FUNCTION randombytes_close() +DECLARE NATIVE FUNCTION randombytes_random(): Number +DECLARE NATIVE FUNCTION randombytes_uniform(upper_bound: Number): Number + +DECLARE NATIVE FUNCTION aead_aes256gcm_is_available(): Boolean + +DECLARE NATIVE FUNCTION auth(message: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION auth_verify(auth: Bytes, message: Bytes, key: Bytes): Boolean +DECLARE NATIVE FUNCTION auth_hmacsha256(message: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION auth_hmacsha256_verify(auth: Bytes, message: Bytes, key: Bytes): Boolean +DECLARE NATIVE FUNCTION auth_hmacsha512(message: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION auth_hmacsha512_verify(auth: Bytes, message: Bytes, key: Bytes): Boolean + +DECLARE NATIVE FUNCTION aead_aes256gcm_encrypt(message: Bytes, ad: Bytes, nonce: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION aead_aes256gcm_decrypt(ciphertext: Bytes, ad: Bytes, nonce: Bytes, key: Bytes): Bytes + +DECLARE NATIVE FUNCTION box(message: Bytes, nonce: Bytes, pk: Bytes, sk: Bytes): Bytes +DECLARE NATIVE FUNCTION box_keypair(OUT pk: Bytes, OUT sk: Bytes) +DECLARE NATIVE FUNCTION box_open(ciphertext: Bytes, nonce: Bytes, pk: Bytes, sk: Bytes): Bytes +DECLARE NATIVE FUNCTION box_seal(message: Bytes, pk: Bytes): Bytes +DECLARE NATIVE FUNCTION box_seal_open(ciphertext: Bytes, pk: Bytes, sk: Bytes): Bytes +DECLARE NATIVE FUNCTION box_seed_keypair(OUT pk: Bytes, OUT sk: Bytes, seed: Bytes) + +DECLARE NATIVE FUNCTION generichash(outlen: Number, in: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION hash(in: Bytes): Bytes +DECLARE NATIVE FUNCTION hash_sha256(in: Bytes): Bytes + +DECLARE NATIVE FUNCTION onetimeauth(in: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION onetimeauth_verify(auth: Bytes, in: Bytes, key: Bytes): Boolean + +DECLARE NATIVE FUNCTION pwhash_scryptsalsa208sha256(outlen: Number, passwd: Bytes, salt: Bytes, opslimit: Number, memlimit: Number): Bytes +DECLARE NATIVE FUNCTION pwhash_scryptsalsa208sha256_str(passwd: String, opslimit: Number, memlimit: Number): String +DECLARE NATIVE FUNCTION pwhash_scryptsalsa208sha256_str_verify(str: String, passwd: String): Boolean + +DECLARE NATIVE FUNCTION scalarmult(sk: Bytes, pk: Bytes): Bytes +DECLARE NATIVE FUNCTION scalarmult_base(sk: Bytes): Bytes + +DECLARE NATIVE FUNCTION secretbox(message: Bytes, nonce: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION secretbox_open(ciphertext: Bytes, nonce: Bytes, key: Bytes): Bytes + +DECLARE NATIVE FUNCTION shorthash(in: Bytes, key: Bytes): Bytes + +DECLARE NATIVE FUNCTION sign(message: Bytes, sk: Bytes): Bytes +DECLARE NATIVE FUNCTION sign_open(signedmessage: Bytes, pk: Bytes): Bytes +DECLARE NATIVE FUNCTION sign_detached(message: Bytes, sk: Bytes): Bytes +DECLARE NATIVE FUNCTION sign_verify_detached(sig: Bytes, message: Bytes, pk: Bytes): Boolean +DECLARE NATIVE FUNCTION sign_keypair(OUT pk: Bytes, OUT sk: Bytes) +DECLARE NATIVE FUNCTION sign_seed_keypair(OUT pk: Bytes, OUT sk: Bytes, seed: Bytes) +DECLARE NATIVE FUNCTION sign_ed25519_sk_to_seed(sk: Bytes): Bytes +DECLARE NATIVE FUNCTION sign_ed25519_sk_to_pk(sk: Bytes): Bytes + +DECLARE NATIVE FUNCTION stream(len: Number, nonce: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION stream_xor(in: Bytes, nonce: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION stream_xsalsa20_xor_ic(in: Bytes, nonce: Bytes, ic: Number, key: Bytes): Bytes +DECLARE NATIVE FUNCTION stream_salsa20(len: Number, nonce: Bytes, key: Bytes): Bytes +DECLARE NATIVE FUNCTION stream_salsa20_xor_ic(in: Bytes, nonce: Bytes, ic: Number, key: Bytes): Bytes diff --git a/lib/sodium_const.cpp b/lib/sodium_const.cpp new file mode 100644 index 0000000000..a694d1ce26 --- /dev/null +++ b/lib/sodium_const.cpp @@ -0,0 +1,18 @@ +#include "number.h" + +#ifdef _MSC_VER +#pragma warning(disable: 4324) +#endif +#include + +namespace rtl { + +extern const Number sodium$box_NONCEBYTES = number_from_uint32(crypto_box_NONCEBYTES); +extern const Number sodium$generichash_BYTES_MAX = number_from_uint32(crypto_generichash_BYTES_MAX); +extern const Number sodium$generichash_KEYBYTES_MAX = number_from_uint32(crypto_generichash_BYTES_MAX); +extern const Number sodium$secretbox_KEYBYTES = number_from_uint32(crypto_secretbox_KEYBYTES); +extern const Number sodium$secretbox_NONCEBYTES = number_from_uint32(crypto_secretbox_NONCEBYTES); +extern const Number sodium$shorthash_KEYBYTES = number_from_uint32(crypto_shorthash_KEYBYTES); +extern const Number sodium$sign_BYTES = number_from_uint32(crypto_sign_BYTES); + +} diff --git a/lib/sqlite.cpp b/lib/sqlite.cpp new file mode 100644 index 0000000000..9877646650 --- /dev/null +++ b/lib/sqlite.cpp @@ -0,0 +1,45 @@ +#include + +#include + +#include "cell.h" + +static int callback(void *rowscell, int columns, char **values, char ** /*names*/) +{ + std::vector *rows = static_cast *>(rowscell); + std::vector row; + for (int i = 0; i < columns; i++) { + row.push_back(Cell(values[i])); + } + rows->push_back(Cell(row)); + return 0; +} + +namespace rtl { + +void *sqlite$open(const std::string &name) +{ + sqlite3 *db; + int r = sqlite3_open(name.c_str(), &db); + if (r != SQLITE_OK) { + } + return db; +} + +Cell sqlite$exec(void *db, const std::string &sql) +{ + std::vector rows; + char *errmsg; + int r = sqlite3_exec(static_cast(db), sql.c_str(), callback, &rows, &errmsg); + if (r != SQLITE_OK) { + fprintf(stderr, "sqlite3_exec error: %s\n", errmsg); + } + return Cell(rows); +} + +void sqlite$close(void *db) +{ + sqlite3_close(static_cast(db)); +} + +} // namespace rtl diff --git a/lib/sqlite.neon b/lib/sqlite.neon new file mode 100644 index 0000000000..981edb3256 --- /dev/null +++ b/lib/sqlite.neon @@ -0,0 +1,51 @@ +%| + | File: sqlite + | + | Functions for working with SQLite relational databases. + |% + +EXPORT Database +EXPORT Row +EXPORT Rows + +%| + | Type: Database + | + | Opaque type representing a SQLite database. + |% +TYPE Database IS POINTER + +%| + | Type: Row + | + | Represents a row in a results. + |% +TYPE Row IS Array + +%| + | Type: Rows + | + | Represents a query result set. + |% +TYPE Rows IS Array + +%| + | Function: open + | + | Open a database in the given named file. + |% +DECLARE NATIVE FUNCTION open(name: String): Database + +%| + | Function: exec + | + | Execute a SQL statement in the given database and return the result set. + |% +DECLARE NATIVE FUNCTION exec(db: Database, sql: String): Rows + +%| + | Function: close + | + | Close a database. + |% +DECLARE NATIVE FUNCTION close(db: Database) diff --git a/lib/string.cpp b/lib/string.cpp new file mode 100644 index 0000000000..3fd4b6bb5a --- /dev/null +++ b/lib/string.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include "number.h" +#include "utf8string.h" + +namespace rtl { + +Number string$find(const std::string &s, const std::string &t) +{ + std::string::size_type i = s.find(t); + if (i == std::string::npos) { + return number_from_sint64(-1); + } + return number_from_uint64(i); +} + +bool string$hasPrefix(const std::string &s, const std::string &prefix) +{ + return s.substr(0, prefix.length()) == prefix; +} + +bool string$hasSuffix(const std::string &s, const std::string &suffix) +{ + return s.length() >= suffix.length() && s.substr(s.length() - suffix.length()) == suffix; +} + +std::string string$join(const std::vector &a, const std::string &d) +{ + std::string r; + for (auto s: a) { + if (not r.empty()) { + r.append(d); + } + r.append(s.str()); + } + return r; +} + +std::string string$lower(const std::string &s) +{ + std::string r; + std::transform(s.begin(), s.end(), std::back_inserter(r), ::tolower); + return r; +} + +std::vector string$split(const std::string &s, const std::string &d) +{ + std::vector r; + std::string::size_type i = 0; + while (i < s.length()) { + std::string::size_type nd = s.find(d, i); + if (nd == std::string::npos) { + r.push_back(s.substr(i)); + break; + } else if (nd > i) { + r.push_back(s.substr(i, nd-i)); + } + i = nd + d.length(); + } + return r; +} + +std::string string$trim(const std::string &s) +{ + std::string::size_type first = s.find_first_not_of(' '); + std::string::size_type last = s.find_last_not_of(' '); + if (first == std::string::npos || last == std::string::npos) { + return ""; + } + return s.substr(first, last-first+1); +} + +std::string string$upper(const std::string &s) +{ + std::string r; + std::transform(s.begin(), s.end(), std::back_inserter(r), ::toupper); + return r; +} + +} diff --git a/lib/string.neon b/lib/string.neon new file mode 100644 index 0000000000..431ee2e540 --- /dev/null +++ b/lib/string.neon @@ -0,0 +1,61 @@ +%| + | File: string + | + | Functions for common string operations. + |% + +%| + | Function: find + | + | Find the index of a string within another. + |% +DECLARE NATIVE FUNCTION find(s: String, t: String): Number + +%| + | Function: hasPrefix + | + | Return TRUE if a string has a specific prefix. + |% +DECLARE NATIVE FUNCTION hasPrefix(s: String, prefix: String): Boolean + +%| + | Function: hasSuffix + | + | Return TRUE if a string has a specific suffix. + |% +DECLARE NATIVE FUNCTION hasSuffix(s: String, suffix: String): Boolean + +%| + | Function: join + | + | Join an array of strings together using the given delimiter. + |% +DECLARE NATIVE FUNCTION join(a: Array, d: String): String + +%| + | Function: lower + | + | Return a string with all alphabetic characters converted to lower case. + |% +DECLARE NATIVE FUNCTION lower(s: String): String + +%| + | Function: split + | + | Split a string into parts using the given delimiter. + |% +DECLARE NATIVE FUNCTION split(s: String, d: String): Array + +%| + | Function: trim + | + | Trim whitespace from the start and end of a string. + |% +DECLARE NATIVE FUNCTION trim(s: String): String + +%| + | Function: upper + | + | Return a string with all alphabetic characters converted to upper case. + |% +DECLARE NATIVE FUNCTION upper(s: String): String diff --git a/lib/struct.neon b/lib/struct.neon new file mode 100644 index 0000000000..4f2b6f65ec --- /dev/null +++ b/lib/struct.neon @@ -0,0 +1,874 @@ +%| + | File: struct + | + | Functions for working with fixed-size structures. + |% + +EXPORT Struct +EXPORT Field +EXPORT Type +EXPORT make +EXPORT field +EXPORT packBool +EXPORT packInt8 +EXPORT packInt16BE +EXPORT packInt16LE +EXPORT packInt32BE +EXPORT packInt32LE +EXPORT packInt64BE +EXPORT packInt64LE +EXPORT packUInt8 +EXPORT packUInt16BE +EXPORT packUInt16LE +EXPORT packUInt32BE +EXPORT packUInt32LE +EXPORT packUInt64BE +EXPORT packUInt64LE +EXPORT unpackBool +EXPORT unpackInt8 +EXPORT unpackInt16BE +EXPORT unpackInt16LE +EXPORT unpackInt32BE +EXPORT unpackInt32LE +EXPORT unpackInt64BE +EXPORT unpackInt64LE +EXPORT unpackUInt8 +EXPORT unpackUInt16BE +EXPORT unpackUInt16LE +EXPORT unpackUInt32BE +EXPORT unpackUInt32LE +EXPORT unpackUInt64BE +EXPORT unpackUInt64LE + +IMPORT bitwise +IMPORT variant + +%| + | Enumeration: Type + | + | The type of a field. + | + | Values: + | bool - boolean + | int8 - signed 8 bit integer + | int16BE - signed 16 bit integer, big endian + | int16LE - signed 16 bit integer, little endian + | int32BE - signed 32 bit integer, big endian + | int32LE - signed 32 bit integer, little endian + | int64BE - signed 64 bit integer, big endian + | int64LE - signed 64 bit integer, little endian + | uint8 - unsigned 8 bit integer + | uint16BE - unsigned 16 bit integer, big endian + | uint16LE - unsigned 16 bit integer, little endian + | uint32BE - unsigned 32 bit integer, big endian + | uint32LE - unsigned 32 bit integer, little endian + | uint64BE - unsigned 64 bit integer, big endian + | uint64LE - unsigned 64 bit integer, little endian + | string - string + | bytes - bytes + |% +TYPE Type IS ENUM + bool + int8 + int16BE + int16LE + int32BE + int32LE + int64BE + int64LE + uint8 + uint16BE + uint16LE + uint32BE + uint32LE + uint64BE + uint64LE + string + bytes +END ENUM + +%| + | Type: Field + | + | Represents a field in a structure. + |% +TYPE Field IS RECORD + name: String + type: Type + width: Number +END RECORD + +%| + | Type: Struct + | + | Represents a complete structure as a sequence of fields. + |% +TYPE Struct IS RECORD + PRIVATE fields: Array +END RECORD + +%| + | Function: make + | + | Make a based on an array of . + |% +FUNCTION make(fields: Array): Struct + RETURN Struct(fields) +END FUNCTION + +%| + | Function: field + | + | Helper function to create records. + |% +FUNCTION field(name: String, type: Type, width: Number): Field + RETURN Field(name, type, width) +END FUNCTION + +%| + | Function: packBool + | + | Pack a value into a . + |% +FUNCTION packBool(b: Boolean): Bytes + RETURN IF b THEN HEXBYTES "01" ELSE HEXBYTES "00" +END FUNCTION + +%| + | Function: packInt8 + | + | Pack a signed into a of width 1. + |% +FUNCTION packInt8(n: Number): Bytes + IF NOT (-0x80 <= n <= 0x7F) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + RETURN packUInt8(IF n < 0 THEN 0x100 + n ELSE n) +END FUNCTION + +%| + | Function: packInt16BE + | + | Pack a signed into a of width 2. + |% +FUNCTION packInt16BE(n: Number): Bytes + IF NOT (-0x8000 <= n <= 0x7FFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt16BE(IF n < 0 THEN 0x10000 + n ELSE n) +END FUNCTION + +%| + | Function: packInt16LE + | + | Pack a signed into a of width 2. + |% +FUNCTION packInt16LE(n: Number): Bytes + IF NOT (-0x8000 <= n <= 0x7FFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt16LE(IF n < 0 THEN 0x10000 + n ELSE n) +END FUNCTION + +%| + | Function: packInt32BE + | + | Pack a signed into a of width 4. + |% +FUNCTION packInt32BE(n: Number): Bytes + IF NOT (-0x80000000 <= n <= 0x7FFFFFFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt32BE(IF n < 0 THEN 0x100000000 + n ELSE n) +END FUNCTION + +%| + | Function: packInt32LE + | + | Pack a signed into a of width 4. + |% +FUNCTION packInt32LE(n: Number): Bytes + IF NOT (-0x80000000 <= n <= 0x7FFFFFFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt32LE(IF n < 0 THEN 0x100000000 + n ELSE n) +END FUNCTION + +%| + | Function: packInt64BE + | + | Pack a signed into a of width 8. + |% +FUNCTION packInt64BE(n: Number): Bytes + IF NOT (-0x8000000000000000 <= n <= 0x7FFFFFFFFFFFFFFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt64BE(IF n < 0 THEN 0x10000000000000000 + n ELSE n) +END FUNCTION + +%| + | Function: packInt64LE + | + | Pack a signed into a of width 8. + |% +FUNCTION packInt64LE(n: Number): Bytes + IF NOT (-0x8000000000000000 <= n <= 0x7FFFFFFFFFFFFFFF) THEN + RAISE InvalidValueException + END IF + RETURN packUInt64LE(IF n < 0 THEN 0x10000000000000000 + n ELSE n) +END FUNCTION + +%| + | Function: packUInt8 + | + | Pack a signed into a of width 1. + |% +FUNCTION packUInt8(n: Number): Bytes + IF NOT (0 <= n <= 0xFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and32(n, 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt16BE + | + | Pack an unsigned into a of width 2. + |% +FUNCTION packUInt16BE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and32(bitwise.shiftRight32(n, 8), 0xFF) + r[1] := bitwise.and32(n, 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt16LE + | + | Pack an unsigned into a of width 2. + |% +FUNCTION packUInt16LE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and32(n, 0xFF) + r[1] := bitwise.and32(bitwise.shiftRight32(n, 8), 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt32BE + | + | Pack an unsigned into a of width 4. + |% +FUNCTION packUInt32BE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFFFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and32(bitwise.shiftRight32(n, 24), 0xFF) + r[1] := bitwise.and32(bitwise.shiftRight32(n, 16), 0xFF) + r[2] := bitwise.and32(bitwise.shiftRight32(n, 8), 0xFF) + r[3] := bitwise.and32(n, 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt32LE + | + | Pack an unsigned into a of width 4. + |% +FUNCTION packUInt32LE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFFFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and32(n, 0xFF) + r[1] := bitwise.and32(bitwise.shiftRight32(n, 8), 0xFF) + r[2] := bitwise.and32(bitwise.shiftRight32(n, 16), 0xFF) + r[3] := bitwise.and32(bitwise.shiftRight32(n, 24), 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt64BE + | + | Pack an unsigned into a of width 8. + |% +FUNCTION packUInt64BE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFFFFFFFFFFFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and64(bitwise.shiftRight64(n, 56), 0xFF) + r[1] := bitwise.and64(bitwise.shiftRight64(n, 48), 0xFF) + r[2] := bitwise.and64(bitwise.shiftRight64(n, 40), 0xFF) + r[3] := bitwise.and64(bitwise.shiftRight64(n, 32), 0xFF) + r[4] := bitwise.and64(bitwise.shiftRight64(n, 24), 0xFF) + r[5] := bitwise.and64(bitwise.shiftRight64(n, 16), 0xFF) + r[6] := bitwise.and64(bitwise.shiftRight64(n, 8), 0xFF) + r[7] := bitwise.and64(n, 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: packUInt64LE + | + | Pack an unsigned into a of width 8. + |% +FUNCTION packUInt64LE(n: Number): Bytes + IF NOT (0 <= n <= 0xFFFFFFFFFFFFFFFF) THEN + RAISE InvalidValueException + END IF + VAR r: Array := [] + r[0] := bitwise.and64(n, 0xFF) + r[1] := bitwise.and64(bitwise.shiftRight64(n, 8), 0xFF) + r[2] := bitwise.and64(bitwise.shiftRight64(n, 16), 0xFF) + r[3] := bitwise.and64(bitwise.shiftRight64(n, 24), 0xFF) + r[4] := bitwise.and64(bitwise.shiftRight64(n, 32), 0xFF) + r[5] := bitwise.and64(bitwise.shiftRight64(n, 40), 0xFF) + r[6] := bitwise.and64(bitwise.shiftRight64(n, 48), 0xFF) + r[7] := bitwise.and64(bitwise.shiftRight64(n, 56), 0xFF) + RETURN r.toBytes() +END FUNCTION + +%| + | Function: unpackBool + | + | Unpack a from a . + |% +FUNCTION unpackBool(b: Bytes): Boolean + IF b.size() # 1 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN a[0] # 0 +END FUNCTION + +%| + | Function: unpackInt8 + | + | Unpack a from a of size 1. + |% +FUNCTION unpackInt8(b: Bytes): Number + LET r: Number := unpackUInt8(b) + IF r < 0x80 THEN + RETURN r + ELSE + RETURN r - 0x100 + END IF +END FUNCTION + +%| + | Function: unpackInt16BE + | + | Unpack a from a of size 2. + |% +FUNCTION unpackInt16BE(b: Bytes): Number + LET r: Number := unpackUInt16BE(b) + IF r < 0x8000 THEN + RETURN r + ELSE + RETURN r - 0x10000 + END IF +END FUNCTION + +%| + | Function: unpackInt16LE + | + | Unpack a from a of size 2. + |% +FUNCTION unpackInt16LE(b: Bytes): Number + LET r: Number := unpackUInt16LE(b) + IF r < 0x8000 THEN + RETURN r + ELSE + RETURN r - 0x10000 + END IF +END FUNCTION + +%| + | Function: unpackInt32BE + | + | Unpack a from a of size 4. + |% +FUNCTION unpackInt32BE(b: Bytes): Number + LET r: Number := unpackUInt32BE(b) + IF r < 0x80000000 THEN + RETURN r + ELSE + RETURN r - 0x100000000 + END IF +END FUNCTION + +%| + | Function: unpackInt32LE + | + | Unpack a from a of size 4. + |% +FUNCTION unpackInt32LE(b: Bytes): Number + LET r: Number := unpackUInt32LE(b) + IF r < 0x80000000 THEN + RETURN r + ELSE + RETURN r - 0x100000000 + END IF +END FUNCTION + +%| + | Function: unpackInt64BE + | + | Unpack a from a of size 8. + |% +FUNCTION unpackInt64BE(b: Bytes): Number + LET r: Number := unpackUInt64BE(b) + IF r < 0x8000000000000000 THEN + RETURN r + ELSE + RETURN r - 0x10000000000000000 + END IF +END FUNCTION + +%| + | Function: unpackInt64LE + | + | Unpack a from a of size 8. + |% +FUNCTION unpackInt64LE(b: Bytes): Number + LET r: Number := unpackUInt64LE(b) + IF r < 0x8000000000000000 THEN + RETURN r + ELSE + RETURN r - 0x10000000000000000 + END IF +END FUNCTION + +%| + | Function: unpackUInt8 + | + | Unpack a from a of size 1. + |% +FUNCTION unpackUInt8(b: Bytes): Number + IF b.size() # 1 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN a[0] +END FUNCTION + +%| + | Function: unpackUInt16BE + | + | Unpack a from a of size 2. + |% +FUNCTION unpackUInt16BE(b: Bytes): Number + IF b.size() # 2 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft32(a[0], 8) + + a[1] +END FUNCTION + +%| + | Function: unpackUInt16LE + | + | Unpack a from a of size 2. + |% +FUNCTION unpackUInt16LE(b: Bytes): Number + IF b.size() # 2 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft32(a[1], 8) + + a[0] +END FUNCTION + +%| + | Function: unpackUInt32BE + | + | Unpack a from a of size 4. + |% +FUNCTION unpackUInt32BE(b: Bytes): Number + IF b.size() # 4 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft32(a[0], 24) + + bitwise.shiftLeft32(a[1], 16) + + bitwise.shiftLeft32(a[2], 8) + + a[3] +END FUNCTION + +%| + | Function: unpackUInt32LE + | + | Unpack a from a of size 4. + |% +FUNCTION unpackUInt32LE(b: Bytes): Number + IF b.size() # 4 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft32(a[3], 24) + + bitwise.shiftLeft32(a[2], 16) + + bitwise.shiftLeft32(a[1], 8) + + a[0] +END FUNCTION + +%| + | Function: unpackUInt64BE + | + | Unpack a from a of size 8. + |% +FUNCTION unpackUInt64BE(b: Bytes): Number + IF b.size() # 8 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft64(a[0], 56) + + bitwise.shiftLeft64(a[1], 48) + + bitwise.shiftLeft64(a[2], 40) + + bitwise.shiftLeft64(a[3], 32) + + bitwise.shiftLeft64(a[4], 24) + + bitwise.shiftLeft64(a[5], 16) + + bitwise.shiftLeft64(a[6], 8) + + a[7] +END FUNCTION + +%| + | Function: unpackUInt64LE + | + | Unpack a from a of size 8. + |% +FUNCTION unpackUInt64LE(b: Bytes): Number + IF b.size() # 8 THEN + RAISE InvalidValueException + END IF + LET a: Array := b.toArray() + RETURN bitwise.shiftLeft64(a[7], 56) + + bitwise.shiftLeft64(a[6], 48) + + bitwise.shiftLeft64(a[5], 40) + + bitwise.shiftLeft64(a[4], 32) + + bitwise.shiftLeft64(a[3], 24) + + bitwise.shiftLeft64(a[2], 16) + + bitwise.shiftLeft64(a[1], 8) + + a[0] +END FUNCTION + +%| + | Function: Struct.pack + | + | Convert a dictionary of values into a structure. + |% +FUNCTION Struct.pack(self: Struct, values: Dictionary): Bytes + VAR buf: Array := [] + FOR f := 0 TO self.fields.size()-1 DO + IF self.fields[f].name IN values THEN + LET v: variant.Variant := values[self.fields[f].name] + CASE self.fields[f].type + WHEN Type.bool DO + buf.extend(packBool(v.getBoolean()).toArray()) + WHEN Type.int8 DO + buf.extend(packInt8(v.getNumber()).toArray()) + WHEN Type.int16BE DO + buf.extend(packInt16BE(v.getNumber()).toArray()) + WHEN Type.int16LE DO + buf.extend(packInt16LE(v.getNumber()).toArray()) + WHEN Type.int32BE DO + buf.extend(packInt32BE(v.getNumber()).toArray()) + WHEN Type.int32LE DO + buf.extend(packInt32LE(v.getNumber()).toArray()) + WHEN Type.int64BE DO + buf.extend(packInt64BE(v.getNumber()).toArray()) + WHEN Type.int64LE DO + buf.extend(packInt64LE(v.getNumber()).toArray()) + WHEN Type.uint8 DO + buf.extend(packUInt8(v.getNumber()).toArray()) + WHEN Type.uint16BE DO + buf.extend(packUInt16BE(v.getNumber()).toArray()) + WHEN Type.uint16LE DO + buf.extend(packUInt16LE(v.getNumber()).toArray()) + WHEN Type.uint32BE DO + buf.extend(packUInt32BE(v.getNumber()).toArray()) + WHEN Type.uint32LE DO + buf.extend(packUInt32LE(v.getNumber()).toArray()) + WHEN Type.uint64BE DO + buf.extend(packUInt64BE(v.getNumber()).toArray()) + WHEN Type.uint64LE DO + buf.extend(packUInt64LE(v.getNumber()).toArray()) + WHEN Type.string DO + LET s: String := values[self.fields[f].name].getString() + FOR i := 0 TO self.fields[f].width-1 DO + buf.append(IF i < s.length() THEN ord(s[i]) ELSE 0) + END FOR + WHEN Type.bytes DO + LET a: Array := values[self.fields[f].name].getBytes().toArray() + FOR i := 0 TO self.fields[f].width-1 DO + buf.append(IF i < a.size() THEN a[i] ELSE 0) + END FOR + END CASE + ELSE + buf.resize(buf.size() + self.fields[f].width) + END IF + END FOR + RETURN buf.toBytes() +END FUNCTION + +%| + | Function: Struct.sizeof + | + | Return the total size of a structure definition. + |% +FUNCTION Struct.sizeof(self: Struct): Number + VAR r: Number := 0 + FOR f := 0 TO self.fields.size()-1 DO + r := r + self.fields[f].width + END FOR + RETURN r +END FUNCTION + +%| + | Function: Struct.unpack + | + | Convert a structure into a dictionary of values. + |% +FUNCTION Struct.unpack(self: Struct, data: Bytes): Dictionary + VAR r: Dictionary := {} + LET a: Array := data.toArray() + VAR i: Number := 0 + FOR f := 0 TO self.fields.size()-1 DO + VAR v: variant.Variant := variant.makeNull() + CASE self.fields[f].type + WHEN Type.bool DO + v := variant.makeBoolean(unpackBool(data[i TO i])) + INC i + WHEN Type.int8 DO + v := variant.makeNumber(unpackInt8(data[i TO i])) + INC i + WHEN Type.int16BE DO + v := variant.makeNumber(unpackInt16BE(data[i TO i+1])) + i := i + 2 + WHEN Type.int16LE DO + v := variant.makeNumber(unpackInt16LE(data[i TO i+1])) + i := i + 2 + WHEN Type.int32BE DO + v := variant.makeNumber(unpackInt32BE(data[i TO i+3])) + i := i + 4 + WHEN Type.int32LE DO + v := variant.makeNumber(unpackInt32LE(data[i TO i+3])) + i := i + 4 + WHEN Type.int64BE DO + v := variant.makeNumber(unpackInt64BE(data[i TO i+7])) + i := i + 8 + WHEN Type.int64LE DO + v := variant.makeNumber(unpackInt64LE(data[i TO i+7])) + i := i + 8 + WHEN Type.uint8 DO + v := variant.makeNumber(unpackInt8(data[i TO i])) + i := i + 2 + WHEN Type.uint16BE DO + v := variant.makeNumber(unpackInt16BE(data[i TO i+1])) + i := i + 2 + WHEN Type.uint16LE DO + v := variant.makeNumber(unpackInt16LE(data[i TO i+1])) + i := i + 2 + WHEN Type.uint32BE DO + v := variant.makeNumber(unpackInt32BE(data[i TO i+3])) + i := i + 4 + WHEN Type.uint32LE DO + v := variant.makeNumber(unpackInt32LE(data[i TO i+3])) + i := i + 4 + WHEN Type.uint64BE DO + v := variant.makeNumber(unpackInt64BE(data[i TO i+7])) + i := i + 8 + WHEN Type.uint64LE DO + v := variant.makeNumber(unpackInt64LE(data[i TO i+7])) + i := i + 8 + WHEN Type.string DO + VAR s: String := "" + FOR j := 0 TO self.fields[f].width-1 DO + IF a[i+j] = 0 THEN + EXIT FOR + END IF + s.append(chr(a[i+j])) + END FOR + v := variant.makeString(s) + i := i + self.fields[f].width + WHEN Type.bytes DO + v := variant.makeBytes(a[i TO i+self.fields[f].width-1].toBytes()) + i := i + self.fields[f].width + END CASE + r[self.fields[f].name] := v + END FOR + RETURN r +END FUNCTION + +BEGIN MAIN + ASSERT packBool(FALSE) = HEXBYTES "00" + ASSERT packBool(TRUE) = HEXBYTES "01" + + ASSERT packInt8(-128) = HEXBYTES "80" + ASSERT packInt8(-1) = HEXBYTES "FF" + ASSERT packInt8(0) = HEXBYTES "00" + ASSERT packInt8(1) = HEXBYTES "01" + ASSERT packInt8(127) = HEXBYTES "7F" + + ASSERT packInt16BE(-32768) = HEXBYTES "80 00" + ASSERT packInt16BE(-1) = HEXBYTES "FF FF" + ASSERT packInt16BE(0) = HEXBYTES "00 00" + ASSERT packInt16BE(1) = HEXBYTES "00 01" + ASSERT packInt16BE(32767) = HEXBYTES "7F FF" + + ASSERT packInt16LE(-32768) = HEXBYTES "00 80" + ASSERT packInt16LE(-1) = HEXBYTES "FF FF" + ASSERT packInt16LE(0) = HEXBYTES "00 00" + ASSERT packInt16LE(1) = HEXBYTES "01 00" + ASSERT packInt16LE(32767) = HEXBYTES "FF 7F" + + ASSERT packInt32BE(-2147483648) = HEXBYTES "80 00 00 00" + ASSERT packInt32BE(-1) = HEXBYTES "FF FF FF FF" + ASSERT packInt32BE(0) = HEXBYTES "00 00 00 00" + ASSERT packInt32BE(1) = HEXBYTES "00 00 00 01" + ASSERT packInt32BE(2147483647) = HEXBYTES "7F FF FF FF" + + ASSERT packInt32LE(-2147483648) = HEXBYTES "00 00 00 80" + ASSERT packInt32LE(-1) = HEXBYTES "FF FF FF FF" + ASSERT packInt32LE(0) = HEXBYTES "00 00 00 00" + ASSERT packInt32LE(1) = HEXBYTES "01 00 00 00" + ASSERT packInt32LE(2147483647) = HEXBYTES "FF FF FF 7F" + + ASSERT packInt64BE(-9223372036854775808) = HEXBYTES "80 00 00 00 00 00 00 00" + ASSERT packInt64BE(-1) = HEXBYTES "FF FF FF FF FF FF FF FF" + ASSERT packInt64BE(0) = HEXBYTES "00 00 00 00 00 00 00 00" + ASSERT packInt64BE(1) = HEXBYTES "00 00 00 00 00 00 00 01" + ASSERT packInt64BE(9223372036854775807) = HEXBYTES "7F FF FF FF FF FF FF FF" + + ASSERT packInt64LE(-9223372036854775808) = HEXBYTES "00 00 00 00 00 00 00 80" + ASSERT packInt64LE(-1) = HEXBYTES "FF FF FF FF FF FF FF FF" + ASSERT packInt64LE(0) = HEXBYTES "00 00 00 00 00 00 00 00" + ASSERT packInt64LE(1) = HEXBYTES "01 00 00 00 00 00 00 00" + ASSERT packInt64LE(9223372036854775807) = HEXBYTES "FF FF FF FF FF FF FF 7F" + + ASSERT packUInt8(0) = HEXBYTES "00" + ASSERT packUInt8(1) = HEXBYTES "01" + ASSERT packUInt8(127) = HEXBYTES "7F" + ASSERT packUInt8(255) = HEXBYTES "FF" + + ASSERT packUInt16BE(0) = HEXBYTES "00 00" + ASSERT packUInt16BE(1) = HEXBYTES "00 01" + ASSERT packUInt16BE(32767) = HEXBYTES "7F FF" + ASSERT packUInt16BE(65535) = HEXBYTES "FF FF" + + ASSERT packUInt16LE(0) = HEXBYTES "00 00" + ASSERT packUInt16LE(1) = HEXBYTES "01 00" + ASSERT packUInt16LE(32767) = HEXBYTES "FF 7F" + ASSERT packUInt16LE(65535) = HEXBYTES "FF FF" + + ASSERT packUInt32BE(0) = HEXBYTES "00 00 00 00" + ASSERT packUInt32BE(1) = HEXBYTES "00 00 00 01" + ASSERT packUInt32BE(2147483647) = HEXBYTES "7F FF FF FF" + ASSERT packUInt32BE(4294967295) = HEXBYTES "FF FF FF FF" + + ASSERT packUInt32LE(0) = HEXBYTES "00 00 00 00" + ASSERT packUInt32LE(1) = HEXBYTES "01 00 00 00" + ASSERT packUInt32LE(2147483647) = HEXBYTES "FF FF FF 7F" + ASSERT packUInt32LE(4294967295) = HEXBYTES "FF FF FF FF" + + ASSERT packUInt64BE(0) = HEXBYTES "00 00 00 00 00 00 00 00" + ASSERT packUInt64BE(1) = HEXBYTES "00 00 00 00 00 00 00 01" + ASSERT packUInt64BE(9223372036854775807) = HEXBYTES "7F FF FF FF FF FF FF FF" + ASSERT packUInt64BE(18446744073709551615) = HEXBYTES "FF FF FF FF FF FF FF FF" + + ASSERT packUInt64LE(0) = HEXBYTES "00 00 00 00 00 00 00 00" + ASSERT packUInt64LE(1) = HEXBYTES "01 00 00 00 00 00 00 00" + ASSERT packUInt64LE(9223372036854775807) = HEXBYTES "FF FF FF FF FF FF FF 7F" + ASSERT packUInt64LE(18446744073709551615) = HEXBYTES "FF FF FF FF FF FF FF FF" + + ASSERT unpackBool(HEXBYTES "00") = FALSE + ASSERT unpackBool(HEXBYTES "01") = TRUE + + ASSERT unpackInt8(HEXBYTES "80") = -128 + ASSERT unpackInt8(HEXBYTES "FF") = -1 + ASSERT unpackInt8(HEXBYTES "00") = 0 + ASSERT unpackInt8(HEXBYTES "01") = 1 + ASSERT unpackInt8(HEXBYTES "7F") = 127 + + ASSERT unpackInt16BE(HEXBYTES "80 00") = -32768 + ASSERT unpackInt16BE(HEXBYTES "FF FF") = -1 + ASSERT unpackInt16BE(HEXBYTES "00 00") = 0 + ASSERT unpackInt16BE(HEXBYTES "00 01") = 1 + ASSERT unpackInt16BE(HEXBYTES "7F FF") = 32767 + + ASSERT unpackInt16LE(HEXBYTES "00 80") = -32768 + ASSERT unpackInt16LE(HEXBYTES "FF FF") = -1 + ASSERT unpackInt16LE(HEXBYTES "00 00") = 0 + ASSERT unpackInt16LE(HEXBYTES "01 00") = 1 + ASSERT unpackInt16LE(HEXBYTES "FF 7F") = 32767 + + ASSERT unpackInt32BE(HEXBYTES "80 00 00 00") = -2147483648 + ASSERT unpackInt32BE(HEXBYTES "FF FF FF FF") = -1 + ASSERT unpackInt32BE(HEXBYTES "00 00 00 00") = 0 + ASSERT unpackInt32BE(HEXBYTES "00 00 00 01") = 1 + ASSERT unpackInt32BE(HEXBYTES "7F FF FF FF") = 2147483647 + + ASSERT unpackInt32LE(HEXBYTES "00 00 00 80") = -2147483648 + ASSERT unpackInt32LE(HEXBYTES "FF FF FF FF") = -1 + ASSERT unpackInt32LE(HEXBYTES "00 00 00 00") = 0 + ASSERT unpackInt32LE(HEXBYTES "01 00 00 00") = 1 + ASSERT unpackInt32LE(HEXBYTES "FF FF FF 7F") = 2147483647 + + ASSERT unpackInt64BE(HEXBYTES "80 00 00 00 00 00 00 00") = -9223372036854775808 + ASSERT unpackInt64BE(HEXBYTES "FF FF FF FF FF FF FF FF") = -1 + ASSERT unpackInt64BE(HEXBYTES "00 00 00 00 00 00 00 00") = 0 + ASSERT unpackInt64BE(HEXBYTES "00 00 00 00 00 00 00 01") = 1 + ASSERT unpackInt64BE(HEXBYTES "7F FF FF FF FF FF FF FF") = 9223372036854775807 + + ASSERT unpackInt64LE(HEXBYTES "00 00 00 00 00 00 00 80") = -9223372036854775808 + ASSERT unpackInt64LE(HEXBYTES "FF FF FF FF FF FF FF FF") = -1 + ASSERT unpackInt64LE(HEXBYTES "00 00 00 00 00 00 00 00") = 0 + ASSERT unpackInt64LE(HEXBYTES "01 00 00 00 00 00 00 00") = 1 + ASSERT unpackInt64LE(HEXBYTES "FF FF FF FF FF FF FF 7F") = 9223372036854775807 + + ASSERT unpackUInt8(HEXBYTES "00") = 0 + ASSERT unpackUInt8(HEXBYTES "01") = 1 + ASSERT unpackUInt8(HEXBYTES "7F") = 127 + ASSERT unpackUInt8(HEXBYTES "FF") = 255 + + ASSERT unpackUInt16BE(HEXBYTES "00 00") = 0 + ASSERT unpackUInt16BE(HEXBYTES "00 01") = 1 + ASSERT unpackUInt16BE(HEXBYTES "7F FF") = 32767 + ASSERT unpackUInt16BE(HEXBYTES "FF FF") = 65535 + + ASSERT unpackUInt16LE(HEXBYTES "00 00") = 0 + ASSERT unpackUInt16LE(HEXBYTES "01 00") = 1 + ASSERT unpackUInt16LE(HEXBYTES "FF 7F") = 32767 + ASSERT unpackUInt16LE(HEXBYTES "FF FF") = 65535 + + ASSERT unpackUInt32BE(HEXBYTES "00 00 00 00") = 0 + ASSERT unpackUInt32BE(HEXBYTES "00 00 00 01") = 1 + ASSERT unpackUInt32BE(HEXBYTES "7F FF FF FF") = 2147483647 + ASSERT unpackUInt32BE(HEXBYTES "FF FF FF FF") = 4294967295 + + ASSERT unpackUInt32LE(HEXBYTES "00 00 00 00") = 0 + ASSERT unpackUInt32LE(HEXBYTES "01 00 00 00") = 1 + ASSERT unpackUInt32LE(HEXBYTES "FF FF FF 7F") = 2147483647 + ASSERT unpackUInt32LE(HEXBYTES "FF FF FF FF") = 4294967295 + + ASSERT unpackUInt64BE(HEXBYTES "00 00 00 00 00 00 00 00") = 0 + ASSERT unpackUInt64BE(HEXBYTES "00 00 00 00 00 00 00 01") = 1 + ASSERT unpackUInt64BE(HEXBYTES "7F FF FF FF FF FF FF FF") = 9223372036854775807 + ASSERT unpackUInt64BE(HEXBYTES "FF FF FF FF FF FF FF FF") = 18446744073709551615 + + ASSERT unpackUInt64LE(HEXBYTES "00 00 00 00 00 00 00 00") = 0 + ASSERT unpackUInt64LE(HEXBYTES "01 00 00 00 00 00 00 00") = 1 + ASSERT unpackUInt64LE(HEXBYTES "FF FF FF FF FF FF FF 7F") = 9223372036854775807 + ASSERT unpackUInt64LE(HEXBYTES "FF FF FF FF FF FF FF FF") = 18446744073709551615 +END MAIN diff --git a/lib/sys.cpp b/lib/sys.cpp index b885de5562..12645c57e2 100644 --- a/lib/sys.cpp +++ b/lib/sys.cpp @@ -1,27 +1,35 @@ +#include #include #include #include #include "number.h" - -static std::vector g_argv; +#include "rtl_exec.h" +#include "utf8string.h" namespace rtl { -std::vector sys$argv() -{ - return g_argv; -} +Cell sys$args; void sys$exit(Number x) { - exit(number_to_sint32(x)); + if (not number_is_integer(x)) { + throw RtlException(Exception_global$InvalidValueException, "sys.exit invalid parameter: " + number_to_string(x)); + } + int r = number_to_sint32(x); + if (r < 0 || r > 255) { + throw RtlException(Exception_global$InvalidValueException, "sys.exit invalid parameter: " + number_to_string(x)); + } + exit(r); } } // namespace rtl void rtl_sys_init(int argc, char *argv[]) { - g_argv.clear(); - std::copy(argv, argv+argc, std::back_inserter(g_argv)); + std::vector &a = rtl::sys$args.array_for_write(); + a.resize(argc); + for (int i = 0; i < argc; i++) { + a[i] = Cell(argv[i]); + } } diff --git a/lib/sys.neon b/lib/sys.neon new file mode 100644 index 0000000000..6d7748e03b --- /dev/null +++ b/lib/sys.neon @@ -0,0 +1,19 @@ +%| + | File: sys + | + | Functions for interacting with the system. + |% + +%| + | Variable: args + | + | Contains the command line arguments passed to the current process when it started. + |% +DECLARE NATIVE VAR args: Array + +%| + | Function: exit + | + | Return to the operating system immediately with the given exit code. + |% +DECLARE NATIVE FUNCTION exit(x: Number) diff --git a/lib/time.cpp b/lib/time.cpp index f79df0a2a2..542888bea7 100644 --- a/lib/time.cpp +++ b/lib/time.cpp @@ -5,11 +5,6 @@ namespace rtl { -Number time$now() -{ - return rtl_time_now(); -} - void time$sleep(Number seconds) { std::chrono::microseconds us(number_to_uint64(number_multiply(seconds, number_from_uint32(1000000)))); diff --git a/lib/time.neon b/lib/time.neon new file mode 100644 index 0000000000..2ec836d89e --- /dev/null +++ b/lib/time.neon @@ -0,0 +1,95 @@ +%| + | File: time + | + | Functions for working with time. + |% + +EXPORT Stopwatch + +%| + | Function: now + | + | Return the current time in seconds. + |% +DECLARE NATIVE FUNCTION now(): Number + +%| + | Function: sleep + | + | Sleep for the given number of seconds. + |% +DECLARE NATIVE FUNCTION sleep(seconds: Number) + +%| + | Function: tick + | + | Return a tick count which is the number of seconds since + | some unspecified time in the past (used by ). + |% +DECLARE NATIVE FUNCTION tick(): Number + +%| + | Type: Stopwatch + | + | Stopwatch useful for measuring time intervals. + |% +TYPE Stopwatch IS RECORD + accumulated: Number + start: Number +END RECORD + +%| + | Function: Stopwatch.elapsed + | + | Return the current total elapsed time. + |% +FUNCTION Stopwatch.elapsed(self: Stopwatch): Number + LET t: Number := tick() + VAR r: Number := self.accumulated + IF self.isRunning() THEN + r := r + t - self.start + END IF + RETURN r +END FUNCTION + +%| + | Function: Stopwatch.isRunning + | + | Return TRUE if the stopwatch is running. + |% +FUNCTION Stopwatch.isRunning(self: Stopwatch): Boolean + RETURN self.start > 0 +END FUNCTION + +%| + | Function: Stopwatch.reset + | + | Stop the timer and reset the accumulated time. + |% +FUNCTION Stopwatch.reset(INOUT self: Stopwatch) + self.accumulated := 0 + self.start := 0 +END FUNCTION + +%| + | Function: Stopwatch.start + | + | Start a stopwatch. + |% +FUNCTION Stopwatch.start(INOUT self: Stopwatch) + self.start := tick() +END FUNCTION + +%| + | Function: Stopwatch.stop + | + | Stop a stopwatch. + |% +FUNCTION Stopwatch.stop(INOUT self: Stopwatch) + LET t: Number := tick() + IF NOT self.isRunning() THEN + EXIT FUNCTION + END IF + self.accumulated := self.accumulated + (t - self.start) + self.start := 0 +END FUNCTION diff --git a/lib/time_darwin.cpp b/lib/time_darwin.cpp new file mode 100644 index 0000000000..d18f5c2002 --- /dev/null +++ b/lib/time_darwin.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "number.h" + +namespace rtl { + +static const Number NANOSECONDS_PER_SECOND = number_from_uint64(NSEC_PER_SEC); + +Number time$tick() +{ + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + return number_add(number_from_uint64(mts.tv_sec), number_divide(number_from_uint64(mts.tv_nsec), NANOSECONDS_PER_SECOND)); +} + +} // namespace rtl diff --git a/lib/time_linux.cpp b/lib/time_linux.cpp new file mode 100644 index 0000000000..38da44985b --- /dev/null +++ b/lib/time_linux.cpp @@ -0,0 +1,16 @@ +#include + +#include "number.h" + +namespace rtl { + +static const Number NANOSECONDS_PER_SECOND = number_from_uint64(1000000000LL); + +Number time$tick() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return number_add(number_from_uint64(ts.tv_sec), number_divide(number_from_uint64(ts.tv_nsec), NANOSECONDS_PER_SECOND)); +} + +} // namespace rtl diff --git a/lib/time_posix.cpp b/lib/time_posix.cpp index 30c620a85f..692299dbce 100644 --- a/lib/time_posix.cpp +++ b/lib/time_posix.cpp @@ -1,8 +1,11 @@ #include +#include #include "number.h" -Number rtl_time_now() +namespace rtl { + +Number time$now() { struct timeval tv; if (gettimeofday(&tv, NULL) != 0) { @@ -10,3 +13,5 @@ Number rtl_time_now() } return number_add(number_from_uint32(tv.tv_sec), number_divide(number_from_uint32(tv.tv_usec), number_from_uint32(1e6))); } + +} // namespace rtl diff --git a/lib/time_win32.cpp b/lib/time_win32.cpp index f86e42b1e3..9a46e910d9 100644 --- a/lib/time_win32.cpp +++ b/lib/time_win32.cpp @@ -1,10 +1,13 @@ +#include #include #include "number.h" -const ULONGLONG FILETIME_UNIX_EPOCH = 11644473600000000ULL; +const ULONGLONG FILETIME_UNIX_EPOCH = 116444736000000000ULL; -Number rtl_time_now() +namespace rtl { + +Number time$now() { FILETIME ft; GetSystemTimeAsFileTime(&ft); @@ -13,3 +16,20 @@ Number rtl_time_now() ticks.HighPart = ft.dwHighDateTime; return number_divide(number_from_uint64(ticks.QuadPart - FILETIME_UNIX_EPOCH), number_from_uint32(10000000)); } + +Number time$tick() +{ + static bool init = false; + static Number frequency; + if (not init) { + init = true; + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + frequency = number_from_uint64(freq.QuadPart); + } + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return number_divide(number_from_uint64(now.QuadPart), frequency); +} + +} // namespace rtl diff --git a/lib/variant.neon b/lib/variant.neon new file mode 100644 index 0000000000..9b93d0eeb0 --- /dev/null +++ b/lib/variant.neon @@ -0,0 +1,314 @@ +%| + | File: variant + | + | Provides a general-purpose type that can hold values of any other type. + |% + +EXPORT Variant +EXPORT Type +EXPORT TypeMismatchException +EXPORT makeNull +EXPORT makeBoolean +EXPORT makeNumber +EXPORT makeString +EXPORT makeBytes +EXPORT makeArray +EXPORT makeDictionary + +%| + | Exception: TypeMismatchException + | + | Indicates an attempt to read a value of the wrong type from a . + |% +DECLARE EXCEPTION TypeMismatchException + +%| + | Enumeration: Type + | + | The type of a variant value. + | + | Values: + | null - null value + | boolean - boolean + | number - number + | string - string + | bytes - bytes + | array - array + | dictionary - dictionary + |% +TYPE Type IS ENUM + null + boolean + number + string + bytes + array + dictionary +END ENUM + +%| + | Type: Variant + | + | Opaque variant type. + |% +TYPE Variant IS RECORD + PRIVATE type: Type + PRIVATE val_boolean: Boolean + PRIVATE val_number: Number + PRIVATE val_string: String + PRIVATE val_bytes: Bytes + PRIVATE val_array: Array + PRIVATE val_dictionary: Dictionary +END RECORD + +%| + | Function: makeNull + | + | Make a with a null value. + |% +FUNCTION makeNull(): Variant + VAR r: Variant := Variant() + r.setNull() + RETURN r +END FUNCTION + +%| + | Function: makeBoolean + | + | Make a with a boolean value. + |% +FUNCTION makeBoolean(b: Boolean): Variant + VAR r: Variant := Variant() + r.setBoolean(b) + RETURN r +END FUNCTION + +%| + | Function: makeNumber + | + | Make a with a number value. + |% +FUNCTION makeNumber(n: Number): Variant + VAR r: Variant := Variant() + r.setNumber(n) + RETURN r +END FUNCTION + +%| + | Function: makeString + | + | Make a with a string value. + |% +FUNCTION makeString(s: String): Variant + VAR r: Variant := Variant() + r.setString(s) + RETURN r +END FUNCTION + +%| + | Function: makeBytes + | + | Make a with a bytes value. + |% +FUNCTION makeBytes(b: Bytes): Variant + VAR r: Variant := Variant() + r.setBytes(b) + RETURN r +END FUNCTION + +%| + | Function: makeArray + | + | Make a with an array value. + |% +FUNCTION makeArray(a: Array): Variant + VAR r: Variant := Variant() + r.setArray(a) + RETURN r +END FUNCTION + +%| + | Function: makeDictionary + | + | Make a with a dictionary value. + |% +FUNCTION makeDictionary(d: Dictionary): Variant + VAR r: Variant := Variant() + r.setDictionary(d) + RETURN r +END FUNCTION + +%| + | Function: Variant.getType + | + | Return the type of a variant. + |% +FUNCTION Variant.getType(IN self: Variant): Type + RETURN self.type +END FUNCTION + +FUNCTION Variant.setNull(INOUT self: Variant) + self.type := Type.null +END FUNCTION + +%| + | Function: Variant.isNull + | + | Return TRUE if the variant is a null value. + |% +FUNCTION Variant.isNull(IN self: Variant): Boolean + RETURN self.type = Type.null +END FUNCTION + +FUNCTION Variant.setBoolean(INOUT self: Variant, b: Boolean) + self.type := Type.boolean + self.val_boolean := b +END FUNCTION + +%| + | Function: Variant.getBoolean + | + | Get a boolean value from a . + |% +FUNCTION Variant.getBoolean(IN self: Variant): Boolean + IF self.type # Type.boolean THEN + RAISE TypeMismatchException + END IF + RETURN self.val_boolean +END FUNCTION + +FUNCTION Variant.setNumber(INOUT self: Variant, n: Number) + self.type := Type.number + self.val_number := n +END FUNCTION + +%| + | Function: Variant.getNumber + | + | Get a number value from a . + |% +FUNCTION Variant.getNumber(IN self: Variant): Number + IF self.type # Type.number THEN + RAISE TypeMismatchException + END IF + RETURN self.val_number +END FUNCTION + +FUNCTION Variant.setString(INOUT self: Variant, s: String) + self.type := Type.string + self.val_string := s +END FUNCTION + +%| + | Function: Variant.getString + | + | Get a string value from a . + |% +FUNCTION Variant.getString(IN self: Variant): String + IF self.type # Type.string THEN + RAISE TypeMismatchException + END IF + RETURN self.val_string +END FUNCTION + +FUNCTION Variant.setBytes(INOUT self: Variant, s: Bytes) + self.type := Type.bytes + self.val_bytes := s +END FUNCTION + +%| + | Function: Variant.getBytes + | + | Get a bytes value from a . + |% +FUNCTION Variant.getBytes(IN self: Variant): Bytes + IF self.type # Type.bytes THEN + RAISE TypeMismatchException + END IF + RETURN self.val_bytes +END FUNCTION + +FUNCTION Variant.setArray(INOUT self: Variant, a: Array) + self.type := Type.array + self.val_array := a +END FUNCTION + +%| + | Function: Variant.getArray + | + | Get an array value from a . + |% +FUNCTION Variant.getArray(IN self: Variant): Array + IF self.type # Type.array THEN + RAISE TypeMismatchException + END IF + RETURN self.val_array +END FUNCTION + +FUNCTION Variant.setDictionary(INOUT self: Variant, d: Dictionary) + self.type := Type.dictionary + self.val_dictionary := d +END FUNCTION + +%| + | Function: Variant.getDictionary + | + | Get a dictionary value from a . + |% +FUNCTION Variant.getDictionary(IN self: Variant): Dictionary + IF self.type # Type.dictionary THEN + RAISE TypeMismatchException + END IF + RETURN self.val_dictionary +END FUNCTION + +%| + | Function: Variant.toString + | + | Return a string representation of a value. + |% +FUNCTION Variant.toString(IN self: Variant): String + CASE self.type + WHEN Type.null DO + RETURN "null" + WHEN Type.boolean DO + RETURN IF self.val_boolean THEN "true" ELSE "false" + WHEN Type.number DO + RETURN str(self.val_number) + WHEN Type.string DO + RETURN self.val_string + WHEN Type.bytes DO + RETURN "" + WHEN Type.array DO + VAR r: String := "[" + FOREACH x OF self.val_array INDEX i DO + IF i > 0 THEN + r.append(", ") + END IF + IF x.getType() = Type.string THEN + r.append("\"\(x)\"") + ELSE + r.append(x.toString()) + END IF + END FOREACH + r.append("]") + RETURN r + WHEN Type.dictionary DO + VAR r: String := "{" + LET keys: Array := self.val_dictionary.keys() % TODO: remove this temporary + FOREACH x OF keys INDEX i DO + IF i > 0 THEN + r.append(", ") + END IF + r.append("\"\(x)\": ") + IF self.val_dictionary[x].getType() = Type.string THEN + r.append("\"\(self.val_dictionary[x])\"") + ELSE + r.append(" \(self.val_dictionary[x])") + END IF + END FOREACH + r.append("}") + RETURN r + END CASE + RETURN "TODO" +END FUNCTION diff --git a/lib/win32.neon b/lib/win32.neon new file mode 100644 index 0000000000..576fec9dcc --- /dev/null +++ b/lib/win32.neon @@ -0,0 +1,147 @@ +%| + | File: win32 + | + | Win32 API access functions. + |% + +EXPORT Beep +EXPORT DebugBreak +EXPORT FindClose +EXPORT FindFirstFile +EXPORT FindNextFile +EXPORT Handle +EXPORT WIN32_FIND_DATA + +IMPORT struct +IMPORT variant + +% Type: Handle +TYPE Handle IS POINTER + +% Type: WIN32_FIND_DATA +TYPE WIN32_FIND_DATA IS RECORD + dwFileAttributes: Number + ftCreationTime: Bytes + ftLastAccessTime: Bytes + ftLastWriteTime: Bytes + nFileSizeHigh: Number + nFileSizeLow: Number + dwReserved0: Number + dwReserved1: Number + cFileName: String + cAlternateFileName: String +END RECORD + +LET Struct_WIN32_FIND_DATA: struct.Struct := struct.make([ + struct.field("dwFileAttributes", struct.Type.int32LE, 4), + struct.field("ftCreationTime", struct.Type.bytes, 8), + struct.field("ftLastAccessTime", struct.Type.bytes, 8), + struct.field("ftLastWriteTime", struct.Type.bytes, 8), + struct.field("nFileSizeHigh", struct.Type.int32LE, 4), + struct.field("nFileSizeLow", struct.Type.int32LE, 4), + struct.field("dwReserved0", struct.Type.int32LE, 4), + struct.field("dwReserved1", struct.Type.int32LE, 4), + struct.field("cFileName", struct.Type.string, 260), + struct.field("cAlternateFileName", struct.Type.string, 14), +]) + +FUNCTION WIN32_FIND_DATA.pack(self: WIN32_FIND_DATA): Bytes + VAR d: Dictionary := {} + d["dwFileAttributes"] := variant.makeNumber(self.dwFileAttributes) + d["ftCreationTime"] := variant.makeBytes(self.ftCreationTime) + d["ftLastAccessTime"] := variant.makeBytes(self.ftLastAccessTime) + d["ftLastWriteTime"] := variant.makeBytes(self.ftLastWriteTime) + d["nFileSizeHigh"] := variant.makeNumber(self.nFileSizeHigh) + d["nFileSizeLow"] := variant.makeNumber(self.nFileSizeLow) + d["dwReserved0"] := variant.makeNumber(self.dwReserved0) + d["dwReserved1"] := variant.makeNumber(self.dwReserved1) + d["cFileName"] := variant.makeString(self.cFileName) + d["cAlternateFileName"] := variant.makeString(self.cAlternateFileName) + RETURN Struct_WIN32_FIND_DATA.pack(d) +END FUNCTION + +FUNCTION WIN32_FIND_DATA.unpack(INOUT self: WIN32_FIND_DATA, data: Bytes) + LET d: Dictionary := Struct_WIN32_FIND_DATA.unpack(data) + self.dwFileAttributes := d["dwFileAttributes"].getNumber() + self.ftCreationTime := d["ftCreationTime"].getBytes() + self.ftLastAccessTime := d["ftLastAccessTime"].getBytes() + self.ftLastWriteTime := d["ftLastWriteTime"].getBytes() + self.nFileSizeHigh := d["nFileSizeHigh"].getNumber() + self.nFileSizeLow := d["nFileSizeLow"].getNumber() + self.dwReserved0 := d["dwReserved0"].getNumber() + self.dwReserved1 := d["dwReserved1"].getNumber() + self.cFileName := d["cFileName"].getString() + self.cAlternateFileName := d["cAlternateFileName"].getString() +END FUNCTION + +% Title: Win32 API Functions + +% Function: Beep +EXTERNAL FUNCTION Beep(dwFreq: Number, dwDuration: Number): Boolean +{ + "library": {"name": "kernel32"} + "types": { + "return": "boolean" + "dwFreq": "uint32" + "dwDuration": "uint32" + } +} +END FUNCTION + +% Function: DebugBreak +EXTERNAL FUNCTION DebugBreak() +{ + "library": {"name": "kernel32"} + "types": {} +} +END FUNCTION + +% Function: FindClose +EXTERNAL FUNCTION FindClose(hFindFile: Handle): Boolean +{ + "library": {"name": "kernel32"} + "types": { + "return": "boolean" + "hFindFile": "pointer" + } +} +END FUNCTION + +% Function: FindFirstFileA +EXTERNAL FUNCTION FindFirstFileA(lpFileName: String, INOUT lpFindFileData: Bytes): Handle +{ + "library": {"name": "kernel32"} + "types": { + "return": "pointer" + "lpFileName": "string" + "lpFindFileData": "bytes" + } +} +END FUNCTION + +FUNCTION FindFirstFile(lpFileName: String, OUT lpFindFileData: WIN32_FIND_DATA): Handle + lpFindFileData := WIN32_FIND_DATA() + VAR fd: Bytes := lpFindFileData.pack() + LET r: Handle := FindFirstFileA(lpFileName, INOUT fd) + lpFindFileData.unpack(fd) + RETURN r +END FUNCTION + +% Function: FindNextFileA +EXTERNAL FUNCTION FindNextFileA(hFindFile: Handle, INOUT lpFindFileData: Bytes): Boolean +{ + "library": {"name": "kernel32"} + "types": { + "return": "boolean" + "hFindFile": "pointer" + "lpFindFileData": "bytes" + } +} +END FUNCTION + +FUNCTION FindNextFile(hFindFile: Handle, INOUT lpFindFileData: WIN32_FIND_DATA): Boolean + VAR fd: Bytes := lpFindFileData.pack() + LET r: Boolean := FindNextFileA(hFindFile, INOUT fd) + lpFindFileData.unpack(fd) + RETURN r +END FUNCTION diff --git a/lib/xml.neon b/lib/xml.neon new file mode 100644 index 0000000000..3828cd8b36 --- /dev/null +++ b/lib/xml.neon @@ -0,0 +1,25 @@ +%| + | File: xml + | + | Provides functions for working with XML files. + |% + +EXPORT parse + +%| + | Type: Document + | + | Represents an XML document. + |% +TYPE Document IS RECORD + dummy: Boolean +END RECORD + +%| + | Function: parse + | + | Parse the given string as an XML document. + |% +FUNCTION parse(s: String): Document + RETURN Document() +END FUNCTION diff --git a/samples/99-bottles.neon b/samples/99-bottles/99-bottles.neon similarity index 80% rename from samples/99-bottles.neon rename to samples/99-bottles/99-bottles.neon index bae5dc273f..0c4dd6f7e7 100644 --- a/samples/99-bottles.neon +++ b/samples/99-bottles/99-bottles.neon @@ -1,4 +1,10 @@ -VAR count: Number +%| + | File: 99-bottles + | + | Prints out the song "99 Bottles of Beer" as from the web site + | http://www.99-bottles-of-beer.net. + |% + VAR suffix: String FOR count := 99 TO 1 STEP -1 DO diff --git a/samples/dhrystone.neon b/samples/benchmark/dhrystone.neon similarity index 90% rename from samples/dhrystone.neon rename to samples/benchmark/dhrystone.neon index 3a2541b7b3..bb3ae1468a 100644 --- a/samples/dhrystone.neon +++ b/samples/benchmark/dhrystone.neon @@ -347,7 +347,7 @@ IMPORT math -TYPE Enumeration := ENUM +TYPE Enumeration IS ENUM Ident_1 Ident_2 Ident_3 @@ -357,14 +357,14 @@ END ENUM % General definitions: -TYPE One_Thirty := Number -TYPE One_Fifty := Number -TYPE Capital_Letter := String -TYPE Str_30 := String -TYPE Arr_1_Dim := Array -TYPE Arr_2_Dim := Array> +TYPE One_Thirty IS Number +TYPE One_Fifty IS Number +TYPE Capital_Letter IS String +TYPE Str_30 IS String +TYPE Arr_1_Dim IS Array +TYPE Arr_2_Dim IS Array> -TYPE Record := RECORD +TYPE Record IS RECORD Ptr_Comp: POINTER TO Record Discr: Enumeration @@ -392,50 +392,46 @@ VAR Arr_2_Glob: Array> IMPORT time -CONST Mic_secs_Per_Second: Number := 1000000.0 +CONSTANT Mic_secs_Per_Second: Number := 1000000.0 -CONST Too_Small_Time: Number := 2 +CONSTANT Too_Small_Time: Number := 2 VAR Begin_Time, End_Time, User_Time: Number VAR Microseconds, Dhrystones_Per_Second: Number % end of variables for time measurement -DECLARE FUNCTION Proc_3 (INOUT Ptr_Ref_Par: POINTER TO Record) -DECLARE FUNCTION Proc_6 (IN Enum_Val_Par: Enumeration, OUT Enum_Ref_Par: Enumeration) -DECLARE FUNCTION Proc_7 (IN Int_1_Par_Val: One_Fifty, IN Int_2_Par_Val: One_Fifty, OUT Int_Par_Ref: One_Fifty) - FUNCTION Proc_1 (Ptr_Val_Par: POINTER TO Record) % executed once VAR Next_Record: POINTER TO Record - IF VALID pvp := Ptr_Val_Par THEN - Next_Record := pvp->Ptr_Comp - IF VALID nr := Next_Record THEN + IF VALID Ptr_Val_Par THEN + Next_Record := Ptr_Val_Par->Ptr_Comp + IF VALID Next_Record THEN % == Ptr_Glob_Next % Local variable, initialized with Ptr_Val_Par->Ptr_Comp, % corresponds to "rename" in Ada, "with" in Pascal - IF VALID pg := Ptr_Glob THEN - value_copy(nr, pg) + IF VALID Ptr_Glob THEN + valueCopy(Next_Record, Ptr_Glob) END IF - pvp->Int_Comp := 5 - nr->Int_Comp := pvp->Int_Comp - nr->Ptr_Comp := pvp->Ptr_Comp - Proc_3 (nr->Ptr_Comp) + Ptr_Val_Par->Int_Comp := 5 + Next_Record->Int_Comp := Ptr_Val_Par->Int_Comp + Next_Record->Ptr_Comp := Ptr_Val_Par->Ptr_Comp + Proc_3 (INOUT Next_Record->Ptr_Comp) % Ptr_Val_Par->Ptr_Comp->Ptr_Comp == Ptr_Glob->Ptr_Comp - IF nr->Discr = Enumeration.Ident_1 THEN + IF Next_Record->Discr = Enumeration.Ident_1 THEN % then, executed - nr->Int_Comp := 6 - Proc_6 (pvp->Enum_Comp, - nr->Enum_Comp) - IF VALID pg := Ptr_Glob THEN - nr->Ptr_Comp := pg->Ptr_Comp + Next_Record->Int_Comp := 6 + Proc_6 (Ptr_Val_Par->Enum_Comp, + OUT Next_Record->Enum_Comp) + IF VALID Ptr_Glob AS pg THEN + Next_Record->Ptr_Comp := pg->Ptr_Comp END IF - Proc_7 (nr->Int_Comp, 10, - nr->Int_Comp) + Proc_7 (Next_Record->Int_Comp, 10, + OUT Next_Record->Int_Comp) ELSE - value_copy(pvp, nr) + valueCopy(Ptr_Val_Par, Next_Record) END IF END IF END IF @@ -465,10 +461,10 @@ FUNCTION Proc_3 (INOUT Ptr_Ref_Par: POINTER TO Record) % executed once % Ptr_Ref_Par becomes Ptr_Glob - IF VALID pg := Ptr_Glob THEN + IF VALID Ptr_Glob THEN % then, executed - Ptr_Ref_Par := pg->Ptr_Comp - Proc_7 (10, Int_Glob, pg->Int_Comp) + Ptr_Ref_Par := Ptr_Glob->Ptr_Comp + Proc_7 (10, Int_Glob, OUT Ptr_Glob->Int_Comp) END IF END FUNCTION % Proc_3 @@ -602,7 +598,7 @@ FUNCTION Func_2 (IN Str_1_Par_Ref: String, IN Str_2_Par_Ref: String): Boolean % Str_2_Par_Ref == "DHRYSTONE PROGRAM, 2'ND STRING" VAR Int_Loc: One_Thirty - VAR Ch_Loc: Capital_Letter + VAR Ch_Loc: Capital_Letter := "" Int_Loc := 2 WHILE Int_Loc <= 2 DO % loop body executed once @@ -638,13 +634,13 @@ FUNCTION main() % main program, corresponds to procedures % Main and Proc_0 in the Ada version - VAR Int_1_Loc: One_Fifty - VAR Int_2_Loc: One_Fifty - VAR Int_3_Loc: One_Fifty + VAR Int_1_Loc: One_Fifty := 0 + VAR Int_2_Loc: One_Fifty := 0 + VAR Int_3_Loc: One_Fifty := 0 VAR Ch_Index: String - VAR Enum_Loc: Enumeration + VAR Enum_Loc: Enumeration := Enumeration.Ident_1 VAR Str_1_Loc: Str_30 - VAR Str_2_Loc: Str_30 + VAR Str_2_Loc: Str_30 := "" VAR Run_Index: Number VAR Number_Of_Runs: Number @@ -653,12 +649,12 @@ FUNCTION main() Next_Ptr_Glob := NEW Record Ptr_Glob := NEW Record - IF VALID pg := Ptr_Glob THEN - pg->Ptr_Comp := Next_Ptr_Glob - pg->Discr := Enumeration.Ident_1 - pg->Enum_Comp := Enumeration.Ident_3 - pg->Int_Comp := 40 - pg->Str_Comp := "DHRYSTONE PROGRAM, SOME STRING" + IF VALID Ptr_Glob THEN + Ptr_Glob->Ptr_Comp := Next_Ptr_Glob + Ptr_Glob->Discr := Enumeration.Ident_1 + Ptr_Glob->Enum_Comp := Enumeration.Ident_3 + Ptr_Glob->Int_Comp := 40 + Ptr_Glob->Str_Comp := "DHRYSTONE PROGRAM, SOME STRING" END IF Str_1_Loc := "DHRYSTONE PROGRAM, 1'ST STRING" @@ -695,12 +691,12 @@ FUNCTION main() WHILE Int_1_Loc < Int_2_Loc DO % loop body executed once Int_3_Loc := 5 * Int_1_Loc - Int_2_Loc % Int_3_Loc == 7 - Proc_7 (Int_1_Loc, Int_2_Loc, Int_3_Loc) + Proc_7 (Int_1_Loc, Int_2_Loc, OUT Int_3_Loc) % Int_3_Loc == 7 Int_1_Loc := Int_1_Loc + 1 END WHILE % Int_1_Loc == 3, Int_2_Loc == 3, Int_3_Loc == 7 - Proc_8 (Arr_1_Glob, Arr_2_Glob, Int_1_Loc, Int_3_Loc) + Proc_8 (INOUT Arr_1_Glob, INOUT Arr_2_Glob, Int_1_Loc, Int_3_Loc) % Int_Glob == 5 Proc_1 (Ptr_Glob) Ch_Index := "A" @@ -708,7 +704,7 @@ FUNCTION main() % loop body executed twice IF Enum_Loc = Func_1 (Ch_Index, "C") THEN % then, not executed - Proc_6 (Enumeration.Ident_1, Enum_Loc) + Proc_6 (Enumeration.Ident_1, OUT Enum_Loc) Str_2_Loc := "DHRYSTONE PROGRAM, 3'RD STRING" Int_2_Loc := Run_Index Int_Glob := Run_Index @@ -720,7 +716,7 @@ FUNCTION main() Int_1_Loc := math.floor(Int_2_Loc / Int_3_Loc) Int_2_Loc := 7 * (Int_2_Loc - Int_3_Loc) - Int_1_Loc % Int_1_Loc == 1, Int_2_Loc == 13, Int_3_Loc == 7 - Proc_2 (Int_1_Loc) + Proc_2 (INOUT Int_1_Loc) % Int_1_Loc == 5 Run_Index := Run_Index + 1 @@ -748,30 +744,30 @@ FUNCTION main() print (" should be: " & str(7)) print ("Arr_2_Glob[8][7]: " & str(Arr_2_Glob[8][7])) print (" should be: Number_Of_Runs + 10") - IF VALID pg := Ptr_Glob THEN + IF VALID Ptr_Glob THEN print ("Ptr_Glob->") - print (" Ptr_Comp: ") % & pg->Ptr_Comp) + print (" Ptr_Comp: ") % & Ptr_Glob->Ptr_Comp) print (" should be: (implementation-dependent)") - print (" Discr: " & pg->Discr.to_string()) + print (" Discr: " & Ptr_Glob->Discr.toString()) print (" should be: " & "Ident_1") - print (" Enum_Comp: " & pg->Enum_Comp.to_string()) + print (" Enum_Comp: " & Ptr_Glob->Enum_Comp.toString()) print (" should be: " & "Ident_3") - print (" Int_Comp: " & str(pg->Int_Comp)) + print (" Int_Comp: " & str(Ptr_Glob->Int_Comp)) print (" should be: " & str(17)) - print (" Str_Comp: " & pg->Str_Comp) + print (" Str_Comp: " & Ptr_Glob->Str_Comp) print (" should be: DHRYSTONE PROGRAM, SOME STRING") END IF - IF VALID npg := Next_Ptr_Glob THEN + IF VALID Next_Ptr_Glob THEN print ("Next_Ptr_Glob->") - print (" Ptr_Comp: ") % & npg->Ptr_Comp) + print (" Ptr_Comp: ") % & Next_Ptr_Glob->Ptr_Comp) print (" should be: (implementation-dependent), same as above") - print (" Discr: " & npg->Discr.to_string()) + print (" Discr: " & Next_Ptr_Glob->Discr.toString()) print (" should be: " & "Ident_1") - print (" Enum_Comp: " & npg->Enum_Comp.to_string()) + print (" Enum_Comp: " & Next_Ptr_Glob->Enum_Comp.toString()) print (" should be: " & "Ident_2") - print (" Int_Comp: " & str(npg->Int_Comp)) + print (" Int_Comp: " & str(Next_Ptr_Glob->Int_Comp)) print (" should be: " & str(18)) - print (" Str_Comp: " & npg->Str_Comp) + print (" Str_Comp: " & Next_Ptr_Glob->Str_Comp) print (" should be: DHRYSTONE PROGRAM, SOME STRING") END IF print ("Int_1_Loc: " & str(Int_1_Loc)) @@ -780,7 +776,7 @@ FUNCTION main() print (" should be: " & str(13)) print ("Int_3_Loc: " & str(Int_3_Loc)) print (" should be: " & str(7)) - print ("Enum_Loc: " & Enum_Loc.to_string()) + print ("Enum_Loc: " & Enum_Loc.toString()) print (" should be: " & "Ident_2") print ("Str_1_Loc: " & Str_1_Loc) print (" should be: DHRYSTONE PROGRAM, 1'ST STRING") diff --git a/samples/whetstone.neon b/samples/benchmark/whetstone.neon similarity index 71% rename from samples/whetstone.neon rename to samples/benchmark/whetstone.neon index 0475d2a9b1..13a8078030 100644 --- a/samples/whetstone.neon +++ b/samples/benchmark/whetstone.neon @@ -62,39 +62,39 @@ IMPORT time %| COMMON T,T1,T2,E1(4),J,K,L |% -VAR T: Number +VAR t: Number VAR T1: Number VAR T2: Number VAR E1: Array -VAR J: Number -VAR K: Number -VAR L: Number - -FUNCTION PA(INOUT E: Array) - J := 0 - WHILE J < 6 DO - E[1] := ( E[1] + E[2] + E[3] - E[4]) * T - E[2] := ( E[1] + E[2] - E[3] + E[4]) * T - E[3] := ( E[1] - E[2] + E[3] + E[4]) * T - E[4] := (-E[1] + E[2] + E[3] + E[4]) / T2 - J := J + 1 +VAR j: Number +VAR k: Number +VAR l: Number + +FUNCTION pa(INOUT e: Array) + j := 0 + WHILE j < 6 DO + e[1] := ( e[1] + e[2] + e[3] - e[4]) * t + e[2] := ( e[1] + e[2] - e[3] + e[4]) * t + e[3] := ( e[1] - e[2] + e[3] + e[4]) * t + e[4] := (-e[1] + e[2] + e[3] + e[4]) / T2 + j := j + 1 END WHILE END FUNCTION FUNCTION P0() - E1[J] := E1[K] - E1[K] := E1[L] - E1[L] := E1[J] + E1[j] := E1[k] + E1[k] := E1[l] + E1[l] := E1[j] END FUNCTION -FUNCTION P3(X: Number, Y: Number, OUT Z: Number) +FUNCTION P3(x: Number, y: Number, OUT z: Number) VAR X1, Y1: Number - X1 := X - Y1 := Y - X1 := T * (X1 + Y1) - Y1 := T * (X1 + Y1) - Z := (X1 + Y1) / T2 + X1 := x + Y1 := y + X1 := t * (X1 + Y1) + Y1 := t * (X1 + Y1) + z := (X1 + Y1) / T2 END FUNCTION %#ifdef PRINTOUT @@ -109,16 +109,15 @@ END FUNCTION FUNCTION main() % used in the FORTRAN version - VAR I: Number VAR N1, N2, N3, N4, N6, N7, N8, N9, N10, N11: Number - VAR X1,X2,X3,X4,X,Y,Z: Number + VAR X1,X2,X3,X4,x,y,z: Number VAR loop: Number - VAR II, JJ: Number + VAR ii, jj: Number % added for this version VAR loopstart: Number VAR startsec, finisec: Number - VAR KIPS: Number + VAR Kips: Number VAR continuous: Boolean loopstart := 1000 % see the note about LOOP below @@ -151,7 +150,7 @@ C C The actual benchmark starts here. C |% - T := 0.499975 + t := 0.499975 T1 := 0.50025 T2 := 2.0 %| @@ -163,11 +162,11 @@ C loop := 1000 |% loop := loopstart - II := 1 + ii := 1 - JJ := 1 + jj := 1 - WHILE JJ <= II DO + WHILE jj <= ii DO N1 := 0 N2 := 12 * loop N3 := 14 * loop @@ -188,11 +187,11 @@ C X3 := -1.0 X4 := -1.0 - FOR I := 1 TO N1 DO - X1 := (X1 + X2 + X3 - X4) * T - X2 := (X1 + X2 - X3 + X4) * T - X3 := (X1 - X2 + X3 + X4) * T - X4 := (-X1+ X2 + X3 + X4) * T + FOR i := 1 TO N1 DO + X1 := (X1 + X2 + X3 - X4) * t + X2 := (X1 + X2 - X3 + X4) * t + X3 := (X1 - X2 + X3 + X4) * t + X4 := (-X1+ X2 + X3 + X4) * t END FOR %#ifdef PRINTOUT % IF (JJ==II)POUT(N1,N1,N1,X1,X2,X3,X4); @@ -208,11 +207,11 @@ C E1[3] := -1.0 E1[4] := -1.0 - FOR I := 1 TO N2 DO - E1[1] := ( E1[1] + E1[2] + E1[3] - E1[4]) * T - E1[2] := ( E1[1] + E1[2] - E1[3] + E1[4]) * T - E1[3] := ( E1[1] - E1[2] + E1[3] + E1[4]) * T - E1[4] := (-E1[1] + E1[2] + E1[3] + E1[4]) * T + FOR i := 1 TO N2 DO + E1[1] := ( E1[1] + E1[2] + E1[3] - E1[4]) * t + E1[2] := ( E1[1] + E1[2] - E1[3] + E1[4]) * t + E1[3] := ( E1[1] - E1[2] + E1[3] + E1[4]) * t + E1[4] := (-E1[1] + E1[2] + E1[3] + E1[4]) * t END FOR %#ifdef PRINTOUT @@ -224,8 +223,8 @@ C C Module 3: Array as parameter C |% - FOR I := 1 TO N3 DO - PA(E1) + FOR i := 1 TO N3 DO + pa(INOUT E1) END FOR %#ifdef PRINTOUT @@ -237,24 +236,24 @@ C C Module 4: Conditional jumps C |% - J := 1 - FOR I := 1 TO N4 DO - IF J = 1 THEN - J := 2 + j := 1 + FOR i := 1 TO N4 DO + IF j = 1 THEN + j := 2 ELSE - J := 3 + j := 3 END IF - IF J > 2 THEN - J := 0 + IF j > 2 THEN + j := 0 ELSE - J := 1 + j := 1 END IF - IF J < 1 THEN - J := 1 + IF j < 1 THEN + j := 1 ELSE - J := 0 + j := 0 END IF END FOR @@ -269,16 +268,16 @@ C Module 6: Integer arithmetic C |% - J := 1 - K := 2 - L := 3 + j := 1 + k := 2 + l := 3 - FOR I := 1 TO N6 DO - J := J * (K-J) * (L-K) - K := L * K - (L-J) * K - L := (L-K) * (K+J) - E1[L-1] := J + K + L - E1[K-1] := J * K * L + FOR i := 1 TO N6 DO + j := j * (k-j) * (l-k) + k := l * k - (l-j) * k + l := (l-k) * (k+j) + E1[l-1] := j + k + l + E1[k-1] := j * k * l END FOR %#ifdef PRINTOUT @@ -290,12 +289,12 @@ C C Module 7: Trigonometric functions C |% - X := 0.5 - Y := 0.5 + x := 0.5 + y := 0.5 - FOR I := 1 TO N7 DO - X := T * math.atan(T2*math.sin(X)*math.cos(X)/(math.cos(X+Y)+math.cos(X-Y)-1.0)) - Y := T * math.atan(T2*math.sin(Y)*math.cos(Y)/(math.cos(X+Y)+math.cos(X-Y)-1.0)) + FOR i := 1 TO N7 DO + x := t * math.atan(T2*math.sin(x)*math.cos(x)/(math.cos(x+y)+math.cos(x-y)-1.0)) + y := t * math.atan(T2*math.sin(y)*math.cos(y)/(math.cos(x+y)+math.cos(x-y)-1.0)) END FOR %#ifdef PRINTOUT @@ -307,12 +306,12 @@ C C Module 8: Procedure calls C |% - X := 1.0 - Y := 1.0 - Z := 1.0 + x := 1.0 + y := 1.0 + z := 1.0 - FOR I := 1 TO N8 DO - P3(X,Y,Z) + FOR i := 1 TO N8 DO + P3(x,y,OUT z) END FOR %#ifdef PRINTOUT @@ -324,14 +323,14 @@ C C Module 9: Array references C |% - J := 1 - K := 2 - L := 3 + j := 1 + k := 2 + l := 3 E1[1] := 1.0 E1[2] := 2.0 E1[3] := 3.0 - FOR I := 1 TO N9 DO + FOR i := 1 TO N9 DO P0() END FOR @@ -344,14 +343,14 @@ C C Module 10: Integer arithmetic C |% - J := 2 - K := 3 - - FOR I := 1 TO N10 DO - J := J + K - K := J + K - J := K - J - K := K - J - J + j := 2 + k := 3 + + FOR i := 1 TO N10 DO + j := j + k + k := j + k + j := k - j + k := k - j - j END FOR %#ifdef PRINTOUT @@ -363,10 +362,10 @@ C C Module 11: Standard functions C |% - X := 0.75 + x := 0.75 - FOR I := 1 TO N11 DO - X := math.sqrt(math.exp(math.log(X)/T1)) + FOR i := 1 TO N11 DO + x := math.sqrt(math.exp(math.log(x)/T1)) END FOR %#ifdef PRINTOUT @@ -378,7 +377,7 @@ C C THIS IS THE END OF THE MAJOR LOOP. C |% - JJ := JJ + 1 + jj := jj + 1 END WHILE %| @@ -403,13 +402,13 @@ C-------------------------------------------------------------------- % RETURN END IF - print("Loops: " & str(loop) & ", Iterations: " & str(II) & ", Duration: " & str(finisec-startsec) & ".") + print("Loops: " & str(loop) & ", Iterations: " & str(ii) & ", Duration: " & str(finisec-startsec) & ".") - KIPS := (100.0*loop*II)/(finisec-startsec) - IF KIPS >= 1000.0 THEN - print("Neon Converted Double Precision Whetstones: " & str(KIPS/1000.0) & " MIPS") + Kips := (100.0*loop*ii)/(finisec-startsec) + IF Kips >= 1000.0 THEN + print("Neon Converted Double Precision Whetstones: " & str(Kips/1000.0) & " MIPS") ELSE - print("Neon Converted Double Precision Whetstones: " & str(KIPS) & " KIPS") + print("Neon Converted Double Precision Whetstones: " & str(Kips) & " KIPS") END IF %END WHILE diff --git a/samples/cal/cal.neon b/samples/cal/cal.neon new file mode 100644 index 0000000000..8c8b5b5bcc --- /dev/null +++ b/samples/cal/cal.neon @@ -0,0 +1,117 @@ +%| + | File: cal + | + | Implementation of the Unix cal command. + |% + +IMPORT datetime +IMPORT sys + +TYPE DateTime IS datetime.DateTime +TYPE Period IS datetime.Period + +CONSTANT MonthName: Array := [ + "", % Month numbers start at 1 + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +] + +FUNCTION build_month(year, month: Number, year_in_title: Boolean): Array + VAR dt: DateTime := DateTime() + dt := dt.withDate(year, month, 1) + % TODO: module record constructor + VAR p: Period := Period() + p.days := dt.weekday MOD 7 + %dt := dt.minusPeriod(p) + dt := dt.minusDuration(86400 * p.days) + LET title: String := IF year_in_title THEN "\(MonthName[month]) \(year)" ELSE MonthName[month] + VAR r: Array := ["\(title:^21)", "Su Mo Tu We Th Fr Sa "] + FOR i := 1 TO 6*7 DO + IF dt.weekday = 7 THEN + r.append("") + END IF + IF dt.month = month THEN + r[r.size()-1].append("\(dt.day:>2d) ") + ELSE + r[r.size()-1].append(" ") + END IF + p.days := 1 + dt := dt.plusPeriod(p) + END FOR + RETURN r +END FUNCTION + +FUNCTION main(args: Array) + + VAR month, year: Number := 0 + VAR options: Dictionary + + FOREACH a OF args[1 TO LAST] DO + IF a[0] = "-" THEN + options[a] := TRUE + ELSIF year = 0 THEN + year := num(a) + ELSE + month := year + year := num(a) + END IF + END FOREACH + + IF year = 0 THEN + LET today: datetime.DateTime := datetime.now() + year := today.year + IF "-y" NOT IN options THEN + month := today.month + END IF + END IF + + IF month > 0 THEN + IF "-3" IN options THEN + LET months: Array> := [ + IF month > 1 THEN build_month(year, month-1, TRUE) ELSE build_month(year-1, 12, TRUE), + build_month(year, month, TRUE), + IF month < 12 THEN build_month(year, month+1, TRUE) ELSE build_month(year+1, 1, TRUE), + ] + FOR line := 0 TO 7 DO + VAR s: String := "" + FOR col := 0 TO 2 DO + s.append(months[col][line] & " ") + END FOR + print(s) + END FOR + ELSE + LET grid: Array := build_month(year, month, TRUE) + FOREACH s OF grid DO + print(s) + END FOREACH + END IF + ELSE + VAR months: Array> := [] + FOR m := 1 TO 12 DO + months.append(build_month(year, m, FALSE)) + END FOR + print("\(year:^66)") + print("") + FOR row := 0 TO 3 DO + FOR line := 0 TO 7 DO + VAR s: String := "" + FOR col := 0 TO 2 DO + s.append(months[3*row+col][line] & " ") + END FOR + print(s) + END FOR + END FOR + END IF +END FUNCTION + +main(sys.args) diff --git a/samples/fizzbuzz/fizzbuzz.neon b/samples/fizzbuzz/fizzbuzz.neon new file mode 100644 index 0000000000..30e1621b66 --- /dev/null +++ b/samples/fizzbuzz/fizzbuzz.neon @@ -0,0 +1,20 @@ +%| + | File: fizzbuzz + | + | For each integer from 1 to 100, print "Fizz" if the number + | is divisible by 3, or "Buzz" if the number is divisible + | by 5, or "FizzBuzz" if the number is divisible by both. + | Otherwise, print the number itself. + |% + +FOR i := 1 TO 100 DO + IF i MOD 15 = 0 THEN + print("FizzBuzz") + ELSIF i MOD 3 = 0 THEN + print("Fizz") + ELSIF i MOD 5 = 0 THEN + print("Buzz") + ELSE + print(i.toString()) + END IF +END FOR diff --git a/samples/flappy/flappy.neon b/samples/flappy/flappy.neon new file mode 100644 index 0000000000..6888485f5c --- /dev/null +++ b/samples/flappy/flappy.neon @@ -0,0 +1,86 @@ +%| + | File: flappy + | + | Implementation of a "Flappy Bird" clone. + |% + +IMPORT random +IMPORT sdl + +TYPE Rect IS sdl.Rect + +FUNCTION rect(x, y, w, h: Number): sdl.Rect + VAR r: sdl.Rect := Rect() + r.x := x + r.y := y + r.w := w + r.h := h + RETURN r +END FUNCTION + +TYPE Player IS RECORD + y: Number + dy: Number +END RECORD + +TYPE Pipe IS RECORD + x: Number + gap: Number +END RECORD + +VAR player: Player +VAR pipes: Array + +BEGIN MAIN + sdl.Init(sdl.INIT_VIDEO) + LET win: sdl.Window := sdl.CreateWindow("Hello World!", 100, 100, 640, 480, sdl.WINDOW_SHOWN) + LET ren: sdl.Renderer := sdl.CreateRenderer(win, -1, sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC) + LET bmp: sdl.Surface := sdl.LoadBMP("tmp/hello.bmp") + LET tex: sdl.Texture := sdl.CreateTextureFromSurface(ren, bmp) + sdl.FreeSurface(bmp) + player.y := 0 + player.dy := 0 + FOR i := 0 TO 4 DO + pipes[i].x := 640 + i * 320 + pipes[i].gap := random.uint32() MOD 300 + 100 + END FOR + VAR quit: Boolean := FALSE + WHILE NOT quit DO + VAR e: sdl.Event + WHILE sdl.PollEvent(OUT e) DO + CASE e.type + WHEN sdl.SDL_QUIT DO + quit := TRUE + WHEN sdl.SDL_KEYDOWN DO + player.dy := -3 + END CASE + END WHILE + sdl.RenderClear(ren) + sdl.RenderCopy(ren, tex, sdl.NullRect, sdl.NullRect) + sdl.SetRenderDrawColor(ren, 0, 255, 0, 255) + sdl.RenderFillRect(ren, rect(300, player.y, 50, 50)) + FOREACH pipe OF pipes DO + sdl.RenderFillRect(ren, rect(pipe.x, 0, 100, pipe.gap)) + sdl.RenderFillRect(ren, rect(pipe.x, pipe.gap+200, 100, 480)) + END FOREACH + sdl.RenderPresent(ren) + player.y := player.y + player.dy + player.dy := player.dy + 0.1 + FOR i := 0 TO pipes.size()-1 DO + pipes[i].x := pipes[i].x - 2 + END FOR + WHILE pipes[0].x < -100 DO + pipes[0 TO 0] := [] + END WHILE + WHILE pipes.size() < 5 DO + VAR p: Pipe + p.x := pipes[LAST].x + 320 + p.gap := random.uint32() MOD 300 + 100 + pipes.append(p) + END WHILE + END WHILE + sdl.DestroyTexture(tex) + sdl.DestroyRenderer(ren) + sdl.DestroyWindow(win) + sdl.Quit() +END MAIN diff --git a/samples/forth/forth.neon b/samples/forth/forth.neon new file mode 100644 index 0000000000..6d1f1ff1cb --- /dev/null +++ b/samples/forth/forth.neon @@ -0,0 +1,1377 @@ +%| + | File: forth + | + | Implementation of the Forth programming language. + |% + +IMPORT bitwise +IMPORT io +IMPORT math +IMPORT regex +IMPORT string +IMPORT sys + +CONSTANT NUMBER_BUFFER_MAX: Number := 80 +CONSTANT Digits: String := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +TYPE Stack IS RECORD + stack: Array +END RECORD + +FUNCTION Stack.drop(INOUT self: Stack, n: Number DEFAULT 1) + self.stack.resize(self.stack.size() - n) +END FUNCTION + +FUNCTION Stack.peek(self: Stack, n: Number DEFAULT 0): Number + RETURN self.stack[LAST-n] +END FUNCTION + +FUNCTION Stack.pop(INOUT self: Stack): Number + LET x: Number := self.stack[LAST] + self.stack.resize(self.stack.size()-1) + RETURN x +END FUNCTION + +FUNCTION Stack.push(INOUT self: Stack, x: Number) + self.stack.append(x MOD 0x100000000) +END FUNCTION + +FUNCTION Stack.size(self: Stack): Number + RETURN self.stack.size() +END FUNCTION + +FUNCTION signed32(n: Number): Number + RETURN IF n >= 0x80000000 THEN n - 0x100000000 ELSE n +END FUNCTION + +TYPE ReturnEntry IS RECORD + ip: Number + limit: Number + index: Number + leave: Number +END RECORD + +CONSTANT CORE_STORE : Number := -1 +CONSTANT CORE_NUMBER : Number := -2 +CONSTANT CORE_NUMBER_GT : Number := -3 +CONSTANT CORE_NUMBER_S : Number := -4 +CONSTANT CORE_TICK : Number := -5 +CONSTANT CORE_COMMENT : Number := -6 +CONSTANT CORE_MUL : Number := -7 +CONSTANT CORE_MUL_DIV : Number := -8 +CONSTANT CORE_MUL_DIV_MOD : Number := -9 +CONSTANT CORE_ADD : Number := -10 +CONSTANT CORE_ADD_STORE : Number := -11 +CONSTANT CORE_PLUS_LOOP : Number := -12 +CONSTANT CORE_COMMA : Number := -13 +CONSTANT CORE_SUB : Number := -14 +CONSTANT CORE_PRINT : Number := -15 +CONSTANT CORE_PRINT_QUOTE : Number := -16 +CONSTANT CORE_DIV : Number := -17 +CONSTANT CORE_DIV_MOD : Number := -18 +CONSTANT CORE_0_LESS : Number := -19 +CONSTANT CORE_0_EQUAL : Number := -20 +CONSTANT CORE_1_PLUS : Number := -21 +CONSTANT CORE_1_MINUS : Number := -22 +CONSTANT CORE_2_STORE : Number := -23 +CONSTANT CORE_2_TIMES : Number := -24 +CONSTANT CORE_2_DIVIDE : Number := -25 +CONSTANT CORE_2_FETCH : Number := -26 +CONSTANT CORE_2DROP : Number := -27 +CONSTANT CORE_2DUP : Number := -28 +CONSTANT CORE_2OVER : Number := -29 +CONSTANT CORE_2SWAP : Number := -30 +CONSTANT CORE_DEFINE : Number := -31 +CONSTANT CORE_SEMICOLON : Number := -32 +CONSTANT CORE_LESS : Number := -33 +CONSTANT CORE_LESS_NUMBER : Number := -34 +CONSTANT CORE_NOT_EQUAL : Number := -35 +CONSTANT CORE_EQUAL : Number := -36 +CONSTANT CORE_GREATER : Number := -37 +CONSTANT CORE_TO_BODY : Number := -38 +CONSTANT CORE_TO_IN : Number := -39 +CONSTANT CORE_TO_NUMBER : Number := -40 +CONSTANT CORE_TO_R : Number := -41 +CONSTANT CORE_Q_DUP : Number := -42 +CONSTANT CORE_FETCH : Number := -43 +CONSTANT CORE_ABS : Number := -44 +CONSTANT CORE_ACCEPT : Number := -45 +CONSTANT CORE_ALIGN : Number := -46 +CONSTANT CORE_ALIGNED : Number := -47 +CONSTANT CORE_ALLOT : Number := -48 +CONSTANT CORE_AND : Number := -49 +CONSTANT CORE_BASE : Number := -50 +CONSTANT CORE_BEGIN : Number := -51 +CONSTANT CORE_BL : Number := -52 +CONSTANT CORE_C_STORE : Number := -53 +CONSTANT CORE_C_COMMA : Number := -54 +CONSTANT CORE_C_FETCH : Number := -55 +CONSTANT CORE_CELL_PLUS : Number := -56 +CONSTANT CORE_CELLS : Number := -57 +CONSTANT CORE_CHAR : Number := -58 +CONSTANT CORE_CHAR_PLUS : Number := -59 +CONSTANT CORE_CHARS : Number := -60 +CONSTANT CORE_CONSTANT : Number := -61 +CONSTANT CORE_COUNT : Number := -62 +CONSTANT CORE_CR : Number := -63 +CONSTANT CORE_CREATE : Number := -64 +CONSTANT CORE_DECIMAL : Number := -65 +CONSTANT CORE_DEPTH : Number := -66 +CONSTANT CORE_DO : Number := -67 +CONSTANT CORE_DOES : Number := -68 +CONSTANT CORE_DROP : Number := -69 +CONSTANT CORE_DUP : Number := -70 +CONSTANT CORE_ELSE : Number := -71 +CONSTANT CORE_EMIT : Number := -72 +CONSTANT CORE_ENVIRONMENT_Q: Number := -73 +CONSTANT CORE_EVALUATE : Number := -74 +CONSTANT CORE_EXECUTE : Number := -75 +CONSTANT CORE_EXIT : Number := -76 +CONSTANT CORE_FALSE : Number := -77 +CONSTANT CORE_FILL : Number := -78 +CONSTANT CORE_FIND : Number := -79 +CONSTANT CORE_FM_MOD : Number := -80 +CONSTANT CORE_HERE : Number := -81 +CONSTANT CORE_HEX : Number := -82 +CONSTANT CORE_HOLD : Number := -83 +CONSTANT CORE_I : Number := -84 +CONSTANT CORE_IF : Number := -85 +CONSTANT CORE_IMMEDIATE : Number := -86 +CONSTANT CORE_INVERT : Number := -87 +CONSTANT CORE_J : Number := -88 +CONSTANT CORE_LEAVE : Number := -89 +CONSTANT CORE_LITERAL : Number := -90 +CONSTANT CORE_LOOP : Number := -91 +CONSTANT CORE_LSHIFT : Number := -92 +CONSTANT CORE_M_MUL : Number := -93 +CONSTANT CORE_MAX : Number := -94 +CONSTANT CORE_MIN : Number := -95 +CONSTANT CORE_MOD : Number := -96 +CONSTANT CORE_MOVE : Number := -97 +CONSTANT CORE_NEGATE : Number := -98 +CONSTANT CORE_NOT : Number := -99 +CONSTANT CORE_OR : Number := -100 +CONSTANT CORE_OVER : Number := -101 +CONSTANT CORE_PAD : Number := -102 +CONSTANT CORE_POSTPONE : Number := -103 +CONSTANT CORE_R_FROM : Number := -104 +CONSTANT CORE_R_FETCH : Number := -105 +CONSTANT CORE_RECURSE : Number := -106 +CONSTANT CORE_REPEAT : Number := -107 +CONSTANT CORE_ROT : Number := -108 +CONSTANT CORE_RSHIFT : Number := -109 +CONSTANT CORE_S_QUOTE : Number := -110 +CONSTANT CORE_S_TO_D : Number := -111 +CONSTANT CORE_SIGN : Number := -112 +CONSTANT CORE_SM_REM : Number := -113 +CONSTANT CORE_SOURCE : Number := -114 +CONSTANT CORE_SPACE : Number := -115 +CONSTANT CORE_SPACES : Number := -116 +CONSTANT CORE_STATE : Number := -117 +CONSTANT CORE_SWAP : Number := -118 +CONSTANT CORE_THEN : Number := -119 +CONSTANT CORE_TRUE : Number := -120 +CONSTANT CORE_TUCK : Number := -121 +CONSTANT CORE_TYPE : Number := -122 +CONSTANT CORE_U_PRINT : Number := -123 +CONSTANT CORE_U_LESS : Number := -124 +CONSTANT CORE_UM_MOD : Number := -125 +CONSTANT CORE_UM_MUL : Number := -126 +CONSTANT CORE_UNLOOP : Number := -127 +CONSTANT CORE_UNTIL : Number := -128 +CONSTANT CORE_VARIABLE : Number := -129 +CONSTANT CORE_WHILE : Number := -130 +CONSTANT CORE_WORD : Number := -131 +CONSTANT CORE_XOR : Number := -132 +CONSTANT CORE_INTERPRET : Number := -133 +CONSTANT CORE_IMM_TICK : Number := -134 +CONSTANT CORE_IMM_CHAR : Number := -135 +CONSTANT CORE_BACKSLASH : Number := -136 +CONSTANT CORE_COMPILE : Number := -137 + +CONSTANT TOOLS_PRINT_S : Number := -201 +CONSTANT TOOLS_QUESTION : Number := -202 +CONSTANT TOOLS_DUMP : Number := -203 +CONSTANT TOOLS_SEE : Number := -204 +CONSTANT TOOLS_WORDS : Number := -205 + +CONSTANT TOOLS_EXT_BYE : Number := -301 +CONSTANT TOOLS_EXT_ELSE : Number := -302 +CONSTANT TOOLS_EXT_IF : Number := -303 +CONSTANT TOOLS_EXT_THEN : Number := -304 + +CONSTANT INT_BRANCH : Number := -1001 +CONSTANT INT_DO : Number := -1002 +CONSTANT INT_DOES : Number := -1003 +CONSTANT INT_EXIT : Number := -1004 +CONSTANT INT_LEAVE : Number := -1005 +CONSTANT INT_LITERAL : Number := -1006 +CONSTANT INT_LOOP : Number := -1007 +CONSTANT INT_PLUS_LOOP : Number := -1008 +CONSTANT INT_POSTPONE : Number := -1009 +CONSTANT INT_POSTPONE_IMM : Number := -1010 +CONSTANT INT_PRINT_QUOTE : Number := -1011 +CONSTANT INT_Q_BRANCH : Number := -1012 +CONSTANT INT_S_QUOTE : Number := -1013 +CONSTANT INT_UNLOOP : Number := -1014 + +TYPE Word IS RECORD + xt: Number + immediate: Boolean +END RECORD + +VAR words: Dictionary := { + "!": Word(CORE_STORE) + "#": Word(CORE_NUMBER) + "#>": Word(CORE_NUMBER_GT) + "#S": Word(CORE_NUMBER_S) + "'": Word(CORE_TICK) + "(": Word(CORE_COMMENT) + "*": Word(CORE_MUL) + "*/": Word(CORE_MUL_DIV) + "*/MOD": Word(CORE_MUL_DIV_MOD) + "+": Word(CORE_ADD) + "+!": Word(CORE_ADD_STORE) + "+LOOP": Word(CORE_PLUS_LOOP) + ",": Word(CORE_COMMA) + "-": Word(CORE_SUB) + ".": Word(CORE_PRINT) + ".\"": Word(CORE_PRINT_QUOTE) + ".S": Word(TOOLS_PRINT_S) + "/": Word(CORE_DIV) + "/MOD": Word(CORE_DIV_MOD) + "0<": Word(CORE_0_LESS) + "0=": Word(CORE_0_EQUAL) + "1+": Word(CORE_1_PLUS) + "1-": Word(CORE_1_MINUS) + "2!": Word(CORE_2_STORE) + "2*": Word(CORE_2_TIMES) + "2/": Word(CORE_2_DIVIDE) + "2@": Word(CORE_2_FETCH) + "2DROP": Word(CORE_2DROP) + "2DUP": Word(CORE_2DUP) + "2OVER": Word(CORE_2OVER) + "2SWAP": Word(CORE_2SWAP) + ":": Word(CORE_DEFINE) + ";": Word(CORE_SEMICOLON) + "<": Word(CORE_LESS) + "<#": Word(CORE_LESS_NUMBER) + "<>": Word(CORE_NOT_EQUAL) + "=": Word(CORE_EQUAL) + ">": Word(CORE_GREATER) + ">BODY": Word(CORE_TO_BODY) + ">IN": Word(CORE_TO_IN) + ">NUMBER": Word(CORE_TO_NUMBER) + ">R": Word(CORE_TO_R) + "?": Word(TOOLS_QUESTION) + "?DUP": Word(CORE_Q_DUP) + "@": Word(CORE_FETCH) + "ABS": Word(CORE_ABS) + "ACCEPT": Word(CORE_ACCEPT) + "ALIGN": Word(CORE_ALIGN) + "ALIGNED": Word(CORE_ALIGNED) + "ALLOT": Word(CORE_ALLOT) + "AND": Word(CORE_AND) + "BASE": Word(CORE_BASE) + "BEGIN": Word(CORE_BEGIN) + "BL": Word(CORE_BL) + "BYE": Word(TOOLS_EXT_BYE) + "C!": Word(CORE_C_STORE) + "C,": Word(CORE_C_COMMA) + "C@": Word(CORE_C_FETCH) + "CELL+": Word(CORE_CELL_PLUS) + "CELLS": Word(CORE_CELLS) + "CHAR": Word(CORE_CHAR) + "CHAR+": Word(CORE_CHAR_PLUS) + "CHARS": Word(CORE_CHARS) + "CONSTANT": Word(CORE_CONSTANT) + "COUNT": Word(CORE_COUNT) + "CR": Word(CORE_CR) + "CREATE": Word(CORE_CREATE) + "DECIMAL": Word(CORE_DECIMAL) + "DEPTH": Word(CORE_DEPTH) + "DO": Word(CORE_DO) + "DOES>": Word(CORE_DOES) + "DROP": Word(CORE_DROP) + "DUMP": Word(TOOLS_DUMP) + "DUP": Word(CORE_DUP) + "ELSE": Word(CORE_ELSE) + "EMIT": Word(CORE_EMIT) + "ENVIRONMENT?": Word(CORE_ENVIRONMENT_Q) + "EVALUATE": Word(CORE_EVALUATE) + "EXECUTE": Word(CORE_EXECUTE) + "EXIT": Word(CORE_EXIT) + "FALSE": Word(CORE_FALSE) + "FILL": Word(CORE_FILL) + "FIND": Word(CORE_FIND) + "FM/MOD": Word(CORE_FM_MOD) + "HERE": Word(CORE_HERE) + "HEX": Word(CORE_HEX) + "HOLD": Word(CORE_HOLD) + "I": Word(CORE_I) + "IF": Word(CORE_IF) + "IMMEDIATE": Word(CORE_IMMEDIATE) + "INVERT": Word(CORE_INVERT) + "J": Word(CORE_J) + "LEAVE": Word(CORE_LEAVE) + "LITERAL": Word(CORE_LITERAL) + "LOOP": Word(CORE_LOOP) + "LSHIFT": Word(CORE_LSHIFT) + "M*": Word(CORE_M_MUL) + "MAX": Word(CORE_MAX) + "MIN": Word(CORE_MIN) + "MOD": Word(CORE_MOD) + "MOVE": Word(CORE_MOVE) + "NEGATE": Word(CORE_NEGATE) + "NOT": Word(CORE_NOT) + "OR": Word(CORE_OR) + "OVER": Word(CORE_OVER) + "PAD": Word(CORE_PAD) + "POSTPONE": Word(CORE_POSTPONE) + "R>": Word(CORE_R_FROM) + "R@": Word(CORE_R_FETCH) + "RECURSE": Word(CORE_RECURSE) + "REPEAT": Word(CORE_REPEAT) + "ROT": Word(CORE_ROT) + "RSHIFT": Word(CORE_RSHIFT) + "S\"": Word(CORE_S_QUOTE) + "S>D": Word(CORE_S_TO_D) + "SEE": Word(TOOLS_SEE) + "SIGN": Word(CORE_SIGN) + "SM/REM": Word(CORE_SM_REM) + "SOURCE": Word(CORE_SOURCE) + "SPACE": Word(CORE_SPACE) + "SPACES": Word(CORE_SPACES) + "STATE": Word(CORE_STATE) + "SWAP": Word(CORE_SWAP) + "THEN": Word(CORE_THEN) + "TRUE": Word(CORE_TRUE) + "TUCK": Word(CORE_TUCK) + "TYPE": Word(CORE_TYPE) + "U.": Word(CORE_U_PRINT) + "U<": Word(CORE_U_LESS) + "UM/MOD": Word(CORE_UM_MOD) + "UM*": Word(CORE_UM_MUL) + "UNLOOP": Word(CORE_UNLOOP) + "UNTIL": Word(CORE_UNTIL) + "VARIABLE": Word(CORE_VARIABLE) + "WHILE": Word(CORE_WHILE) + "WORD": Word(CORE_WORD) + "WORDS": Word(TOOLS_WORDS) + "XOR": Word(CORE_XOR) + "[": Word(CORE_INTERPRET) + "[']": Word(CORE_IMM_TICK) + "[CHAR]": Word(CORE_IMM_CHAR) + "[ELSE]": Word(TOOLS_EXT_ELSE) + "[IF]": Word(TOOLS_EXT_IF) + "[THEN]": Word(TOOLS_EXT_THEN) + @"\": Word(CORE_BACKSLASH) + "]": Word(CORE_COMPILE) +} +VAR code: Array := [0] +VAR stack: Stack +VAR storage: Array +VAR defining: String +VAR define_start: Number +VAR compilation: Stack +VAR return: Array + +FUNCTION alloc(n: Number, INOUT i: Number): Number + LET r: Number := i + i := i + n + RETURN r +END FUNCTION + +VAR index: Number := 0 +LET cell_state: Number := alloc(1, INOUT index) +LET cell_normal_input: Number := alloc(200, INOUT index) +LET cell_normal_input_length: Number := alloc(1, INOUT index) +LET cell_normal_in: Number := alloc(1, INOUT index) +LET cell_eval_input_length: Number := alloc(1, INOUT index) +LET cell_eval_in: Number := alloc(1, INOUT index) +LET cell_base: Number := alloc(1, INOUT index) +LET cell_pad: Number := alloc(84, INOUT index) +LET cell_number_buffer: Number := alloc(NUMBER_BUFFER_MAX, INOUT index) +VAR number_buffer_size: Number +storage[cell_base] := 10 +storage[index] := 0 + +VAR cell_input: Number := cell_normal_input +VAR cell_input_length: Number := cell_normal_input_length +VAR cell_in: Number := cell_normal_in + +FUNCTION get_string_len(addr, len: Number): String + VAR r: String := "" + FOR i := 0 TO len-1 DO + r.append(chr(storage[addr+i])) + END FOR + RETURN r +END FUNCTION + +FUNCTION get_string(addr: Number): String + LET len: Number := storage[addr] + RETURN get_string_len(addr+1, len) +END FUNCTION + +FUNCTION run_instruction(instr: Number, INOUT ip: Number) + CASE instr + WHEN > 0 DO + return.append(ReturnEntry(ip+1)) + ip := instr + WHEN 0 DO + ip := return[LAST].ip + return.resize(return.size()-1) + WHEN CORE_STORE DO + LET addr: Number := stack.pop() + LET x: Number := stack.pop() + storage[addr] := x + WHEN CORE_NUMBER DO + LET ud1h: Number := stack.pop() + LET ud1l: Number := stack.pop() + LET ud1: Number := ud1h * 0x100000000 + ud1l + LET dig: Number := ud1 MOD storage[cell_base] + LET ud2: Number := math.floor(ud1 / storage[cell_base]) + stack.push(ud2 MOD 0x100000000) + stack.push(math.floor(ud2 / 0x100000000)) + INC number_buffer_size + storage[cell_number_buffer + NUMBER_BUFFER_MAX - number_buffer_size] := ord(Digits[dig]) + WHEN CORE_NUMBER_GT DO + LET xdh: Number := stack.pop() + LET xdl: Number := stack.pop() + stack.push(cell_number_buffer + NUMBER_BUFFER_MAX - number_buffer_size) + stack.push(number_buffer_size) + WHEN CORE_NUMBER_S DO + LET ud1h: Number := stack.pop() + LET ud1l: Number := stack.pop() + VAR ud1: Number := ud1h * 0x100000000 + ud1l + REPEAT + LET dig: Number := ud1 MOD storage[cell_base] + ud1 := math.floor(ud1 / storage[cell_base]) + INC number_buffer_size + storage[cell_number_buffer + NUMBER_BUFFER_MAX - number_buffer_size] := ord(Digits[dig]) + UNTIL ud1 = 0 + stack.push(0) + stack.push(0) + WHEN CORE_MUL DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(n1 * n2) + WHEN CORE_MUL_DIV DO + LET n3: Number := signed32(stack.pop()) + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(math.floor(n1 * n2 / n3)) + WHEN CORE_MUL_DIV_MOD DO + LET n3: Number := signed32(stack.pop()) + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + LET p: Number := n1 * n2 + stack.push(p MOD n3) + stack.push(math.floor(p / n3)) + WHEN CORE_ADD DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(n1 + n2) + WHEN CORE_ADD_STORE DO + LET addr: Number := stack.pop() + LET n: Number := signed32(stack.pop()) + storage[addr] := storage[addr] + n + WHEN CORE_COMMA DO + LET x: Number := stack.pop() + storage.append(x) + WHEN CORE_SUB DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(n1 - n2) + WHEN CORE_PRINT DO + LET n: Number := signed32(stack.pop()) + io.write(io.stdout, "\(n) ") + WHEN CORE_DIV DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(math.floor(n1 / n2)) + WHEN CORE_DIV_MOD DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(n1 MOD n2) + stack.push(math.floor(n1 / n2)) + WHEN CORE_0_LESS DO + LET n: Number := signed32(stack.pop()) + stack.push(IF n < 0 THEN -1 ELSE 0) + WHEN CORE_0_EQUAL DO + LET n: Number := stack.pop() + stack.push(IF n = 0 THEN -1 ELSE 0) + WHEN CORE_1_PLUS DO + LET n1: Number := signed32(stack.pop()) + stack.push(n1 + 1) + WHEN CORE_1_MINUS DO + LET n1: Number := signed32(stack.pop()) + stack.push(n1 - 1) + WHEN CORE_2_STORE DO + LET addr: Number := stack.pop() + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + storage[addr] := x2 + storage[addr+1] := x1 + WHEN CORE_2_TIMES DO + LET n1: Number := stack.pop() + stack.push(bitwise.shiftLeft32(n1, 1)) + WHEN CORE_2_DIVIDE DO + LET n1: Number := signed32(stack.pop()) + stack.push(bitwise.shiftRightSigned32(n1, 1)) + WHEN CORE_2_FETCH DO + LET addr: Number := stack.pop() + stack.push(storage[addr+1]) + stack.push(storage[addr]) + WHEN CORE_2DROP DO + stack.drop(2) + WHEN CORE_2DUP DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x1) + stack.push(x2) + stack.push(x1) + stack.push(x2) + WHEN CORE_2OVER DO + LET x4: Number := stack.pop() + LET x3: Number := stack.pop() + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x1) + stack.push(x2) + stack.push(x3) + stack.push(x4) + stack.push(x1) + stack.push(x2) + WHEN CORE_2SWAP DO + LET x4: Number := stack.pop() + LET x3: Number := stack.pop() + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x3) + stack.push(x4) + stack.push(x1) + stack.push(x2) + WHEN CORE_DEFINE DO + defining := next_word() + define_start := code.size() + storage[cell_state] := -1 + WHEN CORE_LESS DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(IF n1 < n2 THEN -1 ELSE 0) + WHEN CORE_LESS_NUMBER DO + number_buffer_size := 0 + WHEN CORE_NOT_EQUAL DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(IF n1 # n2 THEN -1 ELSE 0) + WHEN CORE_EQUAL DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(IF n1 = n2 THEN -1 ELSE 0) + WHEN CORE_GREATER DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(IF n1 > n2 THEN -1 ELSE 0) + WHEN CORE_TO_BODY DO + LET xt: Number := stack.pop() + ASSERT code[xt] = INT_LITERAL + ASSERT code[xt+2] = 0 OR code[xt+2] = INT_BRANCH + ASSERT code[xt+4] = 0 + stack.push(code[xt+1]) + WHEN CORE_TO_IN DO + stack.push(cell_in) + WHEN CORE_TO_NUMBER DO + VAR u: Number := stack.pop() + VAR addr: Number := stack.pop() + LET ud1h: Number := stack.pop() + LET ud1l: Number := stack.pop() + VAR ud1: Number := ud1h * 0x100000000 + ud1l + WHILE u > 0 DO + LET char: String := chr(storage[addr]) + VAR dig: Number := 0 % FIXME: shouldn't need init here + IF "0" <= char <= "9" THEN + dig := ord(char) - ord("0") + ELSIF "A" <= char <= "Z" THEN + dig := ord(char) - ord("A") + 10 + ELSE + EXIT WHILE + END IF + IF dig >= storage[cell_base] THEN + EXIT WHILE + END IF + ud1 := ud1 * storage[cell_base] + dig + INC addr + DEC u + END WHILE + stack.push(ud1 MOD 0x100000000) + stack.push(math.floor(ud1 / 0x100000000)) + stack.push(addr) + stack.push(u) + WHEN CORE_TO_R DO + LET x: Number := stack.pop() + return.append(ReturnEntry(x)) + WHEN CORE_Q_DUP DO + LET x: Number := stack.pop() + stack.push(x) + IF x # 0 THEN + stack.push(x) + END IF + WHEN CORE_FETCH DO + LET addr: Number := stack.pop() + stack.push(storage[addr]) + WHEN CORE_ABS DO + LET n: Number := signed32(stack.pop()) + stack.push(math.abs(n)) + WHEN CORE_ACCEPT DO + LET n: Number := signed32(stack.pop()) + LET addr: Number := stack.pop() + LET s: String := input("") + FOR i := 0 TO min(n-1, s.length()-1) DO + storage[addr+i] := ord(s[i]) + END FOR + stack.push(s.length()) + WHEN CORE_ALIGN DO + % no action required + WHEN CORE_ALIGNED DO + % no action required + WHEN CORE_ALLOT DO + LET n: Number := signed32(stack.pop()) + storage.resize(storage.size() + n) + WHEN CORE_AND DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(bitwise.and32(x1, x2)) + WHEN CORE_BASE DO + stack.push(cell_base) + WHEN CORE_BL DO + stack.push(ord(" ")) + WHEN CORE_C_STORE DO + LET addr: Number := stack.pop() + LET x: Number := stack.pop() + storage[addr] := x + WHEN CORE_C_COMMA DO + LET x: Number := stack.pop() + storage.append(x) + WHEN CORE_C_FETCH DO + LET addr: Number := stack.pop() + stack.push(storage[addr]) + WHEN CORE_CELL_PLUS DO + LET addr: Number := stack.pop() + stack.push(addr+1) + WHEN CORE_CELLS DO + LET n: Number := signed32(stack.pop()) + stack.push(n) + WHEN CORE_CHAR DO + LET s: String := next_word() + stack.push(ord(s[FIRST])) + WHEN CORE_CHAR_PLUS DO + LET addr: Number := stack.pop() + stack.push(addr+1) + WHEN CORE_CHARS DO + LET n: Number := signed32(stack.pop()) + stack.push(n) + WHEN CORE_CONSTANT DO + LET x: Number := stack.pop() + LET s: String := next_word() + words[s] := Word(code.size()) + code.append(INT_LITERAL) + code.append(x) + code.append(0) + WHEN CORE_COUNT DO + LET addr: Number := stack.pop() + stack.push(addr+1) + stack.push(storage[addr]) + WHEN CORE_CR DO + print("") + WHEN CORE_CREATE DO + LET s: String := next_word() + words[s] := Word(code.size()) + code.append(INT_LITERAL) + code.append(storage.size()) + code.append(0) + code.append(0) + code.append(0) + WHEN CORE_DECIMAL DO + storage[cell_base] := 10 + WHEN CORE_DEPTH DO + stack.push(stack.size()) + WHEN CORE_DROP DO + stack.drop() + WHEN CORE_DUP DO + LET x: Number := stack.pop() + stack.push(x) + stack.push(x) + WHEN CORE_EMIT DO + LET x: Number := stack.pop() + io.write(io.stdout, chr(x)) + WHEN CORE_ENVIRONMENT_Q DO + LET u: Number := stack.pop() + LET addr: Number := stack.pop() + CASE get_string_len(addr, u) + WHEN "FLOATING" DO + stack.push(0) + WHEN OTHERS DO + stack.push(0) + END CASE + WHEN CORE_EVALUATE DO + LET u: Number := stack.pop() + LET addr: Number := stack.pop() + storage[cell_eval_input_length] := u + storage[cell_eval_in] := 0 + cell_input := addr + cell_input_length := cell_eval_input_length + cell_in := cell_eval_in + interpret() + WHEN CORE_EXECUTE DO + LET xt: Number := stack.pop() + IF xt > 0 THEN + run_compiled(xt) + ELSE + run_instruction(xt, INOUT ip) + END IF + WHEN CORE_FALSE DO + stack.push(0) + WHEN CORE_FILL DO + LET char: Number := stack.pop() + VAR u: Number := stack.pop() + VAR addr: Number := stack.pop() + WHILE u > 0 DO + storage[addr] := char + INC addr + DEC u + END WHILE + WHEN CORE_FIND DO + LET addr: Number := stack.pop() + LET s: String := get_string(addr) + IF s IN words THEN + stack.push(words[s].xt) + stack.push(IF words[s].immediate THEN 1 ELSE -1) + ELSE + stack.push(addr) + stack.push(0) + END IF + WHEN CORE_FM_MOD DO + LET n1: Number := signed32(stack.pop()) + LET d1h: Number := signed32(stack.pop()) + LET d1l: Number := stack.pop() + LET d1: Number := d1h * 0x100000000 + d1l + stack.push(d1 MOD n1) + stack.push(math.floor(d1 / n1)) + WHEN CORE_HERE DO + stack.push(storage.size()) + WHEN CORE_HEX DO + storage[cell_base] := 0x10 + WHEN CORE_HOLD DO + LET char: Number := stack.pop() + INC number_buffer_size + storage[cell_number_buffer + NUMBER_BUFFER_MAX - number_buffer_size] := char + WHEN CORE_I DO + stack.push(return[LAST].index) + WHEN CORE_IMMEDIATE DO + words[defining].immediate := TRUE + WHEN CORE_INVERT DO + LET x1: Number := stack.pop() + stack.push(bitwise.not32(x1)) + WHEN CORE_J DO + stack.push(return[LAST-1].index) + WHEN CORE_LSHIFT DO + LET u: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(bitwise.shiftLeft32(x1, u)) + WHEN CORE_M_MUL DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + LET p: Number := n1 * n2 + stack.push(p) + stack.push(math.floor(p / 0x100000000)) + WHEN CORE_MAX DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(max(n1, n2)) + WHEN CORE_MIN DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(min(n1, n2)) + WHEN CORE_MOD DO + LET n2: Number := signed32(stack.pop()) + LET n1: Number := signed32(stack.pop()) + stack.push(n1 MOD n2) + WHEN CORE_MOVE DO + VAR u: Number := stack.pop() + VAR addr2: Number := stack.pop() + VAR addr1: Number := stack.pop() + IF addr2 < addr1 THEN + WHILE u > 0 DO + storage[addr2] := storage[addr1] + INC addr1 + INC addr2 + DEC u + END WHILE + ELSIF addr1 < addr2 THEN + addr1 := addr1 + u + addr2 := addr2 + u + WHILE u > 0 DO + DEC addr1 + DEC addr2 + storage[addr2] := storage[addr1] + DEC u + END WHILE + END IF + WHEN CORE_NEGATE DO + LET n1: Number := signed32(stack.pop()) + stack.push(-n1) + WHEN CORE_NOT DO + LET n: Number := stack.pop() + stack.push(IF n # 0 THEN 0 ELSE -1) + WHEN CORE_OR DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(bitwise.or32(x1, x2)) + WHEN CORE_OVER DO + stack.push(stack.peek(1)) + WHEN CORE_PAD DO + stack.push(cell_pad) + WHEN CORE_R_FROM DO + stack.push(return[LAST].ip) + return.resize(return.size()-1) + WHEN CORE_R_FETCH DO + stack.push(return[LAST].ip) + WHEN CORE_ROT DO + LET x3: Number := stack.pop() + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x2) + stack.push(x3) + stack.push(x1) + WHEN CORE_RSHIFT DO + LET u: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(bitwise.shiftRight32(x1, u)) + WHEN CORE_S_TO_D DO + LET n: Number := signed32(stack.pop()) + stack.push(n) + stack.push(IF n < 0 THEN -1 ELSE 0) + WHEN CORE_SIGN DO + LET n: Number := signed32(stack.pop()) + IF n < 0 THEN + INC number_buffer_size + storage[cell_number_buffer + NUMBER_BUFFER_MAX - number_buffer_size] := ord("-") + END IF + WHEN CORE_SM_REM DO + LET n1: Number := signed32(stack.pop()) + LET d1h: Number := signed32(stack.pop()) + LET d1l: Number := stack.pop() + LET d1: Number := d1h * 0x100000000 + d1l + stack.push(d1 MOD n1) + stack.push(math.floor(d1 / n1)) + WHEN CORE_SOURCE DO + stack.push(cell_input) + stack.push(storage[cell_input_length]) + WHEN CORE_SPACE DO + io.write(io.stdout, " ") + WHEN CORE_SPACES DO + VAR n: Number := signed32(stack.pop()) + WHILE n > 0 DO + io.write(io.stdout, " ") + DEC n + END WHILE + WHEN CORE_STATE DO + stack.push(cell_state) + WHEN CORE_SWAP DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x2) + stack.push(x1) + WHEN CORE_TRUE DO + stack.push(-1) + WHEN CORE_TUCK DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(x2) + stack.push(x1) + stack.push(x2) + WHEN CORE_TYPE DO + LET u: Number := stack.pop() + LET addr: Number := stack.pop() + LET s: String := get_string_len(addr, u) + io.write(io.stdout, s) + WHEN CORE_U_PRINT DO + LET n: Number := stack.pop() + io.write(io.stdout, "\(n) ") + WHEN CORE_U_LESS DO + LET u2: Number := stack.pop() + LET u1: Number := stack.pop() + stack.push(IF u1 < u2 THEN -1 ELSE 0) + WHEN CORE_UM_MOD DO + LET u1: Number := stack.pop() + LET udh: Number := stack.pop() + LET udl: Number := stack.pop() + LET ud: Number := udh * 0x100000000 + udl + stack.push(ud MOD u1) + stack.push(math.floor(ud / u1)) + WHEN CORE_UM_MUL DO + LET u2: Number := stack.pop() + LET u1: Number := stack.pop() + LET p: Number := u1 * u2 + stack.push(p) + stack.push(math.floor(p / 0x100000000)) + WHEN CORE_VARIABLE DO + LET s: String := next_word() + words[s] := Word(code.size()) + code.append(INT_LITERAL) + code.append(storage.size()) + code.append(0) + storage.append(0) + WHEN CORE_WORD DO + LET char: Number := stack.pop() + LET s: String := parse(chr(char)) + % TODO: use a real scratch area that is discarded + stack.push(storage.size()) + storage.append(s.length()) + FOR i := 0 TO s.length()-1 DO + storage.append(ord(s[i])) + END FOR + storage.append(ord(" ")) + WHEN CORE_XOR DO + LET x2: Number := stack.pop() + LET x1: Number := stack.pop() + stack.push(bitwise.xor32(x1, x2)) + WHEN TOOLS_QUESTION DO + LET addr: Number := stack.pop() + print("\(storage[addr])") + WHEN TOOLS_PRINT_S DO + FOR i := 0 TO stack.size()-1 DO + print("\(stack.peek(i))") + END FOR + WHEN TOOLS_DUMP DO + LET u: Number := stack.pop() + LET addr: Number := stack.pop() + FOR i := 0 TO u-1 DO + print("\(addr+i): \(storage[addr+i])") + END FOR + WHEN TOOLS_SEE DO + LET s: String := next_word() + IF s IN words THEN + IF words[s].immediate THEN + print("(immediate)") + END IF + VAR a: Number := words[s].xt + IF a > 0 THEN + LOOP + print("\(a): \(code[a])") + IF code[a] = 0 THEN + EXIT LOOP + END IF + INC a + END LOOP + ELSE + print("built-in") + END IF + ELSE + print("word not found") + END IF + WHEN TOOLS_WORDS DO + LET a: Array := words.keys() + FOREACH w OF a DO + print(w) + END FOREACH + WHEN TOOLS_EXT_BYE DO + sys.exit(0) + WHEN INT_BRANCH DO + ip := code[ip+1] + WHEN INT_DO DO + LET u2: Number := stack.pop() + LET u1: Number := stack.pop() + return.append(ReturnEntry(ip+2, u1, u2, code[ip+1])) + ip := ip + 2 + WHEN INT_DOES DO + ASSERT code[LAST] = 0 + code[code.size()-3] := INT_BRANCH + code[code.size()-2] := code[ip+1] + ip := ip + 2 + WHEN INT_EXIT DO + ip := return[LAST].ip + return.resize(return.size()-1) + WHEN INT_LEAVE DO + ip := return[LAST].leave + return.resize(return.size()-1) + WHEN INT_LITERAL DO + stack.push(code[ip+1]) + ip := ip + 2 + WHEN INT_LOOP DO + LET last: Number := return.size() - 1 + return[last].index := (return[last].index + 1) MOD 0x100000000 + IF return[last].index = return[last].limit THEN + return.resize(return.size()-1) + ELSE + ip := return[last].ip + END IF + WHEN INT_PLUS_LOOP DO + LET n: Number := signed32(stack.pop()) + LET last: Number := return.size() - 1 + LET prev: Number := return[last].index + LET limit: Number := return[last].limit + return[last].index := (return[last].index + n) MOD 0x100000000 + % print("prev=\(prev) new=\(return[last].index) limit=\(limit) n=\(n)") + IF (n > 0 AND (IF limit # 0 THEN prev < limit AND (return[last].index >= limit OR return[last].index < prev) + ELSE return[last].index < prev)) + OR (n < 0 AND (IF limit # 0 THEN prev >= limit AND (return[last].index < limit OR return[last].index > prev) + ELSE return[last].index > prev)) THEN + return.resize(return.size()-1) + ELSE + ip := return[last].ip + END IF + WHEN INT_POSTPONE DO + run(code[ip+1], FALSE) + ip := ip + 2 + WHEN INT_POSTPONE_IMM DO + run(code[ip+1], TRUE) + ip := ip + 2 + WHEN INT_PRINT_QUOTE DO + io.write(io.stdout, get_string(code[ip+1])) + ip := ip + 2 + WHEN INT_Q_BRANCH DO + LET x: Number := stack.pop() + IF x = 0 THEN + ip := code[ip+1] + ELSE + ip := ip + 2 + END IF + WHEN INT_S_QUOTE DO + stack.push(code[ip+1]+1) + stack.push(storage[code[ip+1]]) + ip := ip + 2 + WHEN INT_UNLOOP DO + return.resize(return.size()-1) + WHEN OTHERS DO + print("Unknown instruction: \(instr)") + sys.exit(1) + END CASE +END FUNCTION + +FUNCTION run_compiled(start_ip: Number) + VAR ip: Number := start_ip + return.append(ReturnEntry(-1)) + WHILE ip > 0 DO + % print("ip=\(ip) instr=\(code[ip]) size=\(stack.stack.size()) ret=\(return.size()))") + LET prev_ip: Number := ip + run_instruction(code[ip], INOUT ip) + IF ip = prev_ip THEN + INC ip + END IF + END WHILE +END FUNCTION + +FUNCTION run(instr: Number, interpreting: Boolean) + CASE instr + WHEN CORE_TICK DO + IF interpreting THEN + LET s: String := next_word() + stack.push(words[s].xt) + ELSE + LET s: String := next_word() + code.append(INT_LITERAL) + code.append(words[s].xt) + END IF + WHEN CORE_COMMENT DO + LET comment: String := parse(")") + WHEN CORE_PLUS_LOOP DO + ASSERT NOT interpreting + code.append(INT_PLUS_LOOP) + code[compilation.pop()] := code.size() + WHEN CORE_PRINT_QUOTE DO + LET s: String := parse("\"") + IF interpreting THEN + io.write(io.stdout, s) + ELSE + code.append(INT_PRINT_QUOTE) + code.append(storage.size()) + storage.append(s.length()) + FOR i := 0 TO s.length()-1 DO + storage.append(ord(s[i])) + END FOR + END IF + WHEN CORE_SEMICOLON DO + ASSERT NOT interpreting + code.append(0) + words[defining] := Word(define_start) + storage[cell_state] := 0 + WHEN CORE_BACKSLASH DO + storage[cell_in] := storage[cell_input_length] + WHEN CORE_BEGIN DO + ASSERT NOT interpreting + compilation.push(code.size()) + WHEN CORE_DO DO + ASSERT NOT interpreting + code.append(INT_DO) + compilation.push(code.size()) + code.append(0) + WHEN CORE_DOES DO + ASSERT NOT interpreting + code.append(INT_DOES) + code.append(code.size()+2) + code.append(0) + WHEN CORE_ELSE DO + ASSERT NOT interpreting + code.append(INT_BRANCH) + LET dest: Number := code.size() + code.append(0) + code[compilation.pop()] := code.size() + compilation.push(dest) + WHEN CORE_EXIT DO + ASSERT NOT interpreting + code.append(INT_EXIT) + WHEN CORE_IF DO + ASSERT NOT interpreting + code.append(INT_Q_BRANCH) + compilation.push(code.size()) + code.append(0) + WHEN CORE_LEAVE DO + ASSERT NOT interpreting + code.append(INT_LEAVE) + WHEN CORE_LITERAL DO + ASSERT NOT interpreting + LET x: Number := stack.pop() + code.append(INT_LITERAL) + code.append(x) + WHEN CORE_LOOP DO + ASSERT NOT interpreting + code.append(INT_LOOP) + code[compilation.pop()] := code.size() + WHEN CORE_POSTPONE DO + ASSERT NOT interpreting + LET s: String := next_word() + code.append(IF words[s].immediate THEN INT_POSTPONE_IMM ELSE INT_POSTPONE) + code.append(words[s].xt) + WHEN CORE_RECURSE DO + ASSERT NOT interpreting + code.append(define_start) + WHEN CORE_REPEAT DO + ASSERT NOT interpreting + LET dest: Number := compilation.pop() + LET orig: Number := compilation.pop() + code.append(INT_BRANCH) + code.append(dest) + code[orig] := code.size() + WHEN CORE_S_QUOTE DO + ASSERT NOT interpreting + LET s: String := parse("\"") + code.append(INT_S_QUOTE) + code.append(storage.size()) + storage.append(s.length()) + FOR i := 0 TO s.length()-1 DO + storage.append(ord(s[i])) + END FOR + WHEN CORE_THEN DO + ASSERT NOT interpreting + code[compilation.pop()] := code.size() + WHEN CORE_UNLOOP DO + ASSERT NOT interpreting + code.append(INT_UNLOOP) + WHEN CORE_UNTIL DO + ASSERT NOT interpreting + code.append(INT_Q_BRANCH) + code.append(compilation.pop()) + WHEN CORE_WHILE DO + ASSERT NOT interpreting + LET dest: Number := compilation.pop() + code.append(INT_Q_BRANCH) + compilation.push(code.size()) + code.append(0) + compilation.push(dest) + WHEN CORE_INTERPRET DO + storage[cell_state] := 0 + WHEN CORE_IMM_TICK DO + ASSERT NOT interpreting + LET s: String := next_word() + code.append(INT_LITERAL) + code.append(words[s].xt) + WHEN CORE_IMM_CHAR DO + ASSERT NOT interpreting + LET s: String := next_word() + code.append(INT_LITERAL) + code.append(ord(s[FIRST])) + WHEN CORE_COMPILE DO + storage[cell_state] := -1 + WHEN TOOLS_EXT_ELSE DO + VAR depth: Number := 0 + LOOP + LET s: String := next_word() + IF s = "[IF]" THEN + INC depth + ELSIF s = "[THEN]" THEN + IF depth = 0 THEN + EXIT LOOP + END IF + DEC depth + END IF + END LOOP + WHEN TOOLS_EXT_IF DO + LET flag: Number := stack.pop() + IF flag = 0 THEN + VAR depth: Number := 0 + LOOP + LET s: String := next_word() + IF s = "[IF]" THEN + INC depth + ELSIF s = "[ELSE]" THEN + IF depth = 0 THEN + EXIT LOOP + END IF + ELSIF s = "[THEN]" THEN + IF depth = 0 THEN + EXIT LOOP + END IF + DEC depth + END IF + END LOOP + END IF + WHEN TOOLS_EXT_THEN DO + % nothing + WHEN OTHERS DO + IF interpreting THEN + IF instr > 0 THEN + run_compiled(instr) + ELSE + VAR ip: Number := 0 + run_instruction(instr, INOUT ip) + END IF + ELSE + code.append(instr) + END IF + END CASE +END FUNCTION + +FUNCTION skip() + WHILE storage[cell_in] < storage[cell_input_length] AND chr(storage[cell_input+storage[cell_in]]) <= " " DO + INC storage[cell_in] + END WHILE +END FUNCTION + +FUNCTION digit(char: String): Number + IF "0" <= char <= "9" THEN + RETURN ord(char) - ord("0") + ELSIF "A" <= char <= "Z" THEN + RETURN ord(char) - ord("A") + 10 + ELSE + ASSERT FALSE % invalid char + END IF + RETURN 0 +END FUNCTION + +FUNCTION convert(word: String, base: Number): Number + VAR n: Number := 0 + LET negative: Boolean := word[0] = "-" + VAR i: Number := 0 + IF negative THEN + INC i + END IF + WHILE i < word.length() DO + n := n * base + digit(word[i]) + INC i + END WHILE + IF negative THEN + n := -n + END IF + RETURN n +END FUNCTION + +FUNCTION execute(word: String) + LET interpreting: Boolean := storage[cell_state] = 0 + IF word IN words THEN + run(words[word].xt, interpreting OR words[word].immediate) + ELSE + VAR n: Number + VAR m: regex.Match + LET base: Number := storage[cell_base] + IF regex.search("^-?[\(Digits[0 TO base-1])]+$", word, OUT m) THEN + n := convert(word, base) + ELSIF regex.search(@"^#-?[0-9]+$", word, OUT m) THEN + n := convert(word[1 TO LAST], 10) + ELSIF regex.search(@"^\$-?[0-9A-F]+$", word, OUT m) THEN + n := convert(word[1 TO LAST], 0x10) + ELSIF regex.search(@"^%-?[01]+$", word, OUT m) THEN + n := convert(word[1 TO LAST], 0b10) + ELSIF regex.search(@"'.'?$", word, OUT m) THEN + n := ord(word[1]) + ELSE + print("Unknown word: \(word)") + sys.exit(1) + END IF + IF interpreting THEN + stack.push(n) + ELSE + code.append(INT_LITERAL) + code.append(n) + END IF + END IF +END FUNCTION + +VAR current_file: io.File + +FUNCTION refill(): Boolean + IF cell_input # cell_normal_input THEN + cell_input := cell_normal_input + cell_input_length := cell_normal_input_length + cell_in := cell_normal_in + RETURN FALSE + END IF + VAR s: String + IF NOT io.readLine(current_file, OUT s) THEN + RETURN FALSE + END IF + FOR i := 0 TO s.length()-1 DO + storage[cell_input+i] := ord(s[i]) + END FOR + storage[cell_input_length] := s.length() + storage[cell_in] := 0 + RETURN TRUE +END FUNCTION + +FUNCTION parse(delimiter: String): String + VAR r: String := "" + WHILE storage[cell_in] < storage[cell_input_length] DO + LET c: String := chr(storage[cell_input+storage[cell_in]]) + INC storage[cell_in] + IF c = delimiter OR (delimiter = " " AND c <= delimiter) THEN + EXIT WHILE + END IF + r.append(c) + END WHILE + RETURN r +END FUNCTION + +FUNCTION next_word(): String + LOOP + skip() + IF storage[cell_in] >= storage[cell_input_length] THEN + IF NOT refill() THEN + RETURN "" + END IF + NEXT LOOP + END IF + RETURN string.upper(parse(" ")) + END LOOP +END FUNCTION + +FUNCTION interpret() + LOOP + LET word: String := next_word() + IF word = "" THEN + EXIT LOOP + END IF + execute(word) + END LOOP +END FUNCTION + +LET files: Array := sys.args[1 TO LAST] +FOREACH fn OF files DO + current_file := io.open(fn, io.Mode.read) + interpret() + io.close(INOUT current_file) +END FOREACH + +current_file := io.stdin +interpret() diff --git a/samples/forth/tests.forth b/samples/forth/tests.forth new file mode 100644 index 0000000000..d1dc553b5b --- /dev/null +++ b/samples/forth/tests.forth @@ -0,0 +1,1117 @@ +\ Forth 2012 core test suite: +\ http://lars.nocrew.org/forth2012/testsuite.html + +HEX + +T{ -> }T ( Start with a clean slate ) +( Test if any bits are set; Answer in base 1 ) +T{ : BITSSET? IF 0 0 ELSE 0 THEN ; -> }T +T{ 0 BITSSET? -> 0 }T ( Zero is all bits clear ) +\ T{ 1 BITSSET? -> 0 0 }T ( Other numbers have at least one bit ) +\ T{ -1 BITSSET? -> 0 0 }T + +\ F.6.1.0720 AND +T{ 0 0 AND -> 0 }T +T{ 0 1 AND -> 0 }T +T{ 1 0 AND -> 0 }T +T{ 1 1 AND -> 1 }T +T{ 0 INVERT 1 AND -> 1 }T +T{ 1 INVERT 1 AND -> 0 }T + +\ Define 0S and 1S +0 CONSTANT 0S +0S INVERT CONSTANT 1S + +\ F.6.1.1720 INVERT +T{ 0S INVERT -> 1S }T +T{ 1S INVERT -> 0S }T + +\ F.6.1.0950 CONSTANT +T{ 123 CONSTANT X123 -> }T +T{ X123 -> 123 }T +T{ : EQU CONSTANT ; -> }T +T{ X123 EQU Y123 -> }T +T{ Y123 -> 123 }T + +\ F.6.1.0720 AND (continued) +T{ 0S 0S AND -> 0S }T +T{ 0S 1S AND -> 0S }T +T{ 1S 0S AND -> 0S }T +T{ 1S 1S AND -> 1S }T + +\ F.6.1.1980 OR +T{ 0S 0S OR -> 0S }T +T{ 0S 1S OR -> 1S }T +T{ 1S 0S OR -> 1S }T +T{ 1S 1S OR -> 1S }T + +\ F.6.1.2490 XOR +T{ 0S 0S XOR -> 0S }T +T{ 0S 1S XOR -> 1S }T +T{ 1S 0S XOR -> 1S }T +T{ 1S 1S XOR -> 0S }T + +1S 1 RSHIFT INVERT CONSTANT MSB +T{ MSB BITSSET? -> 0 0 }T + +\ F.6.1.0320 2* +T{ 0S 2* -> 0S }T +T{ 1 2* -> 2 }T +T{ 4000 2* -> 8000 }T +T{ 1S 2* 1 XOR -> 1S }T +T{ MSB 2* -> 0S }T + +\ F.6.1.0330 2/ +T{ 0S 2/ -> 0S }T +T{ 1 2/ -> 0 }T +T{ 4000 2/ -> 2000 }T +T{ 1S 2/ -> 1S }T \ MSB PROPOGATED +T{ 1S 1 XOR 2/ -> 1S }T +T{ MSB 2/ MSB AND -> MSB }T + +\ F.6.1.1805 LSHIFT +T{ 1 0 LSHIFT -> 1 }T +T{ 1 1 LSHIFT -> 2 }T +T{ 1 2 LSHIFT -> 4 }T +T{ 1 F LSHIFT -> 8000 }T \ BIGGEST GUARANTEED SHIFT +T{ 1S 1 LSHIFT 1 XOR -> 1S }T +T{ MSB 1 LSHIFT -> 0 }T + +\ F.6.1.2162 RSHIFT +T{ 1 0 RSHIFT -> 1 }T +T{ 1 1 RSHIFT -> 0 }T +T{ 2 1 RSHIFT -> 1 }T +T{ 4 2 RSHIFT -> 1 }T +T{ 8000 F RSHIFT -> 1 }T \ Biggest +T{ MSB 1 RSHIFT MSB AND -> 0 }T \ RSHIFT zero fills MSBs +T{ MSB 1 RSHIFT 2* -> MSB }T + +DECIMAL +T{ #1289 -> 1289 }T +\ SKIP: T{ #12346789. -> 12346789. }T +T{ #-1289 -> -1289 }T +\ SKIP: T{ #-12346789. -> -12346789. }T +T{ $12eF -> 4847 }T +\ SKIP: T{ $12aBcDeF. -> 313249263. }T +T{ $-12eF -> -4847 }T +\ SKIP: T{ $-12AbCdEf. -> -313249263. }T +T{ %10010110 -> 150 }T +\ SKIP: T{ %10010110. -> 150. }T +T{ %-10010110 -> -150 }T +\ SKIP: T{ %-10010110. -> -150. }T +\ TODO: T{ 'z' -> 122 }T +HEX + +0 INVERT CONSTANT MAX-UINT +0 INVERT 1 RSHIFT CONSTANT MAX-INT +0 INVERT 1 RSHIFT INVERT CONSTANT MIN-INT +0 INVERT 1 RSHIFT CONSTANT MID-UINT +0 INVERT 1 RSHIFT INVERT CONSTANT MID-UINT+1 + +0S CONSTANT +1S CONSTANT + +\ F.6.1.0270 0= +T{ 0 0= -> }T +T{ 1 0= -> }T +T{ 2 0= -> }T +T{ -1 0= -> }T +T{ MAX-UINT 0= -> }T +T{ MIN-INT 0= -> }T +T{ MAX-INT 0= -> }T + +\ F.6.1.0530 = +T{ 0 0 = -> }T +T{ 1 1 = -> }T +T{ -1 -1 = -> }T +T{ 1 0 = -> }T +T{ -1 0 = -> }T +T{ 0 1 = -> }T +T{ 0 -1 = -> }T + +\ F.6.1.0250 0< +T{ 0 0< -> }T +T{ -1 0< -> }T +T{ MIN-INT 0< -> }T +T{ 1 0< -> }T +T{ MAX-INT 0< -> }T + +\ F.6.1.0480 < +T{ 0 1 < -> }T +T{ 1 2 < -> }T +T{ -1 0 < -> }T +T{ -1 1 < -> }T +T{ MIN-INT 0 < -> }T +T{ MIN-INT MAX-INT < -> }T +T{ 0 MAX-INT < -> }T +T{ 0 0 < -> }T +T{ 1 1 < -> }T +T{ 1 0 < -> }T +T{ 2 1 < -> }T +T{ 0 -1 < -> }T +T{ 1 -1 < -> }T +T{ 0 MIN-INT < -> }T +T{ MAX-INT MIN-INT < -> }T +T{ MAX-INT 0 < -> }T + +\ F.6.1.0540 > +T{ 0 1 > -> }T +T{ 1 2 > -> }T +T{ -1 0 > -> }T +T{ -1 1 > -> }T +T{ MIN-INT 0 > -> }T +T{ MIN-INT MAX-INT > -> }T +T{ 0 MAX-INT > -> }T +T{ 0 0 > -> }T +T{ 1 1 > -> }T +T{ 1 0 > -> }T +T{ 2 1 > -> }T +T{ 0 -1 > -> }T +T{ 1 -1 > -> }T +T{ 0 MIN-INT > -> }T +T{ MAX-INT MIN-INT > -> }T +T{ MAX-INT 0 > -> }T + +\ F.6.1.2340 U< +T{ 0 1 U< -> }T +T{ 1 2 U< -> }T +T{ 0 MID-UINT U< -> }T +T{ 0 MAX-UINT U< -> }T +T{ MID-UINT MAX-UINT U< -> }T +T{ 0 0 U< -> }T +T{ 1 1 U< -> }T +T{ 1 0 U< -> }T +T{ 2 1 U< -> }T +T{ MID-UINT 0 U< -> }T +T{ MAX-UINT 0 U< -> }T +T{ MAX-UINT MID-UINT U< -> }T + +\ F.6.1.1880 MIN +T{ 0 1 MIN -> 0 }T +T{ 1 2 MIN -> 1 }T +T{ -1 0 MIN -> -1 }T +T{ -1 1 MIN -> -1 }T +T{ MIN-INT 0 MIN -> MIN-INT }T +T{ MIN-INT MAX-INT MIN -> MIN-INT }T +T{ 0 MAX-INT MIN -> 0 }T +T{ 0 0 MIN -> 0 }T +T{ 1 1 MIN -> 1 }T +T{ 1 0 MIN -> 0 }T +T{ 2 1 MIN -> 1 }T +T{ 0 -1 MIN -> -1 }T +T{ 1 -1 MIN -> -1 }T +T{ 0 MIN-INT MIN -> MIN-INT }T +T{ MAX-INT MIN-INT MIN -> MIN-INT }T +T{ MAX-INT 0 MIN -> 0 }T + +\ F.6.1.1870 MAX +T{ 0 1 MAX -> 1 }T +T{ 1 2 MAX -> 2 }T +T{ -1 0 MAX -> 0 }T +T{ -1 1 MAX -> 1 }T +T{ MIN-INT 0 MAX -> 0 }T +T{ MIN-INT MAX-INT MAX -> MAX-INT }T +T{ 0 MAX-INT MAX -> MAX-INT }T +T{ 0 0 MAX -> 0 }T +T{ 1 1 MAX -> 1 }T +T{ 1 0 MAX -> 1 }T +T{ 2 1 MAX -> 2 }T +T{ 0 -1 MAX -> 0 }T +T{ 1 -1 MAX -> 1 }T +T{ 0 MIN-INT MAX -> 0 }T +T{ MAX-INT MIN-INT MAX -> MAX-INT }T +T{ MAX-INT 0 MAX -> MAX-INT }T + +\ F.6.1.1260 DROP +T{ 1 2 DROP -> 1 }T +T{ 0 DROP -> }T + +\ F.6.1.1290 DUP +T{ 1 DUP -> 1 1 }T + +\ F.6.1.1990 OVER +T{ 1 2 OVER -> 1 2 1 }T + +\ F.6.1.2160 ROT +T{ 1 2 3 ROT -> 2 3 1 }T + +\ F.6.1.2260 SWAP +T{ 1 2 SWAP -> 2 1 }T + +\ F.6.1.0370 2DROP +T{ 1 2 2DROP -> }T + +\ F.6.1.0380 2DUP +T{ 1 2 2DUP -> 1 2 1 2 }T + +\ F.6.1.0400 2OVER +T{ 1 2 3 4 2OVER -> 1 2 3 4 1 2 }T + +\ F.6.1.0430 2SWAP +T{ 1 2 3 4 2SWAP -> 3 4 1 2 }T + +\ F.6.1.0630 ?DUP +T{ -1 ?DUP -> -1 -1 }T +T{ 0 ?DUP -> 0 }T +T{ 1 ?DUP -> 1 1 }T + +\ F.6.1.1200 DEPTH +T{ 0 1 DEPTH -> 0 1 2 }T +T{ 0 DEPTH -> 0 1 }T +T{ DEPTH -> 0 }T + +\ F.6.1.0580 >R +T{ : GR1 >R R> ; -> }T +T{ : GR2 >R R@ R> DROP ; -> }T +T{ 123 GR1 -> 123 }T +T{ 123 GR2 -> 123 }T +T{ 1S GR1 -> 1S }T ( Return stack holds cells ) + +\ F.6.1.0120 + +T{ 0 5 + -> 5 }T +T{ 5 0 + -> 5 }T +T{ 0 -5 + -> -5 }T +T{ -5 0 + -> -5 }T +T{ 1 2 + -> 3 }T +T{ 1 -2 + -> -1 }T +T{ -1 2 + -> 1 }T +T{ -1 -2 + -> -3 }T +T{ -1 1 + -> 0 }T +T{ MID-UINT 1 + -> MID-UINT+1 }T + +\ F.6.1.0160 - +T{ 0 5 - -> -5 }T +T{ 5 0 - -> 5 }T +T{ 0 -5 - -> 5 }T +T{ -5 0 - -> -5 }T +T{ 1 2 - -> -1 }T +T{ 1 -2 - -> 3 }T +T{ -1 2 - -> -3 }T +T{ -1 -2 - -> 1 }T +T{ 0 1 - -> -1 }T +T{ MID-UINT+1 1 - -> MID-UINT }T + +\ F.6.1.0290 1+ +T{ 0 1+ -> 1 }T +T{ -1 1+ -> 0 }T +T{ 1 1+ -> 2 }T +T{ MID-UINT 1+ -> MID-UINT+1 }T + +\ F.6.1.0300 1- +T{ 2 1- -> 1 }T +T{ 1 1- -> 0 }T +T{ 0 1- -> -1 }T +T{ MID-UINT+1 1- -> MID-UINT }T + +\ F.6.1.0690 ABS +T{ 0 ABS -> 0 }T +T{ 1 ABS -> 1 }T +T{ -1 ABS -> 1 }T +T{ MIN-INT ABS -> MID-UINT+1 }T + +\ F.6.1.1910 NEGATE +T{ 0 NEGATE -> 0 }T +T{ 1 NEGATE -> -1 }T +T{ -1 NEGATE -> 1 }T +T{ 2 NEGATE -> -2 }T +T{ -2 NEGATE -> 2 }T + +\ F.6.1.2170 S>D +T{ 0 S>D -> 0 0 }T +T{ 1 S>D -> 1 0 }T +T{ 2 S>D -> 2 0 }T +T{ -1 S>D -> -1 -1 }T +T{ -2 S>D -> -2 -1 }T +T{ MIN-INT S>D -> MIN-INT -1 }T +T{ MAX-INT S>D -> MAX-INT 0 }T + +\ F.6.1.0090 * +T{ 0 0 * -> 0 }T \ TEST IDENTITIE\S +T{ 0 1 * -> 0 }T +T{ 1 0 * -> 0 }T +T{ 1 2 * -> 2 }T +T{ 2 1 * -> 2 }T +T{ 3 3 * -> 9 }T +T{ -3 3 * -> -9 }T +T{ 3 -3 * -> -9 }T +T{ -3 -3 * -> 9 }T +T{ MID-UINT+1 1 RSHIFT 2 * -> MID-UINT+1 }T +T{ MID-UINT+1 2 RSHIFT 4 * -> MID-UINT+1 }T +T{ MID-UINT+1 1 RSHIFT MID-UINT+1 OR 2 * -> MID-UINT+1 }T + +\ F.6.1.1810 M* +T{ 0 0 M* -> 0 S>D }T +T{ 0 1 M* -> 0 S>D }T +T{ 1 0 M* -> 0 S>D }T +T{ 1 2 M* -> 2 S>D }T +T{ 2 1 M* -> 2 S>D }T +T{ 3 3 M* -> 9 S>D }T +T{ -3 3 M* -> -9 S>D }T +T{ 3 -3 M* -> -9 S>D }T +T{ -3 -3 M* -> 9 S>D }T +T{ 0 MIN-INT M* -> 0 S>D }T +T{ 1 MIN-INT M* -> MIN-INT S>D }T +T{ 2 MIN-INT M* -> 0 1S }T +T{ 0 MAX-INT M* -> 0 S>D }T +T{ 1 MAX-INT M* -> MAX-INT S>D }T +T{ 2 MAX-INT M* -> MAX-INT 1 LSHIFT 0 }T +T{ MIN-INT MIN-INT M* -> 0 MSB 1 RSHIFT }T +T{ MAX-INT MIN-INT M* -> MSB MSB 2/ }T +T{ MAX-INT MAX-INT M* -> 1 MSB 2/ INVERT }T + +\ F.6.1.2360 UM* +T{ 0 0 UM* -> 0 0 }T +T{ 0 1 UM* -> 0 0 }T +T{ 1 0 UM* -> 0 0 }T +T{ 1 2 UM* -> 2 0 }T +T{ 2 1 UM* -> 2 0 }T +T{ 3 3 UM* -> 9 0 }T +T{ MID-UINT+1 1 RSHIFT 2 UM* -> MID-UINT+1 0 }T +T{ MID-UINT+1 2 UM* -> 0 1 }T +T{ MID-UINT+1 4 UM* -> 0 2 }T +T{ 1S 2 UM* -> 1S 1 LSHIFT 1 }T +T{ MAX-UINT MAX-UINT UM* -> 1 1 INVERT }T + +\ F.6.1.1561 FM/MOD +T{ 0 S>D 1 FM/MOD -> 0 0 }T +T{ 1 S>D 1 FM/MOD -> 0 1 }T +T{ 2 S>D 1 FM/MOD -> 0 2 }T +T{ -1 S>D 1 FM/MOD -> 0 -1 }T +T{ -2 S>D 1 FM/MOD -> 0 -2 }T +T{ 0 S>D -1 FM/MOD -> 0 0 }T +T{ 1 S>D -1 FM/MOD -> 0 -1 }T +T{ 2 S>D -1 FM/MOD -> 0 -2 }T +T{ -1 S>D -1 FM/MOD -> 0 1 }T +T{ -2 S>D -1 FM/MOD -> 0 2 }T +T{ 2 S>D 2 FM/MOD -> 0 1 }T +T{ -1 S>D -1 FM/MOD -> 0 1 }T +T{ -2 S>D -2 FM/MOD -> 0 1 }T +T{ 7 S>D 3 FM/MOD -> 1 2 }T +T{ 7 S>D -3 FM/MOD -> -2 -3 }T +T{ -7 S>D 3 FM/MOD -> 2 -3 }T +T{ -7 S>D -3 FM/MOD -> -1 2 }T +T{ MAX-INT S>D 1 FM/MOD -> 0 MAX-INT }T +T{ MIN-INT S>D 1 FM/MOD -> 0 MIN-INT }T +T{ MAX-INT S>D MAX-INT FM/MOD -> 0 1 }T +T{ MIN-INT S>D MIN-INT FM/MOD -> 0 1 }T +T{ 1S 1 4 FM/MOD -> 3 MAX-INT }T +T{ 1 MIN-INT M* 1 FM/MOD -> 0 MIN-INT }T +T{ 1 MIN-INT M* MIN-INT FM/MOD -> 0 1 }T +T{ 2 MIN-INT M* 2 FM/MOD -> 0 MIN-INT }T +T{ 2 MIN-INT M* MIN-INT FM/MOD -> 0 2 }T +T{ 1 MAX-INT M* 1 FM/MOD -> 0 MAX-INT }T +T{ 1 MAX-INT M* MAX-INT FM/MOD -> 0 1 }T +T{ 2 MAX-INT M* 2 FM/MOD -> 0 MAX-INT }T +T{ 2 MAX-INT M* MAX-INT FM/MOD -> 0 2 }T +T{ MIN-INT MIN-INT M* MIN-INT FM/MOD -> 0 MIN-INT }T +T{ MIN-INT MAX-INT M* MIN-INT FM/MOD -> 0 MAX-INT }T +T{ MIN-INT MAX-INT M* MAX-INT FM/MOD -> 0 MIN-INT }T +T{ MAX-INT MAX-INT M* MAX-INT FM/MOD -> 0 MAX-INT }T + +\ F.6.1.2214 SM/REM +T{ 0 S>D 1 SM/REM -> 0 0 }T +T{ 1 S>D 1 SM/REM -> 0 1 }T +T{ 2 S>D 1 SM/REM -> 0 2 }T +T{ -1 S>D 1 SM/REM -> 0 -1 }T +T{ -2 S>D 1 SM/REM -> 0 -2 }T +T{ 0 S>D -1 SM/REM -> 0 0 }T +T{ 1 S>D -1 SM/REM -> 0 -1 }T +T{ 2 S>D -1 SM/REM -> 0 -2 }T +T{ -1 S>D -1 SM/REM -> 0 1 }T +T{ -2 S>D -1 SM/REM -> 0 2 }T +T{ 2 S>D 2 SM/REM -> 0 1 }T +T{ -1 S>D -1 SM/REM -> 0 1 }T +T{ -2 S>D -2 SM/REM -> 0 1 }T +T{ 7 S>D 3 SM/REM -> 1 2 }T +\ TODO: T{ 7 S>D -3 SM/REM -> 1 -2 }T +\ TODO: T{ -7 S>D 3 SM/REM -> 1 -2 }T +T{ -7 S>D -3 SM/REM -> -1 2 }T +T{ MAX-INT S>D 1 SM/REM -> 0 MAX-INT }T +T{ MIN-INT S>D 1 SM/REM -> 0 MIN-INT }T +T{ MAX-INT S>D MAX-INT SM/REM -> 0 1 }T +T{ MIN-INT S>D MIN-INT SM/REM -> 0 1 }T +T{ 1S 1 4 SM/REM -> 3 MAX-INT }T +T{ 2 MIN-INT M* 2 SM/REM -> 0 MIN-INT }T +T{ 2 MIN-INT M* MIN-INT SM/REM -> 0 2 }T +T{ 2 MAX-INT M* 2 SM/REM -> 0 MAX-INT }T +T{ 2 MAX-INT M* MAX-INT SM/REM -> 0 2 }T +T{ MIN-INT MIN-INT M* MIN-INT SM/REM -> 0 MIN-INT }T +T{ MIN-INT MAX-INT M* MIN-INT SM/REM -> 0 MAX-INT }T +T{ MIN-INT MAX-INT M* MAX-INT SM/REM -> 0 MIN-INT }T +T{ MAX-INT MAX-INT M* MAX-INT SM/REM -> 0 MAX-INT }T + +\ F.6.1.2370 UM/MOD +T{ 0 0 1 UM/MOD -> 0 0 }T +T{ 1 0 1 UM/MOD -> 0 1 }T +T{ 1 0 2 UM/MOD -> 1 0 }T +T{ 3 0 2 UM/MOD -> 1 1 }T +T{ MAX-UINT 2 UM* 2 UM/MOD -> 0 MAX-UINT }T +T{ MAX-UINT 2 UM* MAX-UINT UM/MOD -> 0 2 }T +T{ MAX-UINT MAX-UINT UM* MAX-UINT UM/MOD -> 0 MAX-UINT }T + +: IFFLOORED [ -3 2 / -2 = INVERT ] LITERAL IF POSTPONE \ THEN ; +: IFSYM [ -3 2 / -1 = INVERT ] LITERAL IF POSTPONE \ THEN ; + +\ F.6.1.0240 /MOD +IFFLOORED : T/MOD >R S>D R> FM/MOD ; +IFSYM : T/MOD >R S>D R> SM/REM ; +T{ 0 1 /MOD -> 0 1 T/MOD }T +T{ 1 1 /MOD -> 1 1 T/MOD }T +T{ 2 1 /MOD -> 2 1 T/MOD }T +T{ -1 1 /MOD -> -1 1 T/MOD }T +T{ -2 1 /MOD -> -2 1 T/MOD }T +T{ 0 -1 /MOD -> 0 -1 T/MOD }T +T{ 1 -1 /MOD -> 1 -1 T/MOD }T +T{ 2 -1 /MOD -> 2 -1 T/MOD }T +T{ -1 -1 /MOD -> -1 -1 T/MOD }T +T{ -2 -1 /MOD -> -2 -1 T/MOD }T +T{ 2 2 /MOD -> 2 2 T/MOD }T +T{ -1 -1 /MOD -> -1 -1 T/MOD }T +T{ -2 -2 /MOD -> -2 -2 T/MOD }T +T{ 7 3 /MOD -> 7 3 T/MOD }T +T{ 7 -3 /MOD -> 7 -3 T/MOD }T +T{ -7 3 /MOD -> -7 3 T/MOD }T +T{ -7 -3 /MOD -> -7 -3 T/MOD }T +T{ MAX-INT 1 /MOD -> MAX-INT 1 T/MOD }T +T{ MIN-INT 1 /MOD -> MIN-INT 1 T/MOD }T +T{ MAX-INT MAX-INT /MOD -> MAX-INT MAX-INT T/MOD }T +T{ MIN-INT MIN-INT /MOD -> MIN-INT MIN-INT T/MOD }T + +\ F.6.1.0230 / +IFFLOORED : T/ T/MOD SWAP DROP ; +IFSYM : T/ T/MOD SWAP DROP ; +T{ 0 1 / -> 0 1 T/ }T +T{ 1 1 / -> 1 1 T/ }T +T{ 2 1 / -> 2 1 T/ }T +T{ -1 1 / -> -1 1 T/ }T +T{ -2 1 / -> -2 1 T/ }T +T{ 0 -1 / -> 0 -1 T/ }T +T{ 1 -1 / -> 1 -1 T/ }T +T{ 2 -1 / -> 2 -1 T/ }T +T{ -1 -1 / -> -1 -1 T/ }T +T{ -2 -1 / -> -2 -1 T/ }T +T{ 2 2 / -> 2 2 T/ }T +T{ -1 -1 / -> -1 -1 T/ }T +T{ -2 -2 / -> -2 -2 T/ }T +T{ 7 3 / -> 7 3 T/ }T +T{ 7 -3 / -> 7 -3 T/ }T +T{ -7 3 / -> -7 3 T/ }T +T{ -7 -3 / -> -7 -3 T/ }T +T{ MAX-INT 1 / -> MAX-INT 1 T/ }T +T{ MIN-INT 1 / -> MIN-INT 1 T/ }T +T{ MAX-INT MAX-INT / -> MAX-INT MAX-INT T/ }T +T{ MIN-INT MIN-INT / -> MIN-INT MIN-INT T/ }T + +\ F.6.1.1890 MOD +IFFLOORED : TMOD T/MOD DROP ; +IFSYM : TMOD T/MOD DROP ; +T{ 0 1 MOD -> 0 1 TMOD }T +T{ 1 1 MOD -> 1 1 TMOD }T +T{ 2 1 MOD -> 2 1 TMOD }T +T{ -1 1 MOD -> -1 1 TMOD }T +T{ -2 1 MOD -> -2 1 TMOD }T +T{ 0 -1 MOD -> 0 -1 TMOD }T +T{ 1 -1 MOD -> 1 -1 TMOD }T +T{ 2 -1 MOD -> 2 -1 TMOD }T +T{ -1 -1 MOD -> -1 -1 TMOD }T +T{ -2 -1 MOD -> -2 -1 TMOD }T +T{ 2 2 MOD -> 2 2 TMOD }T +T{ -1 -1 MOD -> -1 -1 TMOD }T +T{ -2 -2 MOD -> -2 -2 TMOD }T +T{ 7 3 MOD -> 7 3 TMOD }T +T{ 7 -3 MOD -> 7 -3 TMOD }T +T{ -7 3 MOD -> -7 3 TMOD }T +T{ -7 -3 MOD -> -7 -3 TMOD }T +T{ MAX-INT 1 MOD -> MAX-INT 1 TMOD }T +T{ MIN-INT 1 MOD -> MIN-INT 1 TMOD }T +T{ MAX-INT MAX-INT MOD -> MAX-INT MAX-INT TMOD }T +T{ MIN-INT MIN-INT MOD -> MIN-INT MIN-INT TMOD }T + +\ F.6.1.0110 */MOD +IFFLOORED : T*/MOD >R M* R> FM/MOD ; +IFSYM : T*/MOD >R M* R> SM/REM ; +T{ 0 2 1 */MOD -> 0 2 1 T*/MOD }T +T{ 1 2 1 */MOD -> 1 2 1 T*/MOD }T +T{ 2 2 1 */MOD -> 2 2 1 T*/MOD }T +T{ -1 2 1 */MOD -> -1 2 1 T*/MOD }T +T{ -2 2 1 */MOD -> -2 2 1 T*/MOD }T +T{ 0 2 -1 */MOD -> 0 2 -1 T*/MOD }T +T{ 1 2 -1 */MOD -> 1 2 -1 T*/MOD }T +T{ 2 2 -1 */MOD -> 2 2 -1 T*/MOD }T +T{ -1 2 -1 */MOD -> -1 2 -1 T*/MOD }T +T{ -2 2 -1 */MOD -> -2 2 -1 T*/MOD }T +T{ 2 2 2 */MOD -> 2 2 2 T*/MOD }T +T{ -1 2 -1 */MOD -> -1 2 -1 T*/MOD }T +T{ -2 2 -2 */MOD -> -2 2 -2 T*/MOD }T +T{ 7 2 3 */MOD -> 7 2 3 T*/MOD }T +T{ 7 2 -3 */MOD -> 7 2 -3 T*/MOD }T +T{ -7 2 3 */MOD -> -7 2 3 T*/MOD }T +T{ -7 2 -3 */MOD -> -7 2 -3 T*/MOD }T +T{ MAX-INT 2 MAX-INT */MOD -> MAX-INT 2 MAX-INT T*/MOD }T +T{ MIN-INT 2 MIN-INT */MOD -> MIN-INT 2 MIN-INT T*/MOD }T + +\ F.6.1.0100 */ +IFFLOORED : T*/ T*/MOD SWAP DROP ; +IFSYM : T*/ T*/MOD SWAP DROP ; +T{ 0 2 1 */ -> 0 2 1 T*/ }T +T{ 1 2 1 */ -> 1 2 1 T*/ }T +T{ 2 2 1 */ -> 2 2 1 T*/ }T +T{ -1 2 1 */ -> -1 2 1 T*/ }T +T{ -2 2 1 */ -> -2 2 1 T*/ }T +T{ 0 2 -1 */ -> 0 2 -1 T*/ }T +T{ 1 2 -1 */ -> 1 2 -1 T*/ }T +T{ 2 2 -1 */ -> 2 2 -1 T*/ }T +T{ -1 2 -1 */ -> -1 2 -1 T*/ }T +T{ -2 2 -1 */ -> -2 2 -1 T*/ }T +T{ 2 2 2 */ -> 2 2 2 T*/ }T +T{ -1 2 -1 */ -> -1 2 -1 T*/ }T +T{ -2 2 -2 */ -> -2 2 -2 T*/ }T +T{ 7 2 3 */ -> 7 2 3 T*/ }T +T{ 7 2 -3 */ -> 7 2 -3 T*/ }T +T{ -7 2 3 */ -> -7 2 3 T*/ }T +T{ -7 2 -3 */ -> -7 2 -3 T*/ }T +T{ MAX-INT 2 MAX-INT */ -> MAX-INT 2 MAX-INT T*/ }T +T{ MIN-INT 2 MIN-INT */ -> MIN-INT 2 MIN-INT T*/ }T + +\ F.6.1.0150 , +HERE 1 , +HERE 2 , +CONSTANT 2ND +CONSTANT 1ST +T{ 1ST 2ND U< -> }T \ HERE MUST GROW WITH ALLOT +T{ 1ST CELL+ -> 2ND }T \ ... BY ONE CELL +T{ 1ST 1 CELLS + -> 2ND }T +T{ 1ST @ 2ND @ -> 1 2 }T +T{ 5 1ST ! -> }T +T{ 1ST @ 2ND @ -> 5 2 }T +T{ 6 2ND ! -> }T +T{ 1ST @ 2ND @ -> 5 6 }T +T{ 1ST 2@ -> 6 5 }T +T{ 2 1 1ST 2! -> }T +T{ 1ST 2@ -> 2 1 }T +T{ 1S 1ST ! 1ST @ -> 1S }T \ CAN STORE CELL-WIDE VALUE + +\ F.6.1.0130 +! +T{ 0 1ST ! -> }T +T{ 1 1ST +! -> }T +T{ 1ST @ -> 1 }T +T{ -1 1ST +! 1ST @ -> 0 }T + +\ F.6.1.0890 CELLS +: BITS ( X -- U ) + 0 SWAP BEGIN DUP WHILE + DUP MSB AND IF >R 1+ R> THEN 2* + REPEAT DROP ; +( CELLS >= 1 AU, INTEGRAL MULTIPLE OF CHAR SIZE, >= 16 BITS ) +T{ 1 CELLS 1 < -> }T +T{ 1 CELLS 1 CHARS MOD -> 0 }T +T{ 1S BITS 10 < -> }T + +\ F.6.1.0860 C, +HERE 1 C, +HERE 2 C, +CONSTANT 2NDC +CONSTANT 1STC +T{ 1STC 2NDC U< -> }T \ HERE MUST GROW WITH ALLOT +T{ 1STC CHAR+ -> 2NDC }T \ ... BY ONE CHAR +T{ 1STC 1 CHARS + -> 2NDC }T +T{ 1STC C@ 2NDC C@ -> 1 2 }T +T{ 3 1STC C! -> }T +T{ 1STC C@ 2NDC C@ -> 3 2 }T +T{ 4 2NDC C! -> }T +T{ 1STC C@ 2NDC C@ -> 3 4 }T + +\ F.6.1.0898 CHARS +( CHARACTERS >= 1 AU, <= SIZE OF CELL, >= 8 BITS ) +T{ 1 CHARS 1 < -> }T +T{ 1 CHARS 1 CELLS > -> }T +( TBD: HOW TO FIND NUMBER OF BITS? ) + +\ F.6.1.0705 ALIGN +ALIGN 1 ALLOT HERE ALIGN HERE 3 CELLS ALLOT +CONSTANT A-ADDR CONSTANT UA-ADDR +T{ UA-ADDR ALIGNED -> A-ADDR }T +T{ 1 A-ADDR C! A-ADDR C@ -> 1 }T +T{ 1234 A-ADDR ! A-ADDR @ -> 1234 }T +T{ 123 456 A-ADDR 2! A-ADDR 2@ -> 123 456 }T +T{ 2 A-ADDR CHAR+ C! A-ADDR CHAR+ C@ -> 2 }T +T{ 3 A-ADDR CELL+ C! A-ADDR CELL+ C@ -> 3 }T +T{ 1234 A-ADDR CELL+ ! A-ADDR CELL+ @ -> 1234 }T +T{ 123 456 A-ADDR CELL+ 2! A-ADDR CELL+ 2@ -> 123 456 }T + +\ F.6.1.0710 ALLOT +HERE 1 ALLOT +HERE +CONSTANT 2NDA +CONSTANT 1STA +T{ 1STA 2NDA U< -> }T \ HERE MUST GROW WITH ALLOT +T{ 1STA 1+ -> 2NDA }T \ ... BY ONE ADDRESS UNIT +( MISSING TEST: NEGATIVE ALLOT ) + +\ F.6.1.0770 BL +T{ BL -> 20 }T + +\ F.6.1.0895 CHAR +T{ CHAR X -> 58 }T +T{ CHAR HELLO -> 48 }T + +\ F.6.1.2520 [CHAR] +T{ : GC1 [CHAR] X ; -> }T +T{ : GC2 [CHAR] HELLO ; -> }T +T{ GC1 -> 58 }T +T{ GC2 -> 48 }T + +\ F.6.1.2500 [ +T{ : GC3 [ GC1 ] LITERAL ; -> }T +T{ GC3 -> 58 }T + +\ F.6.1.2165 S" +T{ : GC4 S" XY" ; -> }T +T{ GC4 SWAP DROP -> 2 }T +T{ GC4 DROP DUP C@ SWAP CHAR+ C@ -> 58 59 }T +: GC5 S" A String"2DROP ; \ There is no space between the " and 2DROP +T{ GC5 -> }T + +\ F.6.1.0070 ' +T{ : GT1 123 ; -> }T +T{ ' GT1 EXECUTE -> 123 }T + +\ F.6.1.2510 ['] +T{ : GT2 ['] GT1 ; IMMEDIATE -> }T +T{ GT2 EXECUTE -> 123 }T + +\ F.6.1.1550 FIND +HERE 3 C, CHAR G C, CHAR T C, CHAR 1 C, CONSTANT GT1STRING +HERE 3 C, CHAR G C, CHAR T C, CHAR 2 C, CONSTANT GT2STRING +T{ GT1STRING FIND -> ' GT1 -1 }T +T{ GT2STRING FIND -> ' GT2 1 }T +( HOW TO SEARCH FOR NON-EXISTENT WORD? ) + +\ F.6.1.1780 LITERAL +T{ : GT3 GT2 LITERAL ; -> }T +T{ GT3 -> ' GT1 }T + +\ F.6.1.0980 COUNT +T{ GT1STRING COUNT -> GT1STRING CHAR+ 3 }T + +\ F.6.1.2033 POSTPONE +T{ : GT4 POSTPONE GT1 ; IMMEDIATE -> }T +T{ : GT5 GT4 ; -> }T +T{ GT5 -> 123 }T +T{ : GT6 345 ; IMMEDIATE -> }T +T{ : GT7 POSTPONE GT6 ; -> }T +T{ GT7 -> 345 }T + +\ F.6.1.2250 STATE +T{ : GT8 STATE @ ; IMMEDIATE -> }T +T{ GT8 -> 0 }T +T{ : GT9 GT8 LITERAL ; -> }T +T{ GT9 0= -> }T + +\ F.6.1.1700 IF +T{ : GI1 IF 123 THEN ; -> }T +T{ : GI2 IF 123 ELSE 234 THEN ; -> }T +T{ 0 GI1 -> }T +T{ 1 GI1 -> 123 }T +T{ -1 GI1 -> 123 }T +T{ 0 GI2 -> 234 }T +T{ 1 GI2 -> 123 }T +T{ -1 GI1 -> 123 }T +\ Multiple ELSEs in an IF statement +: melse IF 1 ELSE 2 ELSE 3 ELSE 4 ELSE 5 THEN ; +T{ melse -> 2 4 }T +T{ melse -> 1 3 5 }T + +\ F.6.1.2430 WHILE +T{ : GI3 BEGIN DUP 5 < WHILE DUP 1+ REPEAT ; -> }T +T{ 0 GI3 -> 0 1 2 3 4 5 }T +T{ 4 GI3 -> 4 5 }T +T{ 5 GI3 -> 5 }T +T{ 6 GI3 -> 6 }T +T{ : GI5 BEGIN DUP 2 > WHILE + DUP 5 < WHILE DUP 1+ REPEAT + 123 ELSE 345 THEN ; -> }T +T{ 1 GI5 -> 1 345 }T +T{ 2 GI5 -> 2 345 }T +T{ 3 GI5 -> 3 4 5 123 }T +T{ 4 GI5 -> 4 5 123 }T +T{ 5 GI5 -> 5 123 }T + +\ F.6.1.2390 UNTIL +T{ : GI4 BEGIN DUP 1+ DUP 5 > UNTIL ; -> }T +T{ 3 GI4 -> 3 4 5 6 }T +T{ 5 GI4 -> 5 6 }T +T{ 6 GI4 -> 6 7 }T + +\ F.6.1.2120 RECURSE +T{ : GI6 ( N -- 0,1,..N ) + DUP IF DUP >R 1- RECURSE R> THEN ; -> }T +T{ 0 GI6 -> 0 }T +T{ 1 GI6 -> 0 1 }T +T{ 2 GI6 -> 0 1 2 }T +T{ 3 GI6 -> 0 1 2 3 }T +T{ 4 GI6 -> 0 1 2 3 4 }T + +DECIMAL + +\ F.6.1.1800 LOOP +T{ : GD1 DO I LOOP ; -> }T +T{ 4 1 GD1 -> 1 2 3 }T +T{ 2 -1 GD1 -> -1 0 1 }T +T{ MID-UINT+1 MID-UINT GD1 -> MID-UINT }T + +\ F.6.1.0140 +LOOP +T{ : GD2 DO I -1 +LOOP ; -> }T +T{ 1 4 GD2 -> 4 3 2 1 }T +T{ -1 2 GD2 -> 2 1 0 -1 }T +T{ MID-UINT MID-UINT+1 GD2 -> MID-UINT+1 MID-UINT }T +VARIABLE gditerations +VARIABLE gdincrement + +: gd7 ( limit start increment -- ) + gdincrement ! + 0 gditerations ! + DO + 1 gditerations +! + I + gditerations @ 6 = IF LEAVE THEN + gdincrement @ + +LOOP gditerations @ +; + +T{ 4 4 -1 gd7 -> 4 1 }T +T{ 1 4 -1 gd7 -> 4 3 2 1 4 }T +T{ 4 1 -1 gd7 -> 1 0 -1 -2 -3 -4 6 }T +T{ 4 1 0 gd7 -> 1 1 1 1 1 1 6 }T +T{ 0 0 0 gd7 -> 0 0 0 0 0 0 6 }T +T{ 1 4 0 gd7 -> 4 4 4 4 4 4 6 }T +T{ 1 4 1 gd7 -> 4 5 6 7 8 9 6 }T +T{ 4 1 1 gd7 -> 1 2 3 3 }T +T{ 4 4 1 gd7 -> 4 5 6 7 8 9 6 }T +T{ 2 -1 -1 gd7 -> -1 -2 -3 -4 -5 -6 6 }T +T{ -1 2 -1 gd7 -> 2 1 0 -1 4 }T +T{ 2 -1 0 gd7 -> -1 -1 -1 -1 -1 -1 6 }T +T{ -1 2 0 gd7 -> 2 2 2 2 2 2 6 }T +T{ -1 2 1 gd7 -> 2 3 4 5 6 7 6 }T +T{ 2 -1 1 gd7 -> -1 0 1 3 }T +T{ -20 30 -10 gd7 -> 30 20 10 0 -10 -20 6 }T +T{ -20 31 -10 gd7 -> 31 21 11 1 -9 -19 6 }T +T{ -20 29 -10 gd7 -> 29 19 9 -1 -11 5 }T + +\ With large and small increments + +MAX-UINT 8 RSHIFT 1+ CONSTANT ustep +ustep NEGATE CONSTANT -ustep +MAX-INT 7 RSHIFT 1+ CONSTANT step +step NEGATE CONSTANT -step + +VARIABLE bump + +T{ : gd8 bump ! DO 1+ bump @ +LOOP ; -> }T + +T{ 0 MAX-UINT 0 ustep gd8 -> 256 }T +T{ 0 0 MAX-UINT -ustep gd8 -> 256 }T +T{ 0 MAX-INT MIN-INT step gd8 -> 256 }T +T{ 0 MIN-INT MAX-INT -step gd8 -> 256 }T + +\ F.6.1.1730 J +T{ : GD3 DO 1 0 DO J LOOP LOOP ; -> }T +T{ 4 1 GD3 -> 1 2 3 }T +T{ 2 -1 GD3 -> -1 0 1 }T +T{ MID-UINT+1 MID-UINT GD3 -> MID-UINT }T +T{ : GD4 DO 1 0 DO J LOOP -1 +LOOP ; -> }T +T{ 1 4 GD4 -> 4 3 2 1 }T +T{ -1 2 GD4 -> 2 1 0 -1 }T +T{ MID-UINT MID-UINT+1 GD4 -> MID-UINT+1 MID-UINT }T + +\ F.6.1.1760 LEAVE +T{ : GD5 123 SWAP 0 DO + I 4 > IF DROP 234 LEAVE THEN + LOOP ; -> }T +T{ 1 GD5 -> 123 }T +T{ 5 GD5 -> 123 }T +T{ 6 GD5 -> 234 }T + +\ F.6.1.2380 UNLOOP +T{ : GD6 ( PAT: {0 0},{0 0}{1 0}{1 1},{0 0}{1 0}{1 1}{2 0}{2 1}{2 2} ) + 0 SWAP 0 DO + I 1+ 0 DO + I J + 3 = IF I UNLOOP I UNLOOP EXIT THEN 1+ + LOOP + LOOP ; -> }T +T{ 1 GD6 -> 1 }T +T{ 2 GD6 -> 3 }T +T{ 3 GD6 -> 4 1 2 }T + +\ F.6.1.0450 : +T{ : NOP : POSTPONE ; ; -> }T +T{ NOP NOP1 NOP NOP2 -> }T +T{ NOP1 -> }T +T{ NOP2 -> }T +\ The following tests the dictionary search order: +T{ : GDX 123 ; : GDX GDX 234 ; -> }T +T{ GDX -> 123 234 }T + +\ F.6.1.0950 CONSTANT +T{ 123 CONSTANT X123 -> }T +T{ X123 -> 123 }T +T{ : EQU CONSTANT ; -> }T +T{ X123 EQU Y123 -> }T +T{ Y123 -> 123 }T + +\ F.6.1.2410 VARIABLE +T{ VARIABLE V1 -> }T +T{ 123 V1 ! -> }T +T{ V1 @ -> 123 }T + +\ F.6.1.1250 DOES> +T{ : DOES1 DOES> @ 1 + ; -> }T +T{ : DOES2 DOES> @ 2 + ; -> }T +T{ CREATE CR1 -> }T +T{ CR1 -> HERE }T +T{ 1 , -> }T +T{ CR1 @ -> 1 }T +T{ DOES1 -> }T +T{ CR1 -> 2 }T +T{ DOES2 -> }T +T{ CR1 -> 3 }T +T{ : WEIRD: CREATE DOES> 1 + DOES> 2 + ; -> }T +T{ WEIRD: W1 -> }T +T{ ' W1 >BODY -> HERE }T +T{ W1 -> HERE 1 + }T +T{ W1 -> HERE 2 + }T + +\ F.6.1.0550 >BODY +T{ CREATE CR0 -> }T +T{ ' CR0 >BODY -> HERE }T + +\ F.6.1.1360 EVALUATE +: GE1 S" 123" ; IMMEDIATE +: GE2 S" 123 1+" ; IMMEDIATE +: GE3 S" : GE4 345 ;" ; +: GE5 EVALUATE ; IMMEDIATE +T{ GE1 EVALUATE -> 123 }T ( TEST EVALUATE IN INTERP. STATE ) +T{ GE2 EVALUATE -> 124 }T +T{ GE3 EVALUATE -> }T +T{ GE4 -> 345 }T + +T{ : GE6 GE1 GE5 ; -> }T ( TEST EVALUATE IN COMPILE STATE ) +T{ GE6 -> 123 }T +T{ : GE7 GE2 GE5 ; -> }T +T{ GE7 -> 124 }T + +\ F.6.1.2216 SOURCE +: GS1 S" SOURCE" 2DUP EVALUATE >R SWAP >R = R> R> = ; +T{ GS1 -> }T +: GS4 SOURCE >IN ! DROP ; +T{ GS4 123 456 + -> }T + +\ F.6.1.0560 >IN +VARIABLE SCANS +: RESCAN? -1 SCANS +! SCANS @ IF 0 >IN ! THEN ; +T{ 2 SCANS ! +345 RESCAN? +-> 345 345 }T + +: GS2 5 SCANS ! S" 123 RESCAN?" EVALUATE ; +T{ GS2 -> 123 123 123 123 123 }T + +\ These tests must start on a new line +DECIMAL +T{ 123456 DEPTH OVER 9 < 35 AND + 3 + >IN ! +-> 123456 23456 3456 456 56 6 }T +T{ 14145 8115 ?DUP 0= 34 AND >IN +! TUCK MOD 14 >IN ! GCD calculation +-> 15 }T +HEX + +\ F.6.1.2450 WORD +: GS3 WORD COUNT SWAP C@ ; +T{ BL GS3 HELLO -> 5 CHAR H }T +T{ CHAR " GS3 GOODBYE" -> 7 CHAR G }T +T{ BL GS3 + DROP -> 0 }T \ Blank lines return zero-length strings + +: S= \ ( ADDR1 C1 ADDR2 C2 -- T/F ) Compare two strings. + >R SWAP R@ = IF \ Make sure strings have same length + R> ?DUP IF \ If non-empty strings + 0 DO + OVER C@ OVER C@ - IF 2DROP UNLOOP EXIT THEN + SWAP CHAR+ SWAP CHAR+ + LOOP + THEN + 2DROP \ If we get here, strings match + ELSE + R> DROP 2DROP \ Lengths mismatch + THEN ; + +\ F.6.1.1670 HOLD +: GP1 <# 41 HOLD 42 HOLD 0 0 #> S" BA" S= ; +T{ GP1 -> }T + +\ F.6.1.2210 SIGN +: GP2 <# -1 SIGN 0 SIGN -1 SIGN 0 0 #> S" --" S= ; +T{ GP2 -> }T + +\ F.6.1.0030 # +: GP3 <# 1 0 # # #> S" 01" S= ; +T{ GP3 -> }T + +24 CONSTANT MAX-BASE \ BASE 2 ... 36 +: COUNT-BITS + 0 0 INVERT BEGIN DUP WHILE >R 1+ R> 2* REPEAT DROP ; +COUNT-BITS 2* CONSTANT #BITS-UD \ NUMBER OF BITS IN UD + +\ F.6.1.0050 #S +: GP4 <# 1 0 #S #> S" 1" S= ; +T{ GP4 -> }T +: GP5 + BASE @ + MAX-BASE 1+ 2 DO \ FOR EACH POSSIBLE BASE + I BASE ! \ TBD: ASSUMES BASE WORKS + I 0 <# #S #> S" 10" S= AND + LOOP + SWAP BASE ! ; +T{ GP5 -> }T + +: GP6 + BASE @ >R 2 BASE ! + MAX-UINT MAX-UINT <# #S #> \ MAXIMUM UD TO BINARY + R> BASE ! \ S: C-ADDR U + DUP #BITS-UD = SWAP + 0 DO \ S: C-ADDR FLAG + OVER C@ [CHAR] 1 = AND \ ALL ONES + >R CHAR+ R> + LOOP SWAP DROP ; +T{ GP6 -> }T + +: GP7 + BASE @ >R MAX-BASE BASE ! + + A 0 DO + I 0 <# #S #> + 1 = SWAP C@ I 30 + = AND AND + LOOP + MAX-BASE A DO + I 0 <# #S #> + 1 = SWAP C@ 41 I A - + = AND AND + LOOP + R> BASE ! ; +T{ GP7 -> }T + +\ F.6.1.0570 >NUMBER +CREATE GN-BUF 0 C, +: GN-STRING GN-BUF 1 ; +: GN-CONSUMED GN-BUF CHAR+ 0 ; +: GN' [CHAR] ' WORD CHAR+ C@ GN-BUF C! GN-STRING ; +T{ 0 0 GN' 0' >NUMBER -> 0 0 GN-CONSUMED }T +T{ 0 0 GN' 1' >NUMBER -> 1 0 GN-CONSUMED }T +T{ 1 0 GN' 1' >NUMBER -> BASE @ 1+ 0 GN-CONSUMED }T +\ FOLLOWING SHOULD FAIL TO CONVERT +T{ 0 0 GN' -' >NUMBER -> 0 0 GN-STRING }T +T{ 0 0 GN' +' >NUMBER -> 0 0 GN-STRING }T +T{ 0 0 GN' .' >NUMBER -> 0 0 GN-STRING }T + +: >NUMBER-BASED + BASE @ >R BASE ! >NUMBER R> BASE ! ; + +T{ 0 0 GN' 2' 10 >NUMBER-BASED -> 2 0 GN-CONSUMED }T +T{ 0 0 GN' 2' 2 >NUMBER-BASED -> 0 0 GN-STRING }T +T{ 0 0 GN' F' 10 >NUMBER-BASED -> F 0 GN-CONSUMED }T +T{ 0 0 GN' G' 10 >NUMBER-BASED -> 0 0 GN-STRING }T +T{ 0 0 GN' G' MAX-BASE >NUMBER-BASED -> 10 0 GN-CONSUMED }T +T{ 0 0 GN' Z' MAX-BASE >NUMBER-BASED -> 23 0 GN-CONSUMED }T + +: GN1 ( UD BASE -- UD' LEN ) + \ UD SHOULD EQUAL UD' AND LEN SHOULD BE ZERO. + BASE @ >R BASE ! + <# #S #> + 0 0 2SWAP >NUMBER SWAP DROP \ RETURN LENGTH ONLY + R> BASE ! ; + +T{ 0 0 2 GN1 -> 0 0 0 }T +T{ MAX-UINT 0 2 GN1 -> MAX-UINT 0 0 }T +T{ MAX-UINT DUP 2 GN1 -> MAX-UINT DUP 0 }T +T{ 0 0 MAX-BASE GN1 -> 0 0 0 }T +T{ MAX-UINT 0 MAX-BASE GN1 -> MAX-UINT 0 0 }T +T{ MAX-UINT DUP MAX-BASE GN1 -> MAX-UINT DUP 0 }T + +\ F.6.1.0750 BASE +: GN2 \ ( -- 16 10 ) + BASE @ >R HEX BASE @ DECIMAL BASE @ R> BASE ! ; +T{ GN2 -> 10 A }T + +CREATE FBUF 00 C, 00 C, 00 C, +CREATE SBUF 12 C, 34 C, 56 C, +: SEEBUF FBUF C@ FBUF CHAR+ C@ FBUF CHAR+ CHAR+ C@ ; + +\ F.6.1.1540 FILL +T{ FBUF 0 20 FILL -> }T +T{ SEEBUF -> 00 00 00 }T +T{ FBUF 1 20 FILL -> }T +T{ SEEBUF -> 20 00 00 }T + +T{ FBUF 3 20 FILL -> }T +T{ SEEBUF -> 20 20 20 }T + +\ F.6.1.1900 MOVE +T{ FBUF FBUF 3 CHARS MOVE -> }T \ BIZARRE SPECIAL CASE +T{ SEEBUF -> 20 20 20 }T +T{ SBUF FBUF 0 CHARS MOVE -> }T +T{ SEEBUF -> 20 20 20 }T + +T{ SBUF FBUF 1 CHARS MOVE -> }T +T{ SEEBUF -> 12 20 20 }T + +T{ SBUF FBUF 3 CHARS MOVE -> }T +T{ SEEBUF -> 12 34 56 }T + +T{ FBUF FBUF CHAR+ 2 CHARS MOVE -> }T +T{ SEEBUF -> 12 12 34 }T + +T{ FBUF CHAR+ FBUF 2 CHARS MOVE -> }T +T{ SEEBUF -> 12 34 34 }T + +\ F.6.1.1320 EMIT +: OUTPUT-TEST + ." YOU SHOULD SEE THE STANDARD GRAPHIC CHARACTERS:" CR + 41 BL DO I EMIT LOOP CR + 61 41 DO I EMIT LOOP CR + 7F 61 DO I EMIT LOOP CR + ." YOU SHOULD SEE 0-9 SEPARATED BY A SPACE:" CR + 9 1+ 0 DO I . LOOP CR + ." YOU SHOULD SEE 0-9 (WITH NO SPACES):" CR + [CHAR] 9 1+ [CHAR] 0 DO I 0 SPACES EMIT LOOP CR + ." YOU SHOULD SEE A-G SEPARATED BY A SPACE:" CR + [CHAR] G 1+ [CHAR] A DO I EMIT SPACE LOOP CR + ." YOU SHOULD SEE 0-5 SEPARATED BY TWO SPACES:" CR + 5 1+ 0 DO I [CHAR] 0 + EMIT 2 SPACES LOOP CR + ." YOU SHOULD SEE TWO SEPARATE LINES:" CR + S" LINE 1" TYPE CR S" LINE 2" TYPE CR + ." YOU SHOULD SEE THE NUMBER RANGES OF SIGNED AND UNSIGNED NUMBERS:" CR + ." SIGNED: " MIN-INT . MAX-INT . CR + ." UNSIGNED: " 0 U. MAX-UINT U. CR +; +T{ OUTPUT-TEST -> }T + +\ F.6.1.0695 ACCEPT +CREATE ABUF 80 CHARS ALLOT +: ACCEPT-TEST + CR ." PLEASE TYPE UP TO 80 CHARACTERS:" CR + ABUF 80 ACCEPT + CR ." RECEIVED: " [CHAR] " EMIT + ABUF SWAP TYPE [CHAR] " EMIT CR +; +\ SKIP: T{ ACCEPT-TEST -> }T + +\ F.6.1.0450 : +T{ : NOP : POSTPONE ; ; -> }T +T{ NOP NOP1 NOP NOP2 -> }T +T{ NOP1 -> }T +T{ NOP2 -> }T +\ The following tests the dictionary search order: +T{ : GDX 123 ; : GDX GDX 234 ; -> }T +T{ GDX -> 123 234 }T + +." Done" CR +BYE diff --git a/samples/forth/ttester.forth b/samples/forth/ttester.forth new file mode 100644 index 0000000000..4ddc4d946e --- /dev/null +++ b/samples/forth/ttester.forth @@ -0,0 +1,345 @@ +\ This file contains the code for ttester, a utility for testing Forth words, +\ as developed by several authors (see below), together with some explanations +\ of its use. + +\ ttester is based on the original tester suite by Hayes: +\ From: John Hayes S1I +\ Subject: tester.fr +\ Date: Mon, 27 Nov 95 13:10:09 PST +\ (C) 1995 JOHNS HOPKINS UNIVERSITY / APPLIED PHYSICS LABORATORY +\ MAY BE DISTRIBUTED FREELY AS LONG AS THIS COPYRIGHT NOTICE REMAINS. +\ VERSION 1.1 +\ All the subsequent changes have been placed in the public domain. +\ The primary changes from the original are the replacement of "{" by "T{" +\ and "}" by "}T" (to avoid conflicts with the uses of { for locals and } +\ for FSL arrays), modifications so that the stack is allowed to be non-empty +\ before T{, and extensions for the handling of floating point tests. +\ Code for testing equality of floating point values comes +\ from ftester.fs written by David N. Williams, based on the idea of +\ approximate equality in Dirk Zoller's float.4th. +\ Further revisions were provided by Anton Ertl, including the ability +\ to handle either integrated or separate floating point stacks. +\ Revision history and possibly newer versions can be found at +\ http://www.complang.tuwien.ac.at/cvsweb/cgi-bin/cvsweb/gforth/test/ttester.fs +\ Explanatory material and minor reformatting (no code changes) by +\ C. G. Montgomery March 2009, with helpful comments from David Williams +\ and Krishna Myneni. + +\ Usage: + +\ The basic usage takes the form T{ -> }T . +\ This executes and compares the resulting stack contents with +\ the values, and reports any discrepancy between the +\ two sets of values. +\ For example: +\ T{ 1 2 3 swap -> 1 3 2 }T ok +\ T{ 1 2 3 swap -> 1 2 2 }T INCORRECT RESULT: T{ 1 2 3 swap -> 1 2 2 }T ok +\ T{ 1 2 3 swap -> 1 2 }T WRONG NUMBER OF RESULTS: T{ 1 2 3 swap -> 1 2 }T ok + +\ Floating point testing can involve further complications. The code +\ attempts to determine whether floating-point support is present, and +\ if so, whether there is a separate floating-point stack, and behave +\ accordingly. The CONSTANTs HAS-FLOATING and HAS-FLOATING-STACK +\ contain the results of its efforts, so the behavior of the code can +\ be modified by the user if necessary. + +\ Then there are the perennial issues of floating point value +\ comparisons. Exact equality is specified by SET-EXACT (the +\ default). If approximate equality tests are desired, execute +\ SET-NEAR . Then the FVARIABLEs REL-NEAR (default 1E-12) and +\ ABS-NEAR (default 0E) contain the values to be used in comparisons +\ by the (internal) word FNEARLY= . + +\ When there is not a separate floating point stack and you want to +\ use approximate equality for FP values, it is necessary to identify +\ which stack items are floating point quantities. This can be done +\ by replacing the closing }T with a version that specifies this, such +\ as RRXR}T which identifies the stack picture ( r r x r ). The code +\ provides such words for all combinations of R and X with up to four +\ stack items. They can be used with either an integrated or separate +\ floating point stacks. Adding more if you need them is +\ straightforward; see the examples in the source. Here is an example +\ which also illustrates controlling the precision of comparisons: + +\ SET-NEAR +\ 1E-6 REL-NEAR F! +\ T{ S" 3.14159E" >FLOAT -> -1E FACOS TRUE RX}T + +\ The word ERROR is now vectored, so that its action can be changed by +\ the user (for example, to add a counter for the number of errors). +\ The default action ERROR1 can be used as a factor in the display of +\ error reports. + +\ Loading ttester.fs does not change BASE. Remember that floating point input +\ is ambiguous if the base is not decimal. + +\ The file defines some 70 words in all, but in most cases only the +\ ones mentioned above will be needed for successful testing. + +BASE @ +DECIMAL + +VARIABLE ACTUAL-DEPTH \ stack record +CREATE ACTUAL-RESULTS 32 CELLS ALLOT +VARIABLE START-DEPTH +VARIABLE XCURSOR \ for ...}T +VARIABLE ERROR-XT + +: ERROR ERROR-XT @ EXECUTE ; \ for vectoring of error reporting + +: "FLOATING" S" FLOATING" ; \ only compiled S" in CORE +: "FLOATING-STACK" S" FLOATING-STACK" ; +"FLOATING" ENVIRONMENT? [IF] + [IF] + TRUE + [ELSE] + FALSE + [THEN] +[ELSE] + FALSE +[THEN] CONSTANT HAS-FLOATING +"FLOATING-STACK" ENVIRONMENT? [IF] + [IF] + TRUE + [ELSE] + FALSE + [THEN] +[ELSE] \ We don't know whether the FP stack is separate. + HAS-FLOATING \ If we have FLOATING, we assume it is. +[THEN] CONSTANT HAS-FLOATING-STACK + +HAS-FLOATING [IF] + \ Set the following to the relative and absolute tolerances you + \ want for approximate float equality, to be used with F~ in + \ FNEARLY=. Keep the signs, because F~ needs them. + FVARIABLE REL-NEAR 1E-12 REL-NEAR F! + FVARIABLE ABS-NEAR 0E ABS-NEAR F! + + \ When EXACT? is TRUE, }F uses FEXACTLY=, otherwise FNEARLY=. + + TRUE VALUE EXACT? + : SET-EXACT ( -- ) TRUE TO EXACT? ; + : SET-NEAR ( -- ) FALSE TO EXACT? ; + + : FEXACTLY= ( F: X Y -- S: FLAG ) + ( + Leave TRUE if the two floats are identical. + ) + 0E F~ ; + + : FABS= ( F: X Y -- S: FLAG ) + ( + Leave TRUE if the two floats are equal within the tolerance + stored in ABS-NEAR. + ) + ABS-NEAR F@ F~ ; + + : FREL= ( F: X Y -- S: FLAG ) + ( + Leave TRUE if the two floats are relatively equal based on the + tolerance stored in ABS-NEAR. + ) + REL-NEAR F@ FNEGATE F~ ; + + : F2DUP FOVER FOVER ; + : F2DROP FDROP FDROP ; + + : FNEARLY= ( F: X Y -- S: FLAG ) + ( + Leave TRUE if the two floats are nearly equal. This is a + refinement of Dirk Zoller's FEQ to also allow X = Y, including + both zero, or to allow approximately equality when X and Y are too + small to satisfy the relative approximation mode in the F~ + specification. + ) + F2DUP FEXACTLY= IF F2DROP TRUE EXIT THEN + F2DUP FREL= IF F2DROP TRUE EXIT THEN + FABS= ; + + : FCONF= ( R1 R2 -- F ) + EXACT? IF + FEXACTLY= + ELSE + FNEARLY= + THEN ; +[THEN] + +HAS-FLOATING-STACK [IF] + VARIABLE ACTUAL-FDEPTH + CREATE ACTUAL-FRESULTS 32 FLOATS ALLOT + VARIABLE START-FDEPTH + VARIABLE FCURSOR + + : EMPTY-FSTACK ( ... -- ... ) + FDEPTH START-FDEPTH @ < IF + FDEPTH START-FDEPTH @ SWAP DO 0E LOOP + THEN + FDEPTH START-FDEPTH @ > IF + FDEPTH START-FDEPTH @ DO FDROP LOOP + THEN ; + + : F{ ( -- ) + FDEPTH START-FDEPTH ! 0 FCURSOR ! ; + + : F-> ( ... -- ... ) + FDEPTH DUP ACTUAL-FDEPTH ! + START-FDEPTH @ > IF + FDEPTH START-FDEPTH @ - 0 DO ACTUAL-FRESULTS I FLOATS + F! LOOP + THEN ; + + : F} ( ... -- ... ) + FDEPTH ACTUAL-FDEPTH @ = IF + FDEPTH START-FDEPTH @ > IF + FDEPTH START-FDEPTH @ - 0 DO + ACTUAL-FRESULTS I FLOATS + F@ FCONF= INVERT IF + S" INCORRECT FP RESULT: " ERROR LEAVE + THEN + LOOP + THEN + ELSE + S" WRONG NUMBER OF FP RESULTS: " ERROR + THEN ; + + : F...}T ( -- ) + FCURSOR @ START-FDEPTH @ + ACTUAL-FDEPTH @ <> IF + S" NUMBER OF FLOAT RESULTS BEFORE '->' DOES NOT MATCH ...}T SPECIFICATION: " ERROR + ELSE FDEPTH START-FDEPTH @ = 0= IF + S" NUMBER OF FLOAT RESULTS BEFORE AND AFTER '->' DOES NOT MATCH: " ERROR + THEN THEN ; + + + : FTESTER ( R -- ) + FDEPTH 0= ACTUAL-FDEPTH @ FCURSOR @ START-FDEPTH @ + 1+ < OR IF + S" NUMBER OF FLOAT RESULTS AFTER '->' BELOW ...}T SPECIFICATION: " ERROR + ELSE ACTUAL-FRESULTS FCURSOR @ FLOATS + F@ FCONF= 0= IF + S" INCORRECT FP RESULT: " ERROR + THEN THEN + 1 FCURSOR +! ; + +[ELSE] + : EMPTY-FSTACK ; + : F{ ; + : F-> ; + : F} ; + : F...}T ; + + HAS-FLOATING [IF] + : COMPUTE-CELLS-PER-FP ( -- U ) + DEPTH 0E DEPTH 1- >R FDROP R> SWAP - ; + + COMPUTE-CELLS-PER-FP CONSTANT CELLS-PER-FP + + : FTESTER ( R -- ) + DEPTH CELLS-PER-FP < ACTUAL-DEPTH @ XCURSOR @ START-DEPTH @ + CELLS-PER-FP + < OR IF + S" NUMBER OF RESULTS AFTER '->' BELOW ...}T SPECIFICATION: " ERROR EXIT + ELSE ACTUAL-RESULTS XCURSOR @ CELLS + F@ FCONF= 0= IF + S" INCORRECT FP RESULT: " ERROR + THEN THEN + CELLS-PER-FP XCURSOR +! ; + [THEN] +[THEN] + +: EMPTY-STACK \ ( ... -- ) empty stack; handles underflowed stack too. + DEPTH START-DEPTH @ < IF + DEPTH START-DEPTH @ SWAP DO 0 LOOP + THEN + DEPTH START-DEPTH @ > IF + DEPTH START-DEPTH @ DO DROP LOOP + THEN + EMPTY-FSTACK ; + +: ERROR1 \ ( C-ADDR U -- ) display an error message + \ followed by the line that had the error. + TYPE SOURCE TYPE CR \ display line corresponding to error + EMPTY-STACK \ throw away everything else +; + +' ERROR1 ERROR-XT ! + +: T{ \ ( -- ) syntactic sugar. + DEPTH START-DEPTH ! 0 XCURSOR ! F{ ; + +: -> \ ( ... -- ) record depth and contents of stack. + DEPTH DUP ACTUAL-DEPTH ! \ record depth + START-DEPTH @ > IF \ if there is something on the stack + DEPTH START-DEPTH @ - 0 DO ACTUAL-RESULTS I CELLS + ! LOOP \ save them + THEN + F-> ; + +: }T \ ( ... -- ) COMPARE STACK (EXPECTED) CONTENTS WITH SAVED + \ (ACTUAL) CONTENTS. + DEPTH ACTUAL-DEPTH @ = IF \ if depths match + DEPTH START-DEPTH @ > IF \ if there is something on the stack + DEPTH START-DEPTH @ - 0 DO \ for each stack item + ACTUAL-RESULTS I CELLS + @ \ compare actual with expected + <> IF S" INCORRECT RESULT: " ERROR LEAVE THEN + LOOP + THEN + ELSE \ depth mismatch + S" WRONG NUMBER OF RESULTS: " ERROR + THEN + F} ; + +: ...}T ( -- ) + XCURSOR @ START-DEPTH @ + ACTUAL-DEPTH @ <> IF + S" NUMBER OF CELL RESULTS BEFORE '->' DOES NOT MATCH ...}T SPECIFICATION: " ERROR + ELSE DEPTH START-DEPTH @ = 0= IF + S" NUMBER OF CELL RESULTS BEFORE AND AFTER '->' DOES NOT MATCH: " ERROR + THEN THEN + F...}T ; + +: XTESTER ( X -- ) + DEPTH 0= ACTUAL-DEPTH @ XCURSOR @ START-DEPTH @ + 1+ < OR IF + S" NUMBER OF CELL RESULTS AFTER '->' BELOW ...}T SPECIFICATION: " ERROR EXIT + ELSE ACTUAL-RESULTS XCURSOR @ CELLS + @ <> IF + S" INCORRECT CELL RESULT: " ERROR + THEN THEN + 1 XCURSOR +! ; + +: X}T XTESTER ...}T ; +: XX}T XTESTER XTESTER ...}T ; +: XXX}T XTESTER XTESTER XTESTER ...}T ; +: XXXX}T XTESTER XTESTER XTESTER XTESTER ...}T ; + +HAS-FLOATING [IF] +: R}T FTESTER ...}T ; +: XR}T FTESTER XTESTER ...}T ; +: RX}T XTESTER FTESTER ...}T ; +: RR}T FTESTER FTESTER ...}T ; +: XXR}T FTESTER XTESTER XTESTER ...}T ; +: XRX}T XTESTER FTESTER XTESTER ...}T ; +: XRR}T FTESTER FTESTER XTESTER ...}T ; +: RXX}T XTESTER XTESTER FTESTER ...}T ; +: RXR}T FTESTER XTESTER FTESTER ...}T ; +: RRX}T XTESTER FTESTER FTESTER ...}T ; +: RRR}T FTESTER FTESTER FTESTER ...}T ; +: XXXR}T FTESTER XTESTER XTESTER XTESTER ...}T ; +: XXRX}T XTESTER FTESTER XTESTER XTESTER ...}T ; +: XXRR}T FTESTER FTESTER XTESTER XTESTER ...}T ; +: XRXX}T XTESTER XTESTER FTESTER XTESTER ...}T ; +: XRXR}T FTESTER XTESTER FTESTER XTESTER ...}T ; +: XRRX}T XTESTER FTESTER FTESTER XTESTER ...}T ; +: XRRR}T FTESTER FTESTER FTESTER XTESTER ...}T ; +: RXXX}T XTESTER XTESTER XTESTER FTESTER ...}T ; +: RXXR}T FTESTER XTESTER XTESTER FTESTER ...}T ; +: RXRX}T XTESTER FTESTER XTESTER FTESTER ...}T ; +: RXRR}T FTESTER FTESTER XTESTER FTESTER ...}T ; +: RRXX}T XTESTER XTESTER FTESTER FTESTER ...}T ; +: RRXR}T FTESTER XTESTER FTESTER FTESTER ...}T ; +: RRRX}T XTESTER FTESTER FTESTER FTESTER ...}T ; +: RRRR}T FTESTER FTESTER FTESTER FTESTER ...}T ; +[THEN] + +\ Set the following flag to TRUE for more verbose output; this may +\ allow you to tell which test caused your system to hang. +VARIABLE VERBOSE + FALSE VERBOSE ! + +\ TODO: TESTING \ ( -- ) TALKING COMMENT. +\ SOURCE VERBOSE @ +\ IF DUP >R TYPE CR R> >IN ! +\ ELSE >IN ! DROP +\ THEN ; + +BASE ! +\ end of ttester.fs diff --git a/samples/hello.neon b/samples/hello.neon deleted file mode 100644 index ad35e5ae34..0000000000 --- a/samples/hello.neon +++ /dev/null @@ -1 +0,0 @@ -print("Hello World") diff --git a/samples/curses.neon b/samples/hello/hello-curses.neon similarity index 100% rename from samples/curses.neon rename to samples/hello/hello-curses.neon diff --git a/samples/hello/hello.neon b/samples/hello/hello.neon new file mode 100644 index 0000000000..d973f3d47e --- /dev/null +++ b/samples/hello/hello.neon @@ -0,0 +1,7 @@ +%| + | File: hello + | + | The canonical "Hello World" program. + |% + +print("Hello World") diff --git a/samples/httpd/httpd.neon b/samples/httpd/httpd.neon new file mode 100644 index 0000000000..ed4966a9b7 --- /dev/null +++ b/samples/httpd/httpd.neon @@ -0,0 +1,157 @@ +%| + | File: httpd + | + | Implementation of a simple HTTP server. + |% + +IMPORT file +IMPORT net +IMPORT regex +IMPORT string +IMPORT variant + +FUNCTION toBytes(s: String): Bytes + RETURN s.toBytes() +END FUNCTION + +TYPE Response IS RECORD + code: Number + content_type: String + headers: Dictionary + content: Bytes +END RECORD + +TYPE RequestHandler IS FUNCTION(path: String, params: Array): Response + +TYPE Handler IS RECORD + path: String + callback: RequestHandler + params: Array +END RECORD + +TYPE Client IS RECORD + socket: net.Socket + header: String +END RECORD + +TYPE HttpServer IS RECORD + port: Number + server: net.Socket + clients: Array + handlers: Array +END RECORD + +FUNCTION HttpServer.addHandler(INOUT self: HttpServer, path: String, callback: RequestHandler, params: Array) + self.handlers.append(Handler(path, callback, params)) +END FUNCTION + +FUNCTION HttpServer.serve(INOUT self: HttpServer) + self.server := net.tcpSocket() + self.server.listen(self.port) + LOOP + VAR read: Array := [self.server] + FOREACH c OF self.clients DO + read.append(c.socket) + END FOREACH + VAR write: Array := [] + VAR error: Array := [] + IF net.select(INOUT read, INOUT write, INOUT error, -1) THEN + IF self.server IN read THEN + self.clients.append(Client(socket WITH self.server.accept())) + END IF + FOR i := self.clients.size()-1 TO 0 STEP -1 DO + IF self.clients[i].socket IN read THEN + IF self.handle_incoming(INOUT self.clients[i]) THEN + self.clients[i].socket.close() + self.clients[i TO i] := [] % TODO: need an array remove method + END IF + END IF + END FOR + END IF + END LOOP +END FUNCTION + +FUNCTION HttpServer.handle_incoming(INOUT self: HttpServer, INOUT client: Client): Boolean + client.header.append(client.socket.recv(1000).toString()) + IF client.header[LAST-3 TO LAST] = "\r\n\r\n" THEN + self.handle_request(INOUT client) + RETURN TRUE + END IF + RETURN FALSE +END FUNCTION + +FUNCTION HttpServer.handle_request(INOUT self: HttpServer, INOUT client: Client) + LET a: Array := string.split(client.header, "\r\n") + LET request: Array := string.split(a[0], " ") + LET method: String := request[0] + LET path: String := request[1] + LET version: String := request[2] + VAR response: Response := Response() + VAR found: Boolean := FALSE + FOREACH h OF self.handlers DO + VAR m: regex.Match + IF regex.search(h.path, path, OUT m) THEN + % TODO: Should be able to do h.callback(path, params), + % but that syntax seems to be handled only as a method + LET rh: RequestHandler := h.callback + response := rh(path, h.params) + found := TRUE + EXIT FOREACH + END IF + END FOREACH + IF NOT found THEN + response.code := 404 + response.content_type := "text/plain" + response.content := toBytes("not found") + END IF + print("\(method) \(path) \(response.code)") + client.socket.send(toBytes("HTTP/1.0 \(response.code) ok\r\n")) + client.socket.send(toBytes("Content-type: \(response.content_type)\r\n")) + FOREACH h OF response.headers.keys() DO + client.socket.send(toBytes("\(h): \(response.headers[h])\r\n")) + END FOREACH + client.socket.send(toBytes("Content-length: \(response.content.size())\r\n")) + client.socket.send(toBytes("\r\n")) + client.socket.send(response.content) +END FUNCTION + +FUNCTION path_handler(path: String, params: Array): Response + VAR r: Response := Response() + LET local_path: String := params[0].getString() & path + IF file.isDirectory(local_path) THEN + IF path[LAST] # "/" THEN + r.code := 302 + r.content_type := "text/plain" + r.headers["Location"] := path & "/" + ELSE + r.code := 200 + r.content_type := "text/html" + VAR page: String := "" + page.append("\n") + page.append("\n") + page.append("\n") + FOREACH fn OF file.files(local_path) DO + page.append("\n") + END FOREACH + page.append("
\(fn)
\n") + page.append("\n") + page.append("\n") + r.content := page.toBytes() + END IF + ELSE + TRY + r.code := 200 + r.content_type := "text/plain" + r.content := file.readBytes(local_path) + EXCEPTION file.FileOpenException DO + r.code := 404 + r.content_type := "text/plain" + r.content := toBytes("file not found") + END TRY + END IF + RETURN r +END FUNCTION + +VAR server: HttpServer := HttpServer(port WITH 8080) +server.addHandler("/", path_handler, [variant.makeString(".")]) +server.serve() diff --git a/samples/life/life.neon b/samples/life/life.neon new file mode 100644 index 0000000000..96d5019d4f --- /dev/null +++ b/samples/life/life.neon @@ -0,0 +1,96 @@ +%| + | File: life + | + | Conway's Game of Life. + |% + +IMPORT multiarray +IMPORT random +IMPORT sdl + +TYPE Rect IS sdl.Rect + +FUNCTION rect(x, y, w, h: Number): sdl.Rect + VAR r: sdl.Rect := Rect() + r.x := x + r.y := y + r.w := w + r.h := h + RETURN r +END FUNCTION + +VAR grid: multiarray.ArrayBoolean2D + +FUNCTION handle(e: sdl.Event, INOUT quit: Boolean) + CASE e.type + WHEN sdl.SDL_QUIT DO + quit := TRUE + END CASE +END FUNCTION + +FUNCTION render(ren: sdl.Renderer) + sdl.SetRenderDrawColor(ren, 0, 0, 0, 255) + sdl.RenderClear(ren) + sdl.SetRenderDrawColor(ren, 255, 255, 255, 255) + FOREACH row OF grid INDEX i DO + FOREACH cell OF row INDEX j DO + IF cell THEN + sdl.RenderFillRect(ren, rect(j*10, i*10, 10, 10)) + END IF + END FOREACH + END FOREACH + sdl.RenderPresent(ren) +END FUNCTION + +FUNCTION update() + VAR newgrid: multiarray.ArrayBoolean2D := [] + FOR i := 0 TO grid.size()-1 DO + FOR j := 0 TO grid[i].size()-1 DO + VAR count: Number := 0 + FOR y := -1 TO 1 DO + FOR x := -1 TO 1 DO + IF x # 0 OR y # 0 THEN + IF grid[(i+y) MOD grid.size(), (j+x) MOD grid[0].size()] THEN + INC count + END IF + END IF + END FOR + END FOR + CASE count + WHEN 0 TO 1 DO + newgrid[i, j] := FALSE + WHEN 2 DO + newgrid[i, j] := grid[i, j] + WHEN 3 DO + newgrid[i, j] := TRUE + WHEN >= 4 DO + newgrid[i, j] := FALSE + END CASE + END FOR + END FOR + grid := newgrid +END FUNCTION + +BEGIN MAIN + sdl.Init(sdl.INIT_VIDEO) + LET win: sdl.Window := sdl.CreateWindow("Hello World!", 100, 100, 640, 480, sdl.WINDOW_SHOWN) + LET ren: sdl.Renderer := sdl.CreateRenderer(win, -1, sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC) + grid := multiarray.makeBoolean2D(48, 64) + FOR i := 0 TO grid.size()-1 DO + FOR j := 0 TO grid[i].size()-1 DO + grid[i, j] := random.uint32() MOD 4 = 0 + END FOR + END FOR + VAR quit: Boolean := FALSE + WHILE NOT quit DO + VAR e: sdl.Event + WHILE sdl.PollEvent(OUT e) DO + handle(e, INOUT quit) + END WHILE + render(ren) + update() + END WHILE + sdl.DestroyRenderer(ren) + sdl.DestroyWindow(win) + sdl.Quit() +END MAIN diff --git a/samples/lisp/lisp.neon b/samples/lisp/lisp.neon new file mode 100644 index 0000000000..613d3f63b6 --- /dev/null +++ b/samples/lisp/lisp.neon @@ -0,0 +1,407 @@ +%| + | File: lisp + | + | Implementation of a Scheme-like Lisp variant. + |% + +EXPORT eval +EXPORT parse +EXPORT repr +EXPORT Value + +IMPORT io +IMPORT math +IMPORT regex +IMPORT sys + +DECLARE EXCEPTION ParseEndOfInputException +DECLARE EXCEPTION SyntaxErrorException +DECLARE EXCEPTION TypeMismatchException + +TYPE Environment IS RECORD + parent: POINTER TO Environment + names: Dictionary +END RECORD + +LET g_env: POINTER TO Environment := NEW Environment + +TYPE Symbol IS RECORD + name: String +END RECORD + +VAR Symbols: Dictionary + +FUNCTION get_symbol(name: String): POINTER TO Symbol + IF name IN Symbols THEN + RETURN Symbols[name] + END IF + LET p: POINTER TO Symbol := NEW Symbol + p->name := name + Symbols[name] := p + RETURN p +END FUNCTION + +TYPE Type IS ENUM + null + boolean + number + symbol + string + pair + proc +END ENUM + +TYPE Value IS RECORD + type: Type + val_boolean: Boolean + val_number: Number + val_symbol: POINTER TO Symbol + val_string: String + val_pair: RECORD + car: POINTER TO Value + cdr: POINTER TO Value + END RECORD + val_proc: FUNCTION(args: Array): POINTER TO Value +END RECORD + +FUNCTION make_null(): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.null + RETURN r +END FUNCTION + +FUNCTION make_boolean(b: Boolean): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.boolean + r->val_boolean := b + RETURN r +END FUNCTION + +FUNCTION make_number(n: Number): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.number + r->val_number := n + RETURN r +END FUNCTION + +FUNCTION make_symbol(name: String): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.symbol + r->val_symbol := get_symbol(name) + RETURN r +END FUNCTION + +FUNCTION make_string(s: String): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.string + r->val_string := s + RETURN r +END FUNCTION + +FUNCTION make_pair(car, cdr: POINTER TO Value): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.pair + r->val_pair.car := car + r->val_pair.cdr := cdr + RETURN r +END FUNCTION + +FUNCTION make_proc(proc: FUNCTION(args: Array): POINTER TO Value): POINTER TO Value + LET r: POINTER TO Value := NEW Value + r->type := Type.proc + r->val_proc := proc + RETURN r +END FUNCTION + +FUNCTION Value.to_string(IN self: Value): String + CASE self.type + WHEN Type.null DO + RETURN "null" + WHEN Type.boolean DO + RETURN IF self.val_boolean THEN "true" ELSE "false" + WHEN Type.number DO + RETURN str(self.val_number) + WHEN Type.symbol DO + IF VALID self.val_symbol AS p THEN + RETURN p->name + END IF + WHEN Type.string DO + RETURN self.val_string + WHEN Type.pair DO + RETURN "" + END CASE + RETURN "TODO" +END FUNCTION + +FUNCTION new_env(e: POINTER TO Environment): POINTER TO Environment + RETURN e +END FUNCTION + +FUNCTION parse2(s: String, INOUT i: Number): POINTER TO Value + WHILE i < s.length() AND s[i] = " " DO + INC i + END WHILE + CASE s[i] + WHEN "(" DO + INC i + VAR r: POINTER TO Value := NIL + VAR p: POINTER TO Value + TRY + LOOP + LET value: POINTER TO Value := parse2(s, INOUT i) + IF value = NIL THEN % TODO: remove this check when the exception handling works + EXIT LOOP + END IF + LET new: POINTER TO Value := NEW Value + new->type := Type.pair + new->val_pair.car := value + IF r = NIL THEN + r := new + ELSE + IF VALID p THEN + p->val_pair.cdr := new + END IF + END IF + p := new + END LOOP + EXCEPTION ParseEndOfInputException DO + % nothing + END TRY + IF s[i] = ")" THEN + INC i + END IF + RETURN r + WHEN "#" DO + LET start: Number := i + INC i + WHILE i < s.length() AND "a" <= s[i] <= "z" DO + INC i + END WHILE + LET t: String := s[start TO i-1] + IF t = "#t" THEN + RETURN make_boolean(TRUE) + ELSIF t = "#f" THEN + RETURN make_boolean(FALSE) + ELSE + ASSERT FALSE, t + END IF + WHEN "0" TO "9" DO + LET start: Number := i + INC i + WHILE i < s.length() AND "0" <= s[i] <= "9" DO + INC i + END WHILE + RETURN make_number(num(s[start TO i-1])) + WHEN "-", "+" DO + LET start: Number := i + INC i + WHILE i < s.length() AND "0" <= s[i] <= "9" DO + INC i + END WHILE + LET t: String := s[start TO i-1] + VAR m: regex.Match + IF regex.search(@"^[-+]?\d+$", t, OUT m) THEN + RETURN make_number(num(t)) + ELSE + RETURN make_symbol(t) + END IF + WHEN "a" TO "z", "!", "$", "%", "&", "*", "/", ":", "<", "=", ">", "?", "~", "_", "^" DO + LET start: Number := i + INC i + WHILE i < s.length() AND ("a" <= s[i] <= "z" OR "0" <= s[i] <= "9" OR s[i] = "!" OR s[i] = "$" OR s[i] = "%" OR s[i] = "&" OR s[i] = "*" OR s[i] = "/" OR s[i] = ":" OR s[i] = "<" OR s[i] = "=" OR s[i] = ">" OR s[i] = "?" OR s[i] = "~" OR s[i] = "_" OR s[i] = "^" OR s[i] = "." OR s[i] = "+" OR s[i] = "-") DO + INC i + END WHILE + RETURN make_symbol(s[start TO i-1]) + WHEN "\"" DO + INC i + LET start: Number := i + WHILE i < s.length() AND s[i] # "\"" DO + INC i + END WHILE + IF i < s.length() THEN + INC i + END IF + RETURN make_string(s[start TO i-2]) +% ELSE +% RAISE SyntaxErrorException + END CASE + RETURN NIL %RAISE ParseEndOfInputException +END FUNCTION + +FUNCTION parse(s: String): POINTER TO Value + VAR i: Number := 0 + RETURN parse2(s, INOUT i) +END FUNCTION + +FUNCTION read(f: io.File): POINTER TO Value + VAR s: String + IF NOT io.readLine(f, OUT s) THEN + sys.exit(0) + END IF + RETURN parse(s) +END FUNCTION + +FUNCTION eval_special(e: POINTER TO Value, env: POINTER TO Environment): POINTER TO Value + IF VALID e, e->val_pair.car AS car THEN + IF car->type # Type.symbol THEN + RETURN NIL + END IF + IF VALID car->val_symbol AS sym THEN + CASE sym->name + WHEN "let" DO + LET env2: POINTER TO Environment := new_env(env) + IF VALID e->val_pair.cdr AS bindings THEN + ASSERT bindings->type = Type.pair + ELSE + ASSERT FALSE, e + END IF + END CASE + ELSE + ASSERT FALSE, car + END IF + END IF + RETURN NIL +END FUNCTION + +FUNCTION eval2(e: POINTER TO Value, env: POINTER TO Environment): POINTER TO Value + IF VALID e THEN + CASE e->type + WHEN Type.boolean, Type.number, Type.string DO + RETURN e + WHEN Type.symbol DO + IF VALID env, e->val_symbol AS s THEN + RETURN env->names[s->name] + END IF + WHEN Type.pair DO + LET r: POINTER TO Value := eval_special(e, env) + IF r # NIL THEN + RETURN r + ELSIF VALID e->val_pair.car AS car THEN + LET proc: POINTER TO Value := eval2(car, env) + VAR args: Array + VAR a: POINTER TO Value := e->val_pair.cdr + LOOP + VAR next: POINTER TO Value := NIL % FIXME: shouldn't need init here + IF VALID a THEN + VAR arg: Value := Value() + IF VALID eval2(a->val_pair.car, env) AS q THEN + valueCopy(arg, q) + ELSE + arg.type := Type.null + END IF + args.append(arg) + next := a->val_pair.cdr + ELSE + EXIT LOOP + END IF + a := next + END LOOP + IF VALID proc THEN + CASE proc->type + WHEN Type.pair DO + % ... + WHEN Type.proc DO + LET f: FUNCTION(args: Array): POINTER TO Value := proc->val_proc % TODO: should be able to call this directly + RETURN f(args) + WHEN OTHERS DO + print("can't call \(proc->type)") + END CASE + END IF + ELSE + print("can't call nil") + END IF + WHEN OTHERS DO + print("can't eval \(e->type)") + END CASE + RETURN make_string("TODO") + ELSE + RETURN NIL + END IF +END FUNCTION + +FUNCTION eval(e: POINTER TO Value): POINTER TO Value + RETURN eval2(e, g_env) +END FUNCTION + +FUNCTION repr(e: POINTER TO Value): String + IF VALID e THEN + CASE e->type + WHEN Type.null DO + RETURN "null" + WHEN Type.boolean DO + RETURN IF e->val_boolean THEN "#t" ELSE "#f" + WHEN Type.number DO + RETURN str(e->val_number) + WHEN Type.symbol DO + IF VALID e->val_symbol AS p THEN + RETURN p->name + END IF + WHEN Type.string DO + RETURN "\"\(e->val_string)\"" + WHEN Type.pair DO + VAR p: POINTER TO Value := e + VAR r: String := "(" + LOOP + VAR next: POINTER TO Value := NIL + IF VALID p THEN + IF r.length() > 1 THEN + r.append(" ") + END IF + r.append(repr(p->val_pair.car)) + IF VALID p->val_pair.cdr AS q THEN + IF q->type = Type.pair THEN + next := q + ELSE + r.append(" . \(repr(q))") + EXIT LOOP + END IF + END IF + ELSE + EXIT LOOP + END IF + p := next + END LOOP + r.append(")") + RETURN r + WHEN Type.proc DO + RETURN "\(e->val_proc)" + WHEN OTHERS DO + ASSERT FALSE, e->type + END CASE + END IF + RETURN "repr not valid" +END FUNCTION + +FUNCTION repl() + LOOP + print(repr(eval(read(io.stdin)))) + %print(repr(read(io.stdin))) + END LOOP +END FUNCTION + +FUNCTION multiply(args: Array): POINTER TO Value + RETURN make_number(args[0].val_number * args[1].val_number) +END FUNCTION + +FUNCTION add(args: Array): POINTER TO Value + RETURN make_number(args[0].val_number + args[1].val_number) +END FUNCTION + +FUNCTION abs(args: Array): POINTER TO Value + RETURN make_number(math.abs(args[0].val_number)) +END FUNCTION + +FUNCTION test_repr() + LET y: POINTER TO Value := make_pair(make_string("foo"), NIL) + LET x: POINTER TO Value := make_pair(y, y) + ASSERT repr(x) = "((\"foo\") \"foo\")" +END FUNCTION + +g_env->names["*"] := make_proc(multiply) +g_env->names["+"] := make_proc(add) +g_env->names["abs"] := make_proc(abs) + +BEGIN MAIN + test_repr() + %repl() +END MAIN diff --git a/samples/mandelbrot/mandelbrot.neon b/samples/mandelbrot/mandelbrot.neon new file mode 100644 index 0000000000..7f5dd9ec20 --- /dev/null +++ b/samples/mandelbrot/mandelbrot.neon @@ -0,0 +1,126 @@ +%| + | File: mandelbrot + | + | Mandelbrot set generator. + |% + +IMPORT math +IMPORT multiarray +IMPORT sdl + +TYPE Rect IS sdl.Rect + +CONSTANT MAX_ITERATIONS: Number := 36 + +FUNCTION rect(x, y, w, h: Number): sdl.Rect + VAR r: sdl.Rect := Rect() + r.x := x + r.y := y + r.w := w + r.h := h + RETURN r +END FUNCTION + +TYPE Colour IS RECORD + r: Number + g: Number + b: Number +END RECORD + +VAR bitmap: sdl.Surface +VAR bmpren: sdl.Renderer +VAR done: multiarray.ArrayBoolean2D +VAR palette: Array +VAR line: Number +VAR stride: Number + +FUNCTION handle(e: sdl.Event, INOUT quit: Boolean) + CASE e.type + WHEN sdl.SDL_QUIT DO + quit := TRUE + END CASE +END FUNCTION + +FUNCTION render(ren: sdl.Renderer) + LET tex: sdl.Texture := sdl.CreateTextureFromSurface(ren, bitmap) + sdl.RenderCopy(ren, tex, sdl.NullRect, sdl.NullRect) + sdl.RenderPresent(ren) + sdl.DestroyTexture(tex) +END FUNCTION + +FUNCTION update() + IF line >= 480 THEN + EXIT FUNCTION + END IF + VAR x: Number := 0 + WHILE x < 640 DO + IF NOT done[line][x] THEN + LET p_re: Number := (x - 420) / 200 + LET p_im: Number := (line - 240) / 200 + VAR z_re, z_im: Number := 0 + VAR n: Number := 0 + LOOP + LET re2: Number := z_re * z_re + LET im2: Number := z_im * z_im + IF re2 + im2 < 4 AND n < MAX_ITERATIONS THEN + z_im := 2 * z_re * z_im + p_im + z_re := re2 - im2 + p_re + INC n + ELSE + EXIT LOOP + END IF + END LOOP + sdl.SetRenderDrawColor(bmpren, palette[n].r, palette[n].g, palette[n].b, 0) + IF stride > 1 THEN + sdl.RenderFillRect(bmpren, rect(x, line, stride, stride)) + ELSE + sdl.RenderDrawPoint(bmpren, x, line) + END IF + done[line][x] := TRUE + END IF + x := x + stride + END WHILE + line := line + stride + IF line >= 480 THEN + %print("done stride \(stride)") + IF stride > 1 THEN + stride := stride / 2 + line := 0 + END IF + END IF +END FUNCTION + +BEGIN MAIN + sdl.Init(sdl.INIT_VIDEO) + LET win: sdl.Window := sdl.CreateWindow("Hello World!", 100, 100, 640, 480, sdl.WINDOW_SHOWN) + LET winren: sdl.Renderer := sdl.CreateRenderer(win, -1, sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC) + bitmap := sdl.CreateRGBSurface(0, 640, 480, 32, 0, 0, 0, 0) + bmpren := sdl.CreateSoftwareRenderer(bitmap) + done := multiarray.makeBoolean2D(480, 640) + FOR i := 0 TO MAX_ITERATIONS-1 DO + LET x: Number := i MOD (MAX_ITERATIONS/6) * 255 / (MAX_ITERATIONS/6) + CASE math.floor(i/(MAX_ITERATIONS/6)) + WHEN 0 DO palette[i] := Colour(0, x, 255) + WHEN 1 DO palette[i] := Colour(0, 255, 255-x) + WHEN 2 DO palette[i] := Colour(x, 255, 0) + WHEN 3 DO palette[i] := Colour(255, 255-x, 0) + WHEN 4 DO palette[i] := Colour(255, 0, x) + WHEN 5 DO palette[i] := Colour(255-x, 0, 255) + END CASE + END FOR + palette[MAX_ITERATIONS] := Colour(0, 0, 0) + line := 0 + stride := 64 + VAR quit: Boolean := FALSE + WHILE NOT quit DO + VAR e: sdl.Event + WHILE sdl.PollEvent(OUT e) DO + handle(e, INOUT quit) + END WHILE + render(winren) + update() + END WHILE + sdl.DestroyRenderer(winren) + sdl.DestroyWindow(win) + sdl.Quit() +END MAIN diff --git a/samples/nd.proj/.gitignore b/samples/nd.proj/.gitignore new file mode 100644 index 0000000000..72e8ffc0db --- /dev/null +++ b/samples/nd.proj/.gitignore @@ -0,0 +1 @@ +* diff --git a/samples/nd.proj/Languages.txt b/samples/nd.proj/Languages.txt new file mode 100644 index 0000000000..9408291935 --- /dev/null +++ b/samples/nd.proj/Languages.txt @@ -0,0 +1,122 @@ +Format: 1.52 + +# This is the Natural Docs languages file for this project. If you change +# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change +# something for all your projects, edit the Languages.txt in Natural Docs' +# Config directory instead. + + +# You can prevent certain file extensions from being scanned like this: +# Ignore Extensions: [extension] [extension] ... + + +#------------------------------------------------------------------------------- +# SYNTAX: +# +# Unlike other Natural Docs configuration files, in this file all comments +# MUST be alone on a line. Some languages deal with the # character, so you +# cannot put comments on the same line as content. +# +# Also, all lists are separated with spaces, not commas, again because some +# languages may need to use them. +# +# Language: [name] +# Alter Language: [name] +# Defines a new language or alters an existing one. Its name can use any +# characters. If any of the properties below have an add/replace form, you +# must use that when using Alter Language. +# +# The language Shebang Script is special. It's entry is only used for +# extensions, and files with those extensions have their shebang (#!) lines +# read to determine the real language of the file. Extensionless files are +# always treated this way. +# +# The language Text File is also special. It's treated as one big comment +# so you can put Natural Docs content in them without special symbols. Also, +# if you don't specify a package separator, ignored prefixes, or enum value +# behavior, it will copy those settings from the language that is used most +# in the source tree. +# +# Extensions: [extension] [extension] ... +# [Add/Replace] Extensions: [extension] [extension] ... +# Defines the file extensions of the language's source files. You can +# redefine extensions found in the main languages file. You can use * to +# mean any undefined extension. +# +# Shebang Strings: [string] [string] ... +# [Add/Replace] Shebang Strings: [string] [string] ... +# Defines a list of strings that can appear in the shebang (#!) line to +# designate that it's part of the language. You can redefine strings found +# in the main languages file. +# +# Ignore Prefixes in Index: [prefix] [prefix] ... +# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ... +# +# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ... +# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ... +# Specifies prefixes that should be ignored when sorting symbols in an +# index. Can be specified in general or for a specific topic type. +# +#------------------------------------------------------------------------------ +# For basic language support only: +# +# Line Comments: [symbol] [symbol] ... +# Defines a space-separated list of symbols that are used for line comments, +# if any. +# +# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ... +# Defines a space-separated list of symbol pairs that are used for block +# comments, if any. +# +# Package Separator: [symbol] +# Defines the default package separator symbol. The default is a dot. +# +# [Topic Type] Prototype Enders: [symbol] [symbol] ... +# When defined, Natural Docs will attempt to get a prototype from the code +# immediately following the topic type. It stops when it reaches one of +# these symbols. Use \n for line breaks. +# +# Line Extender: [symbol] +# Defines the symbol that allows a prototype to span multiple lines if +# normally a line break would end it. +# +# Enum Values: [global|under type|under parent] +# Defines how enum values are referenced. The default is global. +# global - Values are always global, referenced as 'value'. +# under type - Values are under the enum type, referenced as +# 'package.enum.value'. +# under parent - Values are under the enum's parent, referenced as +# 'package.value'. +# +# Perl Package: [perl package] +# Specifies the Perl package used to fine-tune the language behavior in ways +# too complex to do in this file. +# +#------------------------------------------------------------------------------ +# For full language support only: +# +# Full Language Support: [perl package] +# Specifies the Perl package that has the parsing routines necessary for full +# language support. +# +#------------------------------------------------------------------------------- + +# The following languages are defined in the main file, if you'd like to alter +# them: +# +# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python, +# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile, +# ActionScript, ColdFusion, R, Fortran + +# If you add a language that you think would be useful to other developers +# and should be included in Natural Docs by default, please e-mail it to +# languages [at] naturaldocs [dot] org. + + +Language: Neon + + Extension: neon + Shebang String: neon + Line Comment: % + Block Comment: %| |% + Function Prototype Ender: \n diff --git a/samples/nd.proj/Menu.txt b/samples/nd.proj/Menu.txt new file mode 100644 index 0000000000..e053dc180c --- /dev/null +++ b/samples/nd.proj/Menu.txt @@ -0,0 +1,73 @@ +Format: 1.52 + + +# You can add a title and sub-title to your menu like this: +# Title: [project name] +# SubTitle: [subtitle] + +# You can add a footer to your documentation like this: +# Footer: [text] +# If you want to add a copyright notice, this would be the place to do it. + +# You can add a timestamp to your documentation like one of these: +# Timestamp: Generated on month day, year +# Timestamp: Updated mm/dd/yyyy +# Timestamp: Last updated mon day +# +# m - One or two digit month. January is "1" +# mm - Always two digit month. January is "01" +# mon - Short month word. January is "Jan" +# month - Long month word. January is "January" +# d - One or two digit day. 1 is "1" +# dd - Always two digit day. 1 is "01" +# day - Day with letter extension. 1 is "1st" +# yy - Two digit year. 2006 is "06" +# yyyy - Four digit year. 2006 is "2006" +# year - Four digit year. 2006 is "2006" + + +# -------------------------------------------------------------------------- +# +# Cut and paste the lines below to change the order in which your files +# appear on the menu. Don't worry about adding or removing files, Natural +# Docs will take care of that. +# +# You can further organize the menu by grouping the entries. Add a +# "Group: [name] {" line to start a group, and add a "}" to end it. +# +# You can add text and web links to the menu by adding "Text: [text]" and +# "Link: [name] ([URL])" lines, respectively. +# +# The formatting and comments are auto-generated, so don't worry about +# neatness when editing the file. Natural Docs will clean it up the next +# time it is run. When working with groups, just deal with the braces and +# forget about the indentation and comments. +# +# -------------------------------------------------------------------------- + + +File: Neon Samples (samples.txt) +File: 99-bottles (99-bottles/99-bottles.neon) +File: cal (cal/cal.neon) +File: fizzbuzz (fizzbuzz/fizzbuzz.neon) +File: flappy (flappy/flappy.neon) +File: forth (forth/forth.neon) +File: hello (hello/hello.neon) +File: httpd (httpd/httpd.neon) +File: life (life/life.neon) +File: lisp (lisp/lisp.neon) +File: mandelbrot (mandelbrot/mandelbrot.neon) +File: net-services (net-services/net-services.txt) +File: othello (othello/othello.neon) +File: sieve (sieve/sieve.neon) +File: spacedebris (spacedebris/spacedebris.neon) +File: sudoku (sudoku/sudoku.neon) +File: tetris (tetris/tetris.neon) + +Group: Index { + + Index: Everything + File Index: Files + Function Index: Functions + } # Group: Index + diff --git a/samples/net-services/daytime.neon b/samples/net-services/daytime.neon new file mode 100644 index 0000000000..b263bfc537 --- /dev/null +++ b/samples/net-services/daytime.neon @@ -0,0 +1,18 @@ +IMPORT net +IMPORT datetime + +LET server: net.Socket := net.tcpSocket() +server.listen(10013) +LOOP + VAR read: Array := [server] + VAR write: Array := [] + VAR error: Array := [] + IF net.select(INOUT read, INOUT write, INOUT error, -1) THEN + IF server IN read THEN + LET s: net.Socket := server.accept() + LET ts: String := datetime.now().toString() & "\r\n" + s.send(ts.toBytes()) + s.close() + END IF + END IF +END LOOP diff --git a/samples/net-services/discard.neon b/samples/net-services/discard.neon new file mode 100644 index 0000000000..5193d8fab1 --- /dev/null +++ b/samples/net-services/discard.neon @@ -0,0 +1,30 @@ +IMPORT net + +VAR clients: Array := [] + +LET server: net.Socket := net.tcpSocket() +server.listen(10009) +LOOP + VAR read: Array := [server] + FOREACH c OF clients DO + read.append(c) + END FOREACH + VAR write: Array := [] + VAR error: Array := [] + IF net.select(INOUT read, INOUT write, INOUT error, -1) THEN + IF server IN read THEN + clients.append(server.accept()) + END IF + FOR i := clients.size()-1 TO 0 STEP -1 DO + IF clients[i] IN read THEN + LET buf: Bytes := clients[i].recv(1000) + IF buf.size() > 0 THEN + % do nothing + ELSE + clients[i].close() + clients[i TO i] := [] % TODO: need an array remove method + END IF + END IF + END FOR + END IF +END LOOP diff --git a/samples/net-services/echo.neon b/samples/net-services/echo.neon new file mode 100644 index 0000000000..a907a8c9bb --- /dev/null +++ b/samples/net-services/echo.neon @@ -0,0 +1,30 @@ +IMPORT net + +VAR clients: Array := [] + +LET server: net.Socket := net.tcpSocket() +server.listen(10007) +LOOP + VAR read: Array := [server] + FOREACH c OF clients DO + read.append(c) + END FOREACH + VAR write: Array := [] + VAR error: Array := [] + IF net.select(INOUT read, INOUT write, INOUT error, -1) THEN + IF server IN read THEN + clients.append(server.accept()) + END IF + FOR i := clients.size()-1 TO 0 STEP -1 DO + IF clients[i] IN read THEN + LET buf: Bytes := clients[i].recv(1000) + IF buf.size() > 0 THEN + clients[i].send(buf) + ELSE + clients[i].close() + clients[i TO i] := [] % TODO: need an array remove method + END IF + END IF + END FOR + END IF +END LOOP diff --git a/samples/net-services/net-services.txt b/samples/net-services/net-services.txt new file mode 100644 index 0000000000..69c41f6970 --- /dev/null +++ b/samples/net-services/net-services.txt @@ -0,0 +1,3 @@ +Title: net-services + +Simple network services. diff --git a/samples/othello/othello.neon b/samples/othello/othello.neon new file mode 100644 index 0000000000..5978a4c550 --- /dev/null +++ b/samples/othello/othello.neon @@ -0,0 +1,322 @@ +%| + | File: othello + | + | Othello game. + | + | The Othello playing engine used here was originally written by + | Roemer B. Lievaart for the 1987 International Obfuscated C Code Contest. + |% + +IMPORT curses + +CONSTANT Directions: Array := [-1, -11, -10, -9, 1, 11, 10, 9] +CONSTANT Corners: Array := [11, 18, 81, 88] +CONSTANT NearCorners: Array := [22, 27, 72, 77] + +VAR Level: Number := 2 +VAR Moves: Array +VAR Board: Array +VAR BestPos: Number + +FUNCTION initialize() + Board.resize(1600) + FOR i := 0 TO Board.size()-1 STEP 100 DO + FOR j := 0 TO 99 DO + IF j < 11 OR j > 88 OR (j + 1) MOD 10 < 2 THEN + Board[i+j] := 3 + ELSE + Board[i+j] := 0 + END IF + END FOR + END FOR + Moves := [] +END FUNCTION + +FUNCTION updateBoard() + FOR i := 0 TO 99 DO + IF i < 11 OR i > 88 OR (i + 1) MOD 10 < 2 THEN + Board[i] := 3 + ELSE + Board[i] := 0 + END IF + END FOR + Board[44] := 1 + Board[55] := 1 + Board[45] := 2 + Board[54] := 2 + VAR color: Number := 2 + FOREACH m OF Moves DO + IF m # 0 THEN + LET dummy: Boolean := validMove(0, m, color, 0) + END IF + color := 3 - color + END FOREACH +END FUNCTION + +FUNCTION dupeBoard(base: Number) + VAR p: Number := base + 111 + FOR i := base+11 TO base+88 DO + Board[p] := Board[i] + INC p + END FOR +END FUNCTION + +FUNCTION validMove(base, move, color, delta: Number): Boolean + LET p: Number := base + move + IF Board[p] # 0 THEN + RETURN FALSE + END IF + LET other: Number := 3 - color + VAR ok: Boolean := FALSE + FOR d := 7 TO 0 STEP -1 DO + LET dd: Number := Directions[d] + VAR n: Number := p + dd + WHILE Board[n] = other DO + n := n + dd + END WHILE + IF Board[n] = color AND n-dd # p THEN + IF NOT ok THEN + ok := TRUE + dupeBoard(base) + END IF + WHILE n # p DO + n := n - dd + Board[n+delta] := color + END WHILE + END IF + END FOR + RETURN ok +END FUNCTION + +FUNCTION search(d, base, color, depth: Number, flag: Boolean, minscore, maxscore: Number): Number + LET other: Number := 3 - color + IF d > depth THEN + VAR s: Number := 0 + FOR c := 0 TO 3 DO + VAR q: Number := Board[base + Corners[c]] + IF q = color THEN + s := s + 300 + ELSIF q = other THEN + s := s - 300 + ELSE + q := Board[base + NearCorners[c]] + IF q = color THEN + s := s - 50 + ELSIF q = other THEN + s := s + 50 + END IF + END IF + END FOR + RETURN s + END IF + VAR best: Number := 0 + VAR bestscore: Number := -9000 + IF d < depth-1 THEN + bestscore := minscore + END IF + VAR n: Number := 0 + FOR p := 11 TO 88 DO + IF validMove(base, p, color, 100) THEN + INC n + LET ss: Number := -search(d+1, base+100, other, depth, FALSE, -maxscore, -bestscore) + IF ss > bestscore THEN + best := p + BestPos := p + bestscore := ss + IF ss >= maxscore OR ss >= 8003 THEN + RETURN ss + END IF + END IF + END IF + END FOR + IF n = 0 THEN + best := 0 + IF flag THEN + FOR z := base+11 TO base+88 DO + IF Board[z] = color THEN + INC n + ELSIF Board[z] = other THEN + DEC n + END IF + END FOR + IF n > 0 THEN + RETURN n + 8000 + ELSE + RETURN n - 8000 + END IF + END IF + dupeBoard(base) + bestscore := -search(d+1, base+100, other, depth, TRUE, -maxscore, -bestscore) + END IF + BestPos := best + IF d >= depth-1 THEN + RETURN bestscore + 8*n + ELSE + RETURN bestscore + END IF +END FUNCTION + +FUNCTION init() + curses.clear() + curses.attrset(curses.COLOR_PAIR(0)) + curses.mvaddstr(0, 60, "Othello") + curses.attrset(curses.COLOR_PAIR(1)) + FOR j := 0 TO 22 DO + IF j MOD 3 = 2 THEN + curses.mvaddstr(j, 4, "-----+----+----+----+----+----+----+-----") + ELSE + curses.mvaddstr(j, 4, " | | | | | | | ") + END IF + END FOR +END FUNCTION + +FUNCTION displayLabels(x, y: Number) + curses.attrset(curses.COLOR_PAIR(0)) + FOR i := 1 TO 8 DO + curses.attrset(curses.COLOR_PAIR(IF y = i THEN 2 ELSE 0)) + curses.mvaddstr((i-1)*3, 0, " " & chr(ord("A") + i - 1) & " ") + curses.attrset(curses.COLOR_PAIR(IF x = i THEN 2 ELSE 0)) + curses.mvaddstr(23, (i-1)*5+5, " " & chr(ord("1") + i - 1) & " ") + END FOR +END FUNCTION + +FUNCTION displayBoard(): Boolean + VAR black, white: Number := 0 + VAR any: Boolean := FALSE + FOR j := 0 TO 7 DO + FOR i := 0 TO 7 DO + LET move: Number := 10*(j+1) + (i+1) + LET b: Number := Board[move] + CASE b + WHEN 0 DO + IF validMove(0, move, 2, 100) THEN + any := TRUE + END IF + WHEN 1 DO + INC black + WHEN 2 DO + INC white + END CASE + curses.attrset(curses.COLOR_PAIR(1+b)) + FOR k := 0 TO 1 DO + curses.mvaddstr(3*j+k, 5*i+5, " ") + END FOR + END FOR + END FOR + displayLabels(0, 0) + curses.attrset(curses.COLOR_PAIR(0)) + curses.mvaddstr(5, 50, "Black: \(black)") + curses.mvaddstr(7, 50, "White: \(white)") + RETURN any +END FUNCTION + +FUNCTION makeMove(move: Number) + VAR valid: Boolean := validMove(0, move, 2, 0) + Moves.append(move) + LET any: Boolean := displayBoard() + curses.refresh() + LET score: Number := search(0, 0, 1, Level, FALSE, -9000, 9000) + valid := validMove(0, BestPos, 1, 0) + Moves.append(BestPos) +END FUNCTION + +FUNCTION kibitz(): Number + LET score: Number := search(0, 0, 2, Level, FALSE, -9000, 9000) + IF validMove(0, BestPos, 2, 100) THEN + RETURN BestPos + END IF + RETURN 0 +END FUNCTION + +FUNCTION play_again(): Boolean + curses.attrset(curses.COLOR_PAIR(0)) + curses.mvaddstr(10, 50, "Game over. Again?") + LOOP + LET k: Number := curses.getch() + IF k >= 0 THEN + CASE chr(k) + WHEN "n", "q" DO + RETURN FALSE + WHEN "y" DO + RETURN TRUE + END CASE + END IF + END LOOP +END FUNCTION + +FUNCTION main() + init() + VAR quit: Boolean := FALSE + WHILE NOT quit DO + initialize() + updateBoard() + VAR game_over: Boolean := FALSE + WHILE NOT game_over AND NOT quit DO + LET any: Boolean := displayBoard() + IF NOT any THEN + game_over := TRUE + FOR j := 0 TO 7 DO + FOR i := 0 TO 7 DO + IF validMove(0, 10*(j+1)+(i+1), 1, 100) THEN + game_over := FALSE + END IF + END FOR + END FOR + IF game_over THEN + EXIT WHILE + END IF + END IF + VAR x, y: Number := 0 + WHILE NOT quit DO + displayLabels(x, y) + LET k: Number := curses.getch() + IF k >= 0 THEN + CASE chr(k) + WHEN "a" TO "h" DO + y := k - ord("a") + 1 + WHEN "1" TO "8" DO + x := k - ord("1") + 1 + WHEN "k" DO + makeMove(kibitz()) + EXIT WHILE + WHEN "p" DO + IF NOT any THEN + makeMove(0) + EXIT WHILE + END IF + WHEN "q" DO + quit := TRUE + WHEN "u" DO + IF Moves.size() >= 2 THEN + Moves.resize(Moves.size() - 2) + updateBoard() + EXIT WHILE + END IF + END CASE + END IF + IF x # 0 AND y # 0 THEN + LET move: Number := 10*y + x + IF validMove(0, move, 2, 100) THEN + makeMove(move) + EXIT WHILE + ELSE + x := 0 + y := 0 + EXIT WHILE + END IF + END IF + END WHILE + END WHILE + quit := quit OR NOT play_again() + END WHILE +END FUNCTION + +curses.initscr() +curses.start_color() +curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_GREEN) +curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE) +curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_BLACK) +curses.noecho() +curses.curs_set(0) +main() +curses.endwin() diff --git a/samples/rain.neon b/samples/rain/rain.neon similarity index 84% rename from samples/rain.neon rename to samples/rain/rain.neon index 7e1227abba..b0b2157c2e 100644 --- a/samples/rain.neon +++ b/samples/rain/rain.neon @@ -41,39 +41,34 @@ FUNCTION next_j(j: Number): Number new_j := j - 1 END IF - %|IF curses.has_colors() THEN + IF curses.has_colors() THEN VAR z: Number z := random.uint32() MOD 3 - chtype color = COLOR_PAIR(z); + VAR color: Number := curses.COLOR_PAIR(z) - if (z) - color |= A_BOLD; + %|if (z) + color |= A_BOLD;|% - attrset(color); - }|% + curses.attrset(color) + END IF RETURN new_j END FUNCTION FUNCTION main() VAR x, y, j, r, c, k: Number - VAR xpos, ypos: Array + VAR xpos, ypos: Array := [] curses.initscr() - %|if (has_colors()) - { - int bg = COLOR_BLACK; + IF curses.has_colors() THEN + VAR bg: Number := curses.COLOR_BLACK - start_color(); + curses.start_color() -#if defined(NCURSES_VERSION) || (defined(PDC_BUILD) && PDC_BUILD > 3000) - if (use_default_colors() == OK) - bg = -1; -#endif - init_pair(1, COLOR_BLUE, bg); - init_pair(2, COLOR_CYAN, bg); - }|% + curses.init_pair(1, curses.COLOR_BLUE, bg) + curses.init_pair(2, curses.COLOR_CYAN, bg) + END IF curses.nl() curses.noecho() @@ -81,12 +76,12 @@ FUNCTION main() curses.timeout(0) %curses.keypad(stdscr, TRUE) - r := curses.LINES() - 4 - c := curses.COLS() - 4 + r := curses.Lines() - 4 + c := curses.Cols() - 4 - FOR j := 4 TO 0 STEP -1 DO - xpos[j] := random.uint32() MOD c + 2 - ypos[j] := random.uint32() MOD r + 2 + FOR d := 4 TO 0 STEP -1 DO + xpos[d] := random.uint32() MOD c + 2 + ypos[d] := random.uint32() MOD r + 2 END FOR j := 0 @@ -108,9 +103,9 @@ FUNCTION main() j := next_j(j) curses.mvaddstr(ypos[j] - 2, xpos[j], "-") - curses.mvaddstr(ypos[j] - 1, xpos[j] - 1, "/ \") + curses.mvaddstr(ypos[j] - 1, xpos[j] - 1, "/ \\") curses.mvaddstr(ypos[j], xpos[j] - 2, "| O |") - curses.mvaddstr(ypos[j] + 1, xpos[j] - 1, "\ /") + curses.mvaddstr(ypos[j] + 1, xpos[j] - 1, "\\ /") curses.mvaddstr(ypos[j] + 2, xpos[j], "-") j := next_j(j) diff --git a/samples/samples.txt b/samples/samples.txt new file mode 100644 index 0000000000..9e85b46706 --- /dev/null +++ b/samples/samples.txt @@ -0,0 +1,20 @@ +Title: Neon Samples + +Neon samples. + +- <99-bottles> - Print the lyrics to the "99 Bottles of Beer" song +- - Print monthly and yearly calendars +- - Implementation of the FizzBuzz problem +- - Implementation of a "Flappy Bird" clone (SDL) +- - Implementation of the Forth programming language +- - Hello World +- - HTTP server +- - Conway's Game of Life (SDL) +- - Implementation of a Scheme-like Lisp dialect +- - Mandelbrot set generator (SDL) +- - Simple network services (daytime, discard, echo) +- - Othello game (curses) +- - Sieve of Eratosthenes +- - Vector based space rocks game (SDL) +- - Sudoku game (curses) +- - Tetris game (curses) diff --git a/samples/sid.neon b/samples/sid/sid.neon similarity index 100% rename from samples/sid.neon rename to samples/sid/sid.neon diff --git a/samples/sieve/sieve.neon b/samples/sieve/sieve.neon new file mode 100644 index 0000000000..eb0b926dab --- /dev/null +++ b/samples/sieve/sieve.neon @@ -0,0 +1,32 @@ +%| + | File: sieve + | + | Compute prime numbers up to 100 with the Sieve of Eratosthenes. + |% + +CONSTANT Max: Number := 100 + +VAR sieve: Array := [] + +FOR i := 0 TO Max DO + sieve[i] := TRUE +END FOR + +sieve[0] := FALSE +sieve[1] := FALSE + +FOR i := 2 TO Max DO + IF sieve[i] THEN + VAR j: Number := 2 * i + WHILE j <= Max DO + sieve[j] := FALSE + j := j + i + END WHILE + END IF +END FOR + +FOR i := 0 TO sieve.size() - 1 DO + IF sieve[i] THEN + print(i.toString()) + END IF +END FOR diff --git a/samples/spacedebris/spacedebris.neon b/samples/spacedebris/spacedebris.neon new file mode 100644 index 0000000000..e7be629451 --- /dev/null +++ b/samples/spacedebris/spacedebris.neon @@ -0,0 +1,229 @@ +%| + | File: spacedebris + | + | Vector base space rocks game. + |% + +IMPORT math +IMPORT random +IMPORT sdl + +TYPE Rect IS sdl.Rect + +FUNCTION rect(x, y, w, h: Number): sdl.Rect + VAR r: sdl.Rect := Rect() + r.x := x + r.y := y + r.w := w + r.h := h + RETURN r +END FUNCTION + +TYPE Player IS RECORD + x: Number + y: Number + dx: Number + dy: Number + rot: Number + turn: Number + thrust: Boolean +END RECORD + +TYPE Rock IS RECORD + x: Number + y: Number + dx: Number + dy: Number + size: Number +END RECORD + +TYPE Shot IS RECORD + x: Number + y: Number + dx: Number + dy: Number + count: Number +END RECORD + +VAR player: Player +VAR rocks: Array +VAR shots: Array + +FUNCTION handle(e: sdl.Event, INOUT quit: Boolean) + CASE e.type + WHEN sdl.SDL_QUIT DO + quit := TRUE + WHEN sdl.SDL_KEYDOWN DO + CASE e.key.keysym.sym + WHEN sdl.SDLK_LEFT DO + player.turn := -0.1 + WHEN sdl.SDLK_RIGHT DO + player.turn := 0.1 + WHEN sdl.SDLK_SPACE DO + VAR shot: Shot := Shot() + shot.x := player.x + 20 * math.cos(player.rot) + shot.y := player.y + 20 * math.sin(player.rot) + shot.dx := player.dx + 2 * math.cos(player.rot) + shot.dy := player.dy + 2 * math.sin(player.rot) + shot.count := 100 + shots.append(shot) + WHEN sdl.SDLK_UP DO + player.thrust := TRUE + END CASE + WHEN sdl.SDL_KEYUP DO + CASE e.key.keysym.sym + WHEN sdl.SDLK_LEFT DO + player.turn := 0 + WHEN sdl.SDLK_RIGHT DO + player.turn := 0 + WHEN sdl.SDLK_UP DO + player.thrust := FALSE + END CASE + END CASE +END FUNCTION + +FUNCTION render(ren: sdl.Renderer) + sdl.SetRenderDrawColor(ren, 0, 0, 0, 255) + sdl.RenderClear(ren) + sdl.SetRenderDrawColor(ren, 255, 255, 255, 255) + sdl.RenderDrawLines(ren, [ + [player.x+15*math.cos(player.rot), player.y+15*math.sin(player.rot)], + [player.x+10*math.cos(player.rot+2.5), player.y+10*math.sin(player.rot+2.5)], + [player.x+7*math.cos(player.rot+2.7), player.y+7*math.sin(player.rot+2.7)], + [player.x+7*math.cos(player.rot-2.7), player.y+7*math.sin(player.rot-2.7)], + [player.x+10*math.cos(player.rot-2.5), player.y+10*math.sin(player.rot-2.5)], + [player.x+15*math.cos(player.rot), player.y+15*math.sin(player.rot)], + ]) + IF player.thrust THEN + sdl.RenderDrawLines(ren, [ + [player.x+8*math.cos(player.rot+2.8), player.y+8*math.sin(player.rot+2.8)], + [player.x+12*math.cos(player.rot+math.Pi), player.y+12*math.sin(player.rot+math.Pi)], + [player.x+8*math.cos(player.rot-2.8), player.y+8*math.sin(player.rot-2.8)], + ]) + END IF + FOREACH r OF rocks DO + CASE r.size + WHEN 3 DO + sdl.RenderDrawLines(ren, [ + [r.x+15, r.y+15], + [r.x+15, r.y-15], + [r.x-15, r.y-15], + [r.x-15, r.y+15], + [r.x+15, r.y+15], + ]) + WHEN 2 DO + sdl.RenderDrawLines(ren, [ + [r.x+10, r.y+10], + [r.x+10, r.y-10], + [r.x-10, r.y-10], + [r.x-10, r.y+10], + [r.x+10, r.y+10], + ]) + WHEN 1 DO + sdl.RenderDrawLines(ren, [ + [r.x+5, r.y+5], + [r.x+5, r.y-5], + [r.x-5, r.y-5], + [r.x-5, r.y+5], + [r.x+5, r.y+5], + ]) + END CASE + END FOREACH + FOREACH s OF shots DO + sdl.RenderFillRect(ren, rect(s.x-1, s.y-1, 3, 3)) + END FOREACH + sdl.RenderPresent(ren) +END FUNCTION + +FUNCTION update() + player.x := (player.x + player.dx) MOD 640 + player.y := (player.y + player.dy) MOD 480 + player.rot := player.rot + player.turn + IF player.thrust THEN + IF math.hypot(player.dx, player.dy) < 10 THEN + player.dx := player.dx + 0.05*math.cos(player.rot) + player.dy := player.dy + 0.05*math.sin(player.rot) + END IF + ELSE + player.dx := player.dx * 0.995 + player.dy := player.dy * 0.995 + END IF + VAR i: Number := 0 + WHILE i < rocks.size() DO + rocks[i].x := (rocks[i].x + rocks[i].dx) MOD 640 + rocks[i].y := (rocks[i].y + rocks[i].dy) MOD 480 + LET radius: Number := 5 * rocks[i].size + VAR bounds: sdl.Rect := rect(rocks[i].x-radius, rocks[i].y-radius, 2*radius, 2*radius) + VAR gone: Boolean := FALSE + VAR s: Number := 0 + WHILE s < shots.size() DO + IF bounds.x <= shots[s].x <= bounds.x+bounds.w AND bounds.y <= shots[s].y <= bounds.y+bounds.h THEN + IF rocks[i].size > 1 THEN + FOR j := 1 TO 2 DO + VAR rock: Rock := Rock() + rock.x := rocks[i].x + rock.y := rocks[i].y + rock.dx := random.uint32() MOD 10 / 5 - 1 + rock.dy := random.uint32() MOD 10 / 5 - 1 + rock.size := rocks[i].size - 1 + rocks.append(rock) + END FOR + END IF + gone := TRUE + shots[s].count := 0 + EXIT WHILE + END IF + INC s + END WHILE + IF gone THEN + rocks[i TO i] := [] + ELSE + INC i + END IF + END WHILE + i := 0 + WHILE i < shots.size() DO + shots[i].x := (shots[i].x + shots[i].dx) MOD 640 + shots[i].y := (shots[i].y + shots[i].dy) MOD 480 + DEC shots[i].count + IF shots[i].count > 0 THEN + INC i + ELSE + shots[i TO i] := [] + END IF + END WHILE +END FUNCTION + +BEGIN MAIN + sdl.Init(sdl.INIT_VIDEO) + LET win: sdl.Window := sdl.CreateWindow("Hello World!", 100, 100, 640, 480, sdl.WINDOW_SHOWN) + LET ren: sdl.Renderer := sdl.CreateRenderer(win, -1, sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC) + player.x := 320 + player.y := 240 + player.dx := 0 + player.dy := 0 + player.rot := 0 + player.turn := 0 + player.thrust := FALSE + FOR i := 0 TO 1 DO + VAR rock: Rock + rock.x := random.uint32() MOD 640 + rock.y := random.uint32() MOD 480 + rock.dx := random.uint32() MOD 10 / 5 - 1 + rock.dy := random.uint32() MOD 10 / 5 - 1 + rock.size := 3 + rocks.append(rock) + END FOR + VAR quit: Boolean := FALSE + WHILE NOT quit DO + VAR e: sdl.Event + WHILE sdl.PollEvent(OUT e) DO + handle(e, INOUT quit) + END WHILE + render(ren) + update() + END WHILE + sdl.DestroyRenderer(ren) + sdl.DestroyWindow(win) + sdl.Quit() +END MAIN diff --git a/samples/sudoku/sudoku.neon b/samples/sudoku/sudoku.neon new file mode 100644 index 0000000000..e9dcecc0b3 --- /dev/null +++ b/samples/sudoku/sudoku.neon @@ -0,0 +1,592 @@ +%| + | File: sudoku + | + | Sudoku solver and puzzle generator. + |% + +EXPORT solveSudoku +EXPORT makeSolution +EXPORT makePuzzle +EXPORT puzzleFilled +EXPORT findErrors + +IMPORT curses +IMPORT math +IMPORT multiarray +IMPORT random + +%| + | The dlx_* functions are an implementation of Knuth's "Dancing Links" + | technique to solve his "Algorithm X" (exact cover). + |% + +TYPE Node IS RECORD + index: Number + row: Number + column: POINTER TO Node + up: POINTER TO Node + down: POINTER TO Node + left: POINTER TO Node + right: POINTER TO Node + size: Number +END RECORD + +FUNCTION dlx_cover(c: POINTER TO Node) + IF VALID c, c->left AS l, c->right AS r THEN + r->left := c->left + l->right := c->right + VAR i: POINTER TO Node := c->down + WHILE i # c DO + IF VALID i AS ii THEN + VAR j: POINTER TO Node := ii->right + WHILE j # i DO + IF VALID j AS jj, jj->up AS u, jj->down AS d THEN + d->up := jj->up + u->down := jj->down + IF VALID jj->column AS col THEN + DEC col->size + ELSE + ASSERT FALSE + END IF + j := jj->right + ELSE + ASSERT FALSE + END IF + END WHILE + i := ii->down + ELSE + ASSERT FALSE + END IF + END WHILE + ELSE + ASSERT FALSE + END IF +END FUNCTION + +FUNCTION dlx_uncover(c: POINTER TO Node) + IF VALID c, c->left AS l, c->right AS r THEN + VAR i: POINTER TO Node := c->up + WHILE i # c DO + IF VALID i AS ii THEN + VAR j: POINTER TO Node := ii->left + WHILE j # i DO + IF VALID j AS jj, jj->up AS u, jj->down AS d THEN + IF VALID jj->column AS col THEN + INC col->size + ELSE + ASSERT FALSE + END IF + d->up := j + u->down := j + j := jj->left + END IF + END WHILE + i := ii->up + ELSE + ASSERT FALSE + END IF + END WHILE + r->left := c + l->right := c + ELSE + ASSERT FALSE + END IF +END FUNCTION + +FUNCTION dlx_search(head: POINTER TO Node, INOUT solution: Array, k: Number, INOUT solutions: Array>, maxsolutions: Number): Boolean + IF VALID head THEN + IF head->right = head THEN + solutions.append(solution) + RETURN solutions.size() >= maxsolutions + END IF + VAR c: POINTER TO Node := NIL + VAR s: Number := 99999 + VAR d: POINTER TO Node := head->right + WHILE d # head DO + IF VALID d AS dd THEN + IF dd->size = 0 THEN + RETURN FALSE + END IF + IF dd->size < s THEN + s := dd->size + c := dd + END IF + d := dd->right + ELSE + ASSERT FALSE + END IF + END WHILE + IF VALID c THEN + dlx_cover(c) + VAR r: POINTER TO Node := c->down + WHILE r # c DO + IF VALID r AS rr THEN + solution[k] := rr->row + VAR j: POINTER TO Node := rr->right + WHILE j # r DO + IF VALID j AS jj THEN + dlx_cover(jj->column) + j := jj->right + ELSE + ASSERT FALSE + END IF + END WHILE + IF dlx_search(head, INOUT solution, k+1, INOUT solutions, maxsolutions) THEN + RETURN TRUE + END IF + j := rr->left + WHILE j # r DO + IF VALID j AS jj THEN + dlx_uncover(jj->column) + j := jj->left + ELSE + ASSERT FALSE + END IF + END WHILE + r := rr->down + ELSE + ASSERT FALSE + END IF + END WHILE + dlx_uncover(c) + ELSE + ASSERT FALSE + END IF + ELSE + ASSERT FALSE + END IF + RETURN FALSE +END FUNCTION + +FUNCTION dlx_solve(matrix: Array>, skip: Number, maxsolutions: Number): Array> + VAR columns: Array := [] + FOR i := 0 TO matrix[0].size()-1 DO + columns[i] := NEW Node + END FOR + FOR i := 0 TO columns.size()-1 DO + IF VALID columns[i] AS c THEN + c->index := i + c->up := c + c->down := c + IF i >= skip THEN + IF i-1 >= skip THEN + c->left := columns[i-1] + END IF + IF i+1 < columns.size() THEN + c->right := columns[i+1] + END IF + ELSE + c->left := c + c->right := c + END IF + c->size := 0 + ELSE + ASSERT FALSE + END IF + END FOR + FOR i := 0 TO matrix.size()-1 DO + VAR last: POINTER TO Node := NIL + FOR j := 0 TO matrix[i].size()-1 DO + IF VALID columns[j] AS c THEN + IF matrix[i, j] THEN + LET node: POINTER TO Node := NEW Node + node->row := i + node->column := c + node->up := c->up + node->down := c + IF VALID last THEN + node->left := last + node->right := last->right + IF VALID last->right AS lr THEN + lr->left := node + ELSE + ASSERT FALSE + END IF + last->right := node + ELSE + node->left := node + node->right := node + END IF + IF VALID c->up AS cu THEN + cu->down := node + ELSE + ASSERT FALSE + END IF + c->up := node + INC c->size + last := node + END IF + ELSE + ASSERT FALSE + END IF + END FOR + END FOR + LET head: POINTER TO Node := NEW Node + head->right := columns[skip] + head->left := columns[LAST] + IF VALID columns[skip] AS cs THEN + cs->left := head + ELSE + ASSERT FALSE + END IF + IF VALID columns[LAST] AS cl THEN + cl->right := head + ELSE + ASSERT FALSE + END IF + VAR solution: Array := [] + VAR solutions: Array> := [] + LET r: Boolean := dlx_search(head, INOUT solution, 0, INOUT solutions, maxsolutions) + RETURN solutions +END FUNCTION + +FUNCTION print_grid(grid: multiarray.ArrayNumber2D) + FOREACH row OF grid DO + VAR s: String := "" + FOREACH n OF row DO + IF n > 0 THEN + s.append("\(n) ") + ELSE + s.append(" ") + END IF + END FOREACH + print(s) + END FOREACH +END FUNCTION + +%| + | Function: solveSudoku + | + | This solver uses the above implementation to efficiently solve + | a Sudoku puzzle. + |% + +FUNCTION solveSudoku(INOUT grid: multiarray.ArrayNumber2D): Number + VAR mat: Array> := [] + VAR rinfo: Array> + FOR i := 0 TO 8 DO + FOR j := 0 TO 8 DO + LET g: Number := grid[i, j] - 1 + IF g >= 0 THEN + VAR row: Array := [] + row.resize(324) + row[i*9+j] := TRUE + row[9*9+i*9+g] := TRUE + row[9*9*2+j*9+g] := TRUE + row[9*9*3+(math.floor(i/3)*3+math.floor(j/3))*9+g] := TRUE + mat.append(row) + rinfo.append([i, j, g+1]) + ELSE + FOR n := 0 TO 8 DO + VAR row: Array := [] + row.resize(324) + row[i*9+j] := TRUE + row[9*9+i*9+n] := TRUE + row[9*9*2+j*9+n] := TRUE + row[9*9*3+(math.floor(i/3)*3+math.floor(j/3))*9+n] := TRUE + mat.append(row) + rinfo.append([i, j, n+1]) + END FOR + END IF + END FOR + END FOR + LET solutions: Array> := dlx_solve(mat, 0, 2) + IF solutions.size() > 0 THEN + LET r: Array := solutions[0] + FOREACH i OF r DO + grid[rinfo[i, 0], rinfo[i, 1]] := rinfo[i, 2] + END FOREACH + END IF + RETURN solutions.size() +END FUNCTION + +FUNCTION shuffle(INOUT a: Array) + FOR i := a.size()-1 TO 1 STEP -1 DO + LET j: Number := random.uint32() MOD (i+1) + LET temp: Number := a[i] + a[i] := a[j] + a[j] := temp + END FOR +END FUNCTION + +%| + | Function: makeSolution + | + | This function makes a random solution to a Sudoku puzzle. + | + | It does this by filling in the first row randomly, and then calling + | the solver. Due to the way the solver works, this produces a bias + | which can be observed by looking at the first few numbers in the + | second row. The first three numbers will be the lowest numbers that + | don't appear in the first three cells of the first row, in order. + | An improvement would be to remove this bias. + |% + +FUNCTION makeSolution(): multiarray.ArrayNumber2D + VAR r: multiarray.ArrayNumber2D := multiarray.makeNumber2D(9, 9) + r[0] := [1, 2, 3, 4, 5, 6, 7, 8, 9] + shuffle(INOUT r[0]) + LET n: Number := solveSudoku(INOUT r) + RETURN r +END FUNCTION + +%| + | Function: makePuzzle + | + | This function makes a puzzle (a partially filled grid), given a + | solution (completely filled grid) and a number of cells to leave + | filled. + | + | This works by removing pairs of numbers (symmetric around 180 degree + | rotation), checking the resulting puzzle to see that it still has only + | one solution, and repeating. If removing a pair of numbers results in + | a puzzle with more than one solution, the numbers are left in place and + | another pair is tried. + | + | This function has considerable opportunity for improvements in efficiency. + | Also, it makes no attempt to estimate the "difficulty" of the resulting + | puzzle. + |% + +FUNCTION makePuzzle(solution: multiarray.ArrayNumber2D, filled: Number, callback: FUNCTION(n: Number)): multiarray.ArrayNumber2D + VAR r: multiarray.ArrayNumber2D := solution + VAR places: Array := [] + FOR i := 0 TO 9*9-1 DO + places[i] := i + END FOR + shuffle(INOUT places) + VAR n: Number := 9*9 + FOREACH p OF places DO + callback(n) + LET i: Number := math.floor(p / 9) + LET j: Number := p MOD 9 + IF r[i, j] = 0 THEN + NEXT FOREACH + END IF + VAR t: multiarray.ArrayNumber2D := r + t[i, j] := 0 + t[8-i, 8-j] := 0 + LET s: Number := solveSudoku(INOUT t) + IF s = 1 THEN + r[i, j] := 0 + r[8-i, 8-j] := 0 + n := n - 2 + IF n <= filled THEN + RETURN r + END IF + END IF + END FOREACH + RETURN r +END FUNCTION + +FUNCTION puzzleFilled(grid: multiarray.ArrayNumber2D): Boolean + FOREACH row OF grid DO + FOREACH cell OF row DO + IF cell = 0 THEN + RETURN FALSE + END IF + END FOREACH + END FOREACH + RETURN TRUE +END FUNCTION + +FUNCTION findErrors(grid: multiarray.ArrayNumber2D): multiarray.ArrayBoolean2D + VAR r: multiarray.ArrayBoolean2D := multiarray.makeBoolean2D(9, 9) + VAR rows: multiarray.ArrayNumber2D := multiarray.makeNumber2DValue(9, 10, -1) + VAR cols: multiarray.ArrayNumber2D := multiarray.makeNumber2DValue(9, 10, -1) + VAR cels: multiarray.ArrayNumber2D := multiarray.makeNumber2DValue(9, 10, -1) + FOR i := 0 TO 8 DO + FOR j := 0 TO 8 DO + LET n: Number := grid[i, j] + IF n > 0 THEN + % Check for duplicate number in row. + LET x: Number := rows[i, n] + IF x >= 0 THEN + r[i, j] := TRUE + r[i, x] := TRUE + END IF + rows[i, n] := j + % Check for duplicate number in column. + LET y: Number := cols[j, n] + IF y >= 0 THEN + r[i, j] := TRUE + r[y, j] := TRUE + END IF + cols[j, n] := i + % Check for duplicate number in cell. + LET c: Number := math.floor(i / 3)*3 + math.floor(j / 3) + LET z: Number := cels[c, n] + IF z >= 0 THEN + r[i, j] := TRUE + r[math.floor(c / 3)*3 + math.floor(z / 3), (c MOD 3)*3 + (z MOD 3)] := TRUE + END IF + cels[c, n] := (i MOD 3)*3 + (j MOD 3) + END IF + END FOR + END FOR + RETURN r +END FUNCTION + +FUNCTION init() + curses.initscr() + curses.start_color() + curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_RED) + curses.noecho() + curses.curs_set(0) + curses.keypad(curses.stdscr(), TRUE) + curses.mvaddstr( 0, 50, "Sudoku") + curses.mvaddstr( 4, 40, "hjkl/arrows - move cursor") + curses.mvaddstr( 5, 40, "digits - enter number in cell") + curses.mvaddstr( 6, 40, "0/space - clear cell") + curses.mvaddstr( 7, 40, "c - clear puzzle") + curses.mvaddstr( 8, 40, "n - new puzzle") + curses.mvaddstr( 9, 40, "s - solve puzzle") + curses.mvaddstr(10, 40, "q - quit") + curses.mvaddstr(11, 40, "u - undo last move") +END FUNCTION + +FUNCTION draw_grid(grid: multiarray.ArrayNumber2D, cx, cy: Number) + LET errors: multiarray.ArrayBoolean2D := findErrors(grid) + curses.mvaddstr(0, 0, "+---+---+---+---+---+---+---+---+---+") + VAR any_errors: Boolean := FALSE + FOR i := 0 TO 8 DO + curses.mvaddstr(1+2*i, 0, "| | | |") + FOR j := 0 TO 8 DO + IF i = cy AND j = cx THEN + curses.attrset(curses.A_REVERSE) + ELSIF errors[i, j] THEN + curses.attrset(curses.COLOR_PAIR(1)) + any_errors := TRUE + END IF + IF grid[i, j] > 0 THEN + curses.mvaddstr(1+2*i, 1+4*j, " \(grid[i, j]) ") + ELSE + curses.mvaddstr(1+2*i, 1+4*j, " ") + END IF + curses.attrset(curses.A_NORMAL) + END FOR + IF i MOD 3 = 2 THEN + curses.mvaddstr(2+2*i, 0, "+---+---+---+---+---+---+---+---+---+") + ELSE + curses.mvaddstr(2+2*i, 0, "+ + + +") + END IF + END FOR + IF NOT any_errors AND puzzleFilled(grid) THEN + curses.mvaddstr(15, 40, "Puzzle completed.") + ELSE + curses.move(15, 40) + curses.clrtoeol() + END IF +END FUNCTION + +FUNCTION popup_show(message: String): curses.Window + LET w: curses.Window := curses.newwin(5, 40, 10, 20) + curses.wborder(w, 0, 0, 0, 0, 0, 0, 0, 0) + curses.mvwaddstr(w, 2, (40-message.length())/2, message) + curses.wrefresh(w) + RETURN w +END FUNCTION + +FUNCTION popup_close(w: curses.Window) + curses.delwin(w) + curses.redrawwin(curses.stdscr()) +END FUNCTION + +VAR new_puzzle_popup: curses.Window + +FUNCTION update_new_puzzle_popup(n: Number) + curses.mvwaddstr(new_puzzle_popup, 2, 30, "\(n)") + curses.wrefresh(new_puzzle_popup) +END FUNCTION + +BEGIN MAIN + init() + %LET solution: multiarray.ArrayNumber2D := makeSolution() + %print_grid(solution) + %LET puzzle: multiarray.ArrayNumber2D := makePuzzle(solution, 21) + %print_grid(puzzle) + VAR start: multiarray.ArrayNumber2D := multiarray.makeNumber2D(9, 9) + start := [ + % https://en.wikipedia.org/wiki/Sudoku + [5,3,0,0,7,0,0,0,0], + [6,0,0,1,9,5,0,0,0], + [0,9,8,0,0,0,0,6,0], + [8,0,0,0,6,0,0,0,3], + [4,0,0,8,0,3,0,0,1], + [7,0,0,0,2,0,0,0,6], + [0,6,0,0,0,0,2,8,0], + [0,0,0,4,1,9,0,0,5], + [0,0,0,0,8,0,0,7,9], + ] + VAR known_start: Boolean := TRUE + VAR moves: Array := [] + VAR grid: multiarray.ArrayNumber2D := start + VAR quit: Boolean := FALSE + VAR cx, cy: Number := 0 + WHILE NOT quit DO + draw_grid(grid, cx, cy) + LET k: Number := curses.getch() + IF k >= 0 THEN + CASE k + WHEN curses.KEY_DOWN DO + cy := (cy + 1) MOD 9 + WHEN curses.KEY_LEFT DO + cx := (cx - 1) MOD 9 + WHEN curses.KEY_RIGHT DO + cx := (cx + 1) MOD 9 + WHEN curses.KEY_UP DO + cy := (cy - 1) MOD 9 + WHEN OTHERS DO + CASE chr(k) + WHEN " ", "0" DO + IF start[cy, cx] = 0 THEN + moves.append(grid) + grid[cy, cx] := 0 + END IF + WHEN "1" TO "9" DO + IF start[cy, cx] = 0 THEN + moves.append(grid) + grid[cy, cx] := k - ord("0") + END IF + WHEN "c" DO + moves := [] + known_start := FALSE + start := multiarray.makeNumber2D(9, 9) + grid := start + WHEN "h" DO + cx := (cx - 1) MOD 9 + WHEN "j" DO + cy := (cy + 1) MOD 9 + WHEN "k" DO + cy := (cy - 1) MOD 9 + WHEN "l" DO + cx := (cx + 1) MOD 9 + WHEN "n" DO + new_puzzle_popup := popup_show("Creating new puzzle... ") + moves := [] + LET solution: multiarray.ArrayNumber2D := makeSolution() + start := makePuzzle(solution, 30, update_new_puzzle_popup) + known_start := TRUE + grid := start + popup_close(new_puzzle_popup) + WHEN "q" DO + quit := TRUE + WHEN "s" DO + LET popup: curses.Window := popup_show("Solving puzzle...") + moves.append(grid) + IF known_start THEN + grid := start + END IF + LET n: Number := solveSudoku(INOUT grid) + popup_close(popup) + WHEN "u" DO + IF moves.size() > 0 THEN + grid := moves[LAST] + moves.resize(moves.size()-1) + END IF + END CASE + END CASE + END IF + END WHILE + curses.endwin() +END MAIN diff --git a/samples/tetris/tetris.neon b/samples/tetris/tetris.neon new file mode 100644 index 0000000000..5e5956ded9 --- /dev/null +++ b/samples/tetris/tetris.neon @@ -0,0 +1,351 @@ +%| + | File: tetris + | + | Tetris game in curses. + |% + +IMPORT curses +IMPORT math +IMPORT multiarray +IMPORT random +IMPORT time + +CONSTANT BOARD_WIDTH: Number := 10 +CONSTANT BOARD_HEIGHT: Number := 20 + +TYPE Piece IS RECORD + grid: multiarray.ArrayNumber2D + rot: Number +END RECORD + +CONSTANT Pieces: Array := [ + Piece(), + Piece([[0,0,0,0], % I + [1,1,1,1], + [0,0,0,0], + [0,0,0,0]], 2), + Piece([[0,0,0,0], % J + [1,1,1,0], + [0,0,1,0], + [0,0,0,0]], 4), + Piece([[0,0,0,0], % L + [1,1,1,0], + [1,0,0,0], + [0,0,0,0]], 4), + Piece([[1,1,0,0], % O + [1,1,0,0], + [0,0,0,0], + [0,0,0,0]], 1), + Piece([[0,1,1,0], % S + [1,1,0,0], + [0,0,0,0], + [0,0,0,0]], 2), + Piece([[0,0,0,0], % T + [1,1,1,0], + [0,1,0,0], + [0,0,0,0]], 4), + Piece([[1,1,0,0], % Z + [0,1,1,0], + [0,0,0,0], + [0,0,0,0]], 2) +] + +VAR Board: multiarray.ArrayNumber2D := multiarray.makeNumber2D(BOARD_HEIGHT, BOARD_WIDTH) +VAR BoardWindow: curses.Window +VAR NextWindow: curses.Window +VAR StatsWindow: curses.Window + +FUNCTION rotated(p, r: Number): multiarray.ArrayNumber2D + VAR q: multiarray.ArrayNumber2D := multiarray.makeNumber2D(4, 4) + CASE Pieces[p].rot + WHEN 1 DO + q := Pieces[p].grid + WHEN 2 DO + CASE r + WHEN 0 DO + q := Pieces[p].grid + WHEN 1 DO + IF p = 1 THEN + FOR y := 0 TO 3 DO + FOR x := 0 TO 3 DO + q[y, x] := Pieces[p].grid[x, y] + END FOR + END FOR + ELSE + FOR y := 0 TO 2 DO + FOR x := 0 TO 2 DO + q[y, x] := Pieces[p].grid[2-x, y] + END FOR + END FOR + END IF + END CASE + WHEN 4 DO + CASE r + WHEN 0 DO + q := Pieces[p].grid + WHEN 1 DO + FOR y := 0 TO 2 DO + FOR x := 0 TO 2 DO + q[y, x] := Pieces[p].grid[2-x, y] + END FOR + END FOR + WHEN 2 DO + FOR y := 0 TO 2 DO + FOR x := 0 TO 2 DO + q[y, x] := Pieces[p].grid[2-y, 2-x] + END FOR + END FOR + WHEN 3 DO + FOR y := 0 TO 2 DO + FOR x := 0 TO 2 DO + q[y, x] := Pieces[p].grid[x, 2-y] + END FOR + END FOR + END CASE + END CASE + RETURN q +END FUNCTION + +FUNCTION plot(on: Boolean, w: curses.Window, p, y, x, r: Number) + IF on THEN + curses.wattrset(w, curses.COLOR_PAIR(p)) + ELSE + curses.wattrset(w, 0) + END IF + LET q: multiarray.ArrayNumber2D := rotated(p, r) + FOR j := 0 TO 3 DO + FOR i := 0 TO 3 DO + IF q[j, i] # 0 THEN + curses.mvwaddstr(w, y+j, 1+(x+i)*2, " ") + END IF + END FOR + END FOR +END FUNCTION + +FUNCTION piece_fits(p, x, y, r: Number): Boolean + LET q: multiarray.ArrayNumber2D := rotated(p, r) + FOR j := 0 TO 3 DO + FOR i := 0 TO 3 DO + IF q[j, i] # 0 THEN + CASE x + i + WHEN < 0, >= BOARD_WIDTH DO + RETURN FALSE + END CASE + IF y + j >= BOARD_HEIGHT THEN + RETURN FALSE + END IF + IF Board[y+j, x+i] # 0 THEN + RETURN FALSE + END IF + END IF + END FOR + END FOR + RETURN TRUE +END FUNCTION + +FUNCTION drop_piece(p, x, y, r: Number) + LET q: multiarray.ArrayNumber2D := rotated(p, r) + FOR j := 0 TO 3 DO + FOR i := 0 TO 3 DO + IF q[j, i] # 0 THEN + Board[y+j, x+i] := p + END IF + END FOR + END FOR + plot(TRUE, BoardWindow, p, y, x, r) +END FUNCTION + +FUNCTION row_complete(j: Number): Boolean + FOREACH x OF Board[j] DO + IF x = 0 THEN + RETURN FALSE + END IF + END FOREACH + RETURN TRUE +END FUNCTION + +FUNCTION remove_rows(INOUT score, rows: Number) + VAR count: Number := 0 + VAR j: Number := BOARD_HEIGHT-1 + WHILE j >= 0 DO + IF row_complete(j) THEN + INC count + FOR k := j TO 1 STEP -1 DO + Board[k] := Board[k-1] + FOR i := 0 TO BOARD_WIDTH-1 DO + curses.wattrset(BoardWindow, curses.COLOR_PAIR(Board[k, i])) + curses.mvwaddstr(BoardWindow, k, 1+2*i, " ") + END FOR + END FOR + ELSE + DEC j + END IF + END WHILE + score := score + count*count + rows := rows + count +END FUNCTION + +FUNCTION init() + curses.mvaddstr( 0, 30, @" _ _ _____ _ _ ") + curses.mvaddstr( 1, 30, @"| \ | | ___ ___ _ __ |_ _|__| |_ _ __(_)___ ") + curses.mvaddstr( 2, 30, @"| \| |/ _ \/ _ \| '_ \ | |/ _ \ __| '__| / __|") + curses.mvaddstr( 3, 30, @"| |\ | __/ (_) | | | | | | __/ |_| | | \__ \") + curses.mvaddstr( 4, 30, @"|_| \_|\___|\___/|_| |_| |_|\___|\__|_| |_|___/") + curses.mvaddstr( 6, 40, @"rotate left rotate right ") + curses.mvaddstr( 7, 40, @" \ / ") + curses.mvaddstr( 8, 40, @" u o ") + curses.mvaddstr( 9, 40, @"move left - j k l - move right ") + curses.mvaddstr(10, 40, @" | ") + curses.mvaddstr(11, 40, @" move down ") + curses.mvaddstr(13, 40, @" space - drop ") + curses.refresh() + + BoardWindow := curses.newwin(21, 2+2*10, 2, 5) + curses.wtimeout(BoardWindow, 100) + + NextWindow := curses.newwin(6, 10, 14, 30) + curses.box(NextWindow, 0, 0) + curses.mvwaddstr(NextWindow, 0, 2, " Next ") + + StatsWindow := curses.newwin(0, 0, 14, 40) +END FUNCTION + +FUNCTION play_again(): Boolean + curses.wattrset(BoardWindow, 0) + curses.mvwaddstr(BoardWindow, 8, 5, " ") + curses.mvwaddstr(BoardWindow, 9, 5, " G A M E ") + curses.mvwaddstr(BoardWindow, 10, 5, " ") + curses.mvwaddstr(BoardWindow, 11, 5, " O V E R ") + curses.mvwaddstr(BoardWindow, 12, 5, " ") + curses.mvwaddstr(BoardWindow, 13, 5, " again? ") + curses.mvwaddstr(BoardWindow, 14, 5, " ") + LOOP + LET k: Number := curses.wgetch(BoardWindow) + IF k >= 0 THEN + CASE chr(k) + WHEN "n", "q" DO + RETURN FALSE + WHEN "y" DO + RETURN TRUE + END CASE + END IF + END LOOP +END FUNCTION + +FUNCTION main() + init() + VAR quit: Boolean := FALSE + WHILE NOT quit DO + curses.werase(BoardWindow) + curses.wborder(BoardWindow, 0, 0, 32, 0, 0, 0, 0, 0) + FOR j := 0 TO BOARD_HEIGHT-1 DO + FOR i := 0 TO BOARD_WIDTH-1 DO + Board[j, i] := 0 + END FOR + END FOR + VAR p, x, y, r: Number := 0 + VAR rows, score: Number := 0 + VAR next: Number := 1 + random.uint32() MOD 7 + VAR drop_time: Number := 1 + VAR new_piece: Boolean := TRUE + VAR last_drop: Number := 0 + VAR game_over: Boolean := FALSE + WHILE NOT game_over AND NOT quit DO + IF new_piece THEN + p := next + next := 1 + random.uint32() MOD 7 + x := 3 + y := 0 + r := 0 + IF NOT piece_fits(p, x, y, r) THEN + game_over := TRUE + END IF + plot(FALSE, NextWindow, p, 1, 0, 0) + IF NOT game_over THEN + plot(TRUE, NextWindow, next, 1, 0, 0) + END IF + curses.wrefresh(NextWindow) + new_piece := FALSE + END IF + drop_time := 0.9 ^ math.floor(rows / 8) + LET old_x: Number := x + LET old_y: Number := y + LET old_r: Number := r + plot(TRUE, BoardWindow, p, y, x, r) + curses.mvwaddstr(StatsWindow, 1, 5, "Score: \(score)") + curses.mvwaddstr(StatsWindow, 3, 5, " Rows: \(rows)") + curses.wrefresh(StatsWindow) + LOOP + LET now: Number := time.now() + IF now - last_drop >= drop_time THEN + IF NOT piece_fits(p, x, y+1, r) THEN + new_piece := TRUE + ELSE + INC y + END IF + last_drop := now + EXIT LOOP + END IF + LET k: Number := curses.wgetch(BoardWindow) + IF k >= 0 THEN + CASE chr(k) + WHEN " " DO + WHILE piece_fits(p, x, y+1, r) DO + INC y + END WHILE + new_piece := TRUE + EXIT LOOP + WHEN "j" DO + DEC x + EXIT LOOP + WHEN "k" DO + IF piece_fits(p, x, y+1, r) THEN + INC y + last_drop := now + ELSE + new_piece := TRUE + END IF + EXIT LOOP + WHEN "l" DO + INC x + EXIT LOOP + WHEN "u" DO + r := (r - 1) MOD Pieces[p].rot + EXIT LOOP + WHEN "o" DO + r := (r + 1) MOD Pieces[p].rot + EXIT LOOP + WHEN "q" DO + quit := TRUE + EXIT LOOP + END CASE + END IF + END LOOP + IF NOT piece_fits(p, x, y, r) THEN + y := old_y + x := old_x + r := old_r + END IF + plot(FALSE, BoardWindow, p, old_y, old_x, old_r) + IF new_piece THEN + drop_piece(p, x, y, r) + END IF + remove_rows(INOUT score, INOUT rows) + END WHILE + quit := quit OR NOT play_again() + END WHILE +END FUNCTION + +curses.initscr() +curses.start_color() +curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) +curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_BLUE) +curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_MAGENTA) +curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_YELLOW) +curses.init_pair(5, curses.COLOR_BLACK, curses.COLOR_GREEN) +curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE) +curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_RED) +curses.noecho() +curses.curs_set(0) +main() +curses.endwin() diff --git a/scripts/extract_errors.py b/scripts/extract_errors.py index e1cc0c27ac..7333db27b0 100644 --- a/scripts/extract_errors.py +++ b/scripts/extract_errors.py @@ -2,13 +2,18 @@ import re import sys -r = re.compile(r'\berror\((\d+),.*?"(.*?)"') +r = re.compile(r'\berror\w*\((\d+),.*?"(.*?)"') errors = {} for fn in glob.glob("src/*.cpp"): with open(fn) as f: + enable = True for s in f: - if " error(" in s: + if s.startswith("#if 0"): + enable = False + elif s.startswith("#endif"): + enable = True + elif enable and (" error(" in s or " error_a(" in s or " error2(" in s): m = r.search(s) assert m is not None, s number = int(m.group(1)) diff --git a/scripts/make_thunks.py b/scripts/make_thunks.py index b63964e15d..c5f9910f97 100644 --- a/scripts/make_thunks.py +++ b/scripts/make_thunks.py @@ -1,141 +1,309 @@ +import os import re import sys VALUE = "VALUE" REF = "REF" +OUT = "OUT" -AstFromCpp = { - "void": ("TYPE_NOTHING", VALUE), - "Cell*": ("TYPE_GENERIC", REF), - "void*": ("TYPE_POINTER", VALUE), - "bool": ("TYPE_BOOLEAN", VALUE), - "bool*": ("TYPE_BOOLEAN", REF), +AstFromNeon = { + None: ("TYPE_NOTHING", VALUE), + "POINTER": ("TYPE_POINTER", VALUE), + "Cell": ("TYPE_GENERIC", VALUE), + "Boolean": ("TYPE_BOOLEAN", VALUE), "Number": ("TYPE_NUMBER", VALUE), - "Number*": ("TYPE_NUMBER", REF), - "std::string": ("TYPE_STRING", VALUE), - "std::string&": ("TYPE_STRING", VALUE), - "std::string*": ("TYPE_STRING", REF), - "std::vector": ("TYPE_ARRAY_NUMBER", VALUE), - "std::vector&": ("TYPE_ARRAY_NUMBER", VALUE), - "std::vector": ("TYPE_ARRAY_STRING", VALUE), - "std::vector&": ("TYPE_ARRAY_STRING", VALUE), + "INOUT Number": ("TYPE_NUMBER", REF), + "OUT Number": ("TYPE_NUMBER", REF), + "String": ("TYPE_STRING", VALUE), + "INOUT String": ("TYPE_STRING", REF), + "OUT String": ("TYPE_STRING", OUT), + "Bytes": ("TYPE_BYTES", VALUE), + "INOUT Bytes": ("TYPE_BYTES", REF), + "OUT Bytes": ("TYPE_BYTES", OUT), + "Array": ("TYPE_GENERIC", VALUE), + "INOUT Array": ("TYPE_GENERIC", REF), + "Array": ("TYPE_ARRAY_NUMBER", VALUE), + "Array": ("TYPE_ARRAY_STRING", VALUE), + "INOUT Array": ("TYPE_ARRAY_STRING", REF), + "OUT Array": ("TYPE_ARRAY_STRING", OUT), + "Dictionary": ("TYPE_GENERIC", VALUE), } -CppFromAst = { +CppFromAstParam = { ("TYPE_NOTHING", VALUE): "void", + ("TYPE_GENERIC", VALUE): "Cell", ("TYPE_GENERIC", REF): "Cell *", + ("TYPE_GENERIC", OUT): "Cell", ("TYPE_POINTER", VALUE): "void *", + ("TYPE_POINTER", REF): "Cell **", + ("TYPE_POINTER", OUT): "Cell *", + ("TYPE_BOOLEAN", VALUE): "bool", + ("TYPE_BOOLEAN", REF): "bool *", + ("TYPE_NUMBER", VALUE): "Number", + ("TYPE_NUMBER", REF): "Number *", + ("TYPE_STRING", VALUE): "const std::string &", # TODO: should be utf8string I think + ("TYPE_STRING", REF): "utf8string *", + ("TYPE_STRING", OUT): "utf8string", + ("TYPE_BYTES", VALUE): "const std::string &", + ("TYPE_BYTES", REF): "utf8string *", # TODO: should be std::string + ("TYPE_BYTES", OUT): "std::string", + ("TYPE_ARRAY_NUMBER", VALUE): "std::vector", + ("TYPE_ARRAY_STRING", VALUE): "std::vector", + ("TYPE_ARRAY_STRING", REF): "std::vector", + ("TYPE_ARRAY_STRING", OUT): "std::vector", +} + +CppFromAstReturn = { + ("TYPE_NOTHING", VALUE): "void", + ("TYPE_GENERIC", VALUE): "Cell", + ("TYPE_GENERIC", REF): "Cell *", + ("TYPE_POINTER", VALUE): "void *", + ("TYPE_POINTER", REF): "Cell **", ("TYPE_BOOLEAN", VALUE): "bool", ("TYPE_BOOLEAN", REF): "bool *", ("TYPE_NUMBER", VALUE): "Number", ("TYPE_NUMBER", REF): "Number *", ("TYPE_STRING", VALUE): "std::string", ("TYPE_STRING", REF): "std::string *", + ("TYPE_BYTES", VALUE): "std::string", + ("TYPE_BYTES", REF): "std::string *", ("TYPE_ARRAY_NUMBER", VALUE): "std::vector", - ("TYPE_ARRAY_STRING", VALUE): "std::vector", + ("TYPE_ARRAY_STRING", VALUE): "std::vector", + ("TYPE_ARRAY_STRING", REF): "std::vector *", } CppFromAstArg = { ("TYPE_POINTER", VALUE): "void *", + ("TYPE_POINTER", REF): "Cell **", + ("TYPE_POINTER", OUT): "Cell **", + ("TYPE_GENERIC", VALUE): "Cell &", ("TYPE_GENERIC", REF): "Cell *", + ("TYPE_GENERIC", OUT): "Cell *", ("TYPE_BOOLEAN", VALUE): "bool", ("TYPE_BOOLEAN", REF): "bool *", ("TYPE_NUMBER", VALUE): "Number", ("TYPE_NUMBER", REF): "Number *", - ("TYPE_STRING", VALUE): "const std::string &", - ("TYPE_STRING", REF): "std::string *", + ("TYPE_STRING", VALUE): "const std::string &", # TODO: should be utf8string I think + ("TYPE_STRING", REF): "utf8string *", + ("TYPE_STRING", OUT): "utf8string *", + ("TYPE_BYTES", VALUE): "const std::string &", + ("TYPE_BYTES", REF): "utf8string *", # TODO: should be std::string + ("TYPE_BYTES", OUT): "std::string *", ("TYPE_ARRAY_NUMBER", VALUE): "const std::vector &", - ("TYPE_ARRAY_STRING", VALUE): "const std::vector &", + ("TYPE_ARRAY_STRING", VALUE): "const std::vector &", + ("TYPE_ARRAY_STRING", REF): "std::vector *", + ("TYPE_ARRAY_STRING", OUT): "std::vector *", } CellField = { ("TYPE_POINTER", VALUE): "address()", + ("TYPE_POINTER", REF): "address()->address()", ("TYPE_BOOLEAN", VALUE): "boolean()", ("TYPE_BOOLEAN", REF): "address()->boolean()", ("TYPE_NUMBER", VALUE): "number()", ("TYPE_NUMBER", REF): "address()->number()", - ("TYPE_STRING", VALUE): "string()", - ("TYPE_STRING", REF): "address()->string()", + ("TYPE_STRING", VALUE): "string().str()", + ("TYPE_STRING", REF): "address()->string_for_write()", + ("TYPE_STRING", OUT): "address()->string_for_write()", + ("TYPE_BYTES", VALUE): "string().str()", + ("TYPE_BYTES", REF): "address()->string_for_write()", + ("TYPE_BYTES", OUT): "address()->string_for_write()", ("TYPE_ARRAY_NUMBER", VALUE): "array()", ("TYPE_ARRAY_STRING", VALUE): "array()", + ("TYPE_ARRAY_STRING", REF): "array()", + ("TYPE_ARRAY_STRING", OUT): "array()", } ArrayElementField = { ("TYPE_ARRAY_NUMBER", VALUE): "number()", ("TYPE_ARRAY_STRING", VALUE): "string()", + ("TYPE_ARRAY_STRING", REF): "string()", } +def parse_params(paramstr): + r = [] + if not paramstr: + return r + def next_token(s, i): + m = re.match(r"\s*([\w<>]+|.)", s[i:]) + if m is None: + return "", i + t = m.group(1) + return t, i + m.end(0) + i = 0 + while True: + mode = "" + names = [] + while True: + t, i = next_token(paramstr, i) + assert re.match(r"[\w_]+$", t), (paramstr, i, t) + if t in ["OUT", "INOUT"]: + mode = t + t, i = next_token(paramstr, i) + names.append(t) + t, i = next_token(paramstr, i) + if t != ",": + break + assert t == ":", (paramstr, i, t) + t, i = next_token(paramstr, i) + typename = t + m = re.match(r"Array<(.*)>$", typename) + if m is not None and m.group(1) not in ["Number", "String"]: + typename = "Array" + r.extend(zip(["{} {}".format(mode, typename).strip()] * len(names), names)) + t, i = next_token(paramstr, i) + if t != ",": + break + return r + constants = dict() +enums = dict() +variables = dict() functions = dict() +exceptions = [] for fn in sys.argv[1:]: - with open(fn) as f: - prefix = "" - for s in f: - if s.startswith("//") or s.startswith("static "): - continue - m = re.match(r"namespace (\w+) {", s) - if m is not None and m.group(1) != "rtl": - prefix = m.group(1) + "." - if s.startswith("} // namespace"): - prefix = "" - m = re.match(r"extern\s+const\s+(\w+)\s+([\w$]+)\s*=", s) - if m is not None: - ctype = m.group(1) - name = m.group(2) - assert ctype in ["Number"] - constants[name] = [name, ctype, "new ConstantNumberExpression(rtl::{})".format(name)] - m = re.match(r"([\S\* ]+?)\s*([\w$]+)\((.*?)\)$", s) - if m is not None: - rtype = m.group(1).replace(" ", "") - name = prefix + m.group(2) - paramstr = m.group(3).split(",") - if name.startswith("rtl_"): - continue - assert rtype in ["void", "bool", "Number", "std::string", "std::vector", "std::vector", "void*"], rtype - params = [] - if paramstr[0]: - for arg in paramstr: - arg = arg.strip() - if arg.startswith("const"): - arg = arg[5:] - arg = re.sub(r"\w+$", "", arg) - arg = arg.replace(" ", "") - params.append(arg) - functions[name] = [name, AstFromCpp[rtype], [AstFromCpp[x] for x in params]] + if fn.endswith(".neon"): + prefix = os.path.basename(fn)[:-5] + "$" + with open(fn) as f: + in_enum = None + for s in f: + a = s.split() + if a[:1] == ["TYPE"]: + m = re.match("TYPE\s+(\w+)\s+IS\s+(\S+)\s*$", s) + assert m, s + name = m.group(1) + atype = m.group(2) + if atype == "POINTER": + AstFromNeon[name] = ("TYPE_POINTER", VALUE) + AstFromNeon["INOUT "+name] = ("TYPE_POINTER", REF) + AstFromNeon["OUT "+name] = ("TYPE_POINTER", OUT) + elif atype == "ENUM": + # TODO: Why is this TYPE_GENERIC? It should be TYPE_NUMBER. + AstFromNeon[name] = ("TYPE_GENERIC", VALUE) + AstFromNeon["INOUT "+name] = ("TYPE_GENERIC", REF) + enums[name] = [] + in_enum = name + else: + AstFromNeon[name] = ("TYPE_GENERIC", VALUE) + AstFromNeon["INOUT "+name] = ("TYPE_GENERIC", REF) + AstFromNeon["OUT "+name] = ("TYPE_GENERIC", OUT) + elif a[:3] == ["DECLARE", "NATIVE", "FUNCTION"]: + m = re.search(r"(\w+)\((.*?)\)(:\s*(\S+))?\s*$", s) + assert m is not None + name = prefix + m.group(1) + paramstr = m.group(2) + rtype = m.group(4) + params = parse_params(paramstr) + functions[name] = [ + name, + AstFromNeon[rtype], + rtype.split()[-1] if rtype is not None else None, + [AstFromNeon[x[0]] for x in params], + [x[0].split()[-1] for x in params], + [x[1] for x in params] + ] + elif a[:3] == ["DECLARE", "NATIVE", "CONSTANT"]: + m = re.search(r"(\w+)\s*:\s*(\S+)", s) + assert m is not None + name = prefix + m.group(1) + ntype = m.group(2) + assert ntype in ["Number", "String"] + ctype = ntype + if ctype == "String": + ctype = "std::string" + constants[name] = [name, ctype, "new Constant{}Expression(rtl::{})".format(ntype, name)] + elif a[:3] == ["DECLARE", "NATIVE", "VAR"]: + m = re.search(r"(\w+)\s*:\s*(\S+)", s) + assert m is not None + name = prefix + m.group(1) + ntype = m.group(2) + atype = { + "Array": "TYPE_ARRAY_STRING", + "File": "dynamic_cast(scope->lookupName(\"File\"))", + }[ntype] + variables[name] = [name, atype] + elif a[:2] == ["DECLARE", "EXCEPTION"]: + exceptions.append((prefix, a[2])) + elif in_enum: + if a[:2] == ["END", "ENUM"]: + in_enum = None + else: + enums[in_enum].append(a[0]) + else: + print >>sys.stderr, "Unexpected file: {}".format(fn) + sys.exit(1) thunks = set() -for name, rtype, params in functions.values(): +for name, rtype, rtypename, params, paramtypes, paramnames in functions.values(): thunks.add((rtype, tuple(params))) with open("src/thunks.inc", "w") as inc: for rtype, params in thunks: - print >>inc, "static void thunk_{}_{}(std::stack &{}, void *func)".format(rtype[0], "_".join("{}_{}".format(p, m) for p, m in params), "stack" if (params or rtype[0] != "TYPE_NOTHING") else "") + print >>inc, "static void thunk_{}_{}(opstack &{}, void *func)".format(rtype[0], "_".join("{}_{}".format(p, m) for p, m in params), "stack" if (params or rtype[0] != "TYPE_NOTHING") else "") print >>inc, "{" + d = 0 for i, a in reversed(list(enumerate(params))): - if a[0].startswith("TYPE_ARRAY_"): - print >>inc, " {} a{};".format(CppFromAst[a], i) - print >>inc, " for (auto x: stack.top().array()) a{}.push_back(x.{});".format(i, ArrayElementField[a]) - print >>inc, " stack.pop();" + from_stack = True + if a[0].startswith("TYPE_ARRAY_") and a[1] == VALUE: + print >>inc, " {} a{};".format(CppFromAstParam[a], i) + print >>inc, " for (auto x: stack.peek({}).array()) a{}.push_back(x.{});".format(d, i, ArrayElementField[a]) + elif a[0].startswith("TYPE_ARRAY_") and a[1] == REF: + print >>inc, " {} t{};".format(CppFromAstParam[a], i) + print >>inc, " for (auto x: stack.peek({}).address()->array()) t{}.push_back(x.{});".format(d, i, ArrayElementField[a]) + print >>inc, " {} *a{} = &t{};".format(CppFromAstParam[a], i, i) + elif a[0].startswith("TYPE_ARRAY_") and a[1] == OUT: + print >>inc, " {} t{};".format(CppFromAstParam[a], i) + print >>inc, " {} *a{} = &t{};".format(CppFromAstParam[a], i, i) + from_stack = False + elif a == ("TYPE_GENERIC", VALUE): + print >>inc, " {} a{} = stack.peek({});".format(CppFromAstParam[a], i, d) elif a == ("TYPE_GENERIC", REF): - print >>inc, " {} a{} = stack.top().address(); stack.pop();".format(CppFromAst[a], i); + print >>inc, " {} a{} = stack.peek({}).address();".format(CppFromAstParam[a], i, d) elif a[1] == REF: - print >>inc, " {} a{} = &stack.top().{}; stack.pop();".format(CppFromAst[a], i, CellField[a]); + print >>inc, " {} a{} = &stack.peek({}).{};".format(CppFromAstParam[a], i, d, CellField[a]) + elif a[1] == OUT: + print >>inc, " {} t{};".format(CppFromAstParam[a], i) + print >>inc, " {} *a{} = &t{};".format(CppFromAstParam[a], i, i) + from_stack = False else: - print >>inc, " {} a{} = stack.top().{}; stack.pop();".format(CppFromAst[a], i, CellField[a]); + print >>inc, " {} a{} = stack.peek({}).{};".format(CppFromAstParam[a], i, d, CellField[a]) + if from_stack: + d += 1 + stack_count = sum(1 for x in params if x[1] != OUT) + assert d == stack_count + print >>inc, " try {" + print >>inc, " {}reinterpret_cast<{} (*)({})>(func)({});".format("auto r = " if rtype[0] != "TYPE_NOTHING" else "", CppFromAstReturn[rtype], ",".join(CppFromAstArg[x] for x in params), ",".join("a{}".format(x) for x in range(len(params)))) + for i, a in reversed(list(enumerate(params))): + d = len(params) - 1 - i + if a[0].startswith("TYPE_ARRAY_") and a[1] == REF: + print >>inc, " stack.peek({}).address()->array_for_write().clear();".format(d) + print >>inc, " for (auto x: t{}) stack.peek({}).address()->array_for_write().push_back(Cell(x));".format(i, d) + if params: + print >>inc, " stack.drop({});".format(stack_count) if rtype[0] != "TYPE_NOTHING": - print >>inc, " auto r = reinterpret_cast<{} (*)({})>(func)({});".format(CppFromAst[rtype], ",".join(CppFromAstArg[x] for x in params), ",".join("a{}".format(x) for x in range(len(params)))) if rtype[0].startswith("TYPE_ARRAY_"): - print >>inc, " Cell t;" - print >>inc, " for (auto x: r) t.array().push_back(Cell(x));" - print >>inc, " stack.push(t);" + print >>inc, " std::vector t;" + print >>inc, " for (auto x: r) t.push_back(Cell(x));" + print >>inc, " stack.push(Cell(t));" elif rtype[0] == "TYPE_POINTER": - print >>inc, " stack.push(Cell(static_cast(r)));" + print >>inc, " stack.push(Cell(static_cast(r)));" else: - print >>inc, " stack.push(Cell(r));" - else: - print >>inc, " reinterpret_cast<{} (*)({})>(func)({});".format(CppFromAst[rtype], ",".join(CppFromAstArg[x] for x in params), ",".join("a{}".format(x) for x in range(len(params)))) + print >>inc, " stack.push(Cell(r));" + for i, a in reversed(list(enumerate(params))): + if a[1] == OUT: + if a[0].startswith("TYPE_ARRAY_"): + print >>inc, " std::vector o{};".format(i) + print >>inc, " for (auto x: t{}) o{}.push_back(Cell(x));".format(i, i) + print >>inc, " stack.push(Cell(o{}));".format(i) + else: + print >>inc, " stack.push(Cell(t{}));".format(i) + print >>inc, " } catch (RtlException &) {" + if params: + print >>inc, " stack.drop({});".format(stack_count) + print >>inc, " throw;" + print >>inc, " }" print >>inc, "}" print >>inc, "" @@ -148,41 +316,87 @@ print >>inc, "{" for name, ctype, init in constants.values(): if "$" not in name: - print >>inc, " scope->addName(\"{}\", new Constant(\"{}\", {}));".format(name, name, init) + print >>inc, " scope->addName(Token(), \"{}\", new Constant(Token(), \"{}\", {}));".format(name, name, init) print >>inc, "}"; - print >>inc, "static void init_builtin_constants(const std::string &module, Scope *scope)" + print >>inc, "static void init_builtin_constants(const std::string &{}, Scope *{})".format("module" if any("$" in x[0] for x in constants.values()) else "", "scope" if any("$" in x[0] for x in constants.values()) else "") print >>inc, "{" for name, ctype, init in constants.values(): i = name.index("$") module = name[:i] modname = name[i+1:] - print >>inc, " if (module == \"{}\") scope->addName(\"{}\", new Constant(\"{}\", {}));".format(module, modname, modname, init) + print >>inc, " if (module == \"{}\") scope->addName(Token(), \"{}\", new Constant(Token(), \"{}\", {}));".format(module, modname, modname, init) print >>inc, "}"; +with open("src/variables_compile.inc", "w") as inc: + print >>inc, "static void init_builtin_variables(const std::string &module, Scope *scope)" + print >>inc, "{" + for name, atype in variables.values(): + i = name.index("$") + module = name[:i] + modname = name[i+1:] + print >>inc, " if (module == \"{}\") scope->addName(Token(), \"{}\", new PredefinedVariable(\"{}\", {}));".format(module, modname, name, atype) + print >>inc, "}" + +with open("src/variables_exec.inc", "w") as inc: + print >>inc, "namespace rtl {" + for name, atype in variables.values(): + print >>inc, "extern Cell {};".format(name) + print >>inc, "}" + print >>inc, "static struct {" + print >>inc, " const char *name;" + print >>inc, " Cell *value;" + print >>inc, "} BuiltinVariables[] = {" + for name, atype, in variables.values(): + print >>inc, " {{\"{}\", &rtl::{}}},".format(name, name) + print >>inc, "};" + with open("src/functions_compile.inc", "w") as inc: + print >>inc, "struct PredefinedType {" + print >>inc, " const Type *type;" + print >>inc, " const char *modtypename;" + print >>inc, "};" print >>inc, "static struct {" print >>inc, " const char *name;" - print >>inc, " const Type *returntype;" - print >>inc, " struct {ParameterType::Mode m; const Type *p; } params[10];" + print >>inc, " PredefinedType returntype;" + print >>inc, " int count;" + print >>inc, " struct {ParameterType::Mode mode; const char *name; PredefinedType ptype; } params[10];" print >>inc, "} BuiltinFunctions[] = {" - for name, rtype, params in functions.values(): - print >>inc, " {{\"{}\", {}, {{{}}}}},".format(name, rtype[0], ",".join("{{ParameterType::{},{}}}".format("IN" if m == VALUE else "INOUT", p if p != "TYPE_GENERIC" else "nullptr") for p, m in params)) + for name, rtype, rtypename, params, paramtypes, paramnames in functions.values(): + print >>inc, " {{\"{}\", {{{}, {}}}, {}, {{{}}}}},".format(name.replace("global$", ""), rtype[0] if rtype[0] not in ["TYPE_GENERIC","TYPE_POINTER"] else "nullptr", "\"{}\"".format(rtypename) or "nullptr", len(params), ",".join("{{ParameterType::{},\"{}\",{{{},{}}}}}".format("IN" if m == VALUE else "INOUT" if m == REF else "OUT", n, p if p not in ["TYPE_GENERIC","TYPE_POINTER"] else "nullptr", "\"{}\"".format(t) or "nullptr") for (p, m), t, n in zip(params, paramtypes, paramnames))) print >>inc, "};"; with open("src/functions_exec.inc", "w") as inc: print >>inc, "namespace rtl {" - for name, rtype, params in functions.values(): + for name, rtype, rtypename, params, paramtypes, paramnames in functions.values(): if "." in name: namespace, name = name.split(".") - print >>inc, "namespace {} {{ extern {} {}({}); }}".format(namespace, CppFromAst[rtype], name, ", ".join(CppFromAstArg[x] for x in params)) + print >>inc, "namespace {} {{ extern {} {}({}); }}".format(namespace, CppFromAstReturn[rtype], name, ", ".join(CppFromAstArg[x] for x in params)) else: - print >>inc, "extern {} {}({});".format(CppFromAst[rtype], name, ", ".join(CppFromAstArg[x] for x in params)) + print >>inc, "extern {} {}({});".format(CppFromAstReturn[rtype], name, ", ".join(CppFromAstArg[x] for x in params)) print >>inc, "}" print >>inc, "static struct {" print >>inc, " const char *name;" print >>inc, " Thunk thunk;" print >>inc, " void *func;" print >>inc, "} BuiltinFunctions[] = {" - for name, rtype, params in functions.values(): - print >>inc, " {{\"{}\", {}, reinterpret_cast(rtl::{})}},".format(name, "thunk_{}_{}".format(rtype[0], "_".join("{}_{}".format(p, m) for p, m in params)), name.replace(".", "::")) + for name, rtype, rtypename, params, paramtypes, paramnames in functions.values(): + print >>inc, " {{\"{}\", {}, reinterpret_cast(rtl::{})}},".format(name.replace("global$", ""), "thunk_{}_{}".format(rtype[0], "_".join("{}_{}".format(p, m) for p, m in params)), name.replace(".", "::")) print >>inc, "};"; + +with open("src/enums.inc", "w") as inc: + for name, values in enums.items(): + for i, v in enumerate(values): + print >>inc, "static const uint32_t ENUM_{}_{} = {};".format(name, v, i) + +with open("src/exceptions.inc", "w") as inc: + print >>inc, "struct ExceptionName {" + print >>inc, " const char *name;" + print >>inc, "};" + for prefix, name in exceptions: + print >>inc, "static const ExceptionName Exception_{} = {{\"{}\"}};".format(prefix+name, name) + print >>inc + print >>inc, "static const ExceptionName ExceptionNames[] = {" + for prefix, name in exceptions: + if prefix == "global$": + print >>inc, " Exception_{},".format(prefix+name) + print >>inc, "};" diff --git a/scripts/run_test.py b/scripts/run_test.py index 99cc853df5..cdb7311412 100644 --- a/scripts/run_test.py +++ b/scripts/run_test.py @@ -1,4 +1,5 @@ import codecs +import difflib import os import re import subprocess @@ -7,6 +8,7 @@ class TestSkipped: pass errors = None +runner = ["bin/neon"] def run(fn): print ("Running {}...".format(fn)) @@ -22,27 +24,52 @@ def run(fn): if any("SKIP" in x for x in all_comments): print("skipped") raise TestSkipped() + platforms = [m and m.group(1) for m in [re.search(r"PLATFORM:(\w+)", x) for x in all_comments]] + if any(platforms): + while True: + if "win32" in platforms and os.name == "nt": + break + if "posix" in platforms and os.name == "posix": + break + print("skipped: {}".format(",".join(x for x in platforms if x))) + raise TestSkipped() args = [] a = [x for x in all_comments if "ARGS" in x] if a: args = a[0][a[0].index("ARGS")+4:].split() + errnum = None expected_stdout = "" + regex_stdout = None expected_stderr = "" + expected_error_pos = {} if errors: - err = os.path.splitext(os.path.basename(fn))[0] - expected_stderr = err - del errors[err] + lines = src.strip().split("\n") + for i, s in enumerate(lines, 1): + for m in re.finditer(r"(%<)(\d)?", s): + n = int(m.group(2)) if m.group(2) else 1 + expected_error_pos[n] = (i - 1, 1 + m.start(1)) + if lines[-1] == "%$": + expected_error_pos[1] = (len(lines) + 1, 1) + errnum = os.path.splitext(os.path.basename(fn))[0] + expected_stderr = "Error " + errnum + del errors[errnum] else: - out_comments = re.findall("^%=\s*(.*)$", src, re.MULTILINE) - expected_stdout = "".join([x + "\n" for x in out_comments]) + out_comments = re.findall("^(%[=?])\s(.*)$", src, re.MULTILINE) + if any(x[0] == "%?" for x in out_comments): + regex_stdout = "".join([(re.escape(x[1]) if x[0] == "%=" else x[1]) + "\n" for x in out_comments]) + else: + expected_stdout = "".join([x[1] + "\n" for x in out_comments]) err_comments = re.findall("^%!\s*(.*)$", src, re.MULTILINE) expected_stderr = "".join([x + "\n" for x in err_comments]).strip() - p = subprocess.Popen(["bin/neon", fn] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(runner + [fn] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() + if p.returncode == 99: + print("skipped due to runner request") + raise TestSkipped() out = out.decode().replace("\r\n", "\n") try: @@ -57,19 +84,52 @@ def run(fn): return False assert p.returncode == 0, p.returncode - if expected_stderr not in err: - print("*** EXPECTED ERROR") - print("") - sys.stdout.write(expected_stderr) - print("") - print("*** ACTUAL ERROR") - print("") - sys.stdout.write(err) - if todo: - return False - sys.exit(1) - - if out != expected_stdout: + if expected_stderr: + if expected_stderr not in err: + print("*** EXPECTED ERROR") + print("") + sys.stdout.write(expected_stderr) + print("") + print("*** ACTUAL ERROR") + print("") + sys.stdout.write(err) + if todo: + return False + sys.exit(1) + if expected_error_pos: + found = 0 + for m in re.finditer(r"Error N\d+: (\d+):(\d+)", err): + actual_line = int(m.group(1)) + actual_col = int(m.group(2)) + if (actual_line, actual_col) != expected_error_pos[1+found]: + print("") + print("*** WRONG ERROR LOCATION (expected {})".format(expected_error_pos[1+found])) + print("") + sys.stdout.write(err) + if todo: + return False + sys.exit(1) + found += 1 + assert found == len(expected_error_pos), (found, len(expected_error_pos)) + elif errnum and errnum > "N2000": + print("Need error location information for {}".format(errnum)) + if todo: + return False + sys.exit(1) + + if regex_stdout: + if not re.match(regex_stdout, out): + print("*** EXPECTED OUTPUT (regex)") + print("") + sys.stdout.write(regex_stdout) + print("") + print("*** ACTUAL OUTPUT") + print("") + sys.stdout.write(out) + if todo: + return False + sys.exit(1) + elif out != expected_stdout: print("*** EXPECTED OUTPUT") print("") sys.stdout.write(expected_stdout) @@ -77,6 +137,10 @@ def run(fn): print("*** ACTUAL OUTPUT") print("") sys.stdout.write(out) + print("") + print("*** DIFF") + print("") + print("\n".join(x.rstrip() for x in difflib.unified_diff(expected_stdout.split("\n"), out.split("\n")))) if todo: return False sys.exit(1) @@ -89,9 +153,19 @@ def run(fn): def main(): global errors - for a in sys.argv[1:]: + global runner + + i = 1 + while i < len(sys.argv): + a = sys.argv[i] if a == "--errors": errors = dict(x.strip().split(" ", 1) for x in open("src/errors.txt")) + elif a == "--runner": + i += 1 + runner = sys.argv[i].split() + else: + break + i += 1 total = 0 succeeded = 0 diff --git a/scripts/test_doc.py b/scripts/test_doc.py new file mode 100644 index 0000000000..c68bdf622b --- /dev/null +++ b/scripts/test_doc.py @@ -0,0 +1,85 @@ +import itertools +import os +import re +import subprocess +import sys + +def test(code): + if code: + compile_only = re.search(r"\binput\b", code) is not None + p = subprocess.Popen(["bin/neonc" if compile_only else "bin/neon", "-"], stdin=subprocess.PIPE) + p.communicate(code) + p.wait() + assert p.returncode == 0 + +def check_file_md(source): + lines = source.split("\n") + code = "" + lastblank = True + for s in lines: + if not s: + lastblank = True + continue + if s.startswith(" ") and (code or lastblank): + code += s[4:] + "\n" + else: + test(code) + code = "" + lastblank = False + test(code) + +def check_file_neon(fn, source): + lines = enumerate(source.split("\n"), 1) + for incomment, doc in itertools.groupby(lines, lambda line: len(line[1]) >= 2 and line[1][1] == "|"): + if incomment: + full = list(doc) + doc = itertools.dropwhile(lambda x: "Example:" not in x, [x[1] for x in full]) + try: + next(doc) + test("IMPORT {}\n".format(os.path.basename(fn)[:-5]) + "\n".join(re.sub(r"^ \|\s*[>:|]", "", x) for x in doc if x.startswith(" | "))) + except StopIteration: + firstline = next(itertools.dropwhile(lambda x: not x[1][3:].strip(), full)) + undocumented.append("no example in {}:{} for {}".format(fn, firstline[0], firstline[1][3:].strip())) + +def check_file(fn, source): + if fn.endswith(".md"): + check_file_md(source) + elif fn.endswith(".neon"): + check_file_neon(fn, source) + else: + assert False, fn + +def get_branch_files(prefix): + try: + files = [x for x in subprocess.check_output(["git", "ls-tree", "-r", "--name-only", "origin/gh-pages"]).split("\n") if x.endswith("md")] + except (subprocess.CalledProcessError, OSError): + # no git, just exit + sys.exit(0) + for fn in files: + yield (fn, subprocess.check_output(["git", "show", "origin/gh-pages:"+fn])) + +def get_path_files(prefix): + for path, dirs, files in os.walk(prefix): + for f in files: + if f.endswith((".md", ".neon")): + fn = os.path.join(path, f) + yield (fn, open(fn).read()) + +if len(sys.argv) < 2: + paths = ["gh-pages", "lib"] +else: + paths = [sys.argv[1]] + +undocumented = [] +for path in paths: + if path.endswith(":"): + files = get_branch_files(path) + else: + files = get_path_files(path) + + for fn, source in files: + print "Checking {}...".format(fn) + check_file(fn, source) + +for u in undocumented: + print "test_doc.py:", u diff --git a/scripts/update_docs b/scripts/update_docs new file mode 100755 index 0000000000..0bf4353904 --- /dev/null +++ b/scripts/update_docs @@ -0,0 +1,13 @@ +#!/bin/sh -e + +rev=$(git describe --always) +scons docs docs_samples +git clone -b gh-pages . tmp-gh-pages +(cd tmp-gh-pages && git pull --ff-only .. origin/gh-pages) +rm -rf tmp-gh-pages/* +cp -r gh-pages/ tmp-gh-pages/ +rm tmp-gh-pages/html/.gitignore tmp-gh-pages/samples/.gitignore +cp .git/info/exclude tmp-gh-pages/.git/info/ +(cd tmp-gh-pages && git add --all . && git commit -m "Update documentation from $rev" && git push) +rm -rf tmp-gh-pages +echo "git push origin gh-pages:gh-pages" diff --git a/src/analyzer.cpp b/src/analyzer.cpp new file mode 100644 index 0000000000..d6dab9d1ec --- /dev/null +++ b/src/analyzer.cpp @@ -0,0 +1,3337 @@ +#include "analyzer.h" + +#include +#include +#include +#include +#include +#include + +#include "ast.h" +#include "bytecode.h" +#include "format.h" +#include "pt.h" +#include "rtl_compile.h" +#include "support.h" +#include "util.h" + +#include "constants_compile.inc" + +class Analyzer { +public: + Analyzer(ICompilerSupport *support, const pt::Program *program); + + ICompilerSupport *support; + const pt::Program *program; + std::map modules; + Frame *global_frame; + Scope *global_scope; + std::stack frame; + std::stack scope; + std::set exports; + + std::stack> functiontypes; + std::stack>> loops; + + const Type *analyze(const pt::Type *type, const std::string &name = std::string()); + const Type *analyze(const pt::TypeSimple *type, const std::string &name); + const Type *analyze_enum(const pt::TypeEnum *type, const std::string &name); + const Type *analyze_record(const pt::TypeRecord *type, const std::string &name); + const Type *analyze(const pt::TypePointer *type, const std::string &name); + const Type *analyze(const pt::TypeFunctionPointer *type, const std::string &name); + const Type *analyze(const pt::TypeParameterised *type, const std::string &name); + const Type *analyze(const pt::TypeImport *type, const std::string &name); + const Expression *analyze(const pt::Expression *expr); + const Expression *analyze(const pt::DummyExpression *expr); + const Expression *analyze(const pt::IdentityExpression *expr); + const Expression *analyze(const pt::BooleanLiteralExpression *expr); + const Expression *analyze(const pt::NumberLiteralExpression *expr); + const Expression *analyze(const pt::StringLiteralExpression *expr); + const Expression *analyze(const pt::FileLiteralExpression *expr); + const Expression *analyze(const pt::BytesLiteralExpression *expr); + const Expression *analyze(const pt::ArrayLiteralExpression *expr); + const Expression *analyze(const pt::ArrayLiteralRangeExpression *expr); + const Expression *analyze(const pt::DictionaryLiteralExpression *expr); + const Expression *analyze(const pt::NilLiteralExpression *expr); + const Expression *analyze(const pt::IdentifierExpression *expr); + const Name *analyze_qualified_name(const pt::Expression *expr); + const Expression *analyze(const pt::DotExpression *expr); + const Expression *analyze(const pt::ArrowExpression *expr); + const Expression *analyze(const pt::SubscriptExpression *expr); + const Expression *analyze(const pt::InterpolatedStringExpression *expr); + const Expression *analyze(const pt::FunctionCallExpression *expr); + const Expression *analyze(const pt::UnaryPlusExpression *expr); + const Expression *analyze(const pt::UnaryMinusExpression *expr); + const Expression *analyze(const pt::LogicalNotExpression *expr); + const Expression *analyze(const pt::ExponentiationExpression *expr); + const Expression *analyze(const pt::MultiplicationExpression *expr); + const Expression *analyze(const pt::DivisionExpression *expr); + const Expression *analyze(const pt::ModuloExpression *expr); + const Expression *analyze(const pt::AdditionExpression *expr); + const Expression *analyze(const pt::SubtractionExpression *expr); + const Expression *analyze(const pt::ConcatenationExpression *expr); + const Expression *analyze(const pt::ComparisonExpression *expr); + const Expression *analyze(const pt::ChainedComparisonExpression *expr); + const Expression *analyze(const pt::MembershipExpression *expr); + const Expression *analyze(const pt::ConjunctionExpression *expr); + const Expression *analyze(const pt::DisjunctionExpression *expr); + const Expression *analyze(const pt::ConditionalExpression *expr); + const Expression *analyze(const pt::NewRecordExpression *expr); + const Expression *analyze(const pt::ValidPointerExpression *expr); + const Expression *analyze(const pt::RangeSubscriptExpression *expr); + const Statement *analyze(const pt::ImportDeclaration *declaration); + const Statement *analyze(const pt::TypeDeclaration *declaration); + const Statement *analyze_decl(const pt::ConstantDeclaration *declaration); + const Statement *analyze_body(const pt::ConstantDeclaration *declaration); + const Statement *analyze_decl(const pt::VariableDeclaration *declaration); + const Statement *analyze_body(const pt::VariableDeclaration *declaration); + const Statement *analyze_decl(const pt::LetDeclaration *declaration); + const Statement *analyze_body(const pt::LetDeclaration *declaration); + const Statement *analyze_decl(const pt::FunctionDeclaration *declaration); + const Statement *analyze_body(const pt::FunctionDeclaration *declaration); + const Statement *analyze_decl(const pt::ExternalFunctionDeclaration *declaration); + const Statement *analyze_body(const pt::ExternalFunctionDeclaration *declaration); + const Statement *analyze(const pt::NativeFunctionDeclaration *declaration); + const Statement *analyze(const pt::ExceptionDeclaration *declaration); + const Statement *analyze(const pt::ExportDeclaration *declaration); + const Statement *analyze(const pt::MainBlock *statement); + std::vector analyze(const std::vector &statement); + const Statement *analyze(const pt::AssertStatement *statement); + const Statement *analyze(const pt::AssignmentStatement *statement); + const Statement *analyze(const pt::CaseStatement *statement); + const Statement *analyze(const pt::ExitStatement *statement); + const Statement *analyze(const pt::ExpressionStatement *statement); + const Statement *analyze(const pt::ForStatement *statement); + const Statement *analyze(const pt::ForeachStatement *statement); + const Statement *analyze(const pt::IfStatement *statement); + const Statement *analyze(const pt::IncrementStatement *statement); + const Statement *analyze(const pt::LoopStatement *statement); + const Statement *analyze(const pt::NextStatement *statement); + const Statement *analyze(const pt::RaiseStatement *statement); + const Statement *analyze(const pt::RepeatStatement *statement); + const Statement *analyze(const pt::ReturnStatement *statement); + const Statement *analyze(const pt::TryStatement *statement); + const Statement *analyze(const pt::WhileStatement *statement); + const Program *analyze(); +private: + Module *import_module(const Token &token, const std::string &name); + Type *deserialize_type(Scope *scope, const std::string &descriptor, std::string::size_type &i); + Type *deserialize_type(Scope *scope, const std::string &descriptor); +private: + Analyzer(const Analyzer &); + Analyzer &operator=(const Analyzer &); +}; + +class TypeAnalyzer: public pt::IParseTreeVisitor { +public: + TypeAnalyzer(Analyzer *a, const std::string &name): type(nullptr), a(a), name(name) {} + virtual void visit(const pt::TypeSimple *t) override { type = a->analyze(t, name); } + virtual void visit(const pt::TypeEnum *t) override { type = a->analyze_enum(t, name); } + virtual void visit(const pt::TypeRecord *t) override { type = a->analyze_record(t, name); } + virtual void visit(const pt::TypePointer *t) override { type = a->analyze(t, name); } + virtual void visit(const pt::TypeFunctionPointer *t) override { type = a->analyze(t, name); } + virtual void visit(const pt::TypeParameterised *t) override { type = a->analyze(t, name); } + virtual void visit(const pt::TypeImport *t) override { type = a->analyze(t, name); } + virtual void visit(const pt::DummyExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentityExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BooleanLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NumberLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::StringLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FileLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BytesLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralRangeExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DictionaryLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NilLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentifierExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrowExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::InterpolatedStringExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FunctionCallExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryPlusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryMinusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::LogicalNotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ExponentiationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MultiplicationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DivisionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ModuloExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::AdditionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubtractionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConcatenationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ChainedComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MembershipExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DisjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConditionalExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NewRecordExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ValidPointerExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::RangeSubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ImportDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::TypeDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ConstantDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::VariableDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::LetDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::FunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExternalFunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::NativeFunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExceptionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExportDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::MainBlock *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::AssertStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::AssignmentStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::CaseStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ExitStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ExpressionStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ForStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ForeachStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::IfStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::IncrementStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::LoopStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::NextStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::RaiseStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::RepeatStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ReturnStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::TryStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::WhileStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::Program *) override { internal_error("pt::Program"); } + const Type *type; +private: + Analyzer *a; + const std::string name; +private: + TypeAnalyzer(const TypeAnalyzer &); + TypeAnalyzer &operator=(const TypeAnalyzer &); +}; + +class ExpressionAnalyzer: public pt::IParseTreeVisitor { +public: + ExpressionAnalyzer(Analyzer *a): expr(nullptr), a(a) {} + virtual void visit(const pt::TypeSimple *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeEnum *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeRecord *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypePointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeFunctionPointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeParameterised *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeImport *) override { internal_error("pt::Type"); } + virtual void visit(const pt::DummyExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::IdentityExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::BooleanLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::NumberLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::StringLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::FileLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::BytesLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ArrayLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ArrayLiteralRangeExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::DictionaryLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::NilLiteralExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::IdentifierExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::DotExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ArrowExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::SubscriptExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::InterpolatedStringExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::FunctionCallExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::UnaryPlusExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::UnaryMinusExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::LogicalNotExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ExponentiationExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::MultiplicationExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::DivisionExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ModuloExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::AdditionExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::SubtractionExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ConcatenationExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ComparisonExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ChainedComparisonExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::MembershipExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ConjunctionExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::DisjunctionExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ConditionalExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::NewRecordExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ValidPointerExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::RangeSubscriptExpression *p) override { expr = a->analyze(p); } + virtual void visit(const pt::ImportDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::TypeDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ConstantDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::VariableDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::LetDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::FunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExternalFunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::NativeFunctionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExceptionDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::ExportDeclaration *) override { internal_error("pt::Declaration"); } + virtual void visit(const pt::MainBlock *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::AssertStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::AssignmentStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::CaseStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ExitStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ExpressionStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ForStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ForeachStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::IfStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::IncrementStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::LoopStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::NextStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::RaiseStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::RepeatStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::ReturnStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::TryStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::WhileStatement *) override { internal_error("pt::Statement"); } + virtual void visit(const pt::Program *) override { internal_error("pt::Program"); } + const Expression *expr; +private: + Analyzer *a; +private: + ExpressionAnalyzer(const ExpressionAnalyzer &); + ExpressionAnalyzer &operator=(const ExpressionAnalyzer &); +}; + +class DeclarationAnalyzer: public pt::IParseTreeVisitor { +public: + DeclarationAnalyzer(Analyzer *a, std::vector &v): a(a), v(v) {} + virtual void visit(const pt::TypeSimple *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeEnum *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeRecord *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypePointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeFunctionPointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeParameterised *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeImport *) override { internal_error("pt::Type"); } + virtual void visit(const pt::DummyExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentityExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BooleanLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NumberLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::StringLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FileLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BytesLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralRangeExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DictionaryLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NilLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentifierExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrowExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::InterpolatedStringExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FunctionCallExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryPlusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryMinusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::LogicalNotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ExponentiationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MultiplicationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DivisionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ModuloExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::AdditionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubtractionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConcatenationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ChainedComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MembershipExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DisjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConditionalExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NewRecordExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ValidPointerExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::RangeSubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ImportDeclaration *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::TypeDeclaration *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ConstantDeclaration *p) override { v.push_back(a->analyze_decl(p)); } + virtual void visit(const pt::VariableDeclaration *p) override { v.push_back(a->analyze_decl(p)); } + virtual void visit(const pt::LetDeclaration *p) override { v.push_back(a->analyze_decl(p)); } + virtual void visit(const pt::FunctionDeclaration *p) override { v.push_back(a->analyze_decl(p)); } + virtual void visit(const pt::ExternalFunctionDeclaration *p) override { v.push_back(a->analyze_decl(p)); } + virtual void visit(const pt::NativeFunctionDeclaration *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ExceptionDeclaration *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ExportDeclaration *) override {} + virtual void visit(const pt::MainBlock *) override {} + virtual void visit(const pt::AssertStatement *) override {} + virtual void visit(const pt::AssignmentStatement *) override {} + virtual void visit(const pt::CaseStatement *) override {} + virtual void visit(const pt::ExitStatement *) override {} + virtual void visit(const pt::ExpressionStatement *) override {} + virtual void visit(const pt::ForStatement *) override {} + virtual void visit(const pt::ForeachStatement *) override {} + virtual void visit(const pt::IfStatement *) override {} + virtual void visit(const pt::IncrementStatement *) override {} + virtual void visit(const pt::LoopStatement *) override {} + virtual void visit(const pt::NextStatement *) override {} + virtual void visit(const pt::RaiseStatement *) override {} + virtual void visit(const pt::RepeatStatement *) override {} + virtual void visit(const pt::ReturnStatement *) override {} + virtual void visit(const pt::TryStatement *) override {} + virtual void visit(const pt::WhileStatement *) override {} + virtual void visit(const pt::Program *) override { internal_error("pt::Program"); } +private: + Analyzer *a; + std::vector &v; +private: + DeclarationAnalyzer(const DeclarationAnalyzer &); + DeclarationAnalyzer &operator=(const DeclarationAnalyzer &); +}; + +class StatementAnalyzer: public pt::IParseTreeVisitor { +public: + StatementAnalyzer(Analyzer *a, std::vector &v): a(a), v(v) {} + virtual void visit(const pt::TypeSimple *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeEnum *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeRecord *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypePointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeFunctionPointer *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeParameterised *) override { internal_error("pt::Type"); } + virtual void visit(const pt::TypeImport *) override { internal_error("pt::Type"); } + virtual void visit(const pt::DummyExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentityExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BooleanLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NumberLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::StringLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FileLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::BytesLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrayLiteralRangeExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DictionaryLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NilLiteralExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::IdentifierExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ArrowExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::InterpolatedStringExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::FunctionCallExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryPlusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::UnaryMinusExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::LogicalNotExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ExponentiationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MultiplicationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DivisionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ModuloExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::AdditionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::SubtractionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConcatenationExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ChainedComparisonExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::MembershipExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::DisjunctionExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ConditionalExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::NewRecordExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ValidPointerExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::RangeSubscriptExpression *) override { internal_error("pt::Expression"); } + virtual void visit(const pt::ImportDeclaration *) override {} + virtual void visit(const pt::TypeDeclaration *) override {} + virtual void visit(const pt::ConstantDeclaration *p) override { v.push_back(a->analyze_body(p)); } + virtual void visit(const pt::VariableDeclaration *p) override { v.push_back(a->analyze_body(p)); } + virtual void visit(const pt::LetDeclaration *p) override { v.push_back(a->analyze_body(p)); } + virtual void visit(const pt::FunctionDeclaration *p) override { v.push_back(a->analyze_body(p)); } + virtual void visit(const pt::ExternalFunctionDeclaration *p) override { v.push_back(a->analyze_body(p)); } + virtual void visit(const pt::NativeFunctionDeclaration *) override {} + virtual void visit(const pt::ExceptionDeclaration *) override {} + virtual void visit(const pt::ExportDeclaration *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::MainBlock *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::AssertStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::AssignmentStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::CaseStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ExitStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ExpressionStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ForStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ForeachStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::IfStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::IncrementStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::LoopStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::NextStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::RaiseStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::RepeatStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::ReturnStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::TryStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::WhileStatement *p) override { v.push_back(a->analyze(p)); } + virtual void visit(const pt::Program *) override { internal_error("pt::Program"); } +private: + Analyzer *a; + std::vector &v; +private: + StatementAnalyzer(const StatementAnalyzer &); + StatementAnalyzer &operator=(const StatementAnalyzer &); +}; + +static std::string path_basename(const std::string &path) +{ +#ifdef _WIN32 + static const std::string delim = "/\\:"; +#else + static const std::string delim = "/"; +#endif + std::string::size_type i = path.find_last_of(delim); + if (i == std::string::npos) { + return path; + } + return path.substr(i + 1); +} + +static std::string path_stripext(const std::string &name) +{ + std::string::size_type i = name.find_last_of('.'); + if (i == std::string::npos) { + return name; + } + return name.substr(0, i); +} + +Analyzer::Analyzer(ICompilerSupport *support, const pt::Program *program) + : support(support), + program(program), + modules(), + global_frame(nullptr), + global_scope(nullptr), + frame(), + scope(), + exports(), + functiontypes(), + loops() +{ +} + +TypeEnum::TypeEnum(const Token &declaration, const std::string &name, const std::map &names, Analyzer *analyzer) + : TypeNumber(declaration, name), + names(names) +{ + { + std::vector params; + FunctionParameter *fp = new FunctionParameter(Token(), "self", this, ParameterType::IN, nullptr); + params.push_back(fp); + Function *f = new Function(Token(), "enum.toString", TYPE_STRING, analyzer->global_frame, analyzer->global_scope, params); + std::vector values; + for (auto n: names) { + if (n.second < 0) { + internal_error("TypeEnum"); + } + if (values.size() < static_cast(n.second)+1) { + values.resize(n.second+1); + } + if (values[n.second] != nullptr) { + internal_error("TypeEnum"); + } + values[n.second] = new ConstantStringExpression(n.first); + } + f->statements.push_back(new ReturnStatement(0, new ArrayValueIndexExpression(TYPE_STRING, new ArrayLiteralExpression(TYPE_STRING, values), new VariableExpression(fp), false))); + methods["toString"] = f; + } +} + +StringReferenceIndexExpression::StringReferenceIndexExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : ReferenceExpression(ref->type, ref->is_readonly), + ref(ref), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr), + store(nullptr) +{ + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("string__substring"))), args); + } + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + store = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("string__splice"))), args); + } +} + +StringValueIndexExpression::StringValueIndexExpression(const Expression *str, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : Expression(str->type, str->is_readonly), + str(str), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr) +{ + { + std::vector args; + args.push_back(str); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("string__substring"))), args); + } +} + +BytesReferenceIndexExpression::BytesReferenceIndexExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : ReferenceExpression(ref->type, ref->is_readonly), + ref(ref), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr), + store(nullptr) +{ + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("bytes__range"))), args); + } + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + store = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("bytes__splice"))), args); + } +} + +BytesValueIndexExpression::BytesValueIndexExpression(const Expression *str, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : Expression(str->type, str->is_readonly), + str(str), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr) +{ + { + std::vector args; + args.push_back(str); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("bytes__range"))), args); + } +} + +ArrayReferenceRangeExpression::ArrayReferenceRangeExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : ReferenceExpression(ref->type, ref->is_readonly), + ref(ref), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr), + store(nullptr) +{ + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("array__slice"))), args); + } + { + std::vector args; + args.push_back(ref); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + store = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("array__splice"))), args); + } +} + +ArrayValueRangeExpression::ArrayValueRangeExpression(const Expression *array, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer) + : Expression(array->type, false), + array(array), + first(first), + first_from_end(first_from_end), + last(last), + last_from_end(last_from_end), + load(nullptr) +{ + { + std::vector args; + args.push_back(array); + args.push_back(first); + args.push_back(new ConstantBooleanExpression(first_from_end)); + args.push_back(last); + args.push_back(new ConstantBooleanExpression(last_from_end)); + load = new FunctionCall(new VariableExpression(dynamic_cast(analyzer->global_scope->lookupName("array__slice"))), args); + } +} + +Module *Analyzer::import_module(const Token &token, const std::string &name) +{ + static std::vector s_importing; + + auto m = modules.find(name); + if (m != modules.end()) { + return m->second; + } + if (std::find(s_importing.begin(), s_importing.end(), name) != s_importing.end()) { + error(3181, token, "recursive import detected"); + } + s_importing.push_back(name); + Bytecode object; + if (not support->loadBytecode(name, object)) { + internal_error("TODO module not found: " + name); + } + Module *module = new Module(Token(), scope.top(), name); + for (auto t: object.types) { + if (object.strtable[t.descriptor][0] == 'R') { + // Support recursive record type declarations. + TypeRecord *actual_record = new TypeRecord(Token(), name + "." + object.strtable[t.name], std::vector()); + module->scope->addName(Token(), object.strtable[t.name], actual_record); + Type *type = deserialize_type(module->scope, object.strtable[t.descriptor]); + const TypeRecord *rectype = dynamic_cast(type); + const_cast &>(actual_record->fields) = rectype->fields; + const_cast &>(actual_record->field_names) = rectype->field_names; + } else { + module->scope->addName(Token(), object.strtable[t.name], deserialize_type(module->scope, object.strtable[t.descriptor])); + } + } + for (auto c: object.constants) { + const Type *type = deserialize_type(module->scope, object.strtable[c.type]); + int i = 0; + module->scope->addName(Token(), object.strtable[c.name], new Constant(Token(), object.strtable[c.name], type->deserialize_value(c.value, i))); + } + for (auto v: object.variables) { + module->scope->addName(Token(), object.strtable[v.name], new ModuleVariable(name, object.strtable[v.name], deserialize_type(module->scope, object.strtable[v.type]), v.index)); + } + for (auto f: object.functions) { + const std::string function_name = object.strtable[f.name]; + auto i = function_name.find('.'); + if (i != std::string::npos) { + const std::string typestr = function_name.substr(0, i); + const std::string method = function_name.substr(i+1); + Name *n = module->scope->lookupName(typestr); + Type *type = dynamic_cast(n); + type->methods[method] = new ModuleFunction(name, function_name, deserialize_type(module->scope, object.strtable[f.descriptor]), f.entry); + } else { + module->scope->addName(Token(), function_name, new ModuleFunction(name, function_name, deserialize_type(module->scope, object.strtable[f.descriptor]), f.entry)); + } + } + for (auto e: object.exception_exports) { + module->scope->addName(Token(), object.strtable[e.name], new Exception(Token(), object.strtable[e.name])); + } + s_importing.pop_back(); + modules[name] = module; + return module; +} + +const Type *Analyzer::analyze(const pt::Type *type, const std::string &name) +{ + TypeAnalyzer ta(this, name); + type->accept(&ta); + return ta.type; +} + +const Type *Analyzer::analyze(const pt::TypeSimple *type, const std::string &) +{ + const Name *name = scope.top()->lookupName(type->name); + if (name == nullptr) { + error(3011, type->token, "unknown type name"); + } + const Type *r = dynamic_cast(name); + if (r == nullptr) { + error2(3012, type->token, "name is not a type", name->declaration, "name declared here"); + } + return r; +} + +const Type *Analyzer::analyze_enum(const pt::TypeEnum *type, const std::string &name) +{ + std::map names; + int index = 0; + for (auto x: type->names) { + std::string name = x.first.text; + auto t = names.find(name); + if (t != names.end()) { + error2(3010, x.first, "duplicate enum: " + name, type->names[t->second].first, "first declaration here"); + } + names[name] = index; + index++; + } + return new TypeEnum(type->token, name, names, this); +} + +const Type *Analyzer::analyze_record(const pt::TypeRecord *type, const std::string &name) +{ + std::vector fields; + std::map field_names; + for (auto x: type->fields) { + std::string name = x.name.text; + auto prev = field_names.find(name); + if (prev != field_names.end()) { + error2(3009, x.name, "duplicate field: " + x.name.text, prev->second, "first declaration here"); + } + const Type *t = analyze(x.type); + fields.push_back(TypeRecord::Field(x.name, t, x.is_private)); + field_names[name] = x.name; + } + return new TypeRecord(type->token, name, fields); +} + +const Type *Analyzer::analyze(const pt::TypePointer *type, const std::string &) +{ + if (type->reftype != nullptr) { + const pt::TypeSimple *simple = dynamic_cast(type->reftype); + if (simple != nullptr && scope.top()->lookupName(simple->name) == nullptr) { + const std::string name = simple->name; + TypePointer *ptrtype = new TypePointer(type->token, new TypeForwardRecord(type->reftype->token)); + scope.top()->addForward(name, ptrtype); + return ptrtype; + } else { + const Type *reftype = analyze(type->reftype); + const TypeRecord *rectype = dynamic_cast(reftype); + if (rectype == nullptr) { + error(3098, type->reftype->token, "record type expected"); + } + return new TypePointer(type->token, rectype); + } + } else { + return new TypePointer(type->token, nullptr); + } +} + +const Type *Analyzer::analyze(const pt::TypeFunctionPointer *type, const std::string &) +{ + const Type *returntype = type->returntype != nullptr ? analyze(type->returntype) : TYPE_NOTHING; + std::vector params; + bool in_default = false; + for (auto x: type->args) { + ParameterType::Mode mode = ParameterType::IN; + switch (x->mode) { + case pt::FunctionParameter::IN: mode = ParameterType::IN; break; + case pt::FunctionParameter::INOUT: mode = ParameterType::INOUT; break; + case pt::FunctionParameter::OUT: mode = ParameterType::OUT; break; + } + const Type *ptype = analyze(x->type); + const Expression *def = nullptr; + if (x->default_value != nullptr) { + in_default = true; + def = analyze(x->default_value); + if (not def->is_constant) { + error(3177, x->default_value->token, "default value not constant"); + } + } else if (in_default) { + error(3178, x->token, "default value must be specified for this parameter"); + } + ParameterType *pt = new ParameterType(x->name, mode, ptype, def); + params.push_back(pt); + } + return new TypeFunctionPointer(type->token, new TypeFunction(returntype, params)); +} + +const Type *Analyzer::analyze(const pt::TypeParameterised *type, const std::string &) +{ + if (type->name.text == "Array") { + return new TypeArray(type->name, analyze(type->elementtype)); + } + if (type->name.text == "Dictionary") { + return new TypeDictionary(type->name, analyze(type->elementtype)); + } + internal_error("Invalid parameterized type"); +} + +const Type *Analyzer::analyze(const pt::TypeImport *type, const std::string &) +{ + Name *modname = scope.top()->lookupName(type->modname.text); + if (modname == nullptr) { + error(3153, type->modname, "name not found"); + } + Module *module = dynamic_cast(modname); + if (module == nullptr) { + error(3154, type->modname, "module name expected"); + } + Name *name = module->scope->lookupName(type->subname.text); + if (name == nullptr) { + error(3155, type->subname, "name not found in module"); + } + Type *rtype = dynamic_cast(name); + if (rtype == nullptr) { + error(3156, type->subname, "name not a type"); + } + return rtype; +} + +const Expression *Analyzer::analyze(const pt::Expression *expr) +{ + ExpressionAnalyzer ea(this); + expr->accept(&ea); + return ea.expr; +} + +const Expression *Analyzer::analyze(const pt::DummyExpression *) +{ + return new DummyExpression(); +} + +const Expression *Analyzer::analyze(const pt::IdentityExpression *expr) +{ + return analyze(expr->expr); +} + +const Expression *Analyzer::analyze(const pt::BooleanLiteralExpression *expr) +{ + return new ConstantBooleanExpression(expr->value); +} + +const Expression *Analyzer::analyze(const pt::NumberLiteralExpression *expr) +{ + return new ConstantNumberExpression(expr->value); +} + +const Expression *Analyzer::analyze(const pt::StringLiteralExpression *expr) +{ + return new ConstantStringExpression(expr->value); +} + +const Expression *Analyzer::analyze(const pt::FileLiteralExpression *expr) +{ + std::ifstream f(expr->name, std::ios::binary); + if (not f) { + error(3182, expr->token, "could not open file"); + } + std::stringstream buffer; + buffer << f.rdbuf(); + return new ConstantBytesExpression(expr->name, buffer.str()); +} + +const Expression *Analyzer::analyze(const pt::BytesLiteralExpression *expr) +{ + std::string bytes; + std::string::size_type i = 0; + while (i < expr->data.length()) { + if (isspace(expr->data[i])) { + ++i; + continue; + } + auto j = i; + if (not isxdigit(expr->data[i])) { + error(3183, expr->token, "invalid hex data"); + } + j++; + if (j < expr->data.length() && isxdigit(expr->data[j])) { + j++; + } + bytes.push_back(static_cast(std::stoul(expr->data.substr(i, j-i), nullptr, 16))); + i = j; + } + return new ConstantBytesExpression("HEXBYTES literal", bytes); +} + +const Expression *Analyzer::analyze(const pt::ArrayLiteralExpression *expr) +{ + std::vector elements; + const Type *elementtype = nullptr; + for (auto x: expr->elements) { + const Expression *element = analyze(x); + if (elementtype == nullptr) { + elementtype = element->type; + } else if (not element->type->is_assignment_compatible(elementtype)) { + error(3079, x->token, "type mismatch"); + } + elements.push_back(element); + } + return new ArrayLiteralExpression(elementtype, elements); +} + +const Expression *Analyzer::analyze(const pt::ArrayLiteralRangeExpression *expr) +{ + const Expression *first = analyze(expr->first); + if (not first->type->is_assignment_compatible(TYPE_NUMBER)) { + error(2100, expr->first->token, "numeric expression expected"); + } + const Expression *last = analyze(expr->last); + if (not last->type->is_assignment_compatible(TYPE_NUMBER)) { + error(2101, expr->last->token, "numeric expression expected"); + } + const Expression *step = analyze(expr->step); + if (not step->type->is_assignment_compatible(TYPE_NUMBER)) { + error(2102, expr->step->token, "numeric expression expected"); + } + const VariableExpression *range = new VariableExpression(dynamic_cast(scope.top()->lookupName("array__range"))); + std::vector args; + args.push_back(first); + args.push_back(last); + args.push_back(step); + return new FunctionCall(range, args); +} + +const Expression *Analyzer::analyze(const pt::DictionaryLiteralExpression *expr) +{ + std::vector> elements; + std::map keys; + const Type *elementtype = nullptr; + for (auto x: expr->elements) { + std::string key = x.first.text; + auto i = keys.find(key); + if (i != keys.end()) { + error2(3080, x.first, "duplicate key", i->second, "first key here"); + } + keys[key] = x.first; + const Expression *element = analyze(x.second); + if (elementtype == nullptr) { + elementtype = element->type; + } else if (not element->type->is_assignment_compatible(elementtype)) { + error(3072, x.second->token, "type mismatch"); + } + elements.push_back(std::make_pair(key, element)); + } + return new DictionaryLiteralExpression(elementtype, elements); +} + +const Expression *Analyzer::analyze(const pt::NilLiteralExpression *) +{ + return new ConstantNilExpression(); +} + +const Expression *Analyzer::analyze(const pt::IdentifierExpression *expr) +{ + const Name *name = scope.top()->lookupName(expr->name); + if (name == nullptr) { + error(3039, expr->token, "name not found: " + expr->name); + } + const Constant *constant = dynamic_cast(name); + if (constant != nullptr) { + return new ConstantExpression(constant); + } + const Variable *var = dynamic_cast(name); + if (var != nullptr) { + return new VariableExpression(var); + } + error(3040, expr->token, "name is not a constant or variable: " + expr->name); +} + +const Name *Analyzer::analyze_qualified_name(const pt::Expression *expr) +{ + const pt::IdentifierExpression *ident = dynamic_cast(expr); + if (ident != nullptr) { + return scope.top()->lookupName(ident->name); + } + const pt::DotExpression *dotted = dynamic_cast(expr); + if (dotted == nullptr) { + return nullptr; + } + const Name *base = analyze_qualified_name(dotted->base); + const Module *module = dynamic_cast(base); + if (module != nullptr) { + const Name *name = module->scope->lookupName(dotted->name.text); + if (name == nullptr) { + error(3134, dotted->name, "name not found: " + dotted->name.text); + } + return name; + } + return nullptr; +} + +const Expression *Analyzer::analyze(const pt::DotExpression *expr) +{ + const Name *name = analyze_qualified_name(expr); + if (name != nullptr) { + const Constant *constant = dynamic_cast(name); + if (constant != nullptr) { + return new ConstantExpression(constant); + } + const Variable *var = dynamic_cast(name); + if (var != nullptr) { + return new VariableExpression(var); + } + } + name = analyze_qualified_name(expr->base); + if (name != nullptr) { + const TypeEnum *enumtype = dynamic_cast(name); + if (enumtype != nullptr) { + auto name = enumtype->names.find(expr->name.text); + if (name == enumtype->names.end()) { + error2(3023, expr->name, "identifier not member of enum: " + expr->name.text, enumtype->declaration, "enum declared here"); + } + return new ConstantEnumExpression(enumtype, name->second); + } + } + const Expression *base = analyze(expr->base); + const TypeRecord *recordtype = dynamic_cast(base->type); + if (recordtype == nullptr) { + error(3046, expr->base->token, "not a record"); + } + if (dynamic_cast(recordtype) != nullptr) { + internal_error("record not defined yet"); + } + auto f = recordtype->field_names.find(expr->name.text); + if (f == recordtype->field_names.end()) { + error2(3045, expr->name, "field not found", recordtype->declaration, "record declared here"); + } + if (recordtype->fields[f->second].is_private && (functiontypes.empty() || functiontypes.top().first != recordtype)) { + error(3162, expr->name, "field is private"); + } + const Type *type = recordtype->fields[f->second].type; + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new ArrayReferenceIndexExpression(type, ref, new ConstantNumberExpression(number_from_uint32(static_cast(f->second))), true); + } else { + return new ArrayValueIndexExpression(type, base, new ConstantNumberExpression(number_from_uint32(static_cast(f->second))), true); + } +} + +const Expression *Analyzer::analyze(const pt::ArrowExpression *expr) +{ + const Expression *base = analyze(expr->base); + const TypePointer *pointertype = dynamic_cast(base->type); + if (pointertype == nullptr) { + error(3112, expr->base->token, "not a pointer"); + } + if (dynamic_cast(pointertype) == nullptr) { + error(3103, expr->base->token, "pointer must be a valid pointer"); + } + const TypeRecord *recordtype = pointertype->reftype; + if (recordtype == nullptr) { + error(3117, expr->base->token, "pointer must not be a generic pointer"); + } + if (dynamic_cast(recordtype) != nullptr) { + error2(3104, expr->base->token, "record not defined yet", recordtype->declaration, "forward declaration here"); + } + auto f = recordtype->field_names.find(expr->name.text); + if (f == recordtype->field_names.end()) { + error2(3111, expr->name, "field not found", recordtype->declaration, "record declared here"); + } + if (recordtype->fields[f->second].is_private && (functiontypes.empty() || functiontypes.top().first != recordtype)) { + error(3163, expr->name, "field is private"); + } + const Type *type = recordtype->fields[f->second].type; + const PointerDereferenceExpression *ref = new PointerDereferenceExpression(type, base); + return new ArrayReferenceIndexExpression(type, ref, new ConstantNumberExpression(number_from_uint32(static_cast(f->second))), false); +} + +const Expression *Analyzer::analyze(const pt::SubscriptExpression *expr) +{ + const Expression *base = analyze(expr->base); + const Expression *index = analyze(expr->index); + const Type *type = base->type; + const TypeArray *arraytype = dynamic_cast(type); + const TypeDictionary *dicttype = dynamic_cast(type); + if (arraytype != nullptr) { + if (not index->type->is_assignment_compatible(TYPE_NUMBER)) { + error2(3041, expr->index->token, "index must be a number", arraytype->declaration, "array declared here"); + } + type = arraytype->elementtype; + const ReferenceExpression *ref = dynamic_cast(base); + // This check for ArrayReferenceRangeExpression is a stupendous hack that + // allows expressions like a[LAST] (which is compiled as a[LAST TO LAST][0]) + // to work. Without this, the subscript [0] on the range expression tries + // to generate the address of the base so it can apply an INDEXAR opcode. + // Since the range expression is really a function call, it has no address. + if (ref != nullptr && dynamic_cast(ref) == nullptr) { + return new ArrayReferenceIndexExpression(type, ref, index, false); + } else { + return new ArrayValueIndexExpression(type, base, index, false); + } + } else if (dicttype != nullptr) { + if (not index->type->is_assignment_compatible(TYPE_STRING)) { + error2(3042, expr->index->token, "index must be a string", dicttype->declaration, "dictionary declared here"); + } + type = dicttype->elementtype; + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new DictionaryReferenceIndexExpression(type, ref, index); + } else { + return new DictionaryValueIndexExpression(type, base, index); + } + } else if (type == TYPE_STRING) { + if (not index->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3043, expr->index->token, "index must be a number"); + } + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new StringReferenceIndexExpression(ref, index, false, index, false, this); + } else { + return new StringValueIndexExpression(base, index, false, index, false, this); + } + } else { + error2(3044, expr->token, "not an array or dictionary", type->declaration, "declaration here"); + } +} + +const Expression *Analyzer::analyze(const pt::InterpolatedStringExpression *expr) +{ + const VariableExpression *concat = new VariableExpression(dynamic_cast(scope.top()->lookupName("concat"))); + const VariableExpression *format = new VariableExpression(dynamic_cast(scope.top()->lookupName("format"))); + const Expression *r = nullptr; + for (auto x: expr->parts) { + const Expression *e = analyze(x.first); + std::string fmt = x.second.text; + if (not fmt.empty()) { + format::Spec spec; + if (not format::parse(fmt, spec)) { + error(3133, x.second, "invalid format specification"); + } + } + const Expression *str; + if (e->type->is_assignment_compatible(TYPE_STRING)) { + str = e; + } else { + auto toString = e->type->methods.find("toString"); + if (toString == e->type->methods.end()) { + error(3132, x.first->token, "no toString() method found for type"); + } + { + std::vector args; + args.push_back(e); + str = new FunctionCall(new VariableExpression(toString->second), args); + } + } + if (not fmt.empty()) { + std::vector args; + args.push_back(str); + args.push_back(new ConstantStringExpression(fmt)); + str = new FunctionCall(format, args); + } + if (r == nullptr) { + r = str; + } else { + std::vector args; + args.push_back(r); + args.push_back(str); + r = new FunctionCall(concat, args); + } + } + return r; +} + +const Expression *Analyzer::analyze(const pt::FunctionCallExpression *expr) +{ + const pt::IdentifierExpression *fname = dynamic_cast(expr->base); + if (fname != nullptr) { + if (fname->name == "valueCopy") { + if (expr->args.size() != 2) { + error(3136, expr->rparen, "two arguments expected"); + } + const Expression *lhs_expr = analyze(expr->args[0].expr); + const ReferenceExpression *lhs = dynamic_cast(lhs_expr); + if (lhs == nullptr) { + error(3119, expr->args[0].expr->token, "expression is not assignable"); + } + if (lhs_expr->is_readonly && dynamic_cast(lhs_expr->type) == nullptr) { + error(3120, expr->args[0].expr->token, "valueCopy to readonly expression"); + } + const Expression *rhs = analyze(expr->args[1].expr); + const Type *ltype = lhs->type; + const TypePointer *lptype = dynamic_cast(ltype); + if (lptype != nullptr) { + if (dynamic_cast(lptype) == nullptr) { + error(3121, expr->args[0].expr->token, "valid pointer type required"); + } + ltype = lptype->reftype; + lhs = new PointerDereferenceExpression(ltype, lhs); + } + const Type *rtype = rhs->type; + const TypePointer *rptype = dynamic_cast(rtype); + if (rptype != nullptr) { + if (dynamic_cast(rptype) == nullptr) { + error(3122, expr->args[1].expr->token, "valid pointer type required"); + } + rtype = rptype->reftype; + rhs = new PointerDereferenceExpression(rtype, rhs); + } + if (not ltype->is_assignment_compatible(rtype)) { + error(3123, expr->args[1].expr->token, "type mismatch"); + } + std::vector vars; + vars.push_back(lhs); + return new StatementExpression(new AssignmentStatement(expr->token.line, vars, rhs)); + } + const TypeRecord *recordtype = dynamic_cast(scope.top()->lookupName(fname->name)); + if (recordtype != nullptr) { + if (expr->args.size() > recordtype->fields.size()) { + error2(3130, expr->args[recordtype->fields.size()].expr->token, "wrong number of fields", recordtype->declaration, "record declared here"); + } + std::vector elements; + auto f = recordtype->fields.begin(); + for (auto x: expr->args) { + const Expression *element = analyze(x.expr); + if (not element->type->is_assignment_compatible(f->type)) { + error2(3131, x.expr->token, "type mismatch", f->name, "field declared here"); + } + elements.push_back(element); + ++f; + } + return new RecordLiteralExpression(recordtype, elements); + } + } + const pt::DotExpression *dotmethod = dynamic_cast(expr->base); + const Expression *self = nullptr; + const Expression *func = nullptr; + if (dotmethod != nullptr) { + // This check avoids trying to evaluate foo.bar as an + // expression in foo.bar() where foo is actually a module. + bool is_module_call = false; + const pt::IdentifierExpression *ident = dynamic_cast(dotmethod->base); + if (ident != nullptr) { + const Name *name = scope.top()->lookupName(ident->name); + is_module_call = dynamic_cast(name) != nullptr; + } + if (not is_module_call) { + const Expression *base = analyze(dotmethod->base); + auto m = base->type->methods.find(dotmethod->name.text); + if (m == base->type->methods.end()) { + error(3137, dotmethod->name, "method not found"); + } else { + self = base; + } + func = new VariableExpression(m->second); + } else { + func = analyze(expr->base); + } + } else { + func = analyze(expr->base); + } + const TypeFunction *ftype = dynamic_cast(func->type); + if (ftype == nullptr) { + const TypeFunctionPointer *pf = dynamic_cast(func->type); + if (pf == nullptr) { + error(3017, expr->base->token, "not a function"); + } + ftype = pf->functype; + } + int param_index = 0; + std::vector args(ftype->params.size()); + if (self != nullptr) { + args[0] = self; + ++param_index; + } + for (auto a: expr->args) { + const Expression *e = analyze(a.expr); + if (param_index >= static_cast(ftype->params.size())) { + error(3096, a.expr->token, "too many parameters"); + } + int p; + if (param_index >= 0 && a.name.text.empty()) { + p = param_index; + param_index++; + } else { + // Now in named argument mode. + param_index = -1; + if (a.name.text.empty()) { + error(3145, a.expr->token, "parameter name must be specified"); + } + auto fp = ftype->params.begin(); + for (;;) { + if (a.name.text == (*fp)->declaration.text) { + break; + } + ++fp; + if (fp == ftype->params.end()) { + error(3146, a.name, "parameter name not found"); + } + } + p = static_cast(std::distance(ftype->params.begin(), fp)); + if (args[p] != nullptr) { + error(3147, a.name, "parameter already specified"); + } + } + if (dynamic_cast(e) != nullptr && a.mode.type != OUT) { + error2(3193, a.expr->token, "Underscore can only be used with OUT parameter", ftype->params[p]->declaration, "function argument here"); + } + if (ftype->params[p]->mode == ParameterType::IN) { + if (ftype->params[p]->type != nullptr) { + // TODO: Above check works around problem in sdl.RenderDrawLines. + // Something about a compound type in a predefined function parameter list. + if (not ftype->params[p]->type->is_assignment_compatible(e->type)) { + error2(3019, a.expr->token, "type mismatch", ftype->params[p]->declaration, "function argument here"); + } + } + } else { + const ReferenceExpression *ref = dynamic_cast(e); + if (ref == nullptr) { + error2(3018, a.expr->token, "function call argument must be reference", ftype->params[p]->declaration, "function argument here"); + } + if (ref->is_readonly) { + error(3106, a.expr->token, "readonly parameter to OUT"); + } + if (not e->type->is_assignment_compatible(ftype->params[p]->type)) { + error2(3194, a.expr->token, "type mismatch", ftype->params[p]->declaration, "function argument here"); + } + } + if (ftype->params[p]->mode == ParameterType::OUT && a.mode.type != OUT) { + error(3184, a.expr->token, "OUT keyword required"); + } else if (ftype->params[p]->mode == ParameterType::INOUT && a.mode.type != INOUT) { + error(3185, a.expr->token, "INOUT keyword required"); + } else if ((a.mode.type == IN && ftype->params[p]->mode != ParameterType::IN) + || (a.mode.type == INOUT && ftype->params[p]->mode != ParameterType::INOUT)) { + error(3186, a.mode, "parameter mode must match if specified"); + } + args[p] = e; + } + int p = 0; + for (auto a: args) { + if (a == nullptr) { + if (ftype->params[p]->default_value != nullptr) { + args[p] = ftype->params[p]->default_value; + } else { + error(3020, expr->rparen, "argument not specified for: " + ftype->params[p]->declaration.text); + } + } + p++; + } + return new FunctionCall(func, args); +} + +const Expression *Analyzer::analyze(const pt::UnaryPlusExpression *expr) +{ + const Expression *atom = analyze(expr->expr); + if (not atom->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3144, expr->expr->token, "number required"); + } + return atom; +} + +const Expression *Analyzer::analyze(const pt::UnaryMinusExpression *expr) +{ + const Expression *atom = analyze(expr->expr); + if (not atom->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3021, expr->expr->token, "number required for negation"); + } + return new UnaryMinusExpression(atom); +} + +const Expression *Analyzer::analyze(const pt::LogicalNotExpression *expr) +{ + const Expression *atom = analyze(expr->expr); + if (not atom->type->is_assignment_compatible(TYPE_BOOLEAN)) { + error(3022, expr->expr->token, "boolean required for logical not"); + } + return new LogicalNotExpression(atom); +} + +const Expression *Analyzer::analyze(const pt::ExponentiationExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new ExponentiationExpression(left, right); + } else { + error(3024, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::MultiplicationExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new MultiplicationExpression(left, right); + } else { + error(3025, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::DivisionExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new DivisionExpression(left, right); + } else { + error(3026, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::ModuloExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new ModuloExpression(left, right); + } else { + error(3027, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::AdditionExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new AdditionExpression(left, right); + } else if (left->type->is_assignment_compatible(TYPE_STRING) && right->type->is_assignment_compatible(TYPE_STRING)) { + error(3124, expr->token, "type mismatch (use & to concatenate strings)"); + } else { + error(3028, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::SubtractionExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_NUMBER) && right->type->is_assignment_compatible(TYPE_NUMBER)) { + return new SubtractionExpression(left, right); + } else { + error(3029, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::ConcatenationExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_STRING) && right->type->is_assignment_compatible(TYPE_STRING)) { + std::vector args; + args.push_back(left); + args.push_back(right); + return new FunctionCall(new VariableExpression(dynamic_cast(scope.top()->lookupName("concat"))), args); + } else if (left->type->is_assignment_compatible(TYPE_BYTES) && right->type->is_assignment_compatible(TYPE_BYTES)) { + std::vector args; + args.push_back(left); + args.push_back(right); + return new FunctionCall(new VariableExpression(dynamic_cast(scope.top()->lookupName("concatBytes"))), args); + } else if (dynamic_cast(left->type) != nullptr + && dynamic_cast(right->type) != nullptr + && dynamic_cast(left->type)->elementtype == dynamic_cast(right->type)->elementtype) { + std::vector args; + args.push_back(left); + args.push_back(right); + VariableExpression *ve = new VariableExpression(dynamic_cast(scope.top()->lookupName("array__concat"))); + // Since the array__concat function cannot be declared with its proper result type, + // we have to create a new appropriate function type based on the desired result type + // and the existing argument types. + ve->type = new TypeFunction(left->type, dynamic_cast(ve->type)->params); + return new FunctionCall(ve, args); + } else { + error(3116, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::ComparisonExpression *expr) +{ + const Expression *left = analyze(expr->left); + ComparisonExpression::Comparison comp = static_cast(expr->comp); // TODO: remove cast + const Expression *right = analyze(expr->right); + if (not left->type->is_assignment_compatible(right->type)) { + error(3030, expr->token, "type mismatch"); + } + if (left->type->is_assignment_compatible(TYPE_BOOLEAN)) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3031, expr->token, "comparison not available for Boolean"); + } + return new BooleanComparisonExpression(left, right, comp); + } else if (left->type->is_assignment_compatible(TYPE_NUMBER)) { + return new NumericComparisonExpression(left, right, comp); + } else if (left->type->is_assignment_compatible(TYPE_STRING) || left->type->is_assignment_compatible(TYPE_BYTES)) { + return new StringComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3032, expr->token, "comparison not available for Array"); + } + return new ArrayComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3033, expr->token, "comparison not available for Dictionary"); + } + return new DictionaryComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3034, expr->token, "comparison not available for RECORD"); + } + return new ArrayComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + return new NumericComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3100, expr->token, "comparison not available for POINTER"); + } + return new PointerComparisonExpression(left, right, comp); + } else if (dynamic_cast(left->type) != nullptr) { + if (comp != ComparisonExpression::EQ && comp != ComparisonExpression::NE) { + error(3180, expr->token, "comparison not available for FUNCTION"); + } + return new FunctionPointerComparisonExpression(left, right, comp); + } else { + internal_error("unknown type in parseComparison"); + } +} + +const Expression *Analyzer::analyze(const pt::ChainedComparisonExpression *expr) +{ + std::vector comps; + for (auto x: expr->comps) { + const Expression *expr = analyze(x); + const ComparisonExpression *comp = dynamic_cast(expr); + if (comp == nullptr) { + internal_error("ChainedComparisonExpression"); + } + comps.push_back(comp); + } + return new ChainedComparisonExpression(comps); +} + +const Expression *Analyzer::analyze(const pt::MembershipExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + const TypeArray *arraytype = dynamic_cast(right->type); + const TypeDictionary *dicttype = dynamic_cast(right->type); + if (arraytype != nullptr) { + if (not left->type->is_assignment_compatible(arraytype->elementtype)) { + error(3082, expr->left->token, "type mismatch"); + } + return new ArrayInExpression(left, right); + } else if (dicttype != nullptr) { + if (not left->type->is_assignment_compatible(TYPE_STRING)) { + error(3083, expr->left->token, "type mismatch"); + } + return new DictionaryInExpression(left, right); + } else { + error(3081, expr->right->token, "IN must be used with Array or Dictionary"); + } +} + +const Expression *Analyzer::analyze(const pt::ConjunctionExpression *expr) +{ + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_BOOLEAN) && right->type->is_assignment_compatible(TYPE_BOOLEAN)) { + return new ConjunctionExpression(left, right); + } else { + error(3035, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::DisjunctionExpression *expr) +{ + // This is a pretty long-winded way of identifying common tautological + // expressions of the form: + // a # "x" OR a # "y" + // The following only works for expressions like the above where: + // - the identifier ("a") is a simple identifier + // - both # expressions are in the above order + // - it's actually an OR expression + // A more general purpose and cleaner implementation would be welcome. + const pt::ComparisonExpression *lne = dynamic_cast(expr->left); + const pt::ComparisonExpression *rne = dynamic_cast(expr->right); + if (lne != nullptr && rne != nullptr && lne->comp == pt::ComparisonExpression::NE && rne->comp == pt::ComparisonExpression::NE) { + const pt::IdentifierExpression *lid = dynamic_cast(lne->left); + const pt::IdentifierExpression *rid = dynamic_cast(rne->left); + if (lid != nullptr && rid != nullptr && lid->name == rid->name) { + const pt::BooleanLiteralExpression *lble = dynamic_cast(lne->right); + const pt::BooleanLiteralExpression *rble = dynamic_cast(rne->right); + const pt::NumberLiteralExpression *lnle = dynamic_cast(lne->right); + const pt::NumberLiteralExpression *rnle = dynamic_cast(rne->right); + const pt::StringLiteralExpression *lsle = dynamic_cast(lne->right); + const pt::StringLiteralExpression *rsle = dynamic_cast(rne->right); + if ((lble != nullptr && rble != nullptr && lble->value != rble->value) + || (lnle != nullptr && rnle != nullptr && number_is_not_equal(lnle->value, rnle->value)) + || (lsle != nullptr && rsle != nullptr && lsle->value != rsle->value)) { + error(3172, expr->token, "boolean expression is always false"); + } + } + } + + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (left->type->is_assignment_compatible(TYPE_BOOLEAN) && right->type->is_assignment_compatible(TYPE_BOOLEAN)) { + return new DisjunctionExpression(left, right); + } else { + error(3036, expr->token, "type mismatch"); + } +} + +const Expression *Analyzer::analyze(const pt::ConditionalExpression *expr) +{ + const Expression *cond = analyze(expr->cond); + const Expression *left = analyze(expr->left); + const Expression *right = analyze(expr->right); + if (not left->type->is_assignment_compatible(right->type)) { + error(3037, expr->left->token, "type of THEN and ELSE must match"); + } + return new ConditionalExpression(cond, left, right); +} + +const Expression *Analyzer::analyze(const pt::NewRecordExpression *expr) +{ + const TypeRecord *type = dynamic_cast(analyze(expr->type)); + if (type == nullptr) { + error(3099, expr->type->token, "record type expected"); + } + return new NewRecordExpression(type); +} + +const Expression *Analyzer::analyze(const pt::ValidPointerExpression * /*expr*/) +{ + // This should never happen because ValidPointerExpression is handled elsewhere. + internal_error("TODO pt::Expression"); +} + +const Expression *Analyzer::analyze(const pt::RangeSubscriptExpression *expr) +{ + const Expression *base = analyze(expr->base); + const Expression *first = analyze(expr->range->first); + const Expression *last = analyze(expr->range->last); + if (not first->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3141, expr->range->first->token, "range index must be a number"); + } + if (not last->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3142, expr->range->last->token, "range index must be a number"); + } + const Type *type = base->type; + const TypeArray *arraytype = dynamic_cast(type); + if (arraytype != nullptr) { + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new ArrayReferenceRangeExpression(ref, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } else { + return new ArrayValueRangeExpression(base, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } + } else if (type == TYPE_STRING) { + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new StringReferenceIndexExpression(ref, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } else { + return new StringValueIndexExpression(base, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } + } else if (type == TYPE_BYTES) { + const ReferenceExpression *ref = dynamic_cast(base); + if (ref != nullptr) { + return new BytesReferenceIndexExpression(ref, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } else { + return new BytesValueIndexExpression(base, first, expr->range->first_from_end, last, expr->range->last_from_end, this); + } + } else { + error2(3143, expr->base->token, "not an array or string", type->declaration, "declaration here"); + } +} + +Type *Analyzer::deserialize_type(Scope *scope, const std::string &descriptor, std::string::size_type &i) +{ + switch (descriptor.at(i)) { + case 'Z': i++; return TYPE_NOTHING; + case 'B': i++; return TYPE_BOOLEAN; + case 'N': i++; return TYPE_NUMBER; + case 'S': i++; return TYPE_STRING; + case 'Y': i++; return TYPE_BYTES; + case 'A': { + i++; + if (descriptor.at(i) != '<') { + internal_error("deserialize_type"); + } + i++; + const Type *type = deserialize_type(scope, descriptor, i); + if (descriptor.at(i) != '>') { + internal_error("deserialize_type"); + } + i++; + return new TypeArray(Token(), type); + } + case 'D': { + i++; + if (descriptor.at(i) != '<') { + internal_error("deserialize_type"); + } + i++; + const Type *type = deserialize_type(scope, descriptor, i); + if (descriptor.at(i) != '>') { + internal_error("deserialize_type"); + } + i++; + return new TypeDictionary(Token(), type); + } + case 'R': { + i++; + std::vector fields; + if (descriptor.at(i) != '[') { + internal_error("deserialize_type"); + } + i++; + while (descriptor.at(i) != ']') { + bool is_private = false; + if (descriptor.at(i) == '!') { + is_private = true; + i++; + } + std::string name; + while (descriptor.at(i) != ':') { + name.push_back(descriptor.at(i)); + i++; + } + i++; + const Type *type = deserialize_type(scope, descriptor, i); + Token token; + token.text = name; + fields.push_back(TypeRecord::Field(token, type, is_private)); + if (descriptor.at(i) == ',') { + i++; + } + } + i++; + return new TypeRecord(Token(), "record", fields); + } + case 'E': { + i++; + std::map names; + if (descriptor.at(i) != '[') { + internal_error("deserialize_type"); + } + i++; + int value = 0; + for (;;) { + std::string name; + while (descriptor.at(i) != ',' && descriptor.at(i) != ']') { + name.push_back(descriptor.at(i)); + i++; + } + names[name] = value; + value++; + if (descriptor.at(i) == ']') { + break; + } + i++; + } + i++; + return new TypeEnum(Token(), "enum", names, this); + } + case 'F': { + i++; + std::vector params; + if (descriptor.at(i) != '[') { + internal_error("deserialize_type"); + } + i++; + while (descriptor.at(i) != ']') { + ParameterType::Mode mode = ParameterType::IN; + switch (descriptor.at(i)) { + case '>': mode = ParameterType::IN; break; + case '*': mode = ParameterType::INOUT; break; + case '<': mode = ParameterType::OUT; break; + default: + internal_error("unexpected mode indicator"); + } + i++; + std::string name; + while (descriptor.at(i) != ':') { + name.push_back(descriptor.at(i)); + i++; + } + i++; + const Type *type = deserialize_type(scope, descriptor, i); + Token token; + token.text = name; + // TODO: default value + params.push_back(new ParameterType(token, mode, type, nullptr)); + if (descriptor.at(i) == ',') { + i++; + } + } + i++; + if (descriptor.at(i) != ':') { + internal_error("deserialize_type"); + } + i++; + const Type *returntype = deserialize_type(scope, descriptor, i); + return new TypeFunction(returntype, params); + } + case 'P': { + i++; + if (descriptor.at(i) != '<') { + internal_error("deserialize_type"); + } + i++; + const TypeRecord *rectype = nullptr; + if (descriptor.at(i) != '>') { + const Type *type = deserialize_type(scope, descriptor, i); + rectype = dynamic_cast(type); + if (rectype == nullptr) { + internal_error("deserialize_type"); + } + } + if (descriptor.at(i) != '>') { + internal_error("deserialize_type"); + } + i++; + return new TypePointer(Token(), rectype); + } + case 'Q': { + i++; + TypeFunction *f = dynamic_cast(deserialize_type(scope, descriptor, i)); + return new TypeFunctionPointer(Token(), f); + } + case '~': { + i++; + std::string name; + while (descriptor.at(i) != ';') { + name.push_back(descriptor.at(i)); + i++; + } + i++; + Scope *s = scope; + std::string localname = name; + auto dot = name.find('.'); + if (dot != std::string::npos) { + const Module *module = import_module(Token(), name.substr(0, dot)); + s = module->scope; + localname = name.substr(dot+1); + } + Type *type = dynamic_cast(s->lookupName(localname)); + if (type == nullptr) { + internal_error("reference to unknown type in exports: " + name); + } + return type; + } + default: + internal_error("TODO unimplemented type in deserialize_type: " + descriptor); + } +} + +Type *Analyzer::deserialize_type(Scope *scope, const std::string &descriptor) +{ + std::string::size_type i = 0; + Type *r = deserialize_type(scope, descriptor, i); + if (i != descriptor.length()) { + internal_error("deserialize_type: " + descriptor + " " + std::to_string(i)); + } + return r; +} + +const Statement *Analyzer::analyze(const pt::ImportDeclaration *declaration) +{ + const Token &localname = declaration->alias.type != NONE ? declaration->alias : declaration->name.type != NONE ? declaration->name : declaration->module; + if (not scope.top()->allocateName(localname, localname.text)) { + error2(3114, localname, "duplicate definition of name", scope.top()->getDeclaration(localname.text), "first declaration here"); + } + Module *module = import_module(declaration->module, declaration->module.text); + rtl_import(declaration->module.text, module); + if (declaration->name.type == NONE) { + scope.top()->addName(declaration->token, localname.text, module); + } else { + const Name *name = module->scope->lookupName(declaration->name.text); + if (name != nullptr) { + scope.top()->addName(declaration->token, localname.text, module->scope->lookupName(declaration->name.text)); + } else { + error(3176, declaration->name, "name not found in module"); + } + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze(const pt::TypeDeclaration *declaration) +{ + std::string name = declaration->token.text; + if (not scope.top()->allocateName(declaration->token, name)) { + error2(3013, declaration->token, "duplicate identifier", scope.top()->getDeclaration(name), "first declaration here"); + } + TypeRecord *actual_record = nullptr; + const pt::TypeRecord *recdecl = dynamic_cast(declaration->type); + if (recdecl != nullptr) { + // Support recursive record type declarations. + actual_record = new TypeRecord(recdecl->token, name, std::vector()); + scope.top()->addName(declaration->token, name, actual_record); + } + const Type *type = analyze(declaration->type, name); + if (actual_record != nullptr) { + const TypeRecord *rectype = dynamic_cast(type); + const_cast &>(actual_record->fields) = rectype->fields; + const_cast &>(actual_record->field_names) = rectype->field_names; + type = actual_record; + } else { + Type *t = const_cast(type); + if (type != TYPE_BOOLEAN && type != TYPE_NUMBER && type != TYPE_STRING && type != TYPE_BYTES) { + const_cast(t->name) = name; + } + scope.top()->addName(declaration->token, name, t); // Still ugly. + } + const TypeRecord *rectype = dynamic_cast(type); + if (rectype != nullptr) { + scope.top()->resolveForward(name, rectype); + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_decl(const pt::ConstantDeclaration *declaration) +{ + std::string name = declaration->name.text; + if (not scope.top()->allocateName(declaration->name, name)) { + error2(3014, declaration->token, "duplicate identifier", scope.top()->getDeclaration(declaration->name.text), "first declaration here"); + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_body(const pt::ConstantDeclaration *declaration) +{ + std::string name = declaration->name.text; + const Type *type = analyze(declaration->type); + const Expression *value = analyze(declaration->value); + if (not type->is_assignment_compatible(value->type)) { + error(3015, declaration->value->token, "type mismatch"); + } + if (not value->is_constant) { + error(3016, declaration->value->token, "value must be constant"); + } + if (type == TYPE_BOOLEAN) { + value = new ConstantBooleanExpression(value->eval_boolean(declaration->value->token)); + } else if (type == TYPE_NUMBER) { + value = new ConstantNumberExpression(value->eval_number(declaration->value->token)); + } else if (type == TYPE_STRING) { + value = new ConstantStringExpression(value->eval_string(declaration->value->token)); + } + scope.top()->addName(declaration->name, name, new Constant(declaration->name, name, value)); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_decl(const pt::VariableDeclaration *declaration) +{ + for (auto name: declaration->names) { + if (not scope.top()->allocateName(name, name.text)) { + error2(3038, name, "duplicate identifier", scope.top()->getDeclaration(name.text), "first declaration here"); + } + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_body(const pt::VariableDeclaration *declaration) +{ + const Type *type = analyze(declaration->type); + std::vector variables; + for (auto name: declaration->names) { + Variable *v; + if (frame.top() == global_frame) { + v = new GlobalVariable(name, name.text, type, false); + } else { + v = new LocalVariable(name, name.text, type, false); + } + variables.push_back(v); + } + std::vector refs; + const Expression *expr = nullptr; + if (declaration->value != nullptr) { + expr = analyze(declaration->value); + if (not type->is_assignment_compatible(expr->type)) { + error(3113, declaration->value->token, "type mismatch"); + } + } + for (auto v: variables) { + scope.top()->addName(v->declaration, v->name, v, true); + refs.push_back(new VariableExpression(v)); + } + if (declaration->value != nullptr) { + return new AssignmentStatement(declaration->token.line, refs, expr); + } else { + return new ResetStatement(declaration->token.line, refs); + } +} + +const Statement *Analyzer::analyze_decl(const pt::LetDeclaration *declaration) +{ + if (not scope.top()->allocateName(declaration->name, declaration->name.text)) { + error2(3139, declaration->name, "duplicate identifier", scope.top()->getDeclaration(declaration->name.text), "first declaration here"); + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_body(const pt::LetDeclaration *declaration) +{ + const Type *type = analyze(declaration->type); + const Expression *expr = analyze(declaration->value); + if (not type->is_assignment_compatible(expr->type)) { + error(3140, declaration->value->token, "type mismatch"); + } + const TypePointer *ptype = dynamic_cast(type); + if (ptype != nullptr && dynamic_cast(expr) != nullptr) { + type = new TypeValidPointer(ptype); + } + Variable *v; + if (frame.top() == global_frame) { + v = new GlobalVariable(declaration->name, declaration->name.text, type, true); + } else { + v = new LocalVariable(declaration->name, declaration->name.text, type, true); + } + scope.top()->addName(v->declaration, v->name, v, true); + std::vector refs; + refs.push_back(new VariableExpression(v)); + return new AssignmentStatement(declaration->token.line, refs, expr); +} + +const Statement *Analyzer::analyze_decl(const pt::FunctionDeclaration *declaration) +{ + const std::string classtype = declaration->type.text; + Type *type = nullptr; + if (not classtype.empty()) { + Name *tname = scope.top()->lookupName(classtype); + if (tname == nullptr) { + auto decl = scope.top()->getDeclaration(classtype); + if (decl.type == NONE) { + error(3126, declaration->type, "type name not found"); + } else { + error2(3127, declaration->type, "type name is not a type", decl, "declaration here"); + } + } + type = dynamic_cast(tname); + if (type == nullptr) { + error2(3138, declaration->type, "type name is not a type", tname->declaration, "declaration here"); + } + } + std::string name = declaration->name.text; + if (type == nullptr && not scope.top()->allocateName(declaration->name, name)) { + error2(3047, declaration->name, "duplicate definition of name", scope.top()->getDeclaration(name), "first declaration here"); + } + const Type *returntype = declaration->returntype != nullptr ? analyze(declaration->returntype) : TYPE_NOTHING; + std::vector args; + bool in_default = false; + for (auto x: declaration->args) { + ParameterType::Mode mode = ParameterType::IN; + switch (x->mode) { + case pt::FunctionParameter::IN: mode = ParameterType::IN; break; + case pt::FunctionParameter::INOUT: mode = ParameterType::INOUT; break; + case pt::FunctionParameter::OUT: mode = ParameterType::OUT; break; + } + const Type *ptype = analyze(x->type); + if (type != nullptr && args.empty()) { + if (not ptype->is_assignment_compatible(type)) { + error(3128, x->type->token, "expected self parameter"); + } + } + const Expression *def = nullptr; + if (x->default_value != nullptr) { + if (mode != ParameterType::IN) { + error(3175, x->default_value->token, "default value only available for IN parameters"); + } + in_default = true; + def = analyze(x->default_value); + if (not def->is_constant) { + error(3148, x->default_value->token, "default value not constant"); + } + } else if (in_default) { + error(3150, x->token, "default value must be specified for this parameter"); + } + if (scope.top()->lookupName(x->name.text)) { + error(3174, x->name, "duplicate identifier"); + } + FunctionParameter *fp = new FunctionParameter(x->name, x->name.text, ptype, mode, def); + args.push_back(fp); + } + if (type != nullptr && args.empty()) { + error(3129, declaration->rparen, "expected self parameter"); + } + Function *function; + if (type != nullptr) { + auto f = type->methods.find(name); + if (f != type->methods.end()) { + function = dynamic_cast(f->second); + } else { + function = new Function(declaration->name, name, returntype, frame.top(), scope.top(), args); + type->methods[name] = function; + } + } else { + Name *ident = scope.top()->lookupName(name); + function = dynamic_cast(ident); + if (function == nullptr) { + function = new Function(declaration->name, name, returntype, frame.top(), scope.top(), args); + scope.top()->addName(declaration->name, name, function); + } + } + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_body(const pt::FunctionDeclaration *declaration) +{ + const std::string classtype = declaration->type.text; + Type *type = nullptr; + if (not classtype.empty()) { + Name *tname = scope.top()->lookupName(classtype); + if (tname == nullptr) { + internal_error("type name not found"); + } + type = dynamic_cast(tname); + if (type == nullptr) { + internal_error("type name is not a type"); + } + } + Function *function; + if (type != nullptr) { + auto f = type->methods.find(declaration->name.text); + function = dynamic_cast(f->second); + } else { + function = dynamic_cast(scope.top()->lookupName(declaration->name.text)); + } + for (auto x: declaration->args) { + Token decl = scope.top()->getDeclaration(x->name.text); + if (decl.type != NONE) { + error2(3179, x->name, "duplicate identifier", decl, "first declaration here"); + } + } + frame.push(function->frame); + scope.push(function->scope); + functiontypes.push(std::make_pair(type, dynamic_cast(function->type))); + loops.push(std::list>()); + function->statements = analyze(declaration->body); + const Type *returntype = dynamic_cast(function->type)->returntype; + if (returntype != TYPE_NOTHING) { + if (function->statements.empty() || not function->statements.back()->always_returns()) { + error(3085, declaration->end_function, "missing RETURN statement"); + } + } + loops.pop(); + functiontypes.pop(); + scope.top()->checkForward(); + scope.pop(); + frame.pop(); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_decl(const pt::ExternalFunctionDeclaration *declaration) +{ + std::string name = declaration->name.text; + if (not scope.top()->allocateName(declaration->name, name)) { + error2(3092, declaration->name, "duplicate identifier", scope.top()->getDeclaration(name), "first declaration here"); + } + const Type *returntype = declaration->returntype != nullptr ? analyze(declaration->returntype) : TYPE_NOTHING; + std::vector args; + bool in_default = false; + for (auto x: declaration->args) { + ParameterType::Mode mode = ParameterType::IN; + switch (x->mode) { + case pt::FunctionParameter::IN: mode = ParameterType::IN; break; + case pt::FunctionParameter::INOUT: mode = ParameterType::INOUT; break; + case pt::FunctionParameter::OUT: mode = ParameterType::OUT; break; + } + const Type *ptype = analyze(x->type); + const Expression *def = nullptr; + if (x->default_value != nullptr) { + in_default = true; + def = analyze(x->default_value); + if (not def->is_constant) { + error(3149, x->default_value->token, "default value not constant"); + } + } else if (in_default) { + error(3151, x->token, "default value must be specified for this parameter"); + } + FunctionParameter *fp = new FunctionParameter(x->name, x->name.text, ptype, mode, def); + args.push_back(fp); + } + ExternalFunction *function = new ExternalFunction(declaration->name, name, returntype, frame.top(), scope.top(), args); + scope.top()->addName(declaration->name, name, function); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze_body(const pt::ExternalFunctionDeclaration *declaration) +{ + std::string name = declaration->name.text; + ExternalFunction *function = dynamic_cast(scope.top()->lookupName(name)); + + const DictionaryLiteralExpression *dict = dynamic_cast(analyze(declaration->dict)); + if (not dict->is_constant) { + error(3071, declaration->dict->token, "constant dictionary expected"); + } + if (dynamic_cast(dict->elementtype) == nullptr) { + error(3073, declaration->dict->token, "top level dictionary element not dictionary"); + } + for (auto elem: dict->dict) { + auto *d = dynamic_cast(elem.second); + if (not d->dict.empty() && not d->elementtype->is_assignment_compatible(TYPE_STRING)) { + error(3074, declaration->dict->token, "sub level dictionary must have string elements"); + } + } + + auto klibrary = dict->dict.find("library"); + if (klibrary == dict->dict.end()) { + error(3075, declaration->dict->token, "\"library\" key not found"); + } + auto &library_dict = dynamic_cast(klibrary->second)->dict; + auto kname = library_dict.find("name"); + if (kname == library_dict.end()) { + error(3076, declaration->dict->token, "\"name\" key not found in library"); + } + std::string library_name = kname->second->eval_string(declaration->dict->token); + + auto ktypes = dict->dict.find("types"); + if (ktypes == dict->dict.end()) { + error(3077, declaration->dict->token, "\"types\" key not found"); + } + auto &types_dict = dynamic_cast(ktypes->second)->dict; + std::map param_types; + for (auto paramtype: types_dict) { + param_types[paramtype.first] = paramtype.second->eval_string(declaration->dict->token); + } + for (auto p: function->params) { + if (p->mode == ParameterType::OUT) { + error(3164, p->declaration, "OUT parameter mode not supported (use INOUT): " + p->name); + } + if (param_types.find(p->name) == param_types.end()) { + error(3097, declaration->dict->token, "parameter type missing: " + p->name); + } + } + + function->library_name = library_name; + function->param_types = param_types; + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze(const pt::NativeFunctionDeclaration *declaration) +{ + std::string name = declaration->name.text; + if (not scope.top()->allocateName(declaration->name, name)) { + error2(3166, declaration->name, "duplicate identifier", scope.top()->getDeclaration(name), "first declaration here"); + } + const Type *returntype = declaration->returntype != nullptr ? analyze(declaration->returntype) : TYPE_NOTHING; + std::vector params; + bool in_default = false; + for (auto x: declaration->args) { + ParameterType::Mode mode = ParameterType::IN; + switch (x->mode) { + case pt::FunctionParameter::IN: mode = ParameterType::IN; break; + case pt::FunctionParameter::INOUT: mode = ParameterType::INOUT; break; + case pt::FunctionParameter::OUT: mode = ParameterType::OUT; break; + } + const Type *ptype = analyze(x->type); + const Expression *def = nullptr; + if (x->default_value != nullptr) { + in_default = true; + def = analyze(x->default_value); + if (not def->is_constant) { + error(3167, x->default_value->token, "default value not constant"); + } + } else if (in_default) { + error(3168, x->token, "default value must be specified for this parameter"); + } + ParameterType *pt = new ParameterType(x->name, mode, ptype, def); + params.push_back(pt); + } + PredefinedFunction *function = new PredefinedFunction(path_stripext(path_basename(program->source_path))+"$"+name, new TypeFunction(returntype, params)); + scope.top()->addName(declaration->name, name, function); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze(const pt::ExceptionDeclaration *declaration) +{ + std::string name = declaration->name.text; + if (not scope.top()->allocateName(declaration->name, name)) { + error2(3115, declaration->token, "duplicate definition of name", scope.top()->getDeclaration(name), "first declaration here"); + } + if (name.length() < 9 || name.substr(name.length()-9) != "Exception") { + error(3198, declaration->name, "Exception name must end in 'Exception'"); + } + scope.top()->addName(declaration->name, name, new Exception(declaration->name, name)); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze(const pt::ExportDeclaration *declaration) +{ + if (scope.top()->getDeclaration(declaration->name.text).type == NONE) { + error(3152, declaration->name, "export name not declared"); + } + exports.insert(declaration->name.text); + return new NullStatement(declaration->token.line); +} + +const Statement *Analyzer::analyze(const pt::MainBlock *statement) +{ + Token name = statement->token; + name.type = IDENTIFIER; + name.text = "MAIN"; + std::vector args; + const pt::FunctionDeclaration *main = new pt::FunctionDeclaration(statement->token, Token(), name, nullptr, args, Token(), statement->body, Token()); + analyze_decl(main); + return analyze_body(main); +} + +std::vector Analyzer::analyze(const std::vector &statement) +{ + std::vector statements; + DeclarationAnalyzer da(this, statements); + for (auto d: statement) { + d->accept(&da); + } + StatementAnalyzer sa(this, statements); + bool lastexit = false; + for (auto s: statement) { + s->accept(&sa); + if (dynamic_cast(s) != nullptr + || dynamic_cast(s) != nullptr + || dynamic_cast(s) != nullptr + || dynamic_cast(s) != nullptr) { + lastexit = true; + } else if (lastexit) { + error(3165, s->token, "unreachable code"); + } + } + return statements; +} + +const Statement *Analyzer::analyze(const pt::AssertStatement *statement) +{ + const Expression *expr = analyze(statement->expr); + if (not expr->type->is_assignment_compatible(TYPE_BOOLEAN)) { + error(3173, statement->expr->token, "boolean value expected"); + } + std::vector statements = analyze(statement->body); + return new AssertStatement(statement->token.line, statements, expr, statement->source); +} + +const Statement *Analyzer::analyze(const pt::AssignmentStatement *statement) +{ + if (statement->variables.size() != 1) { + internal_error("unexpected multiple assign statement"); + } + const Expression *expr = analyze(statement->variables[0]); + const ReferenceExpression *ref = dynamic_cast(expr); + if (ref == nullptr) { + error(3058, statement->variables[0]->token, "expression is not assignable"); + } + if (expr->is_readonly) { + error(3105, statement->variables[0]->token, "assignment to readonly expression"); + } + const Expression *rhs = analyze(statement->expr); + if (not expr->type->is_assignment_compatible(rhs->type)) { + error(3057, statement->expr->token, "type mismatch"); + } + std::vector vars; + vars.push_back(ref); + return new AssignmentStatement(statement->token.line, vars, rhs); +} + +const Statement *Analyzer::analyze(const pt::CaseStatement *statement) +{ + const Expression *expr = analyze(statement->expr); + if (not expr->type->is_assignment_compatible(TYPE_NUMBER) && not expr->type->is_assignment_compatible(TYPE_STRING) && dynamic_cast(expr->type) == nullptr) { + error(3050, statement->expr->token, "CASE expression must be Number, String, or ENUM"); + } + std::vector, std::vector>> clauses; + for (auto x: statement->clauses) { + std::vector conditions; + for (auto c: x.first) { + const pt::CaseStatement::ComparisonWhenCondition *cwc = dynamic_cast(c); + const pt::CaseStatement::RangeWhenCondition *rwc = dynamic_cast(c); + if (cwc != nullptr) { + const Expression *when = analyze(cwc->expr); + if (not when->type->is_assignment_compatible(expr->type)) { + error(3051, cwc->expr->token, "type mismatch"); + } + if (not when->is_constant) { + error(3052, cwc->expr->token, "WHEN condition must be constant"); + } + ComparisonExpression::Comparison comp = static_cast(cwc->comp); // TODO: remove cast + const CaseStatement::WhenCondition *cond = new CaseStatement::ComparisonWhenCondition(cwc->expr->token, comp, when); + for (auto clause: clauses) { + for (auto c: clause.first) { + if (cond->overlaps(c)) { + error2(3062, cwc->expr->token, "overlapping case condition", c->token, "overlaps here"); + } + } + } + for (auto c: conditions) { + if (cond->overlaps(c)) { + error2(3063, cwc->expr->token, "overlapping case condition", c->token, "overlaps here"); + } + } + conditions.push_back(cond); + } + if (rwc != nullptr) { + const Expression *when = analyze(rwc->low_expr); + if (not when->type->is_assignment_compatible(expr->type)) { + error(3053, rwc->low_expr->token, "type mismatch"); + } + if (not when->is_constant) { + error(3054, rwc->low_expr->token, "WHEN condition must be constant"); + } + const Expression *when2 = analyze(rwc->high_expr); + if (not when2->type->is_assignment_compatible(expr->type)) { + error(3055, rwc->high_expr->token, "type mismatch"); + } + if (not when2->is_constant) { + error(3056, rwc->high_expr->token, "WHEN condition must be constant"); + } + if (when->type->is_assignment_compatible(TYPE_NUMBER) || dynamic_cast(when->type) != nullptr) { + if (number_is_greater(when->eval_number(rwc->low_expr->token), when2->eval_number(rwc->high_expr->token))) { + error(3109, rwc->high_expr->token, "WHEN numeric range condition must be low..high"); + } + } else if (when->type->is_assignment_compatible(TYPE_STRING)) { + if (when->eval_string(rwc->low_expr->token) > when2->eval_string(rwc->high_expr->token)) { + error(3110, rwc->high_expr->token, "WHEN string range condition must be low..high"); + } + } else { + internal_error("range condition type"); + } + const CaseStatement::WhenCondition *cond = new CaseStatement::RangeWhenCondition(rwc->low_expr->token, when, when2); + for (auto clause: clauses) { + for (auto c: clause.first) { + if (cond->overlaps(c)) { + error2(3064, rwc->low_expr->token, "overlapping case condition", c->token, "overlaps here"); + } + } + } + for (auto c: conditions) { + if (cond->overlaps(c)) { + error2(3065, rwc->low_expr->token, "overlapping case condition", c->token, "overlaps here"); + } + } + conditions.push_back(cond); + } + } + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(x.second); + scope.pop(); + clauses.push_back(std::make_pair(conditions, statements)); + } + return new CaseStatement(statement->token.line, expr, clauses); +} + +namespace overlap { + +static bool operator==(const Number &x, const Number &y) { return number_is_equal(x, y); } +static bool operator!=(const Number &x, const Number &y) { return number_is_not_equal(x, y); } +static bool operator<(const Number &x, const Number &y) { return number_is_less(x, y); } +static bool operator>(const Number &x, const Number &y) { return number_is_greater(x, y); } +static bool operator<=(const Number &x, const Number &y) { return number_is_less_equal(x, y); } +static bool operator>=(const Number &x, const Number &y) { return number_is_greater_equal(x, y); } + +template bool check(ComparisonExpression::Comparison comp1, const T &value1, ComparisonExpression::Comparison comp2, const T &value2) +{ + switch (comp1) { + case ComparisonExpression::EQ: + switch (comp2) { + case ComparisonExpression::EQ: return value1 == value2; + case ComparisonExpression::NE: return value1 != value2; + case ComparisonExpression::LT: return value1 < value2; + case ComparisonExpression::GT: return value1 > value2; + case ComparisonExpression::LE: return value1 <= value2; + case ComparisonExpression::GE: return value1 >= value2; + } + break; + case ComparisonExpression::NE: + switch (comp2) { + case ComparisonExpression::EQ: return value1 != value2; + default: return true; + } + break; + case ComparisonExpression::LT: + switch (comp2) { + case ComparisonExpression::EQ: return value1 < value2; + case ComparisonExpression::NE: return true; + case ComparisonExpression::LT: return true; + case ComparisonExpression::GT: return value1 > value2; + case ComparisonExpression::LE: return true; + case ComparisonExpression::GE: return value1 > value2; + } + break; + case ComparisonExpression::GT: + switch (comp2) { + case ComparisonExpression::EQ: return value1 > value2; + case ComparisonExpression::NE: return true; + case ComparisonExpression::LT: return value1 < value2; + case ComparisonExpression::GT: return true; + case ComparisonExpression::LE: return value1 < value2; + case ComparisonExpression::GE: return true; + } + break; + case ComparisonExpression::LE: + switch (comp2) { + case ComparisonExpression::EQ: return value1 >= value2; + case ComparisonExpression::NE: return true; + case ComparisonExpression::LT: return true; + case ComparisonExpression::GT: return value1 > value2; + case ComparisonExpression::LE: return true; + case ComparisonExpression::GE: return value1 >= value2; + } + break; + case ComparisonExpression::GE: + switch (comp2) { + case ComparisonExpression::EQ: return value1 <= value2; + case ComparisonExpression::NE: return true; + case ComparisonExpression::LT: return value1 < value2; + case ComparisonExpression::GT: return true; + case ComparisonExpression::LE: return value1 <= value2; + case ComparisonExpression::GE: return true; + } + break; + } + return false; +} + +template bool check(ComparisonExpression::Comparison comp1, const T &value1, const T &value2low, const T &value2high) +{ + switch (comp1) { + case ComparisonExpression::EQ: return value1 >= value2low && value1 <= value2high; + case ComparisonExpression::NE: return value1 != value2low || value1 != value2high; + case ComparisonExpression::LT: return value1 > value2low; + case ComparisonExpression::GT: return value1 > value2high; + case ComparisonExpression::LE: return value1 >= value2low; + case ComparisonExpression::GE: return value1 <= value2high; + } + return false; +} + +template bool check(const T &value1low, const T &value1high, const T &value2low, const T &value2high) +{ + return value1high >= value2low && value1low <= value2high; +} + +} // namespace overlap + +bool CaseStatement::ComparisonWhenCondition::overlaps(const WhenCondition *cond) const +{ + const ComparisonWhenCondition *cwhen = dynamic_cast(cond); + const RangeWhenCondition *rwhen = dynamic_cast(cond); + if (cwhen != nullptr) { + if (expr->type->is_assignment_compatible(TYPE_NUMBER) || dynamic_cast(expr->type) != nullptr) { + return overlap::check(comp, expr->eval_number(cond->token), cwhen->comp, cwhen->expr->eval_number(cond->token)); + } else if (expr->type->is_assignment_compatible(TYPE_STRING)) { + return overlap::check(comp, expr->eval_string(cond->token), cwhen->comp, cwhen->expr->eval_string(cond->token)); + } else { + internal_error("ComparisonWhenCondition"); + } + } else if (rwhen != nullptr) { + if (expr->type->is_assignment_compatible(TYPE_NUMBER) || dynamic_cast(expr->type) != nullptr) { + return overlap::check(comp, expr->eval_number(cond->token), rwhen->low_expr->eval_number(cond->token), rwhen->high_expr->eval_number(cond->token)); + } else if (expr->type->is_assignment_compatible(TYPE_STRING)) { + return overlap::check(comp, expr->eval_string(cond->token), rwhen->low_expr->eval_string(cond->token), rwhen->high_expr->eval_string(cond->token)); + } else { + internal_error("ComparisonWhenCondition"); + } + } else { + internal_error("ComparisonWhenCondition"); + } +} + +bool CaseStatement::RangeWhenCondition::overlaps(const WhenCondition *cond) const +{ + const ComparisonWhenCondition *cwhen = dynamic_cast(cond); + const RangeWhenCondition *rwhen = dynamic_cast(cond); + if (cwhen != nullptr) { + if (low_expr->type->is_assignment_compatible(TYPE_NUMBER) || dynamic_cast(low_expr->type) != nullptr) { + return overlap::check(cwhen->comp, cwhen->expr->eval_number(cwhen->token), low_expr->eval_number(cwhen->token), high_expr->eval_number(cwhen->token)); + } else if (low_expr->type->is_assignment_compatible(TYPE_STRING)) { + return overlap::check(cwhen->comp, cwhen->expr->eval_string(cwhen->token), low_expr->eval_string(cwhen->token), high_expr->eval_string(cwhen->token)); + } else { + internal_error("RangeWhenCondition"); + } + } else if (rwhen != nullptr) { + if (low_expr->type->is_assignment_compatible(TYPE_NUMBER) || dynamic_cast(low_expr->type) != nullptr) { + return overlap::check(low_expr->eval_number(cwhen->token), high_expr->eval_number(cwhen->token), rwhen->low_expr->eval_number(cwhen->token), rwhen->high_expr->eval_number(cwhen->token)); + } else if (low_expr->type->is_assignment_compatible(TYPE_STRING)) { + return overlap::check(low_expr->eval_string(cwhen->token), high_expr->eval_string(cwhen->token), rwhen->low_expr->eval_string(cwhen->token), rwhen->high_expr->eval_string(cwhen->token)); + } else { + internal_error("RangeWhenCondition"); + } + } else { + internal_error("RangeWhenCondition"); + } +} + +const Statement *Analyzer::analyze(const pt::ExitStatement *statement) +{ + if (statement->type == FUNCTION) { + if (functiontypes.empty()) { + error(3107, statement->token, "EXIT FUNCTION not allowed outside function"); + } else if (functiontypes.top().second->returntype != TYPE_NOTHING) { + error(3108, statement->token, "function must return a value"); + } + return new ReturnStatement(statement->token.line, nullptr); + } + TokenType type = statement->type; + if (not loops.empty()) { + for (auto j = loops.top().rbegin(); j != loops.top().rend(); ++j) { + if (j->first == type) { + return new ExitStatement(statement->token.line, j->second); + } + } + } + error(3078, statement->token, "no matching loop found in current scope"); +} + +const Statement *Analyzer::analyze(const pt::ExpressionStatement *statement) +{ + const Expression *expr = analyze(statement->expr); + if (expr->type == TYPE_NOTHING) { + return new ExpressionStatement(statement->token.line, analyze(statement->expr)); + } + if (dynamic_cast(expr) != nullptr) { + error(3060, statement->expr->token, "':=' expected"); + } + if (dynamic_cast(expr) != nullptr) { + error(3059, statement->token, "return value unused"); + } + error(3061, statement->token, "Unexpected"); +} + +const Statement *Analyzer::analyze(const pt::ForStatement *statement) +{ + scope.push(new Scope(scope.top(), frame.top())); + Token name = statement->var; + if (scope.top()->lookupName(name.text) != nullptr) { + error2(3118, name, "duplicate identifier", scope.top()->getDeclaration(name.text), "first declaration here"); + } + Variable *var; + if (frame.top() == global_frame) { + var = new GlobalVariable(name, name.text, TYPE_NUMBER, false); + } else { + var = new LocalVariable(name, name.text, TYPE_NUMBER, false); + } + scope.top()->addName(var->declaration, var->name, var, true); + var->is_readonly = true; + Variable *bound; + if (frame.top() == global_frame) { + bound = new GlobalVariable(Token(), std::to_string(reinterpret_cast(statement)), TYPE_NUMBER, false); + } else { + bound = new LocalVariable(Token(), std::to_string(reinterpret_cast(statement)), TYPE_NUMBER, false); + } + // TODO: Need better way of declaring unnamed local variable. + scope.top()->addName(Token(), std::to_string(reinterpret_cast(statement)), bound, true); + const Expression *start = analyze(statement->start); + if (not start->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3067, statement->start->token, "numeric expression expected"); + } + const Expression *end = analyze(statement->end); + if (not end->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3068, statement->end->token, "numeric expression expected"); + } + const Expression *step = nullptr; + if (statement->step != nullptr) { + step = analyze(statement->step); + if (step->type != TYPE_NUMBER) { + error(3069, statement->step->token, "numeric expression expected"); + } + if (not step->is_constant) { + error(3070, statement->step->token, "numeric expression must be constant"); + } + if (number_is_zero(step->eval_number(statement->step->token))) { + error(3091, statement->step->token, "STEP value cannot be zero"); + } + } else { + step = new ConstantNumberExpression(number_from_uint32(1)); + } + // TODO: make loop_id a void* + unsigned int loop_id = static_cast(reinterpret_cast(statement)); + loops.top().push_back(std::make_pair(FOR, loop_id)); + std::vector statements = analyze(statement->body); + scope.pop(); + loops.top().pop_back(); + var->is_readonly = false; + return new ForStatement(statement->token.line, loop_id, new VariableExpression(var), start, end, step, new VariableExpression(bound), statements); +} + +const Statement *Analyzer::analyze(const pt::ForeachStatement *statement) +{ + scope.push(new Scope(scope.top(), frame.top())); + Token var_name = statement->var; + if (scope.top()->lookupName(var_name.text) != nullptr) { + error2(3169, var_name, "duplicate identifier", scope.top()->getDeclaration(var_name.text), "first declaration here"); + } + const Expression *array = analyze(statement->array); + const TypeArray *atype = dynamic_cast(array->type); + if (atype == nullptr) { + error(3170, statement->array->token, "array expected"); + } + Variable *var; + if (frame.top() == global_frame) { + var = new GlobalVariable(var_name, var_name.text, atype->elementtype, false); + } else { + var = new LocalVariable(var_name, var_name.text, atype->elementtype, false); + } + scope.top()->addName(var->declaration, var->name, var, true); + var->is_readonly = true; + + Token index_name = statement->index; + Variable *index; + if (index_name.type == IDENTIFIER) { + if (scope.top()->lookupName(index_name.text) != nullptr) { + error2(3171, index_name, "duplicate identifier", scope.top()->getDeclaration(index_name.text), "first declaration here"); + } + if (frame.top() == global_frame) { + index = new GlobalVariable(index_name, index_name.text, TYPE_NUMBER, false); + } else { + index = new LocalVariable(index_name, index_name.text, TYPE_NUMBER, false); + } + scope.top()->addName(index->declaration, index->name, index, true); + } else { + // TODO: Need better way of declaring unnamed local variable. + index_name.text = std::to_string(reinterpret_cast(statement)+1); + if (frame.top() == global_frame) { + index = new GlobalVariable(Token(), index_name.text, TYPE_NUMBER, false); + } else { + index = new LocalVariable(Token(), index_name.text, TYPE_NUMBER, false); + } + scope.top()->addName(Token(), index_name.text, index, true); + } + index->is_readonly = true; + + Variable *bound; + // TODO: Need better way of declaring unnamed local variable. + std::string bound_name = std::to_string(reinterpret_cast(statement)); + if (frame.top() == global_frame) { + bound = new GlobalVariable(Token(), bound_name, TYPE_NUMBER, false); + } else { + bound = new LocalVariable(Token(), bound_name, TYPE_NUMBER, false); + } + scope.top()->addName(Token(), bound_name, bound, true); + // TODO: make loop_id a void* + unsigned int loop_id = static_cast(reinterpret_cast(statement)); + loops.top().push_back(std::make_pair(FOREACH, loop_id)); + std::vector statements = analyze(statement->body); + scope.pop(); + loops.top().pop_back(); + var->is_readonly = false; + return new ForeachStatement(statement->token.line, loop_id, new VariableExpression(var), array, new VariableExpression(index), new VariableExpression(bound), statements); +} + +const Statement *Analyzer::analyze(const pt::IfStatement *statement) +{ + scope.push(new Scope(scope.top(), frame.top())); + std::vector>> condition_statements; + for (auto c: statement->condition_statements) { + const Expression *cond = nullptr; + const pt::ValidPointerExpression *valid = dynamic_cast(c.first); + if (valid != nullptr) { + for (auto v: valid->tests) { + if (not v.shorthand and scope.top()->lookupName(v.name.text) != nullptr) { + error2(3102, v.name, "duplicate identifier", scope.top()->getDeclaration(v.name.text), "first declaration here"); + } + const Expression *ptr = analyze(v.expr); + const TypePointer *ptrtype = dynamic_cast(ptr->type); + if (ptrtype == nullptr) { + error(3101, v.expr->token, "pointer type expression expected"); + } + const TypeValidPointer *vtype = new TypeValidPointer(ptrtype); + Variable *var; + // TODO: Try to make this a local variable always (give the global scope a local space). + if (functiontypes.empty()) { + var = new GlobalVariable(v.name, v.name.text, vtype, true); + } else { + var = new LocalVariable(v.name, v.name.text, vtype, true); + } + scope.top()->addName(v.name, v.name.text, var, true, v.shorthand); + const Expression *ve = new ValidPointerExpression(var, ptr); + if (cond == nullptr) { + cond = ve; + } else { + cond = new ConjunctionExpression(cond, ve); + } + } + } else { + cond = analyze(c.first); + if (not cond->type->is_assignment_compatible(TYPE_BOOLEAN)) { + error(3048, c.first->token, "boolean value expected"); + } + } + scope.push(new Scope(scope.top(), frame.top())); + condition_statements.push_back(std::make_pair(cond, analyze(c.second))); + scope.pop(); + } + std::vector else_statements = analyze(statement->else_statements); + scope.pop(); + return new IfStatement(statement->token.line, condition_statements, else_statements); +} + +const Statement *Analyzer::analyze(const pt::IncrementStatement *statement) +{ + const Expression *e = analyze(statement->expr); + if (not e->type->is_assignment_compatible(TYPE_NUMBER)) { + error(3187, statement->expr->token, "INC and DEC parameter must be Number"); + } + const ReferenceExpression *ref = dynamic_cast(e); + if (ref == nullptr) { + error(3188, statement->expr->token, "INC and DEC call argument must be reference"); + } + if (ref->is_readonly) { + error(3189, statement->expr->token, "readonly parameter to INC or DEC"); + } + return new IncrementStatement(statement->token.line, ref, statement->delta); +} + +const Statement *Analyzer::analyze(const pt::LoopStatement *statement) +{ + unsigned int loop_id = static_cast(reinterpret_cast(statement)); + loops.top().push_back(std::make_pair(LOOP, loop_id)); + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(statement->body); + scope.pop(); + loops.top().pop_back(); + return new LoopStatement(statement->token.line, loop_id, statements); +} + +const Statement *Analyzer::analyze(const pt::NextStatement *statement) +{ + TokenType type = statement->type; + if (not loops.empty()) { + for (auto j = loops.top().rbegin(); j != loops.top().rend(); ++j) { + if (j->first == type) { + return new NextStatement(statement->token.line, j->second); + } + } + } + error(3084, statement->token, "no matching loop found in current scope"); +} + +const Statement *Analyzer::analyze(const pt::RaiseStatement *statement) +{ + Scope *s = scope.top(); + if (statement->name.first.type != NONE) { + const Name *modname = scope.top()->lookupName(statement->name.first.text); + if (modname == nullptr) { + error(3157, statement->name.first, "module name not found: " + statement->name.first.text); + } + const Module *mod = dynamic_cast(modname); + if (mod == nullptr) { + error(3158, statement->name.first, "module name expected"); + } + s = mod->scope; + } + const Name *name = s->lookupName(statement->name.second.text); + if (name == nullptr) { + error(3089, statement->name.second, "exception not found: " + statement->name.second.text); + } + const Exception *exception = dynamic_cast(name); + if (exception == nullptr) { + error2(3090, statement->name.second, "name not an exception", name->declaration, "declaration here"); + } + const Expression *info; + if (statement->info != nullptr) { + info = analyze(statement->info); + } else { + std::vector values; + info = new RecordLiteralExpression(dynamic_cast(s->lookupName("ExceptionInfo")->type), values); + } + return new RaiseStatement(statement->token.line, exception, info); +} + +const Statement *Analyzer::analyze(const pt::RepeatStatement *statement) +{ + unsigned int loop_id = static_cast(reinterpret_cast(statement)); + loops.top().push_back(std::make_pair(REPEAT, loop_id)); + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(statement->body); + const Expression *cond = analyze(statement->cond); + if (not cond->type->is_assignment_compatible(TYPE_BOOLEAN)) { + error(3086, statement->cond->token, "boolean value expected"); + } + scope.pop(); + loops.top().pop_back(); + return new RepeatStatement(statement->token.line, loop_id, cond, statements); +} + +const Statement *Analyzer::analyze(const pt::ReturnStatement *statement) +{ + const Expression *expr = analyze(statement->expr); + if (functiontypes.empty()) { + error(3093, statement->token, "RETURN not allowed outside function"); + } else if (functiontypes.top().second->returntype == TYPE_NOTHING) { + error(3094, statement->token, "function does not return a value"); + } else if (not functiontypes.top().second->returntype->is_assignment_compatible(expr->type)) { + error(3095, statement->expr->token, "type mismatch in RETURN"); + } + return new ReturnStatement(statement->token.line, expr); +} + +const Statement *Analyzer::analyze(const pt::TryStatement *statement) +{ + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(statement->body); + scope.pop(); + std::vector, std::vector>> catches; + for (auto x: statement->catches) { + Scope *s = scope.top(); + if (x.first.at(0).first.type != NONE) { + const Name *modname = scope.top()->lookupName(x.first.at(0).first.text); + if (modname == nullptr) { + error(3159, x.first.at(0).first, "module name not found: " + x.first.at(0).first.text); + } + const Module *mod = dynamic_cast(modname); + if (mod == nullptr) { + error(3160, x.first.at(0).first, "module name expected"); + } + s = mod->scope; + } + const Name *name = s->lookupName(x.first.at(0).second.text); + if (name == nullptr) { + error(3087, x.first.at(0).second, "exception not found: " + x.first.at(0).second.text); + } + const Exception *exception = dynamic_cast(name); + if (exception == nullptr) { + error2(3088, x.first.at(0).second, "name not an exception", name->declaration, "declaration here"); + } + std::vector exceptions; + exceptions.push_back(exception); + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(x.second); + scope.pop(); + catches.push_back(std::make_pair(exceptions, statements)); + } + return new TryStatement(statement->token.line, statements, catches); +} + +const Statement *Analyzer::analyze(const pt::WhileStatement *statement) +{ + const Expression *cond = analyze(statement->cond); + if (not cond->type->is_assignment_compatible(TYPE_BOOLEAN)) { + error(3049, statement->cond->token, "boolean value expected"); + } + unsigned int loop_id = static_cast(reinterpret_cast(statement)); + loops.top().push_back(std::make_pair(WHILE, loop_id)); + scope.push(new Scope(scope.top(), frame.top())); + std::vector statements = analyze(statement->body); + scope.pop(); + loops.top().pop_back(); + return new WhileStatement(statement->token.line, loop_id, cond, statements); +} + +const Program *Analyzer::analyze() +{ + Program *r = new Program(program->source_path, program->source_hash); + global_frame = r->frame; + global_scope = r->scope; + frame.push(global_frame); + scope.push(global_scope); + + // TODO: This bit makes sure that the native constants are + // actually available in the module where they are declared. + // There's certainly a better way that doesn't involve this + // roundabout way. + init_builtin_constants(global_scope); + std::string module_name = program->source_path; + std::string::size_type i = module_name.find_last_of("/\\"); + if (i != std::string::npos) { + module_name = module_name.substr(i+1); + } + if (module_name.size() >= 6 && module_name.substr(module_name.size() - 5) == ".neon") { + module_name = module_name.substr(0, module_name.size() - 5); + } + init_builtin_constants(module_name, global_scope); + + loops.push(std::list>()); + r->statements = analyze(program->body); + loops.pop(); + r->scope->checkForward(); + for (auto n: exports) { + const Name *name = scope.top()->lookupName(n); + if (name == nullptr) { + internal_error("export name not found"); + } + if (r->exports.find(n) != r->exports.end()) { + internal_error("export name already exported"); + } + r->exports[n] = name; + } + scope.pop(); + return r; +} + +class UninitialisedFinder: public pt::IParseTreeVisitor { +public: + UninitialisedFinder(): variables(), assigned(), out_parameters(), outer_scope(nullptr) { + variables.push_back(std::map>()); + } + UninitialisedFinder(const UninitialisedFinder *outer): variables(), assigned(), out_parameters(), outer_scope(outer) { + variables.push_back(std::map>()); + } + virtual void visit(const pt::TypeSimple *) {} + virtual void visit(const pt::TypeEnum *) {} + virtual void visit(const pt::TypeRecord *) {} + virtual void visit(const pt::TypePointer *) {} + virtual void visit(const pt::TypeFunctionPointer *) {} + virtual void visit(const pt::TypeParameterised *) {} + virtual void visit(const pt::TypeImport *) {} + + virtual void visit(const pt::DummyExpression *) {} + virtual void visit(const pt::IdentityExpression *node) { node->expr->accept(this); } + virtual void visit(const pt::BooleanLiteralExpression *) {} + virtual void visit(const pt::NumberLiteralExpression *) {} + virtual void visit(const pt::StringLiteralExpression *) {} + virtual void visit(const pt::FileLiteralExpression *) {} + virtual void visit(const pt::BytesLiteralExpression *) {} + virtual void visit(const pt::ArrayLiteralExpression *node) { for (auto x: node->elements) x->accept(this); } + virtual void visit(const pt::ArrayLiteralRangeExpression *node) { node->first->accept(this); node->last->accept(this); node->step->accept(this); } + virtual void visit(const pt::DictionaryLiteralExpression *node) { for (auto x: node->elements) x.second->accept(this); } + virtual void visit(const pt::NilLiteralExpression *) {} + virtual void visit(const pt::IdentifierExpression *node) { + for (auto v = variables.rbegin(); v != variables.rend(); ++v) { + auto i = v->find(node->name); + if (i != v->end() && not i->second.second) { + error2(3190, node->token, "Uninitialised variable: " + node->name, i->second.first, "Variable declared here"); + } + } + } + virtual void visit(const pt::DotExpression *node) { node->base->accept(this); } + virtual void visit(const pt::ArrowExpression *node) { node->base->accept(this); } + virtual void visit(const pt::SubscriptExpression *node) { node->base->accept(this); node->index->accept(this); } + virtual void visit(const pt::InterpolatedStringExpression *node) { for (auto x: node->parts) x.first->accept(this); } + virtual void visit(const pt::FunctionCallExpression *node) { + node->base->accept(this); + for (auto x: node->args) { + if (x.mode.type == OUT) { + const pt::IdentifierExpression *expr = dynamic_cast(x.expr); + if (expr != nullptr) { + mark_assigned(expr->name); + } + } else { + x.expr->accept(this); + } + } + } + virtual void visit(const pt::UnaryPlusExpression *node) { node->expr->accept(this); } + virtual void visit(const pt::UnaryMinusExpression *node) { node->expr->accept(this); } + virtual void visit(const pt::LogicalNotExpression *node) { node->expr->accept(this); } + virtual void visit(const pt::ExponentiationExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::MultiplicationExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::DivisionExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ModuloExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::AdditionExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::SubtractionExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ConcatenationExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ComparisonExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ChainedComparisonExpression *node) { for (auto x: node->comps) x->accept(this); } + virtual void visit(const pt::MembershipExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ConjunctionExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::DisjunctionExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::ConditionalExpression *node) { node->left->accept(this); node->right->accept(this); } + virtual void visit(const pt::NewRecordExpression *) {} + virtual void visit(const pt::ValidPointerExpression *node) { for (auto x: node->tests) x.expr->accept(this); } + virtual void visit(const pt::RangeSubscriptExpression *node) { node->base->accept(this); node->range->first->accept(this); node->range->last->accept(this); } + + virtual void visit(const pt::ImportDeclaration *) {} + virtual void visit(const pt::TypeDeclaration *) {} + virtual void visit(const pt::ConstantDeclaration *) {} + virtual void visit(const pt::VariableDeclaration *node) { + if (node->value != nullptr) { + node->value->accept(this); + } else { + for (auto name: node->names) { + variables.back()[name.text] = std::make_pair(name, false); + } + } + } + virtual void visit(const pt::LetDeclaration *node) { + node->value->accept(this); + } + virtual void visit(const pt::FunctionDeclaration *node) { + UninitialisedFinder uf; + for (auto a: node->args) { + if (a->mode == pt::FunctionParameter::OUT) { + uf.variables.back()[a->name.text] = std::make_pair(a->name, false); + uf.out_parameters.push_back(a->name.text); + } + } + for (auto s: node->body) { + s->accept(&uf); + } + uf.check_out_parameters(node->end_function); + } + virtual void visit(const pt::ExternalFunctionDeclaration *) {} + virtual void visit(const pt::NativeFunctionDeclaration *) {} + virtual void visit(const pt::ExceptionDeclaration *) {} + virtual void visit(const pt::ExportDeclaration *) {} + virtual void visit(const pt::MainBlock *) {} + + virtual void visit(const pt::AssertStatement *node) { node->expr->accept(this); } + virtual void visit(const pt::AssignmentStatement *node) { + node->expr->accept(this); + for (auto x: node->variables) { + const pt::IdentifierExpression *expr = dynamic_cast(x); + if (expr != nullptr) { + mark_assigned(expr->name); + } + } + } + virtual void visit(const pt::CaseStatement *node) { + node->expr->accept(this); + std::set assigned; + bool first = true; + for (auto c: node->clauses) { + for (auto w: c.first) { + auto *cwc = dynamic_cast(w); + auto *rwc = dynamic_cast(w); + if (cwc != nullptr) { + cwc->expr->accept(this); + } else if (rwc != nullptr) { + rwc->low_expr->accept(this); + rwc->high_expr->accept(this); + } + } + UninitialisedFinder uf(this); + for (auto s: c.second) { + s->accept(&uf); + } + if (first) { + assigned = uf.assigned; + first = false; + } else { + intersect(assigned, uf.assigned); + } + } + for (auto a: assigned) { + mark_assigned(a); + } + } + virtual void visit(const pt::ExitStatement *node) { + if (node->type == FUNCTION) { + check_out_parameters(node->token); + } + } + virtual void visit(const pt::ExpressionStatement *node) { node->expr->accept(this); } + virtual void visit(const pt::ForStatement *node) { + node->start->accept(this); + node->end->accept(this); + if (node->step != nullptr) { + node->step->accept(this); + } + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::ForeachStatement *node) { + node->array->accept(this); + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::IfStatement *node) { + std::set assigned; + bool first = true; + for (auto x: node->condition_statements) { + x.first->accept(this); + UninitialisedFinder uf(this); + for (auto s: x.second) { + s->accept(&uf); + } + if (first) { + assigned = uf.assigned; + first = false; + } else { + intersect(assigned, uf.assigned); + } + } + UninitialisedFinder uf(this); + for (auto s: node->else_statements) { + s->accept(&uf); + } + intersect(assigned, uf.assigned); + for (auto a: assigned) { + mark_assigned(a); + } + } + virtual void visit(const pt::IncrementStatement *node) { + node->expr->accept(this); + } + virtual void visit(const pt::LoopStatement *node) { + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::NextStatement *) {} + virtual void visit(const pt::RaiseStatement *) {} + virtual void visit(const pt::RepeatStatement *node) { + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::ReturnStatement *node) { + node->expr->accept(this); + check_out_parameters(node->token); + } + virtual void visit(const pt::TryStatement *node) { + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::WhileStatement *node) { + node->cond->accept(this); + variables.push_back(std::map>()); + for (auto s: node->body) { + s->accept(this); + } + variables.pop_back(); + } + virtual void visit(const pt::Program *node) { + for (auto s: node->body) { + s->accept(this); + } + } +private: + std::list>> variables; + std::set assigned; + std::vector out_parameters; + const UninitialisedFinder *outer_scope; + + void mark_assigned(const std::string &name) { + for (auto v = variables.rbegin(); v != variables.rend(); ++v) { + auto i = v->find(name); + if (i != v->end()) { + i->second.second = true; + break; + } + } + assigned.insert(name); + } + + void check_out_parameters(const Token &token) + { + const UninitialisedFinder *top_finder = this; + while (top_finder->outer_scope != nullptr) { + top_finder = top_finder->outer_scope; + } + for (auto p: top_finder->out_parameters) { + for (const UninitialisedFinder *uf = this; uf != nullptr; uf = uf->outer_scope) { + if (uf->assigned.find(p) != uf->assigned.end()) { + break; + } + for (auto v = uf->variables.rbegin(); v != uf->variables.rend(); ++v) { + auto i = v->find(p); + if (i != v->end()) { + if (i->second.second) { + uf = nullptr; + break; + } else { + error2(3191, token, "Uninitialised OUT parameter: " + p, i->second.first, "Variable declared here"); + } + } + } + } + } + } + + template void intersect(std::set &lhs, const std::set &rhs) { + for (auto i = lhs.begin(); i != lhs.end(); ) { + if (rhs.find(*i) == rhs.end()) { + auto tmp = i; + ++i; + lhs.erase(tmp); + } else { + ++i; + } + } + } + +private: + UninitialisedFinder(const UninitialisedFinder &); + UninitialisedFinder &operator=(const UninitialisedFinder &); +}; + +const Program *analyze(ICompilerSupport *support, const pt::Program *program) +{ + const Program *r = Analyzer(support, program).analyze(); + + // Find uninitalised variables. + UninitialisedFinder uf; + program->accept(&uf); + + // Find unused imports. + for (size_t i = 0; i < r->frame->getCount(); i++) { + Frame::Slot s = r->frame->getSlot(i); + if (dynamic_cast(s.ref) != nullptr && not s.referenced) { + error(3192, s.token, "Unused import"); + } + } + + return r; +} diff --git a/src/analyzer.h b/src/analyzer.h new file mode 100644 index 0000000000..1f27a86bb8 --- /dev/null +++ b/src/analyzer.h @@ -0,0 +1,14 @@ +#ifndef ANALYZER_H +#define ANALYZER_H + +#include + +#include "bytecode.h" + +class ICompilerSupport; +class Program; +namespace pt { class Program; } + +const Program *analyze(ICompilerSupport *support, const pt::Program *program); + +#endif diff --git a/src/ast.cpp b/src/ast.cpp index be30a2630a..31e0616c23 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -3,16 +3,20 @@ #include #include #include +#include +#include "intrinsic.h" #include "rtl_compile.h" +#include "rtl_exec.h" TypeNothing *TYPE_NOTHING = new TypeNothing(); +TypeDummy *TYPE_DUMMY = new TypeDummy(); TypeBoolean *TYPE_BOOLEAN = new TypeBoolean(); -TypeNumber *TYPE_NUMBER = new TypeNumber(); +TypeNumber *TYPE_NUMBER = new TypeNumber(Token()); TypeString *TYPE_STRING = new TypeString(); -TypeArray *TYPE_ARRAY_NUMBER = new TypeArray(TYPE_NUMBER); -TypeArray *TYPE_ARRAY_STRING = new TypeArray(TYPE_STRING); -TypePointer *TYPE_POINTER = new TypePointer(nullptr); +TypeBytes *TYPE_BYTES = new TypeBytes(); +TypeArray *TYPE_ARRAY_NUMBER = new TypeArray(Token(), TYPE_NUMBER); +TypeArray *TYPE_ARRAY_STRING = new TypeArray(Token(), TYPE_STRING); TypeModule *TYPE_MODULE = new TypeModule(); TypeException *TYPE_EXCEPTION = new TypeException(); @@ -22,51 +26,363 @@ void AstNode::dump(std::ostream &out, int depth) const dumpsubnodes(out, depth); } -TypeArray::TypeArray(const Type *elementtype) - : Type("array"), +std::string TypeBoolean::serialize(const Expression *value) const +{ + return value->eval_boolean() ? std::string(1, 1) : std::string(1, 0); +} + +const Expression *TypeBoolean::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + unsigned char b = value.at(i); + i++; + return new ConstantBooleanExpression(b != 0); +} + +std::string TypeNumber::serialize(const Expression *value) const +{ + Number x = value->eval_number(); + return std::string(reinterpret_cast(&x), sizeof(x)); +} + +const Expression *TypeNumber::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + // TODO: endian + Number x; + memcpy(&x, &value.at(i), sizeof(Number)); + i += sizeof(Number); + return new ConstantNumberExpression(x); +} + +std::string TypeString::serialize(const std::string &value) +{ + uint32_t len = static_cast(value.length()); + std::string r; + r.push_back(static_cast(len >> 24) & 0xff); + r.push_back(static_cast(len >> 16) & 0xff); + r.push_back(static_cast(len >> 8) & 0xff); + r.push_back(static_cast(len & 0xff)); + return r + value; +} + +std::string TypeString::serialize(const Expression *value) const +{ + return serialize(value->eval_string()); +} + +std::string TypeString::deserialize_string(const Bytecode::Bytes &value, int &i) +{ + uint32_t len = (value.at(i) << 24) | (value.at(i+1) << 16) | (value.at(i+2) << 8) | value.at(i+3); + std::string s(&value.at(i+4), &value.at(i+4)+len); + i += 4 + len; + return s; +} + +const Expression *TypeString::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + return new ConstantStringExpression(deserialize_string(value, i)); +} + +TypeArray::TypeArray(const Token &declaration, const Type *elementtype) + : Type(declaration, "array"), elementtype(elementtype) { { std::vector params; - params.push_back(new ParameterType(ParameterType::INOUT, this)); - methods["size"] = new PredefinedFunction("array.size", new TypeFunction(TYPE_NUMBER, params)); + params.push_back(new ParameterType(Token(), ParameterType::INOUT, this, nullptr)); + params.push_back(new ParameterType(Token(), ParameterType::IN, elementtype, nullptr)); + methods["append"] = new PredefinedFunction("array__append", new TypeFunction(TYPE_NOTHING, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::INOUT, this, nullptr)); + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["extend"] = new PredefinedFunction("array__extend", new TypeFunction(TYPE_NOTHING, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::INOUT, this, nullptr)); + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_NUMBER, nullptr)); + methods["resize"] = new PredefinedFunction("array__resize", new TypeFunction(TYPE_NOTHING, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["size"] = new PredefinedFunction("array__size", new TypeFunction(TYPE_NUMBER, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + // TODO: This is just a hack to make this work for now. + // Need to do this properly in a general purpose way. + if (elementtype == TYPE_NUMBER) { + methods["toBytes"] = new PredefinedFunction("array__toBytes__number", new TypeFunction(TYPE_BYTES, params)); + methods["toString"] = new PredefinedFunction("array__toString__number", new TypeFunction(TYPE_STRING, params)); + } else if (elementtype == TYPE_STRING) { + methods["toString"] = new PredefinedFunction("array__toString__string", new TypeFunction(TYPE_STRING, params)); + } } } -bool TypeArray::is_equivalent(const Type *rhs) const +bool TypeFunction::is_assignment_compatible(const Type *rhs) const +{ + // TODO: There needs to be a mechanism for reporting more detail about why the + // type does not match. There are quite a few reasons for this to return false, + // and the user would probably appreciate more detail. + const TypeFunction *f = dynamic_cast(rhs); + if (f == nullptr) { + const TypeFunctionPointer *p = dynamic_cast(rhs); + if (p == nullptr) { + return false; + } + f = p->functype; + } + if (not returntype->is_assignment_compatible(f->returntype)) { + return false; + } + if (params.size() != f->params.size()) { + return false; + } + for (size_t i = 0; i < params.size(); i++) { + if (params[i]->declaration.text != f->params[i]->declaration.text) { + return false; + } + if (params[i]->mode != f->params[i]->mode) { + return false; + } + if (not f->params[i]->type->is_assignment_compatible(params[i]->type)) { + return false; + } + } + return true; +} + +bool TypeArray::is_assignment_compatible(const Type *rhs) const { const TypeArray *a = dynamic_cast(rhs); if (a == nullptr) { return false; } - return elementtype == nullptr || a->elementtype == nullptr || elementtype->is_equivalent(a->elementtype); + return elementtype == nullptr || a->elementtype == nullptr || elementtype->is_assignment_compatible(a->elementtype); } -bool TypeDictionary::is_equivalent(const Type *rhs) const +std::string TypeArray::serialize(const Expression *value) const +{ + std::string r; + const ArrayLiteralExpression *a = dynamic_cast(value); + r.push_back(static_cast(a->elements.size() >> 24) & 0xff); + r.push_back(static_cast(a->elements.size() >> 16) & 0xff); + r.push_back(static_cast(a->elements.size() >> 8) & 0xff); + r.push_back(static_cast(a->elements.size() & 0xff)); + for (auto x: a->elements) { + r.append(a->elementtype->serialize(x)); + } + return r; +} + +const Expression *TypeArray::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + std::vector elements; + uint32_t len = (value.at(i) << 24) | (value.at(i+1) << 16) | (value.at(i+2) << 8) | value.at(i+3); + i += 4; + while (len > 0) { + elements.push_back(elementtype->deserialize_value(value, i)); + len--; + } + return new ArrayLiteralExpression(elementtype, elements); +} + +TypeDictionary::TypeDictionary(const Token &declaration, const Type *elementtype) + : Type(declaration, "dictionary"), + elementtype(elementtype) +{ + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["size"] = new PredefinedFunction("dictionary__size", new TypeFunction(TYPE_NUMBER, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["keys"] = new PredefinedFunction("dictionary__keys", new TypeFunction(TYPE_ARRAY_STRING, params)); + } +} + +bool TypeDictionary::is_assignment_compatible(const Type *rhs) const { const TypeDictionary *d = dynamic_cast(rhs); if (d == nullptr) { return false; } - return elementtype == nullptr || d->elementtype == nullptr || elementtype->is_equivalent(d->elementtype); + return elementtype == nullptr || d->elementtype == nullptr || elementtype->is_assignment_compatible(d->elementtype); +} + +std::string TypeDictionary::serialize(const Expression *value) const +{ + std::string r; + const DictionaryLiteralExpression *d = dynamic_cast(value); + r.push_back(static_cast(d->dict.size() >> 24) & 0xff); + r.push_back(static_cast(d->dict.size() >> 16) & 0xff); + r.push_back(static_cast(d->dict.size() >> 8) & 0xff); + r.push_back(static_cast(d->dict.size() & 0xff)); + for (auto x: d->dict) { + r.append(TypeString::serialize(x.first)); + r.append(d->elementtype->serialize(x.second)); + } + return r; } -bool TypeRecord::is_equivalent(const Type *rhs) const +const Expression *TypeDictionary::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + std::vector> dict; + uint32_t len = (value.at(i) << 24) | (value.at(i+1) << 16) | (value.at(i+2) << 8) | value.at(i+3); + i += 4; + while (len > 0) { + std::string name = TypeString::deserialize_string(value, i); + dict.push_back(std::make_pair(name, elementtype->deserialize_value(value, i))); + len--; + } + return new DictionaryLiteralExpression(elementtype, dict); +} + +bool TypeRecord::is_assignment_compatible(const Type *rhs) const { return this == rhs; } -bool TypePointer::is_equivalent(const Type *rhs) const +std::string TypeRecord::serialize(const Expression *value) const +{ + std::string r; + const RecordLiteralExpression *a = dynamic_cast(value); + r.push_back(static_cast(a->values.size() >> 24) & 0xff); + r.push_back(static_cast(a->values.size() >> 16) & 0xff); + r.push_back(static_cast(a->values.size() >> 8) & 0xff); + r.push_back(static_cast(a->values.size() & 0xff)); + for (auto x: a->values) { + r.append(x->type->serialize(x)); + } + return r; +} + +const Expression *TypeRecord::deserialize_value(const Bytecode::Bytes &value, int &i) const +{ + std::vector elements; + uint32_t len = (value.at(i) << 24) | (value.at(i+1) << 16) | (value.at(i+2) << 8) | value.at(i+3); + i += 4; + int f = 0; + while (len > 0) { + elements.push_back(fields[f].type->deserialize_value(value, i)); + f++; + len--; + } + return new RecordLiteralExpression(this, elements); +} + +std::string TypeRecord::text() const +{ + std::string r = "TypeRecord("; + bool first = true; + for (auto f: fields) { + if (not first) { + r.append(","); + } + first = false; + r.append(f.name.text); + } + r.append(")"); + return r; +} + +TypePointer::TypePointer(const Token &declaration, const TypeRecord *reftype) + : Type(declaration, "pointer"), + reftype(reftype) { + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["toString"] = new PredefinedFunction("pointer__toString", new TypeFunction(TYPE_STRING, params)); + } +} + +bool TypePointer::is_assignment_compatible(const Type *rhs) const +{ + if (this == rhs) { + return true; + } + if (dynamic_cast(rhs) != nullptr) { + return true; + } const TypePointer *p = dynamic_cast(rhs); if (p == nullptr) { return false; } if (reftype == nullptr || p->reftype == nullptr) { - return true; + return false; } // Shortcut check avoids infinite recursion on records with pointer to itself. - return reftype == p->reftype || reftype->is_equivalent(p->reftype); + return reftype == p->reftype || reftype->is_assignment_compatible(p->reftype); +} + +std::string TypePointer::serialize(const Expression *) const +{ + return std::string(); +} + +const Expression *TypePointer::deserialize_value(const Bytecode::Bytes &, int &) const +{ + return new ConstantNilExpression(); +} + +TypeFunctionPointer::TypeFunctionPointer(const Token &declaration, const TypeFunction *functype) + : Type(declaration, "function-pointer"), + functype(functype) +{ + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, this, nullptr)); + methods["toString"] = new PredefinedFunction("functionpointer__toString", new TypeFunction(TYPE_STRING, params)); + } +} + +bool TypeFunctionPointer::is_assignment_compatible(const Type *rhs) const +{ + return functype->is_assignment_compatible(rhs); +} + +std::string TypeFunctionPointer::serialize(const Expression *) const +{ + return std::string(); +} + +const Expression *TypeFunctionPointer::deserialize_value(const Bytecode::Bytes &, int &) const +{ + return new ConstantNumberExpression(number_from_sint32(0)); +} + +bool Expression::eval_boolean(const Token &token) const +{ + try { + return eval_boolean(); + } catch (RtlException &e) { + error(3195, token, "Boolean evaluation exception: " + e.name + ": " + e.info); + } +} + +Number Expression::eval_number(const Token &token) const +{ + try { + return eval_number(); + } catch (RtlException &e) { + error(3196, token, "Numeric evaluation exception: " + e.name + ": " + e.info); + } +} + +std::string Expression::eval_string(const Token &token) const +{ + try { + return eval_string(); + } catch (RtlException &e) { + error(3197, token, "String evaluation exception: " + e.name + ": " + e.info); + } } std::string ConstantBooleanExpression::text() const @@ -90,6 +406,13 @@ std::string ConstantStringExpression::text() const return s.str(); } +std::string ConstantBytesExpression::text() const +{ + std::stringstream s; + s << "ConstantBytesExpression(" << name << ")"; + return s.str(); +} + std::string ConstantEnumExpression::text() const { std::stringstream s; @@ -117,6 +440,16 @@ bool DictionaryLiteralExpression::all_constant(const std::vector &elements) +{ + for (auto e: elements) { + if (not e->is_constant) { + return false; + } + } + return true; +} + std::map DictionaryLiteralExpression::make_dictionary(const std::vector> &elements) { std::map dict; @@ -126,6 +459,46 @@ std::map DictionaryLiteralExpression::make_dict return dict; } +bool BooleanComparisonExpression::eval_boolean() const +{ + switch (comp) { + case EQ: return left->eval_boolean() == right->eval_boolean(); + case NE: return left->eval_boolean() != right->eval_boolean(); + case LT: + case GT: + case LE: + case GE: + internal_error("BooleanComparisonExpression"); + } + internal_error("BooleanComparisonExpression"); +} + +bool NumericComparisonExpression::eval_boolean() const +{ + switch (comp) { + case EQ: return number_is_equal (left->eval_number(), right->eval_number()); + case NE: return number_is_not_equal (left->eval_number(), right->eval_number()); + case LT: return number_is_less (left->eval_number(), right->eval_number()); + case GT: return number_is_greater (left->eval_number(), right->eval_number()); + case LE: return number_is_less_equal (left->eval_number(), right->eval_number()); + case GE: return number_is_greater_equal(left->eval_number(), right->eval_number()); + } + internal_error("NumericComparisonExpression"); +} + +bool StringComparisonExpression::eval_boolean() const +{ + switch (comp) { + case EQ: return left->eval_string() == right->eval_string(); + case NE: return left->eval_string() != right->eval_string(); + case LT: return left->eval_string() < right->eval_string(); + case GT: return left->eval_string() > right->eval_string(); + case LE: return left->eval_string() <= right->eval_string(); + case GE: return left->eval_string() >= right->eval_string(); + } + internal_error("StringComparisonExpression"); +} + bool IfStatement::always_returns() const { for (auto cond: condition_statements) { @@ -186,6 +559,62 @@ std::string FunctionCall::text() const return s.str(); } +Number FunctionCall::eval_number() const +{ + const VariableExpression *ve = dynamic_cast(func); + const PredefinedFunction *f = dynamic_cast(ve->var); + if (f->name == "ord") return rtl::global$ord(args[0]->eval_string()); + if (f->name == "int") return rtl::global$int(args[0]->eval_number()); + if (f->name == "max") return rtl::global$max(args[0]->eval_number(), args[1]->eval_number()); + if (f->name == "min") return rtl::global$min(args[0]->eval_number(), args[1]->eval_number()); + if (f->name == "num") return rtl::global$num(args[0]->eval_string()); + internal_error("unexpected intrinsic"); +} + +std::string FunctionCall::eval_string() const +{ + const VariableExpression *ve = dynamic_cast(func); + const PredefinedFunction *f = dynamic_cast(ve->var); + if (f->name == "chr") return rtl::global$chr(args[0]->eval_number()); + if (f->name == "concat") return rtl::global$concat(args[0]->eval_string(), args[1]->eval_string()); + if (f->name == "format") return rtl::global$format(args[0]->eval_string(), args[1]->eval_string()); + if (f->name == "str") return rtl::global$str(args[0]->eval_number()); + if (f->name == "strb") return rtl::global$strb(args[0]->eval_boolean()); + if (f->name == "substring") return rtl::global$substring(args[0]->eval_string(), args[1]->eval_number(), args[2]->eval_number()); + internal_error("unexpected intrinsic"); +} + +bool FunctionCall::is_intrinsic(const Expression *func, const std::vector &args) +{ + for (auto a: args) { + if (not a->is_constant) { + return false; + } + } + const VariableExpression *ve = dynamic_cast(func); + if (ve == nullptr) { + return false; + } + const PredefinedFunction *f = dynamic_cast(ve->var); + if (f == nullptr) { + return false; + } + if (f->name == "chr" + || f->name == "concat" + || f->name == "format" + || f->name == "int" + || f->name == "max" + || f->name == "min" + || f->name == "num" + || f->name == "ord" + || f->name == "str" + || f->name == "strb" + || f->name == "substring") { + return true; + } + return false; +} + void CompoundStatement::dumpsubnodes(std::ostream &out, int depth) const { for (std::vector::const_iterator i = statements.begin(); i != statements.end(); ++i) { @@ -193,31 +622,73 @@ void CompoundStatement::dumpsubnodes(std::ostream &out, int depth) const } } -Name *Scope::lookupName(const std::string &name) +int Frame::addSlot(const Token &token, const std::string &name, Name *ref, bool init_referenced) +{ + Slot slot(token, name, ref, init_referenced); + int r = static_cast(slots.size()); + slots.push_back(slot); + return r; +} + +const Frame::Slot Frame::getSlot(size_t slot) +{ + return slots.at(slot); +} + +void Frame::setReferent(int slot, Name *ref) +{ + if (slots.at(slot).ref != nullptr) { + internal_error("ref not null"); + } + slots.at(slot).ref = ref; +} + +void Frame::setReferenced(int slot) +{ + slots.at(slot).referenced = true; +} + +bool Scope::allocateName(const Token &token, const std::string &name) { - int enclosing; - return lookupName(name, enclosing); + if (getDeclaration(name).type != NONE) { + return false; + } + names[name] = frame->addSlot(token, name, nullptr, false); + return true; } -Name *Scope::lookupName(const std::string &name, int &enclosing) +Name *Scope::lookupName(const std::string &name, bool mark_referenced) { - enclosing = 0; Scope *s = this; while (s != nullptr) { auto n = s->names.find(name); if (n != s->names.end()) { - s->referenced.insert(n->second); - return n->second; + if (mark_referenced) { + s->frame->setReferenced(n->second); + } + return s->frame->getSlot(n->second).ref; } - enclosing++; s = s->parent; } return nullptr; } -void Scope::addName(const std::string &name, Name *ref, bool init_referenced) +Token Scope::getDeclaration(const std::string &name) const { - if (lookupName(name) != nullptr) { + const Scope *s = this; + while (s != nullptr) { + auto d = s->names.find(name); + if (d != s->names.end()) { + return s->frame->getSlot(d->second).token; + } + s = s->parent; + } + return Token(); +} + +void Scope::addName(const Token &token, const std::string &name, Name *ref, bool init_referenced, bool allow_shadow) +{ + if (not allow_shadow and lookupName(name, false) != nullptr) { // If this error occurs, it means a new name was introduced // but no check was made with lookupName() to see whether the // name already exists yet. This error needs to be detected @@ -225,30 +696,17 @@ void Scope::addName(const std::string &name, Name *ref, bool init_referenced) // pass to the normal error function. internal_error("name presence not checked: " + name); } - names[name] = ref; - if (init_referenced) { - referenced.insert(ref); + auto a = names.find(name); + if (a != names.end()) { + frame->setReferent(a->second, ref); + if (init_referenced) { + frame->setReferenced(a->second); + } + } else { + names[name] = frame->addSlot(token, name, ref, init_referenced); } } -void Scope::scrubName(const std::string &name) -{ - auto i = names.find(name); - names[std::to_string(reinterpret_cast(i->second))] = i->second; - names.erase(i); - referenced.insert(i->second); -} - -int Scope::nextIndex() -{ - return count++; -} - -int Scope::getCount() const -{ - return count; -} - void Scope::addForward(const std::string &name, TypePointer *ptrtype) { forwards[name].push_back(ptrtype); @@ -274,46 +732,118 @@ void Scope::checkForward() } } +Function::Function(const Token &declaration, const std::string &name, const Type *returntype, Frame *outer, Scope *parent, const std::vector ¶ms) + : Variable(declaration, name, makeFunctionType(returntype, params), true), + frame(new Frame(outer)), + scope(new Scope(parent, frame)), + params(params), + entry_label(UINT_MAX), + statements() +{ + for (auto p: params) { + scope->addName(p->declaration, p->name, p, true); + } +} + const Type *Function::makeFunctionType(const Type *returntype, const std::vector ¶ms) { std::vector paramtypes; for (auto p: params) { - paramtypes.push_back(new ParameterType(p->mode, p->type)); + paramtypes.push_back(new ParameterType(p->declaration, p->mode, p->type, p->default_value)); } return new TypeFunction(returntype, paramtypes); } -Program::Program() - : scope(new Scope(nullptr)), - statements() +Program::Program(const std::string &source_path, const std::string &source_hash) + : source_path(source_path), + source_hash(source_hash), + frame(new Frame(nullptr)), + scope(new Scope(nullptr, frame)), + statements(), + exports() { - scope->addName("Boolean", TYPE_BOOLEAN); - scope->addName("Number", TYPE_NUMBER); - scope->addName("String", TYPE_STRING); + scope->addName(Token(), "Boolean", TYPE_BOOLEAN); + scope->addName(Token(), "Number", TYPE_NUMBER); + scope->addName(Token(), "String", TYPE_STRING); + scope->addName(Token(), "Bytes", TYPE_BYTES); + + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_BOOLEAN, nullptr)); + TYPE_BOOLEAN->methods["toString"] = new PredefinedFunction("boolean__toString", new TypeFunction(TYPE_STRING, params)); + } { std::vector params; - params.push_back(new ParameterType(ParameterType::INOUT, TYPE_BOOLEAN)); - TYPE_BOOLEAN->methods["to_string"] = new PredefinedFunction("boolean.to_string", new TypeFunction(TYPE_STRING, params)); + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_NUMBER, nullptr)); + TYPE_NUMBER->methods["toString"] = new PredefinedFunction("number__toString", new TypeFunction(TYPE_STRING, params)); } { std::vector params; - params.push_back(new ParameterType(ParameterType::INOUT, TYPE_NUMBER)); - TYPE_NUMBER->methods["to_string"] = new PredefinedFunction("number.to_string", new TypeFunction(TYPE_STRING, params)); + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_STRING, nullptr)); + TYPE_STRING->methods["length"] = new PredefinedFunction("string__length", new TypeFunction(TYPE_NUMBER, params)); } { std::vector params; - params.push_back(new ParameterType(ParameterType::INOUT, TYPE_STRING)); - TYPE_STRING->methods["length"] = new PredefinedFunction("string.length", new TypeFunction(TYPE_NUMBER, params)); + params.push_back(new ParameterType(Token(), ParameterType::INOUT, TYPE_STRING, nullptr)); + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_STRING, nullptr)); + TYPE_STRING->methods["append"] = new PredefinedFunction("string__append", new TypeFunction(TYPE_NOTHING, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_STRING, nullptr)); + TYPE_STRING->methods["toBytes"] = new PredefinedFunction("string__toBytes", new TypeFunction(TYPE_BYTES, params)); } - scope->addName("DivideByZero", new Exception("DivideByZero")); - scope->addName("ArrayIndex", new Exception("ArrayIndex")); - scope->addName("DictionaryIndex", new Exception("DictionaryIndex")); - scope->addName("FunctionNotFound", new Exception("FunctionNotFound")); - scope->addName("LibraryNotFound", new Exception("LibraryNotFound")); + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::INOUT, TYPE_BYTES, nullptr)); + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_ARRAY_NUMBER, nullptr)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_BYTES, nullptr)); + TYPE_BYTES->methods["size"] = new PredefinedFunction("bytes__size", new TypeFunction(TYPE_NUMBER, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_BYTES, nullptr)); + TYPE_BYTES->methods["toArray"] = new PredefinedFunction("bytes__toArray", new TypeFunction(TYPE_ARRAY_NUMBER, params)); + } + { + std::vector params; + params.push_back(new ParameterType(Token(), ParameterType::IN, TYPE_BYTES, nullptr)); + TYPE_BYTES->methods["toString"] = new PredefinedFunction("bytes__toString", new TypeFunction(TYPE_STRING, params)); + } + + for (auto e: ExceptionNames) { + scope->addName(Token(), e.name, new Exception(Token(), e.name)); + } + + { + // The fields here must match the corresponding references to + // ExceptionInfo in exec.cpp. + std::vector fields; + fields.push_back(TypeRecord::Field(Token("info"), TYPE_STRING, false)); + fields.push_back(TypeRecord::Field(Token("code"), TYPE_NUMBER, false)); + Type *exception_info = new TypeRecord(Token(), "ExceptionInfo", fields); + scope->addName(Token(), "ExceptionInfo", exception_info, true); + } + { + // The fields here must match the corresponding references to + // ExceptionType in exec.cpp. + std::vector fields; + fields.push_back(TypeRecord::Field(Token("name"), TYPE_STRING, false)); + fields.push_back(TypeRecord::Field(Token("info"), TYPE_STRING, false)); + fields.push_back(TypeRecord::Field(Token("code"), TYPE_NUMBER, false)); + fields.push_back(TypeRecord::Field(Token("offset"), TYPE_NUMBER, false)); + Type *exception_type = new TypeRecord(Token(), "ExceptionType", fields); + scope->addName(Token(), "ExceptionType", exception_type, true); + GlobalVariable *current_exception = new GlobalVariable(Token(), "CURRENT_EXCEPTION", exception_type, true); + scope->addName(Token(), "CURRENT_EXCEPTION", current_exception, true); + } rtl_compile_init(scope); } diff --git a/src/ast.h b/src/ast.h index fa0443fe90..d841b465b4 100644 --- a/src/ast.h +++ b/src/ast.h @@ -8,16 +8,120 @@ #include #include +#include + +#include "bytecode.h" #include "number.h" +#include "token.h" #include "util.h" +class Analyzer; + // Compiler class Emitter; +class IAstVisitor { +public: + virtual ~IAstVisitor(); + virtual void visit(const class TypeNothing *node) = 0; + virtual void visit(const class TypeDummy *node) = 0; + virtual void visit(const class TypeBoolean *node) = 0; + virtual void visit(const class TypeNumber *node) = 0; + virtual void visit(const class TypeString *node) = 0; + virtual void visit(const class TypeBytes *node) = 0; + virtual void visit(const class TypeFunction *node) = 0; + virtual void visit(const class TypeArray *node) = 0; + virtual void visit(const class TypeDictionary *node) = 0; + virtual void visit(const class TypeRecord *node) = 0; + virtual void visit(const class TypePointer *node) = 0; + virtual void visit(const class TypeFunctionPointer *node) = 0; + virtual void visit(const class TypeEnum *node) = 0; + virtual void visit(const class TypeModule *node) = 0; + virtual void visit(const class TypeException *node) = 0; + virtual void visit(const class PredefinedVariable *node) = 0; + virtual void visit(const class ModuleVariable *node) = 0; + virtual void visit(const class GlobalVariable *node) = 0; + virtual void visit(const class LocalVariable *node) = 0; + virtual void visit(const class FunctionParameter *node) = 0; + virtual void visit(const class Exception *node) = 0; + virtual void visit(const class Constant *node) = 0; + virtual void visit(const class ConstantBooleanExpression *node) = 0; + virtual void visit(const class ConstantNumberExpression *node) = 0; + virtual void visit(const class ConstantStringExpression *node) = 0; + virtual void visit(const class ConstantBytesExpression *node) = 0; + virtual void visit(const class ConstantEnumExpression *node) = 0; + virtual void visit(const class ConstantNilExpression *node) = 0; + virtual void visit(const class ArrayLiteralExpression *node) = 0; + virtual void visit(const class DictionaryLiteralExpression *node) = 0; + virtual void visit(const class RecordLiteralExpression *node) = 0; + virtual void visit(const class NewRecordExpression *node) = 0; + virtual void visit(const class UnaryMinusExpression *node) = 0; + virtual void visit(const class LogicalNotExpression *node) = 0; + virtual void visit(const class ConditionalExpression *node) = 0; + virtual void visit(const class DisjunctionExpression *node) = 0; + virtual void visit(const class ConjunctionExpression *node) = 0; + virtual void visit(const class ArrayInExpression *node) = 0; + virtual void visit(const class DictionaryInExpression *node) = 0; + virtual void visit(const class ChainedComparisonExpression *node) = 0; + virtual void visit(const class BooleanComparisonExpression *node) = 0; + virtual void visit(const class NumericComparisonExpression *node) = 0; + virtual void visit(const class StringComparisonExpression *node) = 0; + virtual void visit(const class ArrayComparisonExpression *node) = 0; + virtual void visit(const class DictionaryComparisonExpression *node) = 0; + virtual void visit(const class PointerComparisonExpression *node) = 0; + virtual void visit(const class FunctionPointerComparisonExpression *node) = 0; + virtual void visit(const class AdditionExpression *node) = 0; + virtual void visit(const class SubtractionExpression *node) = 0; + virtual void visit(const class MultiplicationExpression *node) = 0; + virtual void visit(const class DivisionExpression *node) = 0; + virtual void visit(const class ModuloExpression *node) = 0; + virtual void visit(const class ExponentiationExpression *node) = 0; + virtual void visit(const class DummyExpression *node) = 0; + virtual void visit(const class ArrayReferenceIndexExpression *node) = 0; + virtual void visit(const class ArrayValueIndexExpression *node) = 0; + virtual void visit(const class DictionaryReferenceIndexExpression *node) = 0; + virtual void visit(const class DictionaryValueIndexExpression *node) = 0; + virtual void visit(const class StringReferenceIndexExpression *node) = 0; + virtual void visit(const class StringValueIndexExpression *node) = 0; + virtual void visit(const class BytesReferenceIndexExpression *node) = 0; + virtual void visit(const class BytesValueIndexExpression *node) = 0; + virtual void visit(const class ArrayReferenceRangeExpression *node) = 0; + virtual void visit(const class ArrayValueRangeExpression *node) = 0; + virtual void visit(const class PointerDereferenceExpression *node) = 0; + virtual void visit(const class ConstantExpression *node) = 0; + virtual void visit(const class VariableExpression *node) = 0; + virtual void visit(const class FunctionCall *node) = 0; + virtual void visit(const class StatementExpression *node) = 0; + virtual void visit(const class NullStatement *node) = 0; + virtual void visit(const class AssertStatement *node) = 0; + virtual void visit(const class AssignmentStatement *node) = 0; + virtual void visit(const class ExpressionStatement *node) = 0; + virtual void visit(const class ReturnStatement *node) = 0; + virtual void visit(const class IncrementStatement *node) = 0; + virtual void visit(const class IfStatement *node) = 0; + virtual void visit(const class WhileStatement *node) = 0; + virtual void visit(const class ForStatement *node) = 0; + virtual void visit(const class ForeachStatement *node) = 0; + virtual void visit(const class LoopStatement *node) = 0; + virtual void visit(const class RepeatStatement *node) = 0; + virtual void visit(const class CaseStatement *node) = 0; + virtual void visit(const class ExitStatement *node) = 0; + virtual void visit(const class NextStatement *node) = 0; + virtual void visit(const class TryStatement *node) = 0; + virtual void visit(const class RaiseStatement *node) = 0; + virtual void visit(const class ResetStatement *node) = 0; + virtual void visit(const class Function *node) = 0; + virtual void visit(const class PredefinedFunction *node) = 0; + virtual void visit(const class ModuleFunction *node) = 0; + virtual void visit(const class Module *node) = 0; + virtual void visit(const class Program *node) = 0; +}; + class AstNode { public: AstNode() {} virtual ~AstNode() {} + virtual void accept(IAstVisitor *visitor) const = 0; void dump(std::ostream &out, int depth = 0) const; virtual std::string text() const = 0; virtual void dumpsubnodes(std::ostream &/*out*/, int /*depth*/) const {} @@ -33,31 +137,68 @@ class TypePointer; class Variable; class FunctionCall; class FunctionParameter; +class Expression; +class Statement; + +class Frame { +public: + Frame(Frame *outer): outer(outer), predeclared(false), slots() {} + + void predeclare(Emitter &emitter); + void postdeclare(Emitter &emitter); + + struct Slot { + Slot(const Token &token, const std::string &name, Name *ref, bool referenced): token(token), name(name), ref(ref), referenced(referenced) {} + Slot(const Slot &rhs): token(rhs.token), name(rhs.name), ref(rhs.ref), referenced(rhs.referenced) {} + Slot &operator=(const Slot &rhs) { + if (this == &rhs) { + return *this; + } + token = rhs.token; + name = rhs.name; + ref = rhs.ref; + referenced = rhs.referenced; + return *this; + } + + Token token; + std::string name; + Name *ref; + bool referenced; + }; + + size_t getCount() const { return slots.size(); } + int addSlot(const Token &token, const std::string &name, Name *ref, bool init_referenced); + const Slot getSlot(size_t slot); + void setReferent(int slot, Name *ref); + void setReferenced(int slot); + +private: + Frame *const outer; + bool predeclared; + std::vector slots; +private: + Frame(const Frame &); + Frame &operator=(const Frame &); +}; class Scope { public: - Scope(Scope *parent): parent(parent), predeclared(false), names(), referenced(), count(0), forwards() {} + Scope(Scope *parent, Frame *frame): parent(parent), frame(frame), names(), forwards() {} virtual ~Scope() {} - virtual void predeclare(Emitter &emitter) const; - virtual void postdeclare(Emitter &emitter) const; - - Name *lookupName(const std::string &name); - Name *lookupName(const std::string &name, int &enclosing); - void addName(const std::string &name, Name *ref, bool init_referenced = false); - void scrubName(const std::string &name); - int nextIndex(); - int getCount() const; + bool allocateName(const Token &token, const std::string &name); + Name *lookupName(const std::string &name, bool mark_referenced = true); + Token getDeclaration(const std::string &name) const; + void addName(const Token &token, const std::string &name, Name *ref, bool init_referenced = false, bool allow_shadow = false); void addForward(const std::string &name, TypePointer *ptrtype); void resolveForward(const std::string &name, const TypeRecord *rectype); void checkForward(); -private: Scope *const parent; - mutable bool predeclared; - std::map names; - std::set referenced; - int count; + Frame *const frame; +private: + std::map names; std::map> forwards; private: Scope(const Scope &); @@ -66,12 +207,15 @@ class Scope { class Name: public AstNode { public: - Name(const std::string &name, const Type *type): name(name), type(type) {} + Name(const Token &declaration, const std::string &name, const Type *type): declaration(declaration), name(name), type(type) {} + const Token declaration; const std::string name; const Type *type; virtual void predeclare(Emitter &) const {} virtual void postdeclare(Emitter &) const {} + + virtual void generate_export(Emitter &emitter, const std::string &name) const = 0; private: Name(const Name &); Name &operator=(const Name &); @@ -79,65 +223,126 @@ class Name: public AstNode { class Type: public Name { public: - Type(const std::string &name): Name(name, nullptr), methods() {} + Type(const Token &declaration, const std::string &name): Name(declaration, name, nullptr), methods() {} std::map methods; - virtual void predeclare(Emitter &emitter) const; - virtual bool is_equivalent(const Type *rhs) const { return this == rhs; } + virtual void predeclare(Emitter &emitter) const override; + virtual void postdeclare(Emitter &emitter) const override; + virtual bool is_assignment_compatible(const Type *rhs) const { return this == rhs; } virtual void generate_load(Emitter &emitter) const = 0; virtual void generate_store(Emitter &emitter) const = 0; virtual void generate_call(Emitter &emitter) const = 0; + virtual void generate_export(Emitter &emitter, const std::string &name) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const = 0; + virtual void get_type_references(std::set &) const {} + virtual std::string serialize(const Expression *value) const = 0; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const = 0; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const = 0; }; class TypeNothing: public Type { public: - TypeNothing(): Type("Nothing") {} - virtual void generate_load(Emitter &) const { internal_error("TypeNothing"); } - virtual void generate_store(Emitter &) const { internal_error("TypeNothing"); } - virtual void generate_call(Emitter &) const { internal_error("TypeNothing"); } + TypeNothing(): Type(Token(), "Nothing") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual void generate_load(Emitter &) const override { internal_error("TypeNothing"); } + virtual void generate_store(Emitter &) const override { internal_error("TypeNothing"); } + virtual void generate_call(Emitter &) const override { internal_error("TypeNothing"); } + virtual std::string get_type_descriptor(Emitter &) const override { return "Z"; } + virtual std::string serialize(const Expression *) const override { internal_error("TypeNothing"); } + virtual const Expression *deserialize_value(const Bytecode::Bytes &, int &) const override { internal_error("TypeNothing"); } + virtual void debuginfo(Emitter &, minijson::object_writer &) const override { internal_error("TypeNothing"); } - virtual std::string text() const { return "TypeNothing"; } + virtual std::string text() const override { return "TypeNothing"; } }; extern TypeNothing *TYPE_NOTHING; +class TypeDummy: public Type { +public: + TypeDummy(): Type(Token(), "Dummy") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual bool is_assignment_compatible(const Type *) const override { return true; } + virtual void generate_load(Emitter &) const override { internal_error("TypeDummy"); } + virtual void generate_store(Emitter &) const override { internal_error("TypeDummy"); } + virtual void generate_call(Emitter &) const override { internal_error("TypeDummy"); } + virtual std::string get_type_descriptor(Emitter &) const override { internal_error("TypeDummy"); } + virtual std::string serialize(const Expression *) const override { internal_error("TypeDummy"); } + virtual const Expression *deserialize_value(const Bytecode::Bytes &, int &) const override { internal_error("TypeDummy"); } + virtual void debuginfo(Emitter &, minijson::object_writer &) const override { internal_error("TypeDummy"); } + + virtual std::string text() const override { return "TypeNothing"; } +}; + +extern TypeDummy *TYPE_DUMMY; + class TypeBoolean: public Type { public: - TypeBoolean(): Type("Boolean") {} - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; + TypeBoolean(): Type(Token(), "Boolean") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &) const override { return "B"; } + virtual std::string serialize(const Expression *) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; - virtual std::string text() const { return "TypeBoolean"; } + virtual std::string text() const override { return "TypeBoolean"; } }; extern TypeBoolean *TYPE_BOOLEAN; class TypeNumber: public Type { public: - TypeNumber(): Type("Number") {} - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; + TypeNumber(const Token &declaration): Type(declaration, "Number") {} + TypeNumber(const Token &declaration, const std::string &name): Type(declaration, name) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &) const override { return "N"; } + virtual std::string serialize(const Expression *value) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; - virtual std::string text() const { return "TypeNumber"; } + virtual std::string text() const override { return "TypeNumber"; } }; extern TypeNumber *TYPE_NUMBER; class TypeString: public Type { public: - TypeString(): Type("String") {} - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; + TypeString(): Type(Token(), "String") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &) const override { return "S"; } + static std::string serialize(const std::string &value); + virtual std::string serialize(const Expression *value) const override; + static std::string deserialize_string(const Bytecode::Bytes &value, int &i); + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; - virtual std::string text() const { return "TypeString"; } + virtual std::string text() const override { return "TypeString"; } }; extern TypeString *TYPE_STRING; +class TypeBytes: public TypeString { +public: + TypeBytes(): TypeString() {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual std::string get_type_descriptor(Emitter &) const override { return "Y"; } + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypeBytes"; } +}; + +extern TypeBytes *TYPE_BYTES; + class ParameterType { public: enum Mode { @@ -145,9 +350,11 @@ class ParameterType { INOUT, OUT }; - ParameterType(Mode mode, const Type *type): mode(mode), type(type) {} + ParameterType(const Token &declaration, Mode mode, const Type *type, const Expression *default_value): declaration(declaration), mode(mode), type(type), default_value(default_value) {} + const Token declaration; const Mode mode; const Type *type; + const Expression *default_value; private: ParameterType(const ParameterType &); ParameterType &operator=(const ParameterType &); @@ -155,15 +362,23 @@ class ParameterType { class TypeFunction: public Type { public: - TypeFunction(const Type *returntype, const std::vector ¶ms): Type("function"), returntype(returntype), params(params) {} - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; + TypeFunction(const Type *returntype, const std::vector ¶ms): Type(Token(), "function"), returntype(returntype), params(params) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual void predeclare(Emitter &emitter) const override; + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual std::string serialize(const Expression *) const override { internal_error("TypeFunction"); } + virtual const Expression *deserialize_value(const Bytecode::Bytes &, int &) const override { internal_error("TypeFunction"); } + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; const Type *returntype; const std::vector params; - virtual std::string text() const { return "TypeFunction(...)"; } + virtual std::string text() const override { return "TypeFunction(...)"; } private: TypeFunction(const TypeFunction &); TypeFunction &operator=(const TypeFunction &); @@ -171,15 +386,22 @@ class TypeFunction: public Type { class TypeArray: public Type { public: - TypeArray(const Type *elementtype); + TypeArray(const Token &declaration, const Type *elementtype); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Type *elementtype; - virtual bool is_equivalent(const Type *rhs) const; - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; - - virtual std::string text() const { return "TypeArray(" + elementtype->text() + ")"; } + virtual void predeclare(Emitter &emitter) const override; + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void get_type_references(std::set &references) const override; + virtual std::string serialize(const Expression *value) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypeArray(" + (elementtype != nullptr ? elementtype->text() : "any") + ")"; } private: TypeArray(const TypeArray &); TypeArray &operator=(const TypeArray &); @@ -190,15 +412,22 @@ extern TypeArray *TYPE_ARRAY_STRING; class TypeDictionary: public Type { public: - TypeDictionary(const Type *elementtype): Type("dictionary"), elementtype(elementtype) {} + TypeDictionary(const Token &declaration, const Type *elementtype); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Type *elementtype; - virtual bool is_equivalent(const Type *rhs) const; - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; - - virtual std::string text() const { return "TypeDictionary(" + elementtype->text() + ")"; } + virtual void predeclare(Emitter &emitter) const override; + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void get_type_references(std::set &references) const override; + virtual std::string serialize(const Expression *value) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypeDictionary(" + (elementtype != nullptr ? elementtype->text() : "any") + ")"; } private: TypeDictionary(const TypeDictionary &); TypeDictionary &operator=(const TypeDictionary &); @@ -206,115 +435,225 @@ class TypeDictionary: public Type { class TypeRecord: public Type { public: - TypeRecord(const std::map > &fields): Type("record"), fields(fields) {} - const std::map > fields; - - virtual bool is_equivalent(const Type *rhs) const; - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; - - virtual std::string text() const { return "TypeRecord(...)"; } + struct Field { + Field(const Token &name, const Type *type, bool is_private): name(name), type(type), is_private(is_private) {} + Field(const Field &rhs): name(rhs.name), type(rhs.type), is_private(rhs.is_private) {} + Field &operator=(const Field &rhs) { + if (&rhs == this) { + return *this; + } + name = rhs.name; + type = rhs.type; + is_private = rhs.is_private; + return *this; + } + Token name; + const Type *type; + bool is_private; + }; + TypeRecord(const Token &declaration, const std::string &name, const std::vector &fields): Type(declaration, name), fields(fields), field_names(make_field_names(fields)), predeclared(false), postdeclared(false) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + const std::vector fields; + const std::map field_names; + + virtual void predeclare(Emitter &emitter) const override; + virtual void postdeclare(Emitter &emitter) const override; + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void get_type_references(std::set &references) const override; + virtual std::string serialize(const Expression *value) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override; +private: + mutable bool predeclared; + mutable bool postdeclared; + static std::map make_field_names(const std::vector &fields) { + std::map r; + size_t i = 0; + for (auto f: fields) { + r[f.name.text] = i; + i++; + } + return r; + } }; class TypeForwardRecord: public TypeRecord { public: - TypeForwardRecord(): TypeRecord(std::map>()) {} + TypeForwardRecord(const Token &declaration): TypeRecord(declaration, "forward", std::vector()) {} }; class TypePointer: public Type { public: - TypePointer(const TypeRecord *reftype): Type("pointer"), reftype(reftype) {} + TypePointer(const Token &declaration, const TypeRecord *reftype); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const TypeRecord *reftype; - virtual bool is_equivalent(const Type *rhs) const; - virtual void generate_load(Emitter &emitter) const; - virtual void generate_store(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const; - - virtual std::string text() const { return "TypePointer(" + reftype->text() + ")"; } + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void get_type_references(std::set &references) const override; + virtual std::string serialize(const Expression *) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypePointer(" + (reftype != nullptr ? reftype->text() : "any") + ")"; } private: TypePointer(const TypePointer &); TypePointer &operator=(const TypePointer &); }; -extern TypePointer *TYPE_POINTER; +class TypePointerNil: public TypePointer { +public: + TypePointerNil(): TypePointer(Token(), nullptr) {} +}; class TypeValidPointer: public TypePointer { public: - TypeValidPointer(const TypePointer *ptrtype): TypePointer(ptrtype->reftype) {} + TypeValidPointer(const TypePointer *ptrtype): TypePointer(Token(), ptrtype->reftype) {} +}; + +class TypeFunctionPointer: public Type { +public: + TypeFunctionPointer(const Token &declaration, const TypeFunction *functype); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const TypeFunction *functype; + + virtual bool is_assignment_compatible(const Type *rhs) const override; + virtual void generate_load(Emitter &emitter) const override; + virtual void generate_store(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override; + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void get_type_references(std::set &references) const override; + virtual std::string serialize(const Expression *) const override; + virtual const Expression *deserialize_value(const Bytecode::Bytes &value, int &i) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypeFunctionPointer(" + functype->text() + ")"; } +private: + TypeFunctionPointer(const TypeFunctionPointer &); + TypeFunctionPointer &operator=(const TypeFunctionPointer &); }; class TypeEnum: public TypeNumber { public: - TypeEnum(const std::map &names); + TypeEnum(const Token &declaration, const std::string &name, const std::map &names, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const std::map names; - virtual std::string text() const { return "TypeEnum(...)"; } + virtual std::string get_type_descriptor(Emitter &emitter) const override; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; + + virtual std::string text() const override { return "TypeEnum(...)"; } }; class TypeModule: public Type { public: - TypeModule(): Type("module") {} + TypeModule(): Type(Token(), "module") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual void generate_load(Emitter &) const { internal_error("TypeModule"); } - virtual void generate_store(Emitter &) const { internal_error("TypeModule"); } - virtual void generate_call(Emitter &) const { internal_error("TypeModule"); } + virtual void generate_load(Emitter &) const override { internal_error("TypeModule"); } + virtual void generate_store(Emitter &) const override { internal_error("TypeModule"); } + virtual void generate_call(Emitter &) const override { internal_error("TypeModule"); } + virtual std::string get_type_descriptor(Emitter &) const override { internal_error("TypeModule"); } + virtual std::string serialize(const Expression *) const override { internal_error("TypeModule"); } + virtual const Expression *deserialize_value(const Bytecode::Bytes &, int &) const override { internal_error("TypeModule"); } + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; - virtual std::string text() const { return "TypeModule(...)"; } + virtual std::string text() const override { return "TypeModule(...)"; } }; extern TypeModule *TYPE_MODULE; class TypeException: public Type { public: - TypeException(): Type("Exception") {} + TypeException(): Type(Token(), "Exception") {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual void generate_load(Emitter &) const { internal_error("TypeException"); } - virtual void generate_store(Emitter &) const { internal_error("TypeException"); } - virtual void generate_call(Emitter &) const { internal_error("TypeException"); } + virtual void generate_load(Emitter &) const override { internal_error("TypeException"); } + virtual void generate_store(Emitter &) const override { internal_error("TypeException"); } + virtual void generate_call(Emitter &) const override { internal_error("TypeException"); } + virtual std::string get_type_descriptor(Emitter &) const override { return "X"; } + virtual std::string serialize(const Expression *) const override { internal_error("TypeException"); } + virtual const Expression *deserialize_value(const Bytecode::Bytes &, int &) const override { internal_error("TypeException"); } + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const override; - virtual std::string text() const { return "TypeException"; } + virtual std::string text() const override { return "TypeException"; } }; extern TypeException *TYPE_EXCEPTION; class Variable: public Name { public: - Variable(const std::string &name, const Type *type, bool is_readonly): Name(name, type), is_readonly(is_readonly) {} + Variable(const Token &declaration, const std::string &name, const Type *type, bool is_readonly): Name(declaration, name, type), is_readonly(is_readonly) {} - const bool is_readonly; + bool is_readonly; virtual void generate_address(Emitter &emitter, int enclosing) const = 0; virtual void generate_load(Emitter &emitter) const; virtual void generate_store(Emitter &emitter) const; virtual void generate_call(Emitter &emitter) const; + virtual void generate_export(Emitter &, const std::string &) const override { internal_error("Variable"); } - virtual std::string text() const { return "Variable(" + name + ", " + type->text() + ")"; } + virtual std::string text() const override { return "Variable(" + name + ", " + type->text() + ")"; } +}; + +class PredefinedVariable: public Variable { +public: + PredefinedVariable(const std::string &name, const Type *type): Variable(Token(), name, type, true) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual void generate_address(Emitter &emitter, int enclosing) const override; + + virtual std::string text() const override { return "PredefinedVariable(" + name + ", " + type->text() + ")"; } +}; + +class ModuleVariable: public Variable { +public: + ModuleVariable(const std::string &module, const std::string &name, const Type *type, int index): Variable(Token(), name, type, false), module(module), index(index) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + const std::string module; + const int index; + + virtual void predeclare(Emitter &emitter) const override; + virtual void generate_address(Emitter &emitter, int enclosing) const override; + + virtual std::string text() const override { return "ModuleVariable(" + module + "." + name + ")"; } }; class GlobalVariable: public Variable { public: - GlobalVariable(const std::string &name, const Type *type, bool is_readonly): Variable(name, type, is_readonly), index(-1) {} + GlobalVariable(const Token &declaration, const std::string &name, const Type *type, bool is_readonly): Variable(declaration, name, type, is_readonly), index(-1) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } mutable int index; - virtual void predeclare(Emitter &emitter) const; - virtual void generate_address(Emitter &emitter, int enclosing) const; + virtual void predeclare(Emitter &emitter) const override; + virtual void generate_address(Emitter &emitter, int enclosing) const override; + virtual void generate_export(Emitter &emitter, const std::string &name) const override; - virtual std::string text() const { return "GlobalVariable(" + name + ", " + type->text() + ")"; } + virtual std::string text() const override { return "GlobalVariable(" + name + ", " + type->text() + ")"; } }; class LocalVariable: public Variable { public: - LocalVariable(const std::string &name, const Type *type, Scope *scope, bool is_readonly): Variable(name, type, is_readonly), scope(scope), index(-1) {} - Scope *scope; - mutable int index; + LocalVariable(const Token &declaration, const std::string &name, const Type *type, bool is_readonly): Variable(declaration, name, type, is_readonly), index(-1) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + int index; - virtual void predeclare(Emitter &emitter) const; - virtual void generate_address(Emitter &emitter, int enclosing) const; + virtual void predeclare(Emitter &) const override { internal_error("LocalVariable"); } + virtual void predeclare(Emitter &emitter, int slot); + virtual void generate_address(Emitter &emitter, int enclosing) const override; - virtual std::string text() const { return "LocalVariable(" + name + ", " + type->text() + ")"; } + virtual std::string text() const override { return "LocalVariable(" + name + ", " + type->text() + ")"; } private: LocalVariable(const LocalVariable &); LocalVariable &operator=(const LocalVariable &); @@ -322,12 +661,14 @@ class LocalVariable: public Variable { class FunctionParameter: public LocalVariable { public: - FunctionParameter(const std::string &name, const Type *type, ParameterType::Mode mode, Scope *scope): LocalVariable(name, type, scope, mode == ParameterType::IN), mode(mode) {} + FunctionParameter(const Token &declaration, const std::string &name, const Type *type, ParameterType::Mode mode, const Expression *default_value): LocalVariable(declaration, name, type, mode == ParameterType::IN), mode(mode), default_value(default_value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } ParameterType::Mode mode; + const Expression *default_value; - virtual void generate_address(Emitter &emitter, int enclosing) const; + virtual void generate_address(Emitter &emitter, int enclosing) const override; - virtual std::string text() const { return "FunctionParameter(" + name + ", " + type->text() + ")"; } + virtual std::string text() const override { return "FunctionParameter(" + name + ", " + type->text() + ")"; } private: FunctionParameter(const FunctionParameter &); FunctionParameter &operator=(const FunctionParameter &); @@ -335,9 +676,11 @@ class FunctionParameter: public LocalVariable { class Exception: public Name { public: - Exception(const std::string &name): Name(name, TYPE_EXCEPTION) {} + Exception(const Token &declaration, const std::string &name): Name(declaration, name, TYPE_EXCEPTION) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + virtual void generate_export(Emitter &emitter, const std::string &name) const override; - virtual std::string text() const { return "Exception(" + name + ")"; } + virtual std::string text() const override { return "Exception(" + name + ")"; } private: Exception(const Exception &); Exception &operator=(const Exception &); @@ -347,14 +690,39 @@ class Expression: public AstNode { public: Expression(const Type *type, bool is_constant, bool is_readonly = true): type(type), is_constant(is_constant), is_readonly(is_readonly) {} - virtual Number eval_number() const = 0; - virtual std::string eval_string() const = 0; - virtual void generate(Emitter &emitter) const = 0; + bool eval_boolean(const Token &token) const; + Number eval_number(const Token &token) const; + std::string eval_string(const Token &token) const; + void generate(Emitter &emitter) const; + virtual void generate_expr(Emitter &emitter) const = 0; virtual void generate_call(Emitter &) const { internal_error("Expression::generate_call"); } const Type *type; const bool is_constant; const bool is_readonly; +protected: + virtual bool eval_boolean() const = 0; + virtual Number eval_number() const = 0; + virtual std::string eval_string() const = 0; + friend class TypeBoolean; + friend class TypeNumber; + friend class TypeString; + friend class UnaryMinusExpression; + friend class LogicalNotExpression; + friend class DisjunctionExpression; + friend class ConjunctionExpression; + friend class AdditionExpression; + friend class SubtractionExpression; + friend class MultiplicationExpression; + friend class DivisionExpression; + friend class ModuloExpression; + friend class ExponentiationExpression; + friend class BooleanComparisonExpression; + friend class NumericComparisonExpression; + friend class StringComparisonExpression; + friend class ConstantExpression; + friend class FunctionCall; + friend class ForStatement; private: Expression(const Expression &); Expression &operator=(const Expression &); @@ -362,11 +730,14 @@ class Expression: public AstNode { class Constant: public Name { public: - Constant(const std::string &name, const Expression *value): Name(name, value->type), value(value) {} + Constant(const Token &declaration, const std::string &name, const Expression *value): Name(declaration, name, value->type), value(value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *value; - virtual std::string text() const { return "Constant(" + name + ", " + value->text() + ")"; } + virtual void generate_export(Emitter &emitter, const std::string &name) const override; + + virtual std::string text() const override { return "Constant(" + name + ", " + value->text() + ")"; } private: Constant(const Constant &); Constant &operator=(const Constant &); @@ -375,78 +746,106 @@ class Constant: public Name { class ConstantBooleanExpression: public Expression { public: ConstantBooleanExpression(bool value): Expression(TYPE_BOOLEAN, true), value(value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const bool value; - virtual Number eval_number() const { internal_error("ConstantBooleanExpression"); } - virtual std::string eval_string() const { internal_error("ConstantBooleanExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { return value; } + virtual Number eval_number() const override { internal_error("ConstantBooleanExpression"); } + virtual std::string eval_string() const override { internal_error("ConstantBooleanExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const; + virtual std::string text() const override; }; class ConstantNumberExpression: public Expression { public: ConstantNumberExpression(Number value): Expression(TYPE_NUMBER, true), value(value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Number value; - virtual Number eval_number() const { return value; } - virtual std::string eval_string() const { internal_error("ConstantNumberExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ConstantNumberExpression"); } + virtual Number eval_number() const override { return value; } + virtual std::string eval_string() const override { internal_error("ConstantNumberExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const; + virtual std::string text() const override; }; class ConstantStringExpression: public Expression { public: ConstantStringExpression(const std::string &value): Expression(TYPE_STRING, true), value(value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const std::string value; - virtual Number eval_number() const { internal_error("ConstantStringExpression"); } - virtual std::string eval_string() const { return value; } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ConstantStringExpression"); } + virtual Number eval_number() const override { internal_error("ConstantStringExpression"); } + virtual std::string eval_string() const override { return value; } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const; + virtual std::string text() const override; +}; + +class ConstantBytesExpression: public Expression { +public: + ConstantBytesExpression(const std::string &name, const std::string &contents): Expression(TYPE_BYTES, true), name(name), contents(contents) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const std::string name; + const std::string contents; + + virtual bool eval_boolean() const override { internal_error("ConstantBytesExpression"); } + virtual Number eval_number() const override { internal_error("ConstantBytesExpression"); } + virtual std::string eval_string() const override { internal_error("ConstantBytesExpression"); } + virtual void generate_expr(Emitter &emitter) const override; + + virtual std::string text() const override; }; class ConstantEnumExpression: public Expression { public: ConstantEnumExpression(const TypeEnum *type, int value): Expression(type, true), value(value) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const int value; - virtual Number eval_number() const { return number_from_uint32(value); } - virtual std::string eval_string() const { internal_error("ConstantEnumExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ConstantEnumExpression"); } + virtual Number eval_number() const override { return number_from_uint32(value); } + virtual std::string eval_string() const override { internal_error("ConstantEnumExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const; + virtual std::string text() const override; }; class ConstantNilExpression: public Expression { public: - ConstantNilExpression(): Expression(new TypePointer(nullptr), true) {} + ConstantNilExpression(): Expression(new TypePointerNil(), true) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("ConstantNilExpression"); } - virtual std::string eval_string() const { internal_error("ConstantNilExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ConstantNilExpression"); } + virtual Number eval_number() const override { internal_error("ConstantNilExpression"); } + virtual std::string eval_string() const override { internal_error("ConstantNilExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "ConstantNilExpression"; } + virtual std::string text() const override { return "ConstantNilExpression"; } }; class ArrayLiteralExpression: public Expression { public: - ArrayLiteralExpression(const Type *elementtype, const std::vector &elements): Expression(new TypeArray(elementtype), all_constant(elements)), elementtype(elementtype), elements(elements) {} + ArrayLiteralExpression(const Type *elementtype, const std::vector &elements): Expression(new TypeArray(Token(), elementtype), all_constant(elements)), elementtype(elementtype), elements(elements) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Type *elementtype; const std::vector elements; - virtual Number eval_number() const { internal_error("ArrayLiteralExpression"); } - virtual std::string eval_string() const { internal_error("ArrayLiteralExpression"); } - virtual void generate(Emitter &) const; + virtual bool eval_boolean() const override { internal_error("ArrayLiteralExpression"); } + virtual Number eval_number() const override { internal_error("ArrayLiteralExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayLiteralExpression"); } + virtual void generate_expr(Emitter &) const override; - virtual std::string text() const { return "ArrayLiteralExpression(...)"; } + virtual std::string text() const override { return "ArrayLiteralExpression(...)"; } private: ArrayLiteralExpression(const ArrayLiteralExpression &); ArrayLiteralExpression &operator=(const ArrayLiteralExpression &); @@ -456,16 +855,18 @@ class ArrayLiteralExpression: public Expression { class DictionaryLiteralExpression: public Expression { public: - DictionaryLiteralExpression(const Type *elementtype, const std::vector> &elements): Expression(new TypeDictionary(elementtype), all_constant(elements)), elementtype(elementtype), dict(make_dictionary(elements)) {} + DictionaryLiteralExpression(const Type *elementtype, const std::vector> &elements): Expression(new TypeDictionary(Token(), elementtype), all_constant(elements)), elementtype(elementtype), dict(make_dictionary(elements)) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Type *elementtype; const std::map dict; - virtual Number eval_number() const { internal_error("DictionaryLiteralExpression"); } - virtual std::string eval_string() const { internal_error("DictionaryLiteralExpression"); } - virtual void generate(Emitter &) const; + virtual bool eval_boolean() const override { internal_error("DictionaryLiteralExpression"); } + virtual Number eval_number() const override { internal_error("DictionaryLiteralExpression"); } + virtual std::string eval_string() const override { internal_error("DictionaryLiteralExpression"); } + virtual void generate_expr(Emitter &) const override; - virtual std::string text() const { return "DictionaryLiteralExpression(...)"; } + virtual std::string text() const override { return "DictionaryLiteralExpression(...)"; } private: DictionaryLiteralExpression(const DictionaryLiteralExpression &); DictionaryLiteralExpression &operator=(const DictionaryLiteralExpression &); @@ -474,17 +875,39 @@ class DictionaryLiteralExpression: public Expression { static std::map make_dictionary(const std::vector> &elements); }; +class RecordLiteralExpression: public Expression { +public: + RecordLiteralExpression(const TypeRecord *type, const std::vector &values): Expression(type, all_constant(values)), values(values) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const std::vector values; + + virtual bool eval_boolean() const override { internal_error("RecordLiteralExpression"); } + virtual Number eval_number() const override { internal_error("RecordLiteralExpression"); } + virtual std::string eval_string() const override { internal_error("RecordLiteralExpression"); } + virtual void generate_expr(Emitter &) const override; + + virtual std::string text() const override { return "RecordLiteralExpression(...)"; } +private: + RecordLiteralExpression(const RecordLiteralExpression &); + RecordLiteralExpression &operator=(const RecordLiteralExpression &); + + static bool all_constant(const std::vector &values); +}; + class NewRecordExpression: public Expression { public: - NewRecordExpression(const TypeRecord *reftype): Expression(new TypePointer(reftype), false), fields(reftype->fields.size()) {} + NewRecordExpression(const TypeRecord *reftype): Expression(new TypePointer(Token(), reftype), false), fields(reftype->fields.size()) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const size_t fields; - virtual Number eval_number() const { internal_error("NewRecordExpression"); } - virtual std::string eval_string() const { internal_error("NewRecordExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("NewRecordExpression"); } + virtual Number eval_number() const override { internal_error("NewRecordExpression"); } + virtual std::string eval_string() const override { internal_error("NewRecordExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "NewRecordExpression(" + type->text() + ")"; } + virtual std::string text() const override { return "NewRecordExpression(" + type->text() + ")"; } private: NewRecordExpression(const NewRecordExpression &); NewRecordExpression &operator=(const NewRecordExpression &); @@ -493,18 +916,20 @@ class NewRecordExpression: public Expression { class UnaryMinusExpression: public Expression { public: UnaryMinusExpression(const Expression *value): Expression(value->type, value->is_constant), value(value) { - if (not type->is_equivalent(TYPE_NUMBER)) { + if (not type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("UnaryMinusExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const value; - virtual Number eval_number() const { return number_negate(value->eval_number()); } - virtual std::string eval_string() const { internal_error("UnaryMinusExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("UnaryMinusExpression"); } + virtual Number eval_number() const override { return number_negate(value->eval_number()); } + virtual std::string eval_string() const override { internal_error("UnaryMinusExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "UnaryMinusExpression(" + value->text() + ")"; } private: @@ -515,18 +940,20 @@ class UnaryMinusExpression: public Expression { class LogicalNotExpression: public Expression { public: LogicalNotExpression(const Expression *value): Expression(value->type, value->is_constant), value(value) { - if (not type->is_equivalent(TYPE_BOOLEAN)) { + if (not type->is_assignment_compatible(TYPE_BOOLEAN)) { internal_error("LogicalNotExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const value; - virtual Number eval_number() const { internal_error("LogicalNotExpression"); } - virtual std::string eval_string() const { internal_error("LogicalNotExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { return not value->eval_boolean(); } + virtual Number eval_number() const override { internal_error("LogicalNotExpression"); } + virtual std::string eval_string() const override { internal_error("LogicalNotExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "LogicalNotExpression(" + value->text() + ")"; } private: @@ -537,20 +964,22 @@ class LogicalNotExpression: public Expression { class ConditionalExpression: public Expression { public: ConditionalExpression(const Expression *condition, const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), condition(condition), left(left), right(right) { - if (not left->type->is_equivalent(right->type)) { + if (not left->type->is_assignment_compatible(right->type)) { internal_error("ConditionalExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *condition; const Expression *left; const Expression *right; - virtual Number eval_number() const { internal_error("ConditionalExpression"); } - virtual std::string eval_string() const { internal_error("ConditionalExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ConditionalExpression"); } + virtual Number eval_number() const override { internal_error("ConditionalExpression"); } + virtual std::string eval_string() const override { internal_error("ConditionalExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ConditionalExpression(" + condition->text() + "," + left->text() + "," + right->text() + ")"; } private: @@ -561,19 +990,21 @@ class ConditionalExpression: public Expression { class DisjunctionExpression: public Expression { public: DisjunctionExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_BOOLEAN) || not right->type->is_equivalent(TYPE_BOOLEAN)) { + if (not left->type->is_assignment_compatible(TYPE_BOOLEAN) || not right->type->is_assignment_compatible(TYPE_BOOLEAN)) { internal_error("DisjunctionExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { internal_error("DisjunctionExpression"); } - virtual std::string eval_string() const { internal_error("DisjunctionExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { return left->eval_boolean() || right->eval_boolean(); } + virtual Number eval_number() const override { internal_error("DisjunctionExpression"); } + virtual std::string eval_string() const override { internal_error("DisjunctionExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "DisjunctionExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -584,19 +1015,21 @@ class DisjunctionExpression: public Expression { class ConjunctionExpression: public Expression { public: ConjunctionExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_BOOLEAN) || not right->type->is_equivalent(TYPE_BOOLEAN)) { + if (not left->type->is_assignment_compatible(TYPE_BOOLEAN) || not right->type->is_assignment_compatible(TYPE_BOOLEAN)) { internal_error("ConjunctionExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { internal_error("ConjunctionExpression"); } - virtual std::string eval_string() const { internal_error("ConjunctionExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { return left->eval_boolean() && right->eval_boolean(); } + virtual Number eval_number() const override { internal_error("ConjunctionExpression"); } + virtual std::string eval_string() const override { internal_error("ConjunctionExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ConjunctionExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -607,15 +1040,17 @@ class ConjunctionExpression: public Expression { class ArrayInExpression: public Expression { public: ArrayInExpression(const Expression *left, const Expression *right): Expression(TYPE_BOOLEAN, false), left(left), right(right) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *left; const Expression *right; - virtual Number eval_number() const { internal_error("ArrayInExpression"); } - virtual std::string eval_string() const { internal_error("ArrayInExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ArrayInExpression"); } + virtual Number eval_number() const override { internal_error("ArrayInExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayInExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "ArrayInExpression(" + left->text() + ", " + right->text() + ")"; } + virtual std::string text() const override { return "ArrayInExpression(" + left->text() + ", " + right->text() + ")"; } private: ArrayInExpression(const ArrayInExpression &); ArrayInExpression &operator=(const ArrayInExpression &); @@ -624,15 +1059,17 @@ class ArrayInExpression: public Expression { class DictionaryInExpression: public Expression { public: DictionaryInExpression(const Expression *left, const Expression *right): Expression(TYPE_BOOLEAN, false), left(left), right(right) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *left; const Expression *right; - virtual Number eval_number() const { internal_error("DictionaryInExpression"); } - virtual std::string eval_string() const { internal_error("DictionaryInExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("DictionaryInExpression"); } + virtual Number eval_number() const override { internal_error("DictionaryInExpression"); } + virtual std::string eval_string() const override { internal_error("DictionaryInExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "DictionaryInExpression(" + left->text() + ", " + right->text() + ")"; } + virtual std::string text() const override { return "DictionaryInExpression(" + left->text() + ", " + right->text() + ")"; } private: DictionaryInExpression(const DictionaryInExpression &); DictionaryInExpression &operator=(const DictionaryInExpression &); @@ -648,20 +1085,43 @@ class ComparisonExpression: public Expression { const Expression *const left; const Expression *const right; const Comparison comp; + + virtual void generate_expr(Emitter &emitter) const override; + virtual void generate_comparison_opcode(Emitter &emitter) const = 0; private: ComparisonExpression(const ComparisonExpression &); ComparisonExpression &operator=(const ComparisonExpression &); }; +class ChainedComparisonExpression: public Expression { +public: + ChainedComparisonExpression(const std::vector &comps): Expression(TYPE_BOOLEAN, false), comps(comps) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const std::vector comps; + + virtual bool eval_boolean() const override { internal_error("ChainedComparisonExpression"); } + virtual Number eval_number() const override { internal_error("ChainedComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("ChainedComparisonExpression"); } + virtual void generate_expr(Emitter &emitter) const override; + + virtual std::string text() const override { return "ChainedComparisonExpression(...)"; } +private: + ChainedComparisonExpression(const ChainedComparisonExpression &); + ChainedComparisonExpression &operator=(const ChainedComparisonExpression &); +}; + class BooleanComparisonExpression: public ComparisonExpression { public: BooleanComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("BooleanComparisonExpression"); } - virtual std::string eval_string() const { internal_error("BooleanComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override; + virtual Number eval_number() const override { internal_error("BooleanComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("BooleanComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "BooleanComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -669,12 +1129,14 @@ class BooleanComparisonExpression: public ComparisonExpression { class NumericComparisonExpression: public ComparisonExpression { public: NumericComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("NumericComparisonExpression"); } - virtual std::string eval_string() const { internal_error("NumericComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override; + virtual Number eval_number() const override { internal_error("NumericComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("NumericComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "NumericComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -682,12 +1144,14 @@ class NumericComparisonExpression: public ComparisonExpression { class StringComparisonExpression: public ComparisonExpression { public: StringComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("StringComparisonExpression"); } - virtual std::string eval_string() const { internal_error("StringComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override; + virtual Number eval_number() const override { internal_error("StringComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("StringComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "StringComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -695,12 +1159,14 @@ class StringComparisonExpression: public ComparisonExpression { class ArrayComparisonExpression: public ComparisonExpression { public: ArrayComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("ArrayComparisonExpression"); } - virtual std::string eval_string() const { internal_error("ArrayComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ArrayComparisonExpression"); } + virtual Number eval_number() const override { internal_error("ArrayComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ArrayComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -708,12 +1174,14 @@ class ArrayComparisonExpression: public ComparisonExpression { class DictionaryComparisonExpression: public ComparisonExpression { public: DictionaryComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("DictionaryComparisonExpression"); } - virtual std::string eval_string() const { internal_error("DictionaryComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("DictionaryComparisonExpression"); } + virtual Number eval_number() const override { internal_error("DictionaryComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("DictionaryComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "DictionaryComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -721,12 +1189,14 @@ class DictionaryComparisonExpression: public ComparisonExpression { class PointerComparisonExpression: public ComparisonExpression { public: PointerComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual Number eval_number() const { internal_error("PointerComparisonExpression"); } - virtual std::string eval_string() const { internal_error("PointerComparisonExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("PointerComparisonExpression"); } + virtual Number eval_number() const override { internal_error("PointerComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("PointerComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "PointerComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; } }; @@ -737,9 +1207,9 @@ class ValidPointerExpression: public PointerComparisonExpression { const Variable *var; - virtual void generate(Emitter &emitter) const; + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ValidPointerExpression(" + left->text() + ")"; } private: @@ -747,22 +1217,39 @@ class ValidPointerExpression: public PointerComparisonExpression { ValidPointerExpression &operator=(const ValidPointerExpression &); }; +class FunctionPointerComparisonExpression: public ComparisonExpression { +public: + FunctionPointerComparisonExpression(const Expression *left, const Expression *right, Comparison comp): ComparisonExpression(left, right, comp) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual bool eval_boolean() const override { internal_error("FunctionPointerComparisonExpression"); } + virtual Number eval_number() const override { internal_error("FunctionPointerComparisonExpression"); } + virtual std::string eval_string() const override { internal_error("FunctionPointerComparisonExpression"); } + virtual void generate_comparison_opcode(Emitter &emitter) const override; + + virtual std::string text() const override { + return "FunctionPointerComparisonExpression(" + left->text() + std::to_string(comp) + right->text() + ")"; + } +}; + class AdditionExpression: public Expression { public: AdditionExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("AdditionExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_add(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("AdditionExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("AdditionExpression"); } + virtual Number eval_number() const override { return number_add(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("AdditionExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "AdditionExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -773,19 +1260,21 @@ class AdditionExpression: public Expression { class SubtractionExpression: public Expression { public: SubtractionExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("SubtractionExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_subtract(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("SubtractionExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("SubtractionExpression"); } + virtual Number eval_number() const override { return number_subtract(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("SubtractionExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "SubtractionExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -796,19 +1285,21 @@ class SubtractionExpression: public Expression { class MultiplicationExpression: public Expression { public: MultiplicationExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("MultiplicationExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_multiply(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("MultiplicationExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("MultiplicationExpression"); } + virtual Number eval_number() const override { return number_multiply(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("MultiplicationExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "MultiplicationExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -819,19 +1310,21 @@ class MultiplicationExpression: public Expression { class DivisionExpression: public Expression { public: DivisionExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("DivisionExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_divide(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("DivisionExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("DivisionExpression"); } + virtual Number eval_number() const override { return number_divide(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("DivisionExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "DivisionExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -842,19 +1335,21 @@ class DivisionExpression: public Expression { class ModuloExpression: public Expression { public: ModuloExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("ModuloExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_modulo(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("ModuloExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ModuloExpression"); } + virtual Number eval_number() const override { return number_modulo(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("ModuloExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ModuloExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -865,19 +1360,21 @@ class ModuloExpression: public Expression { class ExponentiationExpression: public Expression { public: ExponentiationExpression(const Expression *left, const Expression *right): Expression(left->type, left->is_constant && right->is_constant), left(left), right(right) { - if (not left->type->is_equivalent(TYPE_NUMBER) || not right->type->is_equivalent(TYPE_NUMBER)) { + if (not left->type->is_assignment_compatible(TYPE_NUMBER) || not right->type->is_assignment_compatible(TYPE_NUMBER)) { internal_error("ExponentiationExpression"); } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const left; const Expression *const right; - virtual Number eval_number() const { return number_pow(left->eval_number(), right->eval_number()); } - virtual std::string eval_string() const { internal_error("ExponentiationExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ExponentiationExpression"); } + virtual Number eval_number() const override { return number_pow(left->eval_number(), right->eval_number()); } + virtual std::string eval_string() const override { internal_error("ExponentiationExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ExponentiationExpression(" + left->text() + "," + right->text() + ")"; } private: @@ -889,7 +1386,7 @@ class ReferenceExpression: public Expression { public: ReferenceExpression(const Type *type, bool is_readonly): Expression(type, false, is_readonly) {} - virtual void generate(Emitter &emitter) const { generate_load(emitter); } + virtual void generate_expr(Emitter &emitter) const override { generate_load(emitter); } virtual void generate_address_read(Emitter &) const = 0; virtual void generate_address_write(Emitter &) const = 0; virtual void generate_load(Emitter &) const; @@ -899,19 +1396,38 @@ class ReferenceExpression: public Expression { ReferenceExpression &operator=(const ReferenceExpression &); }; +class DummyExpression: public ReferenceExpression { +public: + DummyExpression(): ReferenceExpression(TYPE_DUMMY, false) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual bool eval_boolean() const override { internal_error("DummyExpression"); } + virtual Number eval_number() const override { internal_error("DummyExpression"); } + virtual std::string eval_string() const override { internal_error("DummyExpression"); } + virtual void generate_address_read(Emitter &) const override { internal_error("DummyExpression"); } + virtual void generate_address_write(Emitter &) const override { internal_error("DummyExpression"); } + virtual void generate_load(Emitter &) const override { internal_error("DummyExpression"); } + virtual void generate_store(Emitter &) const override; + + virtual std::string text() const override { return "DummyExpression"; } +}; + class ArrayReferenceIndexExpression: public ReferenceExpression { public: - ArrayReferenceIndexExpression(const Type *type, const ReferenceExpression *array, const Expression *index): ReferenceExpression(type, array->is_readonly), array(array), index(index) {} + ArrayReferenceIndexExpression(const Type *type, const ReferenceExpression *array, const Expression *index, bool always_create): ReferenceExpression(type, array->is_readonly), array(array), index(index), always_create(always_create) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const ReferenceExpression *array; const Expression *index; + const bool always_create; - virtual Number eval_number() const { internal_error("ArrayReferenceIndexExpression"); } - virtual std::string eval_string() const { internal_error("ArrayReferenceIndexExpression"); } - virtual void generate_address_read(Emitter &) const; - virtual void generate_address_write(Emitter &) const; + virtual bool eval_boolean() const override { internal_error("ArrayReferenceIndexExpression"); } + virtual Number eval_number() const override { internal_error("ArrayReferenceIndexExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayReferenceIndexExpression"); } + virtual void generate_address_read(Emitter &) const override; + virtual void generate_address_write(Emitter &) const override; - virtual std::string text() const { return "ArrayReferenceIndexExpression(" + array->text() + ", " + index->text() + ")"; } + virtual std::string text() const override { return "ArrayReferenceIndexExpression(" + array->text() + ", " + index->text() + ")"; } private: ArrayReferenceIndexExpression(const ArrayReferenceIndexExpression &); ArrayReferenceIndexExpression &operator=(const ArrayReferenceIndexExpression &); @@ -919,16 +1435,19 @@ class ArrayReferenceIndexExpression: public ReferenceExpression { class ArrayValueIndexExpression: public Expression { public: - ArrayValueIndexExpression(const Type *type, const Expression *array, const Expression *index): Expression(type, false), array(array), index(index) {} + ArrayValueIndexExpression(const Type *type, const Expression *array, const Expression *index, bool always_create): Expression(type, false), array(array), index(index), always_create(always_create) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *array; const Expression *index; + const bool always_create; - virtual Number eval_number() const { internal_error("ArrayValueIndexExpression"); } - virtual std::string eval_string() const { internal_error("ArrayValueIndexExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("ArrayValueIndexExpression"); } + virtual Number eval_number() const override { internal_error("ArrayValueIndexExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayValueIndexExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "ArrayValueIndexExpression(" + array->text() + ", " + index->text() + ")"; } + virtual std::string text() const override { return "ArrayValueIndexExpression(" + array->text() + ", " + index->text() + ")"; } private: ArrayValueIndexExpression(const ArrayValueIndexExpression &); ArrayValueIndexExpression &operator=(const ArrayValueIndexExpression &); @@ -937,16 +1456,18 @@ class ArrayValueIndexExpression: public Expression { class DictionaryReferenceIndexExpression: public ReferenceExpression { public: DictionaryReferenceIndexExpression(const Type *type, const ReferenceExpression *dictionary, const Expression *index): ReferenceExpression(type, dictionary->is_readonly), dictionary(dictionary), index(index) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const ReferenceExpression *dictionary; const Expression *index; - virtual Number eval_number() const { internal_error("DictionaryReferenceIndexExpression"); } - virtual std::string eval_string() const { internal_error("DictionaryReferenceIndexExpression"); } - virtual void generate_address_read(Emitter &) const; - virtual void generate_address_write(Emitter &) const; + virtual bool eval_boolean() const override { internal_error("DictionaryReferenceIndexExpression"); } + virtual Number eval_number() const override { internal_error("DictionaryReferenceIndexExpression"); } + virtual std::string eval_string() const override { internal_error("DictionaryReferenceIndexExpression"); } + virtual void generate_address_read(Emitter &) const override; + virtual void generate_address_write(Emitter &) const override; - virtual std::string text() const { return "DictionaryReferenceIndexExpression(" + dictionary->text() + ", " + index->text() + ")"; } + virtual std::string text() const override { return "DictionaryReferenceIndexExpression(" + dictionary->text() + ", " + index->text() + ")"; } private: DictionaryReferenceIndexExpression(const DictionaryReferenceIndexExpression &); DictionaryReferenceIndexExpression &operator=(const DictionaryReferenceIndexExpression &); @@ -955,74 +1476,231 @@ class DictionaryReferenceIndexExpression: public ReferenceExpression { class DictionaryValueIndexExpression: public Expression { public: DictionaryValueIndexExpression(const Type *type, const Expression *dictionary, const Expression *index): Expression(type, false), dictionary(dictionary), index(index) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *dictionary; const Expression *index; - virtual Number eval_number() const { internal_error("DictionaryValueIndexExpression"); } - virtual std::string eval_string() const { internal_error("DictionaryValueIndexExpression"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("DictionaryValueIndexExpression"); } + virtual Number eval_number() const override { internal_error("DictionaryValueIndexExpression"); } + virtual std::string eval_string() const override { internal_error("DictionaryValueIndexExpression"); } + virtual void generate_expr(Emitter &emitter) const override; - virtual std::string text() const { return "DictionaryValueIndexExpression(" + dictionary->text() + ", " + index->text() + ")"; } + virtual std::string text() const override { return "DictionaryValueIndexExpression(" + dictionary->text() + ", " + index->text() + ")"; } private: DictionaryValueIndexExpression(const DictionaryValueIndexExpression &); DictionaryValueIndexExpression &operator=(const DictionaryValueIndexExpression &); }; -class StringIndexExpression: public ReferenceExpression { +class StringReferenceIndexExpression: public ReferenceExpression { public: - StringIndexExpression(const ReferenceExpression *ref, const Expression *index); + StringReferenceIndexExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const ReferenceExpression *ref; - const Expression *index; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; const FunctionCall *load; const FunctionCall *store; - virtual Number eval_number() const { internal_error("StringIndexExpression"); } - virtual std::string eval_string() const { internal_error("StringIndexExpression"); } - virtual void generate_address_read(Emitter &) const { internal_error("StringIndexExpression"); } - virtual void generate_address_write(Emitter &) const { internal_error("StringIndexExpression"); } - virtual void generate_load(Emitter &) const; - virtual void generate_store(Emitter &) const; + virtual bool eval_boolean() const override { internal_error("StringReferenceIndexExpression"); } + virtual Number eval_number() const override { internal_error("StringReferenceIndexExpression"); } + virtual std::string eval_string() const override { internal_error("StringReferenceIndexExpression"); } + virtual void generate_address_read(Emitter &) const override { internal_error("StringReferenceIndexExpression"); } + virtual void generate_address_write(Emitter &) const override { internal_error("StringReferenceIndexExpression"); } + virtual void generate_load(Emitter &) const override; + virtual void generate_store(Emitter &) const override; + + virtual std::string text() const override { return "StringReferenceIndexExpression(" + ref->text() + ", " + first->text() + ", " + last->text() + ")"; } +private: + StringReferenceIndexExpression(const StringReferenceIndexExpression &); + StringReferenceIndexExpression &operator=(const StringReferenceIndexExpression &); +}; + +class StringValueIndexExpression: public Expression { +public: + StringValueIndexExpression(const Expression *str, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual std::string text() const { return "StringIndexExpression(" + ref->text() + ", " + index->text() + ")"; } + const Expression *str; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; + + const FunctionCall *load; + + virtual bool eval_boolean() const override { internal_error("StringValueIndexExpression"); } + virtual Number eval_number() const override { internal_error("StringValueIndexExpression"); } + virtual std::string eval_string() const override { internal_error("StringValueIndexExpression"); } + virtual void generate_expr(Emitter &) const override; + + virtual std::string text() const override { return "StringValueIndexExpression(" + str->text() + ", " + first->text() + ", " + last->text() + ")"; } private: - StringIndexExpression(const StringIndexExpression &); - StringIndexExpression &operator=(const StringIndexExpression &); + StringValueIndexExpression(const StringValueIndexExpression &); + StringValueIndexExpression &operator=(const StringValueIndexExpression &); +}; + +class BytesReferenceIndexExpression: public ReferenceExpression { +public: + BytesReferenceIndexExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const ReferenceExpression *ref; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; + + const FunctionCall *load; + const FunctionCall *store; + + virtual bool eval_boolean() const override { internal_error("BytesReferenceIndexExpression"); } + virtual Number eval_number() const override { internal_error("BytesReferenceIndexExpression"); } + virtual std::string eval_string() const override { internal_error("BytesReferenceIndexExpression"); } + virtual void generate_address_read(Emitter &) const override { internal_error("BytesReferenceIndexExpression"); } + virtual void generate_address_write(Emitter &) const override { internal_error("BytesReferenceIndexExpression"); } + virtual void generate_load(Emitter &) const override; + virtual void generate_store(Emitter &) const override; + + virtual std::string text() const override { return "BytesReferenceIndexExpression(" + ref->text() + ", " + first->text() + ", " + last->text() + ")"; } +private: + BytesReferenceIndexExpression(const BytesReferenceIndexExpression &); + BytesReferenceIndexExpression &operator=(const BytesReferenceIndexExpression &); +}; + +class BytesValueIndexExpression: public Expression { +public: + BytesValueIndexExpression(const Expression *str, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const Expression *str; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; + + const FunctionCall *load; + + virtual bool eval_boolean() const override { internal_error("BytesValueIndexExpression"); } + virtual Number eval_number() const override { internal_error("BytesValueIndexExpression"); } + virtual std::string eval_string() const override { internal_error("BytesValueIndexExpression"); } + virtual void generate_expr(Emitter &) const override; + + virtual std::string text() const override { return "BytesValueIndexExpression(" + str->text() + ", " + first->text() + ", " + last->text() + ")"; } +private: + BytesValueIndexExpression(const BytesValueIndexExpression &); + BytesValueIndexExpression &operator=(const BytesValueIndexExpression &); +}; + +class ArrayReferenceRangeExpression: public ReferenceExpression { +public: + ArrayReferenceRangeExpression(const ReferenceExpression *ref, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const ReferenceExpression *ref; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; + + const FunctionCall *load; + const FunctionCall *store; + + virtual bool eval_boolean() const override { internal_error("ArrayReferenceRangeExpression"); } + virtual Number eval_number() const override { internal_error("ArrayReferenceRangeExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayReferenceRangeExpression"); } + virtual void generate_address_read(Emitter &) const override { internal_error("StringReferenceRangeExpression"); } + virtual void generate_address_write(Emitter &) const override { internal_error("StringReferenceRangeExpression"); } + virtual void generate_load(Emitter &) const override; + virtual void generate_store(Emitter &) const override; + + virtual std::string text() const override { return "ArrayReferenceRangeExpression(" + ref->text() + ", " + first->text() + ", " + last->text() + ")"; } +private: + ArrayReferenceRangeExpression(const ArrayReferenceRangeExpression &); + ArrayReferenceRangeExpression &operator=(const ArrayReferenceRangeExpression &); +}; + +class ArrayValueRangeExpression: public Expression { +public: + ArrayValueRangeExpression(const Expression *array, const Expression *first, bool first_from_end, const Expression *last, bool last_from_end, Analyzer *analyzer); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const Expression *array; + const Expression *first; + const bool first_from_end; + const Expression *last; + const bool last_from_end; + + const FunctionCall *load; + + virtual bool eval_boolean() const override { internal_error("ArrayValueRangeExpression"); } + virtual Number eval_number() const override { internal_error("ArrayValueRangeExpression"); } + virtual std::string eval_string() const override { internal_error("ArrayValueRangeExpression"); } + virtual void generate_expr(Emitter &emitter) const override; + + virtual std::string text() const override { return "ArrayValueRangeExpression(" + array->text() + ", " + first->text() + ", " + last->text() + ")"; } +private: + ArrayValueRangeExpression(const ArrayValueRangeExpression &); + ArrayValueRangeExpression &operator=(const ArrayValueRangeExpression &); }; class PointerDereferenceExpression: public ReferenceExpression { public: PointerDereferenceExpression(const Type *type, const Expression *ptr): ReferenceExpression(type, false), ptr(ptr) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *ptr; - virtual Number eval_number() const { internal_error("PointerDereferenceExpression"); } - virtual std::string eval_string() const { internal_error("PointerDereferenceExpression"); } - virtual void generate_address_read(Emitter &emitter) const; - virtual void generate_address_write(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("PointerDereferenceExpression"); } + virtual Number eval_number() const override { internal_error("PointerDereferenceExpression"); } + virtual std::string eval_string() const override { internal_error("PointerDereferenceExpression"); } + virtual void generate_address_read(Emitter &emitter) const override; + virtual void generate_address_write(Emitter &emitter) const override; - virtual std::string text() const { return "PointerDereferenceExpression(" + ptr->text() + ")"; } + virtual std::string text() const override { return "PointerDereferenceExpression(" + ptr->text() + ")"; } private: PointerDereferenceExpression(const PointerDereferenceExpression &); PointerDereferenceExpression &operator=(const PointerDereferenceExpression &); }; +class ConstantExpression: public Expression { +public: + ConstantExpression(const Constant *constant): Expression(constant->type, true, true), constant(constant) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const Constant *constant; + + virtual bool eval_boolean() const override { return constant->value->eval_boolean(); } + virtual Number eval_number() const override { return constant->value->eval_number(); } + virtual std::string eval_string() const override { return constant->value->eval_string(); } + virtual void generate_expr(Emitter &emitter) const override { constant->value->generate(emitter); } + + virtual std::string text() const override { return "ConstantExpression(" + constant->text() + ")"; } +private: + ConstantExpression(const ConstantExpression &); + ConstantExpression &operator=(const ConstantExpression &); +}; + class VariableExpression: public ReferenceExpression { public: VariableExpression(const Variable *var): ReferenceExpression(var->type, var->is_readonly), var(var) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Variable *var; - virtual Number eval_number() const { internal_error("VariableExpression"); } - virtual std::string eval_string() const { internal_error("VariableExpression"); } - virtual void generate(Emitter &emitter) const; - virtual void generate_call(Emitter &emitter) const { var->generate_call(emitter); } - virtual void generate_address_read(Emitter &emitter) const { var->generate_address(emitter, 0); } - virtual void generate_address_write(Emitter &emitter) const { var->generate_address(emitter, 0); } + virtual bool eval_boolean() const override { internal_error("VariableExpression"); } + virtual Number eval_number() const override { internal_error("VariableExpression"); } + virtual std::string eval_string() const override { internal_error("VariableExpression"); } + virtual void generate_expr(Emitter &emitter) const override; + virtual void generate_call(Emitter &emitter) const override { var->generate_call(emitter); } + virtual void generate_address_read(Emitter &emitter) const override { var->generate_address(emitter, 0); } + virtual void generate_address_write(Emitter &emitter) const override { var->generate_address(emitter, 0); } - virtual std::string text() const { + virtual std::string text() const override { return "VariableExpression(" + var->text() + ")"; } private: @@ -1032,19 +1710,55 @@ class VariableExpression: public ReferenceExpression { class FunctionCall: public Expression { public: - FunctionCall(const Expression *func, const std::vector &args): Expression(dynamic_cast(func->type)->returntype, false), func(func), args(args) {} + FunctionCall(const Expression *func, const std::vector &args): Expression(get_expr_type(func), is_intrinsic(func, args)), func(func), args(args) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const func; const std::vector args; - virtual Number eval_number() const { internal_error("FunctionCall"); } - virtual std::string eval_string() const { internal_error("FunctionCall"); } - virtual void generate(Emitter &emitter) const; + virtual bool eval_boolean() const override { internal_error("FunctionCall"); } + virtual Number eval_number() const override; + virtual std::string eval_string() const override; + virtual void generate_expr(Emitter &emitter) const override; + void generate_parameters(Emitter &emitter) const; + bool all_in_parameters() const; - virtual std::string text() const; + virtual std::string text() const override; private: FunctionCall(const FunctionCall &); FunctionCall &operator=(const FunctionCall &); + + static const Type *get_expr_type(const Expression *func) { + const TypeFunction *f = dynamic_cast(func->type); + if (f != nullptr) { + return f->returntype; + } + const TypeFunctionPointer *p = dynamic_cast(func->type); + if (p != nullptr) { + return p->functype->returntype; + } + internal_error("not function or functionpointer"); + } + + static bool is_intrinsic(const Expression *func, const std::vector &args); +}; + +class StatementExpression: public Expression { +public: + StatementExpression(const Statement *stmt): Expression(TYPE_NOTHING, false), stmt(stmt) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const Statement *const stmt; + + virtual bool eval_boolean() const override { internal_error("StatementExpression"); } + virtual Number eval_number() const override { internal_error("StatementExpression"); } + virtual std::string eval_string() const override { internal_error("StatementExpression"); } + virtual void generate_expr(Emitter &emitter) const override; + + virtual std::string text() const override { return "StatementExpression"; } +private: + StatementExpression(const StatementExpression &); + StatementExpression &operator=(const StatementExpression &); }; class Statement: public AstNode { @@ -1058,22 +1772,59 @@ class Statement: public AstNode { virtual void generate_code(Emitter &emitter) const = 0; }; +class CompoundStatement: public Statement { +public: + CompoundStatement(int line, const std::vector &statements): Statement(line), statements(statements) {} + + const std::vector statements; + + virtual void dumpsubnodes(std::ostream &out, int depth) const override; +}; + +class NullStatement: public Statement { +public: + NullStatement(int line): Statement(line) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + virtual void generate_code(Emitter &) const override {} + + virtual std::string text() const override { return "NullStatement"; } +}; + +class AssertStatement: public CompoundStatement { +public: + AssertStatement(int line, const std::vector &statements, const Expression *expr, const std::string &source): CompoundStatement(line, statements), expr(expr), source(source) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const Expression *const expr; + const std::string source; + + virtual void generate_code(Emitter &emitter) const override; + + virtual std::string text() const override { return "AssertStatement(" + expr->text() + ")"; } + +private: + AssertStatement(const AssertStatement &); + AssertStatement &operator=(const AssertStatement &); +}; + class AssignmentStatement: public Statement { public: - AssignmentStatement(int line, const std::vector vars, const Expression *expr): Statement(line), variables(vars), expr(expr) { + AssignmentStatement(int line, const std::vector &vars, const Expression *expr): Statement(line), variables(vars), expr(expr) { for (auto v: variables) { - if (not v->type->is_equivalent(expr->type)) { + if (not v->type->is_assignment_compatible(expr->type)) { internal_error("AssignmentStatement"); } } } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const std::vector variables; const Expression *const expr; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { std::string s = "AssignmentStatement("; for (auto v: variables) { s += v->text() + ", "; @@ -1089,12 +1840,13 @@ class AssignmentStatement: public Statement { class ExpressionStatement: public Statement { public: ExpressionStatement(int line, const Expression *expr): Statement(line), expr(expr) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const expr; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ExpressionStatement(" + expr->text() + ")"; } private: @@ -1105,40 +1857,51 @@ class ExpressionStatement: public Statement { class ReturnStatement: public Statement { public: ReturnStatement(int line, const Expression *expr): Statement(line), expr(expr) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *const expr; - virtual bool always_returns() const { return true; } + virtual bool always_returns() const override { return true; } - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "ReturnStatement(" + expr->text() + ")"; } + virtual std::string text() const override { return "ReturnStatement(" + expr->text() + ")"; } private: ReturnStatement(const ReturnStatement &); ReturnStatement &operator=(const ReturnStatement &); }; -class CompoundStatement: public Statement { +class IncrementStatement: public Statement { public: - CompoundStatement(int line, const std::vector &statements): Statement(line), statements(statements) {} + IncrementStatement(int line, const ReferenceExpression *ref, int delta): Statement(line), ref(ref), delta(delta) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - const std::vector statements; + const ReferenceExpression *ref; + int delta; + + virtual void generate_code(Emitter &emitter) const override; - virtual void dumpsubnodes(std::ostream &out, int depth) const; + virtual std::string text() const override { + return "IncrementStatement(" + ref->text() + ", " + std::to_string(delta) + ")"; + } +private: + IncrementStatement(const IncrementStatement &); + IncrementStatement &operator=(const IncrementStatement &); }; class IfStatement: public Statement { public: IfStatement(int line, const std::vector>> &condition_statements, const std::vector &else_statements): Statement(line), condition_statements(condition_statements), else_statements(else_statements) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const std::vector>> condition_statements; const std::vector else_statements; - virtual bool always_returns() const; + virtual bool always_returns() const override; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "IfStatement(" + condition_statements[0].first->text() + ")"; } private: @@ -1156,12 +1919,13 @@ class BaseLoopStatement: public CompoundStatement { class WhileStatement: public BaseLoopStatement { public: WhileStatement(int line, unsigned int loop_id, const Expression *condition, const std::vector &statements): BaseLoopStatement(line, loop_id, statements), condition(condition) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *condition; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "WhileStatement(" + condition->text() + ")"; } private: @@ -1171,17 +1935,19 @@ class WhileStatement: public BaseLoopStatement { class ForStatement: public BaseLoopStatement { public: - ForStatement(int line, unsigned int loop_id, const VariableExpression *var, const Expression *start, const Expression *end, const Expression *step, const std::vector &statements): BaseLoopStatement(line, loop_id, statements), var(var), start(start), end(end), step(step) { + ForStatement(int line, unsigned int loop_id, const VariableExpression *var, const Expression *start, const Expression *end, const Expression *step, const VariableExpression *bound, const std::vector &statements): BaseLoopStatement(line, loop_id, statements), var(var), start(start), end(end), step(step), bound(bound) { } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const VariableExpression *var; const Expression *start; const Expression *end; const Expression *step; + const VariableExpression *bound; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "ForStatement(" + var->text() + "(" + start->text() + " TO " + end->text() + " STEP " + step->text() + ")"; } private: @@ -1189,15 +1955,37 @@ class ForStatement: public BaseLoopStatement { ForStatement &operator=(const ForStatement &); }; +class ForeachStatement: public BaseLoopStatement { +public: + ForeachStatement(int line, unsigned int loop_id, const VariableExpression *var, const Expression *array, const VariableExpression *index, const VariableExpression *bound, const std::vector &statements): BaseLoopStatement(line, loop_id, statements), var(var), array(array), index(index), bound(bound) { + } + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const VariableExpression *var; + const Expression *array; + const VariableExpression *index; + const VariableExpression *bound; + + virtual void generate_code(Emitter &emitter) const override; + + virtual std::string text() const override { + return "ForeachStatement(" + var->text() + "(" + array->text() + ")"; + } +private: + ForeachStatement(const ForeachStatement &); + ForeachStatement &operator=(const ForeachStatement &); +}; + class LoopStatement: public BaseLoopStatement { public: LoopStatement(int line, unsigned int loop_id, const std::vector &statements): BaseLoopStatement(line, loop_id, statements) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } - virtual bool always_returns() const; + virtual bool always_returns() const override; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "LoopStatement()"; } + virtual std::string text() const override { return "LoopStatement()"; } private: LoopStatement(const LoopStatement &); LoopStatement &operator=(const LoopStatement &); @@ -1206,12 +1994,13 @@ class LoopStatement: public BaseLoopStatement { class RepeatStatement: public BaseLoopStatement { public: RepeatStatement(int line, unsigned int loop_id, const Expression *condition, const std::vector &statements): BaseLoopStatement(line, loop_id, statements), condition(condition) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *condition; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "RepeatStatement(" + condition->text() + ")"; } + virtual std::string text() const override { return "RepeatStatement(" + condition->text() + ")"; } private: RepeatStatement(const RepeatStatement &); RepeatStatement &operator=(const RepeatStatement &); @@ -1221,42 +2010,48 @@ class CaseStatement: public Statement { public: class WhenCondition { public: + WhenCondition(const Token &token): token(token) {} virtual ~WhenCondition() {} + const Token token; virtual bool overlaps(const WhenCondition *cond) const = 0; virtual void generate(Emitter &emitter) const = 0; + private: + WhenCondition(const WhenCondition &); + WhenCondition &operator=(const WhenCondition &); }; class ComparisonWhenCondition: public WhenCondition { public: - ComparisonWhenCondition(ComparisonExpression::Comparison comp, const Expression *expr): comp(comp), expr(expr) {} + ComparisonWhenCondition(const Token &token, ComparisonExpression::Comparison comp, const Expression *expr): WhenCondition(token), comp(comp), expr(expr) {} ComparisonExpression::Comparison comp; const Expression *expr; - virtual bool overlaps(const WhenCondition *cond) const; - virtual void generate(Emitter &emitter) const; + virtual bool overlaps(const WhenCondition *cond) const override; + virtual void generate(Emitter &emitter) const override; private: ComparisonWhenCondition(const ComparisonWhenCondition &); ComparisonWhenCondition &operator=(const ComparisonWhenCondition &); }; class RangeWhenCondition: public WhenCondition { public: - RangeWhenCondition(const Expression *low_expr, const Expression *high_expr): low_expr(low_expr), high_expr(high_expr) {} + RangeWhenCondition(const Token &token, const Expression *low_expr, const Expression *high_expr): WhenCondition(token), low_expr(low_expr), high_expr(high_expr) {} const Expression *low_expr; const Expression *high_expr; - virtual bool overlaps(const WhenCondition *cond) const; - virtual void generate(Emitter &emitter) const; + virtual bool overlaps(const WhenCondition *cond) const override; + virtual void generate(Emitter &emitter) const override; private: RangeWhenCondition(const RangeWhenCondition &); RangeWhenCondition &operator=(const RangeWhenCondition &); }; CaseStatement(int line, const Expression *expr, const std::vector, std::vector>> &clauses): Statement(line), expr(expr), clauses(clauses) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Expression *expr; const std::vector, std::vector>> clauses; - virtual bool always_returns() const; + virtual bool always_returns() const override; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { + virtual std::string text() const override { return "CaseStatement(" + expr->text() + ")"; } private: @@ -1267,12 +2062,13 @@ class CaseStatement: public Statement { class ExitStatement: public Statement { public: ExitStatement(int line, unsigned int loop_id): Statement(line), loop_id(loop_id) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const unsigned int loop_id; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "ExitStatement(...)"; } + virtual std::string text() const override { return "ExitStatement(...)"; } private: ExitStatement(const ExitStatement &); ExitStatement &operator=(const ExitStatement &); @@ -1281,12 +2077,13 @@ class ExitStatement: public Statement { class NextStatement: public Statement { public: NextStatement(int line, unsigned int loop_id): Statement(line), loop_id(loop_id) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const unsigned int loop_id; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "NextStatement(...)"; } + virtual std::string text() const override { return "NextStatement(...)"; } private: NextStatement(const NextStatement &); NextStatement &operator=(const NextStatement &); @@ -1295,15 +2092,16 @@ class NextStatement: public Statement { class TryStatement: public Statement { public: TryStatement(int line, const std::vector &statements, const std::vector, std::vector>> &catches): Statement(line), statements(statements), catches(catches) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const std::vector statements; const std::vector, std::vector>> catches; - virtual bool always_returns() const; + virtual bool always_returns() const override; - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "TryStatement(...)"; } + virtual std::string text() const override { return "TryStatement(...)"; } private: TryStatement(const TryStatement &); TryStatement &operator=(const TryStatement &); @@ -1311,24 +2109,50 @@ class TryStatement: public Statement { class RaiseStatement: public Statement { public: - RaiseStatement(int line, const Exception *exception): Statement(line), exception(exception) {} + RaiseStatement(int line, const Exception *exception, const Expression *info): Statement(line), exception(exception), info(info) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } const Exception *exception; + const Expression *info; - virtual bool always_returns() const { return true; } + virtual bool always_returns() const override { return true; } - virtual void generate_code(Emitter &emitter) const; + virtual void generate_code(Emitter &emitter) const override; - virtual std::string text() const { return "RaiseStatement(" + exception->text() + ")"; } + virtual std::string text() const override { return "RaiseStatement(" + exception->text() + ")"; } private: RaiseStatement(const RaiseStatement &); RaiseStatement &operator=(const RaiseStatement &); }; +class ResetStatement: public Statement { +public: + ResetStatement(int line, const std::vector &vars): Statement(line), variables(vars) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + + const std::vector variables; + + virtual void generate_code(Emitter &emitter) const override; + + virtual std::string text() const override { + std::string s = "ResetStatement("; + for (auto v: variables) { + s += v->text() + ", "; + } + return s + ")"; + } + +private: + ResetStatement(const ResetStatement &); + ResetStatement &operator=(const ResetStatement &); +}; + class Function: public Variable { public: - Function(const std::string &name, const Type *returntype, Scope *scope, const std::vector ¶ms): Variable(name, makeFunctionType(returntype, params), true), scope(scope), params(params), entry_label(UINT_MAX), statements() {} + Function(const Token &declaration, const std::string &name, const Type *returntype, Frame *outer, Scope *parent, const std::vector ¶ms); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + Frame *frame; Scope *scope; const std::vector params; mutable unsigned int entry_label; @@ -1337,14 +2161,17 @@ class Function: public Variable { static const Type *makeFunctionType(const Type *returntype, const std::vector ¶ms); - virtual void predeclare(Emitter &emitter) const; - virtual void postdeclare(Emitter &emitter) const; - virtual void generate_address(Emitter &, int) const { internal_error("Function"); } - virtual void generate_load(Emitter &) const { internal_error("Function"); } - virtual void generate_store(Emitter &) const { internal_error("Function"); } - virtual void generate_call(Emitter &emitter) const; + virtual void predeclare(Emitter &emitter) const override; + virtual void postdeclare(Emitter &emitter) const override; + virtual void generate_address(Emitter &, int) const override {} + virtual void generate_load(Emitter &) const override; + virtual void generate_store(Emitter &) const override { internal_error("Function"); } + virtual void generate_call(Emitter &emitter) const override; + virtual void generate_export(Emitter &emitter, const std::string &name) const override; - virtual std::string text() const { return "Function(" + name + ", " + type->text() + ")"; } + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const; + + virtual std::string text() const override { return "Function(" + name + ", " + type->text() + ")"; } private: Function(const Function &); Function &operator=(const Function &); @@ -1352,29 +2179,46 @@ class Function: public Variable { class PredefinedFunction: public Variable { public: - PredefinedFunction(const std::string &name, const Type *type): Variable(name, type, true), name_index(-1) {} + PredefinedFunction(const std::string &name, const Type *type): Variable(Token(), name, type, true), name_index(-1) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } mutable int name_index; - virtual void predeclare(Emitter &emitter) const; - virtual void generate_address(Emitter &, int) const { internal_error("PredefinedFunction"); } - virtual void generate_load(Emitter &) const { internal_error("PredefinedFunction"); } - virtual void generate_store(Emitter &) const { internal_error("PredefinedFunction"); } - virtual void generate_call(Emitter &emitter) const; + virtual void predeclare(Emitter &emitter) const override; + virtual void generate_address(Emitter &, int) const override { internal_error("PredefinedFunction"); } + virtual void generate_load(Emitter &) const override { internal_error("PredefinedFunction"); } + virtual void generate_store(Emitter &) const override { internal_error("PredefinedFunction"); } + virtual void generate_call(Emitter &emitter) const override; - virtual std::string text() const { return "PredefinedFunction(" + name + ", " + type->text() + ")"; } + virtual std::string text() const override { return "PredefinedFunction(" + name + ", " + type->text() + ")"; } +}; + +class ModuleFunction: public Variable { +public: + ModuleFunction(const std::string &module, const std::string &name, const Type *type, unsigned int entry): Variable(Token(), name, type, true), module(module), entry(entry) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + const std::string module; + const unsigned int entry; + + virtual void predeclare(Emitter &emitter) const override; + virtual void generate_address(Emitter &, int) const override { internal_error("ModuleFunction"); } + virtual void generate_load(Emitter &) const override { internal_error("ModuleFunction"); } + virtual void generate_store(Emitter &) const override { internal_error("ModuleFunction"); } + virtual void generate_call(Emitter &emitter) const override; + + virtual std::string text() const override { return "ModuleFunction(" + module + "." + name + ", " + type->text() + ")"; } }; class ExternalFunction: public Function { public: - ExternalFunction(const std::string &name, const Type *returntype, Scope *scope, const std::vector ¶ms, const std::string &library_name, const std::map ¶m_types): Function(name, returntype, scope, params), library_name(library_name), param_types(param_types), external_index(-1) {} + ExternalFunction(const Token &declaration, const std::string &name, const Type *returntype, Frame *outer, Scope *parent, const std::vector ¶ms): Function(declaration, name, returntype, outer, parent, params), library_name(), param_types(), external_index(-1) {} - const std::string library_name; - const std::map param_types; + std::string library_name; + std::map param_types; mutable int external_index; - virtual void predeclare(Emitter &) const; - virtual void postdeclare(Emitter &) const {} // TODO: Stub this out so we don't inherit the one from Function. Fix Function hierarchy. - virtual void generate_call(Emitter &emitter) const; + virtual void predeclare(Emitter &) const override; + virtual void postdeclare(Emitter &) const override; + virtual void generate_call(Emitter &emitter) const override; private: ExternalFunction(const ExternalFunction &); @@ -1383,13 +2227,15 @@ class ExternalFunction: public Function { class Module: public Name { public: - Module(Scope *scope, const std::string &name): Name(name, TYPE_MODULE), scope(new Scope(scope)) {} + Module(const Token &declaration, Scope *scope, const std::string &name): Name(declaration, name, TYPE_MODULE), scope(new Scope(scope, scope->frame)) {} + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } Scope *scope; - virtual void predeclare(Emitter &emitter) const; + virtual void predeclare(Emitter &emitter) const override; + virtual void generate_export(Emitter &, const std::string &) const override { internal_error("can't export module"); } - virtual std::string text() const { return "Module"; } + virtual std::string text() const override { return "Module"; } private: Module(const Module &); Module &operator=(const Module &); @@ -1397,15 +2243,22 @@ class Module: public Name { class Program: public AstNode { public: - Program(); + Program(const std::string &source_path, const std::string &source_hash); + virtual void accept(IAstVisitor *visitor) const override { visitor->visit(this); } + const std::string source_path; + const std::string source_hash; + Frame *frame; Scope *scope; std::vector statements; + std::map exports; virtual void generate(Emitter &emitter) const; - virtual std::string text() const { return "Program"; } - virtual void dumpsubnodes(std::ostream &out, int depth) const; + virtual void debuginfo(Emitter &emitter, minijson::object_writer &out) const; + + virtual std::string text() const override { return "Program"; } + virtual void dumpsubnodes(std::ostream &out, int depth) const override; private: Program(const Program &); Program &operator=(const Program &); diff --git a/src/bundle.cpp b/src/bundle.cpp new file mode 100644 index 0000000000..1b03412f3b --- /dev/null +++ b/src/bundle.cpp @@ -0,0 +1,82 @@ +#include "bundle.h" + +#include +#include +#include +#include +#include + +#include + +#include "exec.h" +#include "support.h" + +std::map> g_Contents; + +class ZipSupport: public ICompilerSupport { +public: + virtual bool loadBytecode(const std::string &module, Bytecode &bytecode); +}; + +bool ZipSupport::loadBytecode(const std::string &module, Bytecode &bytecode) +{ + auto i = g_Contents.find(module + ".neonx"); + if (i == g_Contents.end()) { + return false; + } + bytecode = Bytecode(i->second); + return true; +} + +void run_from_bundle(const std::string &name, bool enable_assert, unsigned short debug_port, int argc, char *argv[]) +{ + unzFile zip = unzOpen(name.c_str()); + if (zip == NULL) { + fprintf(stderr, "zip open error\n"); + exit(1); + } + int r = unzGoToFirstFile(zip); + if (r != UNZ_OK) { + fprintf(stderr, "zip first\n"); + exit(1); + } + for (;;) { + unz_file_info file_info; + char name[256]; + r = unzGetCurrentFileInfo(zip, &file_info, name, sizeof(name), NULL, 0, NULL, 0); + if (r != UNZ_OK) { + fprintf(stderr, "zip file info\n"); + exit(1); + } + r = unzOpenCurrentFile(zip); + if (r != UNZ_OK) { + fprintf(stderr, "zip file open\n"); + exit(1); + } + std::vector bytecode; + for (;;) { + char buf[4096]; + int n = unzReadCurrentFile(zip, buf, sizeof(buf)); + if (n == 0) { + break; + } else if (n < 0) { + fprintf(stderr, "zip read\n"); + exit(1); + } + for (auto i = 0; i < n; i++) { + bytecode.push_back(buf[i]); + } + } + unzCloseCurrentFile(zip); + g_Contents[name] = bytecode; + r = unzGoToNextFile(zip); + if (r != UNZ_OK) { + break; + } + } + unzClose(zip); + + ZipSupport zip_support; + + exec(name, g_Contents[".neonx"], nullptr, &zip_support, enable_assert, debug_port, argc, argv); +} diff --git a/src/bundle.h b/src/bundle.h new file mode 100644 index 0000000000..a43e54538b --- /dev/null +++ b/src/bundle.h @@ -0,0 +1,8 @@ +#ifndef BUNDLE_H +#define BUNDLE_H + +#include + +void run_from_bundle(const std::string &name, bool enable_assert, unsigned short debug_port, int argc, char *argv[]); + +#endif diff --git a/src/bytecode.cpp b/src/bytecode.cpp index ae3229342f..dbc1cc9f24 100644 --- a/src/bytecode.cpp +++ b/src/bytecode.cpp @@ -1,17 +1,129 @@ #include "bytecode.h" +#include + +Bytecode::Bytecode() + : obj(), + source_hash(), + global_size(0), + strtable(), + types(), + constants(), + variables(), + functions(), + exception_exports(), + imports(), + exceptions(), + code() +{ +} + Bytecode::Bytecode(const std::vector &obj) - : global_size(0), + : obj(obj), + source_hash(), + global_size(0), strtable(), + types(), + constants(), + variables(), + functions(), + exception_exports(), + imports(), exceptions(), code() { size_t i = 0; + + assert(obj.size() > 32); + source_hash = std::string(&obj[i], &obj[i]+32); + i += 32; + global_size = (obj[i] << 8 | obj[i+1]); i += 2; - unsigned int strtablesize = (obj[i] << 8) | obj[i+1]; + + unsigned int strtablesize = (obj[i] << 24) | (obj[i+1] << 16) | (obj[i+2] << 8) | obj[i+3]; + i += 4; + strtable = getstrtable(&obj[i], &obj[i] + strtablesize); + i += strtablesize; + + unsigned int typesize = (obj[i] << 8) | obj[i+1]; + i += 2; + while (typesize > 0) { + Type t; + t.name = (obj[i] << 8) | obj[i+1]; + i += 2; + t.descriptor = (obj[i] << 8) | obj[i+1]; + i += 2; + types.push_back(t); + typesize--; + } + + unsigned int constantsize = (obj[i] << 8) | obj[i+1]; + i += 2; + while (constantsize > 0) { + Constant c; + c.name = (obj[i] << 8) | obj[i+1]; + i += 2; + c.type = (obj[i] << 8) | obj[i+1]; + i += 2; + unsigned int size = (obj[i] << 8) | obj[i+1]; + i += 2; + c.value = Bytes(&obj[i], &obj[i+size]); + i += size; + constants.push_back(c); + constantsize--; + } + + unsigned int variablesize = (obj[i] << 8) | obj[i+1]; + i += 2; + while (variablesize > 0) { + Variable v; + v.name = (obj[i] << 8) | obj[i+1]; + i += 2; + v.type = (obj[i] << 8) | obj[i+1]; + i += 2; + v.index = (obj[i] << 8) | obj[i+1]; + i += 2; + variables.push_back(v); + variablesize--; + } + + unsigned int functionsize = (obj[i] << 8) | obj[i+1]; + i += 2; + while (functionsize > 0) { + Function f; + f.name = (obj[i] << 8) | obj[i+1]; + i += 2; + f.descriptor = (obj[i] << 8) | obj[i+1]; + i += 2; + f.entry = (obj[i] << 8) | obj[i+1]; + i += 2; + functions.push_back(f); + functionsize--; + } + + unsigned int exceptionexportsize = (obj[i] << 8) | obj[i+1]; i += 2; - strtable = getstrtable(obj.begin() + 4, obj.begin() + 4 + strtablesize, i); + while (exceptionexportsize > 0) { + ExceptionExport e; + e.name = (obj[i] << 8) | obj[i+1]; + i += 2; + exception_exports.push_back(e); + exceptionexportsize--; + } + + unsigned int importsize = (obj[i] << 8) | obj[i+1]; + i += 2; + while (importsize > 0) { + std::pair imp; + imp.first = (obj[i] << 8) | obj[i+1]; + i += 2; + imp.second = std::string(&obj[i], &obj[i]+32); + i += 32; + imports.push_back(imp); + importsize--; + } + unsigned int exceptionsize = (obj[i] << 8) | obj[i+1]; i += 2; while (exceptionsize > 0) { @@ -27,22 +139,122 @@ Bytecode::Bytecode(const std::vector &obj) exceptions.push_back(e); exceptionsize--; } - code = bytecode(obj.begin() + i, obj.end()); + + code = Bytes(obj.begin() + i, obj.end()); } -std::vector Bytecode::getstrtable(bytecode::const_iterator start, bytecode::const_iterator end, size_t &i) +std::vector Bytecode::getstrtable(const unsigned char *start, const unsigned char *end) { std::vector r; while (start != end) { - std::string s; - while (*start != 0) { - s.push_back(*start); - ++start; - i++; - } - r.push_back(s); - ++start; - i++; + size_t len = (start[0] << 24) | (start[1] << 16) | (start[2] << 8) | start[3]; + start += 4; + r.push_back(std::string(reinterpret_cast(start), len)); + start += len; } return r; } + +Bytecode::Bytes Bytecode::getBytes() const +{ + std::vector obj; + + assert(source_hash.length() == 32); + for (int i = 0; i < 32; i++) { + obj.push_back(source_hash[i]); + } + + obj.push_back(static_cast(global_size >> 8) & 0xff); + obj.push_back(static_cast(global_size & 0xff)); + + std::vector t; + for (auto s: strtable) { + t.push_back(static_cast(s.length() >> 24) & 0xff); + t.push_back(static_cast(s.length() >> 16) & 0xff); + t.push_back(static_cast(s.length() >> 8) & 0xff); + t.push_back(static_cast(s.length() & 0xff)); + std::copy(s.begin(), s.end(), std::back_inserter(t)); + } + obj.push_back(static_cast(t.size() >> 24) & 0xff); + obj.push_back(static_cast(t.size() >> 16) & 0xff); + obj.push_back(static_cast(t.size() >> 8) & 0xff); + obj.push_back(static_cast(t.size() & 0xff)); + std::copy(t.begin(), t.end(), std::back_inserter(obj)); + + obj.push_back(static_cast(types.size() >> 8) & 0xff); + obj.push_back(static_cast(types.size() & 0xff)); + for (auto t: types) { + obj.push_back(static_cast(t.name >> 8) & 0xff); + obj.push_back(static_cast(t.name & 0xff)); + obj.push_back(static_cast(t.descriptor >> 8) & 0xff); + obj.push_back(static_cast(t.descriptor & 0xff)); + } + + obj.push_back(static_cast(constants.size() >> 8) & 0xff); + obj.push_back(static_cast(constants.size() & 0xff)); + for (auto c: constants) { + obj.push_back(static_cast(c.name >> 8) & 0xff); + obj.push_back(static_cast(c.name & 0xff)); + obj.push_back(static_cast(c.type >> 8) & 0xff); + obj.push_back(static_cast(c.type & 0xff)); + obj.push_back(static_cast(c.value.size() >> 8) & 0xff); + obj.push_back(static_cast(c.value.size() & 0xff)); + std::copy(c.value.begin(), c.value.end(), std::back_inserter(obj)); + } + + obj.push_back(static_cast(variables.size() >> 8) & 0xff); + obj.push_back(static_cast(variables.size() & 0xff)); + for (auto v: variables) { + obj.push_back(static_cast(v.name >> 8) & 0xff); + obj.push_back(static_cast(v.name & 0xff)); + obj.push_back(static_cast(v.type >> 8) & 0xff); + obj.push_back(static_cast(v.type & 0xff)); + obj.push_back(static_cast(v.index >> 8) & 0xff); + obj.push_back(static_cast(v.index & 0xff)); + } + + obj.push_back(static_cast(functions.size() >> 8) & 0xff); + obj.push_back(static_cast(functions.size() & 0xff)); + for (auto f: functions) { + obj.push_back(static_cast(f.name >> 8) & 0xff); + obj.push_back(static_cast(f.name & 0xff)); + obj.push_back(static_cast(f.descriptor >> 8) & 0xff); + obj.push_back(static_cast(f.descriptor & 0xff)); + obj.push_back(static_cast(f.entry >> 8) & 0xff); + obj.push_back(static_cast(f.entry & 0xff)); + } + + obj.push_back(static_cast(exception_exports.size() >> 8) & 0xff); + obj.push_back(static_cast(exception_exports.size() & 0xff)); + for (auto e: exception_exports) { + obj.push_back(static_cast(e.name >> 8) & 0xff); + obj.push_back(static_cast(e.name & 0xff)); + } + + obj.push_back(static_cast(imports.size() >> 8) & 0xff); + obj.push_back(static_cast(imports.size() & 0xff)); + for (auto i: imports) { + obj.push_back(static_cast(i.first >> 8) & 0xff); + obj.push_back(static_cast(i.first & 0xff)); + assert(i.second.length() == 32); + for (int j = 0; j < 32; j++) { + obj.push_back(i.second[j]); + } + } + + obj.push_back(static_cast(exceptions.size() >> 8) & 0xff); + obj.push_back(static_cast(exceptions.size() & 0xff)); + for (auto e: exceptions) { + obj.push_back(static_cast(e.start >> 8) & 0xff); + obj.push_back(static_cast(e.start & 0xff)); + obj.push_back(static_cast(e.end >> 8) & 0xff); + obj.push_back(static_cast(e.end & 0xff)); + obj.push_back(static_cast(e.excid >> 8) & 0xff); + obj.push_back(static_cast(e.excid & 0xff)); + obj.push_back(static_cast(e.handler >> 8) & 0xff); + obj.push_back(static_cast(e.handler & 0xff)); + } + + std::copy(code.begin(), code.end(), std::back_inserter(obj)); + return obj; +} diff --git a/src/bytecode.h b/src/bytecode.h index 0926abe8ff..47488972bf 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -6,25 +6,81 @@ class Bytecode { public: - Bytecode(const std::vector &obj); + Bytecode(); + explicit Bytecode(const std::vector &obj); - typedef std::vector bytecode; + typedef std::vector Bytes; - class ExceptionInfo { - public: + /* + * Type descriptors are one of the following patterns: + * + * B - boolean + * N - number + * S - string + * Y - bytes + * A - array + * D - dictionary + * R[xyz..] - record, fields x, y, z + * P - pointer + * E[id1,id2,...] - enum + */ + + struct Type { + Type(): name(0), descriptor(0) {} + unsigned int name; + unsigned int descriptor; + }; + + struct Constant { + Constant(): name(0), type(0), value() {} + unsigned int name; + unsigned int type; + Bytes value; + }; + + struct Variable { + Variable(): name(0), type(0), index(0) {} + unsigned int name; + unsigned int type; + unsigned int index; + }; + + struct Function { + Function(): name(0), descriptor(0), entry(0) {} + unsigned int name; + unsigned int descriptor; + unsigned int entry; + }; + + struct ExceptionExport { + ExceptionExport(): name(0) {} + unsigned int name; + }; + + struct ExceptionInfo { unsigned int start; unsigned int end; unsigned int excid; unsigned int handler; }; + Bytes obj; + std::string source_hash; size_t global_size; std::vector strtable; + std::vector types; + std::vector constants; + std::vector variables; + std::vector functions; + std::vector exception_exports; + std::vector> imports; std::vector exceptions; - bytecode code; + Bytes code; + + Bytes getBytes() const; private: - std::vector getstrtable(bytecode::const_iterator start, bytecode::const_iterator end, size_t &i); + std::vector getstrtable(const unsigned char *start, const unsigned char *end); }; #endif diff --git a/src/cell.cpp b/src/cell.cpp index a2a0f32af5..8b96944836 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,103 +1,113 @@ #include "cell.h" #include +#include Cell::Cell() - : type(cNone), - address_value(NULL), + : gc(), + type(cNone), + address_value(nullptr), boolean_value(false), number_value(), - string_value(), - array_value(), - dictionary_value() + string_ptr(), + array_ptr(), + dictionary_ptr() { } Cell::Cell(const Cell &rhs) - : type(rhs.type), + : gc(), + type(rhs.type), address_value(rhs.address_value), boolean_value(rhs.boolean_value), number_value(rhs.number_value), - string_value(rhs.string_value), - array_value(rhs.array_value), - dictionary_value(rhs.dictionary_value) + string_ptr(rhs.string_ptr), + array_ptr(rhs.array_ptr), + dictionary_ptr(rhs.dictionary_ptr) { } Cell::Cell(Cell *value) - : type(cAddress), + : gc(), + type(cAddress), address_value(value), boolean_value(false), number_value(), - string_value(), - array_value(), - dictionary_value() + string_ptr(), + array_ptr(), + dictionary_ptr() { } Cell::Cell(bool value) - : type(cBoolean), - address_value(NULL), + : gc(), + type(cBoolean), + address_value(nullptr), boolean_value(value), number_value(), - string_value(), - array_value(), - dictionary_value() + string_ptr(), + array_ptr(), + dictionary_ptr() { } Cell::Cell(Number value) - : type(cNumber), - address_value(NULL), + : gc(), + type(cNumber), + address_value(nullptr), boolean_value(false), number_value(value), - string_value(), - array_value(), - dictionary_value() + string_ptr(), + array_ptr(), + dictionary_ptr() { } -Cell::Cell(const std::string &value) - : type(cString), - address_value(NULL), +Cell::Cell(const utf8string &value) + : gc(), + type(cString), + address_value(nullptr), boolean_value(false), number_value(), - string_value(value), - array_value(), - dictionary_value() + string_ptr(std::make_shared(value)), + array_ptr(), + dictionary_ptr() { } Cell::Cell(const char *value) - : type(cString), - address_value(NULL), + : gc(), + type(cString), + address_value(nullptr), boolean_value(false), number_value(), - string_value(value), - array_value(), - dictionary_value() + string_ptr(std::make_shared(value)), + array_ptr(), + dictionary_ptr() { } -Cell::Cell(const std::vector &value) - : type(cArray), - address_value(NULL), +Cell::Cell(const std::vector &value, bool alloced) + : gc(alloced), + type(cArray), + address_value(nullptr), boolean_value(false), number_value(), - string_value(), - array_value(value), - dictionary_value() + string_ptr(), + array_ptr(std::make_shared>(value)), + dictionary_ptr() { } -Cell::Cell(const std::map &value) - : type(cDictionary), - address_value(NULL), +Cell::Cell(const std::map &value) + : gc(), + type(cDictionary), + address_value(nullptr), boolean_value(false), number_value(), - string_value(), - array_value(), - dictionary_value(value) + string_ptr(), + array_ptr(), + dictionary_ptr(std::make_shared>(value)) { } @@ -110,9 +120,9 @@ Cell &Cell::operator=(const Cell &rhs) address_value = rhs.address_value; boolean_value = rhs.boolean_value; number_value = rhs.number_value; - string_value = rhs.string_value; - array_value = rhs.array_value; - dictionary_value = rhs.dictionary_value; + string_ptr = rhs.string_ptr; + array_ptr = rhs.array_ptr; + dictionary_ptr = rhs.dictionary_ptr; return *this; } @@ -124,14 +134,14 @@ bool Cell::operator==(const Cell &rhs) const case cAddress: return address_value == rhs.address_value; case cBoolean: return boolean_value == rhs.boolean_value; case cNumber: return number_is_equal(number_value, rhs.number_value); - case cString: return string_value == rhs.string_value; - case cArray: return array_value == rhs.array_value; - case cDictionary: return dictionary_value == rhs.dictionary_value; + case cString: return *string_ptr == *rhs.string_ptr; + case cArray: return *array_ptr == *rhs.array_ptr; + case cDictionary: return *dictionary_ptr == *rhs.dictionary_ptr; } return false; } -Cell *Cell::address() +Cell *&Cell::address() { if (type == cNone) { type = cAddress; @@ -158,29 +168,140 @@ Number &Cell::number() return number_value; } -std::string &Cell::string() +const utf8string &Cell::string() { if (type == cNone) { type = cString; } assert(type == cString); - return string_value; + if (not string_ptr) { + string_ptr = std::make_shared(); + } + return *string_ptr; } -std::vector &Cell::array() +utf8string &Cell::string_for_write() +{ + if (type == cNone) { + type = cString; + } + assert(type == cString); + if (not string_ptr) { + string_ptr = std::make_shared(); + } + if (not string_ptr.unique()) { + string_ptr = std::make_shared(*string_ptr); + } + return *string_ptr; +} + +const std::vector &Cell::array() { if (type == cNone) { type = cArray; } assert(type == cArray); - return array_value; + if (not array_ptr) { + array_ptr = std::make_shared>(); + } + return *array_ptr; } -std::map &Cell::dictionary() +std::vector &Cell::array_for_write() +{ + if (type == cNone) { + type = cArray; + } + assert(type == cArray); + if (not array_ptr) { + array_ptr = std::make_shared>(); + } + if (not array_ptr.unique()) { + array_ptr = std::make_shared>(*array_ptr); + } + return *array_ptr; +} + +Cell &Cell::array_index_for_read(size_t i) +{ + if (type == cNone) { + type = cArray; + } + assert(type == cArray); + if (not array_ptr) { + array_ptr = std::make_shared>(); + } + return array_ptr->at(i); +} + +Cell &Cell::array_index_for_write(size_t i) +{ + if (type == cNone) { + type = cArray; + } + assert(type == cArray); + if (not array_ptr) { + array_ptr = std::make_shared>(); + } + if (not array_ptr.unique()) { + array_ptr = std::make_shared>(*array_ptr); + } + if (i >= array_ptr->size()) { + array_ptr->resize(i+1); + } + return array_ptr->at(i); +} + +const std::map &Cell::dictionary() +{ + if (type == cNone) { + type = cDictionary; + } + assert(type == cDictionary); + if (not dictionary_ptr) { + dictionary_ptr = std::make_shared>(); + } + return *dictionary_ptr; +} + +std::map &Cell::dictionary_for_write() +{ + if (type == cNone) { + type = cDictionary; + } + assert(type == cDictionary); + if (not dictionary_ptr) { + dictionary_ptr = std::make_shared>(); + } + if (not dictionary_ptr.unique()) { + dictionary_ptr = std::make_shared>(*dictionary_ptr); + } + return *dictionary_ptr; +} + +Cell &Cell::dictionary_index_for_read(const utf8string &index) +{ + if (type == cNone) { + type = cDictionary; + } + assert(type == cDictionary); + if (not dictionary_ptr) { + dictionary_ptr = std::make_shared>(); + } + return dictionary_ptr->at(index); +} + +Cell &Cell::dictionary_index_for_write(const utf8string &index) { if (type == cNone) { type = cDictionary; } assert(type == cDictionary); - return dictionary_value; + if (not dictionary_ptr) { + dictionary_ptr = std::make_shared>(); + } + if (not dictionary_ptr.unique()) { + dictionary_ptr = std::make_shared>(*dictionary_ptr); + } + return dictionary_ptr->operator[](index); } diff --git a/src/cell.h b/src/cell.h index cc4e79176b..770d14c124 100644 --- a/src/cell.h +++ b/src/cell.h @@ -2,9 +2,11 @@ #define CELL_H #include +#include #include #include "number.h" +#include "utf8string.h" class Cell { public: @@ -13,22 +15,14 @@ class Cell { explicit Cell(Cell *value); explicit Cell(bool value); explicit Cell(Number value); - explicit Cell(const std::string &value); + explicit Cell(const utf8string &value); explicit Cell(const char *value); - explicit Cell(const std::vector &value); - explicit Cell(const std::map &value); + explicit Cell(const std::vector &value, bool alloced = false); + explicit Cell(const std::map &value); Cell &operator=(const Cell &rhs); bool operator==(const Cell &rhs) const; - Cell *address(); - bool &boolean(); - Number &number(); - std::string &string(); - std::vector &array(); - std::map &dictionary(); - -private: - enum { + enum Type { cNone, cAddress, cBoolean, @@ -36,14 +30,40 @@ class Cell { cString, cArray, cDictionary - } type; + }; + Type get_type() const { return type; } + Cell *&address(); + bool &boolean(); + Number &number(); + const utf8string &string(); + utf8string &string_for_write(); + const std::vector &array(); + std::vector &array_for_write(); + Cell &array_index_for_read(size_t i); + Cell &array_index_for_write(size_t i); + const std::map &dictionary(); + std::map &dictionary_for_write(); + Cell &dictionary_index_for_read(const utf8string &index); + Cell &dictionary_index_for_write(const utf8string &index); + + struct GC { + GC(bool alloced = false): alloced(alloced), marked(false) {} + const bool alloced; + bool marked; + private: + GC(const GC &); + GC &operator=(const GC &); + } gc; + +private: + Type type; Cell *address_value; bool boolean_value; Number number_value; - std::string string_value; - std::vector array_value; - std::map dictionary_value; + std::shared_ptr string_ptr; + std::shared_ptr> array_ptr; + std::shared_ptr> dictionary_ptr; }; #endif diff --git a/src/compiler.cpp b/src/compiler.cpp index 50e9dc336e..29d02ae556 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -2,11 +2,13 @@ #include #include +#include #include #include #include #include "ast.h" +#include "bytecode.h" #include "debuginfo.h" #include "opcode.h" @@ -16,6 +18,13 @@ class Emitter { Label(): fixups(), target(UINT_MAX) {} std::vector fixups; unsigned int target; + public: + unsigned int get_target() { + if (target == UINT_MAX) { + internal_error("Label::get_target"); + } + return target; + } }; class LoopLabels { public: @@ -25,14 +34,7 @@ class Emitter { Label *next; }; public: - struct ExceptionInfo { - unsigned int start; - unsigned int end; - unsigned int excid; - unsigned int handler; - }; -public: - Emitter(DebugInfo *debug): code(), strings(), exceptions(), globals(), functions(), function_exit(), loop_labels(), debug_info(debug) {} + Emitter(const std::string &source_hash, DebugInfo *debug): source_hash(source_hash), object(), globals(), functions(), function_exit(), loop_labels(), exported_types(), debug_info(debug) {} void emit(unsigned char b); void emit_uint32(uint32_t value); void emit(unsigned char b, uint32_t value); @@ -53,18 +55,26 @@ class Emitter { Label &get_exit_label(unsigned int loop_id); Label &get_next_label(unsigned int loop_id); void debug_line(int line); - void add_exception(const ExceptionInfo &ei); + void add_exception(const Bytecode::ExceptionInfo &ei); void push_function_exit(Label &label); void pop_function_exit(); Label &get_function_exit(); + void declare_export_type(const Type *type); + void add_export_type(const std::string &name, const std::string &descriptor); + void add_export_constant(const std::string &name, const std::string &type, const std::string &value); + void add_export_variable(const std::string &name, const std::string &type, int index); + void add_export_function(const std::string &name, const std::string &type, int entry); + void add_export_exception(const std::string &name); + void add_import(const std::string &name); + std::string get_type_reference(const Type *type); private: - std::vector code; - std::vector strings; - std::vector exceptions; + const std::string source_hash; + Bytecode object; std::vector globals; std::vector