diff --git a/.github/workflows/execution.yml b/.github/workflows/execution.yml index 127096e..b207d22 100644 --- a/.github/workflows/execution.yml +++ b/.github/workflows/execution.yml @@ -2,30 +2,60 @@ name: Execution on: push: - branches: [ main, dev, staging ] + branches: [ main, staging ] pull_request: - branches: [ main, dev, staging ] + branches: [ main, staging ] workflow_dispatch: inputs: reason: description: Why did you manually run this? jobs: - build: + build-linux: if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - matrix: - node-version: [ 14.x ] + steps: + - uses: actions/checkout@v3 + - name: Setup Ninja + uses: ashutoshvarma/setup-ninja@master + with: + version: 1.10.0 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Cache LLVM and Clang + id: cache-llvm + uses: actions/cache@v3 + with: + path: "./lib/install" + key: llvm-15.0 + - name: "Install LLVM and Clang" + uses: KyleMayes/install-llvm-action@v1 + with: + version: "15.0" + directory: "./lib/install" + cached: ${{ steps.cache-llvm.outputs.cache-hit }} + - name: Symlink libclang.so (Linux) + if: contains(matrix.os, 'ubuntu') + run: sudo ln -s libclang-15.so.1 /lib/x86_64-linux-gnu/libclang.so + working-directory: ${{ env.LLVM_PATH }}/lib + - run: npm install --ignore-scripts + - run: cmake . -B build -G Ninja + - run: cmake --build build + - run: npm run build:syntax + - run: node ./compiler/test.js --action + + build-win: + if: ${{ false }} # disable for now + runs-on: windows-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run build --if-present - - run: npm run test \ No newline at end of file + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - run: npm install + - run: node ./compiler/test.js --action diff --git a/.gitignore b/.gitignore index 296c97b..89c4c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* +# CMake +CMakeFiles/* + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json @@ -23,15 +26,21 @@ package-lock.json # Build artifacts install/ build/ +.env # Ignore uniview files *.uv -# Ignore LLVM outputs +# Ignore Compilation outputs outputs *.ll +*.bc *.asm +*.s *.bin *.exe *.app -*.out \ No newline at end of file +*.out +*.o +*.lib +*.dll \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3fb44c8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "lib/llvm"] + path = lib/llvm + url = https://github.com/llvm/llvm-project + ignore = dirty + branch = release/15.x + depth = 1 diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 0000000..d7af40f --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1 @@ +c_cpp_properties.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fe07467 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "revng.llvm-ir", + "streetsidesoftware.code-spell-checker", + "qupa-project.uniview" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b0a1aa..5f0e347 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,42 +2,131 @@ "files.associations": { "*.qp": "qupa", "*.effect": "hlsl", + "*.frm": "vb", + "*.inc": "asp", "*.uv": "uniview", "xstring": "cpp", + "algorithm": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "barrier": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", "cstddef": "cpp", "cstdint": "cpp", "cstdio": "cpp", "cstdlib": "cpp", "cstring": "cpp", "ctime": "cpp", + "cuchar": "cpp", "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", "exception": "cpp", + "execution": "cpp", + "filesystem": "cpp", + "format": "cpp", + "forward_list": "cpp", + "fstream": "cpp", + "functional": "cpp", + "future": "cpp", "initializer_list": "cpp", + "iomanip": "cpp", "ios": "cpp", "iosfwd": "cpp", "iostream": "cpp", "istream": "cpp", + "iterator": "cpp", + "latch": "cpp", "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", "memory": "cpp", + "mutex": "cpp", "new": "cpp", + "numbers": "cpp", + "numeric": "cpp", + "optional": "cpp", "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ranges": "cpp", + "ratio": "cpp", + "regex": "cpp", + "scoped_allocator": "cpp", + "semaphore": "cpp", + "set": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stack": "cpp", "stdexcept": "cpp", + "stop_token": "cpp", "streambuf": "cpp", + "string": "cpp", + "strstream": "cpp", "system_error": "cpp", + "thread": "cpp", "tuple": "cpp", "type_traits": "cpp", + "typeindex": "cpp", "typeinfo": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", "utility": "cpp", + "valarray": "cpp", + "variant": "cpp", + "vector": "cpp", "xfacet": "cpp", + "xhash": "cpp", "xiosbase": "cpp", "xlocale": "cpp", + "xlocbuf": "cpp", "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", "xlocnum": "cpp", + "xloctime": "cpp", "xmemory": "cpp", "xstddef": "cpp", "xtr1common": "cpp", - "xutility": "cpp" - } + "xtree": "cpp", + "xutility": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__std_stream": "cpp", + "__threading_support": "cpp", + "__tuple": "cpp", + "__verbose_abort": "cpp", + "propagate_const": "cpp", + "string_view": "cpp", + "*.ipp": "cpp" + }, + "editor.insertSpaces": false, + "files.eol": "\n" } \ No newline at end of file diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..75deb60 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,39 @@ +# Building Uniview from source + +## Requirements +* CMake >= 3.11 +* git *used by cmake to get required submodules* +* A modern C/++ compiler, preferably Clang 12 or newer + +First of all you need to build the LLVM source components that will be used by Uniview + +## Dependency Setup +While in the root directory of the repository run either of these commands. +This will run a serise of CMake scripts which will: +1. Pull the require submodules +2. Set the require LLVM build parameters +3. Configure the LLVM build +4. Build the LLVM library +5. Install the library to the repository + +### Windows +```bash +lib/install.bat +``` + +### Ubuntu +```bash +lib/install.bash +``` + +## Building the Project + +Setup the build environment +```bash +cmake . -B build +``` + +Then to build at any time just run +```bash +cmake --build build +``` \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0f03175 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 3.13.4) + +file(STRINGS "VERSION" UNIVIEW_PROJECT_VERSION) +project(uniview-lang VERSION ${UNIVIEW_PROJECT_VERSION} LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +find_program(CLANG_PATH clang++) +set(CMAKE_CXX_COMPILER ${CLANG_PATH}) +set(CMAKE_MAKE_PROGRAM "Ninja") +set(CMAKE_GENERATOR "Ninja") +set(CMAKE_LINKER lld) + +# System information +message("Setup Uniview ${UNIVIEW_PROJECT_VERSION}") +message("-- CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") +message("-- CMAKE_MAKE_PROGRAM: ${CMAKE_MAKE_PROGRAM}") +message("-- CMAKE_GENERATOR: ${CMAKE_GENERATOR}") +message("-- CMAKE_LINKER: ${CMAKE_LINKER}") +message("-- CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}") +message("-- CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +message("-- CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") +message("-- CMAKE_SYSTEM: ${CMAKE_SYSTEM}") + +set(_compiler_arch ${CMAKE_C_COMPILER_ARCHITECTURE_ID}) +if("${_compiler_arch}" STREQUAL "") + set(_compiler_arch ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +message("Compiler architecture is ${_compiler_arch}") + + +SET(LLVM_DIR "./lib/install/lib/cmake/llvm" CACHE STRING "" FORCE) +find_package(LLVM REQUIRED CONFIG) +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + + +include_directories(${LLVM_INCLUDE_DIRS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) + + +################################################################# +# Find the libraries that correspond to the LLVM components +# that we wish to use +################################################################# +llvm_map_components_to_libnames( llvm_libs + # Demangle + Analysis + BitReader + BitWriter + CodeGen + Core + Coroutines + ExecutionEngine + Interpreter + IPO + irReader + # JITLink + Linker + MCJIT + Object + Passes + Support + Target + TransformUtils + + # PLATFORM SPECIFIC + armasmparser + armcodegen + armdesc + arminfo + riscvasmparser + riscvcodegen + riscvdesc + riscvinfo + # webassemblyasmparser + # webassemblycodegen + # webassemblydesc + # webassemblyinfo + x86asmparser + x86codegen + x86desc + x86info +) +set(LLVM_TARGETS_TO_BUILD "ARM;RISCV;X86;") + + + +# set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -flto) +set(CMAKE_C_COMPILER clang) +SET( TAR ) + + +SET( APP_EXE uvc-tools ) +message("Configuring ${APP_EXE}") +add_executable( ${APP_EXE} "src/uvc-tool/main.cpp" + "src/uvc-tool/verbose.cpp" +) +TARGET_LINK_LIBRARIES( ${APP_EXE} "${llvm_libs}" ) +add_custom_command(TARGET ${APP_EXE} POST_BUILD + COMMAND node "${PROJECT_SOURCE_DIR}/tools/env-bind.js" "--uvc_tool=$" +) + + +# SET( LIB uvc-core ) +# message("Configuring ${LIB}") +# ADD_LIBRARY( ${LIB} STATIC +# "src/core/main.c" +# ) +# TARGET_LINK_LIBRARIES( ${LIB} "${llvm_libs}" ) +# add_custom_command(TARGET ${LIB} POST_BUILD +# COMMAND node "${PROJECT_SOURCE_DIR}/tools/env-bind.js" "--uvc_lib=$" +# ) + + +# Add the LLVM library directories +# link_directories(${LLVM_LIBRARY_DIRS}) + + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/changelog.md b/changelog.md index 972bdf4..71f0e6a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,32 @@ # Change Log +## Upcoming + +## Additions + - [x] `printf` function + - [x] `either` types + - [x] `trait` declaration + - [x] `impl` statement for functions tied to a type, based on required behaviour + - [x] `struct {key: value}` syntax now available for creating structures + - [x] Intrinsic `Drop` trait which automatically gets executed on structs with this type when the value falls out of scope + - [x] Intrinsic `Clone` trait which automatically gets called when cloning a struct with the Clone trait implemented + - [x] No longer able to define a variable with the name of a reserved word + - [x] More detailed compilation time metrics with ``--profile`` flag + - [] A value can be lent to a new variable within the same scope + - [] A lent value can be returned only if the lent value is from a non locally defined value + - [] A value can have multiple immutable lends + +## Fixes + - [x] Lent normal values are now treated as non-lent values within a function, however, the final values are stored back in the original location upon function cleanup + +## Changes + - [x] Comments are now able to be used anywhere whitespace can + - [x] Cloning now done via the `Clone()` function instead of via the `$` operator. + +## Tweaks + - [x] Now uses the LLVM tool chain (instead of clang) to generated the platform specific assembly, then clang does the final mile to make the executable binary. + - [x] String constants are now stored globally as constants in the application instead of being allocated each time. + ## Version 0.1.0 ## Added diff --git a/compiler/compile.js b/compiler/compile.js index 5aa6974..fd668f4 100644 --- a/compiler/compile.js +++ b/compiler/compile.js @@ -1,167 +1,240 @@ #!/usr/bin/env node "use strict"; -const Project = require('./component/project.js'); - +const { spawn } = require('child_process'); const path = require('path'); -const os = require('os'); const fs = require('fs'); -const { exec, spawn, spawnSync } = require('child_process'); +const os = require('os'); -const version = "Uniview Compiler v0.1.0 Alpha"; -const root = path.resolve("./"); +require('dotenv').config(); +const Getopt = require('node-getopt'); +const Project = require('./component/project.js'); +const version = "Uniview Compiler v0.1.0 Alpha"; +const root = path.resolve("./"); /*------------------------------------------ Compiler configuration flags ------------------------------------------*/ -if (process.argv.includes("--version")) { +const validModes = ["execute", "verify", "preprocess", "uvir", "llir"]; +let getopt = new Getopt([ + ['m', 'mode=ARG', `compilation mode (${validModes.join("|")})`], + ['', 'opt=ARG', 'optimisation level'], + ['o', 'output=ARG', 'output name'], + ['', 'profile', 'Enable profile timings'], + ['', 'version', 'show version'], + ['', 'verbose', 'verbose logs'] +]).bindHelp(); +let opt = getopt.parse(process.argv.slice(2)); + +if (opt.options.version) { console.info(version); process.exit(0); } -let config = { - output: "out", - source: false, - execute: true, - compileOnly: false, - verifyOnly: false, - optimisation: "0", -}; -let index = process.argv.indexOf('-o'); -if (index != -1 && index > 2) { - config.output = process.argv[index+1] || "out"; -} -if (process.argv.includes('--execute')) { - config.execute = true; -} -if (process.argv.includes('--verifyOnly')) { - config.verifyOnly = true; - config.execute = false; -} -if (process.argv.includes('--compileOnly')) { - config.compileOnly = true; - config.execute = false; +if (opt.options.opt) { + console.warn("Warn: Compilation does not currently support optimisation"); } -index = process.argv.indexOf('-s'); -if (index != -1) { - config.source = process.argv[index+1] || "asm"; + +if (!opt.options.mode) { + opt.options.mode = "execute"; +} else if (!validModes.includes(opt.options.mode)) { + console.error(`Invalid compilation mode "${opt.options.mode}"`); + process.exit(1); } -index = process.argv.indexOf('-opt'); -if (index != -1) { - config.optimisation = String( - Math.min(3, Number(process.argv[index+1]) || 0) - ); + +if (!opt.options.output) { + opt.options.output = "out"; } -if (config.execute + config.verifyOnly + config.compileOnly > 1) { - console.error("Invalid arguments"); + +if (opt.argv.length > 1) { + console.error("Cannot take multiple uv starting points"); process.exit(1); } +let Timers = require('./timers.js'); +if (opt.options.profile) { + Timers.Enable(["read", "link", "compile", "assemble"]) +} + /*------------------------------------------ Compilation to LLVM ------------------------------------------*/ // Load required files -let origin = path.resolve(root, process.argv[2]); +Timers.Checkpoint("read", true); +let origin = path.resolve(root, opt.argv[0]); let project = new Project(root, { - caching: config.caching + caching: false }); project.import(origin, true); +Timers.Checkpoint("read", false); + // Link elements console.info("Linking..."); +Timers.Checkpoint("link", true); project.link(); if (project.error) { console.error("\nLinker error"); process.exit(1); } +Timers.Checkpoint("link", false); + // Compile to LLVM console.info("Processing..."); +Timers.Checkpoint("compile", true); project.compile(); if (project.error) { console.error("\nUncompilable errors"); process.exit(1); } let asm = project.toLLVM(); +Timers.Checkpoint("compile", false); -if (config.verifyOnly) { +if (opt.options.mode == "preprocess") { + console.info("Passed"); process.exit(0); } -fs.writeFileSync(`${config.output}.ll`, asm.flattern(), 'utf8'); - - +fs.writeFileSync(`${opt.options.output}.ll`, asm.flattern(), 'utf8'); +if (opt.options.mode == "uvir") { + process.exit(0); +} /*------------------------------------------ Compilation in Clang ------------------------------------------*/ console.info("Compiling..."); -if (config.execute && config.source !== false) { - console.warn("Warn: Compilation flaged as executing result, but result is configured to output a non-executable"); - config.execute = false; + +let needsLinking = project.includes + .filter(x => ["object", "static"].includes(x.type)).length > 0; + +let tool_mode = "execute"; +switch (opt.options.mode) { + case "execute": + tool_mode = "run"; + break; + case "compile": + needsLinking = true; + tool_mode = "object"; + break; + case "verify": + tool_mode = "verify"; + break; + case "llir": + tool_mode = "ir"; + break; + default: + console.error(`Invalid option mode ${opt.options.mode} for compilation tools`); + console.error(`This error shouldn't occur`); + process.exit(1); +} + + +let args = [ + `${opt.options.output}.ll`, + "--mode", needsLinking ? "o" : tool_mode, + "--output", opt.options.output +].concat(project.includes + .filter(x => x.type=="llvm") + .map(x => x.path) +); + +if (opt.options.verbose) { + args.push("--verbose"); } -if (config.source != "llvm") { - let args = project.includes - .concat([ - ["-Wno-override-module"], - ["--language=ir", `${config.output}.ll`], - [`-O${config.optimisation}`] - ]) - .reduce((prev, curr) => prev.concat(curr), []); - - let exec_out = "./" + config.output; - if (config.source == "asm") { - args.push('-S'); - exec_out += ".s"; - } else if (os.platform() == "win32") { - exec_out += ".exe"; - } else if (os.platform() == "darwin") { - exec_out += ".app"; - } else { - exec_out += ".out"; + +let tool_path = process.env.uvc_tool; +if (!fs.existsSync(tool_path)) { + console.error(`Cannot find tool: ${tool_path}`); + process.exit(1); +} + + +console.info(`\n${tool_path} ${args.join(" ")}\n`); +Timers.Checkpoint("assemble", true); +let tool = spawn(tool_path, args, { + cwd: project.rootPath +}); + +tool.stdout.pipe(process.stdout); +tool.stderr.pipe(process.stderr); + +tool.on('close', (code) => { + Timers.Checkpoint("assemble", false); + + if (needsLinking) { + Link(); + return; } - args = args.concat(["-o", exec_out]); - console.info(`\nclang++ ${args.join(" ")}`); - let clang = spawnSync('clang++', args, { + console.info(`\nStatus Code: ${code}`); + Timers.Print(); + process.exit(code); +}); + + + +function Link() { + Timers.Checkpoint("linking", true); + + let targets = project.includes + .filter(x => x.type!="llvm") + .map(x => x.path); + + console.info(`\nlld-link ${opt.options.output}.o ${targets.join(" ")}\n`); + let linker = spawn("lld-link", [ + opt.options.output+".o", + ...targets, + `/OUT:${opt.options.output}.exe` + ], { cwd: project.rootPath }); - if (clang.status === 0){ - process.stdout.write(clang.output[2]); - - if (config.execute) { - console.info('\nRunning...'); - let app = spawn(exec_out); - process.stdin.pipe (process.stdin); - app.stderr.pipe (process.stderr); - app.stdout.pipe (process.stdout); - - app.on('close', (code) => { - if (code === null) { - console.error(app.signalCode); - process.exit(1); - } - - process.exit(code); - }); - } else { - process.exit(0); + linker.stdout.pipe(process.stdout); + linker.stderr.pipe(process.stderr); + + linker.on('close', (code) => { + Timers.Checkpoint("linking", false); + + if (opt.options.mode == "execute") { + Execution(); + return; } - } else { - console.error("FAILED TO COMPILE"); - process.stderr.write(clang.output[2]); - process.exit(1); - } + console.log(215, opt.options.mode); + + console.info(`\nStatus Code: ${code}`); + Timers.Print(); + process.exit(code); + }); +} + + +function Execution() { + Timers.Checkpoint("execution", true); + console.info(`\n${opt.options.output}.exe\n`); + let exec = spawn(`${opt.options.output}.exe`, [], { + cwd: project.rootPath + }); + + exec.stdout.pipe(process.stdout); + exec.stderr.pipe(process.stderr); + + exec.on('close', (code) => { + Timers.Checkpoint("execution", false); + console.info(`\nStatus Code: ${code}`); + Timers.Print(); + process.exit(code); + }); } \ No newline at end of file diff --git a/compiler/component/class.js b/compiler/component/class.js deleted file mode 100644 index d836271..0000000 --- a/compiler/component/class.js +++ /dev/null @@ -1,221 +0,0 @@ -const Function = require('./function.js'); -const Structure = require('./struct.js'); -const LLVM = require('./../middle/llvm.js'); - -class Class extends Structure { - constructor (ctx, ast, external = false) { - super(ctx, ast, external); - this.names = {}; - this.meta = "CLASS"; - } - - parse () { - this.name = this.ast.tokens[0].tokens; - this.represent = "%class." + ( - this.external ? this.name : `${this.name}.${this.ctx.represent}` - ); - } - - link (stack = []) { - if (stack.indexOf(this) != -1) { - this.ctx.getFile().throw( - `Error: Structure ${this.name} contains itself, either directly or indirectly`, - this.ast.ref.start, - this.ast.ref.end - ); - return; - } - if (this.linked) { - return; - } - - this.size = 0; - for (let node of this.ast.tokens[1].tokens) { - switch (node.type) { - case "comment": - break; - case "struct_attribute": - if (this.linkTerm(node, stack) == false) { - return; - } - break; - case "function": - let space = new Function(this, node, false, false); - - if (!this.names[space.name]) { - this.names[space.name] = space; - } else if ( - !this.names[space.name].merge || - !this.names[space.name].merge(space) - ) { - let first = this.names[space.name].ref.index < space.ref.index ? - this.names[space.name].ref : space.ref; - let second = this.names[space.name].ref.index > space.ref.index ? - this.names[space.name].ref : space.ref; - - this.getFile().throw( - `Multiple definitions of same name ${space.name}`, - first, second - ); - this.project.markError(); - return false; - } - - break; - default: - throw new Error(`Unexpected attribute ${node.type}`); - } - } - - // Link all successful functions - for (let name in this.names) { - this.names[name].link(); - } - - - // Ensure this class has a destructor if any child does - if (!this.getDestructor()) { - let found = false; - let ref = null; - for (let child of this.terms) { - let type = child.typeRef.type; - if (type instanceof Class && type.getDestructor()) { - found = true; - ref = child.declared; - break; - } - } - - if (found) { - this.getFile().throw( - `Error: This class contains no destrutor function, however it's attributes do`, - this.ref, ref - ); - } - } - - // Ensure this class has a clone operation if any child does - let cloner = this.getCloner(); - if (!cloner) { - let found = false; - let ref = null; - for (let child of this.terms) { - let type = child.typeRef.type; - if (type instanceof Class && type.getCloner()) { - found = true; - ref = child.declared; - break; - } - } - - if (found) { - this.getFile().throw( - `Error: This class contains no clone function, however at least one of it's attributes do`, - this.ref, ref - ); - } - } else { - if (!cloner.returnType.match(new TypeRef(0, this, false))) { - this.getFile().throw( - `Error: Cloning functions must return a "${this.name}" value`, - this.ref, cloner.ref - ); - } - } - - this.linked = true; - } - - getFunction (access, signature, template) { - let name; - if (access.length == 0) { - name = "New"; - } else if (access.length != 1) { - return null; - } else { - name = access[0][1]; - } - - if (this.names[name]) { - return this.names[name].getFunction([], signature, template); - } - - return null; - } - - getDestructor () { - if (!this.names['Delete']) { - return false; - } - - return this.names['Delete'].getFunction([], [new TypeRef(0, this, false)], null); - } - getCloner () { - if (!this.names['Clone']) { - return false; - } - - return this.names['Clone'].getFunction([], [new TypeRef(0, this, true)], null); - } - - cloneInstance (argument, ref) { - let cloner = this.getCloner(); - if (cloner) { - let preamble = new LLVM.Fragment(); - let irType = new TypeRef(0, this, false); - let id = new LLVM.ID(); - - preamble.append(new LLVM.Set( - new LLVM.Name(id), - new LLVM.Alloc(irType.toLLVM(ref, true)) - )); - - let instruction = new LLVM.Argument ( - irType.toLLVM(ref, false, true), - new LLVM.Name(id.reference()) - ); - - // Call the clone opperation - preamble.append(new LLVM.Call( - new LLVM.Type("void", 0), - new LLVM.Name(cloner.represent, true, ref), - [ - instruction, - argument - ], ref - )); - - return { - preamble, - instruction - }; - } else { - return super.cloneInstance(argument, ref); - } - } - - - compile () { - super.compile(); - - for (let name in this.names) { - this.names[name].compile(); - } - } - - toLLVM () { - let out = new LLVM.Fragment(); - out.append(super.toLLVM()); - - for (let name in this.names) { - out.append(this.names[name].toLLVM()); - } - - return out; - } -} - - -const TypeRef = require('./typeRef.js'); - -module.exports = Class; \ No newline at end of file diff --git a/compiler/component/execution/base.js b/compiler/component/execution/base.js index 8b48932..4507705 100644 --- a/compiler/component/execution/base.js +++ b/compiler/component/execution/base.js @@ -1,5 +1,10 @@ -const Flattern = require('../../parser/flattern.js'); const LLVM = require("../../middle/llvm.js"); +const TypeRef = require('../typeRef.js'); +const { SyntaxNode } = require('bnf-parser'); + +const Primative = { + types: require('./../../primative/types.js') +}; class ExecutionBase { /** @@ -18,13 +23,6 @@ class ExecutionBase { this.entryPoint = entryPoint.reference(); } - /** - * Return the function this scope is within - * @returns {Function_Instance} - */ - getFunction (access, signature, template) { - return this.getFile().getFunction(access, signature, template); - } getFunctionGroup () { return this.ctx.getFunctionGroup(); @@ -50,46 +48,22 @@ class ExecutionBase { return null; } - /** - * - * @param {BNF_Node} node + * Return the function this scope is within + * @param {SyntaxNode[]} + * @param {TypeRef[]} + * @returns {Function_Instance} */ - resolveTemplate (node) { - let template = []; - for (let arg of node.tokens) { - switch (arg.type) { - case "data_type": - let type = this.getFile().getType( - Flattern.DataTypeList(arg), - this.resolveTemplate(arg.tokens[3]) - ); - if (type === null) { - this.getFile().throw( - `Error: Unknown data type ${Flattern.DataTypeStr(arg)}`, - arg.ref.start, arg.ref.end - ); - return null; - } - - // Update pointer size - type.pointer = arg.tokens[0]; - - template.push(type); - break; - case "constant": - template.push(this.compile_constant(arg)); - break; - default: - this.getFile().throw( - `Error: ${arg.type} are currently unsupported in template arguments`, - arg.ref.start, arg.ref.end - ); - return null; - } + getFunction (access, signature) { + if (!Array.isArray(access)) { + throw new Error(`Unexpected access type`); } - return template; + return this.getFile().getFunction(access, signature); + } + + getType(node) { + return this.ctx.getType(node); } @@ -99,15 +73,14 @@ class ExecutionBase { /** * Get a register - * @param {*} ast + * @param {SyntaxNode} ast * @param {Boolean} read */ getVar (ast, read = true) { let preamble = new LLVM.Fragment(); // Link dynamic access arguments - ast = this.resolveAccess (ast); - let res = this.scope.getVar (ast, read); + let res = this.scope.getVar(ast, read); // Inject reference if it is missing if (res.error) { @@ -115,10 +88,10 @@ class ExecutionBase { return res; } - let accesses = ast.tokens[2]; + let accesses = ast.value.slice(1); for (let access of accesses) { res.hasUpdated = res.hasUpdated || !read; - res = res.access(access[1].tokens, access[1].ref); + res = res.access(access.value, access.ref); if (res.error) { return res; } @@ -161,53 +134,6 @@ class ExecutionBase { - /** - * - * @param {BNF_Node} node - */ - resolveType (node) { - let template = this.resolveTemplate(node.tokens[3]); - if (template === null) { - return null; - } - - let type = this.getFile().getType( - Flattern.DataTypeList(node), - template - ); - - return type; - } - - /** - * Resolves any dynamic access for the variable - * ALTERS original AST - * @param {*} ast - */ - resolveAccess (ast) { - for (let access of ast.tokens[2]) { - if (access[0] == "[]") { - for (let i in access[1]) { - let res = this.compile_expr(access[1][i], null, true); - if (res === null) { - return { - error: true, - msg: `Error: Unexpected dynamic access opperand type ${arg.type}`, - ref: arg.ref - }; - } - - access[1][i] = res; - } - } - } - - return ast; - } - - - - sync (branches, segment, ref){ diff --git a/compiler/component/execution/expr.js b/compiler/component/execution/expr.js index afa888c..6802c35 100644 --- a/compiler/component/execution/expr.js +++ b/compiler/component/execution/expr.js @@ -2,6 +2,8 @@ const LLVM = require("../../middle/llvm.js"); const TypeRef = require('../typeRef.js'); const Structure = require('../struct.js'); +const Flatten = require('../../parser/flatten.js'); + const Primative = { types: require('../../primative/types.js') }; @@ -20,67 +22,53 @@ class ExecutionExpr extends ExecutionBase { let preamble = new LLVM.Fragment(); let type = null; let val = null; - switch (ast.tokens[0].type) { + + switch (ast.value[0].type) { case "float": - type = new TypeRef(0, Primative.types.double); + type = new TypeRef(Primative.types.double); val = new LLVM.Constant( - ast.tokens[0].tokens, + ast.value[0].value, ast.ref.start ); break; case "boolean": - type = new TypeRef(0, Primative.types.bool); + type = new TypeRef(Primative.types.bool); val = new LLVM.Constant( - ast.tokens[0].tokens == "true" ? 1 : 0, + ast.value[0].value == "true" ? 1 : 0, + ast.ref.start + ); + break; + case "void": + type = new TypeRef(Primative.types.void); + val = new LLVM.Constant( + "null", ast.ref.start ); break; case "integer": - type = new TypeRef(0, Primative.types.i64); + type = new TypeRef(Primative.types.i64); val = new LLVM.Constant( - ast.tokens[0].tokens, + ast.value[0].value, ast.ref.start ); break; case "string": - let bytes = ast.tokens[0].tokens[1].length + 1; - let str = ast.tokens[0].tokens[1].replace(/\"/g, "\\22").replace(/\n/g, '\\0A') + "\\00"; - - type = new TypeRef(0, Primative.types.cstring); + let bytes = ast.value[0].value.length + 1; + let str = ast.value[0].value.replace(/\"/g, "\\22").replace(/\n/g, '\\0A') + "\\00"; - let ir_t1 = new LLVM.Type(`[ ${bytes} x i8 ]`, 0, ast.ref); - let ir_t2 = type.toLLVM(); + let global = this.getFunctionInstance() + .bindConst(`private unnamed_addr constant [ ${bytes} x i8 ] c"${str}"`, ast.value.ref); - let str_id = new LLVM.ID(); + type = new TypeRef(Primative.types.cstring, true, true); let ptr_id = new LLVM.ID(); - preamble.append(new LLVM.Set( - new LLVM.Name(str_id, false, ast.ref), - new LLVM.Alloc( - ir_t1, - ast.ref - ), - ast.ref - )); - preamble.append(new LLVM.Store( - new LLVM.Argument( - new LLVM.Type(`[ ${bytes} x i8 ]*`, 0, ast.ref), - new LLVM.Name(str_id.reference(), false, ast.ref), - ast.ref, "#str_const" - ), - new LLVM.Argument( - ir_t1, - new LLVM.Constant(`c"${str}"`, ast.ref), - ast.ref - ) - )); preamble.append(new LLVM.Set( new LLVM.Name(ptr_id, false, ast.ref), new LLVM.Bitcast( - ir_t2, + type.toLLVM(), new LLVM.Argument( new LLVM.Type(`[ ${bytes} x i8 ]*`, 0, ast.ref), - new LLVM.Name(str_id.reference(), false, ast.ref), + global, ast.ref, "#str_const" ), ast.ref @@ -91,7 +79,7 @@ class ExecutionExpr extends ExecutionBase { val = new LLVM.Name(ptr_id, false, ast.ref); break; default: - throw new Error(`Unknown constant type ${ast.tokens[0].type}`); + throw new Error(`Unknown constant type ${ast.value[0].type}`); } return { @@ -113,7 +101,7 @@ class ExecutionExpr extends ExecutionBase { case "constant": return this.compile_constant(ast); case "expr_brackets": - return this.compile_expr(ast.tokens[0], null, true); + return this.compile_expr(ast.value[0], null, true); default: return this.compile_expr(ast, null, true) } @@ -149,8 +137,8 @@ class ExecutionExpr extends ExecutionBase { // Load the two operands ready for operation let opperands = [ - this.compile_expr_opperand(ast.tokens[0]), - this.compile_expr_opperand(ast.tokens[1]) + this.compile_expr_opperand(ast.value[0]), + this.compile_expr_opperand(ast.value[1]) ]; // Catch any errors getting the opperands @@ -175,30 +163,28 @@ class ExecutionExpr extends ExecutionBase { epilog.merge(opperands[0].epilog); epilog.merge(opperands[1].epilog); - - // Check opperands are primatives - if (!opperands[0].type.type.primative) { + if (!opperands[0].type.native) { this.getFile().throw( `Error: Cannot run arithmetic opperation on non-primative type`, - ast.tokens[0].ref.start, ast.tokens[0].ref.end + ast.value[0].ref.start, ast.value[0].ref.end ); return null; } - if (!opperands[1].type.type.primative) { + if (!opperands[1].type.native) { this.getFile().throw( `Error: Cannot run arithmetic opperation on non-primative type`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } // Check opperands are the same type - if (!opperands[0].type.match(opperands[1].type)) { + if (!opperands[0].type.matchApprox(opperands[1].type)) { this.getFile().throw( - `Error: Cannot perform arithmetic opperation on unequal types`, - ast.tokens[0].ref.start, ast.tokens[1].ref.end + `Error: Cannot perform arithmetic opperation on unequal types. ${opperands[0].type} != ${opperands[1].type}`, + ast.value[0].ref.start, ast.value[1].ref.end ); return null; } @@ -214,11 +200,14 @@ class ExecutionExpr extends ExecutionBase { if (mode === null) { this.getFile().throw( `Error: Unable to perform arithmetic opperation for unknown reason`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } + let type = opperands[0].type.duplicate(); + type.lent = false; + return { preamble, epilog, instruction: new LLVM[action]( @@ -227,7 +216,7 @@ class ExecutionExpr extends ExecutionBase { opperands[0].instruction.name, opperands[1].instruction.name ), - type: opperands[0].type + type: type }; } @@ -236,7 +225,7 @@ class ExecutionExpr extends ExecutionBase { let epilog = new LLVM.Fragment(); // Load the two operands ready for operation - let opperand = this.compile_expr_opperand(ast.tokens[0]); + let opperand = this.compile_expr_opperand(ast.value[0]); // Catch any errors getting the opperands if (opperand.error) { @@ -250,10 +239,10 @@ class ExecutionExpr extends ExecutionBase { // Check opperands are primatives - if (!opperand.type.type.primative) { + if (!opperand.type.type.native) { this.getFile().throw( `Error: Cannot run arithmetic opperation on non-primative type`, - ast.tokens[0].ref.start, ast.tokens[0].ref.end + ast.value[0].ref.start, ast.value[0].ref.end ); return null; } @@ -269,13 +258,13 @@ class ExecutionExpr extends ExecutionBase { if (mode === null) { this.getFile().throw( `Error: Unable to perform arithmetic opperation for unknown reason`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } else if (mode === 1) { this.getFile().throw( `Error: Cannot invert a non signed integer`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } @@ -301,8 +290,8 @@ class ExecutionExpr extends ExecutionBase { // Load the two operands ready for operation let opperands = [ - this.compile_expr_opperand(ast.tokens[0]), - this.compile_expr_opperand(ast.tokens[1]) + this.compile_expr_opperand(ast.value[0]), + this.compile_expr_opperand(ast.value[1]) ]; @@ -322,17 +311,17 @@ class ExecutionExpr extends ExecutionBase { // Check opperands are primatives - if (!opperands[0].type.type.primative) { + if (!opperands[0].type.type.native) { this.getFile().throw( `Error: Cannot perform comparison opperation on non-primative type`, - ast.tokens[0].ref.start, ast.tokens[0].ref.end + ast.value[0].ref.start, ast.value[0].ref.end ); return null; } - if (!opperands[1].type.type.primative) { + if (!opperands[1].type.type.native) { this.getFile().throw( `Error: Cannot perform comparison opperation on non-primative type`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } @@ -341,8 +330,8 @@ class ExecutionExpr extends ExecutionBase { // Check opperands are the same type if (!opperands[0].type.match(opperands[1].type)) { this.getFile().throw( - `Error: Cannot perform comparison opperation on unequal types`, - ast.tokens[0].ref.start, ast.tokens[1].ref.end + `Error: Cannot perform comparison opperation on unequal types. ${opperands[0].type} != ${opperands[1].type}`, + ast.value[0].ref.start, ast.value[1].ref.end ); return null; } @@ -360,7 +349,7 @@ class ExecutionExpr extends ExecutionBase { if (mode === null) { this.getFile().throw( `Error: Unable to perform comparison opperation for unknown reason`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } @@ -418,7 +407,7 @@ class ExecutionExpr extends ExecutionBase { opperands[0].instruction.name, opperands[1].instruction.name ), - type: new TypeRef(0, Primative.types.bool) + type: new TypeRef(Primative.types.bool) }; } @@ -429,24 +418,24 @@ class ExecutionExpr extends ExecutionBase { let opperands = []; let action = null; - let type = new TypeRef(0, Primative.types.bool); + let type = new TypeRef(Primative.types.bool); switch (ast.type) { case "expr_and": case "expr_or": action = ast.type == "expr_and" ? "And" : "Or"; opperands = [ - this.compile_expr_opperand(ast.tokens[0]), - this.compile_expr_opperand(ast.tokens[1]) + this.compile_expr_opperand(ast.value[0]), + this.compile_expr_opperand(ast.value[1]) ]; break; case "expr_not": action = "XOr"; opperands = [ - this.compile_expr_opperand(ast.tokens[0]), + this.compile_expr_opperand(ast.value[0]), { preamble: new LLVM.Fragment(), epilog: new LLVM.Fragment(), - instruction: new LLVM.Constant("true"), + instruction: new LLVM.Constant("1"), type } ]; @@ -471,17 +460,17 @@ class ExecutionExpr extends ExecutionBase { // Check opperands are of boolean type - if (!opperands[0].type.match(type)) { + if (!opperands[0].type.weakMatch(type)) { this.getFile().throw( - `Error: Cannot perform boolean opperation on non boolean types`, - ast.tokens[0].ref.start, ast.tokens[0].ref.end + `Error: Cannot perform boolean opperation on a boolean and non-boolean type`, + ast.value[0].ref.start, ast.value[0].ref.end ); return null; } - if (!opperands[1].type.match(type)) { + if (!opperands[1].type.weakMatch(type)) { this.getFile().throw( - `Error: Cannot perform boolean opperation on non boolean types`, - ast.tokens[1].ref.start, ast.tokens[1].ref.end + `Error: Cannot perform boolean opperation on non-boolean types`, + ast.value[1].ref.start, ast.value[1].ref.end ); return null; } @@ -564,6 +553,132 @@ class ExecutionExpr extends ExecutionBase { }; } + compile_expr_struct (ast) { + // Check the struct type is correct + let typeRef = this.getType(ast.value[0]); + if (!(typeRef instanceof TypeRef)) { + this.getFile().throw(`Error: Invalid type "${ + Flatten.AccessToString(ast.value[0]) + }"`, ast.ref.start, ast.ref.end); + return null; + } + if (!(typeRef.type instanceof Structure)) { + this.getFile().throw(`Error: Invalid struct type "${ + Flatten.AccessToString(ast.value[0]) + }"`, ast.ref.start, ast.ref.end); + return null; + } + let type = typeRef.type; + + + // Allocate the new structure + let preamble = new LLVM.Fragment(); + let epilog = new LLVM.Fragment(); + let id = new LLVM.ID(); + preamble.append(new LLVM.Set( + new LLVM.Name(id, false, ast.ref), + new LLVM.Alloc(typeRef.toLLVM(), ast.ref) + )); + id = id.reference(); + + + // Create a hit map for uninitialised struct attributes + let hits = new Array(type.getTermCount()).fill(false); + + + for (let x of ast.value[1].value) { + + // Find attribute name + let name = x.value[0].value; + let i = type.indexOfTerm(name); + if (i == -1) { + this.getFile().throw( + `Error: Invalid struct attribute name "${name}"`, + x.value[0].ref.start, x.value[0].ref.end); + return null; + } + if (hits[i]) { + this.getFile().throw( + `Error: Attempting to set "${name}" twice in one structure`, + x.value[0].ref.start, x.value[0].ref.end); + return null; + } + hits[i] = true; + + + // Resolve attribute address + let res = type.accessGEPByIndex( + i, + new LLVM.Argument(typeRef.toLLVM(), new LLVM.Name(id, false, x.ref), x.ref), + x.ref, false + ); + preamble.merge(res.preamble); + let reg = res.instruction; + + res = this.compile_expr(x.value[1], null, true); + if (res == null) { + return null; + } + preamble.merge(res.preamble); + epilog.merge(res.epilog); + let instruction = res.instruction; + + + // Load the value so it can be written + if (!res.type.native) { + let load = new LLVM.ID(); + preamble.append(new LLVM.Set( + new LLVM.Name(load, false, x.ref), + new LLVM.Load( + new LLVM.Type(instruction.type.term, 0), + instruction.name, x.ref + ) + )); + instruction = new LLVM.Argument( + new LLVM.Type(instruction.type.term, 0, x.ref), + new LLVM.Name(load.reference(), false, x.ref), + x.ref); + } + + + // Write the value into the new struct + preamble.append(new LLVM.Store( + reg, + instruction, + x.ref + )); + } + + + // Throw for uninitialised values + if (hits.includes(false)) { + hits = hits.map((val, i) => { + if (val == true) { + return null; + } + + return type.terms[i].name; + }).filter(val => val != null); + + this.getFile().throw( + `Error: Uninitialised value${hits.length > 1 ? "s" : ""} ` + + hits.join(", "), + ast.ref.start, ast.ref.end + ); + } + + + return { + preamble, + instruction: new LLVM.Argument( + typeRef.toLLVM(ast.ref), + new LLVM.Name(id, false, ast.ref) + ), + epilog, + type: typeRef + }; + } + /** @@ -582,25 +697,29 @@ class ExecutionExpr extends ExecutionBase { res = this.compile_call(ast); break; case "variable": + case "access": res = this.compile_loadVariable(ast); break; case "expr_arithmetic": - res = this.compile_expr_arithmetic(ast.tokens[0]); + res = this.compile_expr_arithmetic(ast.value[0]); break; case "expr_compare": - res = this.compile_expr_compare(ast.tokens[0]); + res = this.compile_expr_compare(ast.value[0]); break; case "expr_bool": - res = this.compile_expr_bool(ast.tokens[0]); + res = this.compile_expr_bool(ast.value[0]); break; case "expr_clone": - res = this.compile_expr_clone(ast.tokens[0]); + res = this.compile_expr_clone(ast.value[0]); break; case "expr_lend": - res = this.compile_expr_lend(ast.tokens[0]); + res = this.compile_expr_lend(ast.value[0]); + break; + case "expr_struct": + res = this.compile_expr_struct(ast); break; default: - throw new Error(`Unexpected expression type ${ast.type}`); + throw new Error(`Unexpected expression type ${ast.type} at ${ast.ref.toString()}`); } if (res === null) { @@ -612,7 +731,7 @@ class ExecutionExpr extends ExecutionBase { return null; } - if (expects instanceof TypeRef && !expects.match(res.type)) { + if (expects instanceof TypeRef && !expects.matchApprox(res.type)) { this.getFile().throw( `Error: Type miss-match, ` + `expected ${expects.toString()}, ` + @@ -634,15 +753,7 @@ class ExecutionExpr extends ExecutionBase { !( res.instruction instanceof LLVM.Argument ) ) { let inner = res.instruction; - let irType = null; - if (expects) { - irType = expects.toLLVM(); - } else { - if (!res.type) { - throw new Error("Error: Cannot simplify due to undeduceable type"); - } - irType = res.type.toLLVM(); - } + let irType = res.type.toLLVM(); let id = new LLVM.ID(ast.ref.start); diff --git a/compiler/component/execution/flow.js b/compiler/component/execution/flow.js index b35d725..e6b99ab 100644 --- a/compiler/component/execution/flow.js +++ b/compiler/component/execution/flow.js @@ -1,8 +1,11 @@ const LLVM = require("../../middle/llvm.js"); const TypeRef = require('../typeRef.js'); +const Flattern = require("../../parser/flatten.js"); + const Primative = { - types: require('../../primative/types.js') + types: require('../../primative/types.js'), + Either: require('../../primative/either.js') }; @@ -14,7 +17,7 @@ class ExecutionFlow extends ExecutionExpr { let frag = new LLVM.Fragment(ast); // Check for elif clause - if (ast.tokens[1].length > 0) { + if (ast.value[1].length > 0) { this.getFile().throw( `Error: Elif statements are currently unsupported`, ast.ref.start, ast.ref.end @@ -29,8 +32,8 @@ class ExecutionFlow extends ExecutionExpr { Prepare condition value ===================================*/ let cond = this.compile_expr( - ast.tokens[0].tokens[0], - new TypeRef(0, Primative.types.bool), + ast.value[0].value[0], + new TypeRef(Primative.types.bool), true ); if (cond == null) { @@ -48,12 +51,16 @@ class ExecutionFlow extends ExecutionExpr { /*=================================== Prepare condition bodies ===================================*/ - let branch_true = this.compile_branch(ast.tokens[0].tokens[1], ast.tokens[0].ref); + let branch_true = this.compile_branch(ast.value[0].value[1], ast.value[0].ref); let branch_false = this.compile_branch( - ast.tokens[1].tokens[0], - ast.tokens[1].ref + ast.value[1].value[0], + ast.value[1].ref ); + if (branch_false == null || branch_true == null) { + return null; + } + @@ -145,14 +152,228 @@ class ExecutionFlow extends ExecutionExpr { } - compile_branch (ast, ref) { + compile_when (ast) { + let frag = new LLVM.Fragment(); + + // Prepare the target variable for reading + let target = this.compile_loadVariable(ast.value[0]); + if (target.error) { + this.getFile().throw(target.msg, target.ref.start, target.ref.end); + return null; + } + frag.append(target.preamble); + + // Check it is an either instance + if (!(target.type.type instanceof Primative.Either.Either_Instance)) { + this.getFile().throw( + `Invalid 'when' clause, target variable must be a 'Either' instance.\nInstead '${Flattern.VariableStr(ast.value[0])}' is of type ${target.type.type.name}`, + ast.ref.start, ast.ref.end); + return null; + } + + let options = target.type.type.signature.map(x => x.type); + let seen = []; + let branches = []; + let spare = null; + + // Process each branch of the when statement + for (let select of ast.value[1]) { + if (select.value[0].type == "data_type") { + let typeRef = this.resolveType(select.value[0]); + if (!(typeRef instanceof TypeRef)) { + this.getFile().throw( + `Error: Invalid type name "${Flattern.DataTypeStr(select.value[0])}"`, + select.ref.start, + select.ref.end + ); + return null; + } + + let index = options.indexOf(typeRef.type); + if (index === -1) { + this.getFile().throw( + `Error: Invalid type "${typeRef.type.name}" as it is not represented within this either`, + select.ref.start, + select.ref.end + ); + return null; + } + if (seen.indexOf(index) != -1) { + this.getFile().throw( + `Error: Type "${typeRef.type.name}" has already been used within this when statement`, + select.ref.start, + select.ref.end + ); + return null; + } + + + let scope = this.scope.clone(); + let variable = scope.getVar(ast.value[0]); + let latent = variable.induceType(typeRef, target.instruction, select.ref); + + let branch = this.compile_branch(select.value[1], select.ref, scope); + if (branch === null) { + return null; + } + + branch.frag.stmts = [ + branch.frag.stmts[0], + latent, + ...branch.frag.stmts.slice(1) + ]; + + + // reintroduce original value if valid + if (!variable.isUndefined()) { + branch.frag.append( + variable.deduceType(target.type, target.instruction, select.ref) + ); + } + + branches.push([ + index, + branch + ]); + seen.push(index); + + } else { + spare = this.compile_branch(select.value[1], select.ref); + if (spare === null) { + return null; + } + } + } + + // Check if the when statement covers all options + if (!spare && options.length != seen.length) { + this.getFile().throw( + `Error: This when statement does not cover all possible types\n` + + ` Suggest adding a default case to resolve the issue`, + ast.ref.start, + ast.ref.end + ); + return null; + } + + // Load the current state of the dynamic type + let ptr = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(ptr, false, ast.ref), + new LLVM.GEP( + new LLVM.Type(target.type.type.represent, 0), + target.instruction, + [ + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("0") + ), + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("1") + ) + ] + ), + ast.ref + )); + let state = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(state, false, ast.ref), + new LLVM.Load( + new LLVM.Type("i8", 0), + new LLVM.Name(ptr.reference(), false, ast.ref) + ), + ast.ref + )); + + // Construct the targeted jump statement + let endpoint_id = new LLVM.ID(); + let endLabel = new LLVM.Label(new LLVM.Name(endpoint_id.reference())); + frag.append(new LLVM.Switch( + new LLVM.Argument( + new LLVM.Type("i8", 0), + new LLVM.Name(state.reference(), false) + ), + spare ? + new LLVM.Label(new LLVM.Name(spare.id.reference(), false)) : + endLabel, + branches.map(x => [ + new LLVM.Argument( + new LLVM.Type("i8", 0), + new LLVM.Constant(x[0].toString()) + ), + new LLVM.Label(new LLVM.Name(x[1].id.reference(), false)) + ]) + )); + + + + + let totalSet = branches.map(x => x[1]); + if (spare) { + totalSet.push(spare); + } + + let continousSet = totalSet.filter( x => x.env.returned == false ); + let allReturned = continousSet.length == 0; + + if (!allReturned) { + for (let branch of continousSet) { + let res = branch.env.cleanup(branch.ref); + if (res.error) { + this.getFile().throw(res.msg, res.ref.start, res.ref.end); + return null; + } + branch.frag.append(res); + } + + let merger = this.sync( + continousSet.map(x => x.env), + endpoint_id, + ast.ref + ); + + // Merge branches + for (const [i, branch] of continousSet.entries()) { + // Merge synchronisation preamble for each branch + branch.frag.merge(merger.preambles[i]); + + // Jump to endpoint + branch.frag.append(new LLVM.Branch_Unco(endLabel)); + } + } + + + // Append the body of branches + for (const branch of totalSet) { + frag.merge(branch.frag); + } + + + frag.append(new LLVM.Label(endpoint_id).toDefinition()); + + if (allReturned) { + frag.append(new LLVM.Raw("unreachable")); + this.returned = true; + } + + + return frag; + } + + + compile_branch (ast, ref, scope = this.scope.clone()) { let id = new LLVM.ID(ref); - let env = this.clone(); + let env = this.clone(scope); env.entryPoint = id; let frag = new LLVM.Fragment(); if (ast !== null) { - frag.merge(env.compile(ast)); + let res = env.compile(ast); + if (res === null) { + return null; + } + frag.merge(res); } // Add the start label diff --git a/compiler/component/execution/index.js b/compiler/component/execution/index.js index cb2bb8a..4579da6 100644 --- a/compiler/component/execution/index.js +++ b/compiler/component/execution/index.js @@ -1,16 +1,18 @@ const Register = require('../memory/variable.js'); const Scope = require('../memory/scope.js'); -const Flattern = require('../../parser/flattern.js'); +const Flatten = require('../../parser/flatten.js'); const LLVM = require("../../middle/llvm.js"); const TypeRef = require('../typeRef.js'); -const Primative = { +const Primitive = { types: require('../../primative/types.js') }; const ExecutionFlow = require('./flow.js'); -const Variable = require('../memory/variable.js'); + +const Reserved = require('../../reserved.js'); +const { ResolveAccess } = require('../resolve.js'); class Execution extends ExecutionFlow { @@ -25,8 +27,8 @@ class Execution extends ExecutionFlow { // Load the target variable // This must occur after the expression is resolve // because this variable now needs to be accessed for writing - // after any reads that might have taken place in the expresion - let access = this.getVar(ast.tokens[0], false); + // after any reads that might have taken place in the expression + let access = this.getVar(ast.value[0], false); if (access.error) { this.getFile().throw( access.msg, access.ref.start, access.ref.start); return null; @@ -35,31 +37,17 @@ class Execution extends ExecutionFlow { access = access.variable; // Resolve the expression - let expr = this.compile_expr(ast.tokens[1], access.type, true); + let expr = this.compile_expr(ast.value[1], access.type, true); if (expr === null) { return null; } frag.merge(expr.preamble); - let targetType = access.type; - if (!expr.type.match(targetType)) { - this.getFile().throw( - `Error: Assignment type mis-match` + - ` cannot assign ${targetType.toString()}` + - ` to ${expr.type.toString()}`, - ast.ref.start, ast.ref.end - ); - return null; - } + // The expression compilation checks the type already + // If there is already a value in this variable, clear it first if (!access.isUndefined() && access.type.type.getDestructor()) { - this.getFile().throw( - `Error: Unsafe value drop\n` + - ` The previous value of ${access.name} was not destructed or consumed - but has now been lost\n` + - ` Suggest adding ${access.type.type.name}.Delete(${access.name}) before the current line`, - ast.ref.start, ast.ref.end - ); - return null; + frag.merge(access.cleanup(ast.ref)); } let chg = access.markUpdated(expr.instruction, false, ast.ref); @@ -67,129 +55,78 @@ class Execution extends ExecutionFlow { this.getFile().throw(chg.msg, chg.ref.start, chg.ref.end); return null; } + frag.merge(chg); frag.merge(expr.epilog); return frag; } compile_declare (ast) { - let name = ast.tokens[1].tokens; + let frag = new LLVM.Fragment(); + let name = ast.value[1].value; - let typeRef = this.resolveType(ast.tokens[0]); - if (!(typeRef instanceof TypeRef)) { - this.getFile().throw(`Error: Invalid type name "${Flattern.DataTypeStr(ast.tokens[0])}"`, ast.ref.start, ast.ref.end); - return null; + if (Reserved.Check(name)) { + this.getFile().throw( + `Error: Attempted to define a variable with a reserved word`, + ast.value[1].ref.start, + ast.value[1].ref.end + ); + // continue compilation to check for later errors as this is not a critical fault } - this.scope.register_Var( - typeRef, - name, - ast.ref.start - ); - - return new LLVM.Fragment(); - } - - /** - * Generates the LLVM for the combined action of define + assign - * @param {BNF_Node} ast - * @returns {LLVM.Fragment} - */ - compile_declare_assign (ast) { - let frag = new LLVM.Fragment(); - - // If there is a goal type - // Get the goal type let targetType = null; - if (ast.tokens[0] !== null) { - targetType = this.resolveType(ast.tokens[0]); + if (ast.value[0].type != "blank") { + targetType = this.getType(ast.value[0]); if (!(targetType instanceof TypeRef)) { - this.getFile().throw(`Error: Invalid type name "${ - Flattern.DataTypeStr(ast.tokens[0]) - }"`, ast.ref.start, ast.ref.end); + this.getFile().throw( + `Error: Invalid type name "${Flatten.DataTypeStr(ast.value[0])}"`, + ast.ref.start, + ast.ref.end + ); return null; } } - - - // Compile the expression - let expr = this.compile_expr(ast.tokens[2], targetType, true); - if (expr === null) { - return null; - } - frag.merge(expr.preamble); - - // If the type was not given, extract it from the expression - if (targetType === null) { + let expr = null; + if (ast.value[2].type != "blank") { + expr = this.compile_expr(ast.value[2], targetType, true); + if (expr === null) { + return null; + } + frag.merge(expr.preamble); targetType = expr.type; } - // Declare the variable and assign it to the expression result - let variable = this.scope.register_Var( - targetType, // type - ast.tokens[1].tokens, // name - ast.ref.start // ref - ); - let chg = variable.markUpdated(expr.instruction, false, ast.ref); - if (chg.error) { - this.getFile().throw(chg.msg, chg.ref.start, chg.ref.end); - return null; - } - - - frag.merge(expr.epilog); - return frag; - } - - - compile_delete (ast) { - let frag = new LLVM.Fragment(); - - let target = this.getVar(ast.tokens[0], true); - if (target.error) { - this.getFile().throw( target.msg, target.ref.start, target.ref.end ); - return null; - } - frag.merge(target.preamble); - target = target.variable; - - if (target.type.lent) { + if (targetType == null) { this.getFile().throw( - `Cannot delete lent values`, - ast.ref.start, ast.ref.end + `Error: No type information on declaration\n Must either have explicit type or an assignment for inferred types`, + ast.ref.start, + ast.ref.end ); - return null; - } - let res = target.delete(ast.ref); - if (res.error) { - this.getFile().throw(res.msg, res.ref.start, res.ref.end); return null; } - let destructor = target.type.type.getDestructor(); - if (destructor) { - if (this.ctx == destructor) { - this.getFile().throw( - `Error: Dangerous destructor, does not properly destruct all child values`, - ast.ref.start, ast.ref.end - ); - } else { - this.getFile().warn( - `Warn: This class type has a destructor, recommend calling ${target.type.type.name}.Delete(${target.name})`, - ast.ref.start, ast.ref.end - ); + let variable = this.scope.register_Var( + targetType, + name, + ast.ref.start + ); + if (expr) { + let chg = variable.markUpdated(expr.instruction, false, ast.ref); + if (chg.error) { + this.getFile().throw(chg.msg, chg.ref.start, chg.ref.end); + return null; } + frag.merge(chg); + frag.merge(expr.epilog); } - frag.merge(res); return frag; } - /** * Generates the LLVM for a call * Used in other compile functions @@ -209,7 +146,8 @@ class Execution extends ExecutionFlow { let signature = []; let args = []; let regs = []; - for (let arg of ast.tokens[2].tokens) { + + for (let arg of ast.value[1].value) { let expr = this.compile_expr(arg, null, true); if (expr === null) { return null; @@ -230,33 +168,18 @@ class Execution extends ExecutionFlow { } } - // Link any [] accessors - let accesses = [ ast.tokens[0].tokens[1].tokens ]; - for (let access of ast.tokens[0].tokens[2]) { - if (access[0] == "[]") { - file.throw ( - `Error: Class base function execution is currently unsupported`, - inner.ref.start, inner.ref.end - ); - return null; - } else { - accesses.push([access[0], access[1].tokens]); - } - } - - // Link any template access - let template = this.resolveTemplate(ast.tokens[1]); - if (template === null) { + let res = ResolveAccess(ast.value[0], this.ctx); + if (res === null) { return null; } + let access = res.access; // Find a function with the given signature - let target = this.getFunction(accesses, signature, template); + let target = this.getFunction(access, signature); if (!target) { - let funcName = Flattern.VariableStr(ast.tokens[0]); file.throw( - `Error: Unable to find function "${funcName}" with signature (${signature.join(", ")})`, + `Error: Unable to find function "${Flatten.AccessToString(ast.value[0])}" with signature (${signature.join(", ")})`, ast.ref.start, ast.ref.end ); return null; @@ -295,7 +218,7 @@ class Execution extends ExecutionFlow { complex ? new LLVM.Type("void", 0, ast.ref) : target.returnType.toLLVM(ast.ref), - new LLVM.Name(target.represent, true, ast.tokens[0].ref), + new LLVM.Name(target.represent, true, ast.value[0].ref), args, ast.ref.start ); @@ -346,12 +269,17 @@ class Execution extends ExecutionFlow { return null; } else { - let id = new LLVM.ID(); - frag.append(new LLVM.Set( - new LLVM.Name(id, false, ast.ref), - out.instruction, - ast.ref - )); + if (out.instruction instanceof LLVM.Argument) { + // Don't save a constant evaluation to a register + // i.e. %2 = i32 is bad + } else { + let id = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(id, false, ast.ref), + out.instruction, + ast.ref + )); + } } } @@ -367,7 +295,7 @@ class Execution extends ExecutionFlow { compile_decompose (ast) { let frag = new LLVM.Fragment(); - let target = this.getVar(ast.tokens[0], true); + let target = this.getVar(ast.value[0], true); if (target.error) { this.getFile().throw( target.msg, target.ref.start, target.ref.end ); return null; @@ -388,7 +316,7 @@ class Execution extends ExecutionFlow { compile_compose (ast) { let frag = new LLVM.Fragment(); - let target = this.getVar(ast.tokens[0], true); + let target = this.getVar(ast.value[0], true); if (target.error) { this.getFile().throw( target.msg, target.ref.start, target.ref.end ); return null; @@ -417,11 +345,11 @@ class Execution extends ExecutionFlow { // Get the return result in LLVM.Argument form let returnType = null; - if (ast.tokens.length == 0){ + if (ast.value.length == 0){ inner = new LLVM.Type("void", false); - returnType = new TypeRef(0, Primative.types.void); + returnType = new TypeRef(Primitive.types.void); } else { - let res = this.compile_expr(ast.tokens[0], this.returnType, true); + let res = this.compile_expr(ast.value[0], this.returnType, true); if (res === null) { return null; } @@ -429,49 +357,27 @@ class Execution extends ExecutionFlow { frag.merge(res.preamble); if (returnType.type.typeSystem == "linear") { - - let size = returnType.type.sizeof(ast.ref); - frag.append(size.preamble); - - let fromID = new LLVM.ID(); - frag.append(new LLVM.Set( - new LLVM.Name(fromID, false), - new LLVM.Bitcast( - new LLVM.Type("i8", 1), - res.instruction - ) - )); - let toID = new LLVM.ID(); + let cacheID = new LLVM.ID(); frag.append(new LLVM.Set( - new LLVM.Name(toID, false), - new LLVM.Bitcast( - new LLVM.Type("i8", 1), - new LLVM.Argument( - returnType.toLLVM(), - new LLVM.Name("0", false, ast.ref), - ast.ref - ) - ) + new LLVM.Name(cacheID, false), + new LLVM.Load( + res.instruction.type.duplicate().offsetPointer(-1), + res.instruction.name + ), + ast.ref )); - frag.append(new LLVM.Call( - new LLVM.Type("void", 0), - new LLVM.Name("llvm.memmove.p0i8.p0i8.i64", true), - [ - new LLVM.Argument( - new LLVM.Type("i8", 1), - new LLVM.Name(toID.reference(), false) - ), - new LLVM.Argument( - new LLVM.Type("i8", 1), - new LLVM.Name(fromID.reference(), false) - ), - size.instruction, - new LLVM.Argument( - new LLVM.Type('i1', 0), - new LLVM.Constant("0") - ) - ] + frag.append(new LLVM.Store( + new LLVM.Argument( + returnType.toLLVM(), + new LLVM.Name("0", false, ast.ref), + ast.ref + ), + new LLVM.Argument( + returnType.toLLVM().offsetPointer(-1), + new LLVM.Name(cacheID.reference(), false) + ), + ast.ref )); inner = new LLVM.Type("void", 0, ast.ref); @@ -513,16 +419,15 @@ class Execution extends ExecutionFlow { compile (ast) { let fragment = new LLVM.Fragment(); - let returnWarned = false; let failed = false; let inner = null; - for (let token of ast.tokens) { - if (this.returned && !returnWarned) { + + for (let token of ast.value) { + if (this.returned) { this.getFile().throw( - `Warn: This function has already returned, this line and preceeding lines will not execute`, + `Warn: This function has already returned, this line and preceding lines will not execute`, token.ref.start, token.ref.end ); - returnWarned = true; break; } @@ -533,9 +438,6 @@ class Execution extends ExecutionFlow { case "assign": inner = this.compile_assign(token); break; - case "declare_assign": - inner = this.compile_declare_assign(token); - break; case "return": inner = this.compile_return(token); break; @@ -545,15 +447,15 @@ class Execution extends ExecutionFlow { case "if": inner = this.compile_if(token); break; + case "when": + inner = this.compile_when(token); + break; case "compose": inner = this.compile_compose(token); break; case "decompose": inner = this.compile_decompose(token); break; - case "delete": - inner = this.compile_delete(token); - break; default: this.getFile().throw( `Unexpected statment ${token.type}`, @@ -569,28 +471,15 @@ class Execution extends ExecutionFlow { } } - if (!failed && this.returned == false && !this.isChild) { - if (this.returnType.type == Primative.types.void) { - // Auto generate return and cleanup for void functions - - // Clean up the scope - let res = this.scope.cleanup(ast.ref); - if (res.error) { - this.getFile().throw(res.msg, res.ref.start, res.ref.end); - } else { - fragment.append(res); - } + if (failed) { + return null; + } else if (this.returned == false && !this.isChild) { + this.getFile().throw( + `Function does not return`, + ast.ref.start, ast.ref.end + ); - fragment.append(new LLVM.Return( - new LLVM.Type("void", 0), - ast.ref - )); - } else { - this.getFile().throw( - `Function does not return`, - ast.ref.start, ast.ref.end - ); - } + return null; } return fragment; @@ -602,8 +491,7 @@ class Execution extends ExecutionFlow { * Deep clone * @returns {Scope} */ - clone () { - let scope = this.scope.clone(); + clone (scope = this.scope.clone()) { let out = new Execution(this, this.returnType, scope); out.isChild = true; return out; diff --git a/compiler/component/file.js b/compiler/component/file.js index 72dbda4..203dc36 100644 --- a/compiler/component/file.js +++ b/compiler/component/file.js @@ -3,16 +3,18 @@ const BNF = require('bnf-parser'); const helper = require('../helper/error.js'); -const LLVM = require('./../middle/llvm.js'); +const LLVM = require('../middle/llvm.js'); const Function = require('./function.js'); const TypeDef = require('./typedef.js'); const Structure = require('./struct.js'); -const Class = require('./class.js'); +const Trait = require('./trait.js'); +const Implement = require('./impl.js'); const TypeRef = require('./typeRef.js'); const Import = require('./import.js'); +const { ResolveAccess } = require("./resolve.js"); // const { Namespace, Namespace_Type } = require('./namespace.js'); -const Parse = require('./../parser/parse.js'); +const Parse = require('../parser/parse.js'); const fs = require('fs'); const Template = require('./template.js'); @@ -29,12 +31,12 @@ class File { this.names = {}; - let prim = this.project.getPrimative(); - if (prim) { - let lib = new Import(this, null); - lib.inject(prim); - this.names["*"] = lib; - } + this.impls = []; + + let prims = this.project.getPrimatives(); + let lib = new Import(this, null); + this.names["*"] = lib; + prims.map(x => lib.inject(x)); this.exports = []; this.imports = []; @@ -49,33 +51,30 @@ class File { let syntax = Parse(this.data, this.path); // read in imports, templates, functions - for (let element of syntax.tokens) { + for (let element of syntax.value) { // Ignore comments switch (element.type) { - case "comment": - break; case "external": - if (element.tokens[0] == "assume") { - for (let inner of element.tokens[1]){ - this.register(inner, true); - } - } else if (element.tokens[0] == "export") { - for (let inner of element.tokens[1]){ - this.exports.push(inner); - } - } else { - console.error(`Error: Unknown external type "${element.tokens[0]}"`); - this.project.markError(); - return false; + switch (element.value[0].value) { + case "assume": + for (let inner of element.value[1].value){ + this.register(inner, true); + } + break; + case "export": + for (let inner of element.value[1].value){ + this.exports.push(inner); + } + break; + default: + console.error(`Error: Unknown external type "${element.value[0].value}"`); + this.project.markError(); + return false; } break; case "library": - let inner = element.tokens[0]; + let inner = element.value[0]; if (inner.type == "import") { - inner.tokens = [ - inner.tokens[0].tokens[1], - inner.tokens[1] - ]; this.register(inner); } else { console.error(` Parse Error: Unknown library action "${inner.type}"`); @@ -84,7 +83,7 @@ class File { } break; case "include": - this.include(element.tokens[0], element.tokens[1], element.ref); + this.include(element.value[0].value, element.value[1].value, element.ref); break; default: this.register(element); @@ -108,14 +107,13 @@ class File { space = new TypeDef(this, element, external); break; case "function_outline": - abstract = !external; - // continue to function case + abstract = !external; // continue to function case case "function": space = new Function(this, element, external, abstract); break; case "function_redirect": space = new Function(this, element, external, false); - space.instances[0].represent = element.tokens[1]; + space.instances[0].represent = element.value[1]; break; case "import": space = new Import(this, element); @@ -123,9 +121,12 @@ class File { case "struct": space = new Structure(this, element, external); break; - case "class": - space = new Class(this, element, external); + case "trait": + space = new Trait(this, element, external); break; + case "impl": + this.impls.push( new Implement(this, element) ); + return; default: throw new Error(`Unexpected file scope namespace type "${element.type}"`); } @@ -175,77 +176,108 @@ class File { } } - getType (typeList, template = [], stack = []) { - let res = null; - // File access must be direct - if (typeList[0][0] == "." || Number.isInteger(typeList[0][0])) { - res = this.names[typeList[0][1]]; - - if (res) { - if (res instanceof Template || typeList.length > 1) { - return res.getType(typeList.slice(1), template); - } else { - return new TypeRef(0, res); - } + getType (access, stack = []) { + if (access instanceof BNF.SyntaxNode) { + let res = ResolveAccess(access, this); + if (res == null) { + return null; } - } else { + + let type = this.getType(res.access, stack); + + + if (type == null) { + return null; + } + + type.constant = res.constant; + type.lent = res.lent; + return type; + } + + // Circular loop check + if (stack.includes(this.id)) { return null; } + stack.push(this.id); - // Circular loop - if (stack.includes(this)) { + if (access.length == 0) { return null; } - stack.push(this); + + if (access.length > 0) { + let term = access[0]; + + switch (term.type) { + case "name": + case "access_static": + break; + default: + return null; + } + + if (this.names[term.value]) { + let res = this.names[term.value].getType(access.slice(1), stack); + if (res) { + return res; + } + } + } // If the name isn't defined in this file // Check other files if (this.names["*"] instanceof Import) { - return this.names["*"].getType(typeList, template); + return this.names["*"].getType(access, stack); } return null; } - getFunction (access, signature, template, stack = []) { - if (access.length < 1) { + + getFunction (access, signature, stack = []) { + if (access instanceof BNF.SyntaxNode) { + throw new TypeError("Internal error, unexpected SyntaxNode"); + } + + if (access.length == 0) { return null; } - let first = access[0]; - let forward = access.slice(1); - if (Array.isArray(first)) { - if (first[0] == ".") { - first = first[1]; - } else { - return null; + if (access.length > 0) { + let term = access[0]; + switch (term.type) { + case "name": + case "access_static": + break; + default: + return null; } - } - if (this.names[first]) { - let res = this.names[first].getFunction(forward, signature, template); - if (res !== null) { - return res; + if (this.names[term.value]) { + let res = this.names[term.value].getFunction(access.slice(1), signature, stack); + if (res) { + return res; + } } } // Circular loop - if (stack.includes(this)) { + if (stack.includes(this.id)) { return null; } - stack.push(this); + stack.push(this.id); - // If the name isn't defined in this file in a regular name space - // Check namespace imports + // If the name isn't defined in this file + // Check other files if (this.names["*"] instanceof Import) { - return this.names["*"].getFunction(access, signature, template, stack); + return this.names["*"].getFunction(access, signature, stack); } return null; } getMain () { - return this.names['main']; + return this.names['main'] || null; } getID () { @@ -284,22 +316,12 @@ class File { } // Check the include type is valid - switch (type) { - case "llvm": - type = "--language=ir"; - break; - case "cpp": - type = "--language=c++"; - break; - case "c": - type = "--language=c"; - break; - default: - this.throw( - `Error: Cannot include file, unable to handle include type ${type}`, - ref.start, ref.end - ); - return; + if (!["llvm", "static"].includes(type)) { + this.throw( + `Error: Cannot include file, unable to handle include type "${type}"`, + ref.start, ref.end + ); + return; } // Check the file exists @@ -331,7 +353,7 @@ class File { throw (msg, refStart, refEnd) { // let area = BNF.Message.HighlightArea(this.data, refStart, refEnd); let area = helper.CodeSection(this.data, refStart, refEnd); - console.error(`\n${this.relPath}:\n ${msg}\n${area.replace(/\t/g, ' ')}`); + console.error(`\n${this.relPath}:\n ${msg.replace(/\n/g, "\n ")}\n${area.replace(/\t/g, ' ')}`); this.project.markError(); } warn (msg, refStart, refEnd) { @@ -349,6 +371,10 @@ class File { for (let external of this.exports) { this.registerExport(external); } + + for (let imp of this.impls) { + imp.link(); + } } @@ -356,6 +382,10 @@ class File { for (let key in this.names) { this.names[key].compile(); } + + for (let imp of this.impls) { + imp.compile(); + } } @@ -378,6 +408,15 @@ class File { } } + for (let imp of this.impls) { + let res = imp.toLLVM(); + if (res instanceof LLVM.Fragment) { + fragment.merge(res); + } else { + fragment.append(res); + } + } + return fragment; } } diff --git a/compiler/component/function.js b/compiler/component/function.js index dbac611..0b767cb 100644 --- a/compiler/component/function.js +++ b/compiler/component/function.js @@ -5,7 +5,7 @@ const Structure = require('./struct.js'); class Function { constructor (ctx, ast, external = false, abstract = false) { - this.name = ast.tokens[0].tokens[1].tokens; + this.name = ast.value[0].value[1].value; this.ctx = ctx; this.ref = ast.ref.start; @@ -41,16 +41,27 @@ class Function { this.instances[0].markExport(); } - getFunction (variable, signature) { - if (variable.length != 0) { + getFunction (access, signature) { + if (access.length != 0) { return null; } return this.matchSignature(signature); } + getType(access, stack) { + return this.ctx.getType(access, stack); + } + matchSignature (sig) { for (let instance of this.instances) { + if (instance.abstract && !instance.external) { + this.getFile().throw( + `Error: Cannot call abstract function "${this.name}" as it has no implementation`, + instance.ref, + instance.ref + ); + } if (instance.matchSignature(sig)) { return instance; } @@ -59,6 +70,36 @@ class Function { return null; } + ensureEquivalence(other) { + outer: for (let instA of this.instances) { + for (let instB of other.instances) { + if (instA.matchSignature(instB.signature)) { + continue outer; + } + } + + this.ctx.getFile().throw( + `Error: Unable to find implementation of "${instA.toString()}" for trait "${this.ctx.trait.name}" in implementation`, + this.ctx.ref, + this.ctx.endRef + ); + } + + outer: for (let instA of other.instances) { + for (let instB of this.instances) { + if (instA.matchSignature(instB.signature)) { + continue outer; + } + } + + this.ctx.getFile().throw( + `Error: Implementation has an extra function instance "${instA.toString()}" for trait "${this.ctx.trait.name}" in implementation`, + this.ctx.ref, + instA.ref + ); + } + } + merge (other){ this.instances = this.instances.concat( other.instances ); return true; @@ -85,6 +126,12 @@ class Function { return; } + relink() { + for (let instance of this.instances) { + instance.relink(); + } + } + compile () { for (let instance of this.instances) { instance.compile(); diff --git a/compiler/component/function_instance.js b/compiler/component/function_instance.js index e3a7c53..adc9b78 100644 --- a/compiler/component/function_instance.js +++ b/compiler/component/function_instance.js @@ -1,13 +1,14 @@ const { Generator_ID } = require('./generate.js'); -const Flattern = require('./../parser/flattern.js'); -const TypeDef = require('./typedef.js'); +const Flatten = require('./../parser/flatten.js'); const LLVM = require('../middle/llvm.js'); const Execution = require('./execution/index.js'); const Scope = require('./memory/scope.js'); const TypeRef = require('./typeRef.js'); -const Structure = require('./struct.js'); +const Primitive = { + types: require('./../primative/types.js') +}; let funcIDGen = new Generator_ID(); @@ -17,7 +18,7 @@ class Function_Instance { this.ast = ast; this.ref = ast.ref.start; this.external = external; - this.abstract = abstract; + this.abstract = abstract || ast.type == "function_outline"; this.returnType = null; this.signature = []; @@ -28,7 +29,11 @@ class Function_Instance { this.id = funcIDGen.next(); - this.name = ast.tokens[0].tokens[1].tokens; + this.constNum = 0; + + this.consts = []; + + this.name = ast.value[0].value[1].value; this.represent = external ? `${this.name}` : `${this.ctx.represent}.${this.id.toString(36)}`; @@ -47,6 +52,10 @@ class Function_Instance { return this.ctx.getFile(); } + getType(access, stack = []) { + this.ctx.getType(access, stack); + } + getFunctionGroup () { return this.ctx.getFunctionGroup(); } @@ -54,7 +63,25 @@ class Function_Instance { return this; } + getType(access, stack) { + return this.ctx.getType(access, stack); + } + + bindConst(val, ref = null) { + let id = this.consts.indexOf(val); + if (id == -1) { + id = this.consts.length; + this.consts.push(val); + } + return new LLVM.Name(`${this.represent}.const.${id}`, true); + } + + + relink () { + this.linked = false; + this.link(); + } link () { if (this.linked) { @@ -62,43 +89,37 @@ class Function_Instance { } let file = this.getFile(); - let head = this.ast.tokens[0]; - let args = head.tokens[2].tokens; - - // Flaten signature types AST into a single array - let types = [ head.tokens[0] ]; - let borrows = [ false ]; - let consts = [ false ]; - if (args.length > 0) { - borrows = borrows.concat(args.map(x => x[0] == "@")); - consts = consts.concat(args.map(x => x[0] == "#")); - types = types.concat(args.map((x) => x[1])); - } - - // Generate an execution instance for type resolving - let exec = new Execution( - this, - null, - new Scope(this, this.getFile().project.config.caching) - ); - for (let [i, type] of types.entries()){ - let search = exec.resolveType(type); - if (search instanceof TypeRef) { - search.pointer = type.tokens[0]; // Copy the pointer level across - search.lent = borrows[i]; - search.constant = consts[i]; - - this.signature.push(search); - } else { - file.throw( - `Invalid type name "${Flattern.DataTypeStr(type)}"`, - type.ref.start, type.ref.end - ); - } - } + let head = this.ast.value[0]; + this.signature = [ head.value[0], ...head.value[2].value.map(x => x.value[0]) ] + .map((x, i) => { + if (x.type == "blank") { + return new TypeRef(Primitive.types.void, false, false, false); + } + + let search = this.getType(x); + if (search instanceof TypeRef) { + // If an argument is void (ignoring return type which is at the front) + if (i != 0 && search.type == Primitive.types.void) { + file.throw( + `Functions cannot include void type as argument`, + x.ref.start, x.ref.end + ); + } + } else { + file.throw( + `Invalid type name "${Flatten.AccessToString(x)}"`, + x.ref.start, x.ref.end + ); + } + + // Return the search even if it's invalid + // That way we check the other args at the same time + // In case they're also invalid + return search; + }); - this.returnType = this.signature.splice(0, 1)[0]; + this.returnType = this.signature.shift(); this.linked = true; } @@ -129,7 +150,7 @@ class Function_Instance { compile () { - if (this.abstract) { + if (this.abstract && !this.external) { return null; } @@ -138,15 +159,14 @@ class Function_Instance { this.getFile().project.config.caching ); - let head = this.ast.tokens[0]; - let args = []; - for (let i=0; i { + return { + type: x, + name: head.value[2].value[i].value[1].value, + ref: head.value[2].value[i].ref + }; + }); let res = scope.register_Args( args ); if (res == null) { @@ -155,7 +175,7 @@ class Function_Instance { let argsRegs = res.registers; let id = new LLVM.ID(); - let complex = this.returnType.type.typeSystem == "linear"; + let complex = !this.returnType.native; if (complex) { argsRegs = [ new LLVM.Argument( @@ -168,9 +188,9 @@ class Function_Instance { let frag = new LLVM.Procedure( complex ? - new LLVM.Type("void", 0, head.tokens[0].ref) : - this.returnType.toLLVM(head.tokens[0].ref), - new LLVM.Name(this.represent, true, head.tokens[1].ref), + new LLVM.Type("void", 0, head.value[0].ref) : + this.returnType.toLLVM(head.value[0].ref), + new LLVM.Name(this.represent, true, head.value[1].ref), argsRegs, "#1", this.external, @@ -193,17 +213,33 @@ class Function_Instance { scope, entry_id.reference() ); - frag.merge(exec.compile(this.ast.tokens[1])); + let inner = exec.compile(this.ast.value[1]); + if (inner === null) { + return null; + } + frag.merge(inner); } let gen = new Generator_ID(0); frag.assign_ID(gen); - this.ir = frag; + this.ir.append(frag); } + toString() { + return `${this.name}(${this.signature.map(x => x.toString()).join(", ")}): ${this.returnType.toString()}`; + } toLLVM() { + this.ir.stmts = [ + ...this.consts + .map((val, i) => new LLVM.Set( + new LLVM.Name(`${this.represent}.const.${i}`, true), + new LLVM.Raw(val) + )), + ...this.ir.stmts + ]; + return this.ir; } } diff --git a/compiler/component/impl.js b/compiler/component/impl.js new file mode 100644 index 0000000..aaec6e5 --- /dev/null +++ b/compiler/component/impl.js @@ -0,0 +1,159 @@ + +const { SyntaxNode } = require('bnf-parser'); + +const Flatten = require('../parser/flatten.js'); + +const Function = require('./function.js'); +const LLVM = require('../middle/llvm.js'); + +const TypeRef = require('./typeRef.js'); + +class Implement { + constructor (ctx, ast, external = false) { + this.ctx = ctx; + this.ast = ast; + this.ref = ast.ref.start; + this.endRef = ast.ref.end; + + this.names = {}; + + this.trait = null; + this.struct = null; + + this.represent = "%impl."; + } + + getFile() { + return this.ctx.getFile(); + } + + + getType(access, stack) { + if ( access.length == 0 ) { + return new TypeRef(this); + } + + if (access instanceof SyntaxNode && access.value[1].value == "Self") { + if (access.value[2].value.length != 0) { + return null; + } + + return new TypeRef( + this.struct, + ["@", "$"].includes(access.value[0].value), + access.value[0].value == "$" + ); + } + + return this.ctx.getType(access, stack); + } + + link () { + let file = this.ctx.getFile(); + + let structToken = this.ast.value[0]; + this.struct = this.ctx.getType(structToken); + if (this.struct == null) { + file.throw( + `Cannot implement for known type "${Flatten.AccessToString(structToken)}"`, + this.ref, structToken.ref.end + ); + return false; + } + this.struct = this.struct.type; + + let traitToken = this.ast.value[1]; + let traitName = "default"; + if (traitToken.type != "blank") { + let type = this.ctx.getFile().getType(traitToken); + if (type == null) { + file.throw( + `Cannot implement for unknown trait "${Flatten.AccessToString(traitToken)}"`, + this.ref, structToken.ref.end + ); + return false; + } + + if (type.lent) { + file.throw( + `Cannot implement for a lent trait "${Flatten.AccessToString(traitToken)}"`, + this.ref, structToken.ref.end + ); + return false; + } + + traitName = type.type.name; + this.trait = type.type; + } + this.represent = this.struct.name + "." + traitName; + + for (let node of this.ast.value[2].value) { + switch (node.type) { + case "function": + let space = new Function(this, node, false, false); + + if (!this.names[space.name]) { + this.names[space.name] = space; + } else if ( !this.names[space.name].merge || + !this.names[space.name].merge(space) ) { + + this.getFile().throw( + `Name collision between functions with the same name "${space.name}" and identical signatures`, + this.names[space.name].ref, + space.ref + ); + this.project.markError(); + return false; + } + + break; + default: + throw new Error(`Unexpected attribute ${node.type}`); + } + } + + + // Link all successful functions + for (let name in this.names) { + this.names[name].link(); + } + + + if (this.trait) { + this.trait.bindImplementation(this); + } + this.struct.bindImplementation(this); + } + + getFunction (access, signature, stack) { + if (access.length != 1) { + return null; + } + + let name = access[0].value; + if (this.names[name]) { + return this.names[name].getFunction([], signature, stack); + } + + return null; + } + + + compile () { + for (let name in this.names) { + this.names[name].compile(); + } + } + + toLLVM () { + let out = new LLVM.Fragment(); + + for (let name in this.names) { + out.append(this.names[name].toLLVM()); + } + + return out; + } +} + +module.exports = Implement; \ No newline at end of file diff --git a/compiler/component/import.js b/compiler/component/import.js index 536e6b3..7c0925b 100644 --- a/compiler/component/import.js +++ b/compiler/component/import.js @@ -5,13 +5,14 @@ class Import { this.ctx = ctx; this.ref = ast ? ast.ref.start : null; - this.name = ( ast && ast.tokens[1] && ast.tokens[1].tokens ) ? ast.tokens[1].tokens : "*"; + let hasAst = ast != null; + this.name = hasAst ? ast.value[0].value : "*"; this.files = []; - if (ast) { + if (hasAst) { this.files.push({ file: null, - path: ast.tokens[0], + path: ast.value[1].value, ref: ast.ref.start }); } @@ -64,9 +65,14 @@ class Import { return null; } - getFunction (access, signature, template, stack) { + getFunction (access, signature, stack) { for (let lib of this.files) { - let opt = lib.file.getFunction(access, signature, template, stack); + if (stack.includes(lib.file.id)) { + continue; + } + + let opt = lib.file.getFunction(access, signature, stack); + stack.push(lib.file.id); if (opt) { return opt; } diff --git a/compiler/component/memory/probability.js b/compiler/component/memory/probability.js index ceeb09a..595ceec 100644 --- a/compiler/component/memory/probability.js +++ b/compiler/component/memory/probability.js @@ -2,6 +2,13 @@ const LLVM = require('./../../middle/llvm.js'); class Probability { + /** + * + * @param {LLVM.Latent} activator The code path to enable on activation + * @param {LLVM.Argument} register The value to be taken on after activation + * @param {*} segment + * @param {*} ref + */ constructor (activator, register, segment, ref) { this.activator = activator; this.register = register; diff --git a/compiler/component/memory/scope.js b/compiler/component/memory/scope.js index 24d79e4..e00f32f 100644 --- a/compiler/component/memory/scope.js +++ b/compiler/component/memory/scope.js @@ -1,4 +1,4 @@ -const Flattern = require('../../parser/flattern.js'); +const Flattern = require('../../parser/flatten.js'); const { Generator_ID } = require('../generate.js'); const LLVM = require("../../middle/llvm.js"); const TypeRef = require('./../typeRef.js'); @@ -12,6 +12,8 @@ class Scope { this.ctx = ctx; this.variables = {}; this.isChild = false; + + this.lentNormals = []; } @@ -50,23 +52,27 @@ class Scope { if (this.variables[arg.name]) { this.getFile().throw( `Duplicate use of argument ${arg.name} function`, - this.variables[arg.name].declared, arg.ref + this.variables[arg.name].ref.start, arg.ref.end ); return null; } + // Load any lent normal types so they can be treated as normal variables + let id = new LLVM.ID(); + let type = arg.type; + let reg = new LLVM.Name(id.reference(), false); + // Creation of namespace this.variables[arg.name] = new Variable( - arg.type.duplicate(), + type.duplicate(), arg.name, arg.ref ); // Declaration in function argument - let id = new LLVM.ID(); registers.push(new LLVM.Argument( - this.variables[arg.name].type.toLLVM(), + arg.type.toLLVM(), new LLVM.Name(id, false) )); @@ -74,7 +80,7 @@ class Scope { let chg = this.variables[arg.name].markUpdated( new LLVM.Argument( this.variables[arg.name].type.toLLVM(), - new LLVM.Name(id.reference(), false) + reg ), true, { @@ -86,6 +92,8 @@ class Scope { this.getFile().throw(chg.msg, chg.ref.start, chg.ref.end); return null; } + + // force will never generate a code fragment this.variables[arg.name].hasUpdated = false; } @@ -120,16 +128,20 @@ class Scope { * @returns {Variable} */ getVar (ast, read = true) { - if (ast.type != "variable") { - throw new TypeError(`Parsed AST must be a branch of type variable, not "${ast.type}"`); + switch (ast.type) { + case "variable": + case "access": + break; + default: + throw new TypeError(`Parsed AST must be a branch of type variable, not "${ast.type}"`); } - let target = this.variables[ast.tokens[1].tokens]; + let target = this.variables[ast.value[0].value]; if (!target) { return { error: true, - msg: `Unknown variable name ${ast.tokens[1].tokens}`, - ref: ast.tokens[1].ref + msg: `Unknown variable name "${ast.value[0].value}"`, + ref: ast.value[0].ref }; } @@ -166,7 +178,7 @@ class Scope { }; } - return new TypeRef (target.pointer - ast.tokens[0], target.type); + return new TypeRef(target.type); } /** @@ -221,6 +233,20 @@ class Scope { frag.merge(res); } + + for (let val of this.lentNormals) { + let res = val[0].read(ref); + frag.merge(res.preamble); + + let lentType = res.type.duplicate(); + lentType.lent = true; + + frag.append(new LLVM.Store( + new LLVM.Argument(lentType.toLLVM(), val[1]), + res.register + )); + } + return frag; } diff --git a/compiler/component/memory/variable.js b/compiler/component/memory/variable.js index 731bf4e..e0e8844 100644 --- a/compiler/component/memory/variable.js +++ b/compiler/component/memory/variable.js @@ -5,6 +5,7 @@ const TypeRef = require('../typeRef.js'); const Value = require('./value.js'); const Probability = require('./probability.js'); +const { SyntaxNode } = require('bnf-parser'); @@ -145,7 +146,23 @@ class Variable extends Value { return out; } - if (this.type.type.typeSystem == 'linear') { + if (this.type.native) { + if (this.type.lent) { + let loadID = new LLVM.ID(); + let loadType = out.register.type.duplicate().offsetPointer(-1); + out.preamble.append(new LLVM.Set( + new LLVM.Name(loadID, false, ref), + new LLVM.Load(loadType, out.register.name, ref), + ref + )); + + out.type = loadType; + out.register = new LLVM.Argument( + loadType, + new LLVM.Name(loadID.reference(), false, ref), + ref); + } + } else { if (this.type.lent) { return { error: true, @@ -179,24 +196,51 @@ class Variable extends Value { /** * Updated the variable to a new value * @param {LLVM.Argument} register - * @param {Boolean} force apply the update even if the value is borrowed + * @param {Boolean} force the update to apply with no execution taking place * @param {*} ref * @returns {Error?} */ markUpdated (register, force = false, ref) { if (!force) { - if (this.type.lent) { + if (this.type.constant) { return { error: true, - msg: "Cannot overwite a lent value", + msg: "Cannot change a constant value", ref }; - } else if (this.type.constant) { - return { - error: true, - msg: "Cannot overwite a constant value", + } + + if (this.type.lent) { + let res = this.cleanup(ref); + if (res.error) { + return res; + } + let frag = res; + + let target = register; + if (register.pointer > 0) { + let frag = res; + let loadID = new LLVM.ID(); + let loadType = register.type.duplicate().offsetPointer(-1); + frag.append(new LLVM.Set( + new LLVM.Name(loadID, false, ref), + new LLVM.Load(loadType, register, ref), + ref + )); + + target = new LLVM.Argument( + loadType, + new LLVM.Name(loadID.reference(), false, ref), + ref); + } + + frag.append(new LLVM.Store( + this.store, + target, ref - }; + )); + + return frag; } } @@ -205,7 +249,7 @@ class Variable extends Value { this.hasUpdated = true; this.store = register; - return true; + return new LLVM.Fragment(); } /** @@ -215,6 +259,10 @@ class Variable extends Value { * @returns {Object[Variable, LLVM.Fragment]|Error} */ access (accessor, ref) { + if (accessor instanceof SyntaxNode) { + throw new Error("Unexpected syntax node"); + } + let preamble = new LLVM.Fragment(); // Resolve any probabilities @@ -224,11 +272,11 @@ class Variable extends Value { } preamble.merge(res.preamble); - if (accessor.tokens != undefined) { - throw new Error("Invalid variable accessor"); - } + // if (accessor.tokens != undefined) { + // throw new Error("Invalid variable accessor"); + // } - // Automatically decompoase the value if needed + // Automatically decompose the value if needed if (!this.isDecomposed) { let res = this.decompose(ref); if (res.error) { @@ -303,7 +351,7 @@ class Variable extends Value { let type = this.type.duplicate(); type.lent = true; - if (this.type.type.typeSystem == "normal") { + if (!this.type.lent && this.type.native) { let ptr = new LLVM.ID(); preamble.append(new LLVM.Set( @@ -349,37 +397,10 @@ class Variable extends Value { } cloneValue (ref) { - // Resolve to composed/probability state - let out = this.resolve(ref, false); - if (out.error) { - return out; - } - - if ( - this.type.type.typeSystem == "normal" || - !this.type.type.cloneInstance - ) { - return { - preamble: new LLVM.Fragment(), - instruction: this.store, - type: this.type.duplicate() - }; - } - - this.store = out.register; - let preamble = out.preamble; - - // Clone the register - let clone = this.type.type.cloneInstance(out.register, ref); - preamble.merge(clone.preamble); - - let type = this.type.duplicate(); - type.lent = false; - return { - preamble: preamble, - instruction: clone.instruction, - type: type + error: true, + msg: "Old code path hit", + ref: ref }; } @@ -455,7 +476,11 @@ class Variable extends Value { let store = res.register; let isLinear = elm[1].type.type.typeSystem == "linear"; if (isLinear) { - let type = elm[1].type.duplicate().toLLVM(ref, true); + let type = elm[1].type.toLLVM(ref); + if (!elm[1].type.native) { + type.offsetPointer(-1); + } + let id = new LLVM.ID(ref); frag.append(new LLVM.Set( new LLVM.Name(id, false, ref), @@ -703,6 +728,111 @@ class Variable extends Value { + induceType(type, register, ref) { + if (type.type.size == 0) { + this.type = type; + this.store = undefined; + this.probability = undefined; + this.hasUpdated = false; + return new LLVM.Latent(new LLVM.Fragment()); + } + + let id = new LLVM.ID(); + let frag = new LLVM.Fragment(); + + frag.append(new LLVM.Set( + new LLVM.Name(id, false, ref), + new LLVM.Bitcast( + type.toLLVM(ref, false, true), + register + ) + )); + + if (type.type.typeSystem == "normal") { + let load = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(load, false), + new LLVM.Load(type.toLLVM(), new LLVM.Name(id.reference())) + )); + id = load; + } + + let latent = new LLVM.Latent( + frag + ); + + this.probability = new Probability( + latent, + new LLVM.Argument(type.toLLVM(), new LLVM.Name(id.reference(), false, ref)) + ); + this.type = type; + + return latent; + } + + // Reverts the behaviour of induceType + deduceType(type, register, ref) { + + // There is no value to be updated + if (this.isUndefined()) { + this.type = type; + return new LLVM.Latent(new LLVM.Fragment()); + } + + // Update the mode of the either type to this type + if (this.type.size == 0) { + throw new Error("Unimplemented"); + } + + // Read the current value + let val = this.read(ref); + if (val.error) { + return val; + } + + // Transform the location to the correct pointer type + let id = new LLVM.ID(); + let frag = new LLVM.Fragment(); + frag.append(new LLVM.Set( + new LLVM.Name(id, false, ref), + new LLVM.Bitcast( + val.type.toLLVM(ref, false, true), + register + ) + )); + + // Load the struct into memory + if (this.type.type.typeSystem != "normal") { + let load = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(load, false), + new LLVM.Load(type.toLLVM(), new LLVM.Name(id.reference())) + )); + id = load; + } + + // Store the value into the correct address + frag.append(new LLVM.Store( + new LLVM.Argument(this.type.toLLVM(), id.reference(), ref), + val + )); + + let latent = new LLVM.Latent( + frag + ); + + this.probability = new Probability( + latent, + register + ); + this.type = type; + + return latent; + } + + + + clone () { let out = new Variable(this.type, this.name, this.ref); @@ -740,44 +870,49 @@ class Variable extends Value { frag.merge(res.preamble); } else { // Run destruct behaviour - let res = this.resolveProbability(ref, true); - if (res !== null) { - return res; - } - - if ( - this.type.type.meta == "CLASS" && - !this.isUndefined(ref) - ) { - return { - error: true, - msg: `Variable "${this.name}" is still defined. All classes must be consumed`, - ref: ref - }; + // The value has not been consumed and will fall out of scope + if (!this.isUndefined(ref)) { + let del = this.type.type.getDestructor(); + if (del) { + let res = this.read(ref); + if (res.error) { + return res; + } + + frag.merge(res.preamble); + frag.append(new LLVM.Call( + new LLVM.Type("void", 0, ref), + new LLVM.Name(del.represent, true, ref), + [res.register] + )); + } else { + if (this.type.type instanceof Structure) { + let names = this.type.type.terms + .filter(x => x.typeRef.type instanceof Structure) + .map(x => x.name); + + for (let name of names) { + let res = this.access(name, ref); + if (res.error) { + return res; + } + frag.append(res.preamble); + + res = res.variable.cleanup(ref); + if (res.error) { + return res; + } + frag.append(res); + } + + this.makeUndefined(ref); + } + } } } return frag; } - - delete (ref) { - if (this.isUndefined()) { - return { - error: true, - msg: "Cannot delete an already undefined value", - ref: ref - }; - } else if (this.type.constant) { - return { - error: true, - msg: "Cannot delete a constant value", - ref: ref - }; - } - - this.makeUndefined(ref); - return new LLVM.Fragment(); - } } diff --git a/compiler/component/project.js b/compiler/component/project.js index 468034f..00f1178 100644 --- a/compiler/component/project.js +++ b/compiler/component/project.js @@ -4,7 +4,7 @@ const path = require('path'); const { Generator_ID } = require('./generate.js'); const LLVM = require('./../middle/llvm.js'); -const Primative = require('../primative/main.js'); +const Primitive = require('../primative/main.js'); const File = require('./file.js'); @@ -20,14 +20,20 @@ class Project { caching: config.caching === undefined ? true : config.caching }; - Primative.Generate(this); - this.exports = []; this.error = false; this.includes = []; - this.import (path.resolve(__dirname, "../../std/uniview.uv"), false); + this.primitives = []; + + // Import the main file + Primitive.Generate(this); // self imports + this.primitives.push(this.files[0]); + + // Import the Uniview language internals + this.import(path.resolve(__dirname, "../../std/uniview.uv"), false); + this.primitives.push(this.files[1]); } import (path, entry = false, relation = this.rootPath) { @@ -92,22 +98,21 @@ class Project { // Shorten the filepath for better logging console.info(" Including:", path.relative(this.rootPath, filename)); - this.includes.push([ - type, - filename - ]); + this.includes.push({ + type: type, + path: filename + }); } hasIncluded (filename) { return this.includes - .map(x => x[1]) .includes(filename); } /** * Returns the primative library */ - getPrimative () { - return this.files[0]; + getPrimatives () { + return this.primitives; } /** diff --git a/compiler/component/resolve.js b/compiler/component/resolve.js new file mode 100644 index 0000000..945c0da --- /dev/null +++ b/compiler/component/resolve.js @@ -0,0 +1,102 @@ +const { SyntaxNode } = require("bnf-parser"); + +function ResolveAccess(node, ctx) { + let constant = false; + let lent = false; + let access = []; + + // Strip out the data_type lent status if present + switch (node.type) { + case "data_type": + if (node.value[0].value == "@") { + constant = false; + lent = true; + } else if (node.value[0].value == "$") { + constant = true; + lent = true; + } + + access = [ + node.value[1], + ...node.value[2].value + ]; + + break; + case "access": + access = node.value; + break; + default: + throw new Error(`Unexpected syntax node with type "${node.type}"`); + } + + // Resolve any templates in the access + access = access.map(x => { + switch (x.type) { + case "name": + case "access_static": + return x; + case "access_template": + return ResolveTemplate_Argument(x, ctx); + case "access_dynamic": + this.throw( + `Error: Dynamic access should not be present in a data type`, + node.ref.start, node.ref.end + ); + return null; + default: + throw new Error(`Unexpected access type ${x.type}`); + } + }); + + // If there where any failures + // Return total failure + if (access.includes(null)) { + return null; + } + + return { + access, + constant, + lent + }; +} + +function ResolveTemplate_Argument (node, ctx) { + let access = node.value.map(arg => { + switch (arg.type) { + case "data_type": + var type = ctx.getType(arg); + if (type === null) { + this.throw( + `Error: Unknown data type ${arg.flat()}`, + arg.ref.start, arg.ref.end + ); + return null; + } + + return type; + case "constant": + default: + this.throw( + `Error: ${arg.type} are currently unsupported in template arguments`, + arg.ref.start, arg.ref.end + ); + return null; + } + }); + + if (access.includes(null)) { + return null; + } + + return new SyntaxNode( + node.type, + access, + node.ref.clone() + ); +} + + +module.exports = { + ResolveAccess +}; \ No newline at end of file diff --git a/compiler/component/struct.js b/compiler/component/struct.js index 39750b4..b03ae62 100644 --- a/compiler/component/struct.js +++ b/compiler/component/struct.js @@ -1,5 +1,5 @@ const LLVM = require('./../middle/llvm.js'); -const Flattern = require('../parser/flattern.js'); +const Flattern = require('../parser/flatten.js'); const TypeDef = require('./typedef.js'); const TypeRef = require('./typeRef.js'); @@ -13,14 +13,25 @@ class Struct_Term { this.name = name; this.typeRef = typeRef; this.declared = ref; - this.size = typeRef.type.size; - this.typeSystem = "linear"; + this.size = -1; this.ir = new LLVM.Fragment(); } + getSize () { + if (this.size == -1) { + this.size = this.typeRef.type.getSize(); + } + + return this.size; + } + toLLVM() { - return this.typeRef.toLLVM(this.declared, true); + let type = this.typeRef.toLLVM(this.declared); + if (!this.typeRef.native) { + type.offsetPointer(-1); + } + return type; } } @@ -30,6 +41,13 @@ class Structure extends TypeDef { super(ctx, ast, external); this.terms = []; this.linked = false; + this.size = -1; + this.alignment = 0; + + this.nestedCloner = null; + + this.defaultImpl = null; + this.impls = []; } /** @@ -43,7 +61,7 @@ class Structure extends TypeDef { if (typeof(name) == "number") { found = i < this.terms.length; i = name; - } else{ + } else { for (; i x.trait.name == "Drop") + .map(x => x.names["drop"].instances[0]); + + if (res.length == 1) { + return res[0]; + } + + return null; + } + + getCloner () { + let res = this.impls + .filter(x => x.trait.name == "Clone") + .map(x => x.names["clone"].instances[0]); + + if (res.length == 1) { + return res[0]; + } + + return null; + } + + hasNestedCloner() { + if (this.nestedCloner !== null) { + // Do nothing value already cached + } else if (this.getCloner() !== null) { + this.nestedCloner = true; + } else { + this.nestedCloner = this.terms + .map(x => x.typeRef.type.hasNestedCloner()) + .reduce((prev, curr) => prev || curr, false); + } + + return this.secondHandClone; + } + indexOfTerm (name) { for (let i=0; i x.trait == impl.trait).length > 0) { + this.ctx.getFile().throw( + `Error: Struct ${this.name} already has an implementation for trait ${impl.trait.name}, however a new one is attempting to be assigned`, + this.defaultImpl.ref, + impl.ref + ); + } + + this.impls.push(impl); + } + } + compile () { let types = []; for (let name in this.terms) { @@ -255,66 +341,28 @@ class Structure extends TypeDef { /** * * @param {LLVM.Argument} argument + * @param {LLVM.Argument} to */ - cloneInstance(argument, ref) { - let preamble = new LLVM.Fragment(); - - let type = new TypeRef(1, this); - - let storeID = new LLVM.ID(); - preamble.append(new LLVM.Set( - new LLVM.Name(storeID, false), - new LLVM.Alloc(type.toLLVM()) - )); - let instruction = new LLVM.Argument( - type.toLLVM(), - new LLVM.Name(storeID.reference(), false) - ); + cloneInstance(argument, to, ref) { + throw new Error("Old code path"); + } - let size = this.sizeof(ref); - preamble.merge(size.preamble); - let fromID = new LLVM.ID(); - preamble.append(new LLVM.Set( - new LLVM.Name(fromID, false), - new LLVM.Bitcast( - new LLVM.Type("i8", 1), - argument - ) - )); - let toID = new LLVM.ID(); - preamble.append(new LLVM.Set( - new LLVM.Name(toID, false), - new LLVM.Bitcast( - new LLVM.Type("i8", 1), - instruction - ) - )); + getSize () { + if (this.size == -1) { + this.alignment = Math.max.apply(null, + this.terms + .map(x => x.typeRef.type) + .map(x => x.native ? x.size : x.alignment) + ); - preamble.append(new LLVM.Call( - new LLVM.Type("void", 0), - new LLVM.Name("llvm.memcpy.p0i8.p0i8.i64", true), - [ - new LLVM.Argument( - new LLVM.Type("i8", 1), - new LLVM.Name(toID.reference(), false) - ), - new LLVM.Argument( - new LLVM.Type("i8", 1), - new LLVM.Name(fromID.reference(), false) - ), - size.instruction, - new LLVM.Argument( - new LLVM.Type('i1', 0), - new LLVM.Constant("0") - ) - ] - )); + this.size = this.terms + .map(x => x.getSize()) + .map(x => Math.ceil(x/this.alignment)*this.alignment) + .reduce((tally, curr) => tally + curr, 0); + } - return { - preamble, - instruction - }; + return this.size; } } diff --git a/compiler/component/trait.js b/compiler/component/trait.js new file mode 100644 index 0000000..8ed0cdd --- /dev/null +++ b/compiler/component/trait.js @@ -0,0 +1,145 @@ +const { SyntaxNode } = require('bnf-parser'); + +const LLVM = require('../middle/llvm.js'); +const Flattern = require('../parser/flatten.js'); +const TypeRef = require('./typeRef.js'); + +const Function = require('./function.js'); + +const Primitive = { + types: require('./../primative/types.js') +}; + + +class Trait { + constructor (ctx, ast) { + this.ctx = ctx; + this.name = ast.value[0].value; + this.ast = ast; + this.ref = ast.ref.start; + + this.names = {}; + + this.impls = []; + } + + getFunction(access, signature, template) { + for (let imp of this.impls) { + let res = imp.getFunction(access, signature, template); + if (res) { + return res; + } + } + + return null; + } + + getFile() { + return this.ctx.getFile(); + } + + getType(access, stack) { + if ( access.length == 0 ) { + return new TypeRef(this); + } + + if (access instanceof SyntaxNode && access.value[1].value == "Self") { + if (access.value[2].value.length != 0) { + return null; + } + + return new TypeRef( + this, + ["@", "$"].includes(access.value[0].value), + access.value[0].value == "$" + ); + } + + return this.ctx.getType(access, stack); + } + + bindImplementation(impl) { + for (let name in this.names) { + this.names[name].ctx = impl; // swap the context (ensures Self links properly) + this.names[name].relink(); + + if (!impl.names[name]) { + impl.getFile().throw( + `Error: Namespace "${name}" not present in implementation of trait ${this.name} for ${impl.struct.name}`, + impl.ref, + impl.endRef + ); + this.ctx.getFile().throw( + ` Above implementation is missing function "${name}" from the below snippet`, + this.ref, + this.names[name].ref + ); + } else { + this.names[name].ensureEquivalence(impl.names[name]); + } + } + + for (let name in impl.names) { + if (!this.names[name]) { + impl.getFile().throw( + `Error: Implementation has extra namespace "${name}" present in trait ${this.name} implementated for ${impl.struct.name}`, + impl.ref, + impl.names[name].ref + ); + } + } + + this.impls.push(impl); + } + + parse () {} + + link () { + if (this.linked) { + return; + } + + for (let node of this.ast.value[2].value) { + switch (node.type) { + case "comment": + break; + case "function": + let space = new Function(this, node, false, false); + + if (!this.names[space.name]) { + this.names[space.name] = space; + } else if ( !this.names[space.name].merge || + !this.names[space.name].merge(space) ) { + + this.getFile().throw( + `Name collision between functions with the same name "${space.name}" and identical signatures`, + this.names[space.name].ref, + space.ref + ); + this.project.markError(); + return false; + } + + break; + default: + throw new Error(`Unexpected attribute ${node.type}`); + } + } + + // Link all successful functions + for (let name in this.names) { + this.names[name].link(); + } + + this.linked = true; + } + + compile () { + } + + toLLVM() { + return new LLVM.Fragment(); + } +} + +module.exports = Trait; \ No newline at end of file diff --git a/compiler/component/typeRef.js b/compiler/component/typeRef.js index 3cea62c..851506f 100644 --- a/compiler/component/typeRef.js +++ b/compiler/component/typeRef.js @@ -1,4 +1,3 @@ -const Flattern = require("../parser/flattern.js"); const LLVM = { Type: require('./../middle/type.js') }; @@ -9,11 +8,12 @@ class TypeRef { * @param {Number} pointerLvl * @param {Type} type */ - constructor (pointerLvl, type, lent = false, constant = false) { - this.pointer = pointerLvl; + constructor (type, lent = false, constant = false, local = false) { + this.native = type.native; this.type = type; this.lent = lent; this.constant = constant; + this.local = local; } getName () { @@ -29,26 +29,34 @@ class TypeRef { } /** - * + * Do the TypeRefs match approximately * @param {TypeRef} other + * @returns */ - match (other) { + weakMatch(other) { if (!(other instanceof TypeRef)) { return false; } - return this.pointer == other.pointer && - this.type == other.type && - this.lent == other.lent; + return this.type === other.type; } /** - * Increases/decreases the pointer reference level - * @param {Number} inc + * Do the TypeRefs match including lent status + * @param {TypeRef} other */ - offsetPointer (inc) { - this.pointer += inc; - return this; + match (other) { + return this.weakMatch(other) && this.lent == other.lent && + this.constant == other.constant; + // ignore local as they don't impact use for computation + } + + matchApprox (other) { + if (!(other instanceof TypeRef)) { + return false; + } + + return this.type == other.type; } @@ -56,7 +64,7 @@ class TypeRef { * Creates a clone of this reference */ duplicate () { - return new TypeRef(this.pointer, this.type, this.lent, this.constant); + return new TypeRef(this.type, this.lent, this.constant, this.local); } @@ -64,15 +72,22 @@ class TypeRef { * @returns {String} */ toString () { - return ( this.lent ? "@" : "$" ) + this.type.name; + return ( this.lent ? (this.constant ? "$" : "@") : "" ) + this.type.name; } toLLVM (ref = null, flat = false, pointer = false) { + if (flat) { + console.warn("Flat used", new Error().stack); + } + if (pointer) { + console.warn("Pointer used", new Error().stack); + } + return new LLVM.Type( this.type.represent, flat ? 0 : pointer ? 1 : - this.lent || this.type.typeSystem == "linear" ? 1 : 0, + this.lent || !this.native ? 1 : 0, ref ); } diff --git a/compiler/component/typedef.js b/compiler/component/typedef.js index 9551a67..7aa8d99 100644 --- a/compiler/component/typedef.js +++ b/compiler/component/typedef.js @@ -1,5 +1,6 @@ const { Generator_ID } = require('./generate.js'); const LLVM = require('./../middle/llvm.js'); +const TypeRef = require('./typeRef.js'); let typeIDGen = new Generator_ID(); class TypeDef { @@ -9,13 +10,14 @@ class TypeDef { this.ref = ast.ref.start; this.external = external; - this.primative = false; + this.native = false; this.linked = false; this.id = typeIDGen.next(); this.represent = "unknown"; this.name = "unknown"; + this.alignment = 0; this.size = 0; this.typeSystem = 'linear'; @@ -31,6 +33,10 @@ class TypeDef { return null; } + getSize () { + return this.size; + } + getFile() { return this.ctx.getFile(); } @@ -42,12 +48,19 @@ class TypeDef { return null; } + getType(access) { + return access.length == 0 ? new TypeRef(this) : null; + } + getDestructor() { return false; } getCloner() { return false; } + hasNestedCloner() { + return false; + } parse () { @@ -79,7 +92,7 @@ class TypeDef { preamble: new LLVM.Fragment(), instruction: new LLVM.Argument( new LLVM.Type("i64"), - new LLVM.Constant(this.size), + new LLVM.Constant(this.getSize()), ref ) }; diff --git a/compiler/helper/error.js b/compiler/helper/error.js index 633d8a6..71456bb 100644 --- a/compiler/helper/error.js +++ b/compiler/helper/error.js @@ -6,44 +6,51 @@ function FixStringLength (string, count, filler = " ") { function CodeSection (string, refStart, refEnd) { string = string.replace(/\t/g, " "); + if ( refStart.index == refEnd.index ) { + refEnd.col = string.split('\n')[refEnd.line-1].length; + } + let offset = refStart.line; let digits = refEnd.line.toString().length; string = string.split('\n') .slice(refStart.line-1, refEnd.line) - .map( (val, i) => ` ${FixStringLength((i+offset).toString(), digits)} | ${val}` ); + .map((val, i) => [(i+offset).toString(), val]); + + let indent = Math.min( + ...string + .map(elm => elm[1].search(/[^ ]/g)) + .map(elm => elm == -1 ? 0 : elm), + refStart.col + ); + let maxLen = Math.max(0, ...string.map(elm => elm[1].length)); if (string.length > 5) { string = [ - ...string.slice(0, 2), - ` ${FixStringLength("*", digits)} | `, - ...string.slice(-2) + ...string + .slice(0, 2) + .map(elm => ` ${FixStringLength(elm[0], digits)} │ ${elm[1].slice(indent)}`), + ` ${FixStringLength("*", digits)} │${"·".repeat(maxLen-indent+1)}`, + ...string + .slice(-2) + .map(elm => ` ${FixStringLength(elm[0], digits)} │ ${elm[1].slice(indent)}`) ]; + } else { + string = string.map(elm => ` ${FixStringLength(elm[0], digits)} │ ${elm[1].slice(indent)}`); } if (refStart.line == refEnd.line) { string.push( - ` ${FixStringLength("*", digits)} | ` + - " ".repeat(refStart.col) + + ` ${FixStringLength("*", digits)} │` + + " ".repeat(refStart.col-indent) + "^".repeat(refEnd.col-refStart.col) ); } - // let highlightA = " ".repeat(digits+2) + "|" + - // " ".repeat(refStart.col) + - // "^".repeat(string[0].length); - - // let highlightB = " ".repeat(digits+2) + "|" + - // "^".repeat(refEnd.col); - - // return [ - // string[0], - // highlightA, - // ...string.slice(1), - // highlightB - // ].join("\n"); - - return string.join('\n') + `\n${refStart.toString()} -> ${refEnd.toString()}`; + return '─'.repeat(digits+2) + "┬" + "─".repeat(maxLen-indent+2) + "\n" + + string.join('\n') + "\n" + + '─'.repeat(digits+2) + "┴" + "─".repeat(maxLen-indent+2) + + `\n ${refStart.toString()} -> ${refEnd.toString()}`; } module.exports = { diff --git a/compiler/middle/call.js b/compiler/middle/call.js index 472feda..ca6c37f 100644 --- a/compiler/middle/call.js +++ b/compiler/middle/call.js @@ -21,7 +21,7 @@ class Call extends Instruction { return super.flattern( `call ${this.type.flattern()} ` + `${this.name.flattern()} ` + - `(${this.args.map(x=>x.flattern()).join(",")})`, + `(${this.args.map(x=>x.flattern()).join(", ")})`, indent); } } diff --git a/compiler/middle/instruction.js b/compiler/middle/instruction.js index 470f38e..3592727 100644 --- a/compiler/middle/instruction.js +++ b/compiler/middle/instruction.js @@ -8,12 +8,7 @@ class Instruction { } flattern(str = "", indent = 0) { - let out = ""; - for (let i=0; i 0) { out += ` {\n` + `${this.stmts - .map(x => x.flattern(indent+2)) + .map(x => x.flattern(indent+4)) .filter(x => x.length !== 0) .join("\n")}` + `\n}`; diff --git a/compiler/middle/raw.js b/compiler/middle/raw.js index e66f99f..9ef0c8e 100644 --- a/compiler/middle/raw.js +++ b/compiler/middle/raw.js @@ -7,8 +7,12 @@ class Raw { this.text = text; } - flattern() { - return this.text; + assign_ID () { + return; + } + + flattern(indent) { + return " ".repeat(indent) + this.text; } } diff --git a/compiler/middle/switch.js b/compiler/middle/switch.js new file mode 100644 index 0000000..e061492 --- /dev/null +++ b/compiler/middle/switch.js @@ -0,0 +1,32 @@ +const Instruction = require("./instruction.js"); +const LLVM = require('./llvm.js'); + + +class Select extends Instruction { + /** + * + * @param {LLVM.Argument} condition + * @param {LLVM.Label} baseline + * @param {Array[LLVM.Constant, LLVM.Label]} opts + * @param {BNF_Reference?} ref + */ + constructor(condition, baseline, opts, ref) { + super (ref); + this.condition = condition; + this.baseline = baseline; + this.opts = opts; + } + + flattern (indent) { + return super.flattern( + `switch ` + + this.condition.flattern(0) + `, ` + + this.baseline.flattern(0) + " [" + + this.opts.map( x => x[0].flattern(0) + ", " + x[1].flattern(0) ).join(" ") + + "]", + indent); + } +} + + +module.exports = Select; \ No newline at end of file diff --git a/compiler/parser/build.js b/compiler/parser/build.js index c50aee0..ceb3fd3 100644 --- a/compiler/parser/build.js +++ b/compiler/parser/build.js @@ -1,9 +1,19 @@ -const BNF = require('bnf-parser'); +console.info("Building Syntax..."); + +const { BNF, Compile, ParseError } = require('bnf-parser'); const fs = require('fs'); let data = fs.readFileSync(__dirname+'/syntax.bnf', 'utf8'); -let syntax = BNF.Build(data, 'syntax.bnf'); +let syntaxTree = BNF.parse(data, false, 'program'); + +if (syntaxTree instanceof ParseError) { + console.error(syntaxTree.toString()); + process.exit(1); +} + +let gen = Compile(syntaxTree); +console.log(gen.terms.keys()); -fs.writeFileSync(__dirname+'/syntax.json', JSON.stringify(syntax)); +fs.writeFileSync(__dirname+'/syntax.json', JSON.stringify(gen.serialize())); console.info('Built Syntax'); \ No newline at end of file diff --git a/compiler/parser/expr.js b/compiler/parser/expr.js index 8a2f560..d73d05e 100644 --- a/compiler/parser/expr.js +++ b/compiler/parser/expr.js @@ -1,5 +1,4 @@ -const BNF = require('bnf-parser'); -const BNF_SytaxNode = BNF.types.BNF_SyntaxNode; +const { SyntaxNode, ReferenceRange } = require('bnf-parser'); @@ -8,22 +7,27 @@ let precedence = { expr_compare: 2, expr_bool: 4, - expr_mul : 0, - expr_div : 0, - expr_mod : 0, - expr_add : 1, - expr_sub : 1, + expr_invert: 0, + expr_lend: 0, + expr_share: 0, + expr_mul : 1, + expr_div : 1, + expr_mod : 1, + expr_add : 2, + expr_sub : 2, - expr_lt : 2, - expr_lt_eq : 2, - expr_gt : 2, - expr_gt_eq : 2, - expr_eq : 3, + expr_lt : 3, + expr_lt_eq : 3, + expr_gt : 3, + expr_gt_eq : 3, + expr_eq : 4, - expr_and : 4, - expr_or : 5, + expr_and : 5, + expr_or : 6, - expr_brackets: 6 + expr_comma: 7, + + expr_brackets: 8 }; function GetPrecedence (a, b) { @@ -36,8 +40,8 @@ function GetPrecedence (a, b) { if (A == undefined && B == undefined) { return 0; } else if (A == B) { - let A = precedence[a.tokens[0].type]; - let B = precedence[b.tokens[0].type]; + let A = precedence[a.value[0].type]; + let B = precedence[b.value[0].type]; if (A == undefined && B == undefined) { return 0; @@ -58,107 +62,103 @@ function GetPrecedence (a, b) { } +const OPERATION_DICT = { + // expr_arithmetic + "+": { + base: "expr_arithmetic", + sub: "expr_add" + }, + "-": { + base: "expr_arithmetic", + sub: "expr_sub" + }, + "*": { + base: "expr_arithmetic", + sub: "expr_mul" + }, + "/": { + base: "expr_arithmetic", + sub: "expr_div" + }, + "%": { + base: "expr_arithmetic", + sub: "expr_mod" + }, + + // expr_compare + "==": { + base: "expr_compare", + sub: "expr_eq", + }, + "!=": { + base: "expr_compare", + sub: "expr_neq" + }, + "<": { + base: "expr_compare", + sub: "expr_lt" + }, + ">": { + base: "expr_compare", + sub: "expr_gt" + }, + "<=": { + base: "expr_compare", + sub: "expr_lt_eq" + }, + ">=": { + base: "expr_compare", + sub: "expr_gt_eq" + }, + + // expr_bool + "&&": { + base: "expr_bool", + sub: "expr_and" + }, + "||": { + base: "expr_bool", + sub: "expr_or" + } +}; + /** * - * @param {BNF_SytaxNode} lhs - * @param {BNF_SytaxNode} opperation - * @param {BNF_SytaxNode} rhs - * @returns {BNF_SytaxNode} + * @param {SyntaxNode} lhs + * @param {SyntaxNode} opperation + * @param {SyntaxNode} rhs + * @returns {SyntaxNode} */ -function Construct_Operation(lhs, opperation, rhs) { - let base; - let sub; - switch (opperation.tokens) { - // expr_arithmetic - case "+": - base = "expr_arithmetic"; - sub = "expr_add"; - break; - case "-": - base = "expr_arithmetic"; - sub = "expr_sub"; - break; - case "*": - base = "expr_arithmetic"; - sub = "expr_mul"; - break; - case "/": - base = "expr_arithmetic"; - sub = "expr_div"; - break; - case "%": - base = "expr_arithmetic"; - sub = "expr_mod"; - break; - - // expr_compare - case "==": - base = "expr_compare"; - sub = "expr_eq"; - break; - case "!=": - base = "expr_compare"; - sub = "expr_neq"; - break; - case "<": - base = "expr_compare"; - sub = "expr_lt"; - break; - case ">": - base = "expr_compare"; - sub = "expr_gt"; - break; - case "<=": - base = "expr_compare"; - sub = "expr_lt_eq"; - break; - case ">=": - base = "expr_compare"; - sub = "expr_gt_eq"; - break; - - // expr_bool - case "&&": - base = "expr_bool"; - sub = "expr_and"; - break; - case "||": - base = "expr_bool"; - sub = "expr_or"; - break; - - default: - throw new Error(`Unexpected expression opperation ${opperation.tokens}`); +function Construct_Operation(lhs, operation, rhs) { + let mode = OPERATION_DICT[operation.value]; + if (!mode) { + throw new Error(`Unexpected expression operation ${operation.value}`); } - let node = new BNF_SytaxNode( - base, + let node = new SyntaxNode( + mode.base, [ - new BNF_SytaxNode( - sub, + new SyntaxNode( + mode.sub, [], - 0, - opperation.ref.start, - opperation.ref.end + operation.ref.clone() ) ], - 0, - lhs.ref.start, - rhs.ref.end + new ReferenceRange(lhs.ref.start.clone(), rhs.ref.end.clone()) ); let p = GetPrecedence(lhs, node); if (p == 1) { - node.tokens[0].tokens = [ - lhs.tokens[0].tokens[1], + node.value[0].value = [ + lhs.value[0].value[1], rhs ]; - lhs.tokens[0].tokens[1] = node; + lhs.value[0].value[1] = node; return lhs; } else { - node.tokens[0].tokens = [ + node.value[0].value = [ lhs, rhs ]; @@ -171,14 +171,14 @@ function Construct_Operation(lhs, opperation, rhs) { /** * - * @param {BNF_SytaxNode[]} queue + * @param {SyntaxNode[]} queue * @returns {BNF_SyntaxNode} */ function ApplyPrecedence (queue) { let root = queue[0]; for (let i=1; i { + switch (x.type) { + case "data_type_lending?": + case "name": + return x.flat(); + case "access_static": + return "." + x.flat(); + case "access_dynamic": + return `[${x.flat()}]`; + case "access_template": + return `#[${ x.value.map(AccessToString).join(", ") }]`; + case "(...)*": + return AccessToString(x); + default: + throw new Error(`Unexpected access syntax type ${x.type}`); + } + }).join(""); +} + + +module.exports = { + AccessToString +} \ No newline at end of file diff --git a/compiler/parser/flattern.js b/compiler/parser/flattern.js deleted file mode 100644 index 0ceeb98..0000000 --- a/compiler/parser/flattern.js +++ /dev/null @@ -1,67 +0,0 @@ -function DataTypeList(node) { - if (node.type == "constant") { - return [ "lit", node.tokens[0].tokens ]; - } - - let out = [ - [ node.tokens[0], node.tokens[1].tokens ] - ]; - for (let access of node.tokens[2]){ - if (access.tokens[0] == "[]") { - out.push([ - "[]", - access.tokens[1].tokens.map( x => DataTypeList(x) ) - ]); - } else { - out.push([access.tokens[0], access.tokens[1].tokens]); - } - } - - return out; -} - -function DataTypeStr (node) { - if (node.type == "constant") { - return node.tokens[0].tokens; - } - - let str = "@".repeat(node.tokens[0]) + node.tokens[1].tokens; - if (node.tokens[2]) { - for (let access of node.tokens[2]){ - if (access.tokens[0] == "[]") { - str += `#[${access.tokens[1].tokens.map( x => DataTypeStr(x) ).join(", ")}]`; - } else { - str += access.tokens[0] + access.tokens[1].tokens; - } - } - } - - return str; -} - - - -let VariableList = DataTypeList; -function VariableStr (node) { - if (node.type == "constant") { - return node.tokens[0].tokens; - } - - let str = "$".repeat(node.tokens[0]) + node.tokens[1].tokens; - if (node.tokens[2]) { - for (let access of node.tokens[2]){ - if (access[0] == "[]") { - str += `[${access[1].tokens.map( x => DataTypeStr(x) ).join(", ")}]`; - } else { - str += access[0] + access[1].tokens; - } - } - } - - return str; -} - - -module.exports = { - VariableList, VariableStr, DataTypeList, DataTypeStr -} \ No newline at end of file diff --git a/compiler/parser/parse.js b/compiler/parser/parse.js index 387a6c7..cc1aa2d 100644 --- a/compiler/parser/parse.js +++ b/compiler/parser/parse.js @@ -1,470 +1,514 @@ const { ApplyPrecedence } = require('./expr.js'); -const BNF = require('bnf-parser'); -const fs = require('fs'); +const { CodeSection } = require('../helper/error.js'); -const BNF_SyntaxNode = BNF.types.BNF_SyntaxNode; +const { SyntaxNode, Parser, ParseError, ReferenceRange, Reference } = require('bnf-parser'); +const fs = require('fs'); -const syntax = BNF.types.BNF_Tree.fromJSON( - JSON.parse(fs.readFileSync(__dirname+"/syntax.json", 'utf8')) +const syntax = new Parser( + JSON.parse( + fs.readFileSync(__dirname+"/syntax.json", 'utf8') + ), + "Uniview.bnf" ); function Simplify_Program (node) { - let out = []; - for (let inner of node.tokens[1]) { - out.push(Simplify_Stmt_Top(inner.tokens[0][0])); - } - node.tokens = out; + node.value = node.value[0].value + .map(x => Simplify_Stmt_Top(x.value[0])); - // Remove irrelevant internal data - node.reached = null; return node; } - function Simplify_Stmt_Top (node) { - let inner; - switch (node.tokens[0].type) { - case "comment": - inner = node.tokens[0]; - break; - case "external": - inner = Simplify_External(node.tokens[0]); - break; - case "include": - inner = Simplify_Include(node.tokens[0]); - break; - case "function": - inner = Simplify_Function(node.tokens[0]); - break; - case "library": - inner = Simplify_Library(node.tokens[0]); - break; - case "class": - inner = Simplify_Class(node.tokens[0]); - break; - case "template": - inner = Simplify_Template(node.tokens[0]); - break; - case "struct": - inner = Simplify_Struct(node.tokens[0]); - break; - case "flag_definition": - inner = Simplify_Flag_Definition(node.tokens[0]); - break; - default: - throw new TypeError(`Unexpected top level statement ${node.tokens[0].type}`); + + let func = STMT_TOP_MAP[node.value[0].type]; + if (!func) { + throw new TypeError(`Unexpected top level statement ${node.value[0].type} at ${node.value[0].ref.toString()}`); } + // Apply the correct simplification if present + let inner = func(node.value[0]); + // Remove irrelevant internal data inner.reached = null; + return inner; } - - - -function Simplify_Library (node) { - switch (node.tokens[0].type) { - case "import": - node.tokens = [ Simplify_Library_Import(node.tokens[0]) ]; - break; - case "expose": - node.tokens = [ Simplify_Library_Expose(node.tokens[0]) ]; +/*================================ + Constants +================================*/ +function Simplify_Constant (node) { + switch (node.value[0].type) { + case "hexadecimal": + case "octal": + case "boolean": + case "void": + case "integer": + case "float": + node.value[0].value = node.value[0].flat(); break; - default: - throw new TypeError(`Unexpected library statement ${node.tokens[0].type}`); - } - node.reached = null; - return node; -} -function Simplify_Library_Import (node) { - let out = [null, "*"]; - switch (node.tokens[0].type) { - case "import_direct": - out[1] = Simplify_Name(node.tokens[0].tokens[6][0]); - case "import_as": - out[0] = Simplify_String(node.tokens[0].tokens[2][0]); + case "string": + node.value[0] = Simplify_String(node.value[0]); break; default: - throw new TypeError(`Unexpected library statement ${node.tokens[0].type}`); + throw new TypeError(`Unexpected constant expression ${node.value[0].type}`); } - node.tokens = out; - node.reached = null; return node; } -function Simplify_Library_Expose (node) { - let match = ""; - if (typeof(node.tokens[2][0].tokens) == "string") { - match = "*"; - } else { - match = Simplify_Name(node.tokens[2][0].tokens[0]).tokens; +function Simplify_String(node) { + let str = node.value[0]; + let inner = str.value[0]; + let out = ""; + if (!Array.isArray(inner.value)) { + throw new TypeError("Internal logic failure. Unexpected string"); + } + for (let charNode of inner.value) { + if (charNode.type == "literal") { + out += charNode.value; + } + else { + let esc = charNode.value; + switch (esc[1].value) { + case "b": + out += "\b"; + break; + case "f": + out += "\f"; + break; + case "n": + out += "\n"; + break; + case "r": + out += "\r"; + break; + case "t": + out += "\t"; + break; + case "v": + out += "\v"; + break; + default: out += esc[1].value; + } + } } - node.tokens = match; - node.reached = null; + return new SyntaxNode( "string", out, node.ref ); +} +function Simplify_Integer(node) { + node.value = node.flat(); return node; } -function Simplify_String (node) { - // Merge the segments together - let data = ""; - for (let seg of node.tokens[0].tokens[1]) { - if (typeof(seg.tokens) == "string") { - data += seg.tokens.slice(1); - } else { - data += seg.tokens[0].tokens; - } - } - // Filter wild cards - let out = ""; - for (let i=0; i Simplify_Access_Opt(x.value[0])) ]; - node.reached = null; + return node; } +function Simplify_Access_Opt (node) { + switch (node.type) { + case "access_static": + return Simplify_Access_Static(node); + case "access_dynamic": + return Simplify_Access_Dynamic(node); + case "access_template": + return Simplify_Access_Template(node); + default: + throw new TypeError(`Unexpected accessor type ${node.type}`); + } +} +function Simplify_Access_Static (node) { + node.value = node.flat(); + return node; +} +function Simplify_Access_Dynamic (node) { + node.value = Simplify_Call_Args(node.value[1]).value; + return node; +} -function Simplify_Class (node) { - let out = [ - Simplify_Name(node.tokens[2][0]), - Simplify_Class_Body(node.tokens[7][0]) - ]; - node.tokens = out; - node.reached = null; +function Simplify_Access_Template (node) { + node.value = Simplify_Access_Template_Args(node.value[0]).value; return node; } -function Simplify_Class_Body (node) { - node.tokens = node.tokens[0] - .filter ( x => x.tokens[0].type != "comment") - .map( x => Simplify_Class_Stmt(x.tokens[1][0]).tokens[0] ); - node.reached = null; +function Simplify_Access_Template_Args (node) { + node.value = [ + node.value[0], + ...node.value[1].value + ].map(Simplify_Access_Template_Arg); + return node; } -function Simplify_Class_Stmt (node) { - switch (node.tokens[0].type) { - case "comment": - break; - case "struct_attribute": - node.tokens = [ Simplify_Struct_Attribute(node.tokens[0]) ]; - break; - case "function": - node.tokens = [ Simplify_Function(node.tokens[0]) ]; - break; +function Simplify_Access_Template_Arg (node) { + switch (node.value[0].type) { + case "constant": + return Simplify_Constant(node.value[0]); + case "data_type": + return Simplify_Data_Type(node.value[0]); default: - throw new Error(`Unexpected class statement "${node.tokens[0].type}"`); + throw new Error(`Unknown template argument syntax type ${node.type}`); } - - node.reached = null; - return node; } -function Simplify_Template (node) { - node.tokens = Simplify_Template_Args(node.tokens[2][0]).tokens; - node.reached = null; + + +/*================================ + Variables +================================*/ +function Simplify_Name (node) { + node.value = node.flat(); return node; } -function Simplify_Template_Args (node) { - let out = [ Simplify_Template_Arg(node.tokens[0][0]) ]; - for (let inner of node.tokens[1]) { - out.push( Simplify_Template_Arg(inner.tokens[3][0]) ); - } +function Simplify_Variable (node) { + // Meaningfully identical in terms of AST simplification + return Simplify_Access(node); +} + +function Simplify_Data_Type (node) { + node.value = [ + // Lending status + node.value[0], + + // Name + Simplify_Name(node.value[1]), + + // Access + new SyntaxNode( + "(...)*", + node.value[2].value.map(x => Simplify_Access_Opt(x.value[0])), + node.value[2].ref + ) + ]; - node.tokens = out; - node.reached = null; return node; } -function Simplify_Template_Arg (node) { - switch (node.tokens[0].type) { - case "data_type": - return Simplify_Data_Type(node.tokens[0]); - case "constant": - return Simplify_Constant(node.tokens[0]); - default: - throw new TypeError(`Unexpected data-type type ${node.tokens[0].type}`); - } +function Simplify_Declare (node) { + node.value = [ + // Data Type (if present) + node.value[1].value[0] ? + Simplify_Data_Type(node.value[1].value[0].value[0]) : + new SyntaxNode("blank", "", node.ref.clone()), + + // Name + Simplify_Name(node.value[0]), + + // Value (if present) + node.value[2].value[0] ? + Simplify_Expr(node.value[2].value[0].value[0]) : + new SyntaxNode("blank", "", node.ref.clone()) + ]; + return node; +} +function Simplify_Assign (node) { + node.value = [ + Simplify_Variable (node.value[0]), // target variable + Simplify_Expr (node.value[1]) // value + ]; + return node; } -function Simplify_Flag_Definition (node) { - // TODO + + +/*================================ + Function +================================*/ +function Simplify_Function (node) { + node.value = [ + Simplify_Function_Head(node.value[0]), // head + node.value[1].value == ";" ? + new SyntaxNode('blank', "", node.ref.clone()) : + Simplify_Function_Body(node.value[1]) // body + ]; return node; } +function Simplify_Function_Head (node) { + let emptyReturn = node.value[2].value.length == 0; + + node.value = [ + !emptyReturn ? // Return type + Simplify_Data_Type (node.value[2].value[0].value[0]) : + new SyntaxNode("blank", "", node.ref.clone()), + Simplify_Name (node.value[0]), // Name + Simplify_Func_Args (node.value[1]), // Arguments + [] + ]; + return node; +} +function Simplify_Func_Args (node) { + node.value = node.value[0].value.length == 0 ? [] : + [ + node.value[0].value[0].value[0], + ...node.value[0].value[0].value[1].value.map(x => x.value[0]) + ].map(Simplify_Func_Arg); - -function Simplify_External (node) { - node.tokens = [ - node.tokens[2][0].tokens, // mode - Simplify_External_Body(node.tokens[6][0]).tokens // internal + return node; +} +function Simplify_Func_Arg (node) { + node.value = [ + Simplify_Data_Type(node.value[1]), + Simplify_Name(node.value[0]) ]; - node.reached = null; + return node; } -function Simplify_External_Body (node) { - let out = []; - for (let inner of node.tokens[0]) { - let next = Simplify_External_Term(inner.tokens[0][0]); - if (next) { - out.push(next); - } - } - node.tokens = out; - node.reached = null; +function Simplify_Function_Body (node) { + node.value = node.value[0].value + .map(x => Simplify_Function_Stmt(x.value[0])); + return node; } -function Simplify_External_Term (node) { - let inner = null; - switch (node.tokens[0].type) { - case "function_outline": - inner = Simplify_Function_Outline(node.tokens[0]); - break; - case "function_redirect": - inner = Simplify_Function_Redirect(node.tokens[0]); - break; - case "struct": - inner = Simplify_Struct(node.tokens[0]); - break; - case "type_def": - inner = Simplify_Type_Def(node.tokens[0]); - break; - case "declare": - inner = Simplify_Declare(node.tokens[0]); - break; - case "comment": - break; - default: - throw new TypeError(`Unexpected external statement ${node.tokens[0].type}`); +function Simplify_Function_Stmt (node) { + let func = STMT_MAP[node.value[0].type]; + + if (func instanceof Function) { + return func(node.value[0]); + } else { + throw new TypeError(`Unexpected function statement ${node.value[0].type}`); } +} - return inner; +function Simplify_Function_Outline (node) { + node.value = [ + Simplify_Function_Head(node.value[0]) // head + ]; + return node; } +function Simplify_Function_Redirect (node) { + node.value = [ + new SyntaxNode( + 'function_head', + [ + // Return Type + node.value[2].value[0] ? + Simplify_Data_Type(node.value[2].value[0].value[0]) : + new SyntaxNode('blank', "", node.ref.clone()), -function Simplify_Type_Def (node) { - node.tokens = [ - Simplify_Name(node.tokens[2][0]), // name - Simplify_Integer(node.tokens[6][0]) // size + // Name + Simplify_Name(node.value[3]), + + // Arguments + Simplify_Func_Args(node.value[1]) + ], + node.ref + ) ]; - node.reached = null; + return node; } -function Simplify_Include (node) { - node.tokens = [ - node.tokens[2][0].tokens, // mode - Simplify_String(node.tokens[4][0]).tokens[1] // path +function Simplify_Call (node) { + node.value = [ + // Function Name + Simplify_Access(node.value[0]), + + // Args + Simplify_Call_Body(node.value[1]) ]; - node.reached = null; return node; } +function Simplify_Call_Body (node) { + return node.value[0].value[0] ? + Simplify_Call_Args(node.value[0].value[0]) : + new SyntaxNode('call_args', [], node.ref.clone()); +} +function Simplify_Call_Args (node) { + node.value = [ + node.value[0], + ...node.value[1].value.map(x => x.value[0]) + ].map(Simplify_Expr); + return node; +} +function Simplify_Call_Procedure(node) { + return Simplify_Call(node.value[0]); +} +function Simplify_Return (node) { + node.value = node.value[0].value.map(x => Simplify_Expr(x.value[0])); + return node; +} + + + + + +/*================================ + Structures +================================*/ function Simplify_Struct (node) { - let out = [ - Simplify_Name(node.tokens[2][0]), - Simplify_Struct_Body(node.tokens[6][0]) + node.value = [ + Simplify_Name(node.value[0]), + Simplify_Struct_Body(node.value[1]) ]; - node.tokens = out; - node.reached = null; return node; } function Simplify_Struct_Body (node) { - node.tokens = node.tokens[0] - .filter ( x => x.tokens[0].type != "comment") - .map( x => Simplify_Struct_Stmt(x.tokens[1][0]).tokens[0] ); - node.reached = null; + node.value = node.value[0].value + .map( x => Simplify_Struct_Stmt(x.value[0]) ); return node; } function Simplify_Struct_Stmt (node) { - switch (node.tokens[0].type) { - case "comment": - break; + switch (node.value[0].type) { case "struct_attribute": - node.tokens = [ Simplify_Struct_Attribute(node.tokens[0]) ]; - break; + return Simplify_Struct_Attribute(node.value[0]); default: - throw new Error(`Unexpected structure statement "${node.tokens[0].type}"`); + throw new Error(`Unexpected structure statement "${node.value[0].type}"`); } - - node.reached = null; - return node; } function Simplify_Struct_Attribute (node) { - let out = [ - Simplify_Data_Type(node.tokens[4][0]), - Simplify_Name(node.tokens[0][0]) + node.value = [ + Simplify_Data_Type(node.value[1]), + Simplify_Name(node.value[0]) ]; - node.tokens = out; - node.reached = null; return node; } +function Simplify_Impl (node) { + let hasFor = node.value[1].value[0] != undefined; + let out = [ + Simplify_Data_Type(node.value[0]), + hasFor ? + Simplify_Impl_For(node.value[1].value[0]) : + new SyntaxNode("blank", "", node.ref.clone()), + Simplify_Impl_Body(node.value[2]) + ]; -function Simplify_Composition (node) { - node = node.tokens[0]; - - if (node.type != "decompose" && node.type != "compose") { - throw new Error(`Unexpected composition statement "${node.type}"`); + // Swap the type references when a for is present + if (hasFor) { + let t = out[1]; + out[1] = out[0]; + out[0] = t; } - node.tokens = [ Simplify_Variable(node.tokens[2][0]) ]; - node.reached = null; + node.value = out; return node; } - - - -function Simplify_Variable (node) { - let inner = [ - 0, - Simplify_Name(node.tokens[0][0]), - node.tokens[1].map(x => { - return Simplify_Variable_Access(x).tokens; - }) - ]; - - node.reached = null; - node.tokens = inner; +function Simplify_Impl_For (node) { + return Simplify_Data_Type(node.value[0]); +} +function Simplify_Impl_Body (node) { + node.value = node.value[0].value + .map( x => Simplify_Impl_Stmt(x.value[0]) ); return node; } - -function Simplify_Variable_Access (node) { - let out = []; - switch (node.tokens[0].type) { - case "accessor_dynamic": - out = [ "[]", Simplify_Variable_Args(node.tokens[0].tokens[2][0]).tokens ]; - break; - case "accessor_refer": - out = [ "->" ]; - break; - case "accessor_static": - out = [ ".", Simplify_Name(node.tokens[0].tokens[1][0]) ]; - break; +function Simplify_Impl_Stmt (node) { + switch (node.value[0].type) { + case "struct_attribute": + return Simplify_Struct_Attribute(node.value[0]); + case "function": + return Simplify_Function(node.value[0]); default: - throw new TypeError(`Unexpected accessor type ${node.type}`); + throw new Error(`Unexpected class statement "${node.value[0].type}"`); } +} - node.tokens = out; - node.reached = null; +function Simplify_Trait (node) { + node.value = [ + Simplify_Name(node.value[0]), + node.value[1].value[0] ? + Simplify_Trait_Reliance(node.value[1].value[0]) : + new SyntaxNode('trait_reliance', [], node.value[1].ref.clone()), + Simplify_Trait_Body(node.value[2]) + ]; return node; } +function Simplify_Trait_Reliance (node) { + node.value = [ + node.value[0], + ...node.value[1].value.map(x => x.value[0]) + ].map(Simplify_Data_Type); -function Simplify_Variable_Args (node) { - let out = [ Simplify_Variable_Arg(node.tokens[0][0]) ]; - for (let inner of node.tokens[1]) { - out.push( Simplify_Variable_Arg(inner.tokens[3][0]) ); - } - - node.tokens = out; - node.reached = null; return node; } - -function Simplify_Variable_Arg (node) { - switch (node.tokens[0].type) { - case "data_type": - return Simplify_Data_Type(node.tokens[0]); - case "constant": - return Simplify_Constant(node.tokens[0]); - case "variable": - return Simplify_Variable(node.tokens[0]); +function Simplify_Trait_Body (node) { + node.value = node.value[0].value + .map( x => Simplify_Trait_Stmt(x.value[0]) ); + return node; +} +function Simplify_Trait_Stmt (node) { + switch (node.value[0].type) { + case "struct_attribute": + return Simplify_Struct_Attribute(node.value[0]); + case "function": + return Simplify_Function(node.value[0]); default: - throw new TypeError(`Unexpected variable access type ${node.tokens[0].type}`); + throw new Error(`Unexpected class statement "${node.value[0].type}"`); } } +function Simplify_Expr_Struct (node) { + node.value = [ + Simplify_Data_Type(node.value[0]), + Simplify_Expr_Struct_Body(node.value[1]) + ]; + return node; +} +function Simplify_Expr_Struct_Body(node) { + return node.value[0].value[0] ? + Simplify_Expr_Struct_Args(node.value[0].value[0]) : + new SyntaxNode('expr_struct_args', [], node.ref); +} +function Simplify_Expr_Struct_Args (node) { + node.value = [ + node.value[0], + ...node.value[1].value.map(x => x.value[0]) + ].map(Simplify_Expr_Struct_Arg); -function Simplify_Name (node) { - let out = node.tokens[0][0].tokens[0].tokens; - for (let inner of node.tokens[1]) { - if (Array.isArray(inner.tokens)) { - if (inner.tokens[0].type == "letter") { - out += inner.tokens[0].tokens[0].tokens; - } else { - out += inner.tokens[0].tokens; - } - } else { - out += inner.tokens; - } - } - - node.tokens = out; - node.reached = null; + return node; +} +function Simplify_Expr_Struct_Arg(node) { + node.value = [ + Simplify_Name(node.value[0]), + Simplify_Expr(node.value[1]) + ]; return node; } -function Simplify_Data_Type (node) { - let inner = [ - 0, - Simplify_Name(node.tokens[0][0]), - node.tokens[1].map(x => { - return Simplify_Data_Type_Access(x); - }), - node.tokens[2].length > 0 ? Simplify_Template(node.tokens[2][0]) : { // Template - type: "template", - tokens: [], - ref: {start: null, end:null} - }, - ]; - node.reached = null; - node.tokens = inner; +/*================================ + Templates +================================*/ +function Simplify_Template (node) { + node.value = Simplify_Template_Args(node.value[0]).value; return node; } +function Simplify_Template_Args (node) { + node.value = [ + node.value[0], + ...node.value[1].value.map(x => x.value[0]) + ].map(Simplify_Struct_Attribute); -function Simplify_Data_Type_Access (node) { - node.tokens = [ ".", Simplify_Name(node.tokens[1][0]) ]; - node.reached = null; return node; } @@ -472,65 +516,132 @@ function Simplify_Data_Type_Access (node) { -function Simplify_Constant (node) { - switch (node.tokens[0].type) { - case "boolean": - node.tokens = [ Simplify_Boolean(node.tokens[0]) ]; - break; - case "integer": - node.tokens = [ Simplify_Integer(node.tokens[0]) ]; - break; - case "float": - node.tokens = [ Simplify_Float(node.tokens[0]) ]; +/*================================ + Expression +================================*/ +function Simplify_Expr (node) { + let queue = [ + Simplify_Expr_Arg(node.value[0]) + ]; + for (let next of node.value[1].value) { + queue.push(next.value[0]); + queue.push(Simplify_Expr_Arg(next.value[1])); + } + + return ApplyPrecedence(queue); +} + +function Simplify_Expr_Arg (node) { + let subject = node.value[1]; + switch (subject.type) { + case "expr_val": + subject = Simplify_Expr_Val(subject); break; - case "string": - node.tokens = [ Simplify_String(node.tokens[0]) ]; + case "constant": + subject = Simplify_Constant(subject); break; + case "expr_brackets": + return Simplify_Expr_Brackets(subject); + case "expr_struct": + return Simplify_Expr_Struct(subject); default: - throw new TypeError(`Unexpected constant expression ${node.tokens[0].type}`); + throw "Implementation error"; } - node.reached = null; - return node; -} -function Simplify_Integer(node) { - node.tokens = ( node.tokens[0].length != 0 ? "-" : "" ) + - ( Simplify_Integer_U( node.tokens[1][0] ).tokens ); - node.reached = null; - return node; + let unary = node.value[0].value[0]; + if (unary) { + return Simplify_Expr_Unary(unary, subject); + } + + return subject; } -function Simplify_Integer_U (node) { - if (node.tokens[0].type == "zero") { - node.tokens = "0"; - } else { - let out = node.tokens[0].tokens[0][0].tokens; - for (let val of node.tokens[0].tokens[1]) { - out += val.tokens; - }; +function Simplify_Expr_Unary (operation, node) { + let operator = operation.flat(); - node.tokens = out; - } + let innerRef = operation.ref.clone(); + let outerRef = operation.ref.clone(); + outerRef.span(node.ref); - node.reached = null; - return node; + switch (operator) { + case "-": + return new SyntaxNode( + "expr_arithmetic", + [ + new SyntaxNode( + "expr_invert", + [ + node + ], + innerRef) + ], + outerRef); + case "!": + return new SyntaxNode( + "expr_bool", + [ + new SyntaxNode( + "expr_not", + [ + node + ], + innerRef) + ], + outerRef); + case "$": + return new SyntaxNode( + "expr_share", + [ + node + ], + outerRef); + case "@": + return new SyntaxNode( + "expr_lend", + [ + node + ], + outerRef); + default: + throw new Error(`Unexpected unary operation "${operator}"`); + } } -function Simplify_Float (node) { - let out = Simplify_Integer(node.tokens[0][0]).tokens; // base - out += "."; - out += Simplify_Integer_U(node.tokens[2][0]).tokens; // remainder - // Scientific notation - if (node.tokens[3].length > 0) { - out += "e"; - out += Simplify_Integer(node.tokens[3][0].tokens[1][0]).tokens; +function Simplify_Expr_Val (node) { + let base = Simplify_Access(node.value[0]); + + let extra = node.value[1].value[0]; + if (!extra) { + return base; } - node.tokens = out; - node.reached = null; - return node; + switch (extra.type) { + case "expr_struct_body": + return new SyntaxNode( + "expr_struct", + [ + base, + Simplify_Expr_Struct_Body(extra) + ], + node.ref + ); + case "call_body": + return new SyntaxNode( + "call", + [ + base, + Simplify_Call_Body(extra) + ], + node.ref + ); + default: + throw new Error(`Unknown expr value syntax type ${extra.type}`); + } } -function Simplify_Boolean (node) { - node.reached = null; + +function Simplify_Expr_Brackets (node) { + node.value = [ + Simplify_Expr(node.value[0]) + ]; return node; } @@ -538,255 +649,132 @@ function Simplify_Boolean (node) { - - -function Simplify_Function (node) { - node.tokens = [ - Simplify_Function_Head(node.tokens[0][0]), // head - Simplify_Function_Body(node.tokens[2][0]) // body - ]; - node.reached = null; +/*================================ + Library Management +================================*/ +function Simplify_Library (node) { + switch (node.value[0].type) { + case "import": + node.value = [ Simplify_Library_Import(node.value[0]) ]; + break; + default: + throw new TypeError(`Unexpected library statement ${node.value[0].type}`); + } return node; } -function Simplify_Function_Outline (node) { - node.tokens = [ - Simplify_Function_Head(node.tokens[0][0]) // head +function Simplify_Library_Import (node) { + node.value = [ + new SyntaxNode("name", node.value[1].flat() || "*", node.value[1].ref), + Simplify_String(node.value[0]) ]; - node.reached = null; + return node; } -function Simplify_Function_Redirect (node) { - node.tokens = [ - new BNF_SyntaxNode ( - 'function_head', - [ - node.tokens[5][0] ? // Return type - Simplify_Data_Type (node.tokens[5][0].tokens[3][0]) : - null, - Simplify_Name (node.tokens[9][0]), // Name - Simplify_Func_Args (node.tokens[4][0]), // Arguments - [], - ], - 0, - node.ref.start, - node.ref.end - ), - Simplify_String (node.tokens[2][0]).tokens[1], // Representation - ]; - node.reached = null; - // Replace null return type with "void" datatype - if (node.tokens[0].tokens[0] === null) { - node.tokens[0].tokens[0] = new BNF_SyntaxNode ( - 'data_type', - [ - 0, - new BNF_SyntaxNode( - 'name', - 'void', - 4, - new BNF.types.BNF_Reference(0,0,0), - new BNF.types.BNF_Reference(0,0,0), - null - ), - [], - { type: 'template', tokens: [], ref: {} } - ], - [], - new BNF.types.BNF_Reference(0,0,0), - new BNF.types.BNF_Reference(0,0,0), - null - ); - } + + + +/*================================ + External +===============================*/ +function Simplify_Include (node) { + node.value = [ + // Mode + node.value[0], + + // Path + Simplify_String(node.value[1]) + ]; return node; } -function Simplify_Function_Head (node) { - node.tokens = [ - node.tokens[6][0] ? // Return type - Simplify_Data_Type (node.tokens[6][0].tokens[2][0]) : - null, - Simplify_Name (node.tokens[2][0]), // Name - Simplify_Func_Args (node.tokens[4][0]), // Arguments - [] - ]; - node.reached = null; - // Replace null return type with "void" datatype - if (node.tokens[0] === null) { - node.tokens[0] = new BNF_SyntaxNode ( - 'data_type', - [ - 0, - new BNF_SyntaxNode( - 'name', - 'void', - 4, - new BNF.types.BNF_Reference(0,0,0), - new BNF.types.BNF_Reference(0,0,0), - null - ), - [], - { type: 'template', tokens: [], ref: {} } - ], - [], - new BNF.types.BNF_Reference(0,0,0), - new BNF.types.BNF_Reference(0,0,0), - null - ); - } +function Simplify_External (node) { + node.value = [ + // Mode + node.value[0], + // Body + Simplify_External_Body(node.value[1]) + ]; return node; } -function Simplify_Function_Body (node) { - let out = []; - for (let inner of node.tokens[2]) { - let res = Simplify_Function_Stmt(inner.tokens[0][0]); - if (res !== null) { - out.push( res.tokens[0] ); - } - } +function Simplify_External_Body (node) { + node.value = node.value[0].value + .map(x => Simplify_External_Term(x.value[0])); - node.tokens = out; - node.reached = null; return node; } -function Simplify_Function_Stmt (node) { - let inner; - switch (node.tokens[0].type) { - case "comment": - return null; +function Simplify_External_Term (node) { + switch (node.value[0].type) { + case "function_outline": + return Simplify_Function_Outline(node.value[0]); + case "function_redirect": + return Simplify_Function_Redirect(node.value[0]); + case "struct": + return Simplify_Struct(node.value[0]); + case "type_def": + return Simplify_Type_Def(node.value[0]); case "declare": - inner = Simplify_Declare(node.tokens[0]); - break; - case "declare_assign": - inner = Simplify_Declare_Assign(node.tokens[0]); - break; - case "delete": - inner = Simplify_Delete(node.tokens[0]); - break; - case "assign": - inner = Simplify_Assign(node.tokens[0]); - break; - case "return": - inner = Simplify_Return(node.tokens[0]); - break; - case "call_procedure": - inner = Simplify_Call(node.tokens[0].tokens[0][0]); - break; - case "if": - inner = Simplify_If(node.tokens[0]); - break; - case "while": - inner = Simplify_While(node.tokens[0]); - break; - case "composition": - inner = Simplify_Composition(node.tokens[0]); - break; + return Simplify_Declare(node.value[0]); default: - throw new TypeError(`Unexpected function statement ${node.tokens[0].type}`); + throw new TypeError(`Unexpected external statement ${node.value[0].type}`); } - - node.tokens = [inner]; - node.reached = null; - return node; } -function Simplify_Func_Args (node) { - node.tokens = node.tokens[2].length > 0 ? Simplify_Func_Args_List(node.tokens[2][0]).tokens : []; - node.reached = null; - return node; -} -function Simplify_Func_Args_List (node) { - let ittr = node.tokens[0].concat(node.tokens[2].map(x => x.tokens[2][0])); - - node.tokens = ittr.map((arg) => [ - arg.tokens[4][0] ? arg.tokens[4][0].tokens : null, // borrowed? - Simplify_Data_Type(arg.tokens[6][0]), // type - Simplify_Name(arg.tokens[0][0]) // name - ]); - node.reached = null; - return node; -} +function Simplify_Type_Def (node) { + node.value = [ + // Name + Simplify_Name(node.value[0]), -function Simplify_Call (node) { - let out = [ - Simplify_Variable(node.tokens[0][0]), // Call name - node.tokens[2].length > 0 ? Simplify_Template(node.tokens[2][0]) : { // Template - type: "template", - tokens: [] - }, - Simplify_Call_Args(node.tokens[4][0]) + // Size + Simplify_Integer(node.value[1]) // size ]; - - node.tokens = out; - node.reached = null; return node; } -function Simplify_Call_Args (node) { - if (node.tokens[2].length > 0) { - let inner = node.tokens[2][0]; - node.tokens = - [ inner.tokens[0][0] ] - .concat( - inner.tokens[1].map(arg => arg.tokens[3][0]) - ).map( x => Simplify_Call_Arg(x) ); - } else { - node.tokens = []; - } - node.reached = null; - return node; -} -function Simplify_Call_Arg(node) { - if (node.tokens[0].type == "expr_lend") { - return Simplify_Expr_Lend(node.tokens[0]); - } else { - return Simplify_Expr(node.tokens[0]); - } -} +/*================================ + If Statement +================================*/ function Simplify_If (node) { - let head = Simplify_If_Stmt(node.tokens[0][0]); - let elif = node.tokens[1].map(x => Simplify_If_Stmt(x.tokens[1][0])); + let head = Simplify_If_Stmt(node.value[0]); + let elif = node.value[1].value.map(x => Simplify_If_Stmt(x.value[0])); + let other = - node.tokens[3][0] ? - Simplify_If_Else(node.tokens[3][0]) : - new BNF_SyntaxNode ("else_stmt", [ - new BNF_SyntaxNode ("function_body", [], 0, head.ref.start, head.ref.end, head.ref.reached) - ], 0, head.ref.start, head.ref.end, head.ref.reached); + node.value[2].value[0] ? + Simplify_If_Else(node.value[2].value[0]) : + new SyntaxNode ( + "else_stmt", + [ new SyntaxNode ("function_body", [], node.value[2].ref.clone()) ], + node.value[2].ref.clone() + ); // Merge elifs into new else statement while (elif.length > 0) { - let last = elif.splice(elif.length-1, 1)[0]; - - let s = last.ref.start; - let e = other.ref.end; - let r = null; + let last = elif.shift(); + let ref = new ReferenceRange(last.ref.start.clone(), other.ref.end.clone()); - other = new BNF_SyntaxNode( + other = new SyntaxNode( "else_stmt", - [new BNF_SyntaxNode( + [new SyntaxNode( "function_body", - [new BNF_SyntaxNode( + [new SyntaxNode( "if", [last, other], - 0, - s, e, r + ref.clone(), )], - 0, - s, e, r + ref.clone() )], - 0, - s, e, r + ref ); } - node.tokens = [ + node.value = [ head, other ]; @@ -794,252 +782,84 @@ function Simplify_If (node) { return node; } function Simplify_If_Stmt (node) { - let out = [ - Simplify_Expr(node.tokens[4][0]), - Simplify_Function_Body(node.tokens[8][0]) + node.value = [ + Simplify_Expr(node.value[0]), + Simplify_Function_Body(node.value[1]) ]; - - node.tokens = out; - node.reached = null; return node; } function Simplify_If_Else (node) { - let out = [ - Simplify_Function_Body(node.tokens[2][0]) + node.value = [ + Simplify_Function_Body(node.value[0]) ]; - - node.tokens = out; - node.reached = null; return node; } -function Simplify_While (node) { - let out = [ - Simplify_Expr(node.tokens[4][0]), - Simplify_Function_Body(node.tokens[8][0]) - ]; - node.tokens = out; - node.reached = null; - return node; -} - - -function Simplify_Return (node) { - let inner = []; - if (node.tokens[1].length == 1) { - inner = [ Simplify_Expr(node.tokens[1][0].tokens[1][0]) ]; - } - - node.tokens = inner; - node.reached = null; - return node; -} - - - -function Simplify_Declare (node) { - let out = [ - Simplify_Data_Type(node.tokens[6][0]), - Simplify_Name(node.tokens[2][0]) - ]; - - node.tokens = out; - node.reached = null; - return node; -} -function Simplify_Declare_Assign (node) { - let out = [ - node.tokens[4][0] ? - Simplify_Data_Type (node.tokens[4][0].tokens[2][0]) : - null, - Simplify_Name(node.tokens[2][0]), - Simplify_Expr(node.tokens[7][0]) +/*================================ + When Statement +================================*/ +function Simplify_When (node) { + node.value = [ + Simplify_Variable(node.value[0]), + Simplify_When_Opt(node.value[1]) ]; - node.tokens = out; - node.reached = null; return node; } -function Simplify_Assign (node) { - node.tokens = [ - Simplify_Variable (node.tokens[0][0]), // target variable - Simplify_Expr (node.tokens[4][0]) // value - ]; - node.reached = null; +function Simplify_When_Opt(node) { + node.value = node.value.map(Simplify_When_Stmt); return node; } - -function Simplify_Delete (node) { - node.tokens = [ - Simplify_Variable(node.tokens[2][0]) - ]; - node.reached = null; - - return node; -} - - - -function Simplify_Expr (node) { - let queue = [ - Simplify_Expr_Arg(node.tokens[1][0]) +function Simplify_When_Stmt(node) { + node.value = [ + node.value[0].type == "literal" ? + node.value[0] : + Simplify_Data_Type(node.value[0]), + Simplify_When_Stmt(node.value[1]) ]; - for (let next of node.tokens[3]) { - queue.push(next.tokens[0][0]); - queue.push(Simplify_Expr_Arg(next.tokens[2][0])); - } - - return ApplyPrecedence(queue); -} -function Simplify_Expr_Arg (node) { - switch (node.tokens[0].type) { - case "expr_val": - return Simplify_Expr_Val(node.tokens[0]); - case "expr_brackets": - return Simplify_Expr_Brackets(node.tokens[0]); - default: - throw new Error(`Unexpected expression argument ${node.tokens[0].type}`); - } -} - -function Simplify_Expr_Val (node) { - let subject = node.tokens[2][0].tokens[0]; - subject = subject.type == "variable" ? - Simplify_Variable(subject) : - Simplify_Constant(subject); - - let call = node.tokens[4]; - if (call.length > 0) { - return Simplify_Expr_Call(subject, call[0], subject.ref); - } - - let unary = node.tokens[0][0]; - if (unary) { - return Simplify_Expr_Unary(unary, subject); - } - - return subject; -} - -function Simplify_Expr_Unary (opperation, node) { - switch (opperation.tokens) { - case "-": - return new BNF_SyntaxNode( - "expr_arithmetic", - [ - new BNF_SyntaxNode( - "expr_invert", - [ - node - ], 1, - opperation.ref.start, - opperation.ref.end - ) - ], 0, - opperation.ref.start, - node.ref.end - ); - case "!": - return new BNF_SyntaxNode( - "expr_bool", - [ - new BNF_SyntaxNode( - "expr_not", - [ - node - ], 1, - opperation.ref.start, - opperation.ref.end - ) - ], 0, - opperation.ref.start, - node.ref.end - ); - case "$": - return new BNF_SyntaxNode( - "expr_clone", - [ - node - ], - 0, - opperation.ref.start, - node.ref.end - ); - default: - throw new Error(`Unexpected unary operation "${opperation.tokens}"`); - } -} - -function Simplify_Expr_Call (name, node, ref) { - return new BNF_SyntaxNode( - "call", - [ - name, // name - node.tokens[0].length > 0 ? // template - Simplify_Template(node.tokens[0][0]) : - new BNF_SyntaxNode( - "template", - [], - 0, - node.ref.start, - node.ref.start - ), - Simplify_Call_Args(node.tokens[2][0]) // args - ], - 0, - ref.start, - ref.end, - null - ); -} - -function Simplify_Expr_Brackets (node) { - node.tokens = [ - Simplify_Expr(node.tokens[2][0]) - ]; - node.reached = null; return node; } -function Simplify_Expr_Lend (node) { - node.tokens = [ - Simplify_Variable(node.tokens[1][0]) - ]; - node.reached = null; - return node; -} +const STMT_MAP = { + "declare": Simplify_Declare, + "assign": Simplify_Assign, + "return": Simplify_Return, + "call_procedure": Simplify_Call_Procedure, + "if": Simplify_If, + "when": Simplify_When +}; +const STMT_TOP_MAP = { + "external": Simplify_External, + "include": Simplify_Include, + "function": Simplify_Function, + "library": Simplify_Library, + "struct": Simplify_Struct, + "impl": Simplify_Impl, + "trait": Simplify_Trait +}; -function Parse (data, filename){ +module.exports = function (data, filename){ // Parse the file and check for errors - let result = BNF.Parse(data+"\n", syntax, "program"); - - if (result.hasError || result.isPartial) { - let ref = result.tree.ref.end; - let cause = "Unknown"; - if (result.tree.ref.reached) { - ref = result.tree.ref.reached.getReach(); - cause = result.tree.ref.reached.getCausation(); - } - - let msg = filename ? `${filename}: ` : ""; - msg += `Syntax error at ${ref.toString()}\n`; - msg += ` ${BNF.Message.HighlightArea(data, ref).split('\n').join('\n ')}\n\n`; - msg += ` Interpreted: ${cause}`; + let res = syntax.parse(data, false, "program"); + if (res instanceof ParseError) { + let msg = filename ? `${filename}:\n ` : ""; + msg += `Syntax error at ${res.ref.toString()}\n`; + msg += ` ${CodeSection(data, res.ref.start, res.ref.end).split('\n').join('\n ')}\n\n`; + msg += ` Interpreted: ${res.msg}`; + console.error(msg); process.exit(1); } - return Simplify_Program(result.tree); -} - -module.exports = Parse; \ No newline at end of file + return Simplify_Program(res); +}; \ No newline at end of file diff --git a/compiler/parser/syntax.bnf b/compiler/parser/syntax.bnf index 98f5b1b..17761af 100644 --- a/compiler/parser/syntax.bnf +++ b/compiler/parser/syntax.bnf @@ -1,181 +1,186 @@ -program ::= w* ( stmt_top w* )* - stmt_top ::= comment | library | class | template_def | struct | flag_definition | external | include | function +program ::= %w* ( stmt_top %w* )* ; + stmt_top ::= + function | + impl | + struct | + trait | + library | + external | + include ; #============================= # Helper patterns #============================= - w ::= " " | "\t" | nl - nl ::= "\r\n" | "\n" + w ::= " " | "\t" | nl | comment ; + nl ::= "\r\n" | "\n" ; - digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" - digit_nz ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" - letter ::= letter_upper | letter_lower - letter_lower ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "t" | "s" | "u" | "v" | "w" | "x" | "y" | "z" - letter_upper ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "T" | "S" | "U" | "V" | "W" | "X" | "Y" | "Z" + digit ::= "0" -> "9" ; + digit_nz ::= "1" -> "9" ; + letter ::= "a" -> "z" | "A" -> "Z" ; #============================= # Comments #============================= - comment ::= comment_single | comment_multi - comment_single ::= "//" !( nl )* nl - comment_multi ::= "/*" ( "\\*" | !( "*/" )* )* "*/" + comment ::= comment_single | comment_multi ; + comment_single ::= "//" !( nl )* nl? ; # Optional as the comment might be on a EOF + comment_multi ::= "/*" ( "\\*" | !( "*/" )+ )* "*/" ; #============================= # Constants #============================= - constant ::= boolean | string | hexidecimal | octal | binary | float | integer + constant ::= boolean | + void | + string | + hexadecimal | octal | binary | + float | integer ; - string ::= string_unicode | string_text - string_unicode ::= "\"" ( "\\\"" | !( "\"" ) )* "\"" - string_text ::= "'" ( "\\'" | !( "'" ) )* "'" + string ::= string_unicode | string_text ; + string_unicode ::= %"\"" ( ( "\\" !"" ) | !( "\"" ) )* %"\"" ; + string_text ::= %"\'" ( ( "\\" !"" ) | !( "\'" ) )* %"\'" ; - hexidecimal ::= "0x" hex_char+ - hex_char ::= digit | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" + hexadecimal ::= "0x" ...hex_char+ ; + hex_char ::= digit | "a" -> "f" | "A" -> "F" ; - octal ::= "0o" octal_char+ - octal_char ::= "0" | "1" | "2" | "3" + octal ::= "0o" ...octal_char+ ; + octal_char ::= "0" -> "3" ; - binary ::= "0b" ( "0" | "1" )+ + binary ::= "0b" ( "0" | "1" )+ ; - boolean ::= "true" | "false" + boolean ::= "true" | "false" ; - integer ::= "-"? integer_u - integer_u ::= ( digit_nz digit* ) | zero - zero ::= "0" - float ::= integer "." integer_u ( "e" integer )? + void ::= "void" ; + + integer ::= "-"? ...integer_u ; + integer_u ::= ( digit_nz digit* ) | zero ; + zero ::= "0" ; + float ::= ...integer "." ...integer_u ( "e" ...integer )? ; #============================= -# Variables +# Accessors #============================= - assign ::= variable w* "=" w* expr w* ";" - declare ::= "let" w+ name w* ":" w* data_type w* ";" - declare_assign ::= "let" w+ name w* ( ":" w* data_type w* )? "=" w* expr w* ";" + access ::= ...name %w* ( ( access_static | access_dynamic | access_template ) %w* )* ; - name ::= letter ( letter | digit | "_" )* - variable ::= name accessor* - accessor ::= accessor_dynamic | accessor_static - accessor_dynamic ::= "[" w* variable_args w* "]" - accessor_static ::= "." name - variable_args ::= variable_arg ( w* "," w* variable_arg )* - variable_arg ::= constant | variable + access_static ::= %(".") ...name %w* ; - data_type ::= name data_type_access* template? - data_type_access ::= "." name + access_dynamic ::= %("[" w* ) call_args %( w* "]" ) %w* ; - delete ::= "delete" w+ variable w* ";" + access_template ::= %( "#[" w* ) access_template_args %( w* "]" w* ) ; + access_template_args ::= access_template_arg %w* ( %( "," w* ) access_template_arg %w* )* %","? ; + access_template_arg ::= constant | data_type ; #============================= -# Flags +# Variables #============================= - flags ::= ":" w* "[" w* flag_args w* "]" - flag_args ::= variable ( w* "," w* variable )* - flag_definition ::= "flag" w+ name w+ "[" w* name ( w* "," w* name )* w* "]" w* ";" + variable ::= ...name ( %w* ( access_static | access_dynamic ) )* ; + name ::= ( letter | "_" )+ ( letter | digit | "_" )* ; + + data_type ::= ...data_type_lending? ...name ( %w* ( access_static | access_template ) )* ; + data_type_lending ::= "@" | "$" ; + + declare ::= %( "let" %w+ ) ...name %w* ( %( ":" w* ) data_type %w* )? ( %( "=" w* ) expr )? %( w* ";" ) ; + assign ::= variable %( w* "=" w* ) expr %( w* ";" ) ; #============================= # Function #============================= - function ::= func_head w* function_body w* ";"? - func_head ::= "fn" w+ name w* func_arguments w* ( ":" w* data_type )? - func_arguments ::= "(" w* func_arg_list? ")" - func_arg_list ::= func_arg w* ( "," w* func_arg w* )* - func_arg ::= name w* ":" w* ( "@" | "#" )? w* data_type - function_body ::= "{" w* ( func_stmt w* )* "}" - func_stmt ::= comment | if | return | composition | declare | delete | assign | declare_assign | call_procedure + function ::= func_head %w* ( function_body | ";" ) ; + func_head ::= %( "fn" w+ ) ...name %( w* "(" w* ) func_arguments %( w* ")" w* ) %w* ( %( ":" w* ) data_type )? ; + func_arguments ::= ( func_arg %w* ( %( "," w* ) func_arg %w* )* )? ; + func_arg ::= ...name %( w* ":" w* ) data_type ; + + function_body ::= %( "{" w* ) ( func_stmt %w* )* %( "}" w* ";"? ) ; + func_stmt ::= if | when | return | declare | assign | call_procedure ; - function_outline ::= func_head w* ";" - function_redirect ::= "fn" w+ string w* func_arguments ( w* ":" w* data_type )? w+ "as" w+ name w* ";" + function_outline ::= func_head %( w* ";" ) ; + function_redirect ::= %( "fn" w+ ) string %( w* "(" w* ) func_arguments %( w* ")" w* ) ( %( w* ":" w* ) data_type w+ )? %( "as" w+ ) ...name %( w* ";" ) ; - call ::= variable w* template? w* call_args - call_args ::= "(" w* ( call_arg ( w* "," w* call_arg )* w* )? ")" - call_arg ::= expr_lend | expr - call_procedure ::= call ";" + call ::= access call_body ; + call_body ::= %( w* "(" w* ) call_args? %( w* ")" ) ; + call_args ::= expr %w* ( %( "," w* ) expr %w* )* %( ","? w* ) ; + call_procedure ::= call %( w* ";" )? ; - return ::= "return" ( w+ expr )? w* ";" + return ::= %"return" ( %w+ expr )? %( w* ";" ) ; #============================= # Structure #============================= - struct ::= "struct" w+ name w* "{" w* struct_body w* "}" w* ";"? - struct_body ::= ( w* struct_stmt )* - struct_stmt ::= comment | struct_attribute - struct_attribute ::= name w* ":" w* data_type w* ";" + struct ::= %( "struct" w+ ) ...name %( w* "{" w* ) struct_body %( w* "}" ) ; + struct_body ::= ( %w* struct_stmt %w* )* ; + struct_stmt ::= struct_attribute | struct_attribute ; + struct_attribute ::= ...name %( w* ":" w* ) data_type %( w* ";" ) ; - composition ::= compose | decompose - decompose ::= "decompose" w+ variable w* ";" - compose ::= "compose" w+ variable w* ";" + impl ::= %( "impl" w+ ) data_type impl_for? %( w* "{" w* ) impl_body %( w* "}" ); + impl_for ::= %( w+ "for" w+ ) data_type ; + impl_body ::= ( impl_stmt %w* )* ; + impl_stmt ::= function | template ; + trait ::= %( "trait" w+ ) ...name %w* trait_reliance? %( w* "{" w* ) trait_body %( w* "}" ) ; + trait_reliance ::= %( ":" w* ) data_type ( %( w* "," w* ) data_type )* ; + trait_body ::= ( %w* trait_stmt )* ; + trait_stmt ::= function | template ; -#============================= -# Class -#============================= - class ::= "class" w+ name w+ class_inherit? w* "{" class_body "}" w* ";"? - class_inherit ::= "extends" w+ variable - class_body ::= ( w* class_stmt )* w* - class_stmt ::= comment | function | struct_attribute - class_access ::= "public:" | "private:" | "protected:" | "static:" + expr_struct ::= %( "new" w+ ) data_type %w* expr_struct_body ; + expr_struct_body ::= %( w* "{" w* ) expr_struct_args? %( w* "}" ) ; + expr_struct_args ::= expr_struct_arg %w* ( %( ";" w* ) expr_struct_arg )* %( w* ";"? ) ; + expr_struct_arg ::= ...name %( w* ":" w* ) expr ; #============================= # Template #============================= - template_def ::= "template" w* template_arguments w* template_opperand - template_def_arguments ::= "(" w* ( template_arg w* )? ( "," w* template_arg w* )* ")" - template_def_arg ::= "^"? data_type w+ name flags? - template_opperand ::= function_outline | function | class - - template ::= "#[" w* template_args w* "]" - template_args ::= template_arg ( w* "," w* template_arg )* - template_arg ::= constant | data_type + template ::= %( %"template" w* "(" w* ) template_args %( w* ")" ) ; + template_args ::= struct_attribute %w* ( %( "," w* ) struct_attribute %w* )* ; #============================= # Expression #============================= - expr ::= w* expr_arg w* ( expr_middle_opper w* expr_arg w* )* - expr_right_opper ::= "!" | "$" | "-" - expr_middle_opper ::= "?" | ":" | "&&" | "||" | "==" | "!=" | "<=" | ">=" | "<" | ">" | "%" | "*" | "/" | "+" | "-" - expr_arg ::= expr_val | expr_brackets - expr_val ::= expr_right_opper? w* ( constant | variable ) w* ( template? w* call_args )? - expr_brackets ::= "(" w* expr w* ")" - - expr_lend ::= "@" variable + expr ::= expr_arg %w* ( ...expr_middle_oper %w* expr_arg %w* )* ; + expr_left_oper ::= "!" | "$" | "@" | "-" ; + expr_middle_oper ::= "?" | ":" | "&&" | "||" | "==" | "!=" | "<=" | ">=" | "<" | ">" | "%" | "*" | "/" | "+" | "-" | "->" ; + expr_arg ::= expr_left_oper? %w* ( constant | expr_brackets | expr_struct | expr_val ) ; + expr_val ::= access call_body? ; + expr_brackets ::= %( "(" w* ) expr %( w* ")" ) ; #============================= # Library Management #============================= - library ::= import | import - - import ::= import_as | import_direct - import_as ::= "import" w* string w* ";" - import_direct ::= "import" w* string w+ "as" w+ name w* ";" + library ::= import | import ; + import ::= %( "import" w* ) string %w* ( %( "as" w+ ) ...name )? %( w* ";" ) ; #============================= # External #============================= - include ::= "include" w+ include_type w* string w* ";" - include_type ::= "llvm" | "cpp" | "c" + include ::= %( "include" w+ ) ...include_type %w* string %( w* ";" ) ; + include_type ::= "llvm" | "static" | "dynamic" | "object" ; - external ::= "external" w+ external_mode w* "{" w* external_body w* "}" ";"? - external_mode ::= "assume" | "export" - external_body ::= ( external_term w* )* - external_term ::= function_redirect | function_outline | type_def | struct | comment - # function_outline + external ::= %( "external" w+ ) ...external_mode %( w* "{" w* ) external_body %( w* "}" ";"? ) ; + external_mode ::= "assume" | "export" ; + external_body ::= ( external_term w* )* ; + external_term ::= function_redirect | function_outline | type_def | struct ; - type_def ::= "type" w+ name w+ "size" w+ integer w* ";" + type_def ::= %( "type" w+ ) ...name %( w+ "size" w+ ) integer %( w* ";" ) ; #============================= # If statement #============================= - if ::= if_stmt ( w* elif_stmt )* w* else_stmt? - if_stmt ::= "if" w* "(" w* expr w* ")" w* function_body - elif_stmt ::= "elif" w* "(" w* expr w* ")" w* function_body - else_stmt ::= "else" w* function_body \ No newline at end of file + if ::= if_stmt %w* ( elif_stmt %w* )* else_stmt? ; + if_stmt ::= %( "if" w* "(" w* ) expr %( w* ")" w* ) function_body ; + elif_stmt ::= %( "elif" w* "(" w* ) expr %( w* ")" w* ) function_body ; + else_stmt ::= %( "else" w* ) function_body ; + + +#============================= +# When statement +#============================= + when ::= %( "when" w+ ) variable %( w* "{" %w* ) when_opt+ %"}" ; + when_opt ::= ( "default" | data_type ) %w* function_body %w* ; \ No newline at end of file diff --git a/compiler/primative/array.js b/compiler/primative/array.js index a4cb242..1392f1e 100644 --- a/compiler/primative/array.js +++ b/compiler/primative/array.js @@ -43,7 +43,7 @@ class Template_Array extends Template { this.instances[signature] = inst; } - return new TypeRef(0, inst); + return new TypeRef(inst); } toLLVM() { diff --git a/compiler/primative/clone.js b/compiler/primative/clone.js new file mode 100644 index 0000000..c993797 --- /dev/null +++ b/compiler/primative/clone.js @@ -0,0 +1,234 @@ +const Function_Instance = require('./function_instance.js'); +const Template = require('../component/template.js'); +const Struct = require('../component/struct.js'); +const LLVM = require('../middle/llvm.js'); + +const types = require('./types.js'); +const TypeRef = require('../component/typeRef.js'); + +class Template_Clone extends Template { + constructor (ctx) { + super(ctx, null); + + this.children = []; + } + + findMatch(type) { + for (let child of this.children) { + if (child.type.match(type)) { + return child.function; + } + } + + return null; + } + + getFunction (access, signature) { + if (signature.length != 1) { + return false; + } + if (signature[0].lent != true && !signature[0].native) { + return false; + } + + let type = null; + if (access.length == 1 && access[0].type == "access_template") { + if (access[0].value.length != 1) { + return null; + } + + type = access[0].value[0]; + type.lent = true; + + if (!type.match(signature[0])) { + return false; + } + } else { + if (access.length !== 0) { + return false; + } + + type = signature[0]; + } + + let cloner = type.type.getCloner(); + if (cloner) { + return cloner; + } + + let match = this.findMatch(type); + if (match) { + return match; + } + + let func = this.generate(type); + if (func) { + this.children.push({ + type: type, + function: func + }); + + return func; + } + + return false; + } + + generate (type) { + type.constant = true; + type.lent = true; + + let out = type.duplicate(); + out.constant = false; + out.lent = false; + + let func = new Function_Instance(this, "Clone", out, [type]); + + if (type.native) { + func.isInline = true; + func.generate = (regs, ir_args) => { + return { + preamble: new LLVM.Fragment(), + instruction: ir_args[0], + type: type.duplicate() + }; + }; + } else { + func.isInline = false; + + if (!(type.type instanceof Struct)) { + throw new Error("Huh? How..."); + } + + let struct = type.type; + func.ir.append(new LLVM.Label(new LLVM.ID()).toDefinition()); + + let llvmType = new LLVM.Type( + struct.represent, + 0, + null + ); + + let cache = new LLVM.ID(); + func.ir.append(new LLVM.Set( + new LLVM.Name(cache, false, null), + new LLVM.Load( + llvmType, + new LLVM.Name("1", false, null), + false), + null + )); + + func.ir.append(new LLVM.Store( + new LLVM.Argument( + type.toLLVM(), + new LLVM.Name("0", false, null), + null + ), + new LLVM.Argument( + llvmType, + new LLVM.Name(cache.reference(), false, null), + null + ), + null + )); + + func.ir.merge( + RecursiveCopy( + struct, + new LLVM.Argument( + type.toLLVM(), + new LLVM.Name("1", false, null), + ), + new LLVM.Argument( + type.toLLVM(), + new LLVM.Name("0", false, null), + ) + ) + ); + } + + func.ir.append(new LLVM.Return( + new LLVM.Type("void", 0, null) + )); + + func.compile(); + + return func; + } + + + toLLVM () { + return this.children + .map(x => x.function.toLLVM()) + .reduce((p, c) => { + p.append(c); + return p; + }, new LLVM.Fragment()); + } +} + + +/** + * + * @param {TypeRef} struct + * @param {LLVM.Argument} from + * @param {LLVM.Argument} to + */ +function RecursiveCopy(struct, from, to) { + let frag = new LLVM.Fragment(); + + frag.merge( + struct.terms + .map((x, i) => { + let type = x.typeRef.type; + if (!type.hasNestedCloner()) { + return null; + } + + let frag = new LLVM.Fragment(); + let fromArg = struct.accessGEPbyIndex(i, from); + let toArg = struct.accessGEPbyIndex(i, to); + frag.merge(fromArg.preamble); + frag.merge(toArg.preamble); + + let cloner = type.getCloner(); + if (cloner) { + frag.append(new LLVM.Call( + new LLVM.Type("void", 0, null), + new LLVM.Name(cloner.represent, false, null), + [ + toArg.instruction, + fromArg.instruction + ], + null + )); + + return frag; + } + + frag.merge(RecursiveCopy( + fromArg.type, + fromArg.instruction, + toArg.instruction + )); + + return frag; + }) + .filter(x => x !== null) + .reduce((p, c) => { + if (c instanceof LLVM.Fragment) { + p.merge(c); + } else { + p.append(c); + } + + return p; + }, new LLVM.Fragment()) + ); + + return frag; +} + + +module.exports = Template_Clone; \ No newline at end of file diff --git a/compiler/primative/either.js b/compiler/primative/either.js new file mode 100644 index 0000000..517e2b5 --- /dev/null +++ b/compiler/primative/either.js @@ -0,0 +1,295 @@ +const LLVM = require('./../middle/llvm.js'); + +const Function_Instance = require('./function_instance.js'); + +const Template = require('../component/template.js'); +const TypeRef = require('../component/typeRef.js'); +const TypeDef = require('../component/typedef.js'); + +class Either extends Template { + constructor (ctx) { + super(ctx, null); + + this.children = []; + } + + findMatch (inputType) { + for (let child of this.children) { + if (child.match(inputType)) { + return new TypeRef(child); + } + } + + return null; + } + + getFile() { + return this.ctx.getFile(); + } + + getFunction (access, signature, template) { + let child = this.getType([], template); + if (child) { + return child.type.getFunction(access, signature, template); + } + + return false; + } + + getType (access, template, stack) { + if (access.length != 0) { + return; + } + + // Valid types for either statement + if (template.map(x => + !(x.type.type instanceof TypeDef) || + x.lent || + x.constant + ).includes(false)) { + return false; + } + + let res = this.findMatch(template); + if (res) { + return res; + } + + let child = new Either_Instance(this.ctx, template, this.children.length); + this.children.push(child); + + return new TypeRef(child); + } + + toLLVM(ref) { + let frag = new LLVM.Fragment(); + for (let child of this.children) { + frag.append(child.toLLVM(ref)); + } + + return frag; + } +} + +class Either_Instance { + constructor (ctx, signature, id) { + this.ctx = ctx; + this.signature = signature; + this.name = `Either#[${signature.map(x => x.type.name).join(", ")}]`; + this.represent = `%Either.${id}`; + this.typeSystem = "linear"; + this.size = -1; + + this.destructor = false; + this.cloner = false; + + this.getSize(); + + if (this.signature.map(x => x.type.meta == "CLASS").includes(true)) { + this.generateCloner(); + this.generateDestructor(); + } + } + + getFile() { + return this.ctx.getFile(); + } + + getFunction (access, signature, template) { + if (access.length != 0 || signature.length != 1) { + return false; + } + + let found = false; + let i=0; + for (; i { + let frag = new LLVM.Fragment(); + + let type = new LLVM.Type(this.represent, 0); + let val = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(val, false), + new LLVM.Alloc(type) + )); + + let state = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(state), + new LLVM.GEP( + type, + new LLVM.Argument( + new LLVM.Type(this.represent, 1), + new LLVM.Name(val.reference(), false) + ), + [ + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("0") + ), + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("1") + ) + ] + ) + )); + frag.append(new LLVM.Store( + new LLVM.Argument( + new LLVM.Type("i8", 1), + new LLVM.Name(state.reference()) + ), + new LLVM.Argument( + new LLVM.Type("i8", 0), + new LLVM.Constant(i.toString()) + ) + )); + + + if (ir_args[0].type.term != "void") { + let data = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(data), + new LLVM.GEP( + type, + new LLVM.Argument( + new LLVM.Type(this.represent, 1), + new LLVM.Name(val.reference(), false) + ), + [ + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("0") + ), + new LLVM.Argument( + new LLVM.Type("i32", 0), + new LLVM.Constant("0") + ) + ] + ) + )); + + let nx_type = new LLVM.Type(ir_args[0].type.term, 1); + let store = new LLVM.ID(); + frag.append(new LLVM.Set( + new LLVM.Name(store, false), + new LLVM.Bitcast( + nx_type, + new LLVM.Argument( + new LLVM.Type(`<${this.getSize()} x i8>`, 1), // -1 + new LLVM.Name(data.reference()) + ) + ) + )); + + frag.append(new LLVM.Store( + new LLVM.Argument( + nx_type, + new LLVM.Name(store.reference()) + ), + ir_args[0] + )); + } + + return { + preamble: frag, + instruction: new LLVM.Argument( + new LLVM.Type(this.represent, 1), + new LLVM.Name(val.reference(), false) + ), + type: new TypeRef(this) + }; + }; + func.compile(); + + + return func; + } + + match (signature) { + if (this.signature.length != signature.length) { + return false; + } + + for (let i=0; i`, 0), // -1 + new LLVM.Type("i8", 0) + ], + ref + ); + } + + sizeof (ref) { + return { + preamble: new LLVM.Fragment(), + instruction: new LLVM.Argument( + new LLVM.Type("i64"), + new LLVM.Constant(this.getSize()+1), + ref + ) + }; + } + + + + + getDestructor() { + return this.destructor; + } + getCloner() { + return this.cloner; + } + + generateCloner() { + throw "unimplemented"; + } + generateDestructor() { + throw "unimplemented"; + } +} + +Either.Either_Instance = Either_Instance; + + +module.exports = Either; \ No newline at end of file diff --git a/compiler/primative/function_instance.js b/compiler/primative/function_instance.js index e48b6c2..1e0bb63 100644 --- a/compiler/primative/function_instance.js +++ b/compiler/primative/function_instance.js @@ -19,17 +19,29 @@ class Function_Instance { this.name = name; this.represent = `${this.name}.${this.id.toString(36)}.${this.ctx.getFile().getID().toString(36)}`; - - this.ir = new LLVM.Procedure ( - returnType.toLLVM(), - new LLVM.Name(this.represent, true), - signature.map ((arg, i) => new LLVM.Argument( - arg.toLLVM(), - new LLVM.Name(i.toString(), false) - )), - "#1", - false - ); + if (returnType.native) { + this.ir = new LLVM.Procedure ( + returnType.toLLVM(), + new LLVM.Name(this.represent, true), + signature.map ((arg, i) => new LLVM.Argument( + arg.toLLVM(), + new LLVM.Name(i.toString(), false) + )), + "#1", + false + ); + } else { + this.ir = new LLVM.Procedure ( + new LLVM.Type("void", 0, null), + new LLVM.Name(this.represent, true), + [returnType, ...signature].map ((arg, i) => new LLVM.Argument( + arg.toLLVM(), + new LLVM.Name(i.toString(), false) + )), + "#1", + false + ); + } } getFileID () { @@ -59,7 +71,9 @@ class Function_Instance { } toLLVM() { - return this.ir; + return this.isInline ? + new LLVM.Fragment() : + this.ir; } } diff --git a/compiler/primative/main.js b/compiler/primative/main.js index eeadd14..97adfb3 100644 --- a/compiler/primative/main.js +++ b/compiler/primative/main.js @@ -1,12 +1,12 @@ const File = require('./../component/file.js'); const Project = require('../component/project.js'); -const Function = require('./../component/function.js'); -const Array_Template = require('./array.js'); -const Blank = require('./blank.js'); +const Clone = require('./clone.js'); +const Either = require('./either.js'); const Static_Cast = require('./static_cast.js'); const Bitcast = require('./bitcast.js'); +const Printf = require('./printf.js'); const SizeOf = require('./sizeof.js'); const types = require('./types.js'); @@ -26,10 +26,10 @@ function Generate (ctx) { file.names.cast = new Static_Cast(file); file.names.bitcast = new Bitcast(file); + file.names.Clone = new Clone(file); file.names.sizeof = new SizeOf(file); - file.names.Blank = new Blank(file); - - // file.names.Array = new Array_Template(file); + file.names.Either = new Either(file); + file.names.printf = new Printf(file); ctx.inject(file); } diff --git a/compiler/primative/printf.js b/compiler/primative/printf.js new file mode 100644 index 0000000..0742251 --- /dev/null +++ b/compiler/primative/printf.js @@ -0,0 +1,57 @@ +const Function_Instance = require('./function_instance.js'); +const Template = require('../component/template.js'); +const LLVM = require('../middle/llvm.js'); + +const types = require('./types.js'); +const TypeRef = require('../component/typeRef.js'); + +class Template_Primative_Printf extends Template { + constructor (ctx) { + super(ctx, null); + + this.func = new Function_Instance(this, "printf", types.void, []); + this.func.generate = (regs, ir_args) => { + return { + preamble: new LLVM.Fragment(), + instruction: new LLVM.Call( + new LLVM.Type("void (i8*, ...)", 0), + new LLVM.Name("printf", true), + ir_args + ), + type: new TypeRef(types.void) + }; + }; + this.func.compile(); + } + + getFunction (access, signature) { + if (access.length != 0) { + return false; + } + + if (signature.length < 2) { + return false; + } + + if (signature[0].type != types.cstring) { + return false; + } + + if (signature + .map(x => !x.type.native && x.type.name != "cstring") + .reduce((state, curr) => state || curr, false) + ) { + return false; + } + + return this.func; + } + + toLLVM() { + let frag = new LLVM.Fragment(); + frag.append(new LLVM.Raw("declare dso_local void @printf(i8*, ...)")); + return frag; + } +} + +module.exports = Template_Primative_Printf; \ No newline at end of file diff --git a/compiler/primative/sizeof.js b/compiler/primative/sizeof.js index 7a53df8..df504f1 100644 --- a/compiler/primative/sizeof.js +++ b/compiler/primative/sizeof.js @@ -33,7 +33,7 @@ class Template_Primative_Size_Of extends Template { } let inputType = template[0]; - let outputType = new TypeRef(0, types.u64); + let outputType = new TypeRef(types.u64); let match = this.findMatch(inputType); if (match) { return match; @@ -60,7 +60,7 @@ class Template_Primative_Size_Of extends Template { func.ir.append(new LLVM.Return( new LLVM.Argument( outputType.toLLVM(), - new LLVM.Constant(inputType.pointer > 0 ? 8 : inputType.type.size, null) + new LLVM.Constant(inputType.pointer > 0 ? 8 : inputType.type.getSize(), null) ) )); diff --git a/compiler/primative/static_cast.js b/compiler/primative/static_cast.js index 04e069e..8e6479a 100644 --- a/compiler/primative/static_cast.js +++ b/compiler/primative/static_cast.js @@ -22,18 +22,26 @@ class Template_Primative_Static_Cast extends Template { return null; } - getFunction (access, signature, template) { - if (access.length !== 0) { + getFunction (access, signature) { + if (access.length !== 1) { return false; } // Check input lengths are correct - if (signature.length != 1 || template.length != 1) { + if (signature.length != 1 || access[0].value.length != 1) { return false; } + if (access[0].type != "access_template") { + return false; + } + + if (!(access[0].value[0] instanceof TypeRef)) { + throw new Error("Internal Error: Template was not resolved before attempt call resolution"); + } + let inputType = signature[0]; - let outputType = template[0]; + let outputType = access[0].value[0]; if (inputType == outputType) { return false; } @@ -61,13 +69,8 @@ class Template_Primative_Static_Cast extends Template { let func = new Function_Instance(this, "static_cast", outputType, [inputType]); func.isInline = false; - // Invalid value cast as one value is a pointer - if (inputType.pointer != outputType.pointer) { - return false; - } - - // If both types are primaives - if (types[inputType.type.name] && types[outputType.type.name]) { + // If both types are primitives + if (inputType.type.native && outputType.type.native) { // Same type of data (i.e. float float, or int int) if (inputType.type.cat == outputType.type.cat) { let mode = inputType.type.cat == "float" ? 2 : diff --git a/compiler/primative/types.js b/compiler/primative/types.js index 59f6dd0..0f1456c 100644 --- a/compiler/primative/types.js +++ b/compiler/primative/types.js @@ -84,7 +84,7 @@ let types = { } }, true), - unsafe_blob: new TypeDef(null, { + addr_space: new TypeDef(null, { tokens: [ { type : "name", @@ -158,15 +158,21 @@ types.uint = types.u64; // Update primative types correct type system for (let key in types) { - types[key].primative = true; + types[key].native = true; types[key].typeSystem = 'normal'; } types.cstring.name = "cstring"; types.cstring.typeSystem = "linear"; -types.cstring.primative = true; -types.unsafe_blob.name = "unsafe_blob"; -types.unsafe_blob.typeSystem = "normal"; -types.unsafe_blob.primative = true; +types.cstring.native = false; + +types.addr_space.name = "addr_space"; +// types.addr_space.typeSystem = "linear"; +types.addr_space.native = true; + + +for (let key in types) { + types[key].alignment = types[key].size; +} module.exports = types; \ No newline at end of file diff --git a/compiler/reserved.js b/compiler/reserved.js new file mode 100644 index 0000000..3e92f00 --- /dev/null +++ b/compiler/reserved.js @@ -0,0 +1,29 @@ +// This should be implemented as a bloom filter in the future +let words = [ + "let", + "struct", "new", + "impl", "trait", + "fn", + + "if", "elif", "else", + "return", + + "false", "true", + + "Self" +]; + + +/** + * Checks if a name is a reserved word + * @param {String} name + * @returns {Boolean} true means invalid + */ +function Check(name) { + return words.includes(name); +} + + +module.exports = { + Check +}; \ No newline at end of file diff --git a/compiler/test.js b/compiler/test.js index 4d168ad..810bace 100644 --- a/compiler/test.js +++ b/compiler/test.js @@ -8,20 +8,14 @@ const path = require('path'); let flags = { clang: process.argv.includes('--bin'), - exec: process.argv.includes('--exec') + exec: process.argv.includes('--exec'), + action: process.argv.includes('--action') }; if (flags.exec) { flags.clang = true; } -let extraArgs = []; -if (flags.exec) { - extraArgs.push('--execute'); -} else if (!flags.clang) { - extraArgs.push('--verifyOnly'); -} else { - extraArgs.push('--compileOnly'); -} +let extraArgs = ["--mode", "execute"]; const root = path.resolve(__dirname, "../"); @@ -31,6 +25,8 @@ let completed = 0; let fails = 0; let totalDuration = 0; +let failed_files = []; + function Compile(filename, id) { let target = path.relative(root, filename); @@ -44,7 +40,8 @@ function Compile(filename, id) { let start = Date.now(); let compile = spawn(`node`, [ "compiler/compile.js", target, - "-o", `./test/temp/${id}` + "-o", `./test/temp/${id}`, + '--verbose' ].concat(extraArgs), { cwd: path.resolve(__dirname, "../") }); @@ -61,6 +58,8 @@ function Compile(filename, id) { msg += "\n\n" + log; // only include the log on failure failed = true; fails++; + + failed_files.push(target); } let duration = (end-start)/1000; @@ -69,7 +68,7 @@ function Compile(filename, id) { completed++; - console.info("\nTest", completed, ' of ', total); + console.info("\nTest", completed, 'of', total); console.log(msg); console.info(failed ? " FAILED" : " success"); res(); @@ -80,9 +79,12 @@ function Compile(filename, id) { +let commentReg = new RegExp(/\s*#/g); + let tests = fs.readFileSync('./test/pre-alpha/_manifest_', 'utf8') .replace(/\r\n/g, "\n") .split('\n') + .filter(x => !commentReg.test(x)) .map( x => { return path.resolve("./test/pre-alpha", x); }); @@ -105,8 +107,13 @@ async function Test () { await Promise.all(tasks); - console.info(`\nFailed ${fails} of ${tests.length}`); - console.info(` Total Time: ${totalDuration.toFixed(3)}s`); + console.info('\nSummary:'); + if (fails > 0) { + console.info(` Failed Tests:\n ${failed_files.join("\n ")}`); + } + + console.info(` Failed ${fails} of ${tests.length}`); + console.info(` Total Compute Time: ${totalDuration.toFixed(3)}s`); if (fails > 0) { process.exit(1); diff --git a/compiler/timers.js b/compiler/timers.js new file mode 100644 index 0000000..cc96fcb --- /dev/null +++ b/compiler/timers.js @@ -0,0 +1,55 @@ +let enabled = false; +let timers = {}; + + +function Enable(names) { + for (let name of names) { + timers[name] = { + start: 0, + tally: 0 + }; + } + + enabled = true; +} + +function Checkpoint(name, start) { + if (!enabled) { + return; + } + + if (!timers[name]) { + timers[name] = { + start: 0, + tally: 0 + } + } + + if (start) { + timers[name].start = Date.now(); + } else { + let end = Date.now(); + timers[name].tally += end - timers[name].start; + } +} + +function Print() { + if (!enabled) { + return; + } + + console.log( + "Timers: \n" + + Object.keys(timers).map(key => ` - ${key}: ${timers[key].tally} ms`).join("\n") + + `\nTotal: ${ + Object.keys(timers) + .map(key => timers[key].tally) + .reduce((p, c) => p + c, 0) + } ms` + ); +} + + +module.exports = { + Enable, Checkpoint, Print +}; \ No newline at end of file diff --git a/docs/compilation.md b/docs/compilation.md new file mode 100644 index 0000000..fe08d4a --- /dev/null +++ b/docs/compilation.md @@ -0,0 +1,23 @@ +# Compilation + +## Windows + +### With Visual Studio + +```bash +winget install Kitware.CMake +winget install LLVM.LLVM +Microsoft.VisualStudio.2022.Community +``` + +### Without Visual Studio + +```bash +winget install Kitware.CMake +winget install LLVM.LLVM +winget install Ninja-build.Ninja +winget install Microsoft.VisualStudio.2022.BuildTools +``` + +Install required MS build tool components: + * Required component `Desktop Development with C++` \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..c174fff --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,113 @@ +cmake_minimum_required(VERSION 3.11 FATAL_ERROR) + +file(STRINGS "../VERSION" UNIVIEW_PROJECT_VERSION) +project(univiewlibs VERSION ${UNIVIEW_PROJECT_VERSION} LANGUAGES C CXX) + +find_program(CLANG_PATH clang++) +set(CMAKE_CXX_COMPILER ${CLANG_PATH}) +set(CMAKE_MAKE_PROGRAM "Ninja") +set(CMAKE_GENERATOR "Ninja") +set(CMAKE_LINKER lld) + +set(CMAKE_BUILD_TYPE "Release") + +include(ExternalProject) + + +# if(NOT DEFINED UNIVIEW_LIBS_BUILD_TYPE) +# set(UNIVIEW_LIBS_BUILD_TYPE Debug) +# endif() + +# System information +message("Setup Uniview Libs ${UNIVIEW_PROJECT_VERSION}") +message("-- CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") +message("-- CMAKE_MAKE_PROGRAM: ${CMAKE_MAKE_PROGRAM}") +message("-- CMAKE_GENERATOR: ${CMAKE_GENERATOR}") +message("-- CMAKE_LINKER: ${CMAKE_LINKER}") +message("-- CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}") +message("-- CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +message("-- CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") +message("-- CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message("-- CMAKE_SYSTEM: ${CMAKE_SYSTEM}") + +# LLVM does a better job of this, CMake's processor value is not valid for LLVM +# set(LLVM_DEFAULT_TARGET_TRIPLE "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_VERSION}") + +set(_compiler_arch ${CMAKE_C_COMPILER_ARCHITECTURE_ID}) +if("${_compiler_arch}" STREQUAL "") + set(_compiler_arch ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +message("Compiler architecture is ${_compiler_arch}") + +set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/install" CACHE PATH "Installation directory" FORCE) +message(STATUS "Install path: ${CMAKE_INSTALL_PREFIX}") + +set(CMAKE_BUILD_TYPE Release CACHE STRING "Set the build type") + + + +# Download the submodules if missing +find_package(Git) +if(GIT_FOUND) + if(EXISTS "${PROJECT_SOURCE_DIR}/../.git") + # Update submodules as needed + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Updating submodules...") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive --depth 1 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE git_submod_result) + if(NOT git_submod_result EQUAL "0") + message(FATAL_ERROR "git submodule update --init --recursive --depth 1 failed with ${git_submod_result}, please checkout submodules") + endif() + endif() + endif() +endif() + + + +################################################################# +# Add LLVM external LIB +################################################################# +# find_package(LLVM REQUIRED CONFIG) + +set(LLVM_TARGETS_TO_BUILD "ARM;RISCV;X86;") +set(LLVM_ENABLE_PROJECTS "llvm;lld") +set(LLVM_BUILD_JIT ON) + + +set(LLVM_ENABLE_COROUTINES ON) +set(LLVM_ENABLE_LLD_CPP ON) +set(LLVM_ENABLE_CXX_BINDINGS OFF) +# set(LLVM_ENABLE_LIBCXX OFF) +# set(LLVM_ENABLE_C_BINDINGS OFF) +# set(LLVM_ENABLE_LIBC OFF) +# set(LIBCXX_ENABLE_STATIC OFF) +# set(LIBC_ENABLE_STATIC OFF) + + +set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True) +set(HAVE_CXX_ATOMICS_WITHOUT_LIB True) + +set(LLVM_ENABLE_Z3_SOLVER OFF) +set(LLVM_ENABLE_ZLIB OFF) +set(LLVM_ENABLE_ZLIB:STRING OFF) + +# Disable examples/benchmarks/docs +set(LLVM_INCLUDE_EXAMPLES OFF) +set(LLVM_BUILD_EXAMPLES OFF) +set(LLVM_INCLUDE_BENCHMARKS OFF) +set(LLVM_BUILD_BENCHMARKS OFF) +set(LLVM_INCLUDE_TESTS OFF) +set(LLVM_BUILD_TESTS OFF) +set(LLVM_INCLUDE_TOOLS OFF) +set(LLVM_BUILD_TOOLS OFF) +set(LLVM_ENABLE_OCAMLDOC OFF) +set(LLVM_BUILD_DOCS OFF) +set(LLVM_ENABLE_DOXYGEN OFF) +set(LLVM_ENABLE_SPHINX OFF) + +add_subdirectory("llvm/llvm") +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Building for ${LLVM_TARGETS_TO_BUILD}") \ No newline at end of file diff --git a/lib/build/.gitignore b/lib/build/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/lib/build/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/lib/install.bash b/lib/install.bash new file mode 100644 index 0000000..3f2deaa --- /dev/null +++ b/lib/install.bash @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Save the directory of where the CLI was +last=`pwd` + +# cd to the directory of this script +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd $SCRIPT_DIR + +# TODO: Update the CLI title to "Configuring - Uniview Dependencies" +cmake -S . -B build -G Ninja +# TODO: Update the CLI title to "Building - Uniview Dependencies" +cmake --build build +# TODO: Update the CLI title to "Installing - Uniview Dependencies" +cmake --build build --target install + +# cd back to where the cli started this script from +cd $last \ No newline at end of file diff --git a/lib/install.bat b/lib/install.bat new file mode 100644 index 0000000..410fca0 --- /dev/null +++ b/lib/install.bat @@ -0,0 +1,16 @@ +@echo off + +@title "Uniview Dependencie Setup" + +@set last=%cd% +@cd %~dp0 + +@title Configuring - Uniview Dependencies +cmake -S . -B build +@title Building - Uniview Dependencies +cmake --build build +@title Installing - Uniview Dependencies +cmake --build build --target install + +@cd %last% +@echo on \ No newline at end of file diff --git a/lib/install/.gitignore b/lib/install/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/lib/install/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/lib/llvm b/lib/llvm new file mode 160000 index 0000000..8dfdcc7 --- /dev/null +++ b/lib/llvm @@ -0,0 +1 @@ +Subproject commit 8dfdcc7b7bf66834a761bd8de445840ef68e4d1a diff --git a/llvm/.gitignore b/llvm/.gitignore new file mode 100644 index 0000000..bc7947b --- /dev/null +++ b/llvm/.gitignore @@ -0,0 +1,3 @@ +* + +!.gitignore \ No newline at end of file diff --git a/package.json b/package.json index b4e71a5..f6f9d2d 100644 --- a/package.json +++ b/package.json @@ -2,42 +2,44 @@ "name": "@qupa/uniview", "version": "0.1.0", "description": "Uniview Language", + "repository": { + "type": "git", + "url": "git+https://github.com/qupa-project/uniview-lang.git" + }, + "author": "", + "license": "MIT", + "homepage": "https://github.com/qupa-project/uniview-lang#readme", + "bugs": { + "url": "https://github.com/qupa-project/uniview-lang/issues" + }, "main": "compile/compile.js", "preferGlobal": true, "engineStrict": true, "engines": { "node": ">=14" }, - "dependencies": { - "bnf-parser": "^2.2.5" - }, "scripts": { - "setup": "npm install && npm run build", - "start": "node compiler/compile.js", - "compile": "node compiler/compile.js", - "test": "npm run test-execution", - "test-translation": "node compiler/test.js", - "test-compilation": "node compiler/test.js --bin", - "test-execution": "node compiler/test.js --bin --exec", - "postinstall": "npm run build", - "build": "npm run build-syntax && npm run build-runtime", - "build-syntax": "node compiler/parser/build.js", - "build-runtime": "clang++ runtime/runtime.cpp -S -emit-llvm -o runtime/runtime.ll" + "test": "npm run test:execution", + "test:translation": "node compiler/test.js", + "test:compilation": "node compiler/test.js --bin", + "test:execution": "node compiler/test.js --bin --exec", + "postinstall": "run-s build:syntax install-tools", + "install-tools": "node tools/install.js", + "build": "run-s build:*", + "build:syntax": "node compiler/parser/build.js", + "build:tools": "cmake . -B build -G Ninja & cmake --build build" }, "bin": { - "uvc": "compiler/compile.js" + "uvc": "compiler/compile.js", + "uvc-env": "tools/env-binder.js" }, - "repository": { - "type": "git", - "url": "git+https://github.com/qupa-project/uniview-lang.git" + "devDependencies": { + "npm-run-all": "^4.1.5" }, - "author": "", - "license": "MIT", - "bugs": { - "url": "https://github.com/qupa-project/uniview-lang/issues" - }, - "homepage": "https://github.com/qupa-project/uniview-lang#readme", - "directories": { - "test": "test" + "dependencies": { + "bnf-parser": "^3.1.4", + "dotenv": "^16.0.1", + "node-getopt": "^0.3.2", + "unzipper": "^0.10.11" } } diff --git a/parse-test.js b/parse-test.js new file mode 100644 index 0000000..4c93851 --- /dev/null +++ b/parse-test.js @@ -0,0 +1,6 @@ +const Parse = require('./compiler/parser/parse.js'); +const fs = require('fs'); + + +let file = fs.readFileSync('./test.uv', 'utf8'); +Parse(file); \ No newline at end of file diff --git a/runtime/.gitignore b/runtime/.gitignore deleted file mode 100644 index d5978fa..0000000 --- a/runtime/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!prebuilt.ll -runtime.ll \ No newline at end of file diff --git a/runtime/runtime.cpp b/runtime/runtime.cpp deleted file mode 100644 index ffe45c5..0000000 --- a/runtime/runtime.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include - -extern "C" { - // Too much of a headache and segfaults in unix - // tm gmtime_safe(const time_t time) { - // tm tm_snapshot; - // #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) - // gmtime_s(&tm_snapshot, &time); - // #else - // gmtime_r(&time, &tm_snapshot); // POSIX - // #endif - - // return tm_snapshot; - // } - - // tm localtime_safe(const time_t time) { - // tm tm_snapshot; - // #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) - // localtime_s(&tm_snapshot, &time); - // #else - // localtime_r(&time, &tm_snapshot); // POSIX - // #endif - - // return tm_snapshot; - // } - - char* Offset(char* ptr, long amount) { - return ptr + amount; - } - - void i32_print(int val) { - std::cout << val; - } - void i64_print(long long int val) { - std::cout << val; - } - void f32_print(float val) { - std::cout << val; - } - void f64_print(double val) { - std::cout << val; - } - void i1_print(bool val) { - if (val) { - std::cout << "true"; - } else { - std::cout << "false"; - } - } - void str_print(char* val) { - std::cout << val; - } - void blob_print(char* val) { - std::cout << val; - } - - void i32_println(int val) { - std::cout << val << std::endl; - } - void i64_println(long long int val) { - std::cout << val << std::endl; - } - void f32_println(float val) { - std::cout << val << std::endl; - } - void f64_println(double val) { - std::cout << val << std::endl; - } - void i1_println(bool val) { - if (val) { - std::cout << "true" << std::endl; - } else { - std::cout << "false" << std::endl; - } - } - void str_println(char* val) { - std::cout << val << std::endl; - } - void blob_println(char* val) { - std::cout << val << std::endl; - } -} \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..6cc4f04 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +!main.uv \ No newline at end of file diff --git a/src/compiler/.gitignore b/src/compiler/.gitignore new file mode 100644 index 0000000..30ebcb2 --- /dev/null +++ b/src/compiler/.gitignore @@ -0,0 +1 @@ +!*.uv \ No newline at end of file diff --git a/src/core/main.c b/src/core/main.c new file mode 100644 index 0000000..63f98ea --- /dev/null +++ b/src/core/main.c @@ -0,0 +1,5 @@ +#include + +__declspec(dllexport) void fun() { + printf("fun() called from a static library"); +} \ No newline at end of file diff --git a/src/core/main.h b/src/core/main.h new file mode 100644 index 0000000..9bdd056 --- /dev/null +++ b/src/core/main.h @@ -0,0 +1,3 @@ +#pragma once + +void fun(void); \ No newline at end of file diff --git a/src/main.uv b/src/main.uv new file mode 100644 index 0000000..3436116 --- /dev/null +++ b/src/main.uv @@ -0,0 +1,95 @@ +import "experimental/file.uv"; +import "print.uv"; + +class Reference { + index: int; + line: int; + col: int; + + fn New(): Reference { + let out = Blank#[Reference](); + out.index = 0; + out.line = 1; + out.col = 1; + + return out; + } +} + +class ReferenceRange { + start: Reference; + end : Reference; + reach: Reference; + + fn New(): ReferenceRange { + let out = Blank#[ReferenceRange](); + out.start = Reference(); + out.end = Reference(); + out.reach = Reference(); + + return out; + } + + fn New(start: Reference): ReferenceRange { + let out = Blank#[ReferenceRange](); + out.start = $start; + out.end = start; + out.reach = Reference(); + + return out; + } + + fn Length(this: #ReferenceRange): int { + return this.end.index - this.start.index; + } + + fn IsFailure(this: #ReferenceRange): bool { + return ReferenceRange.Length(@this) < 1; + } +} + + + +fn MatchName(file: @File, ref: Reference): ReferenceRange { + let range = ReferenceRange($ref); + + let char = cast#[int](File.GetChar(@file)); + if (File.IsEOF($file)) { + delete range; + return ReferenceRange(ref); + } else { + delete ref; + } + + // If char is A-Z or a-z or 0-9 or _ + if ((65 <= char && char <= 90) || (97 <= char && char <= 122) || (48 <= char && char <= 57) || char == 95) { + range.end.index = range.end.index + 1; + range.end.col = range.end.col + 1; + } + + let next = MatchName(@file, $range.end); + range.end = next.end; + delete next; + + return range; +}; + + +fn main(): int { + println("Start"); + let file = File("./test.uv", "r"); + + let start = Reference(); + let res = MatchName(@file, start); + let len = ReferenceRange.Length(res); + if (len < 1) { + println("Failed"); + } else { + print("Consumed "); + print(len); + println(" tokens"); + } + + File.Delete(file); + return 0; +} \ No newline at end of file diff --git a/src/parser/.gitignore b/src/parser/.gitignore new file mode 100644 index 0000000..cabdf35 --- /dev/null +++ b/src/parser/.gitignore @@ -0,0 +1 @@ +*.uv \ No newline at end of file diff --git a/src/uvc-tool/main.cpp b/src/uvc-tool/main.cpp new file mode 100644 index 0000000..e1c62ce --- /dev/null +++ b/src/uvc-tool/main.cpp @@ -0,0 +1,375 @@ +#include "main.hpp" + + +using namespace llvm; +using namespace std; + +void ShowTriples(){ + std::string triple = llvm::sys::getDefaultTargetTriple(); + printf(" Default target triple: %s\n", triple.c_str()); + printf(" Supported targets:\n"); + for (const auto& target : llvm::TargetRegistry::targets()) { + printf(" %s\n", target.getName()); + } + + return; +} + +void InitializeTargets() { + // Manually initialize targets incase build device is using standard LLVM install + // If they aren't using the custom build InitAll will attempt to init unlinked targets + LLVMInitializeARMTargetInfo(); + LLVMInitializeARMTarget(); + LLVMInitializeARMTargetMC(); + LLVMInitializeARMAsmParser(); + LLVMInitializeARMAsmPrinter(); + LLVMInitializeRISCVTargetInfo(); + LLVMInitializeRISCVTarget(); + LLVMInitializeRISCVTargetMC(); + LLVMInitializeRISCVAsmParser(); + LLVMInitializeRISCVAsmPrinter(); + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86AsmParser(); + LLVMInitializeX86AsmPrinter(); + return; +} + + +Config IngestConfig(int argc, char* argv[]) { + Config config; + config.files.clear(); // init + config.mode = Exec_Mode::Object; + config.output = "out"; + config.opt = 0; + + config.target = llvm::sys::getDefaultTargetTriple(); + + // Loop through each argument + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--version") == 0) { + printf("Version: %s\n", "v0.0.0"); + printf(" LLVM: %i.%i.%i\n\n", LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH); + continue; + } + + if (strcmp(argv[i], "--verbose") == 0) { + setVerbose(true); + verbose("Verbose Enabled\n"); + continue; + } + + if (strcmp(argv[i], "--output") == 0 && i+1 < argc) { + config.output = argv[i+1]; + i++; + continue; + } + + if (strcmp(argv[i], "--target") == 0 && i+1 < argc) { + config.target = argv[i+1]; + i++; + continue; + } + + if (strcmp(argv[i], "--opt") == 0 && i+1 < argc) { + config.opt = atoi(argv[i+1]); + i++; + continue; + } + + if (strcmp(argv[i], "--mode") == 0 && i+1 < argc) { + switch (argv[i+1][0]) { + case 'r': + config.mode = Exec_Mode::Run; + break; + case 'i': + config.mode = Exec_Mode::IR; + break; + case 'b': + config.mode = Exec_Mode::Bitcode; + break; + case 'v': + config.mode = Exec_Mode::Verify; + break; + case 'o': + config.mode = Exec_Mode::Object; + break; + default: + printf("Warn: Unknown mode %s\n", argv[i+1]); + } + i++; + continue; + } + + verbose(" Attached input: %s\n", argv[i]); + config.files.push_back(argv[i]); + } + + + if (getVerbose()) { + printf("Config;\n"); + switch (config.mode) { + case Exec_Mode::Run: printf(" - Mode : Run\n"); break; + case Exec_Mode::IR: printf(" - Mode : IR\n"); break; + case Exec_Mode::Bitcode: printf(" - Mode : Bitcode\n"); break; + case Exec_Mode::Object: printf(" - Mode : Object\n"); break; + case Exec_Mode::Verify: printf(" - Mode : Verify\n"); break; + } + printf(" - Target : %s\n", config.target.c_str()); + printf(" - Opt : %i\n", config.opt); + printf(" - Output : %s\n", config.output.c_str()); + } + + return config; +} + + +Module* loadAndLinkModules(LLVMContext& context, const vector& files) { + SMDiagnostic error; + Module* main_module = nullptr; + + for (const auto& file : files) { + verbose("Parsing: %s\n", file.c_str()); + unique_ptr mod = parseIRFile(file, error, context); + if (!mod) { + error.print(/*Prompt=*/"", llvm::outs()); + return nullptr; + } + + verbose(" - Verifying module\n"); + string verifyErrors; + llvm::raw_string_ostream verifyStream(verifyErrors); + if (verifyModule(*mod, &verifyStream)) { + llvm::errs() << " Module verification failed: " << verifyStream.str() << "\n"; + return nullptr; + } + + if (main_module == nullptr) { + verbose(" - Found main module\n"); + main_module = mod.release(); + } else { + verbose(" - Linking to main module\n"); + bool err = Linker::linkModules(*main_module, std::move(mod)); + if (err) { + llvm::errs() << " Module linking failed: " << verifyStream.str() << "\n"; + return nullptr; + } + } + } + + return main_module; +} + + +void Optimise(Module* module, int level) { + verbose("Optimising O%d...\n", level); + + // Create the analysis managers. + LoopAnalysisManager LAM; + FunctionAnalysisManager FAM; + CGSCCAnalysisManager CGAM; + ModuleAnalysisManager MAM; + + PassBuilder PB; + PB.registerModuleAnalyses(MAM); + PB.registerCGSCCAnalyses(CGAM); + PB.registerFunctionAnalyses(FAM); + PB.registerLoopAnalyses(LAM); + PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); + + ModulePassManager MPM; + switch (level) { + case 1: + MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O1); break; + case 2: + MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O2); break; + case 3: + MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O3); break; + default: + MPM = PB.buildO0DefaultPipeline(OptimizationLevel::O0, false); + } + + // Optimize the IR + MPM.run(*module, MAM); + return; +} + + +int Output_Module_Bitcode(Module* module, bool bitcode, Config config) { + std::error_code ec; + llvm::raw_fd_ostream output(config.output, ec, llvm::sys::fs::OF_None); + + if (ec) { + llvm::errs() << "Error opening file: " << ec.message() << "\n"; + return 1; + } + + if (bitcode) { + WriteBitcodeToFile(*module, output); + } else { + module->print(output, nullptr); + } + + + output.flush(); + printf("Wrote: %s", config.output.c_str()); + return 0; +} + + +int Compile_Object(LLVMContext& ctx, Module* module, Config config) { + // Open output file + std::error_code EC; + llvm::raw_fd_ostream dest(config.output.c_str(), EC, llvm::sys::fs::OF_None); + if (EC) { + printf(" \u001b[31merror\u001b[0m: Could not open file \"%s\"\n", EC.message().c_str()); + return 1; + } + + // Initialize the target registry etc. + verbose("Initializing Targets...\n"); + InitializeTargets(); + + std::string err; + auto Target = TargetRegistry::lookupTarget(config.target, err); + if (!Target) { + printf("\n\u001b[31merror\u001b[0m: %s\n", err.c_str()); + + void ShowTriples(); + return 1; + } + + verbose(" - Starting target builder\n"); + TargetOptions opt; + auto RM = llvm::Optional(); + auto TheTargetMachine = Target->createTargetMachine(config.target, "generic", "", opt, RM); + + module->setDataLayout(TheTargetMachine->createDataLayout()); + + + llvm::legacy::PassManager pass; + + + // Optimisation pass + PassManagerBuilder passBuilder; + passBuilder.OptLevel = config.opt; + passBuilder.Inliner = llvm::createFunctionInliningPass(passBuilder.OptLevel, 0, true); + passBuilder.populateModulePassManager(pass); + + // Output pass + auto FileType = llvm::CGFT_ObjectFile; + if (TheTargetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) { + printf(" \u001b[31merror\u001b[0m: TheTargetMachine can't emit a file of this type\n"); + return 1; + } + + verbose(" - Running builder\n"); + pass.run(*module); + + dest.flush(); + + verbose("\n"); + printf("Wrote: %s\n", config.output.c_str()); + return 0; +} + + +int Execute_Module(LLVMContext& context, Module* module) { + // Initialize the target registry etc. + verbose("Initializing Targets...\n"); + InitializeTargets(); + + verbose("Execution:\n"); + + // Find the main function in the module + Function* mainFunction = module->getFunction("main"); + if (!mainFunction) { + printf("\n\u001b[31merror\u001b[0m: %s\n", "main function not found in module"); + return 1; + } + + // Verify the module to check for any errors + string error; + llvm::raw_string_ostream os(error); + if (llvm::verifyModule(*module, &os)) { + printf("\n\u001b[31merror\u001b[0m: %s\n %s\n", "module verification failed with", error.c_str()); + return 1; + } + + // Create an execution engine for the module + unique_ptr modulePtr(module); + EngineBuilder builder( std::move(modulePtr) ); + error.clear(); + builder.setErrorStr(&error); + builder.setEngineKind(llvm::EngineKind::JIT); + auto engine = builder.create(); + if (!engine) { + printf("\n\u001b[31merror\u001b[0m: %s\n %s\n\n", "Failed to create execution engine", error.c_str()); + + ShowTriples(); + return 1; + } + + // Execute the main function + vector args; + llvm::GenericValue result = engine->runFunction(mainFunction, args); + auto code = result.IntVal.getSExtValue(); + + verbose("\n\nExited: %lli\n", code); + return code; +} + + + +int main(int argc, char* argv[]) { + Config config = IngestConfig(argc, argv); + + if (getVerbose()) { + // Set stdout to unbuffered mode + if (setvbuf(stdout, nullptr, _IONBF, 0) != 0) { + std::perror("Failed to set stdout to unbuffered mode"); + return 1; + } + } + + verbose("Ingesting LLVM-IR\n"); + llvm::LLVMContext context; + auto mod = loadAndLinkModules(context, config.files); + if (!mod) { + std::cout << "Failed\n"; + return 1; + } + + mod->setTargetTriple(config.target); + + + Optimise(mod, config.opt); + + + switch (config.mode) { + case Exec_Mode::Verify: + verbose("Verifying...\n"); + llvm::verifyModule(*mod); + break; + case Exec_Mode::IR: + verbose("Writing LLVM-IR\n"); + config.output += ".ll"; + Output_Module_Bitcode(mod, false, config); + break; + case Exec_Mode::Bitcode: + config.output += ".bc"; + Output_Module_Bitcode(mod, true, config); + break; + case Exec_Mode::Run: + return Execute_Module(context, mod); + case Exec_Mode::Object: + verbose("Compiling object:\n"); + config.output += ".o"; + Compile_Object(context, mod, config); + break; + } + + verbose("\n"); + return 0; +} \ No newline at end of file diff --git a/src/uvc-tool/main.hpp b/src/uvc-tool/main.hpp new file mode 100644 index 0000000..c3a1388 --- /dev/null +++ b/src/uvc-tool/main.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +// #include + +#include "verbose.hpp" + +enum class Exec_Mode { + Run, + IR, + Bitcode, + Object, + Verify +}; + +struct Config { + Exec_Mode mode; + std::vector files; + std::string output; + std::string target; + int opt; +}; + +void InitializeTargets(); +void ShowTriples(); + +Config IngestConfig(int argc, char* argv[]); + +llvm::Module* loadAndLinkModules(llvm::LLVMContext& context, const std::vector& files); + +int Output_Module_Bitcode(llvm::Module* module, bool bitcode, Config config); + +int Execute_Module(llvm::LLVMContext& context, llvm::Module* module); + +int Compile_Object(llvm::LLVMContext& ctx, llvm::Module* module, Config config); + +void Optimise(llvm::Module* module); + +int main(int argc, char* argv[]); \ No newline at end of file diff --git a/src/uvc-tool/main.uv b/src/uvc-tool/main.uv new file mode 100644 index 0000000..5db4af4 --- /dev/null +++ b/src/uvc-tool/main.uv @@ -0,0 +1,44 @@ +include static "../../build/uvc-core.lib"; + +include static "../../lib/install/lib/LLVMAnalysis.lib"; +include static "../../lib/install/lib/LLVMBitReader.lib"; +include static "../../lib/install/lib/LLVMBitWriter.lib"; +include static "../../lib/install/lib/LLVMCore.lib"; +include static "../../lib/install/lib/LLVMExecutionEngine.lib"; +include static "../../lib/install/lib/LLVMIRReader.lib"; +include static "../../lib/install/lib/LLVMLinker.lib"; +include static "../../lib/install/lib/LLVMJITLink.lib"; +// include static "../../lib/install/lib/LLVMTarget.lib"; +// include static "../../lib/install/lib/LLVMX86AsmParser.lib"; +// include static "../../lib/install/lib/LLVMX86CodeGen.lib"; +// include static "../../lib/install/lib/LLVMX86Info.lib"; + +external assume { + fn fun (): void; + + fn LLVMContextCreate(): addr_space; + fn LLVMModuleCreateWithNameInContext(name: cstring, ctx: addr_space): addr_space; + fn LLVMContextDispose(ctx: addr_space); + + fn LLVMLinkInMCJIT(); + fn LLVMInitializeX86Target(); + fn LLVMInitializeX86AsmPrinter(); + fn LLVMInitializeX86AsmParser(); +} + +fn main(): int { + fun(); + + // let ctx = LLVMContextCreate(); + + LLVMLinkInMCJIT(); + // LLVMInitializeX86Target(); + // LLVMInitializeX86AsmPrinter(); + // LLVMInitializeX86AsmParser(); + + // LLVMContextDispose(ctx); + // printf("%s", "finished"); + // let mod = LLVMModuleCreateWithNameInContext("main", ctx); + + return 0; +} \ No newline at end of file diff --git a/src/uvc-tool/verbose.cpp b/src/uvc-tool/verbose.cpp new file mode 100644 index 0000000..f2fe60b --- /dev/null +++ b/src/uvc-tool/verbose.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "verbose.hpp" + +bool isVerbose = false; + +void setVerbose(bool setting) { + isVerbose = setting; +} + +int verbose(const char* format, ...) { + if (!isVerbose) { + return 0; + } + + va_list args; + va_start(args, format); + int ret = vprintf(format, args); + va_end(args); + + return ret; +} + +bool getVerbose() { + return isVerbose; +} \ No newline at end of file diff --git a/src/uvc-tool/verbose.hpp b/src/uvc-tool/verbose.hpp new file mode 100644 index 0000000..b41b803 --- /dev/null +++ b/src/uvc-tool/verbose.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +int verbose(const char * restrict, ...); + +bool getVerbose(); +void setVerbose(bool); \ No newline at end of file diff --git a/std/experimental/file.uv b/std/experimental/file.uv index fe3ec06..7fe02fb 100644 --- a/std/experimental/file.uv +++ b/std/experimental/file.uv @@ -1,20 +1,23 @@ external assume { fn fopen_s (fd: @File, path: cstring, access: cstring); - fn fgetc (fd: unsafe_blob): i8; - fn fclose (fd: unsafe_blob): int; - fn feof(fd: unsafe_blob): int; - fn fflush(fd: unsafe_blob); - fn fsetpos(fd: unsafe_blob, pos: int); - fn ftell(fd: unsafe_blob): int; + fn fgetc (fd: addr_space): i8; + fn fclose (fd: addr_space): int; + fn feof(fd: addr_space): int; + fn fflush(fd: addr_space); + fn fsetpos(fd: addr_space, pos: int); + fn ftell(fd: addr_space): int; } -class File { - ptr: unsafe_blob; +struct File { + ptr: addr_space; +} +impl File { fn New(path: cstring, access: cstring): File { - let this = Blank#[File](); - fopen_s(@this, path, access); + let this = new File { + ptr: fopen_s(@this, path, access); + }; return this; } diff --git a/std/experimental/time.uv b/std/experimental/time.uv index e4635c4..0065f4f 100644 --- a/std/experimental/time.uv +++ b/std/experimental/time.uv @@ -1,7 +1,3 @@ -include llvm "../../runtime/runtime.ll"; - -import "print.uv"; - external assume { // struct tm { // tm_sec: i32; // seconds after the minute - [0, 60] including leap second @@ -76,8 +72,6 @@ fn GetDate(unixTime: i64): Date { // y += (m <= 2); // std::cout << m << '/' << d << '/' << y << '\n'; // 8/21/2011 - let date = Blank#[Date](); - let s = unixTime; let z = s/86400 + 719468; @@ -95,7 +89,6 @@ fn GetDate(unixTime: i64): Date { let y = yoe + era*400; let doy = doe - (365*yoe + yoe/4 - yoe/100); let mp = (5*doy + 2)/153; - let d = doy - (153*mp + 2)/5 + 1; let t2: int; if (mp < 10) { @@ -109,16 +102,18 @@ fn GetDate(unixTime: i64): Date { y = y + 1; } - date.day = d; - date.month = m; - date.year = y; - let totalDays = unixTime / (24*60*60); - date.weekDay = (totalDays + 4) % 7; + let date = new Date { + day: doy - (153*mp + 2)/5 + 1; + month: m; + year: y; + + weekDay: (totalDays + 4) % 7; + days: doy; + }; // const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1; // [0, 365] - date.days = 0; return date; } @@ -126,19 +121,18 @@ fn GetDate(unixTime: i64): Date { fn GetTime(unixTime: i64): Time { let unix = cast#[int] (unixTime); - let t = Blank#[Time](); - t.seconds = unix % 60; - t.minutes = unix / 60 % 60; - t.hours = unix / 3600 % 24; - - return t; + return new Time { + seconds: unix % 60; + minutes: unix / 60 % 60; + hours: unix / 3600 % 24; + dayLightSavings: false; + }; } fn GetDateTime(unixTime: i64): DateTime { - let out = Blank#[DateTime](); - out.time = GetTime(unixTime); - out.date = GetDate(unixTime); - - return out; + return new DateTime { + time: GetTime(unixTime); + date: GetDate(unixTime); + }; } \ No newline at end of file diff --git a/std/memory.ll b/std/memory.ll new file mode 100644 index 0000000..3b3c9d0 --- /dev/null +++ b/std/memory.ll @@ -0,0 +1,7 @@ +define dso_local i8* @offset_ptr(i8* %0, i64 %1) { +2: + %3 = ptrtoint i8* %0 to i64 + %4 = add i64 %1, %3 + %5 = inttoptr i64 %4 to i8* + ret i8* %5 +} \ No newline at end of file diff --git a/std/memory.uv b/std/memory.uv index f4518dd..b9f47a2 100644 --- a/std/memory.uv +++ b/std/memory.uv @@ -1,11 +1,13 @@ +include llvm "memory.ll"; + external assume { - fn "llvm.memmove.p0i8.p0i8.i64" (dest: unsafe_blob, src: unsafe_blob, bytes: u64, volatile: bool) as Move; - fn "llvm.memcpy.p0i8.p0i8.i64" (dest: unsafe_blob, src: unsafe_blob, bytes: u64, volatile: bool) as Copy; - fn "llvm.memset.p0i8.i64" (dest: unsafe_blob, val: i8, bytes: u64, volatile: bool) as Set; + fn "llvm.memmove.p0i8.p0i8.i64" (dest: addr_space, src: addr_space, bytes: u64, volatile: bool) as Move; + fn "llvm.memcpy.p0i8.p0i8.i64" (dest: addr_space, src: addr_space, bytes: u64, volatile: bool) as Copy; + fn "llvm.memset.p0i8.i64" (dest: addr_space, val: i8, bytes: u64, volatile: bool) as Set; - fn "malloc" (size: u64): unsafe_blob as Alloc; - fn "realloc" (src: unsafe_blob, size: u64): unsafe_blob as Realloc; - fn "free" (addr: unsafe_blob) as Free; + fn "malloc" (size: u64): addr_space as Alloc; + fn "realloc" (src: addr_space, size: u64): addr_space as Realloc; + fn "free" (addr: addr_space) as Free; - fn Offset(addr: unsafe_blob, amount: i64): unsafe_blob; + fn "offset_ptr" (addr: addr_space, amount: i64): addr_space as Offset; } \ No newline at end of file diff --git a/std/print.uv b/std/print.uv deleted file mode 100644 index c000087..0000000 --- a/std/print.uv +++ /dev/null @@ -1,20 +0,0 @@ -include llvm "./../runtime/runtime.ll"; -external assume { - fn "i1_print" (val: bool) as print; - fn "i1_println" (val: bool) as println; - fn "i32_print" (val: i32) as print; - fn "i32_println" (val: i32) as println; - fn "i64_print" (val: i64) as print; - fn "i64_println" (val: i64) as println; - fn "f32_print" (val: float) as print; - fn "f32_println" (val: float) as println; - fn "f64_print" (val: double) as print; - fn "f64_println" (val: double) as println; - fn "str_print" (val: cstring) as print; - fn "str_println" (val: cstring) as println; - fn "blob_print" (val: unsafe_blob) as print; - fn "blob_println" (val: unsafe_blob) as println; - - fn puts (val : unsafe_blob); - fn putchar (val: i8); -} \ No newline at end of file diff --git a/std/string.uv b/std/string.uv index a8e6bfe..ba0d058 100644 --- a/std/string.uv +++ b/std/string.uv @@ -1,29 +1,30 @@ include llvm "./string.ll"; import "memory.uv" as Memory; -import "print.uv"; - external assume { fn "qupa.cstring.size" (str: cstring): u64 as sizeof; } -class String { - data: unsafe_blob; +struct String { + data: addr_space; size: u64; +} - fn New(str: cstring): String { - let out = Blank#[String](); - out.size = sizeof($str); - - out.data = Memory.Alloc(out.size); +impl String { + fn new(str: cstring): String { + let size = sizeof($str); + let out = new String { + data: Memory.Alloc(size); + size: size; + }; - let other = bitcast#[unsafe_blob](str); + let other = bitcast#[addr_space](str); Memory.Move($out.data, $other, out.size, false); return out; } - fn Append(this: @String, other: String) { + fn append(this: @String, other: String) { let nx_len = this.size + other.size - cast#[u64](1); this.data = Memory.Realloc(this.data, nx_len); @@ -31,27 +32,28 @@ class String { Memory.Move(insert, $other.data, other.size, false); this.size = nx_len; - String.Delete(other); } fn Slice(this: @String, begin: u64): String { return String.Slice(@this, begin, this.size); } - fn Slice(this: @String, begin: u64, end: u64): String { if (begin > this.size) { begin = this.size; } + if (end > this.size - cast#[u64](1)) { end = this.size - cast#[u64](1); } // Create a space for the new data - let sub = Blank#[String](); let length = end - begin; - sub.size = length + cast#[u64](1); - sub.data = Memory.Alloc(sub.size); + let size = length + cast#[u64](1); + let sub = new String { + size: size; + data: Memory.Alloc(size); + }; // Move the taken slice to the new string let sect_start = Memory.Offset($this.data, cast#[i64](begin)); @@ -72,12 +74,13 @@ class String { return sub; } - fn Slice(this: String, begin: u64): String { + + + fn slice(this: String, begin: u64): String { let end = this.size; - return String.Slice(this, begin, end); + return String.slice(this, begin, end); } - - fn Slice(this: String, begin: u64, end: u64): String { + fn slice(this: String, begin: u64, end: u64): String { if (begin > this.size) { begin = this.size; } @@ -86,10 +89,12 @@ class String { } // Create a space for the new data - let sub = Blank#[String](); let length = end - begin; - sub.size = length + cast#[u64](1); - sub.data = Memory.Alloc(sub.size); + let size = length + cast#[u64](1); + let sub = new String { + size: size; + data: Memory.Alloc(size); + }; // Move the taken slice to the new string let sect_start = Memory.Offset($this.data, cast#[i64](begin)); @@ -98,38 +103,45 @@ class String { // Add the null terminator let terminator = Memory.Offset($sub.data, cast#[i64](sub.size) -1); Memory.Set(terminator, cast#[i8](0), cast#[u64](1), false); - - String.Delete(this); return sub; } + fn length(this: $String): u64 { + let out: u64 = this.size - cast#[u64](1); + return out; + } +} - fn Length(this: #String): u64 { - return this.size - cast#[u64](1); + +impl Drop for String { + fn drop(this: @String) { + Memory.Free($this.data); } +} - fn Clone(this: @String): String { +impl Clone for String { + fn clone(this: @String): String { // Initiliase the data structure - let out = Blank#[String](); - out.data = Memory.Alloc(this.size); - out.size = this.size; + let data = Memory.Alloc(this.size); + + let out = new String { + data: data; + size: this.size; + }; // Copy the blob data to the new allocation Memory.Copy($out.data, $this.data, this.size, false); return out; } +} - fn Delete(this: String) { - Memory.Free(this.data); - delete this.data; - delete this.size; - } +fn puts(str: @String) { + printf("%s", bitcast#[cstring]($str.data)); } - -fn puts(str: #String) { - puts(str.data); +fn puts(str: String) { + printf("%s", bitcast#[cstring]($str.data)); } \ No newline at end of file diff --git a/std/uniview.uv b/std/uniview.uv index 87dfde7..304b9ce 100644 --- a/std/uniview.uv +++ b/std/uniview.uv @@ -3,5 +3,10 @@ the language to function properly =======================================*/ +trait Drop { + fn drop(this: @Self); +} -import "memory.uv"; \ No newline at end of file +trait Clone { + fn clone(this: @Self): Self; +} \ No newline at end of file diff --git a/test/pre-alpha/_manifest_ b/test/pre-alpha/_manifest_ index 8fcf8f0..7b0eccf 100644 --- a/test/pre-alpha/_manifest_ +++ b/test/pre-alpha/_manifest_ @@ -1,9 +1,11 @@ cast.uv -class.uv +# class.uv compare.uv +# either.uv +hello-world.uv if.uv -library-behaviour.uv +lending.uv math.uv -string-test.uv +# string-test.uv struct.uv time.uv \ No newline at end of file diff --git a/test/pre-alpha/cast.uv b/test/pre-alpha/cast.uv index 6ab5653..4455268 100644 --- a/test/pre-alpha/cast.uv +++ b/test/pre-alpha/cast.uv @@ -1,6 +1,3 @@ -import "print.uv"; - - fn CheckOverFlow(): int { let a = cast#[u8](128); let b = cast#[i8](a); @@ -63,14 +60,14 @@ fn CheckUnderflow(): int { fn main (): int { let res = CheckOverFlow(); if (res == -1) { - println("Failed overflow check"); + printf("%s", "Failed overflow check\n"); return 1; } res = CheckUnderflow(); if (res == -1) { - println("Failed underflow check"); + printf("%s", "Failed underflow check\n"); return 1; } diff --git a/test/pre-alpha/class.uv b/test/pre-alpha/class.uv index 03fbf41..3478496 100644 --- a/test/pre-alpha/class.uv +++ b/test/pre-alpha/class.uv @@ -1,43 +1,66 @@ -import "print.uv"; - -class Cat { +struct Cat { id: int; +} - fn New(id: int): Cat { - let out = Blank#[Cat](); - out.id = id; - return out; - } +impl Cat { + fn new(id: int): Cat { + let out = new Cat { + id: id; + }; - fn Say(this: Cat) { - print(this.id); - println(": meow"); - Cat.Delete(this); + return out; } - fn Say(this: @Cat) { - print(this.id); - println(": meow"); + fn say(this: $Cat) { + printf("%i: meow\n", this.id); + return; } +} - fn Clone(src: @Cat): Cat { - let out = Blank#[Cat](); - println("Cloning cat"); - out.id = src.id + 1; +impl Clone for Cat { + fn clone(src: @Self): Self { + let out = new Cat { + id: src.id + 1; + }; + printf("%s", "Cloning cat\n"); return out; } +} + +impl Drop for Cat { + fn drop(this: @Self) { + printf("%s", "Deleting cat...\n"); + return; + } +} + - fn Delete(this: Cat) { - println ("Deleting cat..."); - delete this.id; +struct CatCage { + inside: Cat; +} + +fn Consume(o: CatCage){ + return; +} + +impl CatCage { + fn new(cat: Cat): Self { + let o = new CatCage { + inside: cat; + }; + + return o; } } + fn main(): int { - let c = Cat.New(2); - Cat.Say($c); - Cat.Say(@c); - Cat.Delete(c); + let c = Cat.new(cast#[i64](2)); + Cat.say($c); + let c2 = Clone(@c); + Cat.say($c); + + let h = CatCage.new(c); return 0; } \ No newline at end of file diff --git a/test/pre-alpha/compare.uv b/test/pre-alpha/compare.uv index 3e75a1f..21f7b0b 100644 --- a/test/pre-alpha/compare.uv +++ b/test/pre-alpha/compare.uv @@ -11,20 +11,34 @@ fn CompareFloats() { return; } -fn CompareInts() { +fn CompareInts(): bool { let a = 3; let b = 7; let c = a == b; - c = a != b; - c = a > b; - c = a < b; - c = a >= b; - c = a <= b; - return; + if (a == b) { + return false; + } + if (4 < 3) { + return false; + } + if (a > b) { + return false; + } + if (4 >= 5) { + return false; + } + if (5 <= 4) { + return false; + } + if (1 != 1) { + return false; + } + + return true; } -fn CompareBools () { +fn CompareBools (): bool { let a = true; let b = false; @@ -32,13 +46,32 @@ fn CompareBools () { c = a || b; c = a && b; - return; + if ((true || false) != true) { + return false; + } + if (true && false != false) { + return false; + } + if (true && true != true) { + return false; + } + if (!true == true) { + return false; + } + + return true; } fn main(): int { CompareFloats(); - CompareInts(); - CompareBools(); + + + if (CompareInts() == false) { + return 1; + } + if (CompareBools() == false) { + return 1; + } return 0; } \ No newline at end of file diff --git a/test/pre-alpha/either.uv b/test/pre-alpha/either.uv new file mode 100644 index 0000000..1b74f29 --- /dev/null +++ b/test/pre-alpha/either.uv @@ -0,0 +1,26 @@ +fn Producer(switch: bool): Either#[void, int, float] { + if (switch) { + return Either#[void, int, float](void); + } else { + return Either#[void, int, float](3); + } +} + +fn main(): int { + let val = Producer(false); + + when val { + void { + printf("%s", "Failure"); + return 1; + } + int { + printf("Int: %i", val); // val is an integer within this scope + } + float { + printf("Float: %f", val); // val is a float within this scope + } + } + + return 0; +} \ No newline at end of file diff --git a/test/pre-alpha/hello-world.uv b/test/pre-alpha/hello-world.uv new file mode 100644 index 0000000..969a4c4 --- /dev/null +++ b/test/pre-alpha/hello-world.uv @@ -0,0 +1,5 @@ +fn main(): int { + printf("%s", "Hello World"); + + return 0; +} \ No newline at end of file diff --git a/test/pre-alpha/if.uv b/test/pre-alpha/if.uv index 4f6ec9a..1bb921d 100644 --- a/test/pre-alpha/if.uv +++ b/test/pre-alpha/if.uv @@ -1,14 +1,12 @@ -import "print.uv"; - fn test(a: int, b: int) { if (a > b) { - println("Greater"); + printf("%s", "Greater\n"); } elif (a < b) { - println("Lesser"); + printf("%s", "Lesser\n"); } elif (a > b) { - println("Logic error, should have already been caught"); + printf("%s", "Logic error, should have already been caught\n"); } else { - println("Equal"); + printf("%s", "Equal\n"); } return; @@ -16,14 +14,21 @@ fn test(a: int, b: int) { struct Test {} + +fn Consume(t: Test) { + return; +} + fn UnifiedUndefined() { - let t = Blank#[Test](); + let t = new Test {}; if (true) { - delete t; + Consume(t); } else { - delete t; + Consume(t); } + + return; } fn main(): int { diff --git a/test/pre-alpha/lending.uv b/test/pre-alpha/lending.uv new file mode 100644 index 0000000..b0615cc --- /dev/null +++ b/test/pre-alpha/lending.uv @@ -0,0 +1,50 @@ + +fn BoolLending(): int { + let a = false; + FlipFlop(@a); + Flip(@a); + + if (a == true) { + return 0; + } else { + return 1; + } +} +fn FlipFlop(val: @bool) { + Flip(@val); + Flip(@val); + return; +} +fn Flip(val: @bool) { + val = !val; + return; +} + + +fn IntLending(): int { + let a = 1; + Multiply(@a); + if (a != 3) { + return 1; + } + + Multiply(@a); + if (a != 9) { + return 1; + } + + return 0; +} + +fn Multiply(val: @int) { + val = val * 3; + return; +} + +fn main(): int { + if (IntLending() == 1) { + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/test/pre-alpha/library-behaviour.uv b/test/pre-alpha/library-behaviour.uv deleted file mode 100644 index f188fa6..0000000 --- a/test/pre-alpha/library-behaviour.uv +++ /dev/null @@ -1,22 +0,0 @@ -import "print.uv"; -import "print.uv"; // twice to check double importation behaviour - -import "print.uv" as std; // check name-space access - - -fn main(): int { - let a: int; - let b: i64; - let c: i64; - - a = 1; - b = 2; - - std.println(a); - println(b); - - c = a + b; - println(c); - - return 0; -} \ No newline at end of file diff --git a/test/pre-alpha/math.uv b/test/pre-alpha/math.uv index 1e78757..0936ea8 100644 --- a/test/pre-alpha/math.uv +++ b/test/pre-alpha/math.uv @@ -4,11 +4,26 @@ fn main (): int { let c = 3.0; let d = 4.0; - let res_int = a + b; - res_int = a - b; - res_int = a * b; - res_int = a / b; - res_int = a % b; + if (a + b != 3) { + printf("%s", "Failed add"); + return 1; + } + if (a - b != -1) { + printf("%s", "Failed subtract"); + return 1; + } + if (a * b != 2) { + printf("%s", "Failed multiply"); + return 1; + } + if (a / b != 0) { + printf("%s", "Failed divide"); + return 1; + } + if (a % b != 1) { + printf("%s", "Failed modulo"); + return 1; + } let res_float = c + d; res_float = c - d; diff --git a/test/pre-alpha/print.uv b/test/pre-alpha/print.uv new file mode 100644 index 0000000..1047309 --- /dev/null +++ b/test/pre-alpha/print.uv @@ -0,0 +1,3 @@ +fn main(): int { + fprint("%s", "Hello World"); +} \ No newline at end of file diff --git a/test/pre-alpha/string-test.uv b/test/pre-alpha/string-test.uv index 0ef79c0..801e75d 100644 --- a/test/pre-alpha/string-test.uv +++ b/test/pre-alpha/string-test.uv @@ -1,9 +1,8 @@ -import "print.uv"; import "string.uv"; fn main(): int { - let a = String("Hello"); - let b = String(" World"); + let a = String.New("Hello"); + let b = String.New(" World"); String.Append(@a, $b); puts($a); String.Append(@a, b); @@ -16,12 +15,8 @@ fn main(): int { puts($a); puts($t); - String.Delete(t); t = String.Slice(@a, s, e); puts($a); puts($t); - - String.Delete(a); - String.Delete(t); return 0; } diff --git a/test/pre-alpha/struct.uv b/test/pre-alpha/struct.uv index e79c4d6..6b54bdc 100644 --- a/test/pre-alpha/struct.uv +++ b/test/pre-alpha/struct.uv @@ -1,5 +1,3 @@ -import "print.uv"; - // Keep this function before struct definition: // - checks out of order declaration is working // - checks storing structs within structs @@ -31,18 +29,18 @@ struct Person { fn init(ofAge: bool): Person { - let p = Blank#[Person](); + let age = 16; if (ofAge) { - p.age = 21; - } else { - p.age = 16; + age = 21; } - p.id = 0; - p.finger = Blank#[FingerPrint](); - p.finger.id = 1; - - return p; + return new Person { + id: 0; + age: age; + finger: new FingerPrint { + id: 1; + }; + }; } fn age(p: @Person) { @@ -51,21 +49,14 @@ fn age(p: @Person) { } fn print(p: Person) { - print("Person { id: "); - print(p.id); - print(", age: "); - print(p.age); - print(", finger: "); + printf("Person {\n id: %i,\n age: %i,\n finger: ", p.id, p.age); print(p.finger); - print(" }"); + printf("%s", "\n}"); return; } fn print(f: FingerPrint) { - print("FingerPrint { id: "); - print(f.id); - print(" }"); - + printf("FingerPrint { id: %i }", f.id); return; } @@ -74,49 +65,53 @@ fn print(f: FingerPrint) { fn main(): int { let p = init(false); if (check(@p, 0, 16, 1) == 1) { - println("Failed init test"); + printf("%s", "Failed init test\n"); print(p); - print(" != Person { id: 0, age: 16, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person {\n id: 0,\n age: 16,\n finger: Finger Print { id: 1 }\n}\n"); return 1; } - p = init(true); if (check(@p, 0, 21, 1) == 1) { - println("Failed init test"); + printf("%s", "Failed init test\n"); print(p); - print(" != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }\n"); return 1; } - let q = $p; + Clone(1); + + let q = Clone#[Person](@p); if (check(@q, 0, 21, 1) == 1) { - println("Failed clone test"); + printf("%s", "Failed clone test\n"); print(q); - print(" != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }\n"); return 1; } age(@p); if (check(@p, 0, 22, 1) == 1) { - println("Failed clone independence test"); + printf("%s", "Failed clone independence test\n"); print(p); - print(" != Person { id: 0, age: 22, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person { id: 0, age: 22, finger: Finger Print { id: 1 } }\n"); return 1; } if (check(@q, 0, 21, 1) == 1) { - println("Failed clone independence test"); + printf("%s", "Failed clone independence test\n"); print(q); - print(" != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }\n"); return 1; } - p.finger = Blank#[FingerPrint](); + p.finger = new FingerPrint { + id: 12345; + }; + if (check(@p, 0, 22, 1) != 1) { - println("Failed changing sub structure"); + printf("%s", "Failed changing sub structure\n"); print(p); - print(" != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }"); + printf("%s", " != Person { id: 0, age: 21, finger: Finger Print { id: 1 } }\n"); return 1; } diff --git a/test/pre-alpha/time.uv b/test/pre-alpha/time.uv index 2939160..75a205e 100644 --- a/test/pre-alpha/time.uv +++ b/test/pre-alpha/time.uv @@ -1,5 +1,4 @@ import "experimental/time.uv" as Time; -import "print.uv"; fn println(dt: Time.DateTime) { let time = dt.time; @@ -9,44 +8,30 @@ fn println(dt: Time.DateTime) { time.hours = time.hours - 12; } - print(time.hours); - print(":"); - print(time.minutes); - print(":"); - print(time.seconds); + printf("%i:%i:%i", time.hours, time.minutes, time.seconds); if (pm) { - print("PM"); + printf("%s", " PM"); } else { - print("AM"); + printf("%s", " AM"); } - print(" "); let date = dt.date; - print(date.day); - print("/"); - print(date.month); - print("/"); - print(date.year); - print(" "); - print(date.weekDay); - print(" "); - println(date.days); + printf(" %i/%i/%i %i %i\n", date.day, date.month, date.year, date.weekDay, date.days); return; } fn main(): int { let now = Time.Now(); - print("Unix : "); - println(now); + printf("Unix : %i\n", now); - print("UTC : "); + printf("%s", "UTC : "); let dt = Time.GetDateTime(now); println(dt); dt = Time.GetDateTime(now + 10*60*60); - print("UTC+10 : "); + printf("%s", "UTC+10 : "); println(dt); return 0; diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..9ba3c41 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!env-bind.js +!install.js \ No newline at end of file diff --git a/tools/env-bind.js b/tools/env-bind.js new file mode 100644 index 0000000..5a4b57f --- /dev/null +++ b/tools/env-bind.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node +"use strict"; + +const path = require('path'); +const fs = require('fs'); + + +const env_path = path.resolve(__dirname, "../.env"); +let config = require('dotenv').config({path: env_path}).parsed; + +function UpdateEnv(delta) { + if (config === undefined) { + config = delta; + } else { + for (let key in delta) { + config[key] = delta[key]; + } + } + + fs.writeFileSync( + env_path, + Object.entries(config) + .map(([k, v]) => `${k}="${v}"`) + .join("\n") + ); +} + + +if (process.argv[1] == __filename) { + // Read in all arguments + let updates = {}; + for (let arg of process.argv.slice(2)) { + if (arg.indexOf("--") != 0) { + console.warn(`Warn: Invalid argument ${arg}`); + continue; + } + arg = arg.slice(2); + + let terms = arg.split("="); + if (!terms[1]) { + terms[1] = "true" + } + updates[terms[0]] = terms[1]; + } + + UpdateEnv(updates); + + console.log(`Env Updated: ${Object.keys(updates).join(", ")}`) +} + + +module.exports = {UpdateEnv}; \ No newline at end of file diff --git a/tools/install.js b/tools/install.js new file mode 100644 index 0000000..a74c039 --- /dev/null +++ b/tools/install.js @@ -0,0 +1,87 @@ +const unzipper = require('unzipper'); +const https = require('https'); +const fs = require('fs'); +const os = require('os'); + +const env = require('./env-bind.js'); +const path = require('path'); + +const installDir = __dirname; + +/** + * Download a resource from `url` to `dest`. + * @param {string} url - Valid URL to attempt download of resource + * @param {string} dest - Valid path to save the file. + * @returns {Promise} - Returns asynchronously when successfully completed download + */ +function Download(url, dest) { + return new Promise((resolve, reject) => { + // Check file does not exist yet before hitting network + fs.access(dest, fs.constants.F_OK, (err) => { + if (err === null) reject('File already exists'); + + const request = https.get(url, response => { + if (response.statusCode === 200) { + + const file = fs.createWriteStream(dest, { flags: 'wx' }); + file.on('finish', () => resolve()); + file.on('error', err => { + file.close(); + if (err.code === 'EEXIST') reject('File already exists'); + else fs.unlink(dest, () => reject(err.message)); // Delete temp file + }); + response.pipe(file); + } else if (response.statusCode === 302 || response.statusCode === 301) { + //Recursively follow redirects, only a 200 will resolve. + Download(response.headers.location, dest).then(resolve); + } else { + reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`); + } + }); + + request.on('error', err => { + reject(err.message); + }); + }); + }); +} + + +function Unzip(from, to) { + return new Promise((res, rej) => { + let stream = fs.createReadStream(from); + stream.pipe(unzipper.Extract({path: to})); + stream.on('close', res); + }); +} + + + + +async function InstallTools() { + console.info("Installing Tools..."); + + if (os.platform() == "win32") { + console.info(" The Windows version of LLVM does not contain certain prebuilt required tools"); + console.info(" Now locally installing tools for use"); + + console.info(" Downloading tools from https://github.com/qupa-project/uniview-lang/releases/tag/tools") + await Download( + 'https://github.com/qupa-project/uniview-lang/releases/download/tools-v0.0.2/tools.zip', + './tools.zip' + ); + + console.info(" Unzipping tools..."); + await Unzip('./tools.zip', installDir); + + env.UpdateEnv({uvc_tool: path.resolve(__dirname, "./uvc-tools.exe")}); + + console.info(" Installation complete"); + console.info(" Running cleanup"); + fs.unlinkSync('./tools.zip'); + } +} + +InstallTools() + .catch(console.error) + .then( ()=> {process.exit(0)} ) \ No newline at end of file