diff --git a/README.md b/README.md index a1e3f51..21e8743 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,38 @@ # Slothscript -A slow scripting language. +A scripting language for automation and system administration. ```sloth -func fib(n) { - if n <= 1 { return n } - fib(n - 1) + fib(n - 2) +# Single quotes delimit raw string literals, not supporting escapes or +# interpolation +let cxx = 'clang++' +let build_mode = 'Debug' +let generator = 'Ninja' + +# $scriptDir is set by the runtime to be the directory containing the +# currently running script (not necessarily the working directory). +# +# Double quotes delimit rich string literals, `${ expression }` is used for string +# interpolation +let build = Directory("${$scriptDir}/build") + +if not build.exists() { + build.create() } -print(fib(20)) +# Within this block, set the current working directory to be our build +# directory +with ($cwd = build.path) { + # The `!` postfix operator means spawn a sub-process, using the expression + # preceding it as a command and argument list, then block until the process + # exits + [ + 'cmake', $scriptDir, + "-DCMAKE_CXX_COMPILER=${cxx}", + "-DCMAKE_BUILD_TYPE=${build_mode}", + '-G', generator, + ]! +} ``` ## Installation @@ -70,6 +94,7 @@ print(counter()) - `false` - `Bool` literal - `null` - `Null` literal singleton - `not` - prefix logical NOT operator; `assert(not false)` +- `throw` - raise an exception ### Context Variables diff --git a/README.tmpl.md b/README.tmpl.md index baa7f50..1c2c76d 100644 --- a/README.tmpl.md +++ b/README.tmpl.md @@ -1,9 +1,9 @@ # Slothscript -A slow scripting language. +A scripting language for automation and system administration. ```sloth -{{ .test_green_specs_fibonacci_sloth }} +{{ .test_green_specs_configure_cmake_sloth }} ``` ## Installation @@ -53,6 +53,7 @@ First-class functions: - `false` - `Bool` literal - `null` - `Null` literal singleton - `not` - prefix logical NOT operator; `assert(not false)` +- `throw` - raise an exception ### Context Variables diff --git a/dbc.json b/dbc.json index 9218234..a79b757 100644 --- a/dbc.json +++ b/dbc.json @@ -4,14 +4,14 @@ "output": "README.md", "template": "README.tmpl.md", "inputs": [ - "test/green_specs/fibonacci.sloth", + "test/green_specs/configure_cmake.sloth", "test/green_specs/var_reference.sloth", "test/green_specs/first_class_func.sloth", "docs/context.md" ] }, { - "output": "test/green_specs/fibonacci.sloth", + "output": "test/green_specs/configure_cmake.sloth", "filter": "awk '/### Program/{f=1; next} /### /{f=0} f'" }, { diff --git a/dune-project b/dune-project index 37495e8..5a8c72a 100644 --- a/dune-project +++ b/dune-project @@ -8,9 +8,9 @@ (source (github christopherfujino/slothscript)) -(authors "Christopher Fujino") +(authors "Christopher Fujino ") -(maintainers "Christopher Fujino") +(maintainers "Christopher Fujino ") (license "BSD-3-Clause") diff --git a/lib/interpreter/native.ml b/lib/interpreter/native.ml index fc91b1b..fc5b398 100644 --- a/lib/interpreter/native.ml +++ b/lib/interpreter/native.ml @@ -620,8 +620,8 @@ module Make_test () : TestSig = struct | _ -> let msg = Printf.sprintf "Tried to execute sub-process %s but expected %s" - (List.to_string ~f:Fun.id hd.cmd) (List.to_string ~f:Fun.id proc.cmd) + (List.to_string ~f:Fun.id hd.cmd) in Error msg) in diff --git a/native/cpp/bytecode/.clangd b/native/cpp/bytecode/.clangd new file mode 100644 index 0000000..01cd9a9 --- /dev/null +++ b/native/cpp/bytecode/.clangd @@ -0,0 +1,4 @@ +CompileFlags: + # Un-comment this when we start building from Dune + #CompilationDatabase: ../.. + CompilationDatabase: build/ diff --git a/native/cpp/bytecode/CMakeLists.txt b/native/cpp/bytecode/CMakeLists.txt new file mode 100644 index 0000000..f707837 --- /dev/null +++ b/native/cpp/bytecode/CMakeLists.txt @@ -0,0 +1,38 @@ +## Global + +cmake_minimum_required(VERSION 3.31) +project(slothscript-cpp-bytecode VERSION 0.1) + +# These CMAKE_* variables apply to all future targets +set(CMAKE_CXX_EXTENSIONS OFF) # why don't want GNU extension +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Generate a compile_commands.json file +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# IWYU +find_program(IWYU_PATH NAMES include-what-you-use iwyu) +if(NOT IWYU_PATH) + message(FATAL_ERROR + "Could not find the program include-what-you-use (apt install iwyu)") +endif() +set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH}) + +add_compile_options(-Wall -Werror -Wpedantic -Wextra) + +# https://stackoverflow.com/questions/24648357/compiling-a-static-executable-with-cmake +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(BUILD_SHARED_LIBS OFF) +set(CMAKE_EXE_LINKER_FLAGS "-static") + +## Test exe + +add_executable(test test.cpp) + +target_link_libraries(test bytecode) +target_include_directories(test PRIVATE ../../../ignore/test.hpp) + +## Bytecode library + +add_library(bytecode STATIC bytecode.cpp) diff --git a/native/cpp/bytecode/bytecode.cpp b/native/cpp/bytecode/bytecode.cpp new file mode 100644 index 0000000..88f1e78 --- /dev/null +++ b/native/cpp/bytecode/bytecode.cpp @@ -0,0 +1,37 @@ +#include "bytecode.hpp" + +#include +#include +#include + +std::string Chunk::debug() { + std::string message; + size_t byteSize = bytes.size(); + for (size_t offset = 0; offset < byteSize;) { + auto pair = _debugInstruction(offset); + message += pair.first; + offset = pair.second; + } + + return message; +} + +std::pair Chunk::_debugInstruction(size_t offset) { + std::string message = std::format("{: 4} ", offset); + + switch ((OpCode)bytes[offset]) { + case OpCode::RETURN: + message += "RETURN\n"; + return {message, offset + 1}; + } + throw std::runtime_error("Unreachable"); +} + +Chunk::Chunk(std::vector bytes) : bytes(bytes) {} + +VM::VM(std::vector chunks) : _chunks(chunks) { + for (Chunk &chunk : chunks) { + // TODO: implement + ignore(chunk); + } +} diff --git a/native/cpp/bytecode/bytecode.hpp b/native/cpp/bytecode/bytecode.hpp new file mode 100644 index 0000000..bfb8eb4 --- /dev/null +++ b/native/cpp/bytecode/bytecode.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include // for size_t +#include +#include // for pair +#include + +static_assert(sizeof(double) == 8, "double is not 64-bit"); +static_assert(std::numeric_limits::is_iec559, + "double is not IEEE 754 compliant"); + +typedef double Value; + +enum class OpCode : uint8_t { + RETURN, +}; + +class Chunk { +public: + Chunk(std::vector bytes); + + std::string debug(); + + std::vector bytes; + +private: + std::pair _debugInstruction(size_t offset); +}; + +template inline void ignore(T) {} + +class VM { +public: + VM(std::vector chunks); + +private: + std::vector _chunks; +}; diff --git a/native/cpp/bytecode/configure.sloth b/native/cpp/bytecode/configure.sloth new file mode 100755 index 0000000..0eb1167 --- /dev/null +++ b/native/cpp/bytecode/configure.sloth @@ -0,0 +1,31 @@ +#!/usr/bin/env sloth + +func ensureTestHpp() { + let testHppDir = Directory("${$scriptDir}/../../../ignore/test.hpp") + if not testHppDir.exists() { + let monorepoDir = Directory("${$scriptDir}/../../../../../cpp/test.hpp") + if monorepoDir.exists() { + # TODO make these realpaths + ['ln', '-s', monorepoDir.path, testHppDir.path]! + } else { + let remote = 'git@github.com:christopherfujino/test.hpp.git' + ['git', 'clone', '--depth=1', remote, testHppDir.path]! + } + } +} + +ensureTestHpp() + +let build = Directory("${$scriptDir}/build") +if not build.exists() { + build.create() +} + +with ($cwd = build.path) { + [ + 'cmake', '..', + '-GNinja', + '-DCMAKE_CXX_COMPILER=clang++', + ]! + 'cmake --build .'! +} diff --git a/native/cpp/bytecode/test.cpp b/native/cpp/bytecode/test.cpp new file mode 100644 index 0000000..7e2fe2f --- /dev/null +++ b/native/cpp/bytecode/test.cpp @@ -0,0 +1,19 @@ +#include "test.hpp" +#include "bytecode.hpp" +#include + +using namespace _CHRIS_MONOREPO_CPP_TEST; + +static std::vector tests = { + {"RETURN is 0", []() { expect((int)OpCode::RETURN, 0); }}, + {"Chunk is just a vector", + []() { expect((int)sizeof(Chunk), (int)sizeof(std::vector)); }}, + {"Create a RETURN chunk", + []() { + Chunk chunk{{(uint8_t)OpCode::RETURN}}; + expect((int)chunk.bytes.size(), 1); + expect(chunk.debug(), std::string(" 0 RETURN\n")); + }}, +}; + +int main() { return Runner(tests).run(); } diff --git a/native/cpp/bytecode/test.sh b/native/cpp/bytecode/test.sh new file mode 100755 index 0000000..3c58349 --- /dev/null +++ b/native/cpp/bytecode/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPTDIR="$( dirname "$( realpath "${BASH_SOURCE[0]}" )" )" + +BUILD="${SCRIPTDIR}/build" + +cmake --build "$BUILD" +"${BUILD}/test" diff --git a/native/dune b/native/dune index 0df2a9f..4c54643 100644 --- a/native/dune +++ b/native/dune @@ -8,10 +8,10 @@ (data_only_dirs cpp) (rule + (targets libsloth_native.a compile_commands.json) (mode promote) (deps (source_tree cpp)) - (targets libsloth_native.a compile_commands.json) (action ; https://dune.readthedocs.io/en/stable/reference/actions/no-infer.html ; no infer because dune doesn't know how the targets are being created diff --git a/sloth_script.opam b/sloth_script.opam index 515680b..ac82471 100644 --- a/sloth_script.opam +++ b/sloth_script.opam @@ -2,8 +2,8 @@ opam-version: "2.0" synopsis: "A slow scripting language" description: "A longer description" -maintainer: ["Christopher Fujino"] -authors: ["Christopher Fujino"] +maintainer: ["Christopher Fujino "] +authors: ["Christopher Fujino "] license: "BSD-3-Clause" tags: ["topics" "to describe" "your" "project"] homepage: "https://github.com/christopherfujino/slothscript" diff --git a/test/green_specs/configure_cmake.sloth b/test/green_specs/configure_cmake.sloth new file mode 100644 index 0000000..96b633d --- /dev/null +++ b/test/green_specs/configure_cmake.sloth @@ -0,0 +1,197 @@ +### Program +# Single quotes delimit raw string literals, not supporting escapes or +# interpolation +let cxx = 'clang++' +let build_mode = 'Debug' +let generator = 'Ninja' + +# $scriptDir is set by the runtime to be the directory containing the +# currently running script (not necessarily the working directory). +# +# Double quotes delimit rich string literals, `${ expression }` is used for string +# interpolation +let build = Directory("${$scriptDir}/build") + +if not build.exists() { + build.create() +} + +# Within this block, set the current working directory to be our build +# directory +with ($cwd = build.path) { + # The `!` postfix operator means spawn a sub-process, using the expression + # preceding it as a command and argument list, then block until the process + # exits + [ + 'cmake', $scriptDir, + "-DCMAKE_CXX_COMPILER=${cxx}", + "-DCMAKE_BUILD_TYPE=${build_mode}", + '-G', generator, + ]! +} + +### Processes +(((cmd + (cmake + /parent + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_BUILD_TYPE=Debug + -G + Ninja)) + (instructions + ()))) + +### Ast +((StmtDecl + (ExprStmt + (LetExpr + cxx + (String + ((FullString + clang++ + )) + ) + ))) + (StmtDecl + (ExprStmt + (LetExpr + build_mode + (String + ((FullString + Debug + )) + ) + ))) + (StmtDecl + (ExprStmt + (LetExpr + generator + (String + ((FullString + Ninja + )) + ) + ))) + (StmtDecl + (ExprStmt + (LetExpr + build + (FuncInvoc + (IdRef + Directory + ) + ((String + ((StartStringInterp + "" + ) + (ExpressionStringInterp + (ContextId + $scriptDir + )) + (EndStringInterp + /build + )) + )) + ) + ))) + (StmtDecl + (ExprStmt + (IfExpr + (IfCont + (conditional + (UnaryExpr + (target + (FuncInvoc + (ObjDeref + (IdRef + build + ) + exists + ) + () + )) + (pos + ) + (operator + Not))) + (block + ((ExprStmt + (FuncInvoc + (ObjDeref + (IdRef + build + ) + create + ) + () + )))) + (continuation + ()) + (pos + )) + ))) + (StmtDecl + (ExprStmt + (WithExpr + (($cwd + (ObjDeref + (IdRef + build + ) + path + ))) + ((ExprStmt + (UnaryExpr + (target + (List + ((String + ((FullString + cmake + )) + ) + (ContextId + $scriptDir + ) + (String + ((StartStringInterp + -DCMAKE_CXX_COMPILER= + ) + (ExpressionStringInterp + (IdRef + cxx + )) + (EndStringInterp + "" + )) + ) + (String + ((StartStringInterp + -DCMAKE_BUILD_TYPE= + ) + (ExpressionStringInterp + (IdRef + build_mode + )) + (EndStringInterp + "" + )) + ) + (String + ((FullString + -G + )) + ) + (IdRef + generator + )) + )) + (pos + ) + (operator + Bang)))) + )))) + +### Stdout + + +### Failure