all repos — hex @ 938cd5fe0b073c0dbb928f14113fa9fc99eafcb2

A tiny, minimalist, slightly-esoteric concatenative programming lannguage.

Implemented support for reading and writing binary files.
h3rald h3rald@h3rald.com
Thu, 19 Dec 2024 14:36:24 +0100
commit

938cd5fe0b073c0dbb928f14113fa9fc99eafcb2

parent

0358bd8e600c83b5ad1062aaceb4a540467eb10a

M .gitignore.gitignore

@@ -54,6 +54,7 @@ # Exe names

hex # Other +.vscode/ *.hbx src/hex.c example.hex
D .vscode/settings.json

@@ -1,9 +0,0 @@

-{ - "files.associations": { - "*.erd": "json", - "*.vuerd": "json", - "*.mjs": "javascript", - "stdio.h": "c", - "hex.h": "c" - } -}
M CHANGELOG.mdCHANGELOG.md

@@ -13,5 +13,24 @@ <li>Support for 32bit hexadecimal integers, strings, and quotations (lists).</li>

<li>A complete <a href="https://hex.2c.fyi">web site</a> with more documentation and even an interactive playground. </li> </ul> +<h3>v0.2.0 &mdash; <em>Under development</em></h3> + +<h4>New Features</h4> +<ul> + <li>Implemented a virtual machine with a bytecode compiler and interpreter.</li> + <li>{{sym-read}}, {{sym-write}}, {{sym-append}} now support reading and writing from/to binary files as well.</li> +</ul> + +<h4>Fixes</h4> +<ul> + <li>Ensured that {{sym-dec}} is able to print negative integers in decimal format.</li> +</ul> + +<h4>Chores</h4> +<ul> + <li>Split the source code to different files, and now relying on an <a + href="https://github.com/h3rald/hex/blob/master/scripts/amalgamate.sh">amalgamate.sh</a> script to + concatenate them together before compiling</li> +</ul> </article>
A releases/0.2.0.html

@@ -0,0 +1,19 @@

+<h3>v0.2.0 &mdash; <em>Under development</em></h3> + +<h4>New Features</h4> +<ul> + <li>Implemented a virtual machine with a bytecode compiler and interpreter.</li> + <li>{{sym-read}}, {{sym-write}}, {{sym-append}} now support reading and writing from/to binary files as well.</li> +</ul> + +<h4>Fixes</h4> +<ul> + <li>Ensured that {{sym-dec}} is able to print negative integers in decimal format.</li> +</ul> + +<h4>Chores</h4> +<ul> + <li>Split the source code to different files, and now relying on an <a + href="https://github.com/h3rald/hex/blob/master/scripts/amalgamate.sh">amalgamate.sh</a> script to + concatenate them together before compiling</li> +</ul>
M scripts/web.hexscripts/web.hex

@@ -17,7 +17,7 @@ d-templates "/page.html" cat "t-page" :

; Symbols to substitute with the corresponding links ("split" "run" "get" "puts" ":" "." "#" "==" "'" "swap" -"+" "*" "-" "each" "cat" "print" "read" "dec") "symbol-links" : +"+" "*" "-" "each" "cat" "print" "read" "dec" "write" "append") "symbol-links" : ;; Syntax highlighting
M src/doc.csrc/doc.c

@@ -113,9 +113,9 @@ hex_doc(docs, "print", "a", "", "Prints 'a' to standard output.");

hex_doc(docs, "gets", "", "s", "Gets a string from standard input."); // File - hex_doc(docs, "read", "s1", "s2", "Returns the contents of the specified file."); - hex_doc(docs, "write", "s1 s2", "", "Writes 's2' to the file 's1'."); - hex_doc(docs, "append", "s1 s2", "", "Appends 's2' to the file 's1'."); + hex_doc(docs, "read", "s1", "(s2|q)", "Returns the contents of the specified file."); + hex_doc(docs, "write", "(s1|q) s2", "", "Writes 's1' or 'q' to the file 's2'."); + hex_doc(docs, "append", "(s1|q) s2", "", "Appends 's1' or 'q' to the file 's2'."); // Shell hex_doc(docs, "args", "", "q", "Returns the program arguments.");
M src/hex.hsrc/hex.h

@@ -294,6 +294,8 @@ char *hex_type(hex_item_type_t type);

