C++23, rusty error handling, renamed output repr of tokens
This commit is contained in:
parent
32e76fb704
commit
6fd95bd486
9 changed files with 144 additions and 130 deletions
|
|
@ -3,7 +3,7 @@
|
|||
"arguments": [
|
||||
"/usr/bin/clang++",
|
||||
"-c",
|
||||
"-std=c++17",
|
||||
"-std=c++23",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-pedantic",
|
||||
|
|
@ -15,56 +15,5 @@
|
|||
"directory": "/Users/Shautvast/dev/clox",
|
||||
"file": "/Users/Shautvast/dev/clox/src/lox.cpp",
|
||||
"output": "/Users/Shautvast/dev/clox/target/lox.cpp.o"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
"/usr/bin/clang++",
|
||||
"-c",
|
||||
"-std=c++17",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-pedantic",
|
||||
"-Werror",
|
||||
"-o",
|
||||
"target/parser.cpp.o",
|
||||
"src/parser.cpp"
|
||||
],
|
||||
"directory": "/Users/Shautvast/dev/clox",
|
||||
"file": "/Users/Shautvast/dev/clox/src/parser.cpp",
|
||||
"output": "/Users/Shautvast/dev/clox/target/parser.cpp.o"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
"/usr/bin/clang++",
|
||||
"-c",
|
||||
"-std=c++17",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-pedantic",
|
||||
"-Werror",
|
||||
"-o",
|
||||
"target/scanner.cpp.o",
|
||||
"src/scanner.cpp"
|
||||
],
|
||||
"directory": "/Users/Shautvast/dev/clox",
|
||||
"file": "/Users/Shautvast/dev/clox/src/scanner.cpp",
|
||||
"output": "/Users/Shautvast/dev/clox/target/scanner.cpp.o"
|
||||
},
|
||||
{
|
||||
"arguments": [
|
||||
"/usr/bin/clang++",
|
||||
"-c",
|
||||
"-std=c++17",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-pedantic",
|
||||
"-Werror",
|
||||
"-o",
|
||||
"target/tokens.cpp.o",
|
||||
"src/tokens.cpp"
|
||||
],
|
||||
"directory": "/Users/Shautvast/dev/clox",
|
||||
"file": "/Users/Shautvast/dev/clox/src/tokens.cpp",
|
||||
"output": "/Users/Shautvast/dev/clox/target/tokens.cpp.o"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
2
makefile
2
makefile
|
|
@ -1,6 +1,6 @@
|
|||
TARGET := ./target
|
||||
SRC := ./src
|
||||
CC := clang++ -c -std=c++17 -Wall -Wextra -pedantic -Werror
|
||||
CC := clang++ -c -std=c++23 -Wall -Wextra -pedantic -Werror
|
||||
|
||||
SRCS := $(shell find $(SRC) -name '*.c')
|
||||
OBJS := $(SRCS:%=$(TARGET)/%.o)
|
||||
|
|
|
|||
31
src/error.hpp
Normal file
31
src/error.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Error {
|
||||
|
||||
public:
|
||||
string message;
|
||||
Error(string _message) : message(_message){};
|
||||
};
|
||||
|
||||
class Void {};
|
||||
|
||||
template <typename R> using Result = std::variant<R, Error>;
|
||||
|
||||
template <typename R> bool is_err(Result<R> r) {
|
||||
return std::holds_alternative<Error>(r);
|
||||
}
|
||||
template <typename R> bool is_ok(Result<R> r) {
|
||||
return std::holds_alternative<R>(r);
|
||||
}
|
||||
|
||||
template <typename R> R Ok(Result<R> r) { return std::get<R>(r); }
|
||||
/// enables rewrapping errors in a new Result type
|
||||
template <typename R> Error Err(Result<R> r) { return std::get<Error>(r); }
|
||||
template <typename R> string err_msg(Result<R> r) {
|
||||
return std::get<Error>(r).message;
|
||||
}
|
||||
21
src/lox.cpp
21
src/lox.cpp
|
|
@ -12,7 +12,7 @@ using namespace std;
|
|||
void print_tokens(vector<Token> *list);
|
||||
int run_file(string file);
|
||||
void run_prompt(void);
|
||||
ScanResult run(string source);
|
||||
Result<vector<Token>> run(string source);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc > 2) {
|
||||
|
|
@ -36,7 +36,7 @@ int run_file(string filename) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
ScanResult scan_result = run(content);
|
||||
Result<vector<Token>> scan_result = run(content);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -49,17 +49,22 @@ void run_prompt(void) {
|
|||
|
||||
getline(cin, line);
|
||||
|
||||
ScanResult scan_result = run(line.substr(0, line.length()));
|
||||
auto scan_result = run(line.substr(0, line.length()));
|
||||
// print_tokens(&scan_result.token_list);
|
||||
if (!scan_result.had_error) {
|
||||
Expression *e = (new Parser())->parse(scan_result.token_list);
|
||||
cout << e->as_string();
|
||||
cout << "\n";
|
||||
if (is_ok(scan_result)) {
|
||||
auto expression = (new Parser())->parse(get<vector<Token>>(scan_result));
|
||||
if (is_ok(expression)) {
|
||||
cout << Ok(expression)->as_string() << "\n";
|
||||
} else {
|
||||
cout << err_msg(expression) << "\n";
|
||||
}
|
||||
} else {
|
||||
cout << err_msg(scan_result) << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScanResult run(string source) {
|
||||
Result<vector<Token>> run(string source) {
|
||||
Scanner *scanner = new Scanner(source);
|
||||
return scanner->scan_tokens();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "parser.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ string Literal::as_string() {
|
|||
}
|
||||
|
||||
// class Parser
|
||||
Expression *Parser::parse(vector<Token> tokenlist) {
|
||||
Result<Expression *> Parser::parse(vector<Token> tokenlist) {
|
||||
tokens = tokenlist;
|
||||
current_token = 0;
|
||||
return expression();
|
||||
|
|
@ -101,25 +102,25 @@ bool Parser::match(int count, ...) {
|
|||
return false;
|
||||
};
|
||||
|
||||
Token *Parser::consume(Token::Type typ, string message) {
|
||||
Result<Token *> Parser::consume(Token::Type typ, string message) {
|
||||
if (check(typ)) {
|
||||
return advance();
|
||||
}
|
||||
throw error(peek(), message);
|
||||
return error(peek(), message);
|
||||
}
|
||||
|
||||
runtime_error Parser::error(Token token, string message) {
|
||||
Error Parser::error(Token token, string message) {
|
||||
std::cout << token.as_string() << " " << message;
|
||||
return runtime_error(message); // TODO no exceptions
|
||||
return Error(message); // TODO no exceptions
|
||||
}
|
||||
|
||||
Expression *Parser::primary() {
|
||||
Result<Expression *> Parser::primary() {
|
||||
if (match(1, Token::Type::FALSE))
|
||||
return new Literal(false);
|
||||
if (match(1, Token::Type::TRUE))
|
||||
return new Literal(true);
|
||||
if (match(1, Token::Type::NIL))
|
||||
return new Literal(new Void());
|
||||
return new Literal(new NilType());
|
||||
if (match(1, Token::Type::NUMBER)) {
|
||||
return new Literal(stod(previous()->literal));
|
||||
}
|
||||
|
|
@ -127,62 +128,90 @@ Expression *Parser::primary() {
|
|||
return new Literal(previous()->literal);
|
||||
}
|
||||
if (match(1, Token::Type::LEFT_PAREN)) {
|
||||
Expression *expr = expression();
|
||||
consume(Token::Type::RIGHT_PAREN, "Expect ')'.");
|
||||
return new Grouping(expr);
|
||||
auto expr = expression();
|
||||
Result<Token *> r = consume(Token::Type::RIGHT_PAREN, "Expect ')'.");
|
||||
if (is_err(r)) {
|
||||
return Err(r);
|
||||
}
|
||||
return new Grouping(Ok(expr));
|
||||
}
|
||||
throw runtime_error("Expected an expression");
|
||||
return Error("Expected an expression");
|
||||
}
|
||||
|
||||
Expression *Parser::unary() {
|
||||
Result<Expression *> Parser::unary() {
|
||||
if (match(2, Token::BANG, Token::Type::MINUS)) {
|
||||
Token *op = previous();
|
||||
Expression *right = unary();
|
||||
return new Unary(op, right);
|
||||
Result<Expression *> right = unary();
|
||||
if (is_ok(right)) {
|
||||
return new Unary(op, Ok(right));
|
||||
}
|
||||
}
|
||||
return primary();
|
||||
}
|
||||
|
||||
Expression *Parser::expression() { return equality(); }
|
||||
Result<Expression *> Parser::expression() { return equality(); }
|
||||
|
||||
Expression *Parser::factor() {
|
||||
Expression *expr = unary();
|
||||
Result<Expression *> Parser::factor() {
|
||||
Result<Expression *> expr = unary();
|
||||
if (is_err(expr)) {
|
||||
return expr;
|
||||
}
|
||||
while (match(2, Token::Type::SLASH, Token::Type::STAR)) {
|
||||
Token *op = previous();
|
||||
Expression *right = unary();
|
||||
expr = new Binary(expr, op, right);
|
||||
auto right = unary();
|
||||
if (is_err(right)) {
|
||||
return right;
|
||||
}
|
||||
expr = new Binary(Ok(expr), op, Ok(right));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
Expression *Parser::term() {
|
||||
Expression *expr = factor();
|
||||
Result<Expression *> Parser::term() {
|
||||
auto expr = factor();
|
||||
if (is_err(expr)) {
|
||||
return expr;
|
||||
}
|
||||
while (match(2, Token::Type::MINUS, Token::Type::PLUS)) {
|
||||
Token *op = previous();
|
||||
Expression *right = factor();
|
||||
expr = new Binary(expr, op, right);
|
||||
auto right = factor();
|
||||
if (is_err(right)) {
|
||||
return right;
|
||||
}
|
||||
expr = new Binary(Ok(expr), op, Ok(right));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
Expression *Parser::equality(void) {
|
||||
Expression *expr = comparison();
|
||||
|
||||
Result<Expression *> Parser::equality(void) {
|
||||
auto expr = comparison();
|
||||
if (is_err(expr)) {
|
||||
return expr;
|
||||
}
|
||||
while (match(2, Token::Type::BANG_EQUAL, Token::Type::BANG_EQUAL)) {
|
||||
Token *op = previous();
|
||||
Expression *right = comparison();
|
||||
return new Binary(expr, op, right);
|
||||
auto right = comparison();
|
||||
if (is_err(right)) {
|
||||
return right;
|
||||
}
|
||||
return new Binary(Ok(expr), op, Ok(right));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
Expression *Parser::comparison(void) {
|
||||
Expression *expr = term();
|
||||
Result<Expression *> Parser::comparison(void) {
|
||||
auto expr = term();
|
||||
if (is_err(expr)) {
|
||||
return expr;
|
||||
}
|
||||
while (match(4, Token::Type::GREATER, Token::Type::GREATER_EQUAL,
|
||||
Token::Type::LESS, Token::Type::LESS_EQUAL)) {
|
||||
Token *op = previous();
|
||||
Expression *right = term();
|
||||
expr = new Binary(expr, op, right);
|
||||
auto right = term();
|
||||
if (is_err(right)) {
|
||||
return right;
|
||||
}
|
||||
expr = new Binary(Ok(expr), op, Ok(right));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "error.hpp"
|
||||
#include "tokens.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
|
@ -53,7 +54,7 @@ public:
|
|||
};
|
||||
|
||||
/// empty class that is the type of the Nil value
|
||||
class Void {};
|
||||
class NilType {};
|
||||
|
||||
/// encapsulates a value: numeric, string etc
|
||||
class Literal : public Expression {
|
||||
|
|
@ -66,16 +67,16 @@ public:
|
|||
double_t numeric;
|
||||
bool boolean;
|
||||
string str;
|
||||
Void dummy;
|
||||
NilType dummy;
|
||||
|
||||
Value(double_t _numeric) : numeric(_numeric) {}
|
||||
Value(bool _boolean) : boolean(_boolean) {}
|
||||
Value(string _str) : str(_str) {}
|
||||
Value(Void v) : dummy(v) {}
|
||||
Value(NilType v) : dummy(v) {}
|
||||
~Value() {}
|
||||
} value;
|
||||
|
||||
Literal(Void v) : valuetype(ValueType::Nil), value(v){};
|
||||
Literal(NilType v) : valuetype(ValueType::Nil), value(v){};
|
||||
Literal(double_t _numeric) : valuetype(ValueType::Numeric), value(_numeric){};
|
||||
Literal(string _str) : valuetype(ValueType::String), value(_str){};
|
||||
Literal(bool _boolean) : valuetype(ValueType::Boolean), value(_boolean){};
|
||||
|
|
@ -106,26 +107,26 @@ class Parser {
|
|||
/// checks if the current token is of the specified type and
|
||||
/// moves the token forward if so, otherwise throws an exception with
|
||||
/// the specified message
|
||||
Token *consume(Token::Type typ, string message);
|
||||
Result<Token *> consume(Token::Type typ, string message);
|
||||
/// throws an exception for the specified token with the specified message
|
||||
runtime_error error(Token token, string message);
|
||||
Error error(Token token, string message);
|
||||
/// tries to parse the token as a primary value (string, number etc)
|
||||
Expression *primary();
|
||||
Result<Expression *> primary();
|
||||
/// tries to parse the tokens as a unary expression
|
||||
Expression *unary();
|
||||
Result<Expression *> unary();
|
||||
/// tries to parse the tokens
|
||||
Expression *expression();
|
||||
Result<Expression *> expression();
|
||||
/// tries to parse the tokens as a multiplication or division
|
||||
Expression *factor();
|
||||
Result<Expression *> factor();
|
||||
/// tries to parse the tokens as an addition or subtraction
|
||||
Expression *term();
|
||||
Result<Expression *> term();
|
||||
/// tries to parse the tokens as an equality (`a == b` / `a!= b`)
|
||||
Expression *equality(void);
|
||||
Result<Expression *> equality();
|
||||
/// tries to parse the tokens as a comparison (`a > b` / `a >= b` / `a < b` /
|
||||
/// `a <= b` )
|
||||
Expression *comparison(void);
|
||||
Result<Expression *> comparison();
|
||||
|
||||
public:
|
||||
/// public method for parsing expressions
|
||||
Expression *parse(vector<Token> tokenlist);
|
||||
Result<Expression *> parse(vector<Token> tokenlist);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "scanner.hpp"
|
||||
#include "error.hpp"
|
||||
#include "tokens.hpp"
|
||||
#include <cstdbool>
|
||||
#include <iostream>
|
||||
|
|
@ -23,17 +24,15 @@ Scanner::Scanner(string _source)
|
|||
: had_error(false), current_pos(0), start(0), current_line(1),
|
||||
source(_source), token_list(vector<Token>()) {}
|
||||
|
||||
ScanResult Scanner::scan_tokens() {
|
||||
Result<vector<Token>> Scanner::scan_tokens() {
|
||||
while (current_pos < source.length()) {
|
||||
start = current_pos;
|
||||
scan_token();
|
||||
Result<Void> r = scan_token();
|
||||
if (is_err(r)) {
|
||||
return Err(r);
|
||||
}
|
||||
}
|
||||
|
||||
ScanResult scan_result;
|
||||
scan_result.token_list = token_list;
|
||||
scan_result.had_error = had_error;
|
||||
|
||||
return scan_result;
|
||||
return token_list;
|
||||
}
|
||||
|
||||
void Scanner::add_token(Token::Type type) {
|
||||
|
|
@ -51,7 +50,7 @@ char Scanner::advance() {
|
|||
return c;
|
||||
}
|
||||
|
||||
void Scanner::scan_token() {
|
||||
Result<Void> Scanner::scan_token() {
|
||||
char c = advance();
|
||||
|
||||
switch (c) {
|
||||
|
|
@ -119,10 +118,11 @@ void Scanner::scan_token() {
|
|||
} else if (is_alpha(c)) {
|
||||
identifier();
|
||||
} else {
|
||||
error("Unexpected character.");
|
||||
return Error{"Unexpected character."};
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Void{};
|
||||
}
|
||||
|
||||
void Scanner::identifier() {
|
||||
|
|
@ -159,7 +159,7 @@ void Scanner::scan_string() {
|
|||
}
|
||||
|
||||
if (is_at_end()) {
|
||||
error("Unterminated string.");
|
||||
report("Unterminated string.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ bool Scanner::is_alphanumeric(char c) { return is_alpha(c) || is_digit(c); }
|
|||
|
||||
bool Scanner::is_at_end(void) { return current_pos >= source.length(); }
|
||||
|
||||
void Scanner::error(string message) { report("", message); }
|
||||
void Scanner::report(string message) { report("", message); }
|
||||
|
||||
void Scanner::report(string where, std::string message) {
|
||||
cout << "*[Line " << current_line << "] Error " << where << " : " << message
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "error.hpp"
|
||||
#include "tokens.hpp"
|
||||
#include <cstdbool>
|
||||
#include <string>
|
||||
|
|
@ -23,11 +24,11 @@ private:
|
|||
|
||||
public:
|
||||
Scanner(string s);
|
||||
ScanResult scan_tokens();
|
||||
Result<vector<Token>> scan_tokens();
|
||||
Result<Void> scan_token();
|
||||
void add_token(Token::Type type);
|
||||
void add_token(Token::Type type, string literal);
|
||||
char advance();
|
||||
void scan_token();
|
||||
void identifier();
|
||||
void number();
|
||||
bool is_digit(char c);
|
||||
|
|
@ -38,6 +39,6 @@ public:
|
|||
bool is_alpha(char c);
|
||||
bool is_alphanumeric(char c);
|
||||
bool is_at_end(void);
|
||||
void error(string message);
|
||||
void report(string message);
|
||||
void report(string where, string message);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ Token::Token(Token::Type _tokentype, string _lexeme, string _literal, int _line)
|
|||
|
||||
string token_name(Token::Type tokentype) {
|
||||
static const std::string tokens[] = {
|
||||
"END_OF_FILE", "LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE",
|
||||
"COMMA", "DOT", "MINUS", "PLUS", "SEMICOLON",
|
||||
"SLASH", "STAR", "BANG", "BANG_EQUAL", "EQUAL",
|
||||
"EQUAL_EQUAL", "GREATER", "GREATER_EQUAL", "LESS", "LESS_EQUAL",
|
||||
"IDENTIFIER", "STRING", "NUMBER", "AND", "CLASS",
|
||||
"ELSE", "FALSE", "FUN", "FOR", "IF",
|
||||
"NIL", "OR", "PRINT", "RETURN", "SUPER",
|
||||
"THIS", "TRUE", "VAR", "WHILE"};
|
||||
"EOF", "(", ")", "{", "}", ",", "*",
|
||||
"-", "+", ";", "/", "*", "!", "!=",
|
||||
"=", "==", ">", ">=", "<", "<=", "IDENTIFIER",
|
||||
"string", "number", "and", "class", "else", "false", "fun",
|
||||
"for", "if", "Nil", "or", "print", "return", "super",
|
||||
"this", "true", "var", "while"};
|
||||
return tokens[(int)tokentype];
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue