pratt: add parser
This is based on C's operator precedence rules
This commit is contained in:
commit
83c75de773
22
Makefile
Normal file
22
Makefile
Normal file
|
@ -0,0 +1,22 @@
|
|||
CC = gcc
|
||||
CPPFLAGS = -Isrc/ -D_POSIX_C_SOURCE=200809L
|
||||
VPATH = src/
|
||||
CFLAGS = -Wall -Wextra -pedantic -Werror -std=c99
|
||||
USE_CLIMBING = 1
|
||||
|
||||
SRC = \
|
||||
src/eval.c \
|
||||
|
||||
BIN = pratt
|
||||
OBJ = $(SRC:.c=.o)
|
||||
|
||||
.PHONY: all
|
||||
all: $(BIN)
|
||||
|
||||
$(BIN):
|
||||
$(BIN): $(OBJ) src/pratt.o
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(RM) $(OBJ) # remove object files
|
||||
$(RM) $(BIN) # remove main program
|
269
src/eval.c
Normal file
269
src/eval.c
Normal file
|
@ -0,0 +1,269 @@
|
|||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define UNREACHABLE() __builtin_unreachable()
|
||||
#define ARR_SIZE(Arr) (sizeof(Arr) / sizeof(*Arr))
|
||||
#define OP_STRING(...) (const char[]){__VA_ARGS__}
|
||||
#define OP_SIZE(...) (sizeof(OP_STRING(__VA_ARGS__)) - 1)
|
||||
|
||||
typedef bool (nul_f)(const char **input, int *res, int until);
|
||||
typedef bool (left_f)(int lhs, const char **input, int *res, int until);
|
||||
|
||||
// Define the different tokens
|
||||
enum token_kind {
|
||||
#define OP(Name, ...) Name,
|
||||
#include "operators.inc"
|
||||
};
|
||||
|
||||
struct token {
|
||||
enum token_kind kind;
|
||||
int val; // Used for NUMBER
|
||||
};
|
||||
|
||||
// Forward declare functions
|
||||
#define NulFunc(Name) \
|
||||
static nul_f eval_ ## Name ## _nul;
|
||||
#define LeftFunc(Name) \
|
||||
static left_f eval_ ## Name ## _left;
|
||||
|
||||
#define PREFIX_POSTFIX(Name) \
|
||||
NulFunc(Name) \
|
||||
LeftFunc(Name)
|
||||
#define PREFIX(Name) \
|
||||
NulFunc(Name)
|
||||
#define PREFIX_INFIX(Name) \
|
||||
NulFunc(Name) \
|
||||
LeftFunc(Name)
|
||||
#define INFIX(Name) \
|
||||
LeftFunc(Name)
|
||||
#define TERN(Name) \
|
||||
LeftFunc(Name)
|
||||
#define PAREN(Name) \
|
||||
NulFunc(Name)
|
||||
#define NOT_OP(Name)
|
||||
# define OP(Name, NulPrio, LeftPrio, Type, ...) \
|
||||
Type(Name)
|
||||
#include "operators.inc"
|
||||
|
||||
#undef NulFunc
|
||||
#undef LeftFunc
|
||||
|
||||
// Symbol table
|
||||
static const struct {
|
||||
const char *op;
|
||||
const size_t op_len;
|
||||
const int nul_prio;
|
||||
const int left_prio;
|
||||
nul_f *const nul_func;
|
||||
left_f *const left_func;
|
||||
} ops[] = {
|
||||
#define NulFunc(Name) \
|
||||
eval_ ## Name ## _nul
|
||||
#define LeftFunc(Name) \
|
||||
eval_ ## Name ## _left
|
||||
|
||||
#define PREFIX_POSTFIX(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NulFunc(Name), LeftFunc(Name), },
|
||||
#define PREFIX(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NulFunc(Name), NULL, },
|
||||
#define PREFIX_INFIX(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NulFunc(Name), LeftFunc(Name), },
|
||||
#define INFIX(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NULL, LeftFunc(Name), },
|
||||
#define TERN(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NULL, LeftFunc(Name), },
|
||||
#define PAREN(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NulFunc(Name), NULL, },
|
||||
#define NOT_OP(Name, NulPrio, LeftPrio, ...) \
|
||||
[Name] = {OP_STRING(__VA_ARGS__), OP_SIZE(__VA_ARGS__), NulPrio, LeftPrio, NULL, NULL, },
|
||||
# define OP(Name, NulPrio, LeftPrio, Type, Operator, /* Operator String */ ...) \
|
||||
Type(Name, NulPrio, LeftPrio, __VA_ARGS__)
|
||||
#include "operators.inc"
|
||||
|
||||
#undef LeftFunc
|
||||
#undef NulFunc
|
||||
};
|
||||
|
||||
// Lexing functions
|
||||
static void skip_whitespace(const char **input) {
|
||||
while (*input[0] && isspace(*input[0]))
|
||||
*input += 1; // Skip this character
|
||||
}
|
||||
|
||||
static bool expect(enum token_kind expected, const char **input) {
|
||||
skip_whitespace(input);
|
||||
|
||||
if (strncmp(*input, ops[expected].op, ops[expected].op_len) != 0)
|
||||
return false;
|
||||
|
||||
*input += ops[expected].op_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t my_atoi(const char **input, int *val) {
|
||||
size_t len = 0;
|
||||
|
||||
*val = 0; // Initialize its value
|
||||
while (isdigit((*input)[len]))
|
||||
{
|
||||
*val *= 10;
|
||||
*val += (*input)[len] - '0';
|
||||
len += 1;
|
||||
};
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t lex_operator(const char **input, enum token_kind *op) {
|
||||
size_t best_len = 0;
|
||||
|
||||
for (size_t i = 0; i < ARR_SIZE(ops); ++i)
|
||||
{
|
||||
if (ops[i].op_len <= best_len) // Only look at longer operators
|
||||
continue;
|
||||
if (strncmp(*input, ops[i].op, ops[i].op_len) == 0)
|
||||
{
|
||||
best_len = ops[i].op_len;
|
||||
*op = i;
|
||||
}
|
||||
}
|
||||
|
||||
return best_len;
|
||||
}
|
||||
|
||||
static size_t lex_token(const char **input, struct token *tok) {
|
||||
skip_whitespace(input);
|
||||
|
||||
size_t len;
|
||||
|
||||
if ((len = lex_operator(input, &tok->kind)))
|
||||
return len;
|
||||
|
||||
// Assume that it is a number
|
||||
tok->kind = NUMBER;
|
||||
return my_atoi(input, &tok->val);
|
||||
}
|
||||
|
||||
static bool parse_until_left(int lhs, const char **input, int *res, int prio) {
|
||||
struct token tok;
|
||||
size_t len = 0;
|
||||
|
||||
while ((len = lex_token(input, &tok)) &&
|
||||
prio < ops[tok.kind].left_prio) {
|
||||
*input += len;
|
||||
left_f *left_func = ops[tok.kind].left_func;
|
||||
if (!left_func)
|
||||
return false; // Error: not a prefix operator
|
||||
if (!left_func(lhs, input, res, prio))
|
||||
return false; // Error: could not parse right-hand-side
|
||||
lhs = *res;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool parse_until(const char **input, int *res, int prio) {
|
||||
struct token tok;
|
||||
size_t len = 0;
|
||||
|
||||
// Parse left token
|
||||
if (!(len = lex_token(input, &tok)))
|
||||
return false;
|
||||
|
||||
// Assume prefix
|
||||
*input += len;
|
||||
if (tok.kind == NUMBER) {
|
||||
*res = tok.val;
|
||||
} else {
|
||||
nul_f *nul_func = ops[tok.kind].nul_func;
|
||||
if (!nul_func)
|
||||
return false; // Error: not a prefix operator
|
||||
nul_func(input, res, prio);
|
||||
}
|
||||
|
||||
// Do left-loop
|
||||
return parse_until_left(*res, input, res, prio);
|
||||
}
|
||||
|
||||
bool eval_string(const char *input, int *res) {
|
||||
if (!parse_until(&input, res, 0))
|
||||
return false;
|
||||
// Verify that only the expression exists
|
||||
skip_whitespace(&input);
|
||||
return *input == '\0';
|
||||
}
|
||||
|
||||
#define NulFunc(Name) \
|
||||
static bool eval_ ## Name ## _nul( \
|
||||
const char **input, \
|
||||
int *res, \
|
||||
__attribute__((unused)) int until)
|
||||
#define LeftFunc(Name) \
|
||||
static bool eval_ ## Name ## _left( \
|
||||
int lhs, \
|
||||
const char **input, \
|
||||
int *res, \
|
||||
__attribute__((unused)) int until)
|
||||
|
||||
#define PREFIX_POSTFIX(Name, NulPrio, LeftPrio, Operator) \
|
||||
LeftFunc(Name) { \
|
||||
return parse_until_left(lhs Operator, input, res, LeftPrio); \
|
||||
} \
|
||||
NulFunc(Name) { \
|
||||
if (!parse_until(input, res, NulPrio)) \
|
||||
return false; \
|
||||
Operator *res; \
|
||||
return true; \
|
||||
}
|
||||
#define PREFIX(Name, NulPrio, LeftPrio, Operator) \
|
||||
NulFunc(Name) { \
|
||||
if (!parse_until(input, res, NulPrio)) \
|
||||
return false; \
|
||||
*res = Operator *res; \
|
||||
return true; \
|
||||
}
|
||||
#define PREFIX_INFIX(Name, NulPrio, LeftPrio, Operator) \
|
||||
LeftFunc(Name) { \
|
||||
if (!parse_until(input, res, LeftPrio)) \
|
||||
return false; \
|
||||
*res = lhs Operator *res; \
|
||||
return true; \
|
||||
} \
|
||||
NulFunc(Name) { \
|
||||
if (!parse_until(input, res, NulPrio)) \
|
||||
return false; \
|
||||
*res = Operator *res; \
|
||||
return true; \
|
||||
}
|
||||
#define INFIX(Name, NulPrio, LeftPrio, Operator) \
|
||||
LeftFunc(Name) { \
|
||||
if (!parse_until(input, res, LeftPrio)) \
|
||||
return false; \
|
||||
*res = lhs Operator *res; \
|
||||
return true; \
|
||||
}
|
||||
#define TERN(Name, NulPrio, LeftPrio, Operator) \
|
||||
LeftFunc(Name) { \
|
||||
int true_val; \
|
||||
if (!parse_until(input, &true_val, 0) || !expect(COLON, input)) \
|
||||
return false; \
|
||||
int false_val; \
|
||||
if (!parse_until(input, &false_val, until)) \
|
||||
return false; \
|
||||
*res = (lhs ? true_val : false_val); \
|
||||
return true; \
|
||||
}
|
||||
#define PAREN(Name, NulPrio, LeftPrio, Operator) \
|
||||
NulFunc(Name) { \
|
||||
if (!parse_until(input, res, 0)) \
|
||||
return false; \
|
||||
return expect(R_PAREN, input); \
|
||||
}
|
||||
#define NOT_OP(Name, NulPrio, LeftPrio, Operator) /* Nothing */
|
||||
# define OP(Name, NulPrio, LeftPrio, Type, Operator, ...) \
|
||||
Type(Name, NulPrio, LeftPrio, Operator)
|
||||
#include "operators.inc"
|
||||
|
||||
#undef LeftFunc
|
||||
#undef NulFunc
|
8
src/eval.h
Normal file
8
src/eval.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef ONCE_H
|
||||
#define ONCE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool eval_string(const char *input, int *res);
|
||||
|
||||
#endif /* !ONCE_H */
|
45
src/operators.inc
Normal file
45
src/operators.inc
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 10 '?:' ternary (set to 0 in middle)
|
||||
*
|
||||
* Do not use any assignation yet
|
||||
*/
|
||||
#ifndef OP
|
||||
# define OP(Name, NulPrio, LeftPrio, Type, Operator, /* Operator String */ ...)
|
||||
#endif
|
||||
|
||||
// Simple operators
|
||||
OP( PLUS_PLUS, 120, 110, PREFIX_POSTFIX, ++, '+', '+', 0)
|
||||
OP(MINUS_MINUS, 120, 110, PREFIX_POSTFIX, --, '-', '-', 0)
|
||||
OP( NOT, 120, -1, PREFIX, !, '!', 0)
|
||||
OP( BIT_NOT, 120, -1, PREFIX, ~, '~', 0)
|
||||
OP( PLUS, 110, 90, PREFIX_INFIX, +, '+', 0)
|
||||
OP( MINUS, 110, 90, PREFIX_INFIX, -, '-', 0)
|
||||
OP( TIMES, -1, 100, INFIX, *, '*', 0)
|
||||
OP( DIVIDES, -1, 100, INFIX, /, '/', 0)
|
||||
OP( MODULO, -1, 100, INFIX, %, '%', 0)
|
||||
OP( LEFT_SHIFT, -1, 80, INFIX, <<, '<', '<', 0)
|
||||
OP( RIGHT_SHIFT, -1, 80, INFIX, >>, '>', '>', 0)
|
||||
OP( EQUAL, -1, 70, INFIX, ==, '=', '=', 0)
|
||||
OP( NOT_EQUAL, -1, 70, INFIX, !=, '!', '=', 0)
|
||||
OP( BIT_AND, -1, 60, INFIX, &, '&', 0)
|
||||
OP( BIT_XOR, -1, 50, INFIX, ^, '^', 0)
|
||||
OP( BIT_OR, -1, 40, INFIX, |, '|', 0)
|
||||
OP( AND, -1, 30, INFIX, &&, '&', '&', 0)
|
||||
OP( OR, -1, 20, INFIX, ||, '|', '|', 0)
|
||||
// Special operators
|
||||
OP( TERNARY, -1, 10, TERN, PLACEHOLDER, '?', 0)
|
||||
OP( COLON, -1, -1, NOT_OP, PLACEHOLDER, ':', 0)
|
||||
OP( L_PAREN, 0, 0, PAREN, PLACEHOLDER, '(', 0)
|
||||
OP( R_PAREN, -1, -1, NOT_OP, PLACEHOLDER, ')', 0)
|
||||
// Special tokens
|
||||
OP( NUMBER, 0, -1, NOT_OP, PLACEHOLDER, 0)
|
||||
|
||||
#undef OP
|
||||
|
||||
#undef PREFIX_POSTFIX
|
||||
#undef PREFIX
|
||||
#undef PREFIX_INFIX
|
||||
#undef INFIX
|
||||
#undef TERN
|
||||
#undef PAREN
|
||||
#undef NOT_OP
|
28
src/pratt.c
Normal file
28
src/pratt.c
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "eval.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char *line = NULL;
|
||||
size_t size = 0;
|
||||
ssize_t ret = 0;
|
||||
|
||||
while ((getline(&line, &size, stdin)) > 0)
|
||||
{
|
||||
int res;
|
||||
if (!eval_string(line, &res))
|
||||
{
|
||||
fputs("Could not parse input\n", stderr);
|
||||
ret = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("%d\n", res);
|
||||
}
|
||||
|
||||
free(line);
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in a new issue