Skip to content

Commit

Permalink
various improvements
Browse files Browse the repository at this point in the history
more work

Get more things working

more documentation, mostly
  • Loading branch information
bbrk24 committed Sep 29, 2024
1 parent d22645b commit 5c1bf57
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 33 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. See [Keep a

## Unreleased

### Added

- The interpreter can now handle assembly syntax, with the `-A` flag in the command line and a checkbox in the online interpreter. Currently, interactive debugging of assembly is not supported in the web interpreter.

### Changed

- Fixed C++23 support
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Trilangle is a 2-D, stack-based programming language inspired by [Hexagony].
> - [Interpreter flags](#interpreter-flags)
> - [Exit codes](#exit-codes)
> - [The disassembler](#the-disassembler)
> - [Assembly syntax](#assembly-syntax)
> - [C compiler](#c-compiler)
> - [Sample programs](#sample-programs)
> - [cat](#cat)
Expand Down Expand Up @@ -225,6 +226,36 @@ For example, when passing [the cat program below](#cat) with the flags `-Dn`, th
2.2: EXT
```

### Assembly syntax

In addition to producing this syntax, Trilangle is capable of interpreting this syntax. Currently, the output with `--hide-nops` is not guaranteed to be interpretable, as it may be missing jump targets. Each line can maximally consist of a label, an instruction, and a comment. The syntax can be described with the following extended Backus-Naur form:

```
program = line, {newline, line};
line = [label, [":"]], [multiple_whitespace, instruction], {whitespace}, [comment];
newline = ? U+000A END OF LINE ?;
tab = ? U+0009 CHARACTER TABULATION ?;
whitespace = " " | ? U+000D CARRIAGE RETURN ? | tab;
non_whitespace = ? Any single unicode character not in 'newline' or 'whitespace' ?;
multiple_whitespace = whitespace, {whitespace};
label = non_whitespace, {non_whitespace};
comment = ";", {non_whitespace | whitespace};
instruction = instruction_with_target | instruction_with_argument | plain_instruction;
instruction_with_target = ("BNG" | "TSP" | "JMP"), multiple_whitespace, label;
instruction_with_argument = ("PSI" | "PSC"), multiple_whitespace, number_literal;
plain_instruction = ? Any three-character instruction besides the five already covered ?;
number_literal = character_literal | decimal_literal | hex_literal;
character_literal = "'", (non_whitespace | tab), "'";
decimal_literal = "#", decimal_digit;
hex_literal = "0x", hex_digit, {hex_digit};
decimal_digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
hex_digit = "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" | decimal_digit;
```

## C compiler

When using the `-c` flag, the input program will be translated into C code. The C code is not meant to be idiomatic or easy to read, as it is a literal translation of the input. Optimizers such as those used by clang and GCC tend to do a good job of improving the program, in some cases removing the heap allocation altogether; MSVC does not.
Expand Down
11 changes: 9 additions & 2 deletions src/assembly_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ NONNULL_PTR(const std::vector<NONNULL_PTR(std::vector<instruction>)>) assembly_s
invalid_literal(argument);
}
i = 1;
arg_value = parse_unichar([&]() { return curr_line[i++]; });
arg_value = parse_unichar([&]() { return argument[i++]; });
if (arg_value < INT24_C(0) || i != argument.size() - 1) {
invalid_literal(argument);
}
Expand All @@ -165,14 +165,21 @@ NONNULL_PTR(const std::vector<NONNULL_PTR(std::vector<instruction>)>) assembly_s
}
arg_value = static_cast<int24_t>(ul);
} else if (argument[0] == '#' && argument.size() == 2) {
arg_value = argument[1];
arg_value = static_cast<int24_t>(argument[1] - '0');
if (arg_value < INT24_C(0) || arg_value > INT24_C(9)) {
invalid_literal(argument);
}
} else {
invalid_literal(argument);
}

if (opcode == instruction::operation::PSI) {
// PSI expects to be given the digit, not the actual value
auto p = arg_value.add_with_overflow('0');
assert(!p.first);
arg_value = p.second;
}

instruction::argument arg;
arg.number = arg_value;
add_instruction({ opcode, arg });
Expand Down
4 changes: 2 additions & 2 deletions src/assembly_scanner.hh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class assembly_scanner : public any_program_holder<std::pair<size_t, size_t> > {
public:
using IP = std::pair<size_t, size_t>;

inline assembly_scanner(std::istream* program) : m_program(program), m_fragments(nullptr) {}
inline assembly_scanner(NONNULL_PTR(std::istream) program) : m_program(program), m_fragments(nullptr) {}

inline ~assembly_scanner() noexcept {
if (m_fragments == nullptr) {
Expand Down Expand Up @@ -36,6 +36,6 @@ private:
IP get_current_location() const;
void add_instruction(instruction&& i);

std::istream* m_program;
NONNULL_PTR(std::istream) m_program;
std::vector<NONNULL_PTR(std::vector<instruction>)>* m_fragments;
};
2 changes: 1 addition & 1 deletion src/input.hh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct flags {
constexpr bool is_valid() const noexcept {
return !(
(show_stack && !debug) || ((debug || warnings || pipekill || assembly) && disassemble)
|| (hide_nops && !disassemble) || (expand && (debug || warnings || pipekill || disassemble))
|| (hide_nops && !disassemble) || (expand && (debug || warnings || pipekill || disassemble || assembly))
|| (compile && (debug || warnings || pipekill || disassemble || expand))
|| (assume_ascii && (expand || disassemble))
);
Expand Down
19 changes: 14 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
#include "disassembler.hh"
#include "interpreter.hh"

[[noreturn]] inline void empty_program() {
std::cerr << "What program do you want me to run? C'mon, give me something to work with." << std::endl;
exit(EX_DATAERR);
}

inline void execute(const std::string& prg, flags f) {
#ifdef NO_BUFFER
setvbuf(stdout, nullptr, _IONBF, 0);
Expand All @@ -16,16 +21,17 @@ inline void execute(const std::string& prg, flags f) {
if (f.assembly) {
std::istringstream iss(prg);
assembly_scanner as(&iss);
if (as.get_fragments()->size() == 0) {
empty_program();
}
interpreter<assembly_scanner> i(as, f);
i.run();
return;
}

program p(prg);

if (p.side_length() == 0) {
std::cerr << "What program do you want me to run? C'mon, give me something to work with." << std::endl;
exit(EX_DATAERR);
empty_program();
}

if (f.disassemble) {
Expand All @@ -46,7 +52,8 @@ inline void execute(const std::string& prg, flags f) {
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>

extern "C" EMSCRIPTEN_KEEPALIVE void wasm_entrypoint(CONST_C_STR program_text, int disassemble, int expand, int debug) {
extern "C" EMSCRIPTEN_KEEPALIVE void
wasm_entrypoint(CONST_C_STR program_text, int disassemble, int expand, int debug, int assembly) {
// Reset EOF from previous runs
clearerr(stdin);
// Input and output don't need to be synced on the web
Expand All @@ -55,10 +62,12 @@ extern "C" EMSCRIPTEN_KEEPALIVE void wasm_entrypoint(CONST_C_STR program_text, i
flags f;
f.warnings = true;
f.disassemble = disassemble;
f.hide_nops = disassemble;
// Disabled so that the output is compatible with -A
// f.hide_nops = disassemble;
f.expand = expand;
f.debug = debug;
f.show_stack = true;
f.assembly = assembly;

execute(program_text, f);
}
Expand Down
2 changes: 2 additions & 0 deletions src/program_walker.hh
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public:
case PSI:
program_walker::advance(ip, m_program->side_length());
break;
default:
break;
}
program_walker::advance(ip, m_program->side_length());
}
Expand Down
26 changes: 20 additions & 6 deletions tests/cli/invalid_flags/index.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
#!/bin/bash

set -u
set -eu

errors=$($TRILANGLE - 2>&1 1<&-)
result=$?
set -e
test 0 -ne $result
invalid_flags=(
# Not actually a flag
-
# various mode flags together
-Ae -ce -De -AD -Dc
# --assume-ascii without actually executing
-ae -aD
# --show-stack without --debug
-s
# --hide-nops without --disassemble
-n
)

test -n "$errors"
for flag in "${invalid_flags[@]}"
do
result=0
errors=$($TRILANGLE "$flag" 2>&1 1<&-) || result=$?
test 64 = $result
test -n "$errors"
done
11 changes: 8 additions & 3 deletions tests/cli/qdeql/index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ run_qdeql_asm () {

# hello world
test 'hello world' = "$(run_qdeql "${folder}/hello.qd")"
# test 'hello world' = "$(run_qdeql_asm "${folder}/hello.qd")"
test 'hello world' = "$(run_qdeql_asm "${folder}/hello.qd")"

# cat
# Qdeql is limited to bytes, so this cat can't be the same Unicode cat
Expand All @@ -38,15 +38,20 @@ text='
output=$(run_qdeql "${folder}/cat.qd" "$text")
test "$text" = "${output//$'\r'/}"

# output=$(run_qdeql_asm "${folder}/cat.qd" "$text")
# test "$text" = "${output//$'\r'/}"
output=$(run_qdeql_asm "${folder}/cat.qd" "$text")
test "$text" = "${output//$'\r'/}"

# truth machine
test 0 = "$(run_qdeql "${folder}/tm.qd" 0)"
test 1111111111 = "$(run_qdeql "${folder}/tm.qd" 1 | head -c 10)"
test 0 = "$(run_qdeql_asm "${folder}/tm.qd" 0)"
test 1111111111 = "$(run_qdeql_asm "${folder}/tm.qd" 1 | head -c 10)"

# adder
test $'\x05' = "$(run_qdeql "${folder}/sum.qd" $'\x02\x03')"
test $'\x05' = "$(run_qdeql_asm "${folder}/sum.qd" $'\x02\x03')"
# bash complains if I pass a null byte in one of the arguments to run_qdeql
test $'\x03' = "$(printf '%s\0\0\x03' "$(cat "${folder}/sum.qd")" | $TRILANGLE -a "${root}/qdeql/interpreter.trg")"
test $'\x02' = "$(printf '%s\0\x02\0' "$(cat "${folder}/sum.qd")" | $TRILANGLE -a "${root}/qdeql/interpreter.trg")"
test $'\x03' = "$(printf '%s\0\0\x03' "$(cat "${folder}/sum.qd")" | $TRILANGLE -Aa "${root}/qdeql/disassembly.txt")"
test $'\x02' = "$(printf '%s\0\x02\0' "$(cat "${folder}/sum.qd")" | $TRILANGLE -Aa "${root}/qdeql/disassembly.txt")"
40 changes: 34 additions & 6 deletions wasm/in.civet
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interval .= -1
let step: =>
decoder := new TextDecoder
colors := new Colors
isAssembly .= false

// This is a clever little hack: elements.fooBar is the element with id="foo-bar". It uses document.getElementById on
// first access, but then saves it for fast access later.
Expand All @@ -49,7 +50,9 @@ clearOutput := :void =>
elements.stderr.innerHTML = ''

generateContracted := :string =>
programText .= (elements.program as! HTMLTextAreaElement).value.replace /\n| /gu, ''
programText .= (elements.program as! HTMLTextAreaElement).value
return programText if isAssembly
programText |>= .replace /\n| /gu, ''
// programText.length is wrong when there's high Unicode characters
programLength := [programText...]#
// Calculate the largest triangular number less than the length.
Expand All @@ -76,6 +79,8 @@ generateURL := :void =>
url.hash = '#' + encodeURIComponent generateContracted()
if elements.includeInput.checked
url.searchParams.set 'i', (elements.stdin as! HTMLTextAreaElement).value
if elements.assembly.checked
url.searchParams.set 'a', '1'
history.pushState {}, '', url
elements.urlOut.textContent = url.href
elements.urlOutBox.className = ''
Expand Down Expand Up @@ -143,10 +148,10 @@ createWorker := (name: string) => :Promise<void> =>
elements.stdin.oninput = :void ->
worker = undefined
@oninput = null
elements.expand.disabled = false
elements.disassemble.disabled = false
elements.condense.disabled = false
elements.debug.disabled = false
elements.expand.disabled = isAssembly
elements.disassemble.disabled = isAssembly
elements.condense.disabled = isAssembly
elements.debug.disabled = isAssembly
elements.runStop.textContent = 'Run!'
elements.runStop.onclick = interpretProgram

Expand Down Expand Up @@ -184,10 +189,13 @@ createWorker := (name: string) => :Promise<void> =>

promise

interpretProgram := createWorker 'interpretProgram'
interpretBase := createWorker 'interpretProgram'
disassembleProgram := createWorker 'disassembleProgram'
expandBase := createWorker 'expandInput'
debugBase := createWorker 'debugProgram'
interpretAssembly := createWorker 'interpretAssembly'

interpretProgram := => if isAssembly then interpretAssembly() else interpretBase()

expandInput := :Promise<void> =>
await expandBase()
Expand Down Expand Up @@ -389,6 +397,7 @@ do
elements.urlButton.onclick = generateURL
elements.program.addEventListener 'input', hideUrl, { +passive }
elements.includeInput.addEventListener 'change', hideUrl, { +passive }
elements.assembly.addEventListener 'change', hideUrl, { +passive }
elements.stdin.addEventListener 'change', => hideUrl() if elements.includeInput.checked, { +passive }

// Allow the header itself, or noninteractable children (e.g. the select icon)
Expand Down Expand Up @@ -436,6 +445,16 @@ do
@ontouchcancel = null
@ontouchend = null

(elements.assembly as! HTMLInputElement).addEventListener
'change'
:void ->
isAssembly = @checked
elements.expand.disabled = isAssembly
elements.disassemble.disabled = isAssembly
elements.condense.disabled = isAssembly
elements.debug.disabled = isAssembly
{ +passive }

width := elements.runStop.offsetWidth
remSize := parseFloat getComputedStyle(document.body).fontSize
elements.runStop.textContent = 'Run!'
Expand All @@ -452,6 +471,15 @@ do
if inputParam? := params.get 'i'
elements.stdin.value = inputParam
elements.includeInput.checked = true
assemblyParam := params.get 'a'
if Number assemblyParam
isAssembly = true
catch e
console.error e
elements.stderr.innerText = 'Error while loading program from URL.'
finally
elements.assembly.checked = isAssembly
elements.expand.disabled = isAssembly
elements.disassemble.disabled = isAssembly
elements.condense.disabled = isAssembly
elements.debug.disabled = isAssembly
4 changes: 4 additions & 0 deletions wasm/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
<button type="button" id="debug" onclick="debugProgram()">Debug</button>
<button type="button" id="url-button">Generate URL</button>
<span id="copy-alert" class="hidden">Copied!</span>
<div>
<input type="checkbox" id="assembly" />
<label for="assembly">Use assembly syntax</label>
</div>
<div id="url-out-box" class="content-hidden"><span>URL:</span>&nbsp;<code id="url-out"></code></div>
<textarea id="program" rows="10" spellcheck="false" class="textarea"> "
H o
Expand Down
2 changes: 1 addition & 1 deletion wasm/lowdata.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang=en-US><head><meta charset=UTF-8><title>Trilangle online interpreter</title><meta name=viewport content="width=device-width,initial-scale=1.0"><meta name=referrer content=same-origin><meta name=description content="A version of the online interpreter made for slow connections."><link rel=icon href="./favicon.ico"><link rel=stylesheet href="./lowdata.css"><link rel=preload href="./ldworker.wasm" as=fetch type="application/wasm" crossorigin><script defer src="./Colors.js"></script><script defer src="./index.js"></script></head><body><main><div class=fit-width><fieldset id=program-container><legend>Program</legend><button type=button id=run-stop>Stop</button><button type=button id=disassemble onclick=disassembleProgram()>Show disassembly</button><button type=button id=condense onclick=contractInput()>Condense</button><button type=button id=expand onclick=expandInput()>Unfold</button><button type=button id=debug onclick=debugProgram()>Debug</button><button type=button id=url-button>Generate URL</button><span id=copy-alert class=hidden>Copied!</span><div id=url-out-box class=content-hidden><span>URL:</span>&nbsp;<code id=url-out></code></div><textarea id=program rows=10 spellcheck=false class=textarea> "
<html lang=en-US><head><meta charset=UTF-8><title>Trilangle online interpreter</title><meta name=viewport content="width=device-width,initial-scale=1.0"><meta name=referrer content=same-origin><meta name=description content="A version of the online interpreter made for slow connections."><link rel=icon href="./favicon.ico"><link rel=stylesheet href="./lowdata.css"><link rel=preload href="./ldworker.wasm" as=fetch type="application/wasm" crossorigin><script defer src="./Colors.js"></script><script defer src="./index.js"></script></head><body><main><div class=fit-width><fieldset id=program-container><legend>Program</legend><button type=button id=run-stop>Stop</button><button type=button id=disassemble onclick=disassembleProgram()>Show disassembly</button><button type=button id=condense onclick=contractInput()>Condense</button><button type=button id=expand onclick=expandInput()>Unfold</button><button type=button id=debug onclick=debugProgram()>Debug</button><button type=button id=url-button>Generate URL</button><span id=copy-alert class=hidden>Copied!</span><div><input type=checkbox id=assembly><label for=assembly>Use assembly syntax</label></div><div id=url-out-box class=content-hidden><span>URL:</span>&nbsp;<code id=url-out></code></div><textarea id=program rows=10 spellcheck=false class=textarea> "
H o
o " !
" o ( o
Expand Down
15 changes: 8 additions & 7 deletions wasm/worker.civet
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,25 @@ Module.'preInit' = =>
Module.'onRuntimeInitialized' = halfReady
Module.'noExitRuntime' = true

callInterpreter := (warnings: 0 | 1, disassemble: 0 | 1, expand: 0 | 1) => =>
callInterpreter := (warnings: 0 | 1, disassemble: 0 | 1, expand: 0 | 1, assembly: 0 | 1) => =>
inputIndex = 0
try
await Module['ccall']
'wasm_entrypoint'
null
['string', 'number', 'number', 'number']
[programText, warnings, disassemble, expand]
['string', 'number', 'number', 'number', 'number']
[programText, warnings, disassemble, expand, assembly]
async: true
catch e
if e? !<? ExitStatus
postMessage [2, e.toString()]
postMessage [0, null]

signals.set 'interpretProgram', callInterpreter 0, 0, 0
signals.set 'disassembleProgram', callInterpreter 1, 0, 0
signals.set 'expandInput', callInterpreter 0, 1, 0
signals.set 'debugProgram', callInterpreter 0, 0, 1
signals.set 'interpretProgram', callInterpreter 0, 0, 0, 0
signals.set 'disassembleProgram', callInterpreter 1, 0, 0, 0
signals.set 'expandInput', callInterpreter 0, 1, 0, 0
signals.set 'debugProgram', callInterpreter 0, 0, 1, 0
signals.set 'interpretAssembly', callInterpreter 0, 0, 0, 1
// @ts-expect-error __trilangle_resolve is set in library.civet
signals.set 'step', => @.'__trilangle_resolve'()

Expand Down

0 comments on commit 5c1bf57

Please sign in to comment.