void hex_rpad(const char *str, int total_length); void hex_lpad(const char *str, int total_length); void hex_encode_length(uint8_t **bytecode, size_t *size, size_t length); +int hex_is_binary(const uint8_t *data, size_t size); +char *hex_bytes_to_string(const uint8_t *bytes, size_t size); // Native symbols int hex_symbol_store(hex_context_t *ctx);
M src/symbols.csrc/symbols.c

@@ -1467,7 +1467,6 @@ // File symbols

int hex_symbol_read(hex_context_t *ctx) { - HEX_POP(ctx, filename); if (filename.type == HEX_TYPE_INVALID) {

@@ -1477,7 +1476,7 @@ }

int result = 0; if (filename.type == HEX_TYPE_STRING) { - FILE *file = fopen(filename.data.str_value, "r"); + FILE *file = fopen(filename.data.str_value, "rb"); if (!file) { hex_error(ctx, "Could not open file for reading: %s", filename.data.str_value);

@@ -1489,7 +1488,7 @@ fseek(file, 0, SEEK_END);

long length = ftell(file); fseek(file, 0, SEEK_SET); - char *buffer = (char *)malloc(length + 1); + uint8_t *buffer = (uint8_t *)malloc(length); if (!buffer) { hex_error(ctx, "Memory allocation failed");

@@ -1498,8 +1497,39 @@ }

else { size_t bytesRead = fread(buffer, 1, length, file); - buffer[bytesRead] = '\0'; - result = hex_push_string(ctx, buffer); + if (hex_is_binary(buffer, bytesRead)) + { + hex_item_t **quotation = (hex_item_t **)malloc(bytesRead * sizeof(hex_item_t *)); + if (!quotation) + { + hex_error(ctx, "Memory allocation failed"); + result = 1; + } + else + { + for (size_t i = 0; i < bytesRead; i++) + { + quotation[i] = (hex_item_t *)malloc(sizeof(hex_item_t)); + quotation[i]->type = HEX_TYPE_INTEGER; + quotation[i]->data.int_value = buffer[i]; + } + result = hex_push_quotation(ctx, quotation, bytesRead); + } + } + else + { + char *str = hex_bytes_to_string(buffer, bytesRead); + if (!str) + { + hex_error(ctx, "Memory allocation failed"); + result = 1; + } + else + { + result = hex_push_string(ctx, str); + free(str); + } + } free(buffer); } fclose(file);

@@ -1551,9 +1581,34 @@ hex_error(ctx, "Could not open file for writing: %s", filename.data.str_value);

result = 1; } } + else if (data.type == HEX_TYPE_QUOTATION) + { + FILE *file = fopen(filename.data.str_value, "wb"); + if (file) + { + for (size_t i = 0; i < data.quotation_size; i++) + { + if (data.data.quotation_value[i]->type != HEX_TYPE_INTEGER) + { + hex_error(ctx, "Quotation must contain only integers"); + result = 1; + break; + } + uint8_t byte = (uint8_t)data.data.quotation_value[i]->data.int_value; + fwrite(&byte, 1, 1, file); + } + fclose(file); + result = 0; + } + else + { + hex_error(ctx, "Could not open file for writing: %s", filename.data.str_value); + result = 1; + } + } else { - hex_error(ctx, "Symbol 'write' requires a string"); + hex_error(ctx, "Symbol 'write' requires a string or a quotation of integers"); result = 1; } }

@@ -1604,9 +1659,34 @@ hex_error(ctx, "Could not open file for appending: %s", filename.data.str_value);

result = 1; } } + else if (data.type == HEX_TYPE_QUOTATION) + { + FILE *file = fopen(filename.data.str_value, "ab"); + if (file) + { + for (size_t i = 0; i < data.quotation_size; i++) + { + if (data.data.quotation_value[i]->type != HEX_TYPE_INTEGER) + { + hex_error(ctx, "Quotation must contain only integers"); + result = 1; + break; + } + uint8_t byte = (uint8_t)data.data.quotation_value[i]->data.int_value; + fwrite(&byte, 1, 1, file); + } + fclose(file); + result = 0; + } + else + { + hex_error(ctx, "Could not open file for appending: %s", filename.data.str_value); + result = 1; + } + } else { - hex_error(ctx, "Symbol 'append' requires a string"); + hex_error(ctx, "Symbol 'append' requires a string or a quotation of integers"); result = 1; } }
M src/utils.csrc/utils.c

@@ -230,3 +230,92 @@ }

