├── .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 | [](https://github.com/so-dang-cool/dt/blob/core/LICENSE.md)
2 | [](https://unconventions.org)
3 | [](https://justforfunnoreally.dev)
4 |
5 | # dt
6 |
7 |
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 | [](https://repology.org/project/dt-script/versions)
131 |
132 | [](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: [](https://discord.gg/9ByutGCrJX)
143 | * Concatenative language theorists: [](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 |
--------------------------------------------------------------------------------