Extending
nessemble
can be extended to add custom pseudo-instructions using a number of
scripting languages.
Usage
Use the --pseudo
flag while compiling to point to a text file that contains
instructions for all new pseudo-instructions.
Example pseudo.txt
:
.foo = foo.lua
Example example.asm
:
.foo 1, 2, 3
To assemble:
nessemble example.asm --pseudo pseudo.txt
Scripting
Supported scripting languages are Lua, Scheme, Javascript, command-line scripts, and compiled shared objects.
Language | Extension |
---|---|
Lua |
.lua |
Scheme |
.scm |
Javascript |
.js |
Scripts |
.py, .sh, etc. |
Shared Objects |
.so, .dll |
Lua (.lua)
At a minimum, the Lua script must have a function named custom
that accepts
a variable number of integer arguments and returns a string, which will be
written directly to the ROM.
Example
Consider a .foo
pseudo-instruction that multiplies integers together. It
might look something like this in Lua:
function custom(...)
local arg={...}
local product = 0
if arg ~= nil and #arg > 0
then
for i=1,#arg
do
if i == 1
then
product = tonumber(arg[i])
else
product = product * tonumber(arg[i])
end
end
end
return string.char(product % 256)
end
Handling Strings
To use string arguments, an add_string
function must be provided:
local strings = {}
local string_index = 0
function add_string(str)
strings[string_index] = string.gsub(str, "\"", "")
string_index = string_index + 1
end
Note
Keep in mind that strings will have quotes around them.
Example with string arguments:
.foo "product", 1, 2, 3
Handling Errors
Any errors should be thrown by calling error()
and passing along a useful
message.
function custom(...)
local arg={...}
if arg == nil or #arg == 0
then
error("No arguments provided")
end
return ""
Scheme (.scm)
At a minimum, the Scheme script must have a function named custom
that accepts
a list of integers followed by a list of strings, and displays text, which will
be written directly to the ROM.
Example
Consider a .foo
pseudo-instruction that finds the factorial of an integer. It
might look something like this in Scheme:
(define (factorial n)
(if (<= n 0)
1
(* n (factorial (- n 1)))))
(define custom
(lambda (ints texts)
(display
(factorial
(car ints)))))
Handling Strings
Strings are provided in (list ...)
form after the list of integers.
Handling Errors
Any errors should be thrown by calling (exit)
and passing along a useful
message.
Usage:
(exit NUMBER "MESSAGE")
NUMBER
- Return code, required. Defaults to 0."MESSAGE"
- Error message, required if return code is not 0.
Javascript (.js)
At a minimum, the Javascript script must have a function named custom
that
accepts a variable number of integer arguments and returns a string, which will
be written directly to the ROM.
Example
Consider a .foo
pseudo-instruction that finds the difference of a set of
integers. It might look something like this in Javascript:
var custom = function () {
var difference = 0,
i = 0,
l = 0;
for (i = 0, l = arguments.length; i < l; i += 1) {
if (i === 0) {
difference = arguments[i];
} else {
difference -= arguments[i];
}
}
return String.fromCharCode(difference % 256);
};
Handling Strings
To use string arguments, an add_string
function must be provided:
var strings = [],
string_index = 0;
var add_string = function (str) {
strings[string_index] = str.replace(/"/g, "");
string_index += 1;
};
Note
Keep in mind that strings will have quotes around them.
Example with string arguments:
.foo "difference", 10, 3, 1
Handling Errors
Any errors should be thrown by calling throw new Error()
and passing along a
useful message.
var custom = function () {
if (!arguments || !arguments.length) {
throw new Error('No arguments provided');
}
return '';
};
Scripts (.py, .sh, etc)
Scripts need only return a string, which will be written directly to the ROM, and a return code, which is used to indicate an error.
Example
Consider a .foo
pseudo-instruction that adds integers together. It might look
something like this in Python:
#!/usr/bin/env python
import sys
def main(argc, argv):
output = 0
for i in range(1, argc):
output += int(argv[i])
sys.stdout.write(chr(output % 256))
exit(0)
if __name__ == '__main__':
main(len(sys.argv), sys.argv)
Handling Strings
String arguments are sent to the script before integer arguments. The argument types will keep their precedence with other arguments of the same type, just with all the strings appearing first.
Note
Keep in mind that strings will have quotes around them.
Handling Errors
Any errors should be thrown by returning any non-0 return code. Any text that is returned is assumed to be the error message:
#!/usr/bin/env python
def main(argc, argv):
arg = argv[1:]
if len(arg) < 1:
sys.stdout.write('No arguments provided')
exit(1)
exit(0)
if __name__ == '__main__':
main(len(sys.argv), sys.argv)
Shared Objects (.so/.dll)
Shared objects should include a custom
function in the following format:
int custom(char **return_str, size_t *return_len, unsigned int ints[], int length_ints, char *texts[], int length_texts)
char **return_str
- A string (char *) passed by reference (Note: MUST be allocated withmalloc()
as it will befree()
'd')size_t *return_len
- Size of string passed by referenceunsigned int ints[]
- Array of integers fromnessemble
int length_ints
- Size ofints[]
char *texts[]
- Array of strings (char *) fromnessemble
int length_texts
- Size of*texts[]
Example
Consider a .foo
pseudo-instruction that divides integers. It might look
something like this in C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int custom(char **return_str, size_t *return_len, unsigned int ints[], int length_ints, char *texts[], int length_texts) {
int arg = 0;
unsigned int quotient = 0, i = 0, l = 0;
char *str = NULL;
for (i = 0, l = length_ints; i < l; i++) {
arg = ints[i];
if (i == 0) {
quotient = arg;
} else {
quotient = (unsigned int)((double)quotient / (double)arg);
}
}
str = (char *)malloc(sizeof(char) * 2);
str[0] = quotient % 256;
*return_str = str;
*return_len = 1;
return 0;
}
Compiling
gcc -o quotient.so quotient.c -shared -fPIC
Handling Strings
String argument types will keep their precedence with other arguments of the same type.
Note
Keep in mind that strings will have quotes around them.
Handling Errors
Any errors should be thrown by returning a non-0 return code from the custom
function. Any text that is returned is assumed to be the error message:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int custom(char **return_str, size_t *return_len, unsigned int ints[], int length_ints, char *texts[], int length_texts) {
int rc = 0;
size_t result_len = 0;
char *result = NULL;
if (!length_ints && !length_texts) {
result_len = 21;
result = (char *)malloc(sizeof(char) * (result_len + 1));
strcpy(result, "No arguments provided");
rc = 1;
goto cleanup;
}
result_len = 4;
result = (char *)malloc(sizeof(char) * (result_len + 1));
strcpy(result, "Test");
cleanup:
*return_str = result;
*return_len = result_len;
return rc;
}