6
$\begingroup$

I am currently writing a Funge-98 interpreter in Go following the Funge-98 specification and need to handle all of its built-in instructions. However, this is proving to be unwieldy, because there's several many built-in instructions that each do their own thing.

Currently, I have a map of an instruction's character to a function pointer that will actually perform the instruction:

interpreter.instructions = map[rune]func() {
    '^': PointNorth,
    '>': PointEast,
    'v': PointSouth,
    '<': PointWest,
// ...
    '0': PushNumber, // PushNumber checks the current instruction for what to push
    '1': PushNumber,
    '2': PushNumber,
// ...
}

and indexing into this map based on the current cell:

if interpreter.CurrentInstruction() == ' ' { /* nop */ }
else if instr, ok := interpreter.instructions[r]; ok { instr() }
else { /* ... */ }

Already, I have 40 instructions implemented and this map is getting long and unwieldy.

What other ways of organizing this many built-ins are there?

$\endgroup$

2 Answers 2

6
$\begingroup$

Fundamentally, all of your builtins have to be in one place at some point. If your concern is source code bloat, you can try to modularize the initialization function into several smaller functions. That looks like Go, which I'm not intimately familiar with, so I'll write a bit of Rust pseudocode here. Instead of

pub fn initialize_builtins() -> HashMap<char, Function> {
  let mut hashmap = HashMap::new();
  hashmap.insert('^', ...);
  hashmap.insert('<', ...);
  hashmap.insert('>', ...);
  hashmap.insert('v', ...);
  hashmap.insert('0', ...);
  hashmap.insert('1', ...);
  hashmap.insert('2', ...);
  hashmap.insert('3', ...);
  hashmap
}

you can split that into several different functions (maybe even different files) like so

pub fn initialize_builtins() -> HashMap<char, Function> {
  let mut hashmap = HashMap::new();
  init_directions(&mut hashmap);
  init_numbers(&mut hashmap);
  hashmap
}

priv fn init_directions(hashmap: &mut HashMap<char, Function>) {
  hashmap.insert('^', ...);
  hashmap.insert('<', ...);
  hashmap.insert('>', ...);
  hashmap.insert('v', ...);
}

priv fn init_numbers(hashmap: &mut HashMap<char, Function>) {
  hashmap.insert('0', ...);
  hashmap.insert('1', ...);
  hashmap.insert('2', ...);
  hashmap.insert('3', ...);
}

Those private helper functions can be relegated to their own modules if you like.

$\endgroup$
4
$\begingroup$

If the implementation language supports it, you may wish to use Decorators.

Decorators allow you to keep registration of built-ins attached to their definition, which can allow for cleaner, more extendable code.

This does however mean there is no clear list of Built-ins in the source, which can make it harder for other programmers to find them. It also introduces some complications when debugging, as Decorators don't carry the same guarantees regarding execution timing.

An example of this implementation could be:

function builtin(...symbols: string[]) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    symbols.forEach(s=>interpreter.instructions[s] = target);
  };
}

@builtin("^")
function PointNorth(symbol: String){
}

@builtin("0","1","2","3","4","5","6","7","8","9")
function PushNumber(symbol: String){
}
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .