sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

commit 33ac9371b8f5ef9c3d80c443f91ea42c5a8778d4
parent 9c9da834c37dbab7780f3307bfc367862d35e8cd
Author: Roberto E. Vargas Caballero <k0ga@shike2.net>
Date:   Fri, 21 Nov 2025 23:00:34 +0100

bc: Initial version

This version is almost finished, but it does not implements the return
keyword (it is ignored) and it does not include the default bc library
(the option to load it is silently ignored). It also lack tests.

Diffstat:
M.gitignore | 2++
MMakefile | 8++++++--
Abc.y | 707+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 715 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -7,6 +7,8 @@ /libutil.a /proto /basename +/bc +/bc.c /cal /cat /chgrp diff --git a/Makefile b/Makefile @@ -2,7 +2,7 @@ include config.mk .SUFFIXES: -.SUFFIXES: .o .c +.SUFFIXES: .y .o .c CPPFLAGS =\ -D_DEFAULT_SOURCE \ @@ -94,6 +94,7 @@ LIB = libutf.a libutil.a BIN =\ basename\ + bc\ cal\ cat\ chgrp\ @@ -220,6 +221,9 @@ $(OBJ) $(BIN): $(HDR) .c: $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LIB) +bc: bc.c + $(CC) $(LDFLAGS) -o $@ bc.c $(LIB) + $(MAKEOBJ): make/make.h make/make: $(MAKEOBJ) @@ -272,7 +276,7 @@ clean: @cd tests && $(MAKE) clean rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz rm -f scripts/make - rm -f getconf.h + rm -f getconf.h bc.c rm -f proto rm -rf build diff --git a/bc.y b/bc.y @@ -0,0 +1,707 @@ +%{ +#include <unistd.h> + +#include <ctype.h> +#include <errno.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "arg.h" + +#define DIGITS "0123456789ABCDEF" + +int yydebug; + +typedef struct macro Macro; + +struct macro { + char *init; + char *cmp; + char *inc; + char *vars; + char *body; + int n; +}; + +static int yyerror(char *); +static int yylex(void); + +static void quit(void); +static char *code(char *, ...); +static char *forcode(Macro, char *); +static char *whilecode(Macro, char *); +static char *ifcode(Macro, char *); +static char *funcode(Macro, char *, char *); +static Macro macro(char *, char *, char *); +static Macro function(char *, char *); + +static char *ftn(char *); +static char *var(char *); +static char *ary(char *); +static void writeout(char *); + +static size_t used; +static char yytext[BUFSIZ], buff[BUFSIZ]; +static int lineno, nerr; +static jmp_buf recover; +static int nested; +int cflag, dflag, lflag, sflag; + +%} + +%union { + char *str; + char id[2]; + Macro macro; +} + +%token <id> ID +%token <str> STRING NUMBER +%token <str> EQOP '+' '-' '*' '/' '%' '^' INCDEC +%token EOFTOK +%token EQ +%token LE +%token GE +%token NE +%token DEF +%token BREAK +%token QUIT +%token LENGTH +%token RETURN +%token FOR +%token IF +%token WHILE +%token SQRT +%token SCALE +%token IBASE +%token OBASE +%token AUTO + +%type <str> assign nexpr expr exprstat rel stat ary statlst +%type <str> autolst arglst +%type <str> parlst locals local +%type <str> function +%type <macro> fordef cond funbody + +%right '=' EQOP +%left '+' '-' +%left '*' '/' '%' +%right '^' + +%start program + +%% + +program : EOFTOK {quit();} + | item program + ; + +item : scolonlst '\n' {used = 0;} + | function {writeout($1); used = 0;} + ; + +scolonlst: /* empty */ + | stat {writeout($1);} + | scolonlst ';' stat {writeout($3);} + | scolonlst ';' + ; + +statlst : /* empty */ {$$ = code("");} + | stat + | statlst '\n' stat {$$ = code("%s%s", $1, $3);} + | statlst ';' stat {$$ = code("%s%s", $1, $3);} + | statlst '\n' + | statlst ';' + ; + +stat : exprstat + | STRING {$$ = code("[%s]P", $1);} + | BREAK {$$ = code(" %dQ", nested);} + | QUIT {quit();} + | RETURN + | RETURN '(' expr ')' + | RETURN '(' ')' + | FOR fordef stat {$$ = forcode($2, $3);} + | IF cond stat {$$ = ifcode($2, $3);} + | WHILE cond stat {$$ = whilecode($2, $3);} + | '{' statlst '}' {$$ = $2;} + ; + +fordef : '(' expr ';' rel ';' expr ')' {$$ = macro($2, $4, $6);} + ; + +cond : '(' rel ')' {$$ = macro(NULL, $2, NULL);} + ; + +function: DEF ID parlst funbody {$$ = funcode($4, $2, $3);} + ; + +funbody : '{' '\n' autolst statlst '}' {$$ = function($3, $4);} + ; + +parlst : '(' ')' {$$ = "%s";} + | '(' locals ')' {$$ = $2;} + ; + +autolst : /* empty */ {$$ = "%s";} + | AUTO locals '\n' {$$ = $2;} + | AUTO locals ';' {$$ = $2;} + ; + +locals : local + | locals ',' local {$$ = code($1, $3);} + ; + +local : ID {$$ = code("S%s%%sL%ss.", var($1), var($1));} + | ID '[' ']' {$$ = code("S%s%%sL%ss.", ary($1), ary($1));} + ; + +arglst : expr + | ID '[' ']' {$$ = code("%s", ary($1));} + | expr ',' arglst {$$ = code("%s%s", $1, $3);} + | ID '[' ']' ',' arglst {$$ = code("%s%s", ary($1), $5);} + ; + +rel : expr + | expr EQ expr {$$ = code("%s%s=", $3, $1);} + | expr LE expr {$$ = code("%s%s!=", $3, $1);} + | expr GE expr {$$ = code("%s%s!<", $3, $1);} + | expr NE expr {$$ = code("%s%s!=", $3, $1);} + | expr '<' expr {$$ = code("%s%s<", $3, $1);} + | expr '>' expr {$$ = code("%s%s>", $3, $1);} + ; + +exprstat: nexpr {$$ = code("%s%s", $1, sflag ? "s." : "p");} + | assign {$$ = code("%ss.", $1);} + ; + +expr : nexpr + | assign + ; + +nexpr : NUMBER {$$ = code(" %s", $1);} + | ID {$$ = code("l%s", var($1));} + | SCALE {$$ = code("K");} + | IBASE {$$ = code("I");} + | OBASE {$$ = code("O");} + | ID ary {$$ = code("%s;%s", $2, ary($1));} + | '(' expr ')' {$$ = $2;} + | ID '(' arglst ')' {$$ = code("%sl%sx", $3, ftn($1));} + | ID '(' ')' {$$ = code("l%sx", ftn($1));} + | '-' expr {$$ = code("0%s-", $2);} + | expr '+' expr {$$ = code("%s%s+", $1, $3);} + | expr '-' expr {$$ = code("%s%s-", $1, $3);} + | expr '*' expr {$$ = code("%s%s*", $1, $3);} + | expr '/' expr {$$ = code("%s%s/", $1, $3);} + | expr '%' expr {$$ = code("%s%s%", $1, $3);} + | expr '^' expr {$$ = code("%s%s^", $1, $3);} + | LENGTH '(' expr ')' {$$ = code("%sZ", $3);} + | SQRT '(' expr ')' {$$ = code("%sv", $3);} + | SCALE '(' expr ')' {$$ = code("%sX", $3);} + | INCDEC ID {$$ = code("l%s1%sds%s", var($2), $1, var($2));} + | INCDEC SCALE {$$ = code("K1%sk", $1);} + | INCDEC IBASE {$$ = code("I1%sdi", $1);} + | INCDEC OBASE {$$ = code("O1%sdo", $1);} + | INCDEC ID ary {$$ = code("%sdS_;%s1%sdL_:%s", $3, ary($2), $1, ary($2));} + | ID INCDEC {$$ = code("l%sd1%ss%s", var($1), $2, var($1));} + | SCALE INCDEC {$$ = code("Kd1%sk", $2);} + | IBASE INCDEC {$$ = code("Id1%si", $2);} + | OBASE INCDEC {$$ = code("Od1%so", $2);} + | ID ary INCDEC {$$ = code("%sds.;%sd1%sl.:%s", $2, ary($1), $3, ary($1));} + ; + +assign : ID '=' expr {$$ = code("%sds%s", $3, var($1));} + | SCALE '=' expr {$$ = code("%sdk", $3);} + | IBASE '=' expr {$$ = code("%sdi", $3);} + | OBASE '=' expr {$$ = code("%sdo", $3);} + | ID ary '=' expr {$$ = code("%sd%s:%s", $4, $2, ary($1));} + | ID EQOP expr {$$ = code("%sl%s%sds%s", $3, var($1), $2, var($1));} + | SCALE EQOP expr {$$ = code("%sK%sdk", $3, $2);} + | IBASE EQOP expr {$$ = code("%sI%sdi", $3, $2);} + | OBASE EQOP expr {$$ = code("%sO%sdo", $3, $2);} + | ID ary EQOP expr {$$ = code("%s%sds.;%s%sdl.:s", $4, $2, ary($1), $3, ary($1));} + ; + +ary : '[' expr ']' {$$ = $2;} + ; + +%% +static int +yyerror(char *s) +{ + fprintf(stderr, "bc: %s:%d:%s\n", "<stdin>", lineno, s); + nerr++; + longjmp(recover, 1); +} + +static void +writeout(char *s) +{ + if (write(1, s, strlen(s)) < 0) + goto err; + if (write(1, (char[]){'\n'}, 1) < 0) + goto err; + return; + +err: + perror("bc:writing to dc"); + exit(1); +} + +static char * +code(char *fmt, ...) +{ + char *s; + va_list va; + size_t n, room; + + va_start(va, fmt); + room = sizeof(buff) - used; + n = vsnprintf(buff+used, room, fmt, va); + va_end(va); + + if (n < 0 || n >= room) { + fprintf(stderr, "bc: unable to code requested operation\n"); + exit(1); + } + + s = buff + used; + used += n + 1; + + return s; +} + +static char * +funcode(Macro d, char *id, char *params) +{ + char *s; + + s = code(d.vars, d.body); + s = code(params, s); + s = code("[%s]s%s", s, id); + return s; +} + +static char * +forcode(Macro d, char *body) +{ + char *s; + + s = code("[%s%ss.%s%d]s%d", + body, + d.inc, + d.cmp, + d.n, d.n); + writeout(s); + + s = code("%ss.l%dx", d.init, d.n); + nested--; + + return s; +} + +static char * +whilecode(Macro d, char *body) +{ + char *s; + + s = code("[%ss.%s%d]s%d", body, d.cmp, d.n, d.n); + writeout(s); + + s = code("l%dx", d.n); + nested--; + + return s; +} + +static char * +ifcode(Macro d, char *body) +{ + char *s; + + s = code("[%s]s%d", body, d.n); + writeout(s); + + s = code("%s%d", d.cmp, d.n); + nested--; + + return s; +} + +static Macro +macro(char *init, char *cmp, char *inc) +{ + Macro d; + + if (nested == 10) + yyerror("bc:too much nesting"); + + d.init = init; + d.cmp = cmp; + d.inc = inc; + d.n = nested++; + + return d; +} + +static Macro +function(char *vars, char *body) +{ + Macro d; + + if (nested == 10) + yyerror("bc:too much nesting"); + + d.vars = vars; + d.body = body; + d.n = nested++; + + return d; +} + +static char * +ary(char *s) +{ + return code("%c", toupper(s[0])); +} + +static char * +ftn(char *s) +{ + return code("%c", s[0] | 0x80); +} + +static char * +var(char *s) +{ + return code("%s", s); +} + +static void +quit(void) +{ + exit(nerr > 0 ? 1 : 0); +} + +static void +skipspaces(void) +{ + int ch; + + while (isspace(ch = getchar())) { + if (ch == '\n') { + lineno++; + break; + } + } + ungetc(ch, stdin); +} + +static int +iden(int ch) +{ + static struct keyword { + char *str; + int token; + } keywords[] = { + {"define", DEF}, + {"break", BREAK}, + {"quit", QUIT}, + {"length", LENGTH}, + {"return", RETURN}, + {"for", FOR}, + {"if", IF}, + {"while", WHILE}, + {"sqrt", SQRT}, + {"scale", SCALE}, + {"ibase", IBASE}, + {"obase", OBASE}, + {"auto", AUTO}, + {NULL} + }; + struct keyword *p; + char *bp; + + ungetc(ch, stdin); + for (bp = yytext; bp < &yytext[sizeof(yytext)]; ++bp) { + ch = getchar(); + if (!islower(ch)) + break; + *bp = ch; + } + + if (bp == &yytext[sizeof(yytext)]) + yyerror("too long token"); + *bp = '\0'; + ungetc(ch, stdin); + + if (strlen(yytext) == 1) { + strcpy(yylval.id, yytext); + return ID; + } + + for (p = keywords; p->str && strcmp(p->str, yytext); ++p) + ; + + if (!p->str) + yyerror("invalid keyword"); + + return p->token; +} + +static char * +digits(char *bp) +{ + int ch; + char *digits = DIGITS, *p; + + while (bp < &yytext[sizeof(yytext)]) { + ch = getchar(); + p = strchr(digits, ch); + if (!p) + break; + *bp++ = ch; + } + + if (bp == &yytext[sizeof(yytext)]) + return NULL; + ungetc(ch, stdin); + + return bp; +} + +static int +number(int ch) +{ + int d; + char *bp; + + ungetc(ch, stdin); + if ((bp = digits(yytext)) == NULL) + goto toolong; + + if ((ch = getchar()) != '.') { + ungetc(ch, stdin); + goto end; + } + *bp++ = '.'; + + if ((bp = digits(bp)) == NULL) + goto toolong; + +end: + if (bp == &yytext[sizeof(yytext)]) + goto toolong; + *bp = '\0'; + yylval.str = yytext; + + return NUMBER; + +toolong: + yyerror("bc:too long number"); +} + +static int +string(int ch) +{ + char *bp; + + for (bp = yytext; bp < &yytext[sizeof(yytext)]; ++bp) { + if ((ch = getchar()) == '"') + break; + *bp = ch; + } + + if (bp == &yytext[sizeof(yytext)]) + yyerror("bc:too long string"); + *bp = '\0'; + yylval.str = yytext; + + return STRING; +} + +static int +follow(int next, int yes, int no) +{ + int ch; + + ch = getchar(); + if (ch == next) + return yes; + ungetc(ch, stdin); + return no; +} + +static int +operand(int ch) +{ + switch (ch) { + case '\n': + case '{': + case '}': + case '[': + case ']': + case '(': + case ')': + case ',': + case ';': + return ch; + case '"': + return string(ch); + case '*': + yylval.str = "*"; + return follow('=', EQOP, '*'); + case '/': + yylval.str = "/"; + return follow('=', EQOP, '/'); + case '%': + yylval.str = "%"; + return follow('=', EQOP, '%'); + case '=': + return follow('=', EQ, '='); + case '+': + case '-': + yylval.str = (ch == '+') ? "+" : "-"; + if (follow('=', EQOP, ch) != ch) + return EQOP; + return follow(ch, INCDEC, ch); + case '^': + yylval.str = "^"; + return follow('=', EQOP, '^'); + case '<': + return follow('=', LE, '<'); + case '>': + return follow('=', GE, '>'); + case '!': + if (getchar() == '=') + return NE; + default: + yyerror("invalid operand"); + } +} + +static void +comment(void) +{ + do { + while (getchar() != '*') + ; + } while (getchar() != '/'); +} + +static int +yylex(void) +{ + int peekc, ch; + +repeat: + skipspaces(); + + ch = getchar(); + if (ch == EOF) { + return EOFTOK; + } else if (!isascii(ch)) { + yyerror("invalid input character"); + } else if (islower(ch)) { + return iden(ch); + } else if (strchr(DIGITS, ch)) { + return number(ch); + } else { + if (ch == '/') { + peekc = getchar(); + if (peekc == '*') { + comment(); + goto repeat; + } + ungetc(peekc, stdin); + } + return operand(ch); + } +} + +static void +spawn(void) +{ + int fds[2]; + char errmsg[] = "bc:error execing dc\n"; + + if (pipe(fds) < 0) { + perror("bc:creating pipe"); + exit(1); + } + + switch (fork()) { + case -1: + perror("bc:forking dc"); + exit(1); + case 0: + close(1); + dup(fds[1]); + close(fds[0]); + close(fds[1]); + break; + default: + close(0); + dup(fds[0]); + close(fds[0]); + close(fds[1]); + execlp("dc", "dc", (char *) NULL); + + /* it shouldn't happen */ + write(3, errmsg, sizeof(errmsg)-1); + _Exit(2); + } +} + +static void +init(void) +{ +} + +static int +run(void) +{ + if (setjmp(recover)) + return 1; + + yyparse(); + return 1; +} + +static void +usage(void) +{ + fputs("usage: bc [-cdls]\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'c': + cflag = 1; + break; + case 'd': + dflag = 1; + yydebug = 3; + break; + case 'l': + lflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (!cflag) + spawn(); + + for (init(); run(); init()) + ; +}