steve@gumby.Altos.COM (Steve Scherf) (04/12/91)
This file contains the source code to an easy-to-use assembler for Chip
48 v2.25. Apologies to net.bandwidth.watchers about this very long posting,
but I have spent a lot of effort on this program and want the world to use it.
The shar archive begins after the virtual perforation.
--------------------------- 8< Cut Here >8 -----------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
# chasm.c
# chasm.h
# chasm.doc
# grammar.y
# lex.c
# link.c
# Makefile
# README
# sample.ch
# This archive created: Thu Apr 11 15:57:01 1991
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'chasm.c'" '(2235 characters)'
if test -f 'chasm.c'
then
echo shar: "will not over-write existing file 'chasm.c'"
else
cat << \SHAR_EOF > 'chasm.c'
/*
* chasm.c - main module
*
* @(#)chasm.c 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
#include <stdio.h>
#include <string.h>
#include "chasm.h"
char *whatstr = "@(#) chasm v0.1 Copyright (c) 1991 Steve Scherf";
int aflag; /* true if ascii output */
int oflag; /* true if redfined output filename */
char *infile;
char *outfile;
char *cout = "c.out";
char *cascii = "c.ascii";
char *ctmp = "c.tmp";
char *xstrrchr();
FILE *ifp;
FILE *ofp;
main(argc, argv)
int argc;
char **argv;
{
int i;
char *name;
name = *argv;
infile = argv[argc-1];
if(!--argc)
usage(name);
for(argv++; *argv && argc > 1; argv += 1 + i, argc -= 1 + i) {
if(**argv != '-')
usage(name);
i = 0;
while(*(++*argv))
switch(**argv) {
case 'a':
aflag++;
break;
case 'o':
if(!(outfile = *(argv+1)))
usage(name);
oflag++;
i++;
break;
default:
usage(name);
}
}
if(!oflag)
if(aflag)
outfile = cascii;
else
outfile = cout;
if((ifp = fopen(infile, "r")) == 0) {
fprintf(stderr, "%s: cannot open %s\n", name, infile);
exit(1);
}
if((ofp = fopen(ctmp, "w")) == 0) {
fprintf(stderr, "%s: cannot open %s\n", name, ctmp);
exit(1);
}
init_links();
yyparse();
chk_var();
if(chk_size() || (chk_val() + chk_lab()) || pars_err ||
!out_inst(ofp, aflag)) {
if(unlink(ctmp))
perror(ctmp);
printf("No output written to %s\n", outfile);
exit(1);
}
if(sunlink(outfile))
exit(1);
if(link(ctmp, outfile)) {
fprintf(stderr, "Cannot link %s to %s\n", ctmp, outfile);
sunlink(ctmp);
exit(1);
}
sunlink(ctmp);
}
/* unlink only if file exists */
sunlink(n)
char *n;
{
if(!access(n, 0) && unlink(n)) {
perror(n);
return -1;
}
return 0;
}
/* print usage message and exit */
usage(name)
char *name;
{
char *n;
if(n = xstrrchr(name, '/'))
n++;
else
n = name;
fprintf(stderr, "Usage: %s [-a] [-o output_file] input_file\n", n);
exit(1);
}
/* return a pointer to the char c in s, null if nonexistent */
char *
xstrrchr(s, c)
register char *s;
register char c;
{
register char *p = 0;
while(*s) {
if(*s == c)
p = s;
s++;
}
return p;
}
SHAR_EOF
fi
echo shar: "extracting 'chasm.h'" '(1913 characters)'
if test -f 'chasm.h'
then
echo shar: "will not over-write existing file 'chasm.h'"
else
cat << \SHAR_EOF > 'chasm.h'
/*
* chasm.h - chasm header file
*
* @(#)chasm.h 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
#include <stdio.h>
/* external definitions */
extern FILE *ifp;
extern int pars_err;
extern int add_lab();
#define LOADADR 0x200 /* binaries are loaded at this address */
#define MAXADDR 0xFFF /* maximum addressible memory location */
/* input token definitions */
#define STRING 257 /* string token */
#define REG 258 /* register token */
#define VAR 259 /* variable token */
#define CONST 260 /* long constant token */
#define LABEL 261 /* label token */
#define IBASE 262 /* first instruction */
/* instruction token definitions */
#define ADD 0 /* add */
#define AND 1 /* bitwise and */
#define BCD 2 /* store BCD representation of a number */
#define CLD 3 /* clear display */
#define DATA 4 /* store numeric data in memory (pseudo-instruction) */
#define DEF 5 /* define a constant (pseudo-instruction) */
#define DMP 6 /* dump registers */
#define DSP 7 /* display sprite at coordinates */
#define INK 8 /* input keystroke */
#define JMP 9 /* jump to instruction */
#define JOF 10 /* jump to instruction at offset */
#define JSR 11 /* jump to subroutine */
#define MEM 12 /* allocate memory (pseudo instruction) */
#define MOV 13 /* store a value */
#define OR 14 /* bitwise or */
#define RES 15 /* restore registers */
#define RET 16 /* return from subroutine */
#define RND 17 /* random number */
#define SAR 18 /* subtract and replace */
#define SEQ 19 /* skip if equal */
#define SHL 20 /* bitwise left shift */
#define SHR 21 /* bitwise right shift */
#define SIP 22 /* skip if key pressed */
#define SNE 23 /* skip if not equal */
#define SNP 24 /* skip if not pressed */
#define SSC 25 /* set sprite character */
#define SUB 26 /* subtract */
#define XOR 27 /* bitwise exclusive or */
SHAR_EOF
fi
echo shar: "extracting 'chasm.doc'" '(11452 characters)'
if test -f 'chasm.doc'
then
echo shar: "will not over-write existing file 'chasm.doc'"
else
cat << \SHAR_EOF > 'chasm.doc'
C H A S M
Chip 48 v2.25 assembler
Written by Steve Scherf, steve@Altos.COM
Copyright (c) 1991 Steve Scherf
This documentation and the accompanying source code may be freely distributed
with the following restrictions:
- All files must be distributed intact and unaltered. Any fixes or
patches to any of the files must be distributed as separate
information, but may be included with the original files.
- No person other than the author may sell this software or
distribute it with any product which is not in the public domain
or is intended for sale.
- This software is provided as-is, and is not promised to perform
reliably. The user of this software does so at his own risk.
- Possession of this software implies knowledge and acceptance of
these copyright restrictions.
The following is the list of files that are included in this distribution:
chasm.c
chasm.doc
chasm.h
grammar.y
lex.c
link.c
Makefile
README
sample.ch
Chasm v0.1
The purpose of this program is to aid in the creation of programs for
the Chip 48 v2.25 virtual machine, written by Andreas Gustafsson
(gson@niksula.hut.fi). This document assumes a fundamental understanding of
Chip 48. For a description of the Chip 48 program, please refer to the
documentation which is available from a variety of ftp sites and mail servers.
HOW TO USE "CHASM":
Usage: chasm [-a] [-o output_file] input_file
The "input_file" must be a text file consisting of legal "chasm" instructions.
The instructions in this file will be converted to a binary suitable for
execution by the Chip 48 v2.25 virtual machine. The "-a" option causes output
to be in the form of ascii hex characters rather than in an executable binary
format. This is useful for debugging, as it provides a human-readable output
file. The "-o" option forces the output to be placed in the filename specified
by "output_file". If an error occurs during assembly, no output file is
generated.
Files created by "chasm" are:
c.out - The default name of the executable binary produced by "chasm".
c.ascii - The default name of the ascii hex file produced by "chasm".
c.tmp - A temporary file used during assembly.
INSTRUCTION DESCRIPTIONS:
The following text describes the usage and syntax of the "chasm" instruction
set. The descriptions are all in the following format:
MNEMONIC: what the mnemonic stands for
syntax rules
Explanation of the instruction.
In the syntax rules:
"constant" is a value represented by a number, a single-quoted character, or
a label. Numbers may be hex, decimal or octal. These types are
specified as they are in the C language; i.e. hex numbers are
preceded by 0x, octal numbers are preceded by a zero and decimal
numbers have no preceding characters. Single-quoted characters
represent the ascii value of that character. Special characters may
be escaped as in C.
Examples:
0xA9 - hex A9 or decimal 169
075 - octal 75 or decimal 61
32 - decimal 32
'c' - decimal 99
Special escape characters are:
'\t' - tab or decimal
'\b' - backspace or decimal
'\r' - carriage return or decimal
'\n' - newline or decimal
'\0' - null or decimal 0
'\\' - backslash or decimal
Any escaped character that is not in the above list is treated as if
it were not escaped.
A special form of constant is a string. As in many languages, a string
is a series of characters preceded and ended by double quotes. The
special escape characters defined above are also valid within strings.
Strings may only be used in the data instruction and have the same
effect as a series of constants.
Example:
"abcd"
is the same as
'a', 'b', 'c', 'd'
or
97, 98, 99, 100
"label" is a word not beginning with a digit and composed of alphanumeric
characters and/or the underbar ("_") character.
"register" is a "V" followed by a single hex digit. This corresponds to one
of the 16 virtual machine registers.
"dtimer", "stimer", and "I" correspond to the special predefined variables
delay_timer, sound_timer, and I respectively. For explanations of
these variables, see the documentation for Chip 48 v2.25.
In the explanation of the instructions, "arg1", "arg2" and "arg3" refer to the
first, second and third arguments to the instruction respectively.
Instructions that are marked with an asterisk are pseudo-instructions.
ADD: add
add constant, register
add register, register
add register, I
Add arg1 and arg2 and put the result in arg2. In the second case
VF is true if a carry occurred.
AND: and
and register, register
Store the bitwise and of arg1 and arg2 in arg2. VF may change.
BCD: binary coded decimal
bcd register
Store the three byte binary coded decimal representation of arg1 in
the memory address pointed to by I. (I ... I+2)
CLD: clear display
cld
Clear the display.
DATA: * data
data datalist
Store a list of one-byte constants in memory, where datalist is a
list of constants and/or strings separated by commas. Examples:
data 'a', 'b', 'c'
data 0xAB, "hello", 56, 0123, xyz
In the latter example, xyz is a label that must be defined somewhere
within the program. The data list may span more than one line.
DEF: * define
def label, constant
Assign a value to a label. Example:
def xyz, 0xFF
DMP: dump
dmp register
Dump registers V0 through arg1 to the memory address pointed to by I.
DSP: display sprite
dsp constant, register, register
Display sprite pointed to by I. The sprite is taken to be arg1 bytes
long and is displayed at coordinates arg2, arg3. VF is true if a
collision occurred with another sprite.
INK: input key
ink register
Get keypress and store hex value in arg1. Execution is suspended
until a key is pressed. The speaker beeps when a key is pressed.
JMP: jump
jmp constant
Cause execution to branch to the instruction at address arg1.
JOF: jump to offset
jof constant
Cause execution to branch to the instruction at address arg1 plus
the value in V0. This is good for branch tables. Offsets are easy
to compute since all true instructions are two bytes; pseudo-
instructions are as follows: "mem" takes exactly as many bytes as
specified; "data" takes one byte per element in the data list, and
one byte per character in a string; "def" takes no memory. Keep in
mind that if an odd number of bytes is specified with "mem" or "data",
a pad byte may be added before the next instruction to insure that
it is on an even address boundary.
JSR: jump to subroutine
jsr constant
Cause execution to branch to the instruction at address arg1. Execution
returns to the branch point when a "ret" instruction is executed.
MEM: * memory
mem constant
Allocate arg1 bytes of memory. Memory is all zeros.
MOV: move
mov register, register
mov constant, register
mov constant, I
mov register, {stimer, dtimer, I}
mov dtimer, register
Store value of arg1 in arg2. In the first case VF may change.
OR: or
or register, register
Store the bitwise or of arg1 and arg2 in arg2. VF may change.
RES: restore
res register
Restore registers V0 through arg1 from the memory address pointed to
by I.
RET: return
ret
Cause execution to branch to the instruction following the last
"jsr" instruction executed.
RND: random
rnd constant, register
Generate a random number bitwise and arg1. The result is stored
in arg2.
SAR: subtract and replace
sar register, register
Subtract arg2 from arg1 and put the result in arg2; VF equals
not borrow.
SEQ: skip if equal
seq register, register
Skip the next instruction if arg1 equals arg2.
SHL: shift left
shl register
Shift arg1 left one bit; VF equals carry.
SHR: shift right
shr register
Shift arg1 right one bit; VF equals carry.
SIP: skip if pressed
sip register
Skip the next instruction if the key represented by the hex value
arg1 is pressed.
SNE: skip if not equal
sne constant, register
sne register, register
Skip the next instruction if arg1 does not equal arg2.
SNP: skip if not pressed
snp register
Skip the next instruction if the key represented by the hex value
arg1 is not pressed.
SSC: set sprite coordinates
ssc register
Store the address of a 5 byte font sprite for the hex character
arg1.
SUB: subtract
sub register, register
Subtract arg1 from arg2 and put the result in arg2; VF equals
not borrow.
XOR: exclusive or
xor register, register
Store the bitwise exclusive or of arg1 and arg2 in arg2. VF may change.
NOTES:
1] Labels may be used before they are defined, as long as they are defined
somewhere within the file. The only exception is the "mem"
pseudo-instruction; labels used in this instruction must be defined
in advance.
2] In addition to the "def" instruction, a label may be defined by
putting it on a line by itself followed by a colon. This will
set the value of the label to the address of the instruction following
the label definition.
Example:
add V0, V1
xyz:
mov 0xFF, V3
In this code fragment, xyz is set to the address of the "mov"
instruction. This label is suitable for use in a "jmp", "jsr" or
"jof" instruction.
3] The maximum size for a constant is 0xFFF. Some instructions require
a constant to be smaller. The general rule is: addresses must be
<= 0xFFF, numerical constants must be <= 0xFF, and sprite sizes
must be <= 0xF. Using a value that is too large will generate an
assembler error.
4] Since both data and instructions share common space, care must be
taken to ensure that program execution does not fall into addresses
containing data. This will result in program failure unless the data
resembles legal instructions. Data space is allocated with the "data"
and "mem" pseudo-instructions.
5] Instructions must occupy a single line, except for the "data"
instruction.
6] Blank lines and white space are ignored.
7] Instructions, registers, and numerical constants may be specified in
either upper or lower case.
8] Comments are delineated by either the ";" or the "/" character.
Any text after either of these two characters is ignored by the
assembler until a newline is reached, so comments must appear after
instructions.
ERROR MESSAGES:
Error messages generated by the assembler are accompanied by an input file
line number indicating which line in the input file had the error.
Below is a list of possible assembler error messages and their meanings.
"label multiply defined" - A label has been defined more than once.
"constant must be forward declared" - A previously undefined label has been
used in a "mem" instruction.
"illegal variable usage" - A special variable has been used as an argument
to an instruction that does not allow that variable.
"duplicate label" - More than one "line label" in a row has been defined.
"constant out of range" - A constant on the specified line is larger than 0xFFF.
"Error - program too large for address space" - The binary that would be
generated from the input file is larger than the maximum legal size.
The maximum addressible memory location is 0xFFF, but the first 0x200
bytes of address space are reserved. Thus, the maximum binary size is
0xDFF bytes.
"Error - undefined labels:" - The list of labels following this message have
not been defined.
"Error - constant size:" - The list of line numbers following this message
specifies instructions that have constants that are illegally large.
SHAR_EOF
fi
echo shar: "extracting 'grammar.y'" '(4647 characters)'
if test -f 'grammar.y'
then
echo shar: "will not over-write existing file 'grammar.y'"
else
cat << \SHAR_EOF > 'grammar.y'
/*
* grammar.y - yacc parser
*
* @(#)grammar.y 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
%{
#include <stdio.h>
extern char *vartab[];
extern int bcnt;
extern int svin;
int lineno = 1;
int pars_err;
%}
/* input token definitions */
%token STRING
%token REG
%token VAR
%token CONST
%token LABEL
/* instruction token definitions */
%token ADD
%token AND
%token BCD
%token CLD
%token DATA
%token DEF
%token DMP
%token DSP
%token INK
%token JMP
%token JOF
%token JSR
%token MEM
%token MOV
%token OR
%token RES
%token RET
%token RND
%token SAR
%token SEQ
%token SHL
%token SHR
%token SIP
%token SNE
%token SNP
%token SSC
%token SUB
%token XOR
%% /* rules section */
line : /* empty */
| line inst '\n'
{
lineno++;
}
| line error '\n'
{
lineno++;
yyerrok;
}
;
inst : /* empty */
| ADD const ',' REG
{
add_inst(lineno, 0x7, $4, 1, 0x0, 0, $2, 2);
}
| ADD REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x4, 1);
}
| ADD REG ',' VAR
{
if($4 == 2)
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x1E, 2);
else
yyerror("illegal variable usage");
}
| AND REG ',' REG
{
add_inst(lineno, 0xF, $2, 1, $4, 1, 0x2, 1);
}
| BCD REG
{
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x33, 2);
}
| CLD
{
add_inst(lineno, 0x0, 0x0, 0, 0x0, 0, 0x0E0, 3);
}
| DATA dlist
{
add_str_list();
}
| DEF LABEL ',' const
{
register int i;
i = add_lab($2);
lineno++;
add_val(i, $4);
lineno--;
}
| DMP REG
{
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x55, 2);
}
| DSP const ',' REG ',' REG
{
add_inst(lineno, 0xD, $4, 1, $6, 1, $2, 1);
}
| INK REG
{
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x0A, 2);
}
| JMP const
{
add_inst(lineno, 0x1, 0x0, 0, 0x0, 0, $2, 3);
}
| JOF const
{
add_inst(lineno, 0xB, 0x0, 0, 0x0, 0, $2, 3);
}
| JSR const
{
add_inst(lineno, 0x2, 0x0, 0, 0x0, 0, $2, 3);
}
| MEM const
{
add_pad($2);
}
| MOV const ',' REG
{
add_inst(lineno, 0x6, $4, 1, 0x0, 0, $2, 2);
}
| MOV REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x0, 1);
}
| MOV const ',' VAR
{
if($4 == 2)
add_inst(lineno, 0xA, 0x0, 0, 0x0, 0, $2, 3);
else
yyerror("illegal variable usage");
}
| MOV REG ',' VAR
{
switch($4) {
case 0:
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x15, 2);
break;
case 1:
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x18, 2);
break;
case 2:
yyerror("illegal variable usage");
break;
}
}
| MOV VAR ',' REG
{
if($2 == 0)
add_inst(lineno, 0xF, $4, 1, 0x0, 0, 0x07, 2);
else
yyerror("illegal variable usage");
}
| OR REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x1, 1);
}
| RES REG
{
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x65, 2);
}
| RET
{
add_inst(lineno, 0x0, 0x0, 0, 0x0, 0, 0x0EE, 3);
}
| RND const ',' REG
{
add_inst(lineno, 0xC, $4, 1, 0x0, 0, $2, 2);
}
| SAR REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x7, 1);
}
| SEQ REG ',' REG
{
add_inst(lineno, 0x5, $2, 1, $4, 1, 0x0, 1);
}
| SEQ const ',' REG
{
add_inst(lineno, 0x3, $4, 1, 0x0, 0, $2, 2);
}
| SHL REG
{
add_inst(lineno, 0x8, $2, 1, 0x0, 0, 0x0E, 2);
}
| SHR REG
{
add_inst(lineno, 0x8, $2, 1, 0x0, 0, 0x06, 2);
}
| SIP REG
{
add_inst(lineno, 0xE, $2, 1, 0x0, 0, 0x9E, 2);
}
| SNE const ',' REG
{
add_inst(lineno, 0x4, $4, 1, 0x0, 0, $2, 2);
}
| SNE REG ',' REG
{
add_inst(lineno, 0x9, $2, 1, $4, 1, 0x0, 1);
}
| SNP REG
{
add_inst(lineno, 0xE, $2, 1, 0x0, 0, 0xA1, 2);
}
| SSC REG
{
add_inst(lineno, 0xF, $2, 1, 0x0, 0, 0x29, 2);
}
| SUB REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x5, 1);
}
| XOR REG ',' REG
{
add_inst(lineno, 0x8, $4, 1, $2, 1, 0x3, 1);
}
| LABEL ':'
{
if(svin != -1) {
add_val(svin, 0);
yyerror("duplicate label");
}
svin = add_lab($1);
}
;
dlist : dlent
| dlist ',' dlent
| dlist ',' newln dlent
;
dlent : const
{
add_con(lineno, $1);
$$ = $1;
}
| STRING
{
add_str(lineno, (char *)$1);
$$ = $1;
}
;
const : CONST
{
if($1 > 0xFFF || $1 < 0) {
yyerror("constant out of range");
$$ = 0;
}
else
$$ = $1;
}
| LABEL
{
register int i;
i = add_lab($1);
$$ = ((i + 1) << 12) + 0xFFF;
}
newln : '\n'
{
lineno++;
}
| newln '\n'
{
lineno++;
}
;
%% /* C code */
yyerror(s)
char *s;
{
printf("line %d: %s\n", lineno, s);
pars_err = 1;
}
SHAR_EOF
fi
echo shar: "extracting 'lex.c'" '(5351 characters)'
if test -f 'lex.c'
then
echo shar: "will not over-write existing file 'lex.c'"
else
cat << \SHAR_EOF > 'lex.c'
/*
* lex.c - lexical analyzer
*
* @(#)lex.c 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
#include <stdio.h>
#include <ctype.h>
#include "chasm.h"
extern int yylval;
char *insttab[] = {
"add", "and", "bcd", "cld", "data", "def", "dmp", "dsp", "ink",
"jmp", "jof", "jsr", "mem", "mov", "or", "res", "ret", "rnd",
"sar", "seq", "shl", "shr", "sip", "sne", "snp", "ssc", "sub", "xor"
};
int ninst = sizeof(insttab) / sizeof(char *);
char *vartab[] = {
"dtimer",
"stimer",
"i"
};
int nvar = sizeof(vartab) / sizeof(char *);
char getch();
char getcconst();
char getslash();
char getslashval();
char getqchar();
char *gettoken();
int getinstr();
int getstr();
int getconst();
int getreg();
int gethex();
int getoct();
int getdec();
int getvar();
/* lexical analyzer */
yylex()
{
unsigned int x;
register int i;
register char *s;
char l[512], lbl[512], c;
s = gettoken();
if(!s)
return 0;
if(*s == '"')
if((yylval = (int)getstr()) != 0)
return STRING;
else
return 1;
if(*s == '\'')
if((yylval = getcconst()) != -1)
return CONST;
else
return 1;
if(isctok(*s))
return *s;
if((i = getinstr(s)) >= 0)
return(i + IBASE);
if((yylval = getvar(s)) != -1)
return VAR;
if((yylval = getreg(s)) != -1)
return REG;
if((yylval = getconst(s)) != -1)
return CONST;
if(islabel(s) != 0) {
strcpy(lbl, s);
yylval = (int)lbl;
return LABEL;
}
return 1;
}
/* read one token from input */
char *
gettoken()
{
register char *p;
static char l[512];
static char ctok = 0;
if(ctok) {
l[0] = ctok;
l[1] = '\0';
ctok = 0;
return l;
}
p = l;
while((*p = getch()) != EOF && isspace(*p) && !isctok(*p))
continue;
if(*p == EOF)
return 0;
if(isctok(*p)) {
*(++p) = '\0';
return l;
}
p++;
while((*p = getch()) != EOF && !isspace(*p) && *p != ',' && *p != ':' &&
p < l + sizeof(l) - 1)
p++;
if(isctok(*p))
ctok = *p;
*p = '\0';
return l;
}
/* get one character, ignoring comments */
char
getch()
{
register char c;
if((c = getc(ifp)) != ';' && c != '/')
return c;
while(getc(ifp) != '\n')
continue;
return '\n';
}
/* return true if c is a one character token */
isctok(c)
register c;
{
if(c == ',' || c == '\n' || c == '"' || c == '\'' || c == ':')
return 1;
return 0;
}
/* get a single-quoted character constant from input */
char
getcconst()
{
char c, d;
c = getc(ifp);
if(c == '\\') {
if((c = getslash()) == EOF)
return -1;
}
else
if(c == '\n') {
ungetc(c, ifp);
return -1;
}
if((d = getc(ifp)) == '\n')
ungetc(d, ifp);
else
if(d == '\'')
return c;
return -1;
}
/* get string of characters from input */
int
getstr()
{
register char *p;
static char l[512];
p = l;
while((*p = getc(ifp)) != EOF && *p != '\n' && *p != '"' &&
p < l + sizeof(l) - 1) {
if(*p == '\\' && (*p = getslash()) == EOF)
return 0;
p++;
}
if(*p == '\n')
ungetc('\n', ifp);
if(*p != '"')
return 0;
*p = '\0';
return((int)l);
}
/* get a backslash-escaped character from input */
char
getslash()
{
register char c;
if((c = getc(ifp)) == '\n') {
ungetc(c, ifp);
return EOF;
}
switch(c) {
case 'b':
return '\b';
case 'n':
return '\n';
case 'r':
return '\r';
case 't':
return '\t';
case '0':
return '\0';
case EOF:
return EOF;
default:
return c;
}
}
/* return true if s is an alphanumeric label */
islabel(s)
char *s;
{
if(isdigit(*s))
return 0;
while(*s && (isalnum(*s) || *s == '_'))
s++;
return(*s == '\0');
}
/* if s is an instruction, return index in table */
int
getinstr(s)
char *s;
{
register char *p1, *p2;
register int i;
for(i = 0; i < ninst; i++) {
for(p1 = s, p2 = insttab[i]; *p1 && *p2; p1++, p2++)
if(*p1 != *p2 && *p1 != toupper(*p2))
break;
if(!*p1 && !*p2)
return i;
}
return -1;
}
/* if s is a register, return its number */
int
getreg(s)
register char *s;
{
register int x;
if((s[0] == 'V' || s[0] == 'v') && s[2] == '\0') {
x = gethex(s+1);
if(x >= 0x0 && x <= 0xF)
return x;
}
return -1;
}
/* convert s to an integer of the appropriate base */
int
getconst(s)
register char *s;
{
if(s[0] == '0')
if(s[1] == 'x' || s[1] == 'X')
return(gethex(s+2));
else
return(getoct(s+1));
return(getdec(s));
}
/* convert s to a hex integer */
int
gethex(s)
register char *s;
{
register int n, l;
for(n = 0; *s; s++) {
if(!isxdigit(*s))
return -1;
if(isdigit(*s))
n = (n << 4) + *s - '0';
else {
if(isupper(*s))
*s = tolower(*s);
n = (n << 4) + *s - 'a' + 10;
}
}
return n;
}
/* convert s to an octal integer */
int
getoct(s)
register char *s;
{
register int n;
for(n = 0; *s; s++) {
if(!isdigit(*s) || *s > '7')
return -1;
n = (n << 3) + *s - '0';
}
return n;
}
/* convert s to a decimal integer */
int
getdec(s)
register char *s;
{
register int n;
for(n = 0; *s; s++) {
if(!isdigit(*s))
return -1;
n = (n * 10) + *s - '0';
}
return n;
}
/* if s is a special variable, return its index */
int
getvar(s)
char *s;
{
register char *p1, *p2;
register int i;
for(i = 0; i < nvar; i++) {
for(p1 = s, p2 = vartab[i]; *p1 && *p2; p1++, p2++)
if(*p1 != *p2 && *p1 != toupper(*p2))
break;
if(!*p1 && !*p2)
return i;
}
return -1;
}
SHAR_EOF
fi
echo shar: "extracting 'link.c'" '(8096 characters)'
if test -f 'link.c'
then
echo shar: "will not over-write existing file 'link.c'"
else
cat << \SHAR_EOF > 'link.c'
/*
* link.c - linked list routines
*
* @(#)link.c 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
#include <stdio.h>
#include "chasm.h"
#define T_INST 0 /* instruction type link */
#define T_CON 1 /* constant type link */
#define T_STR 2 /* string type link */
#define T_PAD 3 /* pad type link */
struct link {
struct link *l_next;
int l_type;
int l_n;
union {
struct {
char *l_l;
int l_v;
int l_va;
} l;
struct {
int l_i[4];
int l_s[3];
} i;
struct {
struct link *l_c;
int l_con;
unsigned char *l_s;
} c;
} u;
};
#define l_line l_n
#define l_pad l_n
#define l_lab u.l.l_l
#define l_val u.l.l_v
#define l_valid u.l.l_va
#define l_op u.i.l_i
#define l_sz u.i.l_s
#define l_cp u.c.l_c
#define l_const u.c.l_con
#define l_len u.c.l_con
#define l_str u.c.l_s
struct link *lh; /* head of linked-list of labels */
struct link *lt; /* tail of linked-list of labels */
struct link *ih; /* head of linked-list of instructions */
struct link *it; /* tail of linked-list of instructions */
struct link *ch; /* head of linked-list of constants */
struct link *ct; /* tail of linked-list of constants */
struct link *get_lab_in();
struct link *smalloc();
int bcnt = LOADADR; /* output byte count */
int svin = -1; /* saved index of label to be defined */
extern int lineno;
/* set up linked lists */
init_links()
{
lh = smalloc(sizeof(struct link));
ih = smalloc(sizeof(struct link));
lh->l_next = lh;
lt = lh;
ih->l_next = ih;
it = ih;
ch = 0;
ct = 0;
}
/* add label to list if needed, return index */
int
add_lab(lab)
char *lab;
{
register struct link *l;
register int i;
for(i = 0, l = lh->l_next; l != lh; i++, l = l->l_next)
if(!strcmp(lab, l->l_lab))
return i;
l = smalloc(sizeof(struct link));
l->l_next = lh;
lt->l_next = l;
lt = l;
l->l_lab = (char *)smalloc(strlen(lab)+1);
strcpy(l->l_lab, lab);
l->l_val = 0;
l->l_valid = 0;
return i;
}
/* assign value to label by index */
add_val(in, val)
int in, val;
{
register int i;
register struct link *l;
for(i = 0, l = lh->l_next; i < in; i++)
l = l->l_next;
if(l->l_valid) {
lineno--;
yyerror("label multiply defined");
lineno++;
}
else {
l->l_valid = 1;
l->l_val = val;
}
}
/* get label by index */
struct link *
get_lab_in(in)
register int in;
{
register struct link *l;
for(l = lh->l_next; in; in--)
l = l->l_next;
return l;
}
/* check the size of the potential binary */
chk_size()
{
if(--bcnt > MAXADDR) {
printf("Error - program too large for address space ");
printf("(0x%X > 0x%3X)\n", bcnt, MAXADDR);
return 1;
}
return 0;
}
/* check the linked list of labels for any that are undefined */
chk_lab()
{
static int first = 1;
register struct link *l;
for(l = lh->l_next; l != lh; l = l->l_next)
if(!l->l_valid) {
if(first) {
printf("Error - undefined labels:\n");
first = 0;
}
printf("\t%s\n", l->l_lab);
}
return(!first);
}
/* check the value of constants to make sure they are in bounds */
chk_val()
{
register struct link *l, *lp;
register int x;
struct link *lp2;
int i, y, err = 0;
for(l = ih->l_next; l != ih; l = l->l_next)
switch(l->l_type) {
case T_CON:
for(lp = l->l_cp; lp; lp = lp->l_cp) {
x = lp->l_const;
if(x > 0xFFF) {
lp2 = get_lab_in((x >> 12)-1);
x = lp2->l_const;
}
if(x > 0xFF) {
print_ctl(lp->l_line, !err);
err = 1;
break;
}
}
break;
case T_INST:
for(i = 0; i < 3; i++) {
if(l->l_op[i+1] > 0xFFF) {
lp = get_lab_in((l->l_op[i+1] >> 12)-1);
x = lp->l_val;
}
else
x = l->l_op[i+1];
y = l->l_sz[i];
if(y < 0)
y++;
if(x >= (1 << (y * 4))) {
print_ctl(l->l_line, !err);
err = 1;
break;
}
}
break;
default:
break;
}
return err;
}
/* check to see if the last instruction is a label */
chk_var()
{
if(svin != -1)
add_val(svin, bcnt);
}
/* add instruction to list */
add_inst(line, a0, a1, s1, a2, s2, a3, s3)
int line, a0, a1, s1, a2, s2, a3, s3;
{
register struct link *l;
ct = ch;
if(svin != -1) {
add_val(svin, bcnt + (bcnt % 2));
svin = -1;
}
if(bcnt % 2)
add_pad(1);
l = smalloc(sizeof(struct link));
l->l_line = line;
l->l_type = T_INST;
l->l_op[0] = a0;
l->l_op[1] = a1;
l->l_op[2] = a2;
l->l_op[3] = a3;
l->l_sz[0] = s1;
l->l_sz[1] = s2;
l->l_sz[2] = s3;
l->l_next = ih;
it->l_next = l;
it = l;
bcnt += 2;
}
/* add constant to string list */
add_con(line, c)
int line, c;
{
if(!ch) {
ch = smalloc(sizeof(struct link));
ch->l_cp = 0;
ct = ch;
}
if(!ct->l_cp) {
ct->l_cp = smalloc(sizeof(struct link));
ct = ct->l_cp;
ct->l_cp = 0;
}
else
ct = ct->l_cp;
ct->l_type = T_CON;
ct->l_line = line;
ct->l_const = c;
}
/* add a string to linked list of constants */
add_str(line, s)
int line;
char *s;
{
int i;
if(!ch) {
ch = smalloc(sizeof(struct link));
ch->l_cp = 0;
ct = ch;
}
if(!ct->l_cp) {
ct->l_cp = smalloc(sizeof(struct link));
ct = ct->l_cp;
ct->l_cp = 0;
}
else
ct = ct->l_cp;
i = strlen(s);
ct->l_len = i;
ct->l_str = (unsigned char *)smalloc(++i);
strcpy(ct->l_str, s);
ct->l_type = T_STR;
ct->l_line = line;
}
/* add a byte pad directive to the linked list of instructions */
add_pad(n)
int n;
{
register struct link *l;
if(svin != -1) {
add_val(svin, bcnt);
svin = -1;
}
if(n > 0xFFF) {
n = n >> 12;
l = get_lab_in(--n);
if(!l->l_valid) {
yyerror("constant must be forward declared");
n = 0;
}
else
n = l->l_val;
}
bcnt += n;
ct = ch;
l = smalloc(sizeof(struct link));
l->l_type = T_PAD;
l->l_pad = n;
l->l_next = ih;
it->l_next = l;
it = l;
}
/* add a constant list directive to the linked list of instructions */
add_str_list()
{
register struct link *l;
register int i;
if(ch == ct)
return;
if(svin != -1) {
add_val(svin, bcnt);
svin = -1;
}
for(l = ch->l_cp, i = 0; l; l = l->l_cp)
if(l->l_type == T_STR)
i += l->l_len;
else
i++;
bcnt += i;
ch->l_type = T_CON;
ch->l_next = ih;
it->l_next = ch;
it = ch;
ch = ct->l_cp;
ct->l_cp = 0;
ct = ch;
}
/* output instructions accordingly */
out_inst(fp, asc)
FILE *fp;
int asc;
{
register struct link *l, *lp, *lp2;
register int y;
int i, x;
unsigned char c, *p;
for(l = ih->l_next; l != ih; l = l->l_next)
switch(l->l_type) {
case T_CON:
for(lp = l->l_cp; lp; lp = lp->l_cp) {
if(lp->l_type == T_STR) {
p = lp->l_str;
while(*p) {
if(asc) {
if(fprintf(fp, "%.2X",
*p) != 2)
return 0;
}
else
if(fwrite(p, 1, 1, fp)
!= 1)
return 0;
p++;
}
}
else {
y = lp->l_const;
if(y > 0xFFF) {
y = y >> 12;
lp2 = get_lab_in(--y);
y = lp2->l_val;
}
if(asc) {
if(fprintf(fp, "%.2X", y) != 2)
return 0;
}
else {
c = y;
if(fwrite(&c, 1, 1, fp) != 1)
return 0;
}
}
}
break;
case T_INST:
x = l->l_op[0] << 12;
for(i = 1; i < 4; i++) {
y = l->l_op[i];
if(y > 0xFFF) {
y = y >> 12;
lp = get_lab_in(--y);
y = lp->l_val;
}
x |= y << (12 - (i * 4));
}
if(asc) {
if(fprintf(fp, "%.4X", x) != 4)
return 0;
}
else {
c = x >> 8;
if(fwrite(&c, 1, 1, fp) != 1)
return 0;
c = x & 0xFF;
if(fwrite(&c, 1, 1, fp) != 1)
return 0;
}
break;
case T_PAD:
for(i = 0; i < l->l_pad; i++)
if(asc) {
if(fprintf(fp, "%.2X", 0) != 2)
return 0;
}
else {
c = 0;
if(fwrite(&c, 1, 1, fp) != 1)
return 0;
}
break;
}
return 1;
}
/* print constant size error list */
print_ctl(line, first)
int line, first;
{
if(first)
printf("Error - constant size:\n");
printf("\tline %d\n", line);
}
/* "safe" malloc */
struct link *
smalloc(size)
int size;
{
struct link *p;
if(!(p = (struct link *)malloc(size))) {
perror("malloc");
exit(1);
}
return p;
}
SHAR_EOF
fi
echo shar: "extracting 'Makefile'" '(537 characters)'
if test -f 'Makefile'
then
echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
# Makefile - chasm makefile
#
# @(#)Makefile 1.1 91/04/10
#
# Copyright (c) 1991 Steve Scherf
#
# Author: Steve Scherf
# Date: Wed Apr 10 22:53:11 PDT 1991
#
SRC = chasm.c lex.c link.c
OBJ = chasm.o lex.o link.o y.tab.o
GRM = grammar.y
HDR = chasm.h
BIN = chasm
MAK = Makefile
RDM = README
FILES = $(SRC) $(GRM) $(HDR) $(MAK) $(RDM)
CFLAGS = -O
$(BIN): $(OBJ)
cc $(CFLAGS) -o $(BIN) $(OBJ)
mcs -d $(BIN)
lex.o: $(HDR)
link.o: $(HDR)
chasm.o: $(HDR)
y.tab.c: $(GRM)
yacc $?
wc:
wc $(FILES)
shar:
shar $(FILES) > chasm.shar
SHAR_EOF
fi
echo shar: "extracting 'README'" '(2027 characters)'
if test -f 'README'
then
echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
/*
* README - chasm README file
*
* @(#)README 1.1 91/04/10
*
* Copyright (c) 1991 Steve Scherf
*
* Author: Steve Scherf
* Date: Wed Apr 10 22:53:11 PDT 1991
*
*/
Chasm v0.1
Since you're reading this, you've most likely unshar-ed the chasm package.
The unpacked files should include:
chasm.c
chasm.doc
chasm.h
grammar.y
lex.c
link.c
Makefile
README
sample.ch
Chasm is written for System V Unix, but should run under BSD and other
operating systems with little or no modification. To compile, merely type
"make". The result should be the successful compilation of a binary called
"chasm". This program was written mostly as an exercise in learning yacc
and general assembler construction, and as a result is probably a little
over-complex. There are many features that are not really necessary, but
are useful and might come in handy. Chasm really does make it easy to write
Chip 48 programs!
At this point I have extensively tested chasm to make sure it generates the
opcodes laid out in the original Chip 48 v2.25 document. Each instruction
does indeed produce the proper opcode. However, I have only written a few
programs with it and have not actually tested every instruction in the context
of a program. Therefore, it is possible that some instructions may not work
properly if the documentation is erroneous; nevertheless, I believe that chasm
is fairly bug-free.
Chasm is simple to use. Simply type "chasm filename" where "filename" is
a file containing chasm assembly instructions. If chasm successfully
assembles the file, the result should be a file called "c.out" which contains
an executable Chip 48 binary. This file can be downloaded to the HP 48
via kermit (make sure the mode is set to binary).
Included is a sample chasm file called sample.ch. Try assembling and
running it!
If you have any questions, comments or bugs to report, don't hesitate to send
email. I can't promise that I will respond very quickly since I am often
very busy, but I will try.
Steve Scherf
steve@Altos.COM
SHAR_EOF
fi
echo shar: "extracting 'sample.ch'" '(2060 characters)'
if test -f 'sample.ch'
then
echo shar: "will not over-write existing file 'sample.ch'"
else
cat << \SHAR_EOF > 'sample.ch'
/ This is a sample chasm file. It doesn't do an awful lot, but makes
/ a good example. Keep in mind that this is pretty much of a hack.
/ The only key used by the program is "+".
/ Registers used:
/ V0 - scratch
/ V1 - keypress flag
/ V2 - key comparison
/ V3 - sprite x
/ V4 - sprite y
/ V5 - sad/smile flag
/ V6 - scratch
/ V7 - scratch
jmp start / jump over the data
data "By Steve Scherf" / this shows up for all to see
start:
cld / blank the screen
mov 0, V1 / initialize keypress flag
mov 0xF, V2 / this is the plus key
mov 32, V3 / initialize x coord
mov 16, V4 / initialize y coord
mov 0, V5 / initialize smiley flag
mov sprite, I / set pointer to sprite
dsp 6, V3, V4 / display the sprite
loop:
jsr get_key / check for keyboard input
mov dtimer, V0 / copy dtimer to V0
sne 0, V0 / is it time to move sprite?
jsr move_sprite / do so if it is time
jmp loop / go back to top
/ don't fall through here
get_key:
mov 0, V7 / clear V7
snp V2 / check for the "+" key
mov 1, V7 / set V7 to true if pressed
mov V1, V6 / set V6 to old value of keypress flag
mov V7, V1 / set keypress flag accordingly
seq 1, V7 / is the key pressed?
ret / return if not
sne 1, V6 / if V6 is true, key has not been lifted
ret / return if not lifted
mov 1, V6
mov V6, stimer / beep for 1/60th second
mov 1, V6
xor V6, V5 / toggle smile/sad flag
dsp 6, V3, V4 / clear the old sprite
mov sprite, I / point I to the new sprite
mov 6, V6
seq 0, V5 / is this the second sprite?
add V6, I / point I to the second sprite
dsp 6, V3, V4 / display the new sprite
ret
move_sprite:
dsp 6, V3, V4 / clear the old sprite
add 1, V3 / compute new coords
add 255, V4 / this is the same as subtracting 1
dsp 6, V3, V4 / display the sprite at new coords
mov 15, V0 / we can only set timer from a register
mov V0, dtimer / set timer for 1/4 second
ret
sprite:
data 0x66, 0x66, 0x00, 0x00, 0x42, 0x3C / smiley sprite
data 0x66, 0x66, 0x00, 0x00, 0x3C, 0x42 / sad sprite
SHAR_EOF
fi
exit 0
# End of shell archive
--
Steve Scherf
steve@Altos.COM ...!{sun|sco|pyramid|amdahl|uunet}!altos!steve
These opinions are solely mine, but others may share them if they like.