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(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
|
||||||
set(HEADER_FILES src/include/lex.hpp)
|
set(HEADER_FILES src/include/lex.hpp src/include/value.hpp src/include/parse.hpp)
|
||||||
set(SOURCE_FILES src/lex.cpp)
|
set(SOURCE_FILES src/lex.cpp src/parse.cpp src/value.cpp)
|
||||||
set(CXX_WARNING_FLAGS -Wall -Wextra -Wpedantic -pedantic)
|
set(CXX_WARNING_FLAGS -Wall -Wextra -Wpedantic -pedantic)
|
||||||
|
|
||||||
# we're not actually shipping a library yet,
|
# we're not actually shipping a library yet,
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
enum TokenType {
|
enum class TokenType {
|
||||||
OpenParen,
|
OpenParen,
|
||||||
CloseParen,
|
CloseParen,
|
||||||
Dollar,
|
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) {
|
std::ostream &operator<<(std::ostream &os, Token const &t) {
|
||||||
os << "Token(";
|
os << "Token(";
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case OpenParen: os << "OpenParen)"; break;
|
case TokenType::OpenParen: os << "OpenParen)"; break;
|
||||||
case CloseParen: os << "CloseParen)"; break;
|
case TokenType::CloseParen: os << "CloseParen)"; break;
|
||||||
case Dollar: os << "Dollar)"; break;
|
case TokenType::Dollar: os << "Dollar)"; break;
|
||||||
case Symbol: os << "Symbol, " << get<string>(*t.value) << ")"; break;
|
case TokenType::Symbol: os << "Symbol, " << get<string>(*t.value) << ")"; break;
|
||||||
case String: os << "String, \"" << get<string>(*t.value) << "\")"; break;
|
case TokenType::String: os << "String, \"" << get<string>(*t.value) << "\")"; break;
|
||||||
case Int: os << "Int, " << get<int64_t>(*t.value) << ")"; break;
|
case TokenType::Int: os << "Int, " << get<int64_t>(*t.value) << ")"; break;
|
||||||
case Double: os << "Double, " << get<double>(*t.value) << ")"; break;
|
case TokenType::Double: os << "Double, " << get<double>(*t.value) << ")"; break;
|
||||||
case End: os << "END)"; break;
|
case TokenType::End: os << "END)"; break;
|
||||||
default:
|
default:
|
||||||
os << ")";
|
os << ")";
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
|
#include "value.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <lex.hpp>
|
#include <lex.hpp>
|
||||||
|
#include <parse.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -8,9 +10,9 @@ int main() {
|
|||||||
string s;
|
string s;
|
||||||
getline(cin, s);
|
getline(cin, s);
|
||||||
cout << s << endl;
|
cout << s << endl;
|
||||||
for (auto t : lex(s)) {
|
Parser p(s);
|
||||||
cout << t << " ";
|
print_val(*p.next());
|
||||||
}
|
|
||||||
cout << endl;
|
cout << endl;
|
||||||
return 0;
|
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