Last active
July 16, 2020 15:11
-
-
Save telescreen/39a61f01f7b71ddc0a13cb73fd698b3c to your computer and use it in GitHub Desktop.
calc.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdarg.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <stdbool.h> | |
/* clang -Wall -W -g -std=c11 -Wno-missing-field-initializers -pedantic calc.c -o calc */ | |
typedef enum { | |
PLUS, | |
MINUS, | |
TIMES, | |
SLASH, | |
LPAREN, | |
RPAREN, | |
NUMBER, | |
SEMI, | |
EOI, | |
} TokenType; | |
typedef void (*ParseFn)(); | |
void unary(); | |
void binary(); | |
void grouping(); | |
void number(); | |
typedef enum { | |
PREC_NONE, | |
PREC_TERM, | |
PREC_FACTOR, | |
PREC_UNARY, | |
PREC_PRIMARY | |
} Precedence; | |
typedef struct { | |
ParseFn prefix; | |
ParseFn infix; | |
Precedence precedence; | |
} ParseRule; | |
ParseRule rules[] = { | |
[PLUS] = { unary, binary, PREC_TERM }, | |
[MINUS] = { unary, binary, PREC_TERM }, | |
[TIMES] = { NULL, binary, PREC_FACTOR }, | |
[SLASH] = { NULL, binary, PREC_FACTOR }, | |
[LPAREN] = { grouping, NULL, PREC_NONE }, | |
[RPAREN] = { NULL, NULL, PREC_NONE }, | |
[NUMBER] = { number, NULL, PREC_NONE }, | |
[SEMI] = { NULL, NULL, PREC_NONE }, | |
[EOI] = { NULL, NULL, PREC_NONE }, | |
}; | |
typedef struct { | |
char *start; | |
char *current; | |
int line; | |
} Scanner; | |
typedef struct { | |
TokenType type; | |
char* start; | |
int length; | |
int line; | |
} Token; | |
typedef struct { | |
Token current; | |
Token next; | |
} Parser; | |
Scanner scanner; | |
Parser parser; | |
Token makeToken(TokenType type) | |
{ | |
Token token; | |
token.type = type; | |
token.start = scanner.start; | |
token.length = (int)(scanner.current - scanner.start); | |
token.line = scanner.line; | |
return token; | |
} | |
void initScanner(char* buffer) | |
{ | |
scanner.start = buffer; | |
scanner.current = buffer; | |
scanner.line = 1; | |
} | |
void skipWhitespaceNewline() | |
{ | |
/* Skip white space */ | |
while(isblank(*scanner.current)) ++scanner.current; | |
while (*scanner.current == '\n') { | |
++scanner.current; | |
scanner.line++; | |
} | |
} | |
Token nextToken() | |
{ | |
skipWhitespaceNewline(); | |
if (*scanner.current == '\0') return makeToken(EOI); | |
scanner.start = scanner.current; | |
char* c = scanner.current++; | |
if (isdigit(*c)) { | |
while (isdigit(*scanner.current) || *scanner.current == '.') ++scanner.current; | |
return makeToken(NUMBER); | |
} | |
switch(*c) { | |
case '+': return makeToken(PLUS); | |
case '-': return makeToken(MINUS); | |
case '*': return makeToken(TIMES); | |
case '/': return makeToken(SLASH); | |
case '(': return makeToken(LPAREN); | |
case ')': return makeToken(RPAREN); | |
case ';': return makeToken(SEMI); | |
default: | |
fprintf(stderr, "Ignoring illegal input <%c>\n", *c); | |
return makeToken(EOI); | |
} | |
} | |
void errorAt(const Token *token, const char* message) | |
{ | |
fprintf(stderr, "[line %d] Error at %.*s: %s\n", | |
token->line, token->length, token->start, message); | |
} | |
void error(const char* message) | |
{ | |
errorAt(&parser.current, message); | |
} | |
void advance() | |
{ | |
parser.current = parser.next; | |
parser.next = nextToken(); | |
} | |
void consume(TokenType type, const char* message) | |
{ | |
if (parser.next.type == type) { | |
advance(); | |
return; | |
} | |
errorAt(&parser.next, message); | |
} | |
static ParseRule* getRule(TokenType type) | |
{ | |
return &rules[type]; | |
} | |
void parse(Precedence precedence) | |
{ | |
advance(); | |
ParseFn prefixFn = getRule(parser.current.type)->prefix; | |
if (prefixFn == NULL) { | |
error("Expect expression"); | |
return; | |
} | |
prefixFn(); | |
while (precedence <= getRule(parser.next.type)->precedence) { | |
advance(); | |
ParseFn infixFn = getRule(parser.current.type)->infix; | |
infixFn(); | |
} | |
} | |
void expression() | |
{ | |
parse(PREC_TERM); | |
} | |
void unary() | |
{ | |
TokenType oprand = parser.current.type; | |
parse(PREC_UNARY); | |
switch(oprand) { | |
case MINUS: | |
case PLUS: | |
printf("Binary operator: %d\n", oprand); break; | |
default: | |
return; | |
} | |
} | |
void binary() | |
{ | |
TokenType operand = parser.current.type; | |
// Compile the right operand | |
parse(getRule(operand)->precedence + 1); | |
switch (operand) { | |
case PLUS: | |
case MINUS: | |
case TIMES: | |
case SLASH: printf("Binary operator: %d\n", operand); break; | |
default: | |
return; | |
} | |
} | |
void grouping() | |
{ | |
expression(); | |
consume(RPAREN, "Expect ')' after an expression."); | |
} | |
void number() | |
{ | |
double value = strtod(parser.current.start, NULL); | |
printf("%.4f\n", value); | |
} | |
int main() | |
{ | |
char buffer[1024]; | |
if (fgets(buffer, 1024, stdin)) { | |
initScanner(buffer); | |
advance(); | |
expression(); | |
consume(EOI, "Expect end of expression"); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment