I only did the exercises today on page 254 of The UNIX Programming Environment.
The first exercise of making the built in constants to be truly constant and unchangeable was pretty straightforward. I added a new type called CONST and added that to the grammar. This way it would be similar to how UNDEF works. Inside init, I updated the install function to set the constants to the type CONST.
The next exercise of adding atan2 and rand was also relatively easy. The issue here is that I hardcoded the number of arguments that can be handled. There is probably a way of making this be variadic. I imagine it will be similar to how I did variadic functions for my node addon. I would need to get the arguments as an array and then apply them to the function that is being called.
The next exercise is to add the ability to shell out. This was very easy! I added an extra if statement to the yylex function where it checks for the '!' character and once it sees it, it will read in the rest of the string and then execute it.
The final exercise was to convert math.c to a table, this seems like it would be easy but I don't enough C to create a function pointers. I'm guessing there is a simple and clean way of doing this so that you can get rid of math.c and just have it all be in init.
Below is my code, this is just the 2 files that I have changed. I also removed math.c. I added it all to init.c but didn't actually convert the functions into a table.
hoc.y
%{
#include "hoc.h"
extern float Pow();
%}
%union {
float val;
Symbol *sym;
}
%token <val> NUMBER
%token <sym> VAR BLTN UNDEF CONST
%type <val> expr asgn
%right '='
%left '+' '-'
%left '*' '/'
%left '%'
%left UNARYMINUS
%right '^'
%%
list:
| list '\n'
| list ';'
| list asgn ';'
| list asgn '\n'
| list expr ';' { printf("s => \t%.8g\n",$2); }
| list expr '\n' { printf("s => \t%.8g\n",$2); }
| list error '\n' { yyerrok; }
;
asgn: VAR '=' expr {
$$ = $1->u.val = $3;
$1->type = VAR;
}
| CONST '=' expr {
execerror("cannot assign to constant", $1->name);
}
;
expr: NUMBER { $$ = $1; }
| VAR {
if ($1->type == UNDEF) {
execerror("undefined variable", $1->name);
}
$$ = $1->u.val;
}
| CONST {
if ($1->type == UNDEF) {
execerror("undefined variable", $1->name);
}
$$ = $1->u.val;
}
| asgn
| BLTN '(' expr ')' { $$ = (*($1->u.ptr))($3); }
| BLTN '(' expr ',' expr ')' { $$ = (*($1->u.ptr))($3,$5); }
| '+' expr %prec UNARYMINUS { $$ = $2; }
| '-' expr %prec UNARYMINUS { $$ = -$2; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr {
if ($3 == 0.0) execerror("Division by 0.", "");
$$ = $1 / $3;
}
| expr '%' expr { $$ = fmod($1,$3); }
| expr '^' expr { $$ = Pow($1,$3); }
| '(' expr ')' { $$ = $2; }
;
%%
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf begin;
char *progname;
int lineno = 1;
main(argc, argv)
char *argv[];
{
int fpecatch();
progname = argv[0];
init();
setjmp(begin);
signal(SIGFPE, fpecatch);
yyparse();
}
execerror(s,t)
char *s, *t;
{
warning (s, t);
longjmp(begin, 0);
}
fpecatch()
{
execerror("floating point exception", (char*)0);
}
yylex()
{
int c;
while ((c=getchar()) == ' ' || c == '\t');
if (c == EOF) return 0;
if (c == '.' || isdigit(c)) {
ungetc(c,stdin);
scanf("%f",&yylval.val);
return NUMBER;
}
if (isalpha(c)) {
Symbol *s;
char sbuf[100];
char *p = sbuf;
do {
*p++ = c;
} while ((c=getchar()) != EOF && isalnum(c));
ungetc(c, stdin);
*p = '\0';
if ((s=lookup(sbuf)) == 0) {
s = install(sbuf, UNDEF, 0.0);
}
yylval.sym = s;
return s->type == UNDEF ? VAR : s->type;
}
if (c == '!') {
char sbuf[100];
char *p = sbuf;
c=getchar();
do {
*p++ = c;
} while ((c=getchar()) != EOF && c != ';' && c != '\n');
ungetc(c, stdin);
*p = '\0';
system(sbuf);
}
if (c == '\n')
{
lineno++;
}
return c;
}
yyerror(s)
char *s;
{
warning(s, (char *) 0);
}
warning(s,t)
char *s, *t;
{
fprintf(stderr, "%s: %s", progname, s);
if (t) fprintf(stderr, " %s", t);
fprintf(stderr, " near line %d\n", lineno);
}
init.c
#include "hoc.h"
#include "y.tab.h"
#include <math.h>
#include <stdlib.h>
#include <errno.h>
extern int errno;
float Log(), Log10(), Exp(), Sqrt(), integer(), Rand();
float errcheck();
static struct {
char *name;
float cval;
} consts[] = {
"PI", 3.14159265358979323846,
"E", 2.71828182845904523536,
"GAMMA", 0.57721566490153286060,
"DEG", 57.29577951308232087680,
"PHI", 1.61803398874989484820,
0, 0
};
static struct {
char *name;
float (*func)();
} builtins[] = {
"sin", sin,
"cos", cos,
"atan", atan,
"atan2", atan2,
"log", Log,
"log10", Log10,
"exp", Exp,
"sqrt", Sqrt,
"int", integer,
"abs", fabs,
"rand", Rand,
0, 0
};
init()
{
int i;
Symbol *s;
for (i=0;consts[i].name; i++) {
install(consts[i].name, CONST, consts[i].cval);
}
for (i=0;builtins[i].name; i++) {
s = install(builtins[i].name, BLTN, 0.0);
s->u.ptr = builtins[i].func;
}
}
float Log(x)
float x;
{
return errcheck(log(x), "log");
}
float Log10(x)
float x;
{
return errcheck(log10(x), "log10");
}
float Exp(x)
float x;
{
return errcheck(exp(x), "exp");
}
float Sqrt(x)
float x;
{
return errcheck(sqrt(x), "sqrt");
}
float Pow(x, y)
float x, y;
{
return errcheck(pow(x,y), "exponentiation");
}
float integer(x)
float x;
{
return (float)(long)x;
}
float Rand(x)
float x;
{
return rand() % (int)x;
}
float errcheck(d, s)
float d;
char *s;
{
if (errno == EDOM) {
errno = 0;
execerror(s, "argument out of domain");
} else if (errno == ERANGE) {
errno = 0;
execerror(s, "result out of range");
}
return d;
}