├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── compile.yml │ ├── flakehub-publish-tagged.yml │ └── release.yml ├── .gitignore ├── .tool-versions ├── LICENSE.md ├── README.md ├── aqua.yaml ├── build.zig ├── build.zig.zon ├── default.nix ├── demos ├── beer.dt ├── dtutils │ ├── README.md │ ├── false │ ├── head │ ├── tee │ ├── true │ └── yes ├── fib.dt ├── http-echo-server ├── joy-combinators.dt ├── red-green.dt ├── shell.dt └── 世界を挨拶.dt ├── dev ├── README.md └── dt ├── flake.lock ├── flake.nix ├── img ├── dt-logo-black.svg ├── dt-logo-white.svg └── dt-logo.svg ├── meta ├── emails │ ├── Re dt and Red Green quotes 2022-10-26T12_49_51+00 00.eml │ ├── Re dt and Red Green quotes 2022-10-26T15_38_21+00 00.eml │ ├── Re dt and Red Green quotes 2022-10-26T17_43_07+00 00.eml │ └── dt and Red Green quotes 2022-10-26T09_00_08+00 00.eml ├── ideas.md ├── misc │ └── rockstar_count └── quotes.md └── src ├── builtins.zig ├── dt.dt ├── inspiration ├── interpret.zig ├── main.zig ├── stdlib.dt ├── string.zig ├── tests ├── basic.dt ├── bool_tests.zig ├── def_scope_tests.zig ├── dt_args_basic.zig ├── dt_test_utils.zig ├── dtsh_interactive_basic.zig ├── dtsh_run_basic.zig ├── hello.dt ├── project_euler │ ├── problem-01.dt │ ├── problem-02a.dt │ ├── problem-02b.dt │ ├── problem-03.dt │ └── problem-04.dt └── project_euler_tests.zig ├── tokens.zig └── types.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | *.zig text eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository 2 | github: 3 | - so-dang-cool 4 | -------------------------------------------------------------------------------- /.github/workflows/compile.yml: -------------------------------------------------------------------------------- 1 | name: compile 2 | 3 | on: 4 | push: 5 | branches: [core] 6 | paths: ["**.zig", "**.dt", "src/inspiration", ".github/workflows/*.yml"] 7 | 8 | pull_request: 9 | branches: [core] 10 | paths: ["**.zig", "**.dt", "src/inspiration", ".github/workflows/*.yml"] 11 | 12 | jobs: 13 | compile: 14 | 15 | strategy: 16 | matrix: 17 | zig_version: [0.14.0, 0.13.0, 0.12.0, 0.11.0] 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Set up Zig 23 | uses: goto-bus-stop/setup-zig@v2 24 | with: 25 | version: ${{ matrix.zig_version }} 26 | 27 | - name: Check out repository 28 | uses: actions/checkout@v4 29 | 30 | - name: futz with build.zig.zon (name) 31 | if: matrix.zig_version != '0.14.0' 32 | run: | 33 | sed -i 's/\.dt/"dt"/' build.zig.zon 34 | 35 | - name: zig build 36 | run: | 37 | zig env 38 | zig build 39 | 40 | - name: zig build test 41 | run: | 42 | zig env 43 | zig build test 44 | 45 | - name: zig build cross 46 | run: | 47 | zig env 48 | zig build cross 49 | -------------------------------------------------------------------------------- /.github/workflows/flakehub-publish-tagged.yml: -------------------------------------------------------------------------------- 1 | name: "Publish tags to FlakeHub" 2 | on: 3 | push: 4 | tags: 5 | - "v?[0-9]+.[0-9]+.[0-9]+*" 6 | workflow_dispatch: 7 | inputs: 8 | tag: 9 | description: "The existing tag to publish to FlakeHub" 10 | type: "string" 11 | required: true 12 | jobs: 13 | flakehub-publish: 14 | runs-on: "ubuntu-latest" 15 | permissions: 16 | id-token: "write" 17 | contents: "read" 18 | steps: 19 | - uses: "actions/checkout@v3" 20 | with: 21 | ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" 22 | - uses: "DeterminateSystems/nix-installer-action@main" 23 | - uses: "DeterminateSystems/flakehub-push@main" 24 | with: 25 | visibility: "public" 26 | name: "so-dang-cool/dt" 27 | tag: "${{ inputs.tag }}" 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Set up Zig 11 | uses: goto-bus-stop/setup-zig@v2 12 | with: 13 | version: 0.14.0 14 | 15 | - name: Check out repository 16 | uses: actions/checkout@v4 17 | 18 | - name: zig build test 19 | run: | 20 | zig env 21 | zig build test 22 | 23 | - name: zig build cross -Doptimize=ReleaseSmall 24 | run: | 25 | zig env 26 | zig build cross -Doptimize=ReleaseSmall 27 | 28 | - name: Upload artifacts 29 | uses: skx/github-action-publish-binaries@master 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | with: 33 | args: "zig-out/bin/*.tgz" 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | docs/ 4 | .zig-cache/ 5 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | zig 0.14 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, J.R. Hill 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/github/license/so-dang-cool/dt)](https://github.com/so-dang-cool/dt/blob/core/LICENSE.md) 2 | [![unconventions.org](https://img.shields.io/badge/unconventions-%E7%84%A1%E9%99%90-orange)](https://unconventions.org) 3 | [![justforfunnoreally.dev badge](https://img.shields.io/badge/justforfunnoreally-dev-9ff)](https://justforfunnoreally.dev) 4 | 5 | # dt 6 | 7 | dt 8 | 9 | It's duct tape for your unix pipes. A programming language for doing small 10 | stuff fast, easy, and readable. 11 | 12 | In the words of [Red Green](https://www.redgreen.com): 13 | 14 | > Remember, it's only temporary... unless it works! 15 | 16 | Note: [The dt User Guide](https://dt.plumbing/user-guide/) exists but is still 17 | in progress. Basic usage is shown in the instructions below. 18 | 19 | 20 | ## Use in pipes 21 | 22 | When piping in/out, the REPL is skipped. If something is piping into `dt` then 23 | standard input is fed into `dt` as a list of lines. 24 | 25 | ``` 26 | $ seq 3 | dt rev pls 27 | 3 28 | 2 29 | 1 30 | ``` 31 | 32 | Great for aliases: 33 | 34 | ``` 35 | $ alias scream-lines="dt [upcase words unlines] map pls" 36 | $ echo "hey you pikachu" | scream-lines 37 | HEY 38 | YOU 39 | PIKACHU 40 | ``` 41 | 42 | If you want to read lines manually, use `stream` as the first command: 43 | 44 | ``` 45 | $ alias head.dt="dt stream [rl pl] args last to-int times" 46 | $ seq 100 | head.dt 3 47 | 1 48 | 2 49 | 3 50 | ``` 51 | 52 | ## Use as a shebang 53 | 54 | When the first argument to `dt` is a file starting with `#!` it will interpret 55 | the file. In short: `dt` supports shebang scripting. 56 | 57 | A naive tee implementation: 58 | 59 | `tee.dt` 60 | 61 | ``` 62 | #!/usr/bin/env dt 63 | 64 | readln unlines \stdin : 65 | args pop \file : 66 | 67 | stdin pl 68 | stdin file writef 69 | ``` 70 | 71 | Then use like: 72 | 73 | ``` 74 | cat wish-list | sed 's/red/green/g' | tee.dt new-wish-list 75 | ``` 76 | 77 | ## Interactive mode 78 | 79 | Running `dt` by itself with no pipes in or out starts a read-eval-print loop 80 | (REPL). 81 | 82 | ``` 83 | $ dt 84 | dt 1.x.x 85 | Now, this is only temporary... unless it works. 86 | » # Comments start with # 87 | » 88 | » 1 1 + println 89 | 2 90 | » 91 | » # Printing is common, so there are a bunch of printing shorthands. 92 | » # "p" is print, "pl" is print line, "pls" is print lines (i.e. of a list of values) 93 | » # Let's define a command that consumes a value, prints it, then returns its double. 94 | » 95 | » [ \n : n p " " p n 2 *] \print-and-double def 96 | » 97 | » 98 | » # And let's do it... 7 times! 99 | » 100 | » 1 \print-and-double 7 times drop 101 | 1 2 4 8 16 32 64 102 | » 103 | » 104 | » # You can conditionally execute code 105 | » 106 | » ["hi" pl] false do? 107 | » ["bye" pl] true do? 108 | bye 109 | » 110 | » quit 111 | ``` 112 | 113 | For a best experience, also install 114 | [`rlwrap`](https://github.com/hanslub42/rlwrap) and set a shell alias like so: 115 | 116 | ``` 117 | $ alias dtsh='rlwrap dt' 118 | $ dtsh 119 | dt 1.x.x 120 | Now, this is only temporary... unless it works. 121 | » 122 | ``` 123 | 124 | The above example assumes a bash-like shell. Details on the syntax and 125 | configuration files to set an alias that persists will vary by your shell. 126 | 127 | 128 | ## Installing 129 | 130 | [![Packaging status](https://repology.org/badge/vertical-allrepos/dt-script.svg)](https://repology.org/project/dt-script/versions) 131 | 132 | [![FlakeHub](https://img.shields.io/endpoint?url=https://flakehub.com/f/so-dang-cool/dt/badge)](https://flakehub.com/flake/so-dang-cool/dt) 133 | 134 | More ways to install: https://dt.plumbing/user-guide/install.html 135 | 136 | 137 | ## Community and resources 138 | 139 | Discuss dt on Discord. As interest grows and the language matures we will probably prefer 140 | discussion to be on platforms that can be indexed for searches. Let us know of other communities! 141 | 142 | * Scripters, packagers, and terminal fans: [![discord badge](https://img.shields.io/discord/1141777454164365382?logo=discord)](https://discord.gg/9ByutGCrJX) 143 | * Concatenative language theorists: [![discord badge](https://img.shields.io/discord/1150472957093744721?logo=discord)](https://discord.gg/pwrVPJJMfZ) 144 | 145 | Other resources: 146 | 147 | * [dt User Guide](https://dt.plumbing/user-guide/) ([sources](https://github.com/so-dang-cool/dt/tree/gh-pages)) 148 | * [dt on Rosetta Code](https://www.rosettacode.org/wiki/Category:Dt) 149 | * [dt on the concatenative wiki](https://concatenative.org/wiki/view/dt) 150 | 151 | ## The nerdy stuff 152 | 153 | The dt language is a functional programming language, and a 154 | [concatenative language](https://concatenative.org/wiki/view/Concatenative%20language), 155 | with a very imperative feel. For those interested, the user guide has a more 156 | in-depth discussion of [the language classification](https://dt.plumbing/user-guide/misc/classification.html). 157 | 158 | `dt` is implemented in Zig with no plans to self-host or rewrite in Rust or Go. 159 | Please do suggest better strategies for memory management and optimizations! I 160 | have some experience working at this level, but still much to learn. The 161 | current implementation is still fairly naive. 162 | 163 | ## Credits 164 | 165 | Shared as open source software, distributed under the terms of [the 3-Clause BSD License](https://opensource.org/license/BSD-3-clause/). 166 | 167 | By J.R. Hill | https://so.dang.cool | https://github.com/booniepepper 168 | 169 | -------------------------------------------------------------------------------- /aqua.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # aqua - Declarative CLI Version Manager 3 | # https://aquaproj.github.io/ 4 | # checksum: 5 | # enabled: true 6 | # require_checksum: true 7 | # supported_envs: 8 | # - all 9 | registries: 10 | - type: standard 11 | ref: v4.63.0 # renovate: depName=aquaproj/aqua-registry 12 | packages: 13 | - name: ziglang/zig@0.14.0 14 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Build = if (@hasDecl(std, "Build")) std.Build else std.build.Builder; 3 | 4 | pub fn build(b: *Build) !void { 5 | const target = b.standardTargetOptions(.{}); 6 | const optimize = if (comptime @hasDecl(Build, "standardOptimizeOption")) b.standardOptimizeOption(.{}) else b.standardReleaseOptions(); 7 | const root_source_file = if (@hasDecl(Build, "path")) b.path("src/main.zig") else Build.LazyPath.relative("src/main.zig"); 8 | 9 | // Dt executable 10 | const dt_step = b.step("dt", "Install dt executable"); 11 | 12 | const dt = b.addExecutable(.{ 13 | .name = "dt", 14 | .root_source_file = root_source_file, 15 | .optimize = optimize, 16 | .target = target, 17 | }); 18 | 19 | const dt_install = 20 | b.addInstallArtifact(dt, .{}); 21 | 22 | dt_step.dependOn(&dt_install.step); 23 | b.default_step.dependOn(dt_step); 24 | 25 | // Dt cross-compiled executables 26 | const cross_step = b.step("cross", "Install cross-compiled executables"); 27 | 28 | inline for (TRIPLES) |TRIPLE| { 29 | const exe = "dt-" ++ TRIPLE; 30 | 31 | const targetParser = if (comptime @hasDecl(std.Target, "Query")) 32 | // Zig 0.14 33 | std.Target.Query 34 | else 35 | // Zig 0.13 36 | std.zig.CrossTarget; 37 | 38 | const query = try targetParser.parse(.{ .arch_os_abi = TRIPLE }); 39 | 40 | const cross = b.addExecutable(.{ 41 | .name = exe, 42 | .root_source_file = root_source_file, 43 | .optimize = optimize, 44 | .target = if (comptime @hasDecl(std.zig.system, "resolveTargetQuery")) 45 | // Zig 0.12 46 | .{ .query = query, .result = try std.zig.system.resolveTargetQuery(query) } 47 | else 48 | // Zig 0.11 49 | query, 50 | }); 51 | 52 | const cross_install = 53 | b.addInstallArtifact(cross, .{}); 54 | 55 | const is_wasm = if (query.cpu_arch) |arch| arch.isWasm() else false; 56 | const exe_filename = if (is_wasm) exe ++ ".wasm" else if (query.os_tag == .windows) exe ++ ".exe" else exe; 57 | 58 | const cross_tar = b.addSystemCommand(&.{ 59 | "tar", "--transform", "s|" ++ exe ++ "|dt|", "-czvf", exe ++ ".tgz", exe_filename, 60 | }); 61 | 62 | const zig_out_bin = "./zig-out/bin/"; 63 | if (comptime @hasDecl(@TypeOf(cross_tar.*), "setCwd")) { 64 | if (comptime @hasDecl(Build, "path")) { 65 | // Zig 0.13.0 66 | cross_tar.setCwd(b.path(zig_out_bin)); 67 | } else { 68 | // Zig 0.12.0 69 | cross_tar.setCwd(.{ .path = zig_out_bin }); 70 | } 71 | } else { 72 | // Zig 0.11 73 | cross_tar.cwd = zig_out_bin; 74 | } 75 | 76 | cross_tar.step.dependOn(&cross_install.step); 77 | cross_step.dependOn(&cross_tar.step); 78 | } 79 | 80 | // Tests 81 | const test_step = b.step("test", "Run tests"); 82 | 83 | const test_exe = 84 | b.addTest(.{ 85 | .root_source_file = root_source_file, 86 | .optimize = optimize, 87 | .target = target, 88 | }); 89 | 90 | const test_run = b.addRunArtifact(test_exe); 91 | test_run.step.dependOn(dt_step); 92 | test_step.dependOn(&test_run.step); 93 | b.default_step.dependOn(test_step); 94 | } 95 | 96 | const TRIPLES = .{ 97 | "aarch64-linux-gnu", 98 | "aarch64-linux-musleabi", 99 | "aarch64-macos-none", 100 | "arm-linux-musleabi", 101 | "arm-linux-musleabihf", 102 | "mips-linux-gnu", 103 | "mips-linux-musl", 104 | "mips64-linux-gnuabi64", 105 | "mips64-linux-musl", 106 | "mips64el-linux-gnuabi64", 107 | "mips64el-linux-musl", 108 | "mipsel-linux-gnu", 109 | "mipsel-linux-musl", 110 | "powerpc-linux-gnu", 111 | "powerpc-linux-musl", 112 | "powerpc64le-linux-gnu", 113 | "powerpc64le-linux-musl", 114 | "riscv64-linux-gnu", 115 | "riscv64-linux-musl", 116 | "wasm32-wasi-musl", 117 | "x86_64-linux-gnu", 118 | "x86_64-linux-musl", 119 | "x86_64-macos-none", 120 | "x86-linux-gnu", 121 | "x86-linux-musl", 122 | "x86-windows-gnu", 123 | "x86_64-windows-gnu", 124 | }; 125 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .fingerprint = 0x1720097920e57ecd, 3 | .name = .dt, 4 | .version = "1.3.2", // Update in main.zig as well 5 | .paths = .{""}, 6 | } 7 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { lib 2 | , stdenv 3 | , testers 4 | , zig_0_12 5 | }: 6 | 7 | stdenv.mkDerivation (finalAttrs: { 8 | pname = "dt"; 9 | version = "1.3.1"; 10 | 11 | src = lib.cleanSource ./.; 12 | 13 | nativeBuildInputs = [ zig_0_12.hook ]; 14 | 15 | passthru.tests.version = testers.testVersion { package = finalAttrs.finalPackage; }; 16 | 17 | meta = { 18 | homepage = "https://dt.plumbing"; 19 | description = "Duct tape for your unix pipes"; 20 | longDescription = '' 21 | dt is a utility and programming language. The utility is intended for 22 | ergonomic in-the-shell execution. The language is straightforward (in 23 | the most literal sense) with a minimal syntax that allows for 24 | high-level, higher-order programming. 25 | 26 | It's meant to supplement (not replace!) other tools like awk, sed, 27 | xargs, and shell built-ins. Something like the Perl one-liners popular 28 | yesteryear, but hopefully easier to read and reason through. 29 | 30 | In short, dt is intended to be generally useful, with zero pretense of 31 | elegance. 32 | ''; 33 | changelog = "https://github.com/so-dang-cool/dt/releases/tag/v${finalAttrs.version}"; 34 | license = lib.licenses.bsd3; 35 | maintainers = with lib.maintainers; [ booniepepper ]; 36 | platforms = lib.platforms.unix; 37 | mainProgram = "dt"; 38 | }; 39 | }) 40 | -------------------------------------------------------------------------------- /demos/beer.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | [red "USAGE: beer.dt N" pl norm 1 exit] shebang-args not do? 4 | 5 | [ \n: 6 | n p " bottles of beer on the wall" pl 7 | n p " bottles of beeeer" pl 8 | "take one down, pass it around" pl n 1 - \m: 9 | m p " bottles of beer on the wall" pl nl 10 | [m bottles] m do? ] \bottles def 11 | 12 | shebang-args first \n: 13 | 14 | n bottles 15 | 16 | -------------------------------------------------------------------------------- /demos/dtutils/README.md: -------------------------------------------------------------------------------- 1 | ## dtutils 2 | 3 | A set of demo scripts showcasing common [POSIX utilities][util] 4 | ([full reference][posix]), and other common utilities present in software 5 | suites like GNU coreutils, busybox, etc. 6 | 7 | These scripts do not have a goal of being as-performant as implementations in 8 | C or other similar languages. 9 | 10 | If anyone is interested in fleshing these out, feel free to send a PR! 11 | 12 | 13 | [posix]: https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html 14 | [util]: https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html 15 | -------------------------------------------------------------------------------- /demos/dtutils/false: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | 1 exit 4 | -------------------------------------------------------------------------------- /demos/dtutils/head: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | shebang-args first to-int \n: 4 | 5 | [rl pl] n times 6 | -------------------------------------------------------------------------------- /demos/dtutils/tee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | readlns unlines \stdin : 4 | args pop \file : 5 | 6 | stdin pl 7 | stdin file writef 8 | -------------------------------------------------------------------------------- /demos/dtutils/true: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | 0 exit 4 | -------------------------------------------------------------------------------- /demos/dtutils/yes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | ["y" pl] loop 3 | 4 | -------------------------------------------------------------------------------- /demos/fib.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | args first \script-name: 4 | 5 | [ "USAGE: " script-name " N\n" 6 | "Prints N lines of fibonacci numbers starting at zero.\n" 7 | "N must be a positive integer.\n" 8 | ] \usage: 9 | 10 | [usage [do eprint] each 1 exit] \usage-fail def 11 | 12 | \usage-fail shebang-args len not do? 13 | 14 | shebang-args first to-int \n: 15 | 16 | n 1 - \n: 17 | \usage-fail n 0 lt? do? 18 | 19 | 0 pl 20 | 21 | 0 1 22 | 23 | [ [a b]: b pl 24 | a b + \c: 25 | [1 exit] c not do? 26 | b c ] n times 27 | -------------------------------------------------------------------------------- /demos/http-echo-server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! command -v nc >/dev/null 4 | then 5 | >&2 echo 'Requires netcat (nc) on PATH' 6 | exit 1 7 | elif ! command -v nc >/dev/null 8 | then 9 | >&2 echo 'Requires dt on PATH' 10 | exit 1 11 | fi 12 | 13 | while true 14 | do 15 | nc -l 5555 | dt \ 16 | 'deq swap words ... [body method path scheme]:' \ 17 | '"method: " p method pl' \ 18 | '" path: " p path pl' \ 19 | '"scheme: " p scheme pl' \ 20 | '"=== body ====" pl ' \ 21 | 'body pls' \ 22 | '"=== /body ===" pl' 23 | done 24 | -------------------------------------------------------------------------------- /demos/joy-combinators.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | # Joy combinators 4 | # From: http://tunes.org/~iepos/joy.html#appendix 5 | 6 | [ \drop \zap def 7 | 8 | \do \i def 9 | 10 | \quote \unit def 11 | 12 | [dup i i] \rep def 13 | 14 | [dup i] \m def 15 | 16 | [\as: 17 | as i as] \run def 18 | 19 | [[bs as]: 20 | as i] \k def 21 | 22 | [swap k] \z def 23 | 24 | [swap zap] \nip def 25 | 26 | [[bs as]: 27 | as i bs i] \sap def 28 | 29 | [[bs as]: 30 | as bs i] \t def 31 | 32 | [[bs as]: 33 | as i bs] \dip def 34 | 35 | \concat \cat def 36 | 37 | [swap cat] \swat def 38 | 39 | \enq \cons def 40 | 41 | [swap push] \take def 42 | 43 | \push \tack def 44 | 45 | [[bs as]: 46 | bs as i bs] \sip def 47 | 48 | [\dup dip i] \w def 49 | 50 | [\dup dip swap] \peek def 51 | 52 | [[bs as]: 53 | bs as cons 54 | bs as take ] \cake def 55 | 56 | [[cs bs as]: 57 | bs as] \poke def 58 | 59 | [\cons dip] \b def 60 | 61 | [\swap dip i] \c def 62 | 63 | [rot rot] \dig def 64 | 65 | \rot \bury def 66 | 67 | [bury swap] \flip def 68 | 69 | [[cs bs as]: 70 | cs bs cons 71 | cs as i] \s def 72 | 73 | [[ds cs bs as]: 74 | ds cs cons 75 | as i 76 | ds bs i] \s' def 77 | 78 | [[ds cs bs as]: 79 | cs ds as cons cons 80 | bs as i] \j def 81 | 82 | [[es ds cs bs as]: 83 | ds as cons 84 | es bs cons cat 85 | cs bs i] \j' def 86 | 87 | ] \using-joy-combinators def! 88 | 89 | 90 | 91 | "dt " p version pl 92 | "(With Joy combinators)" pl 93 | 94 | 95 | using-joy-combinators 96 | 97 | repl 98 | 99 | -------------------------------------------------------------------------------- /demos/red-green.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | # An example of mutual recursion. Pipe text (or direct a file into) this 4 | # script. Kinda like `cat` or `lolcat`. 5 | 6 | [ rl red pl green-line ] \red-line def 7 | [ rl green pl red-line ] \green-line def 8 | 9 | red-line 10 | -------------------------------------------------------------------------------- /demos/shell.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | # TODO: Try making a very simple repl-based shell 4 | 5 | ls .s 6 | 7 | repl 8 | 9 | -------------------------------------------------------------------------------- /demos/世界を挨拶.dt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dt 2 | 3 | # Not working yet... We'll need unicode support! 4 | # Must split on \u{3000}, and handle glyphs well. Maybe just use Zigstr once it supports Zig 0.11 5 | 6 | \def \です def! 7 | \do \を です 8 | \pl \言って です 9 | \get-line \聴いて です 10 | 11 | [["よろしくね、" 名前 "。"] を 言って] \挨拶 です 12 | 13 | "お名前は? " 言って 聴いて \名前 です 14 | 15 | \挨拶 を 16 | -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | This folder contains misc tools used while developing dt. 2 | 3 | You probably do not need these! 4 | 5 | -------------------------------------------------------------------------------- /dev/dt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | DT_ROOT="$(dirname "$0")/.." 6 | DT="$DT_ROOT/zig-out/bin/dt" 7 | 8 | # Running this outside the project root is likely an error: 9 | if [[ $DT_ROOT != dev/.. ]]; then 10 | >&2 echo "ERROR: $0 is intended to be run when developing dt." 11 | >&2 echo "If you're just trying to run a dt built from source:" 12 | >&2 echo "- Navigate to $DT_ROOT" 13 | >&2 echo "- Run './build release' to compile a release executable (Expects Zig 0.12)" 14 | >&2 echo "- Use the result at: $DT" 15 | exit 1 16 | fi 17 | 18 | zig build dt \ 19 | && >&2 echo 'INFO: dt compiled (zig build exited successfully)' \ 20 | && exec rlwrap "$DT" "$@" 21 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "narHash": "sha256-mnQjUcYgp9Guu3RNVAB2Srr1TqKcPpRXmJf4LJk6KRY=", 24 | "rev": "fdd898f8f79e8d2f99ed2ab6b3751811ef683242", 25 | "revCount": 531102, 26 | "type": "tarball", 27 | "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.531102%2Brev-fdd898f8f79e8d2f99ed2ab6b3751811ef683242/018af92d-4afe-74eb-9205-c0d057d7a0e4/source.tar.gz" 28 | }, 29 | "original": { 30 | "type": "tarball", 31 | "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.%2A.tar.gz" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | }, 40 | "systems": { 41 | "locked": { 42 | "lastModified": 1681028828, 43 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 44 | "owner": "nix-systems", 45 | "repo": "default", 46 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "nix-systems", 51 | "repo": "default", 52 | "type": "github" 53 | } 54 | } 55 | }, 56 | "root": "root", 57 | "version": 7 58 | } 59 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | # This flake was initially generated by fh, the CLI for FlakeHub (version 0.1.5) 2 | { 3 | description = "duct tape for your unix pipes"; 4 | 5 | inputs = { 6 | nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz"; 7 | flake-utils = { 8 | url = "github:numtide/flake-utils"; 9 | }; 10 | }; 11 | 12 | outputs = { self, nixpkgs, flake-utils, ... }: 13 | flake-utils.lib.eachDefaultSystem (system: let 14 | pkgs = import nixpkgs { inherit system; }; 15 | dt = pkgs.callPackage ./. { }; 16 | in { 17 | packages.default = dt; 18 | devShells.default = pkgs.mkShell { 19 | packages = with pkgs; [ zig_0_12 ]; 20 | }; 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /meta/emails/Re dt and Red Green quotes 2022-10-26T12_49_51+00 00.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | X-Original-To: hiljusti@so.dang.cool 3 | Delivered-To: hiljusti@so.dang.cool 4 | Authentication-Results: mailin033.protonmail.ch; dkim=pass (Good 2048 5 | bit rsa-sha256 signature) header.d=gmail.com header.a=rsa-sha256 6 | Authentication-Results: mailin033.protonmail.ch; arc=none smtp.remote-ip=209.85.166.174 7 | Authentication-Results: mailin033.protonmail.ch; dkim=pass (2048-bit key) 8 | header.d=gmail.com header.i=@gmail.com header.b="KJZPZCkK" 9 | Authentication-Results: mailin033.protonmail.ch; spf=pass smtp.mailfrom=gmail.com 10 | Authentication-Results: mailin033.protonmail.ch; dmarc=pass (p=none dis=none) 11 | header.from=gmail.com 12 | Received: from mail-il1-f174.google.com (mail-il1-f174.google.com [209.85.166.174]) 13 | (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) 14 | key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No 15 | client certificate requested) by mailin033.protonmail.ch (Postfix) with ESMTPS id 16 | 4My7ty14Y5z9vNQR for ; Wed, 26 Oct 2022 12:50:02 +0000 (UTC) 17 | Received: by mail-il1-f174.google.com with SMTP id o2so5281466ilo.8 18 | for ; Wed, 26 Oct 2022 05:50:01 -0700 (PDT) 19 | Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 20 | d=gmail.com; s=20210112; 21 | h=to:subject:message-id:date:from:in-reply-to:references:mime-version 22 | :from:to:cc:subject:date:message-id:reply-to; 23 | bh=GI2uffyE1kH33Fz+EOX4YNaJvcuAPd/O+J0zKtDtxjo=; 24 | b=KJZPZCkK9LNeyQJ1bYbORMqD4IvoDTDFApstTaJcLedv2C62nVhJKJ8cgAIIs7iRG7 25 | RCFFyEZanrfjNIEO7GTLPKmEIuCBYA2+i2Dk5h12MSO7lmotoo6N+p9jeq+YRxJwe8sc 26 | gj5mHVIYTjoOHrL0jtVPanu71MSRrQNTGSl3YnCS2F8TbsFngSvUpsTI6D7Z0LNxMp7u 27 | ztEyM6woSJMJxf3PnbEofmW+sHUf82Yw0zMKPxe78nMdmh4NlfJa6CJyctqyQ1NSIxQD 28 | bqYmVL5m5ZkgsESqy64yI3PVlutE0RMg5XX7PGUMf9Y+ZWhm7CGOH1D4VTO7oEzUN4Uo 29 | iH0Q== 30 | X-Google-Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 31 | d=1e100.net; s=20210112; 32 | h=to:subject:message-id:date:from:in-reply-to:references:mime-version 33 | :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; 34 | bh=GI2uffyE1kH33Fz+EOX4YNaJvcuAPd/O+J0zKtDtxjo=; 35 | b=66EA9AghLi5YFgxZfdJ8rp7F1NE4eZGERkJF5zIERbbRGtShRWVNIP+8o8T1XJs65p 36 | xPlG37dw96HCzMybNE9s7Fpr7qeX9EUoNkuMW/3TmUDQZvL5mHn3TFcjyfA3j3UvBBPS 37 | 5ynznS5R1egXNdmob20DV/E+sn96ETVercNF3Evnh3Ck0m8ryTvHfGbMjBRokBorVMYc 38 | ss3EXFlW3Eo9cYeePrCnCzErfhggC5FJXItILGDc8yt1av+e+mKbzZabXjxlmsfCE6j1 39 | 6vTBsiPRX+3ayfTbn2JIDqv8EedFmtGZBU7z+7iNhrUwLj3mFUEamZ5euAy/Qvyhecnv 40 | GvrA== 41 | X-Gm-Message-State: ACrzQf0p4tBjGwH3EPnn2JsJ8XAuz1ZgaCwVCFc3Vf+NWY40bkdziyyG 42 | AFgVXNNGK7pu/V+l50pvDJbjM000rIEAE4pggHrXFO4H 43 | X-Google-Smtp-Source: AMsMyM5xcDlMrZ33uFxTy73eR0E26+joq0SJn2sIr9k2kr87HB1DbOGuUhjQdYXvgq5UREURtzF98k5RziWRk2v2cnw= 44 | X-Received: by 2002:a05:6e02:164d:b0:2fc:636c:e233 with SMTP id 45 | v13-20020a056e02164d00b002fc636ce233mr26278462ilu.251.1666788600703; Wed, 26 Oct 2022 46 | 05:50:00 -0700 (PDT) 47 | Mime-Version: 1.0 48 | References: 49 | In-Reply-To: 50 | From: Dave Smith 51 | Date: Wed, 26 Oct 2022 08:49:51 -0400 52 | Message-Id: 53 | Subject: Re: "dt" and Red Green quotes 54 | To: "J.R. Hill" 55 | Content-Type: multipart/mixed;boundary=---------------------a979c1e72f31d2a4ccf6dba5d8ab2b70 56 | X-Spamd-Result: default: False [-1.92 / 25.00]; BAYES_HAM(-0.92)[86.27%]; 57 | DMARC_POLICY_ALLOW(-0.50)[gmail.com,none]; R_DKIM_ALLOW(-0.20)[gmail.com:s=20210112]; 58 | R_SPF_ALLOW(-0.20)[+ip4:209.85.128.0/17:c]; 59 | MIME_GOOD(-0.10)[multipart/alternative,text/plain]; ASN(0.00)[asn:15169, 60 | ipnet:209.85.128.0/17, country:US]; NEURAL_HAM(-0.00)[-0.987]; 61 | MIME_TRACE(0.00)[0:+,1:+,2:~]; FROM_EQ_ENVFROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; 62 | DKIM_TRACE(0.00)[gmail.com:+]; TO_DN_ALL(0.00)[]; MID_RHS_MATCH_FROMTLD(0.00)[]; 63 | FROM_HAS_DN(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; 64 | RCPT_COUNT_ONE(0.00)[1]; PREVIOUSLY_DELIVERED(0.00)[hiljusti@so.dang.cool]; 65 | ARC_NA(0.00)[] 66 | X-Rspamd-Queue-Id: 4My7ty14Y5z9vNQR 67 | X-Rspamd-Server: cp5-mailin-033.plabs.ch 68 | X-Pm-Spam: 0yeiAIic37iBOIJChpR3Y2bi4AiOiiW5abg3iiACLWbfxNvc2imUcOBi7TJCISQ6I 69 | EuIDgCwOIJlTgojITLuEIsOTQCJIU9kEgojITLwIAiLCPFJUR9FQEVkUUSUN9OSUgjoIMwCgk9lI 70 | WZ1J9pZ1vmZbIojgiAyeFUPJI6RCgHsIIBnfhB3cSb6IAuIDyDAMNAT23IjMjMyME1NTyDEOLACi 71 | k9WbWZfxR4aWgjoIMBC9g0HISfgwNyIniWQaOAii1MjNTOjJI0ZmymEMNJGlmZmYzN5YQwN29iBI 72 | LACiiE2ciO7BJpICz19ccFGtgojICMgwNjInl3JbIojggwSMnIlJ9ycG6CIdIICxgAjLESftFOUk 73 | NE9RXZ0Sg00TnRvJB1bSy2VcbFmtsBSZ2br9BycykW5Yb12cxAibjLgAtfSEOkFUR9ENOV0XkVSZ 74 | 0gT02W5RZxWvgUGc2cuVVyZGzHVIZJXul1WYGIvxtzb2hHJIbRmvuxVbTLuABSMCE1ZQXl0OT10X 75 | EULl9IRVSiBMQwk62FEIXZhJUgZ2wmVcdRXhvlGdiboAIpKzgG4XWIzw44SOSNx4YuNj0TcMIxGp 76 | lR3cCZpBB3bitC5bYlWspB3c2auUV0bmuVxXIADuTBCMEUfZVMSEO19TT50FQNFIjRgoVMSEkyBT 77 | bV2zv5GICdwBJsdWoXNaIFGuQNFIiRSBNvZWcmRcb0iwgAjL1UGBBBX1g1MUUB1GzBiOWZk5IgZX 78 | 0WFbYh2lTBycEUgYVjcmk3JbX4Ggw4CMEISZVNRUMUlQXZ0Sg00T2UuVVyZGtGVIYlWszlGIGIvN 79 | 1vbW5mxbIFGilNXdCZlBR1bmy2VcI1GhcxWaibwB92cmlWRacxluytFIWZnRVlcmympbdZHbdRXY 80 | 2Zh1wuaWt29YXxVuuADICMIB1MVEF01XUN1BgU0RkQE9ogWTNFRSTBCpsNmbWdlRBpZCtiBbZNXz 81 | ldWYGXt44xMCLERIS1UfMFkVUSgQVzTWn2FcZBSogMXYXYgQVhbGg3Qcb52lhZHIGbklRLIEgU0S 82 | bI3ggsER2cnlF0bmlXJdX4Gtx4CMEILR1fSUMkFVSRUfgYURWTzVFnc2oSBZYMXg2BSYWYpxBEZC 83 | N0lSI9GyLREIHIpN5hZ2yHVdZBSmt9mcGXg452ZWvWxZcUGtvJnZSbkB1hb2cW5abAiwgEjLERJt 84 | 9TTVOUdSRQUgzVWT2cnFBoZSgXMYYBSENl0SGIy9RLIEpHNIZ52hyVHdSZgw90bmlG5IYV2zyF2c 85 | Wa5x4gXGsmFdaRWcw0ibjLgEtJREWV9TQxUJB9FRSVNBNzZXlWdYIhGhhBycHIhZlkbGLERIS0Ug 86 | gI3bERgslnc20mFbdJXlyZGI2bc1BhbioXRdbI3nkByc2bh15caWgiIbfBS9 87 | X-Pm-Origin: external 88 | X-Pm-Transfer-Encryption: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) 89 | X-Pm-Content-Encryption: on-delivery 90 | X-Pm-Spamscore: 1 91 | X-Pm-Spam-Action: inbox 92 | 93 | -----------------------a979c1e72f31d2a4ccf6dba5d8ab2b70 94 | Content-Type: multipart/related;boundary=---------------------43918e2a05c5254f300962b0f65a847a 95 | 96 | -----------------------43918e2a05c5254f300962b0f65a847a 97 | Content-Type: text/html;charset=utf-8 98 | Content-Transfer-Encoding: base64 99 | 100 | PGRpdiBkaXI9Imx0ciI+U3VyZS4gIEdvb2QgbHVjayE8L2Rpdj48YnI+PGRpdiBjbGFzcz0iZ21h 101 | aWxfcXVvdGUiPjxkaXYgZGlyPSJsdHIiIGNsYXNzPSJnbWFpbF9hdHRyIj5PbiBXZWQsIE9jdCAy 102 | NiwgMjAyMiBhdCA1OjEwIEFNIEouUi4gSGlsbCAmbHQ7aGlsanVzdGlAc28uZGFuZy5jb29sJmd0 103 | OyB3cm90ZTo8YnI+PC9kaXY+PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0i 104 | bWFyZ2luOjBweCAwcHggMHB4IDAuOGV4O2JvcmRlci1sZWZ0OjFweCBzb2xpZCByZ2IoMjA0LDIw 105 | NCwyMDQpO3BhZGRpbmctbGVmdDoxZXgiPjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsO2Zv 106 | bnQtc2l6ZToxNHB4Ij5IZXkgdGhlcmUhPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6QXJp 107 | YWw7Zm9udC1zaXplOjE0cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlh 108 | bDtmb250LXNpemU6MTRweCI+SSYjMzk7bSBhIG1hbiA8aT4oYnV0IEkgY2FuIGNoYW5nZSwgaWYg 109 | SSBoYXZlIHRvLi4uIEkgZ3Vlc3MuLi4pPC9pPiB3aG8mIzM5O3Mgd29ya2luZyBvbiBhIG5ldyBw 110 | cm9ncmFtbWluZyBsYW5ndWFnZSBhbmQgbG93LWxldmVsIExpbnV4IHV0aWxpdHkgY2FsbGVkICZx 111 | dW90O2R0LiZxdW90OyBUaGUgbGFuZ3VhZ2UgYW5kIHRvb2wgaXMgcGFydGlhbGx5IGluc3BpcmVk 112 | IGJ5IGR1Y3QgdGFwZSBhbmQgYWxzbyBieSBoZWFsdGh5IGRvc2VzIG9mIHRoZSBSZWQgR3JlZW4g 113 | c2hvdyBhcyBJIHdhcyBncm93aW5nIHVwLiBUaGUgdmlzaW9uIGlzIGZvciAmcXVvdDtkdCZxdW90 114 | OyB0byBiZSBhIGRvLWFueXRoaW5nLCBmaXgtYW55dGhpbmcsIG9ubHktdGVtcG9yYXJ5LXVubGVz 115 | cy1pdC13b3JrcyB0b29sLjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbDtm 116 | b250LXNpemU6MTRweCI+PGJyPjwvZGl2Pgo8ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbDtm 117 | b250LXNpemU6MTRweCI+CiAgICA8ZGl2PgoKICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAg 118 | ICA8ZGl2Pkl0JiMzOTtzIGJlZW4gYSB3b3JrIGluIHByb2dyZXNzIGZvciBhYm91dCBhIHllYXIg 119 | bm93LiBNeSBkYXkgam9iIGlzIG1haW50YWluaW5nIHRoZSBpbnRlcm5hbCBzb2Z0d2FyZSBpbmZy 120 | YXN0cnVjdHVyZSBvZiBBbWF6b24gKHRoaW5rIFNjb3R0eSBmcm9tIFN0YXIgVHJlaykgc28gaXQm 121 | IzM5O3MgYSBzbG93IGpvdXJuZXkgaW4gbXkgc3BhcmUgdGltZS4uLiBidXQgSSYjMzk7bSBmaW5h 122 | bGx5IHJlYWR5IHRvIHN0YXJ0IGRvY3VtZW50aW5nIHRoZSBsYW5ndWFnZSBhbmQgZ2V0dGluZyBz 123 | b21lIGluaXRpYWwgZmVlZGJhY2suIEl0IHByb2JhYmx5IHdvbiYjMzk7dCBiZSByZWFkeSBmb3Ig 124 | cHJpbWUgdGltZSBmb3IgYW5vdGhlciB5ZWFyIG9yIHR3byBhZnRlciBmaW5pc2hpbmcgZG9jdW1l 125 | bnRhdGlvbiBhbmQuPGJyPjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+SWYgeW91JiMzOTtyZSBp 126 | bnRlcmVzdGVkIGluIG1vcmUgZGV0YWlscywgb3IgaGF2ZSBhIHRlY2gtc2F2dnkgcGVyc29uIHdo 127 | byBjYW4gdGVsbCB5b3UgaWYgSSYjMzk7bSA8aT5vZmYgbXkgcm9ja2VyPC9pPiBvciBpZiBJJiMz 128 | OTttIDxpPmFjdHVhbGx5IG9uIHRvIHNvbWV0aGluZzwvaT4sIHRoZSBtb3JlIGRldGFpbGVkIGV4 129 | cGxhbmF0aW9uIGlzIHRoYXQgSSYjMzk7bSBjcmVhdGluZyBhIG5ldyA8YSBocmVmPSJodHRwczov 130 | L2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGFja19tYWNoaW5lIiB0aXRsZT0ic3RhY2sgbWFjaGlu 131 | ZSIgdGFyZ2V0PSJfYmxhbmsiPnN0YWNrIG1hY2hpbmU8L2E+IGxpa2UgdGhlIDxhIGhyZWY9Imh0 132 | dHBzOi8vZG9jcy5vcmFjbGUuY29tL2phdmFzZS9zcGVjcy9qdm1zL3NlMTcvaHRtbC9pbmRleC5o 133 | dG1sIiB0aXRsZT0iSlZNIiB0YXJnZXQ9Il9ibGFuayI+SlZNPC9hPiBvciBsaWtlIDxhIGhyZWY9 134 | Imh0dHBzOi8vdGVudGhvdXNhbmRtZXRlcnMuY29tL2Jsb2cvcHl0aG9uLWJlaGluZC10aGUtc2Nl 135 | bmVzLTEtaG93LXRoZS1jcHl0aG9uLXZtLXdvcmtzLyIgdGl0bGU9IlB5dGhvbiYjMzk7cyBiYWNr 136 | ZW5kIiB0YXJnZXQ9Il9ibGFuayI+UHl0aG9uJiMzOTtzIGJhY2tlbmQ8L2E+IGFuZCAmcXVvdDtk 137 | dCZxdW90OyBpcyBteSBmaXJzdCBmcm9udGVuZC4gVGhlIGxhdGVyIHBsYW4gaXMgdGhhdCBpdCB3 138 | aWxsIGJlIGFibGUgdG8gdGFyZ2V0IG5hdGl2ZSBhcmNoaXRlY3R1cmVzIGxpa2UgTExWTSBidXQg 139 | YWxzbyB0YXJnZXQgb3RoZXIgVk1zIGFzIHdlbGwgYXMgYWxsb3cgb3RoZXIgbGFuZ3VhZ2VzIHRv 140 | IGNvbXBpbGUgZm9yIGl0Ljxicj48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PlRoZSBwcm9qZWN0 141 | cyBhbmQgd2Vic2l0ZSBhcmUgaGVyZTo8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pjx1bD48bGk+ 142 | PHNwYW4+TGFuZ3VhZ2UvVG9vbCBwcm9qZWN0OiA8YSByZWw9Im5vcmVmZXJyZXIgbm9mb2xsb3cg 143 | bm9vcGVuZXIiIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9kdCIgdGFyZ2V0PSJf 144 | YmxhbmsiPmh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9kdDwvYT48L3NwYW4+PC9saT48bGk+ 145 | PHNwYW4+Vk0gcHJvamVjdDogPHNwYW4+PGEgcmVsPSJub3JlZmVycmVyIG5vZm9sbG93IG5vb3Bl 146 | bmVyIiBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vaGlsanVzdGkvcmFpbCIgdGFyZ2V0PSJfYmxh 147 | bmsiPmh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9yYWlsPC9hPjwvc3Bhbj48YnI+PC9zcGFu 148 | PjwvbGk+PGxpPjxzcGFuPldlYnNpdGU6IDxhIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxvdyBub29w 149 | ZW5lciIgaHJlZj0iaHR0cHM6Ly9oaWxqdXN0aS5naXRodWIuaW8vZHQvIiB0YXJnZXQ9Il9ibGFu 150 | ayI+aHR0cHM6Ly9oaWxqdXN0aS5naXRodWIuaW8vZHQvPC9hPjwvc3Bhbj48L2xpPjwvdWw+PC9k 151 | aXY+PGRpdj48YnI+PC9kaXY+PGRpdj5Bbnl3YXkuLi4gSSBkbyBoYXZlIGEgcXVlc3Rpb24gaW4g 152 | YWxsIHRoaXMhPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj48Yj5JcyBpdCBvayBmb3IgbWUgdG8g 153 | dXNlIFJlZCBHcmVlbiBxdW90ZXM8L2I+IChlc3BlY2lhbGx5IGFib3V0IGR1Y3QgdGFwZSkgIGlu 154 | IHRoZSAmcXVvdDtkdCZxdW90OyB3ZWJzaXRlLCBpdHMgZG9jdW1lbnRhdGlvbiwgYW5kIGV2ZW4g 155 | YnVpbHQgaW50byB0aGUgbGFuZ3VhZ2UgaXRzZWxmPyBJJiMzOTtsbCBhbHdheXMgZ2l2ZSBhdHRy 156 | aWJ1dGlvbi48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PlRoYW5rcyEgS2VlcGluZyBteSBzdGlj 157 | ayBvbiB0aGUgaWNlLDxicj48L2Rpdj48ZGl2Pkp1c3RpbiBSIEhpbGw8YnI+PC9kaXY+CjwvZGl2 158 | Pgo8L2Jsb2NrcXVvdGU+PC9kaXY+Cg== 159 | -----------------------43918e2a05c5254f300962b0f65a847a-- 160 | -----------------------a979c1e72f31d2a4ccf6dba5d8ab2b70-- 161 | -------------------------------------------------------------------------------- /meta/emails/Re dt and Red Green quotes 2022-10-26T15_38_21+00 00.eml: -------------------------------------------------------------------------------- 1 | In-Reply-To: 2 | References: 3 | 4 | X-Pm-Origin: internal 5 | X-Pm-Content-Encryption: on-compose 6 | Subject: Re: "dt" and Red Green quotes 7 | To: redgreenjrtv@gmail.com 8 | From: J.R. Hill 9 | Date: Wed, 26 Oct 2022 15:38:21 +0000 10 | Mime-Version: 1.0 11 | Content-Type: multipart/mixed;boundary=---------------------ad6ad5723a2e7658b27887ea6badcb55 12 | Message-Id: <8ctPNpnS3RSHfZ9J5iuOBgYkCuQnlnIorYgHQa51xYau8TgbljgCD0pwaV7xzlRrMDLNbYp1W_Rsd2hMQGwbaNbPjHmnk1gk1mkQMAU7jLg=@so.dang.cool> 13 | X-Pm-Recipient-Authentication: redgreenjrtv%40gmail.com=none 14 | X-Pm-Recipient-Encryption: redgreenjrtv%40gmail.com=none 15 | 16 | -----------------------ad6ad5723a2e7658b27887ea6badcb55 17 | Content-Type: multipart/related;boundary=---------------------b3c65a9764e1833c394949e484ec0969 18 | 19 | -----------------------b3c65a9764e1833c394949e484ec0969 20 | Content-Type: text/html;charset=utf-8 21 | Content-Transfer-Encoding: base64 22 | 23 | VGhhbmtzITxicj48YnI+PGJyPjxicj4tLS0tLS0tLSBPcmlnaW5hbCBNZXNzYWdlIC0tLS0tLS0t 24 | PGJyPk9uIE9jdCAyNiwgMjAyMiwgNTo0OSBBTSwgRGF2ZSBTbWl0aCA8IHJlZGdyZWVuanJ0dkBn 25 | bWFpbC5jb20+IHdyb3RlOjxibG9ja3F1b3RlIGNsYXNzPSJwcm90b25tYWlsX3F1b3RlIj48YnI+ 26 | PGRpdiBkaXI9Imx0ciI+U3VyZS4gIEdvb2QgbHVjayE8L2Rpdj48YnI+PGRpdiBjbGFzcz0iZ21h 27 | aWxfcXVvdGUiPjxkaXYgZGlyPSJsdHIiIGNsYXNzPSJnbWFpbF9hdHRyIj5PbiBXZWQsIE9jdCAy 28 | NiwgMjAyMiBhdCA1OjEwIEFNIEouUi4gSGlsbCAmbHQ7aGlsanVzdGlAc28uZGFuZy5jb29sJmd0 29 | OyB3cm90ZTo8YnI+PC9kaXY+PGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0i 30 | bWFyZ2luOjBweCAwcHggMHB4IDAuOGV4O2JvcmRlci1sZWZ0OjFweCBzb2xpZCByZ2IoMjA0LDIw 31 | NCwyMDQpO3BhZGRpbmctbGVmdDoxZXgiPjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsO2Zv 32 | bnQtc2l6ZToxNHB4Ij5IZXkgdGhlcmUhPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6QXJp 33 | YWw7Zm9udC1zaXplOjE0cHgiPjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlh 34 | bDtmb250LXNpemU6MTRweCI+SSYjMzk7bSBhIG1hbiA8aT4oYnV0IEkgY2FuIGNoYW5nZSwgaWYg 35 | SSBoYXZlIHRvLi4uIEkgZ3Vlc3MuLi4pPC9pPiB3aG8mIzM5O3Mgd29ya2luZyBvbiBhIG5ldyBw 36 | cm9ncmFtbWluZyBsYW5ndWFnZSBhbmQgbG93LWxldmVsIExpbnV4IHV0aWxpdHkgY2FsbGVkICZx 37 | dW90O2R0LiZxdW90OyBUaGUgbGFuZ3VhZ2UgYW5kIHRvb2wgaXMgcGFydGlhbGx5IGluc3BpcmVk 38 | IGJ5IGR1Y3QgdGFwZSBhbmQgYWxzbyBieSBoZWFsdGh5IGRvc2VzIG9mIHRoZSBSZWQgR3JlZW4g 39 | c2hvdyBhcyBJIHdhcyBncm93aW5nIHVwLiBUaGUgdmlzaW9uIGlzIGZvciAmcXVvdDtkdCZxdW90 40 | OyB0byBiZSBhIGRvLWFueXRoaW5nLCBmaXgtYW55dGhpbmcsIG9ubHktdGVtcG9yYXJ5LXVubGVz 41 | cy1pdC13b3JrcyB0b29sLjxicj48L2Rpdj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbDtm 42 | b250LXNpemU6MTRweCI+PGJyPjwvZGl2Pgo8ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbDtm 43 | b250LXNpemU6MTRweCI+CiAgICA8ZGl2PgoKICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAg 44 | ICA8ZGl2Pkl0JiMzOTtzIGJlZW4gYSB3b3JrIGluIHByb2dyZXNzIGZvciBhYm91dCBhIHllYXIg 45 | bm93LiBNeSBkYXkgam9iIGlzIG1haW50YWluaW5nIHRoZSBpbnRlcm5hbCBzb2Z0d2FyZSBpbmZy 46 | YXN0cnVjdHVyZSBvZiBBbWF6b24gKHRoaW5rIFNjb3R0eSBmcm9tIFN0YXIgVHJlaykgc28gaXQm 47 | IzM5O3MgYSBzbG93IGpvdXJuZXkgaW4gbXkgc3BhcmUgdGltZS4uLiBidXQgSSYjMzk7bSBmaW5h 48 | bGx5IHJlYWR5IHRvIHN0YXJ0IGRvY3VtZW50aW5nIHRoZSBsYW5ndWFnZSBhbmQgZ2V0dGluZyBz 49 | b21lIGluaXRpYWwgZmVlZGJhY2suIEl0IHByb2JhYmx5IHdvbiYjMzk7dCBiZSByZWFkeSBmb3Ig 50 | cHJpbWUgdGltZSBmb3IgYW5vdGhlciB5ZWFyIG9yIHR3byBhZnRlciBmaW5pc2hpbmcgZG9jdW1l 51 | bnRhdGlvbiBhbmQuPGJyPjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+SWYgeW91JiMzOTtyZSBp 52 | bnRlcmVzdGVkIGluIG1vcmUgZGV0YWlscywgb3IgaGF2ZSBhIHRlY2gtc2F2dnkgcGVyc29uIHdo 53 | byBjYW4gdGVsbCB5b3UgaWYgSSYjMzk7bSA8aT5vZmYgbXkgcm9ja2VyPC9pPiBvciBpZiBJJiMz 54 | OTttIDxpPmFjdHVhbGx5IG9uIHRvIHNvbWV0aGluZzwvaT4sIHRoZSBtb3JlIGRldGFpbGVkIGV4 55 | cGxhbmF0aW9uIGlzIHRoYXQgSSYjMzk7bSBjcmVhdGluZyBhIG5ldyA8YSBocmVmPSJodHRwczov 56 | L2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGFja19tYWNoaW5lIiB0aXRsZT0ic3RhY2sgbWFjaGlu 57 | ZSIgdGFyZ2V0PSJfYmxhbmsiPnN0YWNrIG1hY2hpbmU8L2E+IGxpa2UgdGhlIDxhIGhyZWY9Imh0 58 | dHBzOi8vZG9jcy5vcmFjbGUuY29tL2phdmFzZS9zcGVjcy9qdm1zL3NlMTcvaHRtbC9pbmRleC5o 59 | dG1sIiB0aXRsZT0iSlZNIiB0YXJnZXQ9Il9ibGFuayI+SlZNPC9hPiBvciBsaWtlIDxhIGhyZWY9 60 | Imh0dHBzOi8vdGVudGhvdXNhbmRtZXRlcnMuY29tL2Jsb2cvcHl0aG9uLWJlaGluZC10aGUtc2Nl 61 | bmVzLTEtaG93LXRoZS1jcHl0aG9uLXZtLXdvcmtzLyIgdGl0bGU9IlB5dGhvbiYjMzk7cyBiYWNr 62 | ZW5kIiB0YXJnZXQ9Il9ibGFuayI+UHl0aG9uJiMzOTtzIGJhY2tlbmQ8L2E+IGFuZCAmcXVvdDtk 63 | dCZxdW90OyBpcyBteSBmaXJzdCBmcm9udGVuZC4gVGhlIGxhdGVyIHBsYW4gaXMgdGhhdCBpdCB3 64 | aWxsIGJlIGFibGUgdG8gdGFyZ2V0IG5hdGl2ZSBhcmNoaXRlY3R1cmVzIGxpa2UgTExWTSBidXQg 65 | YWxzbyB0YXJnZXQgb3RoZXIgVk1zIGFzIHdlbGwgYXMgYWxsb3cgb3RoZXIgbGFuZ3VhZ2VzIHRv 66 | IGNvbXBpbGUgZm9yIGl0Ljxicj48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PlRoZSBwcm9qZWN0 67 | cyBhbmQgd2Vic2l0ZSBhcmUgaGVyZTo8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pjx1bD48bGk+ 68 | PHNwYW4+TGFuZ3VhZ2UvVG9vbCBwcm9qZWN0OiA8YSByZWw9Im5vcmVmZXJyZXIgbm9mb2xsb3cg 69 | bm9vcGVuZXIiIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9kdCIgdGFyZ2V0PSJf 70 | YmxhbmsiPmh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9kdDwvYT48L3NwYW4+PC9saT48bGk+ 71 | PHNwYW4+Vk0gcHJvamVjdDogPHNwYW4+PGEgcmVsPSJub3JlZmVycmVyIG5vZm9sbG93IG5vb3Bl 72 | bmVyIiBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vaGlsanVzdGkvcmFpbCIgdGFyZ2V0PSJfYmxh 73 | bmsiPmh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9yYWlsPC9hPjwvc3Bhbj48YnI+PC9zcGFu 74 | PjwvbGk+PGxpPjxzcGFuPldlYnNpdGU6IDxhIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxvdyBub29w 75 | ZW5lciIgaHJlZj0iaHR0cHM6Ly9oaWxqdXN0aS5naXRodWIuaW8vZHQvIiB0YXJnZXQ9Il9ibGFu 76 | ayI+aHR0cHM6Ly9oaWxqdXN0aS5naXRodWIuaW8vZHQvPC9hPjwvc3Bhbj48L2xpPjwvdWw+PC9k 77 | aXY+PGRpdj48YnI+PC9kaXY+PGRpdj5Bbnl3YXkuLi4gSSBkbyBoYXZlIGEgcXVlc3Rpb24gaW4g 78 | YWxsIHRoaXMhPC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj48Yj5JcyBpdCBvayBmb3IgbWUgdG8g 79 | dXNlIFJlZCBHcmVlbiBxdW90ZXM8L2I+IChlc3BlY2lhbGx5IGFib3V0IGR1Y3QgdGFwZSkgIGlu 80 | IHRoZSAmcXVvdDtkdCZxdW90OyB3ZWJzaXRlLCBpdHMgZG9jdW1lbnRhdGlvbiwgYW5kIGV2ZW4g 81 | YnVpbHQgaW50byB0aGUgbGFuZ3VhZ2UgaXRzZWxmPyBJJiMzOTtsbCBhbHdheXMgZ2l2ZSBhdHRy 82 | aWJ1dGlvbi48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PlRoYW5rcyEgS2VlcGluZyBteSBzdGlj 83 | ayBvbiB0aGUgaWNlLDxicj48L2Rpdj48ZGl2Pkp1c3RpbiBSIEhpbGw8YnI+PC9kaXY+CjwvZGl2 84 | Pgo8L2Jsb2NrcXVvdGU+PC9kaXY+CjwvZGl2Pg== 85 | -----------------------b3c65a9764e1833c394949e484ec0969-- 86 | -----------------------ad6ad5723a2e7658b27887ea6badcb55-- 87 | -------------------------------------------------------------------------------- /meta/emails/Re dt and Red Green quotes 2022-10-26T17_43_07+00 00.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | X-Original-To: hiljusti@so.dang.cool 3 | Delivered-To: hiljusti@so.dang.cool 4 | Authentication-Results: mailin014.protonmail.ch; dkim=pass (Good 2048 5 | bit rsa-sha256 signature) header.d=gmail.com header.a=rsa-sha256 6 | Authentication-Results: mailin014.protonmail.ch; arc=none smtp.remote-ip=209.85.166.42 7 | Authentication-Results: mailin014.protonmail.ch; dkim=pass (2048-bit key) 8 | header.d=gmail.com header.i=@gmail.com header.b="KO21rKV8" 9 | Authentication-Results: mailin014.protonmail.ch; spf=pass smtp.mailfrom=gmail.com 10 | Authentication-Results: mailin014.protonmail.ch; dmarc=pass (p=none dis=none) 11 | header.from=gmail.com 12 | Received: from mail-io1-f42.google.com (mail-io1-f42.google.com [209.85.166.42]) (using 13 | TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) 14 | key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No 15 | client certificate requested) by mailin014.protonmail.ch (Postfix) with ESMTPS id 16 | 4MyGPM0tGgz7QQ5b for ; Wed, 26 Oct 2022 17:43:18 +0000 (UTC) 17 | Received: by mail-io1-f42.google.com with SMTP id 63so4478874iov.8 18 | for ; Wed, 26 Oct 2022 10:43:18 -0700 (PDT) 19 | Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 20 | d=gmail.com; s=20210112; 21 | h=to:subject:message-id:date:from:in-reply-to:references:mime-version 22 | :from:to:cc:subject:date:message-id:reply-to; 23 | bh=P5ywXRRykyU2grqRnBnGmFjKyJyYdwZIkULxgW1phIs=; 24 | b=KO21rKV8nubvoaB0fWhDpvlqhlYYyP8SsmEXfyhANsYdbuNkofhQWe/zRUYBbUBjzJ 25 | wozlVjWSLQ1gP53sVSriePKfqfvkQlwuXIhPfg44F6O3Zev3/O896x4D9614/E/lqea/ 26 | GEfRS90riAk5+JQuZxLXouPAU9n6pRSnuuIlY4On/YzEYqBl07FmnvjprOVamxgYopv5 27 | mJ6X5bJEGSraRuQWhdszrorHWTjpoWM5+0Ea3SHNKW9/8Snpbar4I0p7kSoBJemkZ+EN 28 | STI7JzA3OPotVPan2WBX6Qd/fEqQGKTfkT5k3EIffxbS4OBU+LqKJ6aVlk9OJeu2qrhW 29 | PZqA== 30 | X-Google-Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 31 | d=1e100.net; s=20210112; 32 | h=to:subject:message-id:date:from:in-reply-to:references:mime-version 33 | :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; 34 | bh=P5ywXRRykyU2grqRnBnGmFjKyJyYdwZIkULxgW1phIs=; 35 | b=tpXwdHY1++hmEJ/eimWDhiFs6KLwDngnkMHiJ9GrPv4SGb7nTUDZglfPqwW5Rf42lg 36 | 6gIHGyTRL6yGln4M3sdWuYP2cYERPFkHujJzy+blVWZ0V2f2XYfgTDbNpJPWm45m8Z7B 37 | l8zylUfT8GA6jgtTLdgWBcuEjJq8ecJuz5w13hhZrJ+92tFaX7FkwLhcX0rCG1PdzDIP 38 | XGUEZewq1HR0cgCZusHWIWCr1NbBxBtSxZ3r0E+UMQ4ztXtJsChnGh+SCUm3wqFLaMsU 39 | 6lALeqpXg8hGZr/320JRWI1u/mXtq/YXs4X1mEu900dTXwya52Zk+pzTgKmobgQcV1Xs 40 | VhLQ== 41 | X-Gm-Message-State: ACrzQf1rc74356SNyTamosY1KXuDO/rimki3eTGmsfl7eJtJI9bWlHkq 42 | WTgpOnXhmNMbE3+yLRH+2dow0v50v2kJ6dnevId0XuZs 43 | X-Google-Smtp-Source: AMsMyM62yPKAyS54umbL5ajAhJbhxpXOdh00uFks97HR4AWLqDMMnUC/UX6L3eYtLeUDTHxyZeQVp5qEaG3EvI5HrrE= 44 | X-Received: by 2002:a05:6638:d93:b0:364:5a1:f48f with SMTP id 45 | l19-20020a0566380d9300b0036405a1f48fmr29846940jaj.149.1666806197665; Wed, 26 Oct 2022 46 | 10:43:17 -0700 (PDT) 47 | Mime-Version: 1.0 48 | References: 49 | 50 | <8ctPNpnS3RSHfZ9J5iuOBgYkCuQnlnIorYgHQa51xYau8TgbljgCD0pwaV7xzlRrMDLNbYp1W_Rsd2hMQGwbaNbPjHmnk1gk1mkQMAU7jLg=@so.dang.cool> 51 | In-Reply-To: <8ctPNpnS3RSHfZ9J5iuOBgYkCuQnlnIorYgHQa51xYau8TgbljgCD0pwaV7xzlRrMDLNbYp1W_Rsd2hMQGwbaNbPjHmnk1gk1mkQMAU7jLg=@so.dang.cool> 52 | From: Dave Smith 53 | Date: Wed, 26 Oct 2022 13:43:07 -0400 54 | Message-Id: 55 | Subject: Re: "dt" and Red Green quotes 56 | To: "J.R. Hill" 57 | Content-Type: multipart/mixed;boundary=---------------------d55e71d4c14368dcec4f0b8b686e36e2 58 | X-Rspamd-Queue-Id: 4MyGPM0tGgz7QQ5b 59 | X-Spamd-Result: default: False [-3.70 / 25.00]; BAYES_HAM(-2.70)[98.66%]; 60 | DMARC_POLICY_ALLOW(-0.50)[gmail.com,none]; R_DKIM_ALLOW(-0.20)[gmail.com:s=20210112]; 61 | R_SPF_ALLOW(-0.20)[+ip4:209.85.128.0/17:c]; 62 | MIME_GOOD(-0.10)[multipart/alternative,text/plain]; ASN(0.00)[asn:15169, 63 | ipnet:209.85.128.0/17, country:US]; NEURAL_HAM(-0.00)[-0.997]; 64 | MIME_TRACE(0.00)[0:+,1:+,2:~]; FROM_EQ_ENVFROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; 65 | DKIM_TRACE(0.00)[gmail.com:+]; TO_DN_ALL(0.00)[]; MID_RHS_MATCH_FROMTLD(0.00)[]; 66 | FROM_HAS_DN(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; TO_MATCH_ENVRCPT_ALL(0.00)[]; 67 | RCPT_COUNT_ONE(0.00)[1]; PREVIOUSLY_DELIVERED(0.00)[hiljusti@so.dang.cool]; 68 | ARC_NA(0.00)[] 69 | X-Rspamd-Server: cp4-mailin-014.plabs.ch 70 | X-Pm-Spam: 0yeiAIic37iBOIJChpR3Y2bi4AiOiiW5abg3iiACLWbfxNvc2imUcOBi7TJCISQ6I 71 | EuIDgCwOIJlTgojITLuMwgNySlBITQ0itAiOjMsAJQICEk9UXB1SJRUR1QJR4iT0wiAOLACilR2X 72 | nYnVluX2im8ZOBi7QJCIkUE9ogIjiyAec9FztFGcjIgo4wMC2DEMMMj3zEzMzMzkAzNT4DINLACi 73 | k9WbWZfxR4aWgjoIMBC9g0HISfgwNyIniWQaOAii1MjNTOxcgyYjjDdNNQmyycTNmZ0IMzMz9iBI 74 | LACiiE2ciO7BJpICz19ccFGtgojICMgwNjInl3JbIojggwSMnIlJ9ycG6CIdIICtw4CMFIDJRfVk 75 | fU5STNVQFtUS0XygJCIFgDoTQZXlnFmcSZyBB1ZX0GFda9WurgCIiMclBbbi5jAMLgj12EjLiN04 76 | BsMi0XNaZQWgg4Wa2duwFpbWwHNbatWll5mLFdc1AxbigjALStEfOFkUERN9VOX0SkZVT00g25WR 77 | WZvxUgcGu2VcZVGyzVHIXZuJ1lYWvGxIbt2zhJHImbvRxubVuDEIMBCIS91SUQE51fT0PlJRTBSG 78 | t9mcHIzVJuZXlW1YIxGvzt2bHIhJRvbmuVxbIADuGBCMkUFVFJTUGF9TU9kNlNFImblRBlcipWFb 79 | bBCpjByc2bt15sb2hSBeYVnzgQWZWZk5NldXtiBcYlWsg4GXHcvJlkdmcXJZbBibkVmc3ZlJ5qZW 80 | 2nRcWF20tdWXWYslNvLmcV1bbAiwgAjL1UGBhFX0fE9TT9kOTBSREU6YhFIEgE8TZ9GluByc3bgQ 81 | VicHzGlbaBChTBibEUgYVjUmk3JbX4Gtw4CMFIQN9QRlTVNQINFQgojR2cuVVyZGhG1IdNGogMXZ 82 | 1UGBJlIHy29YZxFuuADICMIB1MVEF01XUN1BgU0RkQE9ogWTNFRSTBCpsNmbWdlRBpZCtiBbZNXz 83 | ldWYGXg44xMCLERIS1UfHl0UkTEV1lIEh3NcZU2gzFGaGIgEtJREvSBTcBiEzBySWaudR1YXsmUc 84 | I5GvuBCdWZlNNhc3smlcexVuhZHIGbkl4tXGxC4MIRELf1USkVMFQgSUzWVTcF2noBSZXYgMQgYX 85 | hGVbcQ3gl52bHIhZlkbGLERIS0UggI3bERgslnc20mFbdJXlt4GXCMx4RLIEfU1SVFkMfRUSVQgU 86 | VzTWn2FcZBSogMXYSY2BxpYWECBZSl0Ny9GIEILRNpIHh25ZdVHymBSZmct94gXG0XVYa9GygM3J 87 | GZt9luYWtG4XM4CxLREIUSf1FMVkfURSRYUgzVWT2cnFBoZSgXMYYBS2pxWYCZEBlNS0yG9IIREL 88 | pNHI2Zh5VydHmSBZc9mtg4GXWZ25xvZWtGUcZJnvkBSb2bh15caWgiIbfBS9 89 | X-Pm-Origin: external 90 | X-Pm-Transfer-Encryption: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) 91 | X-Pm-Content-Encryption: on-delivery 92 | X-Pm-Spamscore: 1 93 | X-Pm-Spam-Action: inbox 94 | 95 | -----------------------d55e71d4c14368dcec4f0b8b686e36e2 96 | Content-Type: multipart/related;boundary=---------------------af2d1255cfc8479f3093aab8f77b512b 97 | 98 | -----------------------af2d1255cfc8479f3093aab8f77b512b 99 | Content-Type: text/html;charset=utf-8 100 | Content-Transfer-Encoding: base64 101 | 102 | PGRpdiBkaXI9Imx0ciI+QWxzbyBJIHdpbGwgZm9yd2FyZCB0byBIYXJvbGQgZm9yIGhpcyBleHBl 103 | cnQgYW5hbHlzaXMuIDpEPC9kaXY+PGJyPjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj48ZGl2IGRp 104 | cj0ibHRyIiBjbGFzcz0iZ21haWxfYXR0ciI+T24gV2VkLCBPY3QgMjYsIDIwMjIgYXQgMTE6Mzgg 105 | QU0gSi5SLiBIaWxsICZsdDtoaWxqdXN0aUBzby5kYW5nLmNvb2wmZ3Q7IHdyb3RlOjxicj48L2Rp 106 | dj48YmxvY2txdW90ZSBjbGFzcz0iZ21haWxfcXVvdGUiIHN0eWxlPSJtYXJnaW46MHB4IDBweCAw 107 | cHggMC44ZXg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkIHJnYigyMDQsMjA0LDIwNCk7cGFkZGluZy1s 108 | ZWZ0OjFleCI+VGhhbmtzITxicj48YnI+PGJyPjxicj4tLS0tLS0tLSBPcmlnaW5hbCBNZXNzYWdl 109 | IC0tLS0tLS0tPGJyPk9uIE9jdCAyNiwgMjAyMiwgNTo0OSBBTSwgRGF2ZSBTbWl0aCAmbHQ7IDxh 110 | IGhyZWY9Im1haWx0bzpyZWRncmVlbmpydHZAZ21haWwuY29tIiB0YXJnZXQ9Il9ibGFuayI+cmVk 111 | Z3JlZW5qcnR2QGdtYWlsLmNvbTwvYT4mZ3Q7IHdyb3RlOjxibG9ja3F1b3RlPjxicj48ZGl2IGRp 112 | cj0ibHRyIj5TdXJlLiAgR29vZCBsdWNrITwvZGl2Pjxicj48ZGl2IGNsYXNzPSJnbWFpbF9xdW90 113 | ZSI+PGRpdiBkaXI9Imx0ciIgY2xhc3M9ImdtYWlsX2F0dHIiPk9uIFdlZCwgT2N0IDI2LCAyMDIy 114 | IGF0IDU6MTAgQU0gSi5SLiBIaWxsICZsdDtoaWxqdXN0aUBzby5kYW5nLmNvb2wmZ3Q7IHdyb3Rl 115 | Ojxicj48L2Rpdj48YmxvY2txdW90ZSBjbGFzcz0iZ21haWxfcXVvdGUiIHN0eWxlPSJtYXJnaW46 116 | MHB4IDBweCAwcHggMC44ZXg7Ym9yZGVyLWxlZnQ6MXB4IHNvbGlkIHJnYigyMDQsMjA0LDIwNCk7 117 | cGFkZGluZy1sZWZ0OjFleCI+PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6QXJpYWw7Zm9udC1zaXpl 118 | OjE0cHgiPkhleSB0aGVyZSE8L2Rpdj48ZGl2IHN0eWxlPSJmb250LWZhbWlseTpBcmlhbDtmb250 119 | LXNpemU6MTRweCI+PGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsO2ZvbnQt 120 | c2l6ZToxNHB4Ij5JJiMzOTttIGEgbWFuIDxpPihidXQgSSBjYW4gY2hhbmdlLCBpZiBJIGhhdmUg 121 | dG8uLi4gSSBndWVzcy4uLik8L2k+IHdobyYjMzk7cyB3b3JraW5nIG9uIGEgbmV3IHByb2dyYW1t 122 | aW5nIGxhbmd1YWdlIGFuZCBsb3ctbGV2ZWwgTGludXggdXRpbGl0eSBjYWxsZWQgJnF1b3Q7ZHQu 123 | JnF1b3Q7IFRoZSBsYW5ndWFnZSBhbmQgdG9vbCBpcyBwYXJ0aWFsbHkgaW5zcGlyZWQgYnkgZHVj 124 | dCB0YXBlIGFuZCBhbHNvIGJ5IGhlYWx0aHkgZG9zZXMgb2YgdGhlIFJlZCBHcmVlbiBzaG93IGFz 125 | IEkgd2FzIGdyb3dpbmcgdXAuIFRoZSB2aXNpb24gaXMgZm9yICZxdW90O2R0JnF1b3Q7IHRvIGJl 126 | IGEgZG8tYW55dGhpbmcsIGZpeC1hbnl0aGluZywgb25seS10ZW1wb3JhcnktdW5sZXNzLWl0LXdv 127 | cmtzIHRvb2wuPGJyPjwvZGl2PjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsO2ZvbnQtc2l6 128 | ZToxNHB4Ij48YnI+PC9kaXY+CjxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkFyaWFsO2ZvbnQtc2l6 129 | ZToxNHB4Ij4KICAgIDxkaXY+CgogICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgIDxkaXY+ 130 | SXQmIzM5O3MgYmVlbiBhIHdvcmsgaW4gcHJvZ3Jlc3MgZm9yIGFib3V0IGEgeWVhciBub3cuIE15 131 | IGRheSBqb2IgaXMgbWFpbnRhaW5pbmcgdGhlIGludGVybmFsIHNvZnR3YXJlIGluZnJhc3RydWN0 132 | dXJlIG9mIEFtYXpvbiAodGhpbmsgU2NvdHR5IGZyb20gU3RhciBUcmVrKSBzbyBpdCYjMzk7cyBh 133 | IHNsb3cgam91cm5leSBpbiBteSBzcGFyZSB0aW1lLi4uIGJ1dCBJJiMzOTttIGZpbmFsbHkgcmVh 134 | ZHkgdG8gc3RhcnQgZG9jdW1lbnRpbmcgdGhlIGxhbmd1YWdlIGFuZCBnZXR0aW5nIHNvbWUgaW5p 135 | dGlhbCBmZWVkYmFjay4gSXQgcHJvYmFibHkgd29uJiMzOTt0IGJlIHJlYWR5IGZvciBwcmltZSB0 136 | aW1lIGZvciBhbm90aGVyIHllYXIgb3IgdHdvIGFmdGVyIGZpbmlzaGluZyBkb2N1bWVudGF0aW9u 137 | IGFuZC48YnI+PC9kaXY+PGRpdj48YnI+PC9kaXY+PGRpdj5JZiB5b3UmIzM5O3JlIGludGVyZXN0 138 | ZWQgaW4gbW9yZSBkZXRhaWxzLCBvciBoYXZlIGEgdGVjaC1zYXZ2eSBwZXJzb24gd2hvIGNhbiB0 139 | ZWxsIHlvdSBpZiBJJiMzOTttIDxpPm9mZiBteSByb2NrZXI8L2k+IG9yIGlmIEkmIzM5O20gPGk+ 140 | YWN0dWFsbHkgb24gdG8gc29tZXRoaW5nPC9pPiwgdGhlIG1vcmUgZGV0YWlsZWQgZXhwbGFuYXRp 141 | b24gaXMgdGhhdCBJJiMzOTttIGNyZWF0aW5nIGEgbmV3IDxhIGhyZWY9Imh0dHBzOi8vZW4ud2lr 142 | aXBlZGlhLm9yZy93aWtpL1N0YWNrX21hY2hpbmUiIHRpdGxlPSJzdGFjayBtYWNoaW5lIiB0YXJn 143 | ZXQ9Il9ibGFuayI+c3RhY2sgbWFjaGluZTwvYT4gbGlrZSB0aGUgPGEgaHJlZj0iaHR0cHM6Ly9k 144 | b2NzLm9yYWNsZS5jb20vamF2YXNlL3NwZWNzL2p2bXMvc2UxNy9odG1sL2luZGV4Lmh0bWwiIHRp 145 | dGxlPSJKVk0iIHRhcmdldD0iX2JsYW5rIj5KVk08L2E+IG9yIGxpa2UgPGEgaHJlZj0iaHR0cHM6 146 | Ly90ZW50aG91c2FuZG1ldGVycy5jb20vYmxvZy9weXRob24tYmVoaW5kLXRoZS1zY2VuZXMtMS1o 147 | b3ctdGhlLWNweXRob24tdm0td29ya3MvIiB0aXRsZT0iUHl0aG9uJiMzOTtzIGJhY2tlbmQiIHRh 148 | cmdldD0iX2JsYW5rIj5QeXRob24mIzM5O3MgYmFja2VuZDwvYT4gYW5kICZxdW90O2R0JnF1b3Q7 149 | IGlzIG15IGZpcnN0IGZyb250ZW5kLiBUaGUgbGF0ZXIgcGxhbiBpcyB0aGF0IGl0IHdpbGwgYmUg 150 | YWJsZSB0byB0YXJnZXQgbmF0aXZlIGFyY2hpdGVjdHVyZXMgbGlrZSBMTFZNIGJ1dCBhbHNvIHRh 151 | cmdldCBvdGhlciBWTXMgYXMgd2VsbCBhcyBhbGxvdyBvdGhlciBsYW5ndWFnZXMgdG8gY29tcGls 152 | ZSBmb3IgaXQuPGJyPjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+VGhlIHByb2plY3RzIGFuZCB3 153 | ZWJzaXRlIGFyZSBoZXJlOjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+PHVsPjxsaT48c3Bhbj5M 154 | YW5ndWFnZS9Ub29sIHByb2plY3Q6IDxhIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxvdyBub29wZW5l 155 | ciIgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2hpbGp1c3RpL2R0IiB0YXJnZXQ9Il9ibGFuayI+ 156 | aHR0cHM6Ly9naXRodWIuY29tL2hpbGp1c3RpL2R0PC9hPjwvc3Bhbj48L2xpPjxsaT48c3Bhbj5W 157 | TSBwcm9qZWN0OiA8c3Bhbj48YSByZWw9Im5vcmVmZXJyZXIgbm9mb2xsb3cgbm9vcGVuZXIiIGhy 158 | ZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9yYWlsIiB0YXJnZXQ9Il9ibGFuayI+aHR0 159 | cHM6Ly9naXRodWIuY29tL2hpbGp1c3RpL3JhaWw8L2E+PC9zcGFuPjxicj48L3NwYW4+PC9saT48 160 | bGk+PHNwYW4+V2Vic2l0ZTogPGEgcmVsPSJub3JlZmVycmVyIG5vZm9sbG93IG5vb3BlbmVyIiBo 161 | cmVmPSJodHRwczovL2hpbGp1c3RpLmdpdGh1Yi5pby9kdC8iIHRhcmdldD0iX2JsYW5rIj5odHRw 162 | czovL2hpbGp1c3RpLmdpdGh1Yi5pby9kdC88L2E+PC9zcGFuPjwvbGk+PC91bD48L2Rpdj48ZGl2 163 | Pjxicj48L2Rpdj48ZGl2PkFueXdheS4uLiBJIGRvIGhhdmUgYSBxdWVzdGlvbiBpbiBhbGwgdGhp 164 | cyE8L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2PjxiPklzIGl0IG9rIGZvciBtZSB0byB1c2UgUmVk 165 | IEdyZWVuIHF1b3RlczwvYj4gKGVzcGVjaWFsbHkgYWJvdXQgZHVjdCB0YXBlKSAgaW4gdGhlICZx 166 | dW90O2R0JnF1b3Q7IHdlYnNpdGUsIGl0cyBkb2N1bWVudGF0aW9uLCBhbmQgZXZlbiBidWlsdCBp 167 | bnRvIHRoZSBsYW5ndWFnZSBpdHNlbGY/IEkmIzM5O2xsIGFsd2F5cyBnaXZlIGF0dHJpYnV0aW9u 168 | LjwvZGl2PjxkaXY+PGJyPjwvZGl2PjxkaXY+VGhhbmtzISBLZWVwaW5nIG15IHN0aWNrIG9uIHRo 169 | ZSBpY2UsPGJyPjwvZGl2PjxkaXY+SnVzdGluIFIgSGlsbDxicj48L2Rpdj4KPC9kaXY+CjwvYmxv 170 | Y2txdW90ZT48L2Rpdj4KPC9ibG9ja3F1b3RlPjwvYmxvY2txdW90ZT48L2Rpdj4K 171 | -----------------------af2d1255cfc8479f3093aab8f77b512b-- 172 | -----------------------d55e71d4c14368dcec4f0b8b686e36e2-- 173 | -------------------------------------------------------------------------------- /meta/emails/dt and Red Green quotes 2022-10-26T09_00_08+00 00.eml: -------------------------------------------------------------------------------- 1 | X-Pm-Content-Encryption: on-compose 2 | X-Pm-Origin: internal 3 | Subject: "dt" and Red Green quotes 4 | To: red@redgreen.com 5 | From: J.R. Hill 6 | Date: Wed, 26 Oct 2022 09:00:08 +0000 7 | Mime-Version: 1.0 8 | Content-Type: multipart/mixed;boundary=---------------------3adca86f6dd7c4a68f00833f9346dc0c 9 | Message-Id: 10 | X-Pm-Scheduled-Sent-Original-Time: Wed, 26 Oct 2022 08:59:55 +0000 11 | X-Pm-Recipient-Authentication: red%40redgreen.com=none 12 | X-Pm-Recipient-Encryption: red%40redgreen.com=none 13 | 14 | -----------------------3adca86f6dd7c4a68f00833f9346dc0c 15 | Content-Type: multipart/related;boundary=---------------------b43806a1feb13aba709949573e7fda29 16 | 17 | -----------------------b43806a1feb13aba709949573e7fda29 18 | Content-Type: text/html;charset=utf-8 19 | Content-Transfer-Encoding: base64 20 | 21 | PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsOyBmb250LXNpemU6IDE0cHg7Ij5IZXkgdGhl 22 | cmUhPC9kaXY+PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsOyBmb250LXNpemU6IDE0cHg7 23 | Ij48YnI+PC9kaXY+PGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsOyBmb250LXNpemU6IDE0 24 | cHg7Ij5JJ20gYSBtYW4gPGk+KGJ1dCBJIGNhbiBjaGFuZ2UsIGlmIEkgaGF2ZSB0by4uLiBJIGd1 25 | ZXNzLi4uKTwvaT4gd2hvJ3Mgd29ya2luZyBvbiBhIG5ldyBwcm9ncmFtbWluZyBsYW5ndWFnZSBh 26 | bmQgbG93LWxldmVsIExpbnV4IHV0aWxpdHkgY2FsbGVkICJkdC4iIFRoZSBsYW5ndWFnZSBhbmQg 27 | dG9vbCBpcyBwYXJ0aWFsbHkgaW5zcGlyZWQgYnkgZHVjdCB0YXBlIGFuZCBhbHNvIGJ5IGhlYWx0 28 | aHkgZG9zZXMgb2YgdGhlIFJlZCBHcmVlbiBzaG93IGFzIEkgd2FzIGdyb3dpbmcgdXAuIFRoZSB2 29 | aXNpb24gaXMgZm9yICJkdCIgdG8gYmUgYSBkby1hbnl0aGluZywgZml4LWFueXRoaW5nLCBvbmx5 30 | LXRlbXBvcmFyeS11bmxlc3MtaXQtd29ya3MgdG9vbC48YnI+PC9kaXY+PGRpdiBzdHlsZT0iZm9u 31 | dC1mYW1pbHk6IEFyaWFsOyBmb250LXNpemU6IDE0cHg7Ij48YnI+PC9kaXY+CjxkaXYgY2xhc3M9 32 | InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrIiBzdHlsZT0iZm9udC1mYW1pbHk6IEFyaWFsOyBm 33 | b250LXNpemU6IDE0cHg7Ij4KICAgIDxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Js 34 | b2NrLXVzZXIgcHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stZW1wdHkiPgoKICAgICAgICAgICAg 35 | PC9kaXY+CgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwcm90b25tYWlsX3NpZ25hdHVyZV9ibG9j 36 | ay1wcm90b24iPkl0J3MgYmVlbiBhIHdvcmsgaW4gcHJvZ3Jlc3MgZm9yIGFib3V0IGEgeWVhciBu 37 | b3cuIE15IGRheSBqb2IgaXMgbWFpbnRhaW5pbmcgdGhlIGludGVybmFsIHNvZnR3YXJlIGluZnJh 38 | c3RydWN0dXJlIG9mIEFtYXpvbiAodGhpbmsgU2NvdHR5IGZyb20gU3RhciBUcmVrKSBzbyBpdCdz 39 | IGEgc2xvdyBqb3VybmV5IGluIG15IHNwYXJlIHRpbWUuLi4gYnV0IEknbSBmaW5hbGx5IHJlYWR5 40 | IHRvIHN0YXJ0IGRvY3VtZW50aW5nIHRoZSBsYW5ndWFnZSBhbmQgZ2V0dGluZyBzb21lIGluaXRp 41 | YWwgZmVlZGJhY2suIEl0IHByb2JhYmx5IHdvbid0IGJlIHJlYWR5IGZvciBwcmltZSB0aW1lIGZv 42 | ciBhbm90aGVyIHllYXIgb3IgdHdvIGFmdGVyIGZpbmlzaGluZyBkb2N1bWVudGF0aW9uIGFuZC48 43 | YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIj48 44 | YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIj5J 45 | ZiB5b3UncmUgaW50ZXJlc3RlZCBpbiBtb3JlIGRldGFpbHMsIG9yIGhhdmUgYSB0ZWNoLXNhdnZ5 46 | IHBlcnNvbiB3aG8gY2FuIHRlbGwgeW91IGlmIEknbSA8aT5vZmYgbXkgcm9ja2VyPC9pPiBvciBp 47 | ZiBJJ20gPGk+YWN0dWFsbHkgb24gdG8gc29tZXRoaW5nPC9pPiwgdGhlIG1vcmUgZGV0YWlsZWQg 48 | ZXhwbGFuYXRpb24gaXMgdGhhdCBJJ20gY3JlYXRpbmcgYSBuZXcgPGEgaHJlZj0iaHR0cHM6Ly9l 49 | bi53aWtpcGVkaWEub3JnL3dpa2kvU3RhY2tfbWFjaGluZSIgdGl0bGU9InN0YWNrIG1hY2hpbmUi 50 | PnN0YWNrIG1hY2hpbmU8L2E+IGxpa2UgdGhlIDxhIGhyZWY9Imh0dHBzOi8vZG9jcy5vcmFjbGUu 51 | Y29tL2phdmFzZS9zcGVjcy9qdm1zL3NlMTcvaHRtbC9pbmRleC5odG1sIiB0aXRsZT0iSlZNIj5K 52 | Vk08L2E+IG9yIGxpa2UgPGEgaHJlZj0iaHR0cHM6Ly90ZW50aG91c2FuZG1ldGVycy5jb20vYmxv 53 | Zy9weXRob24tYmVoaW5kLXRoZS1zY2VuZXMtMS1ob3ctdGhlLWNweXRob24tdm0td29ya3MvIiB0 54 | aXRsZT0iUHl0aG9uJ3MgYmFja2VuZCI+UHl0aG9uJ3MgYmFja2VuZDwvYT4gYW5kICJkdCIgaXMg 55 | bXkgZmlyc3QgZnJvbnRlbmQuIFRoZSBsYXRlciBwbGFuIGlzIHRoYXQgaXQgd2lsbCBiZSBhYmxl 56 | IHRvIHRhcmdldCBuYXRpdmUgYXJjaGl0ZWN0dXJlcyBsaWtlIExMVk0gYnV0IGFsc28gdGFyZ2V0 57 | IG90aGVyIFZNcyBhcyB3ZWxsIGFzIGFsbG93IG90aGVyIGxhbmd1YWdlcyB0byBjb21waWxlIGZv 58 | ciBpdC48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJv 59 | dG9uIj48YnI+PC9kaXY+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJv 60 | dG9uIj5UaGUgcHJvamVjdHMgYW5kIHdlYnNpdGUgYXJlIGhlcmU6PC9kaXY+PGRpdiBjbGFzcz0i 61 | cHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIj48YnI+PC9kaXY+PGRpdiBjbGFzcz0i 62 | cHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2stcHJvdG9uIj48dWw+PGxpPjxzcGFuPkxhbmd1YWdl 63 | L1Rvb2wgcHJvamVjdDogPGEgdGFyZ2V0PSJfYmxhbmsiIHJlbD0ibm9yZWZlcnJlciBub2ZvbGxv 64 | dyBub29wZW5lciIgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL2hpbGp1c3RpL2R0Ij5odHRwczov 65 | L2dpdGh1Yi5jb20vaGlsanVzdGkvZHQ8L2E+PC9zcGFuPjwvbGk+PGxpPjxzcGFuPlZNIHByb2pl 66 | Y3Q6IDxzcGFuPjxhIHRhcmdldD0iX2JsYW5rIiByZWw9Im5vcmVmZXJyZXIgbm9mb2xsb3cgbm9v 67 | cGVuZXIiIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9oaWxqdXN0aS9yYWlsIj5odHRwczovL2dp 68 | dGh1Yi5jb20vaGlsanVzdGkvcmFpbDwvYT48L3NwYW4+PGJyPjwvc3Bhbj48L2xpPjxsaT48c3Bh 69 | bj5XZWJzaXRlOiA8YSB0YXJnZXQ9Il9ibGFuayIgcmVsPSJub3JlZmVycmVyIG5vZm9sbG93IG5v 70 | b3BlbmVyIiBocmVmPSJodHRwczovL2hpbGp1c3RpLmdpdGh1Yi5pby9kdC8iPmh0dHBzOi8vaGls 71 | anVzdGkuZ2l0aHViLmlvL2R0LzwvYT48L3NwYW4+PC9saT48L3VsPjwvZGl2PjxkaXYgY2xhc3M9 72 | InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9 73 | InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiI+QW55d2F5Li4uIEkgZG8gaGF2ZSBh 74 | IHF1ZXN0aW9uIGluIGFsbCB0aGlzITwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0 75 | dXJlX2Jsb2NrLXByb3RvbiI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0 76 | dXJlX2Jsb2NrLXByb3RvbiI+PGI+SXMgaXQgb2sgZm9yIG1lIHRvIHVzZSBSZWQgR3JlZW4gcXVv 77 | dGVzPC9iPiAoZXNwZWNpYWxseSBhYm91dCBkdWN0IHRhcGUpICBpbiB0aGUgImR0IiB3ZWJzaXRl 78 | LCBpdHMgZG9jdW1lbnRhdGlvbiwgYW5kIGV2ZW4gYnVpbHQgaW50byB0aGUgbGFuZ3VhZ2UgaXRz 79 | ZWxmPyBJJ2xsIGFsd2F5cyBnaXZlIGF0dHJpYnV0aW9uLjwvZGl2PjxkaXYgY2xhc3M9InByb3Rv 80 | bm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiI+PGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rv 81 | bm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiI+VGhhbmtzISBLZWVwaW5nIG15IHN0aWNrIG9u 82 | IHRoZSBpY2UsPGJyPjwvZGl2PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2Nr 83 | LXByb3RvbiI+SnVzdGluIFIgSGlsbDxicj48L2Rpdj4KPC9kaXY+Cg== 84 | -----------------------b43806a1feb13aba709949573e7fda29-- 85 | -----------------------3adca86f6dd7c4a68f00833f9346dc0c-- 86 | -------------------------------------------------------------------------------- /meta/ideas.md: -------------------------------------------------------------------------------- 1 | * dictionary as trie with linked list definitions 2 | * put errors on the stack; they are always falsy 3 | -------------------------------------------------------------------------------- /meta/misc/rockstar_count: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /meta/quotes.md: -------------------------------------------------------------------------------- 1 | # [_Diligence, Patience, and Humility_](https://www.oreilly.com/openbook/opensources/book/larry.html), Larry Wall 2 | 3 | > The yinyang represents a dualistic philosophy, much like The Force in Star Wars. You know, how is The Force like duct tape? Answer: it has a light side, a dark side, and it holds the universe together. I'm not a dualist myself, because I believe the light is stronger than the darkness. Nevertheless, the concept of balanced forces is useful at times, especially to engineers. When an engineer wants to balance forces, and wants them to stay balanced, he reaches for the duct tape. 4 | 5 | --- 6 | 7 | > I have upon more than one occasion been requested to eject someone from the Perl community, generally for being offensive in some fashion or other. So far I have consistently refused. I believe this is the right policy. At least, it's worked so far, on a practical level. Either the offensive person has left eventually of their own accord, or they've settled down and learned to deal with others more constructively. It's odd. People understand instinctively that the best way for computer programs to communicate with each other is for each of the them to be strict in what they emit, and liberal in what they accept. The odd thing is that people themselves are not willing to be strict in how they speak and liberal in how they listen. You'd think that would also be obvious. Instead, we're taught to express ourselves. 8 | 9 | --- 10 | 11 | > But I have to tell you that I don't evaluate the success of Perl in terms of how many people like me. When I integrate these curves, I count the number of people I've helped get their job done. 12 | 13 | # [The humble programmer](https://www.cs.utexas.edu/users/EWD/ewd03xx/EWD340.PDF), Edsger W. Dijkstra 14 | 15 | > As an aside I would like to insert a warning to those who identify the difficulty of the programming task with the struggle against the inadequacies of our current tools, because they might conclude that, once our tools will be much more adequate, programming will no longer be a problem. Programming will remain very difficult, because once we have freed ourselves from the circumstantial cumbersomeness, we will find ourselves free to tackle the problems that are now well beyond our programming capacity. 16 | 17 | # [How do we tell truths that might hurt?](https://www.cs.utexas.edu/users/EWD/ewd04xx/EWD498.PDF), Edsger W. Dijkstra 18 | 19 | > The tools we use have a profound (and devious!) influence on our thinking habits, and, therefore, our thinking abilities. 20 | 21 | --- 22 | 23 | > It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration. 24 | 25 | _Note: [My](https://github.com/so-dang-cool) first language was BASIC._ 26 | 27 | # [The end of Computing Science?](https://www.cs.utexas.edu/users/EWD/ewd13xx/EWD1304.PDF), Edsger W. Dijkstra 28 | 29 | > I would therefore like to posit that computing's central challenge, viz. "How not to make a mess of it", has not been met. On the contrary, most of our systems are much more complicated than can be considered healthy, and are too messy and chaotic to be used in comfort and confidence. The average customer of the computing industry has been served so poorly that he expects his system to crash all the time, and we witness a massive world-wide distribution of bug-ridden software for which we should be deeply ashamed. 30 | -------------------------------------------------------------------------------- /src/builtins.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ArrayList = std.ArrayList; 3 | const osExit = if (@hasDecl(std, "posix")) std.posix.exit else std.os.exit; 4 | const osChdir = if (@hasDecl(std, "posix")) std.posix.chdir else std.os.chdir; 5 | 6 | const interpret = @import("interpret.zig"); 7 | const Command = interpret.Command; 8 | const DtMachine = interpret.DtMachine; 9 | 10 | const types = @import("types.zig"); 11 | const Val = types.Val; 12 | const Quote = types.Quote; 13 | const Error = types.Error; 14 | 15 | const builtin = @import("builtin"); 16 | 17 | const main = @import("main.zig"); 18 | 19 | const Token = @import("tokens.zig").Token; 20 | 21 | const bangDescription = "If nested, any commands or variables defined will be available in the calling scope."; 22 | 23 | pub fn defineAll(dt: *DtMachine) !void { 24 | try dt.define("quit", "( -- ) Quit. Prints a warning if there are any values left on stack.", .{ .builtin = quit }); 25 | try dt.define("exit", "( exitcode -- ) Exit with the specified exit code.", .{ .builtin = exit }); 26 | try dt.define("version", "( -- version ) Produce the version of dt in use.", .{ .builtin = version }); 27 | 28 | try dt.define("cwd", "( -- dirname ) Produce the current working directory.", .{ .builtin = cwd }); 29 | if (builtin.os.tag != .wasi) { 30 | try dt.define("cd", "( dirname -- ) Change the process's working directory.", .{ .builtin = cd }); 31 | } 32 | try dt.define("ls", "( -- [filename] ) Produce a quote of files and directories in the process's working directory.", .{ .builtin = ls }); 33 | try dt.define("readf", "( filename -- contents ) Read a file's contents as a string.", .{ .builtin = readf }); 34 | try dt.define("writef", "( contents filename -- ) Write a string as a file. If a file previously existed, it will be overwritten.", .{ .builtin = writef }); 35 | try dt.define("appendf", "( contents filename -- ) Write a string to a file. If a file previously existed, the new content will be appended.", .{ .builtin = appendf }); 36 | // TODO: pathsep/filesep, env get, env set 37 | 38 | if (builtin.os.tag != .wasi) { 39 | try dt.define("exec", "( process -- ) Execute a child process (from a String). When successful, returns stdout as a string. When unsuccessful, prints the child's stderr to stderr, and returns boolean false.", .{ .builtin = exec }); 40 | } 41 | try dt.define("def!", "( action name -- ) Defines a new command. " ++ bangDescription, .{ .builtin = @"def!" }); 42 | try dt.define("defs", "( -- [name] ) Produce a quote of all defined commands.", .{ .builtin = defs }); 43 | try dt.define("def?", "( name -- bool ) Determine whether a command is defined.", .{ .builtin = @"def?" }); 44 | try dt.define("usage", "( name -- description ) Print the usage notes of a given command.", .{ .builtin = usage }); 45 | try dt.define("def-usage", "( name description -- ) Define the usage notes of a given command.", .{ .builtin = @"def-usage" }); 46 | try dt.define(":", "( ... [name] -- ) Bind variables to a quote of names.", .{ .builtin = @":" }); 47 | 48 | try dt.define("do!", "( ... action -- ... ) Execute an action. " ++ bangDescription, .{ .builtin = @"do!" }); 49 | try dt.define("do", "( ... action -- ... ) Execute an action.", .{ .builtin = do }); 50 | try dt.define("doin", "( context action -- ) Execute an action in a context.", .{ .builtin = doin }); 51 | try dt.define("do!?", "( ... action condition -- ... ) Conditionally execute an action. " ++ bangDescription, .{ .builtin = @"do!?" }); 52 | try dt.define("do?", "( ... action condition -- ... ) Conditionally execute an action.", .{ .builtin = @"do?" }); 53 | try dt.define("loop", "( ... action -- ... ) Execute an action forever until it fails.", .{ .builtin = loop }); 54 | 55 | try dt.define("dup", "( a -- a a ) Duplicate the most recent value.", .{ .builtin = dup }); 56 | try dt.define("drop", "( a -- ) Drop the most recent value.", .{ .builtin = drop }); 57 | try dt.define("swap", "( a b -- b a ) Swap the two most recent values.", .{ .builtin = swap }); 58 | try dt.define("rot", "( a b c -- c a b ) Rotate the three most recent values.", .{ .builtin = rot }); 59 | 60 | try dt.define("p", "( a -- ) Print the most recent value to standard output.", .{ .builtin = p }); 61 | try dt.define("ep", "( a -- ) Print the most recent value to standard error.", .{ .builtin = ep }); 62 | try dt.define("red", "( -- ) Print a control character for red to standard output and standard error.", .{ .builtin = red }); 63 | try dt.define("green", "( -- ) Print a control character for green and bold (for the colorblind) to standard output and standard error.", .{ .builtin = green }); 64 | try dt.define("norm", "( -- ) Print a control character to reset any styling to standard output and standard error.", .{ .builtin = norm }); 65 | try dt.define(".s", "( -- ) Print the state of the process: all available values.", .{ .builtin = @".s" }); 66 | 67 | try dt.define("rl", "( -- line ) Read a string from standard input until newline.", .{ .builtin = rl }); 68 | try dt.define("rls", "( -- [line] ) Read strings, separated by newlines, from standard input until EOF. (For example: until ctrl+d in a Unix-like system, or until a pipe is closed.)", .{ .builtin = rls }); 69 | try dt.define("procname", "( -- name ) Produce the name of the current process. This can be used, for example, to get the name of a shebang script.", .{ .builtin = procname }); 70 | try dt.define("args", "( -- [arg] ) Produce the arguments provided to the process when it was launched.", .{ .builtin = args }); 71 | try dt.define("eval", "( code -- ... ) Evaluate a string as dt commands and execute them.", .{ .builtin = eval }); 72 | try dt.define("interactive?", "( -- bool ) Determine if the input mode is interactive (a TTY) or not.", .{ .builtin = @"interactive?" }); 73 | 74 | try dt.define("+", "( x y -- z ) Add two numeric values.", .{ .builtin = @"+" }); 75 | try dt.define("-", "( x y -- z ) Subtract two numeric values. In standard notation: a - b = c", .{ .builtin = @"-" }); 76 | try dt.define("*", "( x y -- z ) Multiply two numeric values.", .{ .builtin = @"*" }); 77 | try dt.define("/", "( x y -- z ) Divide two numeric values. In standard notation: a / b = c", .{ .builtin = @"/" }); 78 | try dt.define("%", "( x y -- z ) Modulo two numeric values. In standard notation: a % b = c", .{ .builtin = @"%" }); 79 | try dt.define("abs", "( x -- y ) Determine the absolute value of a number.", .{ .builtin = abs }); 80 | try dt.define("rand", "( -- x ) Produces a random integer.", .{ .builtin = rand }); 81 | 82 | try dt.define("eq?", "( a b -- bool ) Determine if two values are equal. Works for most types with coercion.", .{ .builtin = @"eq?" }); 83 | try dt.define("gt?", "( x y -- bool ) Determine if a value is greater than another. In standard notation: a > b", .{ .builtin = @"gt?" }); 84 | try dt.define("gte?", "( x y -- bool ) Determine if a value is greater-than/equal-to another. In standard notation: a ≧ b", .{ .builtin = @"gte?" }); 85 | try dt.define("lt?", "( x y -- bool ) Determine if a value is less than another. In standard notation: a < b", .{ .builtin = @"lt?" }); 86 | try dt.define("lte?", "( x y -- bool ) Determine if a value is less-than/equal-to another. In standard notation: a ≦ b", .{ .builtin = @"lte?" }); 87 | 88 | try dt.define("and", "( a b -- bool ) Determine if two values are both truthy.", .{ .builtin = boolAnd }); 89 | try dt.define("or", "( a b -- bool ) Determine if either of two values are truthy.", .{ .builtin = boolOr }); 90 | try dt.define("not", "( a -- bool ) Determine the inverse truthiness of a value.", .{ .builtin = not }); 91 | 92 | try dt.define("split", "( string delim -- [substring] ) Split a string on all occurrences of a delimiter.", .{ .builtin = split }); 93 | try dt.define("join", "( [substring] delim -- string ) Join strings with a delimiter.", .{ .builtin = join }); 94 | try dt.define("upcase", "( string -- upper ) Convert a string to its uppercase form.", .{ .builtin = upcase }); 95 | try dt.define("downcase", "( string -- lower ) Convert a string to its lowercase form.", .{ .builtin = downcase }); 96 | try dt.define("starts-with?", "( string prefix -- bool ) Determine if a string starts with a prefix.", .{ .builtin = startsWith }); 97 | try dt.define("ends-with?", "( string suffix -- bool ) Determine if a string ends with a suffix.", .{ .builtin = endsWith }); 98 | try dt.define("contains?", "( haystack needle -- bool ) With Strings, determine if a string contains a substring. With quotes, determine if a quote contains a value.", .{ .builtin = contains }); 99 | 100 | try dt.define("map", "( [...] command -- [...] ) Apply an action to all values in a quote.", .{ .builtin = map }); 101 | try dt.define("filter", "( [...] predicate -- [...] ) Require some condition of all values in a quote. Truthy results are preserved, and falsy results are not.", .{ .builtin = filter }); 102 | try dt.define("any?", "( [...] predicate -- bool ) Determine whether any value in a quote passes a condition. Stops at the first truthy result.", .{ .builtin = any }); 103 | try dt.define("len", "( [...] -- x ) The length of a string or quote. (Always 1 for single values.)", .{ .builtin = len }); 104 | 105 | try dt.define("...", "( [...] -- ... ) Unpack a quote.", .{ .builtin = ellipsis }); 106 | try dt.define("rev", "( [...] -- [...] ) Reverse a quote or string. Other types are unmodified.", .{ .builtin = rev }); 107 | try dt.define("sort", "( [...] -- [...] ) Sort a list of values. When values are of different type, they are sorted in the following order: bool, int, float, string, command, deferred command, quote.", .{ .builtin = sort }); 108 | try dt.define("quote", "( a -- [a] ) Quote a value.", .{ .builtin = quoteVal }); 109 | try dt.define("quote-all", "( ... -- [...] ) Quote all current context.", .{ .builtin = quoteAll }); 110 | try dt.define("anything?", "( -- bool ) True if any value is present.", .{ .builtin = @"anything?" }); 111 | try dt.define("concat", "( [...] [...] -- [...] ) Concatenate two quotes. Values are coerced into quotes. (For String concatenation, see join.)", .{ .builtin = concat }); 112 | try dt.define("push", "( [...] a -- [...a] ) Push a value into a quote as its new last value.", .{ .builtin = push }); 113 | try dt.define("pop", "( [...a] -- [...] a ) Pop the last value from a quote.", .{ .builtin = pop }); 114 | try dt.define("enq", "( a [...] -- [a...] ) Enqueue a value into a quote as its new first value.", .{ .builtin = enq }); 115 | try dt.define("deq", "( [a...] -- a [...] ) Dequeue the first value from a quote.", .{ .builtin = deq }); 116 | 117 | try dt.define("to-bool", "( a -- bool ) Coerce a value to a boolean.", .{ .builtin = @"to-bool" }); 118 | try dt.define("to-int", "( a -- int ) Coerce a value to an integer.", .{ .builtin = @"to-int" }); 119 | try dt.define("to-float", "( a -- float ) Coerce a value to a floating-point number.", .{ .builtin = @"to-float" }); 120 | try dt.define("to-string", "( a -- string ) Coerce a value to a string.", .{ .builtin = @"to-string" }); 121 | try dt.define("to-cmd", "( a -- command ) Coerce a value to a command.", .{ .builtin = @"to-cmd" }); 122 | try dt.define("to-def", "( a -- deferred ) Coerce a value to a deferred command. (Read as \"definition\" or \"deferred\".)", .{ .builtin = @"to-def" }); 123 | try dt.define("to-quote", "( a -- [...] ) Coerce value to a quote. To quote a quote, use quote.", .{ .builtin = @"to-quote" }); 124 | 125 | try dt.define("inspire", "( -- wisdom ) Get inspiration.", .{ .builtin = inspire }); 126 | try dt.define("buildinfo", "( -- buildinfo ) Get details on how the current dt was built.", .{ .builtin = buildinfo }); 127 | } 128 | 129 | pub fn quit(dt: *DtMachine) !void { 130 | const ctx = try dt.popContext(); 131 | 132 | if (ctx.items.len > 0) { 133 | const stderr = std.io.getStdErr().writer(); 134 | try dt.red(); 135 | try stderr.print("warning(quit): Exited with unused values: [ ", .{}); 136 | 137 | for (ctx.items) |item| { 138 | try item.print(stderr); 139 | try stderr.print(" ", .{}); 140 | } 141 | try stderr.print("] \n", .{}); 142 | try dt.norm(); 143 | } 144 | 145 | osExit(0); 146 | } 147 | 148 | test "drop quit" { 149 | const dt = @import("tests/dt_test_utils.zig").dt; 150 | 151 | const res = try dt(&.{ "drop", "quit" }); 152 | try std.testing.expectEqualStrings("", res.stdout); 153 | try std.testing.expectEqualStrings("", res.stderr); 154 | try std.testing.expectEqual(@as(u8, 0), res.term.Exited); 155 | } 156 | 157 | pub fn exit(dt: *DtMachine) !void { 158 | const log = std.log.scoped(.exit); 159 | 160 | const val = dt.pop() catch Val{ .int = 255 }; 161 | var i = val.intoInt() catch it: { 162 | log.err("Attempted to exit with a value that could not be coerced to integer: {any}", .{val}); 163 | log.err("The program will exit with status code of 1.", .{}); 164 | break :it 1; 165 | }; 166 | 167 | if (i < 0) { 168 | log.err("Attempted to exit with a value less than 0 ({})", .{i}); 169 | log.err("The program will exit with status code of 1.", .{}); 170 | i = 1; 171 | } else if (i > 255) { 172 | log.err("Attempted to exit with a value greater than 255 ({})", .{i}); 173 | log.err("The program will exit with status code of 255.", .{}); 174 | i = 255; 175 | } 176 | 177 | const code: u8 = @intCast(i); 178 | osExit(code); 179 | } 180 | 181 | test "7 exit" { 182 | const dt = @import("tests/dt_test_utils.zig").dt; 183 | 184 | const res = try dt(&.{ "7", "exit" }); 185 | try std.testing.expectEqualStrings("", res.stdout); 186 | try std.testing.expectEqualStrings("", res.stderr); 187 | try std.testing.expectEqual(@as(u8, 7), res.term.Exited); 188 | } 189 | 190 | pub fn version(dt: *DtMachine) !void { 191 | try dt.push(.{ .string = main.version }); 192 | } 193 | 194 | test "version" { 195 | var dt = try DtMachine.init(std.testing.allocator); 196 | defer dt.deinit(); 197 | 198 | try version(&dt); 199 | var v = try dt.pop(); 200 | 201 | try std.testing.expect(v.isString()); 202 | } 203 | 204 | pub fn cwd(dt: *DtMachine) !void { 205 | const theCwd = try std.process.getCwdAlloc(dt.alloc); 206 | try dt.push(.{ .string = theCwd }); 207 | } 208 | 209 | test "cwd" { 210 | var dt = try DtMachine.init(std.testing.allocator); 211 | defer dt.deinit(); 212 | 213 | try cwd(&dt); 214 | var dir = try dt.pop(); 215 | defer std.testing.allocator.free(dir.string); 216 | 217 | try std.testing.expect(dir.isString()); 218 | } 219 | 220 | pub fn cd(dt: *DtMachine) !void { 221 | const log = std.log.scoped(.cd); 222 | 223 | const val = try dt.pop(); 224 | var path = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 225 | 226 | if (std.mem.eql(u8, path, "~")) { 227 | // TODO: Consider windows and other OS conventions. 228 | path = try std.process.getEnvVarOwned(dt.alloc, "HOME"); 229 | } 230 | 231 | osChdir(path) catch |e| return dt.rewind(log, val, e); 232 | } 233 | 234 | test "\".\" cd" { 235 | var dt = try DtMachine.init(std.testing.allocator); 236 | defer dt.deinit(); 237 | 238 | try dt.push(.{ .string = "." }); 239 | try cd(&dt); 240 | } 241 | 242 | pub fn ls(dt: *DtMachine) !void { 243 | var theCwd = if (comptime @hasDecl(std.fs.Dir, "openIterableDir")) 244 | // Zig 0.11 245 | try std.fs.cwd().openIterableDir("/", .{}) 246 | else 247 | // Zig 0.12 248 | try std.fs.cwd().openDir("/", .{ .iterate = true }); 249 | var entries = theCwd.iterate(); 250 | 251 | var quote = Quote.init(dt.alloc); 252 | while (try entries.next()) |entry| { 253 | const name = try dt.alloc.dupe(u8, entry.name); 254 | try quote.append(.{ .string = name }); 255 | } 256 | 257 | try dt.push(.{ .quote = quote }); 258 | 259 | theCwd.close(); 260 | } 261 | 262 | test "ls" { 263 | var dt = try DtMachine.init(std.testing.allocator); 264 | defer dt.deinit(); 265 | 266 | try ls(&dt); 267 | const filesVal = try dt.pop(); 268 | const files = try filesVal.intoQuote(&dt); 269 | defer files.deinit(); 270 | 271 | for (files.items) |file| { 272 | try std.testing.expect(file.isString()); 273 | std.testing.allocator.free(file.string); 274 | } 275 | } 276 | 277 | pub fn readf(dt: *DtMachine) !void { 278 | const log = std.log.scoped(.readf); 279 | 280 | const val = try dt.pop(); 281 | const filename = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 282 | 283 | // We get a Dir from CWD so we can resolve relative paths 284 | const theCwdPath = try std.process.getCwdAlloc(dt.alloc); 285 | defer dt.alloc.free(theCwdPath); 286 | const theCwd = try std.fs.openDirAbsolute(theCwdPath, .{}); 287 | 288 | const contents = _readf(dt, log, theCwd, filename) catch |e| { 289 | try dt.red(); 290 | switch (e) { 291 | error.IsDir => log.warn("\"{s}\" is a directory.", .{filename}), 292 | error.FileNotFound => log.warn("\"{s}\" not found.", .{filename}), 293 | else => { 294 | try dt.norm(); 295 | return e; 296 | }, 297 | } 298 | try dt.norm(); 299 | return; 300 | }; 301 | 302 | try dt.push(.{ .string = contents }); 303 | } 304 | 305 | fn _readf(dt: *DtMachine, log: anytype, dir: std.fs.Dir, filename: []const u8) ![]const u8 { 306 | _ = log; 307 | var file = try dir.openFile(filename, .{ .mode = .read_only }); 308 | defer file.close(); 309 | return try file.readToEndAlloc(dt.alloc, std.math.pow(usize, 2, 16)); 310 | } 311 | 312 | test "\"src/inspiration\" readf" { 313 | var dt = try DtMachine.init(std.testing.allocator); 314 | defer dt.deinit(); 315 | 316 | try dt.push(.{ .string = "src/inspiration" }); 317 | try readf(&dt); 318 | var contents = try dt.pop(); 319 | 320 | try std.testing.expect(contents.isString()); 321 | try std.testing.expect(contents.string.len > 0); 322 | 323 | std.testing.allocator.free(contents.string); 324 | } 325 | 326 | pub fn writef(dt: *DtMachine) !void { 327 | const log = std.log.scoped(.writef); 328 | 329 | const vals = try dt.popN(2); 330 | const filename = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 331 | const contents = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 332 | 333 | // We get a Dir from CWD so we can resolve relative paths 334 | const theCwdPath = try std.process.getCwdAlloc(dt.alloc); 335 | var theCwd = try std.fs.openDirAbsolute(theCwdPath, .{}); 336 | 337 | if (builtin.zig_version.major == 0 and builtin.zig_version.minor >= 13) { 338 | // Zig 0.14, 0.13 339 | try theCwd.writeFile(.{ .data = contents, .sub_path = filename }); 340 | } else { 341 | // Zig 0.12, 0.11 342 | try theCwd.writeFile(filename, contents); 343 | } 344 | theCwd.close(); 345 | } 346 | 347 | pub fn appendf(dt: *DtMachine) !void { 348 | const log = std.log.scoped(.writef); 349 | 350 | const vals = try dt.popN(2); 351 | const filename = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 352 | const contents = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 353 | 354 | // We get a Dir from CWD so we can resolve relative paths 355 | const theCwdPath = try std.process.getCwdAlloc(dt.alloc); 356 | var theCwd = try std.fs.openDirAbsolute(theCwdPath, .{}); 357 | defer theCwd.close(); 358 | 359 | var file = try theCwd.openFile(filename, .{ .mode = .write_only }); 360 | defer file.close(); 361 | 362 | try file.seekFromEnd(0); 363 | try file.writeAll(contents); 364 | } 365 | 366 | pub fn exec(dt: *DtMachine) !void { 367 | const log = std.log.scoped(.exec); 368 | 369 | const val = try dt.pop(); 370 | const childProcess = try val.intoString(dt); 371 | var childArgs = std.mem.splitAny(u8, childProcess, " \t"); 372 | var argv = ArrayList([]const u8).init(dt.alloc); 373 | defer argv.deinit(); 374 | 375 | while (childArgs.next()) |arg| try argv.append(arg); 376 | 377 | const run = if (comptime @hasDecl(std.process.Child, "run")) std.process.Child.run else std.process.Child.exec; 378 | 379 | const result = run(.{ 380 | .allocator = dt.alloc, 381 | .argv = argv.items, 382 | }) catch |e| return dt.rewind(log, val, e); 383 | 384 | switch (result.term) { 385 | .Exited => |code| if (code == 0) { 386 | dt.alloc.free(result.stderr); 387 | try dt.push(.{ .string = result.stdout }); 388 | return; 389 | }, 390 | else => { 391 | try dt.red(); 392 | log.warn("{s}", .{result.stderr}); 393 | try dt.norm(); 394 | 395 | dt.alloc.free(result.stdout); 396 | dt.alloc.free(result.stderr); 397 | 398 | try dt.push(.{ .bool = false }); 399 | }, 400 | } 401 | } 402 | 403 | test "\"echo hi\" exec" { 404 | var dt = try DtMachine.init(std.testing.allocator); 405 | defer dt.deinit(); 406 | 407 | try dt.push(.{ .string = "echo hi" }); 408 | try exec(&dt); 409 | 410 | const res = try dt.pop(); 411 | try std.testing.expect(res.isString()); 412 | try std.testing.expectEqualStrings("hi\n", res.string); 413 | std.testing.allocator.free(res.string); 414 | } 415 | 416 | pub fn @"def!"(dt: *DtMachine) !void { 417 | const log = std.log.scoped(.@"def!"); 418 | 419 | const vals = try dt.popN(2); 420 | 421 | const quote = try vals[0].intoQuote(dt); 422 | const name = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 423 | 424 | try dt.define(name, name, .{ .quote = quote }); 425 | } 426 | 427 | pub fn defs(dt: *DtMachine) !void { 428 | var quote = Quote.init(dt.alloc); 429 | var defNames = dt.defs.keyIterator(); 430 | 431 | while (defNames.next()) |defName| { 432 | const cmdName = try dt.alloc.dupe(u8, defName.*); 433 | try quote.append(.{ .string = cmdName }); 434 | } 435 | 436 | const items = quote.items; 437 | std.mem.sort(Val, items, dt, Val.isLessThan); 438 | 439 | try dt.push(.{ .quote = quote }); 440 | } 441 | 442 | pub fn @"def?"(dt: *DtMachine) !void { 443 | const log = std.log.scoped(.@"def?"); 444 | 445 | const val = try dt.pop(); 446 | const name = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 447 | 448 | try dt.push(.{ .bool = dt.defs.contains(name) }); 449 | } 450 | 451 | pub fn usage(dt: *DtMachine) !void { 452 | const log = std.log.scoped(.usage); 453 | 454 | const val = try dt.pop(); 455 | const cmdName = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 456 | 457 | const cmd = dt.defs.get(cmdName) orelse return dt.rewind(log, val, Error.CommandUndefined); 458 | 459 | const description = try dt.alloc.dupe(u8, cmd.description); 460 | 461 | try dt.push(.{ .string = description }); 462 | } 463 | 464 | pub fn @"def-usage"(dt: *DtMachine) !void { 465 | const log = std.log.scoped(.@"def-usage"); 466 | 467 | const vals = try dt.popN(2); 468 | const name = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 469 | const description = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 470 | 471 | const cmd = dt.defs.get(name) orelse return dt.rewindN(2, log, vals, Error.CommandUndefined); 472 | 473 | try dt.define(name, description, cmd.action); 474 | } 475 | 476 | // Variable binding 477 | pub fn @":"(dt: *DtMachine) !void { 478 | const log = std.log.scoped(.@":"); 479 | 480 | var termVal = try dt.pop(); 481 | 482 | // Single term 483 | if (termVal.isCommand() or termVal.isDeferredCommand() or termVal.isString()) { 484 | const cmdName = try termVal.intoString(dt); 485 | 486 | const val = dt.pop() catch |e| return dt.rewind(log, termVal, e); 487 | 488 | var quote = Quote.init(dt.alloc); 489 | try quote.append(val); 490 | try dt.define(cmdName, cmdName, .{ .quote = quote }); 491 | return; 492 | } 493 | 494 | // Multiple terms 495 | 496 | const terms = (try termVal.intoQuote(dt)).items; 497 | 498 | var vals = try dt.alloc.alloc(Val, terms.len); 499 | 500 | var i = terms.len; 501 | 502 | while (i > 0) : (i -= 1) { 503 | vals[i - 1] = dt.pop() catch |e| { 504 | while (i < terms.len) : (i += 1) { 505 | try dt.push(vals[i]); 506 | } 507 | return dt.rewind(log, termVal, e); 508 | }; 509 | } 510 | 511 | for (terms, vals) |termV, val| { 512 | const term = try termV.intoString(dt); 513 | var quote = ArrayList(Val).init(dt.alloc); 514 | try quote.append(val); 515 | try dt.define(term, term, .{ .quote = quote }); 516 | } 517 | } 518 | 519 | pub fn loop(dt: *DtMachine) !void { 520 | const val = try dt.pop(); 521 | switch (val) { 522 | .command => |cmd| return while (true) try dt.handleCmd(cmd), 523 | .deferred_command => |cmd| return while (true) try dt.handleCmd(cmd), 524 | else => {}, 525 | } 526 | 527 | const quote = try val.intoQuote(dt); 528 | const cmd = Command{ .name = "anonymous loop", .description = "", .action = .{ .quote = quote } }; 529 | 530 | while (true) try cmd.run(dt); 531 | } 532 | 533 | pub fn dup(dt: *DtMachine) !void { 534 | const val = try dt.pop(); 535 | try dt.push(val); 536 | try dt.push(val); 537 | } 538 | 539 | pub fn drop(dt: *DtMachine) !void { 540 | _ = try dt.pop(); 541 | } 542 | 543 | pub fn swap(dt: *DtMachine) !void { 544 | const vals = try dt.popN(2); 545 | try dt.push(vals[1]); 546 | try dt.push(vals[0]); 547 | } 548 | 549 | // ... a b c (rot) ... c a b 550 | // [ 0 1 2 ] [ 2 0 1 ] 551 | pub fn rot(dt: *DtMachine) !void { 552 | const vals = try dt.popN(3); 553 | try dt.push(vals[2]); 554 | try dt.push(vals[0]); 555 | try dt.push(vals[1]); 556 | } 557 | 558 | pub fn p(dt: *DtMachine) !void { 559 | const val = try dt.pop(); 560 | const stdout = std.io.getStdOut().writer(); 561 | try _p(val, stdout); 562 | } 563 | 564 | pub fn ep(dt: *DtMachine) !void { 565 | const val = try dt.pop(); 566 | const stderr = std.io.getStdErr().writer(); 567 | try _p(val, stderr); 568 | } 569 | 570 | fn _p(val: Val, writer: std.fs.File.Writer) !void { 571 | switch (val) { 572 | // When printing strings, do not show " around a string. 573 | .string => |s| try writer.print("{s}", .{s}), 574 | else => try val.print(writer), 575 | } 576 | } 577 | 578 | pub fn red(dt: *DtMachine) !void { 579 | try dt.red(); 580 | } 581 | 582 | pub fn green(dt: *DtMachine) !void { 583 | try dt.green(); 584 | } 585 | 586 | pub fn norm(dt: *DtMachine) !void { 587 | try dt.norm(); 588 | } 589 | 590 | pub fn @".s"(dt: *DtMachine) !void { 591 | const stderr = std.io.getStdErr().writer(); 592 | try stderr.print("[ ", .{}); 593 | 594 | const top = dt.nest.first orelse { 595 | try stderr.print("]", .{}); 596 | return; 597 | }; 598 | 599 | for (top.data.items) |val| { 600 | try val.print(stderr); 601 | try stderr.print(" ", .{}); 602 | } 603 | 604 | try stderr.print("]\n", .{}); 605 | } 606 | 607 | pub fn rl(dt: *DtMachine) !void { 608 | var line = ArrayList(u8).init(dt.alloc); 609 | const stdin = std.io.getStdIn().reader(); 610 | try stdin.streamUntilDelimiter(line.writer(), '\n', null); 611 | 612 | try dt.push(.{ .string = line.items }); 613 | } 614 | 615 | pub fn rls(dt: *DtMachine) !void { 616 | var lines = Quote.init(dt.alloc); 617 | 618 | while (true) { 619 | rl(dt) catch |err| switch (err) { 620 | error.EndOfStream => break, 621 | else => return err, 622 | }; 623 | const val = try dt.pop(); 624 | const line = try val.intoString(dt); 625 | try lines.append(.{ .string = line }); 626 | } 627 | 628 | try dt.push(.{ .quote = lines }); 629 | } 630 | 631 | pub fn procname(dt: *DtMachine) !void { 632 | var procArgs = try std.process.argsWithAllocator(dt.alloc); 633 | const name = procArgs.next() orelse return Error.ProcessNameUnknown; 634 | try dt.push(.{ .string = name }); 635 | } 636 | 637 | pub fn args(dt: *DtMachine) !void { 638 | var quote = Quote.init(dt.alloc); 639 | var procArgs = try std.process.argsWithAllocator(dt.alloc); 640 | _ = procArgs.next(); // Discard process name 641 | 642 | while (procArgs.next()) |arg| { 643 | try quote.append(.{ .string = arg }); 644 | } 645 | 646 | try dt.push(.{ .quote = quote }); 647 | } 648 | 649 | pub fn eval(dt: *DtMachine) !void { 650 | const log = std.log.scoped(.eval); 651 | 652 | var val = try dt.pop(); 653 | const code = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 654 | 655 | var tokens = Token.parse(dt.alloc, code); 656 | while (try tokens.next()) |tok| { 657 | try dt.interpret(tok); 658 | } 659 | } 660 | 661 | pub fn @"interactive?"(state: *DtMachine) !void { 662 | try state.push(.{ .bool = std.io.getStdIn().isTty() }); 663 | } 664 | 665 | pub fn @"+"(dt: *DtMachine) !void { 666 | const log = std.log.scoped(.@"+"); 667 | 668 | const vals = try dt.popN(2); 669 | 670 | if (vals[0].isInt() and vals[1].isInt()) { 671 | const a = try vals[0].intoInt(); 672 | const b = try vals[1].intoInt(); 673 | 674 | const res = @addWithOverflow(a, b); 675 | 676 | if (res[1] == 1) return dt.rewindN(2, log, vals, Error.IntegerOverflow); 677 | 678 | try dt.push(.{ .int = res[0] }); 679 | return; 680 | } 681 | 682 | const a = vals[0].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 683 | const b = vals[1].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 684 | 685 | try dt.push(.{ .float = a + b }); 686 | } 687 | 688 | pub fn @"-"(dt: *DtMachine) !void { 689 | const log = std.log.scoped(.@"-"); 690 | 691 | const vals = try dt.popN(2); 692 | 693 | if (vals[0].isInt() and vals[1].isInt()) { 694 | const a = try vals[0].intoInt(); 695 | const b = try vals[1].intoInt(); 696 | 697 | const res = @subWithOverflow(a, b); 698 | 699 | if (res[1] == 1) return dt.rewindN(2, log, vals, Error.IntegerUnderflow); 700 | 701 | try dt.push(.{ .int = res[0] }); 702 | return; 703 | } 704 | 705 | const a = vals[0].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 706 | const b = vals[1].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 707 | 708 | try dt.push(.{ .float = a - b }); 709 | } 710 | 711 | pub fn @"*"(dt: *DtMachine) !void { 712 | const log = std.log.scoped(.@"*"); 713 | 714 | const vals = try dt.popN(2); 715 | 716 | if (vals[0].isInt() and vals[1].isInt()) { 717 | const a = try vals[0].intoInt(); 718 | const b = try vals[1].intoInt(); 719 | 720 | const res = @mulWithOverflow(a, b); 721 | 722 | if (res[1] == 1) return dt.rewindN(2, log, vals, Error.IntegerOverflow); 723 | 724 | try dt.push(.{ .int = res[0] }); 725 | return; 726 | } 727 | 728 | const a = vals[0].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 729 | const b = vals[1].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 730 | 731 | try dt.push(.{ .float = a * b }); 732 | return; 733 | } 734 | 735 | pub fn @"/"(dt: *DtMachine) !void { 736 | const log = std.log.scoped(.@"/"); 737 | 738 | const vals = try dt.popN(2); 739 | 740 | if (vals[0].isInt() and vals[1].isInt()) { 741 | const a = try vals[0].intoInt(); 742 | const b = try vals[1].intoInt(); 743 | 744 | if (b == 0) return dt.rewindN(2, log, vals, Error.DivisionByZero); 745 | 746 | try dt.push(.{ .int = @divTrunc(a, b) }); 747 | return; 748 | } 749 | 750 | const a = vals[0].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 751 | const b = vals[1].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 752 | 753 | if (b == 0) return dt.rewindN(2, log, vals, Error.DivisionByZero); 754 | 755 | try dt.push(.{ .float = a / b }); 756 | } 757 | 758 | pub fn @"%"(dt: *DtMachine) !void { 759 | const log = std.log.scoped(.@"%"); 760 | 761 | const vals = try dt.popN(2); 762 | 763 | if (vals[0].isInt() and vals[1].isInt()) { 764 | const a = try vals[0].intoInt(); 765 | const b = try vals[1].intoInt(); 766 | 767 | try dt.push(.{ .int = @mod(a, b) }); 768 | return; 769 | } 770 | 771 | const a = vals[0].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 772 | const b = vals[1].intoFloat() catch |e| return dt.rewindN(2, log, vals, e); 773 | 774 | try dt.push(.{ .float = @mod(a, b) }); 775 | return; 776 | } 777 | 778 | pub fn abs(dt: *DtMachine) !void { 779 | const log = std.log.scoped(.abs); 780 | 781 | const val = try dt.pop(); 782 | 783 | if (val.isInt()) { 784 | const a = try val.intoInt(); 785 | 786 | // Safe abs for 0.11.x and 0.12.x 787 | try dt.push(.{ .int = if (a >= 0) a else -a }); 788 | 789 | return; 790 | } 791 | 792 | const a = val.intoFloat() catch |e| return dt.rewind(log, val, e); 793 | 794 | // Safe abs for 0.11.x and 0.12.x 795 | try dt.push(.{ .float = if (a >= 0) a else -a }); 796 | } 797 | 798 | pub fn rand(dt: *DtMachine) !void { 799 | const n = std.crypto.random.int(i64); 800 | try dt.push(.{ .int = n }); 801 | } 802 | 803 | pub fn @"eq?"(dt: *DtMachine) !void { 804 | const vals = try dt.popN(2); 805 | const eq = Val.isEqualTo(dt, vals[0], vals[1]); 806 | try dt.push(.{ .bool = eq }); 807 | } 808 | 809 | pub fn @"gt?"(dt: *DtMachine) !void { 810 | const vals = try dt.popN(2); 811 | const gt = Val.isLessThan(dt, vals[1], vals[0]); 812 | try dt.push(.{ .bool = gt }); 813 | } 814 | 815 | pub fn @"gte?"(dt: *DtMachine) !void { 816 | const vals = try dt.popN(2); 817 | const gte = !Val.isLessThan(dt, vals[0], vals[1]); 818 | try dt.push(.{ .bool = gte }); 819 | } 820 | 821 | pub fn @"lt?"(dt: *DtMachine) !void { 822 | const vals = try dt.popN(2); 823 | const lt = Val.isLessThan(dt, vals[0], vals[1]); 824 | try dt.push(.{ .bool = lt }); 825 | } 826 | 827 | pub fn @"lte?"(dt: *DtMachine) !void { 828 | const vals = try dt.popN(2); 829 | const lte = !Val.isLessThan(dt, vals[1], vals[0]); 830 | try dt.push(.{ .bool = lte }); 831 | } 832 | 833 | pub fn boolAnd(dt: *DtMachine) !void { 834 | var vals = try dt.popN(2); 835 | 836 | const a = vals[0].intoBool(dt); 837 | const b = vals[1].intoBool(dt); 838 | 839 | try dt.push(.{ .bool = a and b }); 840 | } 841 | 842 | pub fn boolOr(dt: *DtMachine) !void { 843 | var vals = try dt.popN(2); 844 | 845 | const a = vals[0].intoBool(dt); 846 | const b = vals[1].intoBool(dt); 847 | 848 | try dt.push(.{ .bool = a or b }); 849 | } 850 | 851 | pub fn not(dt: *DtMachine) !void { 852 | var val = try dt.pop(); 853 | 854 | const a = val.intoBool(dt); 855 | try dt.push(.{ .bool = !a }); 856 | } 857 | 858 | pub fn split(dt: *DtMachine) !void { 859 | const log = std.log.scoped(.split); 860 | 861 | var vals = try dt.popN(2); 862 | 863 | const str = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 864 | const delim = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 865 | 866 | if (delim.len > 0) { 867 | var parts = std.mem.splitSequence(u8, str, delim); 868 | var quote = Quote.init(dt.alloc); 869 | while (parts.next()) |part| { 870 | try quote.append(.{ .string = part }); 871 | } 872 | try dt.push(.{ .quote = quote }); 873 | } else { 874 | var quote = Quote.init(dt.alloc); 875 | for (str) |c| { 876 | var s = try dt.alloc.create([1]u8); 877 | s[0] = c; 878 | try quote.append(.{ .string = s }); 879 | } 880 | try dt.push(.{ .quote = quote }); 881 | } 882 | } 883 | 884 | pub fn join(dt: *DtMachine) !void { 885 | const log = std.log.scoped(.join); 886 | 887 | var vals = try dt.popN(2); 888 | 889 | if (!vals[0].isQuote()) { 890 | const str = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 891 | try dt.push(.{ .string = str }); 892 | return; 893 | } 894 | 895 | const strs = try vals[0].intoQuote(dt); 896 | const delim = try vals[1].intoString(dt); 897 | 898 | var parts = try ArrayList([]const u8).initCapacity(dt.alloc, strs.items.len); 899 | for (strs.items) |part| { 900 | const s = try part.intoString(dt); 901 | try parts.append(s); 902 | } 903 | const acc = try std.mem.join(dt.alloc, delim, parts.items); 904 | try dt.push(.{ .string = acc }); 905 | } 906 | 907 | pub fn upcase(dt: *DtMachine) !void { 908 | const log = std.log.scoped(.upcase); 909 | 910 | var val = try dt.pop(); 911 | const before = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 912 | 913 | const after = try std.ascii.allocUpperString(dt.alloc, before); 914 | 915 | try dt.push(.{ .string = after }); 916 | } 917 | 918 | pub fn downcase(dt: *DtMachine) !void { 919 | const log = std.log.scoped(.downcase); 920 | 921 | var val = try dt.pop(); 922 | const before = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 923 | 924 | const after = try std.ascii.allocLowerString(dt.alloc, before); 925 | 926 | try dt.push(.{ .string = after }); 927 | } 928 | 929 | pub fn startsWith(dt: *DtMachine) !void { 930 | const log = std.log.scoped(.@"starts-with?"); 931 | 932 | var vals = try dt.popN(2); 933 | 934 | const str = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 935 | const prefix = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 936 | 937 | try dt.push(.{ .bool = std.mem.startsWith(u8, str, prefix) }); 938 | } 939 | 940 | pub fn endsWith(dt: *DtMachine) !void { 941 | const log = std.log.scoped(.@"ends-with?"); 942 | 943 | var vals = try dt.popN(2); 944 | 945 | const str = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 946 | const suffix = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 947 | 948 | try dt.push(.{ .bool = std.mem.endsWith(u8, str, suffix) }); 949 | } 950 | 951 | pub fn contains(dt: *DtMachine) !void { 952 | const log = std.log.scoped(.contains); 953 | 954 | var vals = try dt.popN(2); 955 | 956 | if (vals[0].isString() and vals[1].isString()) { 957 | const str = vals[0].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 958 | const substr = vals[1].intoString(dt) catch |e| return dt.rewindN(2, log, vals, e); 959 | 960 | try dt.push(.{ .bool = std.mem.containsAtLeast(u8, str, 1, substr) }); 961 | return; 962 | } 963 | 964 | var child = try dt.child(); 965 | 966 | const haystack = try vals[0].intoQuote(dt); 967 | const needle = vals[1]; 968 | 969 | for (haystack.items) |item| { 970 | try child.push(item); 971 | try child.push(needle); 972 | try @"eq?"(&child); 973 | const found = (try child.pop()).intoBool(&child); 974 | if (found) { 975 | try dt.push(.{ .bool = true }); 976 | return; 977 | } 978 | } 979 | 980 | try dt.push(.{ .bool = false }); 981 | } 982 | 983 | pub fn @"do!"(dt: *DtMachine) !void { 984 | var val = try dt.pop(); 985 | 986 | if (val.isCommand() or val.isDeferredCommand() or val.isString()) { 987 | const cmdName = try val.intoString(dt); 988 | 989 | try dt.handleCmd(cmdName); 990 | return; 991 | } 992 | 993 | const quote = try val.intoQuote(dt); 994 | 995 | for (quote.items) |v| try dt.handleVal(v); 996 | } 997 | 998 | // Same as do! but does not uplevel any definitions 999 | pub fn do(dt: *DtMachine) !void { 1000 | var val = try dt.pop(); 1001 | 1002 | var jail = try dt.child(); 1003 | jail.nest = dt.nest; 1004 | 1005 | if (val.isCommand() or val.isDeferredCommand()) { 1006 | const cmdName = try val.intoString(dt); 1007 | 1008 | try jail.handleCmd(cmdName); 1009 | 1010 | dt.nest = jail.nest; 1011 | return; 1012 | } 1013 | 1014 | const quote = try val.intoQuote(dt); 1015 | 1016 | for (quote.items) |v| try jail.handleVal(v); 1017 | dt.nest = jail.nest; 1018 | } 1019 | 1020 | pub fn @"do!?"(dt: *DtMachine) !void { 1021 | const log = std.log.scoped(.@"do!?"); 1022 | 1023 | var val = try dt.pop(); 1024 | const cond = val.intoBool(dt); 1025 | 1026 | try if (cond) @"do!"(dt) else drop(dt) catch |e| return dt.rewind(log, val, e); 1027 | } 1028 | 1029 | pub fn @"do?"(dt: *DtMachine) !void { 1030 | const log = std.log.scoped(.@"do?"); 1031 | 1032 | var val = try dt.pop(); 1033 | const cond = val.intoBool(dt); 1034 | 1035 | try if (cond) do(dt) else drop(dt) catch |e| return dt.rewind(log, val, e); 1036 | } 1037 | 1038 | pub fn doin(dt: *DtMachine) !void { 1039 | const log = std.log.scoped(.doin); 1040 | 1041 | const vals = try dt.popN(2); 1042 | 1043 | const quote = try vals[0].intoQuote(dt); 1044 | const f = vals[1]; 1045 | 1046 | _doin(dt, quote, f) catch |e| return dt.rewindN(2, log, vals, e); 1047 | } 1048 | 1049 | fn _doin(dt: *DtMachine, quote: Quote, f: Val) !void { 1050 | var child = try dt.child(); 1051 | 1052 | try child.push(.{ .quote = quote }); 1053 | try ellipsis(&child); 1054 | try child.push(f); 1055 | try do(&child); 1056 | 1057 | const resultQuote = try child.popContext(); 1058 | 1059 | try dt.push(.{ .quote = resultQuote }); 1060 | } 1061 | 1062 | pub fn map(dt: *DtMachine) !void { 1063 | const log = std.log.scoped(.map); 1064 | 1065 | const vals = try dt.popN(2); 1066 | 1067 | const quote = try vals[0].intoQuote(dt); 1068 | const f = vals[1]; 1069 | 1070 | _map(dt, quote, f) catch |e| return dt.rewindN(2, log, vals, e); 1071 | } 1072 | 1073 | fn _map(dt: *DtMachine, as: Quote, f: Val) !void { 1074 | var child = try dt.child(); 1075 | 1076 | for (as.items) |a| { 1077 | try child.push(a); 1078 | try child.push(f); 1079 | try do(&child); 1080 | } 1081 | 1082 | const newQuote = try child.popContext(); 1083 | 1084 | try dt.push(Val{ .quote = newQuote }); 1085 | } 1086 | 1087 | pub fn filter(dt: *DtMachine) !void { 1088 | const log = std.log.scoped(.filter); 1089 | 1090 | const vals = try dt.popN(2); 1091 | 1092 | const quote = try vals[0].intoQuote(dt); 1093 | const f = vals[1]; 1094 | 1095 | _filter(dt, quote, f) catch |e| return dt.rewindN(2, log, vals, e); 1096 | } 1097 | 1098 | fn _filter(dt: *DtMachine, as: Quote, f: Val) !void { 1099 | var quote = Quote.init(dt.alloc); 1100 | 1101 | for (as.items) |a| { 1102 | var child = try dt.child(); 1103 | 1104 | try child.push(a); 1105 | try child.push(f); 1106 | try do(&child); 1107 | 1108 | var lastVal = try child.pop(); 1109 | const cond = lastVal.intoBool(dt); 1110 | 1111 | if (cond) { 1112 | try quote.append(a); 1113 | } 1114 | } 1115 | 1116 | try dt.push(Val{ .quote = quote }); 1117 | } 1118 | 1119 | pub fn any(dt: *DtMachine) !void { 1120 | const log = std.log.scoped(.any); 1121 | 1122 | const vals = try dt.popN(2); 1123 | 1124 | const quote = try vals[0].intoQuote(dt); 1125 | const f = vals[1]; 1126 | 1127 | if (quote.items.len == 0) { 1128 | try dt.push(.{ .bool = false }); 1129 | return; 1130 | } 1131 | 1132 | _any(dt, quote, f) catch |e| return dt.rewindN(2, log, vals, e); 1133 | } 1134 | 1135 | fn _any(dt: *DtMachine, as: Quote, f: Val) !void { 1136 | for (as.items) |a| { 1137 | var child = try dt.child(); 1138 | 1139 | try child.push(a); 1140 | try child.push(f); 1141 | try do(&child); 1142 | 1143 | var lastVal = try child.pop(); 1144 | const cond = lastVal.intoBool(dt); 1145 | 1146 | if (cond) { 1147 | try dt.push(Val{ .bool = true }); 1148 | return; 1149 | } 1150 | } 1151 | 1152 | try dt.push(Val{ .bool = false }); 1153 | } 1154 | 1155 | pub fn pop(dt: *DtMachine) !void { 1156 | const log = std.log.scoped(.pop); 1157 | 1158 | const val = try dt.pop(); 1159 | 1160 | var quote = try val.intoQuote(dt); 1161 | 1162 | if (quote.items.len > 0) { 1163 | const lastVal = quote.pop(); 1164 | try dt.push(.{ .quote = quote }); 1165 | try dt.push(if (@TypeOf(lastVal) == ?Val) lastVal.? else lastVal); 1166 | return; 1167 | } 1168 | 1169 | try dt.rewind(log, val, Error.StackUnderflow); 1170 | } 1171 | 1172 | pub fn push(dt: *DtMachine) !void { 1173 | const vals = try dt.popN(2); 1174 | 1175 | const pushMe = vals[1]; 1176 | var quote = try vals[0].intoQuote(dt); 1177 | 1178 | try quote.append(pushMe); 1179 | try dt.push(Val{ .quote = quote }); 1180 | } 1181 | 1182 | pub fn enq(dt: *DtMachine) !void { 1183 | const vals = try dt.popN(2); 1184 | 1185 | const pushMe = vals[0]; 1186 | const quote = try vals[1].intoQuote(dt); 1187 | 1188 | var newQuote = Quote.init(dt.alloc); 1189 | try newQuote.append(pushMe); 1190 | try newQuote.appendSlice(quote.items); 1191 | 1192 | try dt.push(.{ .quote = newQuote }); 1193 | } 1194 | 1195 | pub fn deq(dt: *DtMachine) !void { 1196 | const log = std.log.scoped(.deq); 1197 | 1198 | const val = try dt.pop(); 1199 | 1200 | var quote = try val.intoQuote(dt); 1201 | 1202 | if (quote.items.len == 0) { 1203 | return dt.rewind(log, val, Error.StackUnderflow); 1204 | } 1205 | 1206 | const firstVal = quote.orderedRemove(0); 1207 | try dt.push(firstVal); 1208 | try dt.push(Val{ .quote = quote }); 1209 | } 1210 | 1211 | pub fn len(dt: *DtMachine) !void { 1212 | const val = try dt.pop(); 1213 | 1214 | if (val.isString()) { 1215 | const s = try val.intoString(dt); 1216 | const length: i64 = @intCast(s.len); 1217 | try dt.push(.{ .int = length }); 1218 | return; 1219 | } 1220 | 1221 | const quote = try val.intoQuote(dt); 1222 | const length: i64 = @intCast(quote.items.len); 1223 | try dt.push(.{ .int = length }); 1224 | } 1225 | 1226 | pub fn ellipsis(dt: *DtMachine) !void { 1227 | const log = std.log.scoped(.@"..."); 1228 | 1229 | const val = try dt.pop(); 1230 | 1231 | const quote = val.intoQuote(dt) catch |e| return dt.rewind(log, val, e); 1232 | 1233 | // TODO: Push as slice 1234 | for (quote.items) |v| { 1235 | try dt.push(v); 1236 | } 1237 | } 1238 | 1239 | pub fn rev(dt: *DtMachine) !void { 1240 | const log = std.log.scoped(.rev); 1241 | _ = log; 1242 | 1243 | const val = try dt.pop(); 1244 | 1245 | if (val.isQuote()) { 1246 | const quote = try val.intoQuote(dt); 1247 | const length = quote.items.len; 1248 | 1249 | var newItems = try dt.alloc.alloc(Val, length); 1250 | for (quote.items, 0..) |v, i| { 1251 | newItems[length - i - 1] = v; 1252 | } 1253 | 1254 | const newQuote = Quote.fromOwnedSlice(dt.alloc, newItems); 1255 | 1256 | try dt.push(.{ .quote = newQuote }); 1257 | return; 1258 | } 1259 | 1260 | if (val.isString()) { 1261 | const str = try val.intoString(dt); 1262 | var newStr = try dt.alloc.alloc(u8, str.len); 1263 | 1264 | for (str, 0..) |c, i| { 1265 | newStr[str.len - 1 - i] = c; 1266 | } 1267 | 1268 | try dt.push(.{ .string = newStr }); 1269 | return; 1270 | } 1271 | 1272 | // Some scalar value. 1273 | try dt.push(val); 1274 | } 1275 | 1276 | pub fn sort(dt: *DtMachine) !void { 1277 | const val = try dt.pop(); 1278 | 1279 | // Scalar value 1280 | if (!val.isQuote()) return try dt.push(val); 1281 | 1282 | const quote = try val.intoQuote(dt); 1283 | const items = quote.items; 1284 | std.mem.sort(Val, items, dt, Val.isLessThan); 1285 | 1286 | try dt.push(.{ .quote = quote }); 1287 | } 1288 | 1289 | pub fn quoteVal(dt: *DtMachine) !void { 1290 | const val = try dt.pop(); 1291 | var quote = Quote.init(dt.alloc); 1292 | try quote.append(val); 1293 | try dt.push(.{ .quote = quote }); 1294 | } 1295 | 1296 | pub fn quoteAll(dt: *DtMachine) !void { 1297 | try dt.quoteContext(); 1298 | } 1299 | 1300 | pub fn @"anything?"(dt: *DtMachine) !void { 1301 | const top = dt.nest.first orelse return Error.ContextStackUnderflow; 1302 | try dt.push(.{ .bool = top.data.items.len != 0 }); 1303 | } 1304 | 1305 | pub fn concat(dt: *DtMachine) !void { 1306 | const vals = try dt.popN(2); 1307 | 1308 | var a = try vals[0].intoQuote(dt); 1309 | const b = try vals[1].intoQuote(dt); 1310 | 1311 | try a.appendSlice(b.items); 1312 | 1313 | try dt.push(.{ .quote = a }); 1314 | } 1315 | 1316 | pub fn @"to-bool"(state: *DtMachine) !void { 1317 | const val = try state.pop(); 1318 | const b = val.intoBool(state); 1319 | try state.push(.{ .bool = b }); 1320 | } 1321 | 1322 | pub fn @"to-int"(dt: *DtMachine) !void { 1323 | const log = std.log.scoped(.@"to-int"); 1324 | 1325 | const val = try dt.pop(); 1326 | const i = val.intoInt() catch |e| return dt.rewind(log, val, e); 1327 | try dt.push(.{ .int = i }); 1328 | } 1329 | 1330 | pub fn @"to-float"(dt: *DtMachine) !void { 1331 | const log = std.log.scoped(.@"to-float"); 1332 | 1333 | const val = try dt.pop(); 1334 | const f = val.intoFloat() catch |e| return dt.rewind(log, val, e); 1335 | try dt.push(.{ .float = f }); 1336 | } 1337 | 1338 | pub fn @"to-string"(dt: *DtMachine) !void { 1339 | const log = std.log.scoped(.@"to-string"); 1340 | 1341 | const val = try dt.pop(); 1342 | const s = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 1343 | try dt.push(.{ .string = s }); 1344 | } 1345 | 1346 | pub fn @"to-cmd"(dt: *DtMachine) !void { 1347 | const log = std.log.scoped(.@"to-cmd"); 1348 | 1349 | const val = try dt.pop(); 1350 | const cmd = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 1351 | try dt.push(.{ .command = cmd }); 1352 | } 1353 | 1354 | pub fn @"to-def"(dt: *DtMachine) !void { 1355 | const log = std.log.scoped(.@"to-def"); 1356 | 1357 | const val = try dt.pop(); 1358 | const cmd = val.intoString(dt) catch |e| return dt.rewind(log, val, e); 1359 | try dt.push(.{ .deferred_command = cmd }); 1360 | } 1361 | 1362 | pub fn @"to-quote"(dt: *DtMachine) !void { 1363 | const val = try dt.pop(); 1364 | const quote = try val.intoQuote(dt); 1365 | try dt.push(.{ .quote = quote }); 1366 | } 1367 | 1368 | pub fn inspire(dt: *DtMachine) !void { 1369 | const i = std.crypto.random.uintLessThan(usize, dt.inspiration.items.len); 1370 | try dt.push(.{ .string = dt.inspiration.items[i] }); 1371 | } 1372 | 1373 | // Highly recommend to not take a dependency on the text format here. Only 1374 | // expect that output will be a single string. 1375 | pub fn buildinfo(dt: *DtMachine) !void { 1376 | const infoBits: [2][]const u8 = .{ 1377 | "zig version:", 1378 | builtin.zig_version_string, 1379 | }; 1380 | const info = try std.mem.join(dt.alloc, " ", &infoBits); 1381 | try dt.push(.{ .string = info }); 1382 | } 1383 | -------------------------------------------------------------------------------- /src/dt.dt: -------------------------------------------------------------------------------- 1 | ### Flags and such ### 2 | 3 | [ [ action flag ]: 4 | args [ flag eq? ] any? 5 | action swap do? 6 | ] \dt/handle-flag def 7 | 8 | [ "dt " p version pl ] \dt/print-version def 9 | [ anything? \pls swap do? ] \dt/print-last-val def 10 | 11 | [ dt/print-version 12 | [ "USAGE: dt FLAGS CODE..." 13 | "dt is \"duct tape\" for your unix pipes." 14 | "" 15 | "dt can be used in pipes to transform data with general purpose programming" 16 | "operations. It can also excel in short shebang scripts, or be explored as an" 17 | "interactive REPL (read-eval-print loop)." 18 | "" 19 | "The default behavior of dt when standard input comes from a pipe is to read all" 20 | "lines as a quote of strings. If you need to do something more manual, use stream" 21 | "as the first argument." 22 | "" 23 | 24 | "" 25 | "More info at https://dt.plumbing" 26 | ] pls 27 | ] \dt/print-help def 28 | 29 | [ [ dt/print-help 0 exit ] "--help" dt/handle-flag 30 | [ dt/print-version 0 exit ] "--version" dt/handle-flag 31 | ] \dt/handle-flags def 32 | 33 | [ args unwords words 34 | dup len 0 neq? \has-args: 35 | # Drop the first arg if it's '--stream' 36 | [ deq swap \first-arg: 37 | first-arg "--stream" neq? \keep: 38 | [ first-arg swap enq ] keep do? 39 | ] has-args do? 40 | ] \dt/args def 41 | 42 | [ dt/handle-flags dt/args unwords eval ] \dt/run-args def! 43 | 44 | 45 | ### PIPE things ### 46 | 47 | [ readlns dt/run-args dt/print-last-val ] \dt/pipe-thru-args def! 48 | 49 | 50 | ### REPL things ### 51 | 52 | [ red "dt " ep version epl norm inspire epl ] \dt/repl-greet def 53 | 54 | # Definitions only used in the dt REPL 55 | [ \quit \.q def 56 | \quit \exit def 57 | # TODO: define a help command 58 | ] \dt/repl-prelude def 59 | 60 | [ dt/handle-flags dt/repl-greet dt/repl-prelude repl ] \dt/main-repl def! 61 | -------------------------------------------------------------------------------- /src/inspiration: -------------------------------------------------------------------------------- 1 | Be generous with the duct tape, you know; spare the duct tape, spoil the job. 2 | If it ain't broke, you're not trying. 3 | If the women don't find you handsome, they should at least find you handy. 4 | Remember, you may have to grow old, but you don't have to mature. 5 | Remember, I'm pulling for ya. We're all in this together! 6 | When the going gets tough, switch to power tools. 7 | Quando omni flunkus, moritadi. 8 | Learn from my mistakes - someone should. 9 | You're only limited by your own imagination, and the laws in your area. 10 | Now, this is only temporary... unless it works. 11 | Keep your stick on the ice. -------------------------------------------------------------------------------- /src/interpret.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Color = std.io.tty.Color; 3 | const ArrayList = std.ArrayList; 4 | const Stack = std.SinglyLinkedList; 5 | const Allocator = std.mem.Allocator; 6 | // const stdin = std.io.getStdIn().reader(); 7 | // const stdout = std.io.getStdOut().writer(); 8 | // const stderr = std.io.getStdErr().writer(); 9 | 10 | const string = @import("string.zig"); 11 | const String = string.String; 12 | 13 | const Token = @import("tokens.zig").Token; 14 | 15 | const inspiration = @embedFile("inspiration"); 16 | 17 | const types = @import("types.zig"); 18 | const Error = types.Error; 19 | const Dictionary = types.Dictionary; 20 | const Val = types.Val; 21 | const Quote = types.Quote; 22 | 23 | pub const DtMachine = struct { 24 | alloc: Allocator, 25 | 26 | nest: Stack(ArrayList(Val)), 27 | depth: u8, 28 | 29 | defs: Dictionary, 30 | 31 | stdoutConfig: std.io.tty.Config, 32 | stderrConfig: std.io.tty.Config, 33 | 34 | stdout: std.fs.File.Writer, 35 | stderr: std.fs.File.Writer, 36 | 37 | inspiration: ArrayList(String), 38 | 39 | pub fn init(alloc: Allocator) !DtMachine { 40 | var nest = Stack(Quote){}; 41 | const mainNode = try alloc.create(Stack(Quote).Node); 42 | mainNode.* = Stack(Quote).Node{ .data = Quote.init(alloc) }; 43 | nest.prepend(mainNode); 44 | 45 | var inspirations = ArrayList(String).init(alloc); 46 | var lines = std.mem.tokenizeScalar(u8, inspiration, '\n'); 47 | while (lines.next()) |line| { 48 | try inspirations.append(line); 49 | } 50 | 51 | return .{ 52 | .alloc = alloc, 53 | .nest = nest, 54 | .depth = 0, 55 | .defs = Dictionary.init(alloc), 56 | .stdoutConfig = std.io.tty.detectConfig(std.io.getStdOut()), 57 | .stderrConfig = std.io.tty.detectConfig(std.io.getStdErr()), 58 | .stdout = std.io.getStdOut().writer(), 59 | .stderr = std.io.getStdErr().writer(), 60 | .inspiration = inspirations, 61 | }; 62 | } 63 | 64 | pub fn deinit(self: *DtMachine) void { 65 | self.defs.deinit(); 66 | self.inspiration.deinit(); 67 | 68 | var node = self.nest.first; 69 | while (node) |n| { 70 | node = n.next; 71 | n.data.deinit(); 72 | self.alloc.destroy(n); 73 | } 74 | } 75 | 76 | pub fn interpret(self: *DtMachine, tok: Token) !void { 77 | switch (tok) { 78 | .term => |cmdName| try self.handleCmd(cmdName), 79 | .left_bracket => { 80 | try self.pushContext(); 81 | self.depth += 1; 82 | }, 83 | .right_bracket => { 84 | if (self.depth == 0) { 85 | return Error.TooManyRightBrackets; 86 | } 87 | 88 | self.depth -= 1; 89 | 90 | const context = try self.popContext(); 91 | try self.push(Val{ .quote = context }); 92 | }, 93 | .bool => |b| try self.push(Val{ .bool = b }), 94 | .int => |i| try self.push(Val{ .int = i }), 95 | .float => |f| try self.push(Val{ .float = f }), 96 | .string => |s| { 97 | const unescaped = try string.unescape(self.alloc, s); 98 | try self.push(Val{ .string = unescaped }); 99 | }, 100 | .deferred_term => |cmd| try self.push(Val{ .deferred_command = cmd }), 101 | .none => {}, 102 | } 103 | } 104 | 105 | pub fn handleVal(self: *DtMachine, val: Val) anyerror!void { 106 | const log = std.log.scoped(.@"dt.handleVal"); 107 | 108 | switch (val) { 109 | .command => |name| { 110 | self.handleCmd(name) catch |e| { 111 | if (e != error.CommandUndefined) return e; 112 | try self.red(); 113 | log.warn("Undefined: {s}", .{name}); 114 | try self.norm(); 115 | }; 116 | }, 117 | // TODO: Ensure that this is never necessary; Clone immediately before words that mutate for efficiency. 118 | else => try self.push(try val.deepClone(self)), 119 | } 120 | } 121 | 122 | pub fn handleCmd(self: *DtMachine, cmdName: String) !void { 123 | const log = std.log.scoped(.@"dt.handleCmd"); 124 | 125 | if (self.depth > 0) { 126 | try self.push(Val{ .command = cmdName }); 127 | return; 128 | } 129 | 130 | if (self.defs.get(cmdName)) |cmd| { 131 | try cmd.run(self); 132 | return; 133 | } else { 134 | try self.red(); 135 | log.warn("Undefined: {s}", .{cmdName}); 136 | try self.norm(); 137 | } 138 | } 139 | 140 | pub fn loadFile(self: *DtMachine, code: []const u8) !void { 141 | var toks = Token.parse(self.alloc, code); 142 | while (try toks.next()) |token| try self.interpret(token); 143 | } 144 | 145 | pub fn red(self: *DtMachine) !void { 146 | try self.norm(); 147 | try self.stdoutConfig.setColor(self.stdout, Color.red); 148 | try self.stderrConfig.setColor(self.stderr, Color.red); 149 | } 150 | 151 | pub fn green(self: *DtMachine) !void { 152 | try self.stdoutConfig.setColor(self.stdout, Color.green); 153 | try self.stdoutConfig.setColor(self.stdout, Color.bold); 154 | 155 | try self.stderrConfig.setColor(self.stderr, Color.green); 156 | try self.stdoutConfig.setColor(self.stdout, Color.bold); 157 | } 158 | 159 | pub fn norm(self: *DtMachine) !void { 160 | try self.stdoutConfig.setColor(self.stdout, Color.reset); 161 | try self.stderrConfig.setColor(self.stderr, Color.reset); 162 | } 163 | 164 | pub fn child(self: *DtMachine) !DtMachine { 165 | var newMachine = try DtMachine.init(self.alloc); 166 | 167 | // TODO: Persistent map for dictionary would make this much cheaper. 168 | newMachine.defs = try self.defs.clone(); 169 | 170 | return newMachine; 171 | } 172 | 173 | pub fn define(self: *DtMachine, name: String, description: String, action: Action) !void { 174 | const cmd = Command{ .name = name, .description = description, .action = action }; 175 | try self.defs.put(name, cmd); 176 | } 177 | 178 | pub fn rewind(self: *DtMachine, log: anytype, val: Val, err: anyerror) anyerror!void { 179 | log.warn("{s}, rewinding {any}", .{ @errorName(err), val }); 180 | try self.push(val); 181 | return err; 182 | } 183 | 184 | pub fn rewindN(self: *DtMachine, comptime n: comptime_int, log: anytype, vals: [n]Val, err: anyerror) anyerror!void { 185 | log.warn("{s}, rewinding {any}", .{ @errorName(err), vals }); 186 | for (vals) |val| try self.push(val); 187 | return err; 188 | } 189 | 190 | pub fn push(self: *DtMachine, val: Val) !void { 191 | var top = self.nest.first orelse return Error.ContextStackUnderflow; 192 | try top.data.append(val); 193 | } 194 | 195 | pub fn pushN(self: *DtMachine, comptime n: comptime_int, vals: [n]Val) !void { 196 | // TODO: push as slice 197 | for (vals) |val| try self.push(val); 198 | } 199 | 200 | pub fn pop(self: *DtMachine) !Val { 201 | var top = self.nest.first orelse return Error.ContextStackUnderflow; 202 | if (top.data.items.len < 1) { 203 | return Error.StackUnderflow; 204 | } 205 | const val = top.data.pop(); 206 | return if (@TypeOf(val) == ?Val) val.? else val; 207 | } 208 | 209 | // Removes and returns top N values from the stack from oldest to youngest. Last index is the most recent, 0 is the oldest. 210 | pub fn popN(self: *DtMachine, comptime n: comptime_int) ![n]Val { 211 | var vals: [n]Val = undefined; 212 | 213 | comptime var i = n - 1; 214 | inline while (i >= 0) : (i -= 1) { 215 | vals[i] = self.pop() catch |e| { 216 | comptime var j = i + 1; 217 | inline while (j > n) : (j += 1) { 218 | try self.push(vals[j]); 219 | } 220 | return e; 221 | }; 222 | } 223 | 224 | return vals; 225 | } 226 | 227 | pub fn pushContext(self: *DtMachine) !void { 228 | const node = try self.alloc.create(Stack(Quote).Node); 229 | node.* = .{ .data = Quote.init(self.alloc) }; 230 | self.nest.prepend(node); 231 | } 232 | 233 | pub fn popContext(self: *DtMachine) !Quote { 234 | const node = self.nest.popFirst() orelse return Error.ContextStackUnderflow; 235 | return node.data; 236 | } 237 | 238 | pub fn quoteContext(self: *DtMachine) !void { 239 | const node = self.nest.popFirst(); 240 | const quote = if (node) |n| n.data else Quote.init(self.alloc); 241 | 242 | if (self.nest.first == null) try self.pushContext(); 243 | 244 | try self.push(.{ .quote = quote }); 245 | } 246 | }; 247 | 248 | pub const Command = struct { 249 | name: String, 250 | description: String, 251 | action: Action, 252 | 253 | pub fn run(self: Command, state: *DtMachine) anyerror!void { 254 | switch (self.action) { 255 | .builtin => |b| return try b(state), 256 | .quote => |quote| { 257 | var again = true; 258 | 259 | var vals = quote.items; 260 | var lastIndex = vals.len - 1; 261 | 262 | while (again) { 263 | again = false; 264 | 265 | for (vals[0..lastIndex]) |val| { 266 | try state.handleVal(val); 267 | } 268 | 269 | const lastVal = vals[lastIndex]; 270 | 271 | switch (lastVal) { 272 | // Tail calls optimized, yay! 273 | .command => |cmdName| { 274 | // Even if this is the same command name, we should re-fetch in case it's been redefined 275 | const cmd = state.defs.get(cmdName) orelse return Error.CommandUndefined; 276 | switch (cmd.action) { 277 | .quote => |nextQuote| { 278 | again = true; 279 | vals = nextQuote.items; 280 | lastIndex = vals.len - 1; 281 | }, 282 | .builtin => |b| return try b(state), 283 | } 284 | }, 285 | else => try state.handleVal(lastVal), 286 | } 287 | } 288 | }, 289 | } 290 | } 291 | }; 292 | 293 | pub const Action = union(enum) { 294 | builtin: *const fn (*DtMachine) anyerror!void, 295 | quote: Quote, 296 | }; 297 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const osExit = if (@hasDecl(std, "posix")) std.posix.exit else std.os.exit; 4 | 5 | const builtins = @import("builtins.zig"); 6 | 7 | const interpret = @import("interpret.zig"); 8 | const DtMachine = interpret.DtMachine; 9 | 10 | // TODO: Change to @import when it's supported for zon 11 | pub const version = "1.3.2"; // Update in build.zig.zon as well. 12 | 13 | const stdlib = @embedFile("stdlib.dt"); 14 | const dtlib = @embedFile("dt.dt"); 15 | 16 | pub fn main() !void { 17 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 18 | defer arena.deinit(); 19 | 20 | var dt = try DtMachine.init(arena.allocator()); 21 | 22 | try builtins.defineAll(&dt); 23 | try dt.loadFile(stdlib); 24 | try dt.loadFile(dtlib); 25 | 26 | const stdinPiped = !std.io.getStdIn().isTty(); 27 | const stdoutPiped = !std.io.getStdOut().isTty(); 28 | 29 | if (@import("builtin").os.tag == .windows) { 30 | _ = std.os.windows.kernel32.SetConsoleOutputCP(65001); 31 | } 32 | 33 | const firstArgMaybe = try readFirstArg(arena.allocator()); 34 | 35 | if (firstArgMaybe) |firstArg| { 36 | if (try readShebangFile(arena.allocator(), firstArg)) |fileContents| { 37 | return dt.loadFile(fileContents) catch |e| return doneOrDie(&dt, e); 38 | } else if ((std.mem.eql(u8, firstArg, "--stream") or std.mem.startsWith(u8, firstArg, "--stream ")) and (stdinPiped or stdoutPiped)) { 39 | dt.handleCmd("dt/run-args") catch |e| return doneOrDie(&dt, e); 40 | } 41 | } 42 | 43 | if (stdinPiped) { 44 | dt.handleCmd("dt/pipe-thru-args") catch |e| return doneOrDie(&dt, e); 45 | } else { 46 | dt.handleCmd("dt/run-args") catch |e| return doneOrDie(&dt, e); 47 | } 48 | } 49 | 50 | fn readFirstArg(allocator: Allocator) !?[]const u8 { 51 | var args = try std.process.argsWithAllocator(allocator); 52 | _ = args.skip(); // Discard process name 53 | return if (args.next()) |arg| try allocator.dupe(u8, arg) else null; 54 | } 55 | 56 | fn doneOrDie(dt: *DtMachine, reason: anyerror) !void { 57 | const stderr = std.io.getStdErr().writer(); 58 | try stderr.print("\n", .{}); 59 | switch (reason) { 60 | error.EndOfStream => {}, 61 | error.BrokenPipe => {}, 62 | else => { 63 | try dt.red(); 64 | try stderr.print("\nRIP: {any}\n", .{reason}); 65 | try dt.norm(); 66 | 67 | osExit(1); 68 | }, 69 | } 70 | } 71 | 72 | fn readShebangFile(allocator: Allocator, maybeFilepath: []const u8) !?[]const u8 { 73 | // We get a Dir from CWD so we can resolve relative paths 74 | const theCwdPath = try std.process.getCwdAlloc(allocator); 75 | var theCwd = try std.fs.openDirAbsolute(theCwdPath, .{}); 76 | 77 | const file = theCwd.openFile(maybeFilepath, .{}) catch return null; 78 | defer file.close(); 79 | 80 | const contents = try file.readToEndAlloc(allocator, std.math.pow(usize, 2, 16)); 81 | 82 | if (std.mem.startsWith(u8, contents, "#!")) { 83 | return contents; 84 | } 85 | 86 | return null; 87 | } 88 | 89 | test { 90 | std.testing.refAllDecls(@This()); 91 | _ = @import("tests/bool_tests.zig"); 92 | _ = @import("tests/dt_args_basic.zig"); 93 | _ = @import("tests/dtsh_run_basic.zig"); 94 | _ = @import("tests/def_scope_tests.zig"); 95 | _ = @import("tests/project_euler_tests.zig"); 96 | _ = @import("tests/dtsh_interactive_basic.zig"); 97 | } 98 | -------------------------------------------------------------------------------- /src/stdlib.dt: -------------------------------------------------------------------------------- 1 | ### Binding ### 2 | 3 | [ swap quote [ do ] concat swap def! ] 4 | \def def! 5 | \def "( action name -- ) Define a command." def-usage 6 | 7 | [ swap rot dup rot def swap def-usage ] 8 | \define def! 9 | \define "( action description name -- ) Define a command with a description." def-usage 10 | 11 | [ dup rot swap dup rot [ do ] enq swap def! usage def-usage ] 12 | \alias def! 13 | \alias "( prev new -- ) Alias a new command from another, copying the description of the previous command." def-usage 14 | 15 | [ def? not ] "( name -- bool ) Determine whether a command is undefined." 16 | \undef? define 17 | 18 | 19 | ### Display ### 20 | 21 | # Display: stdout 22 | 23 | [ "\n" p ] 24 | "( -- ) Print a newline to standard output." 25 | \nl define 26 | 27 | [ p nl ] 28 | "( a -- ) Print the most recent value and a newline to standard output." 29 | \pl define 30 | 31 | [ \pl each ] 32 | "( [...] -- ) Print the values of the most recent quote, each followed by a newline, to standard output." 33 | \pls define 34 | 35 | \p \print alias 36 | \pl \println alias 37 | \pls \printlns alias 38 | 39 | # Display: stderr 40 | 41 | [ "\n" ep ] 42 | "( -- ) Print a newline to standard error." 43 | \enl define 44 | 45 | [ ep enl ] 46 | "( a -- ) Print the most recent value and a newline to standard error." 47 | \epl define 48 | 49 | [ \epl each ] 50 | "( [...] -- ) Print the values of the most recent quote, each followed by a newline, to standard error." 51 | \epls define 52 | 53 | \ep \eprint alias 54 | \epl \eprintln alias 55 | \epls \eprintlns alias 56 | 57 | # Display: misc 58 | 59 | \.s \status alias 60 | 61 | 62 | ### Reading ### 63 | 64 | \rl \read-line alias # Drop in 2.0 65 | \rl \readln alias 66 | \rls \read-lines alias # Drop in 2.0 67 | \rls \readlns alias 68 | 69 | 70 | ### Filesystem and process things ### 71 | 72 | [ cwd pl ] 73 | "( -- ) Print the current working directory to standard output." 74 | \pwd define 75 | 76 | 77 | ### Math and such ### 78 | 79 | [ 2 % 0 eq? ] "( a -- bool ) Determine if a number is even." \even? define 80 | [ 2 % 1 eq? ] "( a -- bool ) Determine if a number is odd." \odd? define 81 | [ % 0 eq? ] "( a b -- bool ) Determine if a number a is evenly divisible by number b." \divisor? define 82 | 83 | 84 | ### Boolean operators ### 85 | 86 | [ eq? not ] 87 | "( a b -- bool ) Determine if two values are unequal." 88 | \neq? define 89 | 90 | [ and not ] 91 | "( a b -- bool ) Determine if two values are not both truthy." 92 | \nand define 93 | 94 | [ or not ] 95 | "( a b -- bool ) Determine if neither of two values are truthy." 96 | \nor define 97 | 98 | 99 | ### String things ### 100 | 101 | [ "" split ] 102 | "( string -- [substring] ) Splits a string into individual characters, where a character is a single byte. (Not friendly to UTF-8 runes.)" 103 | \chars define 104 | 105 | # TODO: runes 106 | 107 | [ " " split ] 108 | "( string -- [substring] ) Splits a string on spaces." 109 | \words define 110 | 111 | [ " " join ] 112 | "( [substring] -- string ) Joins strings with spaces." 113 | \unwords define 114 | 115 | [ "\n" split ] 116 | "( string -- [substring] ) Splits a string on newlines." 117 | \lines define 118 | 119 | [ "\n" join ] 120 | "( [substring] -- string ) Joins strings with newlines." 121 | \unlines define 122 | 123 | 124 | ### Quote manipulation ### 125 | 126 | [ deq drop ] 127 | "( [a...] -- a ) The first element of a quote." 128 | \first define 129 | 130 | [ pop swap drop ] 131 | "( [...a] -- a ) The last element of a quote." 132 | \last define 133 | 134 | \... \unquote alias 135 | 136 | 137 | ### Control flow ### 138 | 139 | [ [ cmd n ]: 140 | n 0 gt? \continue : 141 | [ cmd do cmd n 1 - times ] continue do? 142 | ] 143 | "( ... action n -- ... ) Perform a given action n times." 144 | \times define 145 | 146 | [ map drop ] 147 | "( [...] action -- ) Perform a given action with each value in a quote." 148 | \each define 149 | 150 | [ [ action cond ]: 151 | [ action do action cond while ] cond do do? 152 | ] 153 | "( ... action cond -- ... ) Perform an action while the condition is truthy." 154 | \while define 155 | 156 | 157 | ### Unixy things ### 158 | 159 | [ args deq swap drop ] \shebang-args def! 160 | 161 | 162 | ### Parsing ### 163 | 164 | [ lines [ "," split ] map ] 165 | "( string -- [[...]] ) Naive conversion of a raw string in comma-separated value (CSV) format to a quote of lines of cells" 166 | \parse-csv define 167 | 168 | 169 | ### Testing ### 170 | 171 | [ [ cond msg ]: [ msg epl 1 exit ] cond not do? ] 172 | "( cond message -- ) Naive assertion that requires a condition to be truthy. If falsy, it will print a message to standard error and fail with an exit code of 1." 173 | \assert-true define 174 | 175 | 176 | ### REPL ### 177 | 178 | [ "» " p readln eval repl ] \repl def! 179 | 180 | [ defs [ [ name ]: green name p norm "\t" p name usage pl ] each ] 181 | "( -- ) Print commands and their usage" 182 | \help define 183 | -------------------------------------------------------------------------------- /src/string.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | 4 | pub const String = []const u8; 5 | 6 | const Escape = struct { 7 | from: String, 8 | to: String, 9 | }; 10 | 11 | const escapes = [_]Escape{ 12 | .{ .from = "\n", .to = "\\n" }, 13 | .{ .from = "\r", .to = "\\r" }, 14 | .{ .from = "\t", .to = "\\t" }, 15 | .{ .from = "\"", .to = "\\\"" }, 16 | .{ .from = "\\", .to = "\\\\" }, 17 | }; 18 | 19 | pub fn escape(allocator: Allocator, unescaped: String) !String { 20 | var result = try allocator.dupe(u8, unescaped); 21 | 22 | for (escapes) |esc| { 23 | result = try std.mem.replaceOwned(u8, allocator, result, esc.from, esc.to); 24 | } 25 | 26 | return result; 27 | } 28 | 29 | pub fn unescape(allocator: Allocator, unescaped: String) !String { 30 | var result = try allocator.dupe(u8, unescaped); 31 | 32 | for (escapes) |esc| { 33 | result = try std.mem.replaceOwned(u8, allocator, result, esc.to, esc.from); 34 | } 35 | 36 | return result; 37 | } 38 | -------------------------------------------------------------------------------- /src/tests/basic.dt: -------------------------------------------------------------------------------- 1 | "Hello world!" pl 2 | -------------------------------------------------------------------------------- /src/tests/bool_tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqual = std.testing.expectEqual; 3 | const expectEqualStrings = std.testing.expectEqualStrings; 4 | 5 | const dt = @import("dt_test_utils.zig").dt; 6 | 7 | test "true" { 8 | const res = try dt(&.{ "true", "print" }); 9 | try expectEqualStrings("", res.stderr); 10 | try expectEqualStrings("true", res.stdout); 11 | try expectEqual(@as(u8, 0), res.term.Exited); 12 | } 13 | 14 | test "false" { 15 | const res = try dt(&.{ "false", "print" }); 16 | try expectEqualStrings("", res.stderr); 17 | try expectEqualStrings("false", res.stdout); 18 | try expectEqual(@as(u8, 0), res.term.Exited); 19 | } 20 | 21 | test "not" { 22 | try expectEqualStrings("true", (try dt(&.{"false not print"})).stdout); 23 | try expectEqualStrings("false", (try dt(&.{"true not print"})).stdout); 24 | } 25 | 26 | test "bool_equality" { 27 | const first_result = try dt(&.{"true true eq? print"}); 28 | try expectEqualStrings("", first_result.stderr); 29 | try expectEqualStrings("true", first_result.stdout); 30 | try expectEqualStrings("true", (try dt(&.{"false false eq? print"})).stdout); 31 | try expectEqualStrings("true", (try dt(&.{"true false neq? print"})).stdout); 32 | try expectEqualStrings("true", (try dt(&.{"false true neq? print"})).stdout); 33 | 34 | try expectEqualStrings("false", (try dt(&.{"true true neq? print"})).stdout); 35 | try expectEqualStrings("false", (try dt(&.{"false false neq? print"})).stdout); 36 | try expectEqualStrings("false", (try dt(&.{"true false eq? print"})).stdout); 37 | try expectEqualStrings("false", (try dt(&.{"false true eq? print"})).stdout); 38 | } 39 | 40 | test "numeric_equality" { 41 | try expectEqualStrings("true", (try dt(&.{"1 1 eq? print"})).stdout); 42 | try expectEqualStrings("true", (try dt(&.{"1 1.0 eq? print"})).stdout); 43 | try expectEqualStrings("true", (try dt(&.{"1.0 1 eq? print"})).stdout); 44 | try expectEqualStrings("true", (try dt(&.{"1.0 1.0 eq? print"})).stdout); 45 | 46 | try expectEqualStrings("false", (try dt(&.{"1 2 eq? print"})).stdout); 47 | try expectEqualStrings("false", (try dt(&.{"1 2.0 eq? print"})).stdout); 48 | try expectEqualStrings("false", (try dt(&.{"1.0 2 eq? print"})).stdout); 49 | try expectEqualStrings("false", (try dt(&.{"1.0 2.0 eq? print"})).stdout); 50 | 51 | try expectEqualStrings("false", (try dt(&.{"1 1 neq? print"})).stdout); 52 | try expectEqualStrings("false", (try dt(&.{"1 1.0 neq? print"})).stdout); 53 | try expectEqualStrings("false", (try dt(&.{"1.0 1 neq? print"})).stdout); 54 | try expectEqualStrings("false", (try dt(&.{"1.0 1.0 neq? print"})).stdout); 55 | 56 | try expectEqualStrings("true", (try dt(&.{"1 2 neq? print"})).stdout); 57 | try expectEqualStrings("true", (try dt(&.{"1 2.0 neq? print"})).stdout); 58 | try expectEqualStrings("true", (try dt(&.{"1.0 2 neq? print"})).stdout); 59 | try expectEqualStrings("true", (try dt(&.{"1.0 2.0 neq? print"})).stdout); 60 | } 61 | 62 | test "string_equality" { 63 | try expectEqualStrings("true", (try dt(&.{"\"apple\" \"apple\" eq? print"})).stdout); 64 | try expectEqualStrings("true", (try dt(&.{"\"apple\" \"orange\" neq? print"})).stdout); 65 | 66 | try expectEqualStrings("false", (try dt(&.{"\"apple\" \"apple\" neq? print"})).stdout); 67 | try expectEqualStrings("false", (try dt(&.{"\"apple\" \"orange\" eq? print"})).stdout); 68 | } 69 | 70 | test "quote_of_many_types_equality" { 71 | try expectEqualStrings("true", (try dt(&.{"[ true 2 -3.4e5 \"6\" [ print ] ]\n[ true 2 -3.4e5 \"6\" [ print ] ]\neq? print\n"})).stdout); 72 | } 73 | 74 | test "comparison_lt" { 75 | try expectEqualStrings("true", (try dt(&.{"1 2 lt? print"})).stdout); 76 | try expectEqualStrings("true", (try dt(&.{"1 1.1 lt? print"})).stdout); 77 | try expectEqualStrings("true", (try dt(&.{"1.1 2 lt? print"})).stdout); 78 | try expectEqualStrings("true", (try dt(&.{"1.1 2.2 lt? print"})).stdout); 79 | 80 | try expectEqualStrings("false", (try dt(&.{"1 1 lt? print"})).stdout); 81 | try expectEqualStrings("false", (try dt(&.{"1 1.0 lt? print"})).stdout); 82 | try expectEqualStrings("false", (try dt(&.{"1.0 1 lt? print"})).stdout); 83 | try expectEqualStrings("false", (try dt(&.{"1.0 1.0 lt? print"})).stdout); 84 | try expectEqualStrings("false", (try dt(&.{"1 0 lt? print"})).stdout); 85 | try expectEqualStrings("false", (try dt(&.{"1 0.9 lt? print"})).stdout); 86 | try expectEqualStrings("false", (try dt(&.{"1.1 1 lt? print"})).stdout); 87 | try expectEqualStrings("false", (try dt(&.{"1.1 0.9 lt? print"})).stdout); 88 | } 89 | 90 | test "comparison_lte" { 91 | try expectEqualStrings("true", (try dt(&.{"1 2 lte? print"})).stdout); 92 | try expectEqualStrings("true", (try dt(&.{"1 1 lte? print"})).stdout); 93 | try expectEqualStrings("true", (try dt(&.{"1 1.1 lte? print"})).stdout); 94 | try expectEqualStrings("true", (try dt(&.{"1.1 2 lte? print"})).stdout); 95 | try expectEqualStrings("true", (try dt(&.{"1.1 1.1 lte? print"})).stdout); 96 | try expectEqualStrings("true", (try dt(&.{"1 1.0 lte? print"})).stdout); 97 | try expectEqualStrings("true", (try dt(&.{"1.0 1 lte? print"})).stdout); 98 | 99 | try expectEqualStrings("false", (try dt(&.{"1 0 lte? print"})).stdout); 100 | try expectEqualStrings("false", (try dt(&.{"1 0.9 lte? print"})).stdout); 101 | try expectEqualStrings("false", (try dt(&.{"1.1 1 lte? print"})).stdout); 102 | try expectEqualStrings("false", (try dt(&.{"1.1 0.9 lte? print"})).stdout); 103 | } 104 | test "comparison_gt" { 105 | try expectEqualStrings("true", (try dt(&.{"2 1 gt? print"})).stdout); 106 | try expectEqualStrings("true", (try dt(&.{"1.1 1 gt? print"})).stdout); 107 | try expectEqualStrings("true", (try dt(&.{"2 1.1 gt? print"})).stdout); 108 | try expectEqualStrings("true", (try dt(&.{"2.2 1.1 gt? print"})).stdout); 109 | 110 | try expectEqualStrings("false", (try dt(&.{"1 1 gt? print"})).stdout); 111 | try expectEqualStrings("false", (try dt(&.{"1.0 1 gt? print"})).stdout); 112 | try expectEqualStrings("false", (try dt(&.{"1 1.0 gt? print"})).stdout); 113 | try expectEqualStrings("false", (try dt(&.{"1.0 1.0 gt? print"})).stdout); 114 | try expectEqualStrings("false", (try dt(&.{"0 1 gt? print"})).stdout); 115 | try expectEqualStrings("false", (try dt(&.{"0.9 1 gt? print"})).stdout); 116 | try expectEqualStrings("false", (try dt(&.{"1 1.1 gt? print"})).stdout); 117 | try expectEqualStrings("false", (try dt(&.{"0.9 1.1 gt? print"})).stdout); 118 | } 119 | 120 | test "comparison_gte" { 121 | try expectEqualStrings("true", (try dt(&.{"2 1 gte? print"})).stdout); 122 | try expectEqualStrings("true", (try dt(&.{"1 1 gte? print"})).stdout); 123 | try expectEqualStrings("true", (try dt(&.{"1.1 1 gte? print"})).stdout); 124 | try expectEqualStrings("true", (try dt(&.{"2 1.1 gte? print"})).stdout); 125 | try expectEqualStrings("true", (try dt(&.{"1.1 1.1 gte? print"})).stdout); 126 | try expectEqualStrings("true", (try dt(&.{"1.0 1 gte? print"})).stdout); 127 | try expectEqualStrings("true", (try dt(&.{"1 1.0 gte? print"})).stdout); 128 | 129 | try expectEqualStrings("false", (try dt(&.{"0 1 gte? print"})).stdout); 130 | try expectEqualStrings("false", (try dt(&.{"0.9 1 gte? print"})).stdout); 131 | try expectEqualStrings("false", (try dt(&.{"1 1.1 gte? print"})).stdout); 132 | try expectEqualStrings("false", (try dt(&.{"0.9 1.1 gte? print"})).stdout); 133 | } 134 | -------------------------------------------------------------------------------- /src/tests/def_scope_tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqualStrings = std.testing.expectEqualStrings; 3 | 4 | const dt = @import("dt_test_utils.zig").dt; 5 | 6 | test "basic_def" { 7 | const source = 8 | \\ [1 +] \inc def 9 | \\ 1 inc print 10 | \\ \inc def? print 11 | ; 12 | try expectEqualStrings("2true", (try dt(&.{source})).stdout); 13 | } 14 | 15 | test "basic_do_bang_def" { 16 | const source = 17 | \\ [[] \empty-quote : ] do! 18 | \\ 19 | \\ "empty-quote" def? "do! should define in parent context" assert-true 20 | ; 21 | try expectEqualStrings("", (try dt(&.{source})).stderr); 22 | try expectEqualStrings("", (try dt(&.{source})).stdout); 23 | } 24 | 25 | test "basic_do_def" { 26 | const source = 27 | \\ [[] \empty-quote : ] do 28 | \\ 29 | \\"empty-quote" undef? "do should NOT define in parent context" assert-true 30 | ; 31 | try expectEqualStrings("", (try dt(&.{source})).stderr); 32 | try expectEqualStrings("", (try dt(&.{source})).stdout); 33 | } 34 | 35 | test "basic_colon_do" { 36 | const source = 37 | \\ 3 "banana" 38 | \\ 39 | \\ # Print banana three times 40 | \\ [[n str]: [str print] n times] do 41 | \\ 42 | \\ "n" undef? "do must not leak definitions, but n was defined" assert-true 43 | \\ "str" undef? "do must not leak definitions, but str was defined" assert-true 44 | ; 45 | const res = (try dt(&.{source})); 46 | try expectEqualStrings("", res.stderr); 47 | try expectEqualStrings("banana" ** 3, res.stdout); 48 | } 49 | 50 | test "colon_in_times" { 51 | const source = 52 | \\ 1 53 | \\ [ [n] : 54 | \\ n println 55 | \\ n 2 * 56 | \\ ] 7 times 57 | \\ drop 58 | \\ 59 | \\ \n undef? "times must not leak definitions, but n was defined" assert-true 60 | ; 61 | const res = (try dt(&.{source})); 62 | try expectEqualStrings("", res.stderr); 63 | try expectEqualStrings("1\n" ++ "2\n" ++ "4\n" ++ "8\n" ++ "16\n" ++ "32\n" ++ "64\n", res.stdout); 64 | } 65 | 66 | test "colon_in_each" { 67 | const source = 68 | \\ [ 1 2 3 4 5 ] 69 | \\ [ \n : n n * println ] each 70 | \\ 71 | \\ \n undef? "each must not leak definitions, but n was defined" assert-true 72 | ; 73 | const res = (try dt(&.{source})); 74 | try expectEqualStrings("", res.stderr); 75 | try expectEqualStrings("1\n" ++ "4\n" ++ "9\n" ++ "16\n" ++ "25\n", res.stdout); 76 | } 77 | 78 | test "colon_in_map" { 79 | const source = 80 | \\ ["apple" "banana" "cereal"] 81 | \\ [[food]: food upcase] map print 82 | \\ 83 | \\ \food undef? "map must not leak definitions, but food was defined" assert-true 84 | ; 85 | const res = (try dt(&.{source})); 86 | try expectEqualStrings("", res.stderr); 87 | try expectEqualStrings("[ \"APPLE\" \"BANANA\" \"CEREAL\" ]", res.stdout); 88 | } 89 | 90 | test "colon_in_filter" { 91 | const source = 92 | \\ [[1 "banana"] [2 "banana"] [3 "banana"] [4 "bananas make a bunch!"]] 93 | \\ [...[n str]: n even? str len odd?] filter ... print 94 | \\ 95 | \\ \n undef? "filter must not leak definitions, but n was defined" assert-true 96 | \\ \str undef? "filter must not leak definitions, but str was defined" assert-true 97 | ; 98 | const res = (try dt(&.{source})); 99 | try expectEqualStrings("", res.stderr); 100 | try expectEqualStrings("[ 4 \"bananas make a bunch!\" ]", res.stdout); 101 | } 102 | 103 | test "shadowing_in_do" { 104 | const source = 105 | \\ [ 5 ] "fav-number" def 106 | \\ 107 | \\ fav-number println 108 | \\ 109 | \\ [ 110 | \\ [ 8 ] "fav-number" def 111 | \\ fav-number println 112 | \\ ] do 113 | \\ 114 | \\ fav-number println 115 | ; 116 | const res = (try dt(&.{source})); 117 | try expectEqualStrings("", res.stderr); 118 | try expectEqualStrings("5\n" ++ "8\n" ++ "5\n", res.stdout); 119 | } 120 | 121 | test "shadowing_colon_in_do" { 122 | const source = 123 | \\ [ 6 ] "fav-number" def 124 | \\ 125 | \\ fav-number println 126 | \\ 127 | \\ 2 [ [ fav-number ] : fav-number println ] do 128 | \\ 129 | \\ fav-number println 130 | ; 131 | const res = (try dt(&.{source})); 132 | try expectEqualStrings("", res.stderr); 133 | try expectEqualStrings("6\n" ++ "2\n" ++ "6\n", res.stdout); 134 | } 135 | 136 | test "shadowing_in_times" { 137 | const source = 138 | \\ [ 1 ] \n def 139 | \\ 140 | \\ n [ \n : n println n 1 + ] 3 times 141 | \\ drop 142 | \\ 143 | \\ n println 144 | ; 145 | 146 | const res = (try dt(&.{source})); 147 | try expectEqualStrings("", res.stderr); 148 | try expectEqualStrings("1\n" ++ "2\n" ++ "3\n" ++ "1\n", res.stdout); 149 | } 150 | 151 | test "shadowing_in_each" { 152 | const source = 153 | \\ [ "pepperoni" ] \pizza def 154 | \\ 155 | \\ pizza println 156 | \\ 157 | \\ [ "cheese" "bbq" "combo" ] [ \pizza : pizza println ] each 158 | \\ 159 | \\ pizza println 160 | ; 161 | const res = (try dt(&.{source})); 162 | try expectEqualStrings("", res.stderr); 163 | try expectEqualStrings("pepperoni\n" ++ "cheese\n" ++ "bbq\n" ++ "combo\n" ++ "pepperoni\n", res.stdout); 164 | } 165 | 166 | test "shadowing_in_map" { 167 | const source = 168 | \\ [ "banana" ] \x def 169 | \\ 170 | \\ x println 171 | \\ 172 | \\ [ 1 2 3 ] [ \x : x 2.0 / ] map println 173 | \\ 174 | \\ x println 175 | ; 176 | const res = (try dt(&.{source})); 177 | try expectEqualStrings("", res.stderr); 178 | try expectEqualStrings("banana\n" ++ "[ 0.5 1 1.5 ]\n" ++ "banana\n", res.stdout); 179 | } 180 | 181 | test "shadowing_in_filter" { 182 | const source = 183 | \\ [ "whee" ] \happy-word def 184 | \\ 185 | \\ happy-word println 186 | \\ 187 | \\ [ "yay" "hurray" "whoo" "huzzah" ] [ \happy-word : happy-word len 4 gt? ] filter println 188 | \\ 189 | \\ happy-word println 190 | ; 191 | const res = (try dt(&.{source})); 192 | try expectEqualStrings("", res.stderr); 193 | try expectEqualStrings("whee\n" ++ "[ \"hurray\" \"huzzah\" ]\n" ++ "whee\n", res.stdout); 194 | } 195 | -------------------------------------------------------------------------------- /src/tests/dt_args_basic.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqualStrings = std.testing.expectEqualStrings; 3 | 4 | const dt = @import("dt_test_utils.zig").dt; 5 | 6 | test "status" { 7 | try expectEqualStrings("[ ]\n", (try dt(&.{ "quote-all", "drop", "status" })).stderr); 8 | } 9 | 10 | test "one_plus_one_is_two" { 11 | try expectEqualStrings("2\n", (try dt(&.{ "1", "1", "+", "println" })).stdout); 12 | } 13 | 14 | test "quoted_one_plus_one_is_two" { 15 | try expectEqualStrings("2\n", (try dt(&.{"1 1 + println"})).stdout); 16 | } 17 | -------------------------------------------------------------------------------- /src/tests/dt_test_utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Child = std.process.Child; 3 | const allocator = std.heap.page_allocator; 4 | 5 | const MAX_FILE_SIZE = 1 << 12; 6 | 7 | const ChildResult = if (@hasDecl(Child, "RunResult")) Child.RunResult else Child.ExecResult; 8 | const run = if (@hasDecl(Child, "run")) Child.run else Child.exec; 9 | 10 | pub fn dtRunFile(file_path: []const u8) !ChildResult { 11 | const cur_dir = std.fs.cwd(); 12 | const contents = try cur_dir.readFileAlloc(allocator, file_path, MAX_FILE_SIZE); 13 | return try dtStdin(contents); 14 | } 15 | 16 | pub fn dtStdin(input: []const u8) !ChildResult { 17 | return try run(.{ .allocator = allocator, .argv = &.{ 18 | "./zig-out/bin/dt", 19 | "[\"#\" starts-with? not] filter", 20 | "unwords", 21 | "eval", 22 | input, 23 | } }); 24 | } 25 | 26 | pub fn dt(argv: []const []const u8) !ChildResult { 27 | var args = std.ArrayList([]const u8).init(allocator); 28 | defer args.deinit(); 29 | 30 | try args.append("./zig-out/bin/dt"); 31 | 32 | for (argv) |arg| { 33 | try args.append(arg); 34 | } 35 | 36 | return try run(.{ .allocator = allocator, .argv = args.items }); 37 | } 38 | -------------------------------------------------------------------------------- /src/tests/dtsh_interactive_basic.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqualStrings = std.testing.expectEqualStrings; 3 | 4 | const utils = @import("dt_test_utils.zig"); 5 | const dtStdin = utils.dtStdin; 6 | const dt = utils.dt; 7 | 8 | test "one_plus_one_how_hard_could_it_be" { 9 | try expectEqualStrings("2\n", (try dt(&.{"1 1 + pl"})).stdout); 10 | } 11 | 12 | test "one_plus_one_is_two" { 13 | try expectEqualStrings("2\n", (try dtStdin("1 1 + pl\n")).stdout); 14 | } 15 | 16 | test "one_plus_one_is_still_two" { 17 | try expectEqualStrings("2\n", (try dtStdin("1 1 [ + ] do pl\n")).stdout); 18 | } 19 | 20 | test "one_plus_one_is_definitely_two" { 21 | try expectEqualStrings("2\n", (try dtStdin("1 [ 1 + ] do pl\n")).stdout); 22 | } 23 | 24 | test "one_plus_one_is_positively_two" { 25 | try expectEqualStrings("2\n", (try dtStdin("[ 1 ] 2 times + pl\n")).stdout); 26 | } 27 | 28 | test "one_plus_one_is_never_not_two" { 29 | try expectEqualStrings("2\n", (try dtStdin("[ 1 ] [ 1 ] [ + ] [ concat ] 2 times do pl\n")).stdout); 30 | } 31 | -------------------------------------------------------------------------------- /src/tests/dtsh_run_basic.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqual = std.testing.expectEqual; 3 | const expectEqualStrings = std.testing.expectEqualStrings; 4 | 5 | const dtRunFile = @import("dt_test_utils.zig").dtRunFile; 6 | 7 | test "say_hello" { 8 | const res = try dtRunFile("./src/tests/basic.dt"); 9 | try expectEqualStrings("", res.stderr); 10 | try expectEqual(@as(u8, 0), res.term.Exited); 11 | try expectEqualStrings("Hello world!\n", res.stdout); 12 | } 13 | -------------------------------------------------------------------------------- /src/tests/hello.dt: -------------------------------------------------------------------------------- 1 | [ "hello" pl ] \greet def 2 | -------------------------------------------------------------------------------- /src/tests/project_euler/problem-01.dt: -------------------------------------------------------------------------------- 1 | # Find the sum of all the multiples of 3 or 5 below 1000. 2 | # https://projecteuler.net/problem=1 3 | 4 | # TODO: Builtin for range? [ lower upper step ] : etc... 5 | [ [ 1 ] [ [ dup 1 + ] doin ] 998 times ] "range" def 6 | 7 | range 8 | 9 | [ [ n ] : 10 | n 3 divisor? 11 | n 5 divisor? or 12 | ] filter 13 | 14 | dup len 1 - [ k ] : 15 | 16 | [ [ + ] doin ] k times 17 | 18 | pop println 19 | -------------------------------------------------------------------------------- /src/tests/project_euler/problem-02a.dt: -------------------------------------------------------------------------------- 1 | # By considering the terms in the Fibonacci sequence whose values do not 2 | # exceed four million, find the sum of the even-valued terms. 3 | # https://projecteuler.net/problem=2 4 | 5 | [ 1 2 ] 6 | 7 | [ [ a b ] : a b a b + ] [ fib-once ] def 8 | 9 | [ 4 1000 * 1000 * ] [ 4_million ] def 10 | 11 | [ dup last 4_million gt? ] [ [ fib-once ] doin ] while 12 | 13 | [ even? ] filter 14 | 15 | [ dup length 1 lt? ] [ [ + ] doin ] while 16 | 17 | unquote println 18 | -------------------------------------------------------------------------------- /src/tests/project_euler/problem-02b.dt: -------------------------------------------------------------------------------- 1 | # By considering the terms in the Fibonacci sequence whose values do not 2 | # exceed four million, find the sum of the even-valued terms. 3 | # https://projecteuler.net/problem=2 4 | 5 | [ 1 2 ] 6 | 7 | [ [a b]: a b a b + ] [ fib-once ] def 8 | 9 | [ 4 1000 * 1000 * ] [ 4_million ] def 10 | 11 | [ [fib-once] doin ] [ swap dup last 4_million gt? ] while 12 | 13 | [ even? ] filter 14 | 15 | \ns : 16 | ns [ [+] doin ] 17 | ns len 1 - times 18 | 19 | unquote println 20 | -------------------------------------------------------------------------------- /src/tests/project_euler/problem-03.dt: -------------------------------------------------------------------------------- 1 | # What is the largest prime factor of the number 600851475143? 2 | # https://projecteuler.net/problem=3 3 | 4 | # Uhh ok this is sorta cheating, but pretty unixy I guess. 5 | # TODO: Implement a proper primes generator and sieve? 6 | "/usr/bin/factor 600851475143" exec "stdout" extract words last pl 7 | -------------------------------------------------------------------------------- /src/tests/project_euler/problem-04.dt: -------------------------------------------------------------------------------- 1 | # Find the largest palindrome made from the product of two 3-digit numbers. 2 | # https://projecteuler.net/problem=4 3 | 4 | [ digits dup rev eq? ] [ palin? ] def 5 | 6 | [ 999 999 [ ] ] 7 | 8 | [...[ a b ps ]:: 9 | [ 10 | [ a b * palin? ] [ [ ] a push 11 | b 1 - push 12 | ps a b * push push ] 13 | # Assuming both factors will be in the 900s... 14 | [ b 900 eq? ] [ [ ] a 1 - push 15 | 999 push 16 | ps push ] 17 | [ otherwise ] [ [ ] a push 18 | b 1 - push 19 | ps push ] 20 | ] ? 21 | ] [ palin-once ] def 22 | 23 | [ palin-once ] 10000 times 24 | 25 | last maximum println 26 | -------------------------------------------------------------------------------- /src/tests/project_euler_tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqualStrings = std.testing.expectEqualStrings; 3 | 4 | const dtRunFile = @import("dt_test_utils.zig").dtRunFile; 5 | 6 | test "problem_01" { 7 | try expectEqualStrings("233168\n", (try dtRunFile("./src/tests/project_euler/problem-01.dt")).stdout); 8 | } 9 | 10 | // Broken by zig impl changes 11 | 12 | // test "problem_02a" { 13 | // try expectEqualStrings("4613732\n", (try dtRunFile("./src/tests/project_euler/problem-02a.dt")).stdout); 14 | // } 15 | 16 | // test "problem_02b" { 17 | // try expectEqualStrings("4613732\n", (try dtRunFile("./src/tests/project_euler/problem-02b.dt")).stdout); 18 | // } 19 | 20 | // test "problem_03" { 21 | // try expectEqualStrings("6857\n", (try dtRunFile("./src/tests/project_euler/problem-03.dt")).stdout); 22 | // } 23 | 24 | // test "problem_04" { 25 | // try expectEqualStrings("906609\n", (try dtRunFile("./src/tests/project_euler/problem-04.dt")).stdout); 26 | // } 27 | -------------------------------------------------------------------------------- /src/tokens.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ArrayList = std.ArrayList; 3 | const Allocator = std.mem.Allocator; 4 | 5 | const specialChars = .{ 6 | .alwaysSingle = "[]:", 7 | .whitespace = " ,\t\r\n", 8 | }; 9 | 10 | pub const TokenIterator = struct { 11 | allocator: Allocator, 12 | buf: []const u8, 13 | index: usize, 14 | 15 | const Self = @This(); 16 | 17 | pub fn next(self: *Self) !?Token { 18 | // If the index is out-of-bounds then we are done now and forever 19 | if (self.index >= self.buf.len) return null; 20 | 21 | // First, skip any whitespace. Return null if nothing else remains 22 | const start = std.mem.indexOfNonePos(u8, self.buf, self.index, specialChars.whitespace) orelse { 23 | self.index = self.buf.len; 24 | return null; 25 | }; 26 | 27 | switch (self.buf[start]) { 28 | '"' => { // Parse a string 29 | const strStart = start + 1; 30 | var end = start + 1; 31 | var keepLookin = true; 32 | while (keepLookin) { 33 | // Out-of-bounds: unterminated string. Return string as slice from start to end of buffer. 34 | if (end >= self.buf.len) { 35 | self.index = self.buf.len; 36 | return .{ .string = self.buf[start..(self.buf.len)] }; 37 | } 38 | 39 | end = std.mem.indexOfPos(u8, self.buf, end, "\"") orelse self.buf.len; 40 | 41 | if (self.buf[end - 1] != '\\') { 42 | // We found the end! 43 | keepLookin = false; 44 | } else { 45 | // Found a quote, but it was escaped. Search from next character 46 | end += 1; 47 | } 48 | } 49 | self.index = end + 1; 50 | return .{ .string = self.buf[strStart..end] }; 51 | }, 52 | '#' => { // Ignore a comment (by recursively returning the next non-comment token) 53 | self.index = std.mem.indexOfAnyPos(u8, self.buf, start, "\r\n") orelse self.buf.len; 54 | return self.next(); 55 | }, 56 | '[' => { 57 | self.index = start + 1; 58 | return .left_bracket; 59 | }, 60 | ']' => { 61 | self.index = start + 1; 62 | return .right_bracket; 63 | }, 64 | ':' => { 65 | self.index = start + 1; 66 | return .{ .term = ":" }; 67 | }, 68 | else => { // Parse a token 69 | const end = std.mem.indexOfAnyPos(u8, self.buf, start, specialChars.alwaysSingle ++ specialChars.whitespace) orelse self.buf.len; 70 | self.index = end; 71 | return Token.parseOneToken(self.buf[start..end]); 72 | }, 73 | } 74 | } 75 | }; 76 | 77 | pub const Token = union(enum) { 78 | left_bracket: void, 79 | right_bracket: void, 80 | bool: bool, 81 | int: i64, 82 | float: f64, 83 | term: []const u8, 84 | deferred_term: []const u8, 85 | string: []const u8, 86 | none: void, 87 | 88 | pub fn parse(allocator: Allocator, code: []const u8) TokenIterator { 89 | return .{ 90 | .allocator = allocator, 91 | .buf = code, 92 | .index = 0, 93 | }; 94 | } 95 | 96 | fn parseOneToken(part: []const u8) Token { 97 | if (std.mem.eql(u8, part, "[")) { 98 | return .left_bracket; 99 | } 100 | if (std.mem.eql(u8, part, "]")) { 101 | return .right_bracket; 102 | } 103 | if (std.mem.eql(u8, part, "true")) { 104 | return .{ .bool = true }; 105 | } 106 | if (std.mem.eql(u8, part, "false")) { 107 | return .{ .bool = false }; 108 | } 109 | if (std.mem.startsWith(u8, part, "\\")) { 110 | const deferredTerm = part[1..]; 111 | return .{ .deferred_term = deferredTerm }; 112 | } 113 | if (std.fmt.parseInt(i64, part, 10)) |i| { 114 | return .{ .int = i }; 115 | } else |_| {} 116 | if (std.fmt.parseFloat(f64, part)) |f| { 117 | return .{ .float = f }; 118 | } else |_| {} 119 | 120 | return .{ .term = part }; 121 | } 122 | 123 | fn assertEql(self: Token, other: Token) void { 124 | switch (self) { 125 | .left_bracket => std.debug.assert(other == Token.left_bracket), 126 | .right_bracket => std.debug.assert(other == Token.right_bracket), 127 | .bool => |b| std.debug.assert(other.bool == b), 128 | .int => |i| std.debug.assert(other.int == i), 129 | .float => |f| std.debug.assert(other.float == f), 130 | .string => |s| std.debug.assert(std.mem.eql(u8, other.string, s)), 131 | .term => |t| std.debug.assert(std.mem.eql(u8, other.term, t)), 132 | .deferred_term => |t| std.debug.assert(std.mem.eql(u8, other.deferred_term, t)), 133 | .none => std.debug.assert(other == Token.none), 134 | } 135 | } 136 | }; 137 | 138 | // Testing! 139 | 140 | test "parse hello.dt" { 141 | var expected = ArrayList(Token).init(std.testing.allocator); 142 | defer expected.deinit(); 143 | try expected.append(Token.left_bracket); 144 | try expected.append(Token{ .string = "hello" }); 145 | try expected.append(Token{ .term = "pl" }); 146 | try expected.append(Token.right_bracket); 147 | try expected.append(Token{ .deferred_term = "greet" }); 148 | try expected.append(Token{ .term = "def" }); 149 | 150 | const helloFile = @embedFile("tests/hello.dt"); 151 | var tokens = Token.parse(std.testing.allocator, helloFile); 152 | 153 | var i: u8 = 0; 154 | while (try tokens.next()) |token| : (i += 1) { 155 | std.log.info("Expected: {any}, Actual: {any} ... ", .{ expected.items[i], token }); 156 | expected.items[i].assertEql(token); 157 | std.log.info("PASS\n", .{}); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/types.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Atomic = std.atomic.Atomic; 3 | const ArrayList = std.ArrayList; 4 | const Allocator = std.mem.Allocator; 5 | const StringHashMap = std.StringHashMap; 6 | 7 | const string = @import("string.zig"); 8 | const String = string.String; 9 | 10 | const interpret = @import("interpret.zig"); 11 | const Command = interpret.Command; 12 | const DtMachine = interpret.DtMachine; 13 | 14 | pub const Quote = ArrayList(Val); 15 | pub const Dictionary = StringHashMap(Command); 16 | 17 | pub const Val = union(enum) { 18 | bool: bool, 19 | int: i64, // TODO: std.math.big.int.Mutable? 20 | float: f64, // TODO: std.math.big.Rational? 21 | string: String, 22 | command: String, 23 | deferred_command: String, 24 | quote: Quote, 25 | 26 | pub fn deinit(self: *Val, state: *DtMachine) void { 27 | switch (self.*) { 28 | .string => |s| state.alloc.free(s), 29 | .command => |cmd| state.alloc.free(cmd), 30 | .deferred_command => |cmd| state.alloc.free(cmd), 31 | .quote => |q| { 32 | for (q.items) |*i| { 33 | i.deinit(state); 34 | } 35 | q.deinit(); 36 | }, 37 | else => {}, 38 | } 39 | } 40 | 41 | pub fn isBool(self: Val) bool { 42 | return switch (self) { 43 | .bool => true, 44 | else => false, 45 | }; 46 | } 47 | 48 | pub fn intoBool(self: Val, state: *DtMachine) bool { 49 | return switch (self) { 50 | .bool => |b| b, 51 | .int => |i| i > 0, 52 | .float => |f| f > 0, 53 | .string => |s| !std.mem.eql(u8, "", s), 54 | .quote => |q| q.items.len > 0, 55 | 56 | // Commands are truthy if defined 57 | .command => |cmd| state.defs.contains(cmd), 58 | .deferred_command => |cmd| state.defs.contains(cmd), 59 | }; 60 | } 61 | 62 | pub fn isInt(self: Val) bool { 63 | return switch (self) { 64 | .int => true, 65 | else => false, 66 | }; 67 | } 68 | 69 | pub fn intoInt(self: Val) !i64 { 70 | return switch (self) { 71 | .int => |i| i, 72 | 73 | .bool => |b| if (b) 1 else 0, 74 | .float => |f| @as(i64, @intFromFloat(f)), 75 | .string => |s| std.fmt.parseInt(i64, s, 10), 76 | else => Error.NoCoercionToInteger, 77 | }; 78 | } 79 | 80 | pub fn isFloat(self: Val) bool { 81 | return switch (self) { 82 | .float => true, 83 | else => false, 84 | }; 85 | } 86 | 87 | pub fn intoFloat(self: Val) !f64 { 88 | return switch (self) { 89 | .float => |f| f, 90 | 91 | .bool => |b| if (b) 1 else 0, 92 | .int => |i| @as(f64, @floatFromInt(i)), 93 | .string => |s| std.fmt.parseFloat(f64, s), 94 | else => Error.NoCoercionToInteger, 95 | }; 96 | } 97 | 98 | pub fn isCommand(self: Val) bool { 99 | return switch (self) { 100 | .command => true, 101 | else => false, 102 | }; 103 | } 104 | 105 | pub fn isDeferredCommand(self: Val) bool { 106 | return switch (self) { 107 | .deferred_command => true, 108 | else => false, 109 | }; 110 | } 111 | 112 | pub fn isString(self: Val) bool { 113 | return switch (self) { 114 | .string => true, 115 | else => false, 116 | }; 117 | } 118 | 119 | pub fn intoString(self: Val, state: *DtMachine) !String { 120 | return switch (self) { 121 | .command => |cmd| cmd, 122 | 123 | .deferred_command => |cmd| cmd, 124 | .string => |s| s, 125 | .bool => |b| if (b) "true" else "false", 126 | .int => |i| try std.fmt.allocPrint(state.alloc, "{}", .{i}), 127 | .float => |f| try std.fmt.allocPrint(state.alloc, "{}", .{f}), 128 | .quote => |q| switch (q.items.len) { 129 | 0 => "", 130 | 1 => q.items[0].intoString(state), 131 | else => Error.NoCoercionToString, 132 | }, 133 | }; 134 | } 135 | 136 | pub fn isQuote(self: Val) bool { 137 | return switch (self) { 138 | .quote => true, 139 | else => false, 140 | }; 141 | } 142 | 143 | pub fn intoQuote(self: Val, state: *DtMachine) !Quote { 144 | return switch (self) { 145 | .quote => |q| q, 146 | else => { 147 | var q = Quote.init(state.alloc); 148 | try q.append(self); 149 | return q; 150 | }, 151 | }; 152 | } 153 | 154 | pub fn deepClone(self: Val, state: *DtMachine) anyerror!Val { 155 | switch (self) { 156 | .string => |s| { 157 | const cloned = try state.alloc.dupe(u8, s); 158 | return .{ .string = cloned }; 159 | }, 160 | .command => |cmd| { 161 | const cloned = try state.alloc.dupe(u8, cmd); 162 | return .{ .command = cloned }; 163 | }, 164 | .deferred_command => |cmd| { 165 | const cloned = try state.alloc.dupe(u8, cmd); 166 | return .{ .deferred_command = cloned }; 167 | }, 168 | .quote => |q| return .{ .quote = try _deepClone(q, state) }, 169 | else => return self, 170 | } 171 | } 172 | 173 | fn _deepClone(quote: Quote, state: *DtMachine) anyerror!Quote { 174 | var cloned = try Quote.initCapacity(state.alloc, quote.items.len); 175 | for (quote.items) |item| { 176 | try cloned.append(try item.deepClone(state)); 177 | } 178 | return cloned; 179 | } 180 | 181 | pub fn isEqualTo(dt: *DtMachine, lhs: Val, rhs: Val) bool { 182 | if (lhs.isBool() and rhs.isBool()) { 183 | const a = lhs.intoBool(dt); 184 | const b = rhs.intoBool(dt); 185 | 186 | return a == b; 187 | } 188 | 189 | if (lhs.isInt() and rhs.isInt()) { 190 | const a = lhs.intoInt() catch unreachable; 191 | const b = rhs.intoInt() catch unreachable; 192 | 193 | return a == b; 194 | } 195 | 196 | if ((lhs.isInt() or lhs.isFloat()) and (rhs.isInt() or rhs.isFloat())) { 197 | const a = lhs.intoFloat() catch unreachable; 198 | const b = rhs.intoFloat() catch unreachable; 199 | 200 | return a == b; 201 | } 202 | 203 | if (lhs.isString() and rhs.isString()) { 204 | const a = lhs.intoString(dt) catch unreachable; 205 | const b = rhs.intoString(dt) catch unreachable; 206 | 207 | return std.mem.eql(u8, a, b); 208 | } 209 | 210 | if (lhs.isCommand() and rhs.isCommand()) { 211 | const a = lhs.intoString(dt) catch unreachable; 212 | const b = rhs.intoString(dt) catch unreachable; 213 | 214 | return std.mem.eql(u8, a, b); 215 | } 216 | 217 | if (lhs.isDeferredCommand() and rhs.isDeferredCommand()) { 218 | const a = lhs.intoString(dt) catch unreachable; 219 | const b = rhs.intoString(dt) catch unreachable; 220 | 221 | return std.mem.eql(u8, a, b); 222 | } 223 | 224 | if (lhs.isQuote() and rhs.isQuote()) { 225 | const quoteA = lhs.intoQuote(dt) catch unreachable; 226 | const quoteB = rhs.intoQuote(dt) catch unreachable; 227 | 228 | const as: []Val = quoteA.items; 229 | const bs: []Val = quoteB.items; 230 | 231 | if (as.len != bs.len) return false; 232 | 233 | for (as, bs) |a, b| { 234 | if (!Val.isEqualTo(dt, a, b)) return false; 235 | } 236 | 237 | // Length is equal and all values were equal 238 | return true; 239 | } 240 | 241 | return false; 242 | } 243 | 244 | /// This provides the following "natural" ordering when vals are different types: 245 | /// bool, int/float, string, command, deferred command, quote 246 | /// 247 | /// Strings are compared character-by-character (lexicographically), where 248 | /// capital letters are "less than" lowercase letters. When one string is a 249 | /// prefix of another, the shorter string is "less than" the other. 250 | /// 251 | /// (The same string rules apply to command and deferred command names.) 252 | /// 253 | /// Quotes are compared value-by-value. When one quote is a prefix of 254 | /// another, the shorter quote is "less than" the other. 255 | pub fn isLessThan(dt: *DtMachine, lhs: Val, rhs: Val) bool { 256 | 257 | // We'll consider a bool comparison "less than" when lhs = false and rhs = true 258 | if (lhs.isBool() and rhs.isBool()) return !lhs.intoBool(dt) and rhs.intoBool(dt); 259 | if (lhs.isBool()) return true; 260 | 261 | if (lhs.isInt() and rhs.isInt()) { 262 | const a = lhs.intoInt() catch unreachable; 263 | const b = rhs.intoInt() catch unreachable; 264 | return a < b; 265 | } 266 | if ((lhs.isInt() or lhs.isFloat()) and (rhs.isInt() or rhs.isFloat())) { 267 | const a = lhs.intoFloat() catch unreachable; 268 | const b = rhs.intoFloat() catch unreachable; 269 | return a < b; 270 | } 271 | if (lhs.isInt()) return true; 272 | if (lhs.isFloat()) return true; 273 | 274 | if (lhs.isString() and rhs.isString()) { 275 | const a = lhs.intoString(dt) catch unreachable; 276 | const b = rhs.intoString(dt) catch unreachable; 277 | return std.mem.lessThan(u8, a, b); 278 | } 279 | if (lhs.isString()) return true; 280 | 281 | if (lhs.isCommand() and rhs.isCommand()) { 282 | const a = lhs.intoString(dt) catch unreachable; 283 | const b = rhs.intoString(dt) catch unreachable; 284 | return std.mem.lessThan(u8, a, b); 285 | } 286 | if (lhs.isCommand()) return true; 287 | 288 | if (lhs.isDeferredCommand() and rhs.isDeferredCommand()) { 289 | const a = lhs.intoString(dt) catch unreachable; 290 | const b = rhs.intoString(dt) catch unreachable; 291 | return std.mem.lessThan(u8, a, b); 292 | } 293 | if (lhs.isDeferredCommand()) return true; 294 | 295 | if (lhs.isQuote() and rhs.isQuote()) { 296 | const as = lhs.intoQuote(dt) catch unreachable; 297 | const bs = rhs.intoQuote(dt) catch unreachable; 298 | 299 | for (as.items, bs.items) |a, b| { 300 | if (Val.isLessThan(dt, a, b)) return true; 301 | } 302 | 303 | if (as.items.len < bs.items.len) return true; 304 | } 305 | 306 | return false; 307 | } 308 | 309 | pub fn print(self: Val, writer: std.fs.File.Writer) !void { 310 | switch (self) { 311 | .bool => |b| try writer.print("{}", .{b}), 312 | .int => |i| try writer.print("{}", .{i}), 313 | .float => |f| try writer.print("{d}", .{f}), 314 | .command => |cmd| try writer.print("{s}", .{cmd}), 315 | .deferred_command => |cmd| try writer.print("\\{s}", .{cmd}), 316 | .quote => |q| { 317 | try writer.print("[ ", .{}); 318 | for (q.items) |val| { 319 | try val.print(writer); 320 | try writer.print(" ", .{}); 321 | } 322 | try writer.print("]", .{}); 323 | }, 324 | .string => |s| try writer.print("\"{s}\"", .{s}), 325 | } 326 | } 327 | }; 328 | 329 | pub const Error = error{ 330 | TooManyRightBrackets, 331 | 332 | ContextStackUnderflow, 333 | StackUnderflow, 334 | WrongArguments, 335 | CommandUndefined, 336 | 337 | DivisionByZero, 338 | IntegerOverflow, 339 | IntegerUnderflow, 340 | 341 | NoCoercionToInteger, 342 | NoCoercionToFloat, 343 | NoCoercionToString, 344 | NoCoercionToCommand, 345 | 346 | ProcessNameUnknown, 347 | }; 348 | --------------------------------------------------------------------------------