#!/bin/bash # NEASM - The Neander Assembler. # Written by Vítor De Araújo, http://inf.ufrgs.br/~vbuaraujo . version=(0.0.1 2009-04-23) usage() { cat <outfile NEASM reads an infile in its quirky format and outputs the assembled Neander code in hexmem format. This output can be either used with the Neander-js implementation, or converted into a Neander MEM file, using a command like xxd -r -p file.hex >file.mem or neasm infile.asm | xxd -r -p >file.mem NEASM Assembly syntax: A NEASM infile may contain, in this order: - A .text section, containing literal definitions. In this section, you can define literals (constants) that will be used along the program. The definitions have the format "pos: #value". Then you can use the literal in your program by typing #value. E.g.: 255: #-1 ... ADD #-1 - A .data section, containing variable definitions. Variable definitions have the form "pos: name = val". Then you can refer to them by typing their names. E.g.: 128: a = 42 ... LDA a - A .code section, containing instructions, offsets and labels. Instructions have the form "NAME arg", where NAME is the name of the instruction. 'arg' may be a number, a literal, a variable name, or a label name. A label definition has the form "name:". It is set to the address of the next instruction. An offset has the form "pos:". It sets the address of the next instruction. NOTE: if you want to use an offset to define the address of a label, it must appear before the label. A section is started by typing its name. For example, ".text" begins a .text section. Comments are introduced by a ";". EOF } [[ $# -ne 1 || $1 == -* ]] && { usage; exit 1; } [[ $1 = "-" ]] || { if [[ -f $1 ]]; then exec <"$1" || exit 2 else echo "$1: not a file." >&2 exit 2 fi } # var_ used to keep variable address; # lit_ used to keep literals; # mem[pos] used to keep memory. mem=() cNOP=0 cSTA=16a cLDA=32a cADD=48a cOR=64a cAND=80a cNOT=96 cJMP=128a cJN=144a cJZ=160a cHLT=240 line= linenum=0 readline() { read line || return 1 ((linenum++)) line="${line%%;*}" } mem() { [[ -z ${mem[$1]} ]] || { echo "Memory overlap at position $1, line $linenum: $line" >&2; exit 3; } [[ $1 -lt 256 ]] || { echo "Program goes beyond memory at line $linenum: $line" >&2; exit 3; } if [[ $2 -lt 0 ]]; then mem[$1]=$(($2+256)) else mem[$1]="$2" fi } while [[ $line ]] || readline; do case "$line" in .text) while readline; do case "$line" in *:*\#*) set -- $line pos="${1%:}" val="${2#\#}" name="${val//-/_}" mem pos "$val" eval "lit_$name=$pos" ;; .*) break ;; "") continue ;; *) echo "Error at line $linenum: $line" >&2; exit 3 ;; esac done ;; .data) while readline; do case "$line" in *:*) set -- $line pos="${1%:}" name="$2" case "$3" in "=") val="$4" ;; "") val=0 ;; *) echo "Error at line $linenum: $line" >&2; exit 3 ;; esac eval "var_$name=$pos" mem "$pos" "$val" ;; "") continue ;; .*) break ;; *) echo "Error at line $linenum: $line" >&2; exit 3 ;; esac done ;; .code) pc=0 while readline; do case "$line" in [0-9]*:) pc="${line%%:*}" ;; *:) eval "var_${line%%:*}=$pc" ;; "") continue ;; *) set -- $line arg=0 opcode="c$1" opcode="${!opcode}" [[ $opcode == *a ]] && { arg=1 opcode="${opcode%a}" } if [[ $arg ]]; then case "$2" in \#*) val="lit_${2#\#}" val="${val//-/_}" val="${!val}" [[ $val ]] || { echo "Unknown constant \"$2\" at line $linenum." >&2; exit 3; } ;; [A-Za-z_]*) if [[ $1 == J* ]]; then # Save the label request, fill in the gaps later. eval "jmp_$2+=\" $((pc+1))@$linenum\"" val="" else val="var_$2" val="${!val}" [[ $val ]] || { echo "Unknown variable \"$2\" at line $linenum." >&2; exit 3; } fi ;; *[^0-9-]*) echo "Error at line $linenum: $line" >&2; exit 3 ;; *) val="$2" ;; esac else [[ $2 ]] && { echo "Unexpected arg at line $line: $line" >&2; exit 3; } fi mem $((pc++)) "$opcode" ((arg)) && mem $((pc++)) "$val" ;; esac done ;; "") continue ;; *) echo "Error at line $linenum: $line" >&2; exit 3 ;; esac done # Fill in the label addresses. for jmp in ${!jmp_*}; do jmpto="var_${jmp#jmp_}" for item in ${!jmp}; do pos="${item%%@*}" linenum="${item#*@}" if [[ ${!jmpto} ]]; then mem "$pos" "${!jmpto}" else echo "Unknown label \"${jmpto#var_}\" at line $linenum." >&2; exit 3 fi done done # Write away header and memory. echo -n "034e4452" for ((i=0; i<256; i++)); do printf "%02x00" "${mem[i]}" || { echo "### ERROR AT $i ###" >&2; exit 3; } ((i%15==12)) && echo done echo