(*bytecode)[*size] = length & 0x7F; (*size)++; } + +int hex_is_binary(const uint8_t *data, size_t size) +{ + const double binary_threshold = 0.1; // 10% of bytes being non-printable + size_t non_printable_count = 0; + for (size_t i = 0; i < size; i++) + { + uint8_t byte = data[i]; + // Check if the byte is a printable ASCII character or a common control character. + if (!((byte >= 32 && byte <= 126) || byte == 9 || byte == 10 || byte == 13)) + { + non_printable_count++; + } + // Early exit if the threshold is exceeded. + if ((double)non_printable_count / size > binary_threshold) + { + return 1; + } + } + return 0; +} + +char *hex_bytes_to_string(const uint8_t *bytes, size_t size) +{ + char *str = (char *)malloc(size * 4 + 1); // Allocate enough space for worst case + if (!str) + { + return NULL; // Allocation failed + } + + char *ptr = str; + for (size_t i = 0; i < size; i++) + { + uint8_t byte = bytes[i]; + switch (byte) + { + case '\n': + *ptr++ = '\\'; + *ptr++ = 'n'; + break; + case '\t': + *ptr++ = '\\'; + *ptr++ = 't'; + break; + case '\r': + if (i + 1 < size && bytes[i + 1] == '\n') + { + i++; // Skip the '\n' part of the '\r\n' sequence + } + *ptr++ = '\\'; + *ptr++ = 'n'; + break; + case '\b': + *ptr++ = '\\'; + *ptr++ = 'b'; + break; + case '\f': + *ptr++ = '\\'; + *ptr++ = 'f'; + break; + case '\v': + *ptr++ = '\\'; + *ptr++ = 'v'; + break; + case '\\': + *ptr++ = '\\'; + *ptr++ = '\\'; + break; + case '\"': + *ptr++ = '\\'; + *ptr++ = '\"'; + break; + default: + if (byte < 32 || byte > 126) + { + // Escape non-printable characters as hex (e.g., \x1F) + ptr += sprintf(ptr, "\\x%02x", byte); + } + else + { + *ptr++ = byte; + } + break; + } + } + *ptr = '\0'; // Null-terminate the string + + return str; +}
M web/assets/styles.cssweb/assets/styles.css

@@ -83,15 +83,19 @@

a { color: lime; text-decoration: underline; + text-decoration-thickness: from-font; } a:visited { color: limegreen; } -a[href^=http] { +a[href^="http"] { text-decoration-style: dotted; - text-decoration-thickness: from-font; +} + +a[href^="https://hex.2c.fyi"] { + text-decoration: underline; } strong {
M web/contents/spec.htmlweb/contents/spec.html

@@ -565,14 +565,18 @@ <p><mark> &rarr; s</mark></p>

<p>Reads a line from standard input and pushes it on the stack as a string.</p> <h4 id="file-symbols">File Symbols<a href="#top"></a></h4> <h5 id="read-symbol"><code>$:read$$</code> Symbol<a href="#top"></a></h5> - <p><mark>s1 &rarr; s2</mark></p> - <p>Reads the content of the file <code>s1</code> and pushes it on the stack as a string.</p> + <p><mark>s1 &rarr; (s2|q)</mark></p> + <p>Reads the content of the file <code>s1</code> and pushes it on the stack as a string, if the file is in textual + format, or as a quotation of integers representing bytes, if the file is in binary format.</p> <h5 id="write-symbol"><code>$:write$$</code> Symbol<a href="#top"></a></h5> - <p><mark>s1 s2 &rarr;</mark></p> - <p>Writes the string <code>s1</code> to the file <code>s2</code>. + <p><mark>(s1|q) s2 &rarr;</mark></p> + <p>Writes the string <code>s1</code> or the array of integers representing bytes <code>q</code> to the file + <code>s2</code>. <h5 id="append-symbol"><code>$:append$$</code> Symbol<a href="#top"></a></h5> - <p><mark> s1 s2 &rarr;</mark></p> - <p>Appends the string <code>s1</code> to the file <code>s2</code>.</p> + <p><mark>(s1|q) s2 &rarr;</mark></p> + <p>Appends the string <code>s1</code> or the array of integers representing bytes <code>q</code> to the file + <code>s2</code>. + </p> <h4 id="shell-symbols">Shell Symbols<a href="#top"></a></h4> <h5 id="args-symbol"><code>$:args$$</code> Symbol<a href="#top"></a></h5> <p><mark> &rarr; q</mark></p>