Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile Trilangle to C #26

Merged
merged 18 commits into from
Sep 28, 2023
Merged
2 changes: 1 addition & 1 deletion .github/workflows/lint-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Lint Builds
paths:
# It would be nice if I could specify this per-job, but I'm not making a whole new file just to avoid running
# clang for web-only changes.
- src/*
- src/**
- wasm/*
- .clang-format
- .github/workflows/lint-build.yml
Expand Down
14 changes: 9 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Run Tests
branches:
- '**'
paths:
- src/*
- src/**
- tests/**
- .github/workflows/test.yml
- .gitmodules
Expand Down Expand Up @@ -37,12 +37,17 @@ jobs:
shell: bash
run: |
g++ -Og -std=gnu++17 src/*.cpp -o trilangle
export PATH="${PATH}:${PWD}"
for script in tests/cli/*/index.sh
do
echo "Running ${script}..."
"$script"
TRILANGLE=./trilangle "$script"
done
for script in tests/compiler/*/index.sh
do
echo "Running ${script}..."
TRILANGLE=./trgc.sh "$script"
done
echo 'Done.'
cli-tests-windows:
runs-on: windows-latest
name: Run CLI Tests for Windows
Expand All @@ -57,9 +62,8 @@ jobs:
# Screw it, I'm not figuring out how to make this work in PowerShell.
shell: bash
run: |
export PATH="${PATH}:${PWD}"
for script in tests/cli/*/index.sh
do
echo "Running ${script}..."
"$script"
TRILANGLE=./trilangle "$script"
done
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ wasm/*.css
*.obj
trilangle
*.nativecodeanalysis.xml
out.c
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. See [Keep a

- Added prebuilt binaries for ARM64 macOS, x86 Windows, and ARM64 Windows.
- Added a 'play' feature to the online debugger, to automatically step through the code.
- Added a compile mode to emit C code.
- Added `-a` flag to assume ASCII I/O.

## [1.5.0] - 2023-08-31

Expand Down
160 changes: 160 additions & 0 deletions src/compiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// This file is not in the compiler folder because the MSVC build script requires this folder to contain all the .cpp
// files.
#include "compiler.hh"
#include <iostream>
#include "compiler/strings.hh"

using std::cerr;
using std::endl;

void compiler::write_state(std::ostream& os) {
if (m_fragments == nullptr) {
build_state();
}

os << header;

for (size_t i = 0; i < m_fragments->size(); ++i) {
const std::vector<instruction>& frag = *m_fragments->at(i);
for (size_t j = 0; j < frag.size(); ++j) {
os << "\nlbl" << i << '_' << j << ": ";
get_c_code(frag[j], os, m_flags.assume_ascii);
}
}

os << footer << std::flush;
}

void compiler::get_c_code(const instruction& i, std::ostream& os, bool assume_ascii) {
using op = instruction::operation;

switch (i.m_op) {
case op::BNG: {
os << "if (lws_top(stack) < 0) goto lbl";
const auto& choice = i.m_arg.choice;
const auto &dest1 = choice.first, &dest2 = choice.second;
os << dest2.first << '_' << dest2.second << "; goto lbl" << dest1.first << '_' << dest1.second << ';';
return;
}
case op::JMP: {
os << "goto lbl";
const auto& dest = i.m_arg.next;
os << dest.first << '_' << dest.second << ';';
return;
}
case op::TKL:
case op::EXT:
os << "lws_deinit(stack); return 0;";
return;
case op::TJN:
case op::TSP:
cerr << "Threading is not supported for compiled programs." << endl;
exit(EXIT_FAILURE);
case op::NOP:
break;
case op::ADD:
os << "lws_push(stack, lws_pop(stack) + lws_pop(stack));";
return;
case op::SUB:
// The calls need to be well-ordered
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) - tmp); }";
return;
case op::MUL:
os << "lws_push(stack, lws_pop(stack) * lws_pop(stack));";
return;
case op::DIV:
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) / tmp); }";
return;
case op::MOD:
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) % tmp); }";
return;
case op::PSC: {
os << "lws_push(stack,";
const auto value = i.m_arg.number;
os << static_cast<int32_t>(value) << ");";
return;
}
case op::PSI: {
os << "lws_push(stack,";
const auto value = i.m_arg.number;
os << static_cast<int32_t>(value - (int24_t)'0') << ");";
return;
}
case op::POP:
os << "(void)lws_pop(stack);";
return;
case op::INC:
os << "lws_push(stack, (lws_pop(stack) + 1) << 8 >> 8);";
return;
case op::DEC:
os << "lws_push(stack, (lws_pop(stack) - 1) << 8 >> 8);";
return;
case op::AND:
os << "lws_push(stack, lws_pop(stack) & lws_pop(stack));";
return;
case op::IOR:
os << "lws_push(stack, lws_pop(stack) | lws_pop(stack));";
return;
case op::XOR:
os << "lws_push(stack, lws_pop(stack) ^ lws_pop(stack));";
return;
case op::NOT:
os << "lws_push(stack, ~lws_pop(stack));";
return;
case op::GTC:
if (assume_ascii) {
os << "{ int temp = getchar(); lws_push(stack, temp == EOF ? -1 : temp); }";
} else {
os << "lws_push(stack, get_unichar());";
}
return;
case op::PTC:
if (assume_ascii) {
os << "putchar";
} else {
os << "print_unichar";
}
os << "(lws_top(stack));";
return;
case op::GTI:
os << "{"
"int32_t i = -1;"
"while (!(feof(stdin) || scanf(\"%\" SCNi32, &i))) (void)getchar();"
"lws_push(stack, i);"
"}";
return;
case op::PTI:
os << R"( printf("%" PRId32 "\n", lws_top(stack)); )";
return;
case op::IDX:
os << "lws_push(stack, lws_index(stack, lws_pop(stack)));";
return;
case op::DUP:
os << "lws_push(stack, lws_top(stack));";
return;
case op::DP2: {
const char* str = "lws_push(stack, lws_index(stack, 1));";
os << str << str;
return;
}
case op::RND:
case op::GDT:
case op::GTM:
cerr << "Nondeterministic instructions are not yet supported for compiled programs." << endl;
Comment on lines +140 to +143
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be doable with time() and rand(), but seeding the RNG is a problem

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time() doesn't provide sub-second precision however, so it might work for GDT but not GTM.

exit(EXIT_FAILURE);
case op::EXP:
os << "lws_push(stack, 1 << lws_pop(stack));";
return;
case op::SWP:
os << "{"
"int32_t temp1 = lws_pop(stack);"
"int32_t temp2 = lws_pop(stack);"
"lws_push(stack, temp1);"
"lws_push(stack, temp2);"
"}";
return;
default:
cerr << "Unknown opcode." << endl;
exit(EXIT_FAILURE);
}
}
12 changes: 12 additions & 0 deletions src/compiler.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include "instruction_scanner.hh"

class compiler : public instruction_scanner {
public:
CONSTEXPR_VECTOR compiler(NONNULL_PTR(const program) p, flags f) : instruction_scanner(p), m_flags(f) {}
void write_state(std::ostream& os);
private:
flags m_flags;
static void get_c_code(const instruction& i, std::ostream& os, bool assume_ascii);
};
110 changes: 110 additions & 0 deletions src/compiler/lightweight_stack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// This entire file is a string. Comment out the opening quote marker to get syntax highlighting while editing.
R"(
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#ifndef __GNUC__
#define __builtin_expect(exp, c) exp
#endif

struct lightweight_stack_s {
int32_t* base;
size_t length;
size_t capacity;
};

typedef struct lightweight_stack_s* lws;

static inline int32_t lws_pop(lws stack) {
return stack->base[--stack->length];
}

static inline int lws_push(lws stack, int32_t value) {
if (__builtin_expect(stack->length == stack->capacity, 0L)) {
bbrk24 marked this conversation as resolved.
Show resolved Hide resolved
size_t new_cap = stack->capacity * 7 / 4;
int32_t* new_ptr = (int32_t*)realloc(stack->base, new_cap * sizeof(int32_t));
if (new_ptr == NULL) {
return 0;
}
stack->base = new_ptr;
stack->capacity = new_cap;
}
stack->base[stack->length++] = value;
return 1;
}

static inline int32_t lws_index(const lws stack, int32_t index) {
return stack->base[stack->length - index - 1];
}

static inline int32_t lws_top(const lws stack) {
return lws_index(stack, 0);
}

static inline void lws_init(lws stack) {
stack->length = 0;
stack->base = (int32_t*)calloc(4, sizeof(int32_t));
// Note: actually handling errors prevents the compiler from doing optimizations we want
stack->capacity = 4;
}

static inline void lws_deinit(const lws stack) {
free(stack->base);
}

#define INVALID_CHAR 0xfffd
// Basically copied from parse_unichar in string_processing.hh
static inline int32_t get_unichar() {
unsigned char buf[4];
size_t buf_max = 4;
for (size_t i = 0; i < buf_max; ++i) {
int c = getchar();
if (c == EOF) {
if (i == 0) {
return -1;
} else {
return INVALID_CHAR;
}
}
buf[i] = c;
if (i != 0 && (buf[i] & 0xc0) != 0x80) {
return INVALID_CHAR;
}
if (i == 0) {
if (buf[0] < 0x80) {
return buf[0];
} else if ((buf[0] & 0xe0) == 0xc0) {
buf_max = 2;
} else if ((buf[0] & 0xf0) == 0xe0) {
buf_max = 3;
} else if ((buf[0] & 0xf8) != 0xf0) {
return INVALID_CHAR;
}
}
}
switch (buf_max) {
case 2:
return ((buf[0] & 0x1f) << 6) | (buf[1] & 0x3f);
case 3:
return ((buf[0] & 0x0f) << 12) | ((buf[1] & 0x3f) << 6) | (buf[2] & 0x3f);
case 4:
return ((buf[0] & 0x07) << 18) | ((buf[1] & 0x3f) << 12) | ((buf[2] & 0x3f) << 6) | (buf[3] & 0x3f);
}
}

static inline void print_unichar(int32_t c) {
if (c <= 0x7f) {
putchar(c);
} else if (c <= 0x07ff) {
char buffer[] = { 0xc0 | (c >> 6), 0x80 | (c & 0x3f), 0 };
printf(buffer);
} else if (c <= 0xffff) {
char buffer[] = { 0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f), 0 };
printf(buffer);
} else {
char buffer[] = { 0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f), 0 };
printf(buffer);
}
}
//)"
12 changes: 12 additions & 0 deletions src/compiler/strings.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

constexpr const char* header =
"#include <inttypes.h>"
#include "lightweight_stack.h"
R"#(
int main(int argc, const char** argv) {
struct lightweight_stack_s stack_storage;
lws stack = &stack_storage;
lws_init(stack);)#";

constexpr const char* footer = "}\n";
Loading