Initial parser attempt added - seems to parse correctly
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful
This commit is contained in:
@@ -9,8 +9,8 @@ set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
||||
set(HEADER_FILES src/include/lex.hpp)
|
||||
set(SOURCE_FILES src/lex.cpp)
|
||||
set(HEADER_FILES src/include/lex.hpp src/include/value.hpp src/include/parse.hpp)
|
||||
set(SOURCE_FILES src/lex.cpp src/parse.cpp src/value.cpp)
|
||||
set(CXX_WARNING_FLAGS -Wall -Wextra -Wpedantic -pedantic)
|
||||
|
||||
# we're not actually shipping a library yet,
|
||||
|
@@ -1,12 +1,11 @@
|
||||
#pragma once
|
||||
#include <deque>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
|
||||
enum TokenType {
|
||||
enum class TokenType {
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
Dollar,
|
||||
|
32
src/include/parse.hpp
Normal file
32
src/include/parse.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <value.hpp>
|
||||
#include <lex.hpp>
|
||||
|
||||
|
||||
|
||||
|
||||
// The Parser produces a regular lisp value.
|
||||
// lisp code is made of lisp lists and atoms.
|
||||
class Parser {
|
||||
private:
|
||||
// the token stream.
|
||||
std::deque<Token> ts;
|
||||
Token get_token();
|
||||
void unget_token(Token);
|
||||
|
||||
// these may need to be interned later
|
||||
String make_string(std::string);
|
||||
Symbol make_symbol(std::string);
|
||||
|
||||
|
||||
std::optional<LispValue> parse_one();
|
||||
LispValue parse_list();
|
||||
|
||||
public:
|
||||
Parser(Lexer);
|
||||
|
||||
void feed(Lexer);
|
||||
|
||||
std::optional<LispValue> next();
|
||||
};
|
||||
|
33
src/include/value.hpp
Normal file
33
src/include/value.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <iostream>
|
||||
|
||||
// we're using a pure variant as our value type.
|
||||
struct Integer {int64_t value;};
|
||||
struct Double {double value;};
|
||||
struct String {std::string value;}; // might be a good idea to intern strings
|
||||
struct Symbol {std::string value;};
|
||||
struct List;
|
||||
struct Nil {};
|
||||
|
||||
|
||||
using LispValue = std::variant<Integer, Double, String, Symbol, List>;
|
||||
struct List {std::vector<LispValue> list;};
|
||||
// during compilation, we don't really care for cyclical lists etc.
|
||||
// during compilation we'll mostly be dealing with regular, flat lists
|
||||
// that form function calls.
|
||||
// We will have a different set of values during runtime
|
||||
// as the runtime will be a bytecode interpreter anyhow.
|
||||
|
||||
|
||||
|
||||
void print_val(LispValue);
|
||||
|
||||
String make_string(std::string);
|
||||
Symbol make_symbol(std::string);
|
||||
|
16
src/lex.cpp
16
src/lex.cpp
@@ -9,14 +9,14 @@ using namespace std;
|
||||
std::ostream &operator<<(std::ostream &os, Token const &t) {
|
||||
os << "Token(";
|
||||
switch (t.type) {
|
||||
case OpenParen: os << "OpenParen)"; break;
|
||||
case CloseParen: os << "CloseParen)"; break;
|
||||
case Dollar: os << "Dollar)"; break;
|
||||
case Symbol: os << "Symbol, " << get<string>(*t.value) << ")"; break;
|
||||
case String: os << "String, \"" << get<string>(*t.value) << "\")"; break;
|
||||
case Int: os << "Int, " << get<int64_t>(*t.value) << ")"; break;
|
||||
case Double: os << "Double, " << get<double>(*t.value) << ")"; break;
|
||||
case End: os << "END)"; break;
|
||||
case TokenType::OpenParen: os << "OpenParen)"; break;
|
||||
case TokenType::CloseParen: os << "CloseParen)"; break;
|
||||
case TokenType::Dollar: os << "Dollar)"; break;
|
||||
case TokenType::Symbol: os << "Symbol, " << get<string>(*t.value) << ")"; break;
|
||||
case TokenType::String: os << "String, \"" << get<string>(*t.value) << "\")"; break;
|
||||
case TokenType::Int: os << "Int, " << get<int64_t>(*t.value) << ")"; break;
|
||||
case TokenType::Double: os << "Double, " << get<double>(*t.value) << ")"; break;
|
||||
case TokenType::End: os << "END)"; break;
|
||||
default:
|
||||
os << ")";
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "value.hpp"
|
||||
#include <iostream>
|
||||
#include <lex.hpp>
|
||||
#include <parse.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
@@ -8,9 +10,9 @@ int main() {
|
||||
string s;
|
||||
getline(cin, s);
|
||||
cout << s << endl;
|
||||
for (auto t : lex(s)) {
|
||||
cout << t << " ";
|
||||
}
|
||||
Parser p(s);
|
||||
print_val(*p.next());
|
||||
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
74
src/parse.cpp
Normal file
74
src/parse.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <lex.hpp>
|
||||
|
||||
#include <parse.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Parser::Parser(Lexer l) : ts(l.collect()) {}
|
||||
|
||||
void Parser::feed(Lexer l) {
|
||||
ts.append_range(l.collect());
|
||||
}
|
||||
|
||||
Token Parser::get_token() {
|
||||
Token t = ts.front();
|
||||
ts.pop_front();
|
||||
return t;
|
||||
}
|
||||
void Parser::unget_token(Token t) {
|
||||
ts.push_front(t);
|
||||
}
|
||||
|
||||
String Parser::make_string(string s) {
|
||||
return String {s};
|
||||
}
|
||||
|
||||
Symbol Parser::make_symbol(string s) {
|
||||
return Symbol {s};
|
||||
}
|
||||
|
||||
optional<LispValue> Parser::parse_one() {
|
||||
Token t = get_token();
|
||||
switch (t.type) {
|
||||
case TokenType::Int: return Integer {get<int64_t>(*t.value)};
|
||||
case TokenType::Double: return Double {get<double>(*t.value)};
|
||||
case TokenType::String: return make_string(get<string>(*t.value));
|
||||
case TokenType::Symbol: return make_symbol(get<string>(*t.value));
|
||||
case TokenType::OpenParen: return parse_list();
|
||||
case TokenType::CloseParen: throw "whatever";
|
||||
|
||||
// I don't know what this will actually do, in theory maybe just like the OpenParen,
|
||||
// but parses things in a different namespace? unimplemented for now.
|
||||
case TokenType::Dollar: return parse_one();
|
||||
case TokenType::End : return nullopt;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
LispValue Parser::parse_list() {
|
||||
// assumes that we have read the OpenParen, and are reading elements until
|
||||
// we find the CloseParen
|
||||
List l;
|
||||
Token t = get_token();
|
||||
|
||||
while (true) {
|
||||
if (t.type == TokenType::End) {
|
||||
// this is clearly an error!
|
||||
cerr << "Parser::parse_list: Input ended before list ended." << endl;
|
||||
break;
|
||||
}
|
||||
if (t.type == TokenType::CloseParen)
|
||||
break;
|
||||
unget_token(t);
|
||||
l.list.push_back(*parse_one());
|
||||
t = get_token();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
optional<LispValue> Parser::next() {
|
||||
return parse_one();
|
||||
}
|
35
src/value.cpp
Normal file
35
src/value.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <concepts>
|
||||
#include <value.hpp>
|
||||
|
||||
|
||||
template <typename T>
|
||||
requires std::convertible_to<T, LispValue>
|
||||
void value_printer(T t) {
|
||||
if constexpr (std::same_as<T, LispValue>) {
|
||||
print_val(t);
|
||||
} else if constexpr (requires { std::cout << t.value;}) {
|
||||
std::cout << t.value;
|
||||
} else {
|
||||
std::cout << "{UNKNOWN}" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void value_printer(List l) {
|
||||
std::cout << "(";
|
||||
for (auto i : l.list) {
|
||||
value_printer(i);
|
||||
std::cout << " ";
|
||||
}
|
||||
std::cout << ")";
|
||||
}
|
||||
|
||||
template <>
|
||||
void value_printer(String s) {
|
||||
std::cout << '"' << s.value << '"';
|
||||
}
|
||||
|
||||
void print_val(LispValue v) {
|
||||
std::visit([](auto arg) {value_printer(arg);}, v);
|
||||
}
|
||||
|
Reference in New Issue
Block a user