Working
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/*
|
||||
trivial*
|
||||
trivial
|
||||
26
Makefile
Normal file
26
Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
# Compiler and flags
|
||||
CXX = g++
|
||||
CXXFLAGS = -std=c++17 -Wall -g
|
||||
LIBS = -lncurses -lm
|
||||
|
||||
# Source files
|
||||
TRIVIAL_SRC = main.cpp $(wildcard src/*.cpp) $(wildcard src/network/*.cpp)
|
||||
|
||||
# Object files in build/
|
||||
TRIVIAL_OBJ = $(patsubst %.cpp,build/%.o,$(TRIVIAL_SRC))
|
||||
|
||||
# Default target
|
||||
all: trivial
|
||||
|
||||
# Link
|
||||
trivial: $(TRIVIAL_OBJ)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(TRIVIAL_OBJ) $(LIBS)
|
||||
|
||||
# Compile rule
|
||||
build/%.o: %.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
# Clean
|
||||
clean:
|
||||
rm -rf build trivial
|
||||
63
ReadME.md
Normal file
63
ReadME.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 10. User Manual
|
||||
|
||||
## Installation Requirements
|
||||
|
||||
To build and run this project, the following requirements must be met:
|
||||
|
||||
- A C++ compiler (such as `g++` or `clang++`)
|
||||
- The `make` build system installed
|
||||
- A Unix-based environment:
|
||||
- **macOS** is fully supported
|
||||
- **Linux** is supported, but may require additional standard library headers depending on the distribution and compiler configuration
|
||||
|
||||
Windows is not natively supported without a compatibility layer such as WSL (Windows Subsystem for Linux).
|
||||
|
||||
---
|
||||
|
||||
## Cloning the Repository
|
||||
|
||||
To obtain the project source code, clone the repository using Git:
|
||||
|
||||
```bash
|
||||
git clone URL_NOT_ADDED_YET
|
||||
```
|
||||
After cloning, navigate into the project directory:
|
||||
|
||||
```bash
|
||||
cd <project-folder-name>
|
||||
```
|
||||
|
||||
## Building the Project
|
||||
|
||||
The project uses a Makefile for compilation. To build the interpreter, run:
|
||||
|
||||
```bash
|
||||
make trivial
|
||||
```
|
||||
This command compiles all necessary source files and generates the executable named trivial.
|
||||
|
||||
If compilation issues occur on Linux, it may be necessary to install additional development headers or ensure that a modern C++ compiler standard (e.g., C++11 or later) is enabled.
|
||||
|
||||
## Running the Interpreter
|
||||
|
||||
Once the project has been successfully built, there are two ways to run it:
|
||||
|
||||
1. Interactive Shell Mode
|
||||
|
||||
To launch the Trivial interactive interpreter:
|
||||
|
||||
```./trivial```
|
||||
|
||||
This starts a shell-like environment where Trivial statements can be entered and executed line by line.
|
||||
|
||||
2. File Execution Mode
|
||||
|
||||
To execute a Trivial program stored in a file:
|
||||
|
||||
```./trivial {filename}```
|
||||
|
||||
Replace {filename} with the path to the Trivial source file. The interpreter will read the file, process it, and execute the program sequentially.
|
||||
|
||||
Example:
|
||||
|
||||
```./trivial example.tri```
|
||||
34
function.txt
Normal file
34
function.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
This is a record of the output
|
||||
|
||||
./trivial
|
||||
>> i = 10
|
||||
>> print i
|
||||
10
|
||||
>> i = i - i
|
||||
>> print i
|
||||
0
|
||||
>> i = 10
|
||||
>> i = i - 1
|
||||
>> print i
|
||||
9
|
||||
|
||||
arizotaz@Coltons-MacBook-Pro trivial_c++ % cat test.triv
|
||||
f = function(a) { return a * 2 }
|
||||
print f(10)
|
||||
|
||||
i = 0
|
||||
while (i < 3) {
|
||||
print i
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
if (1 > 0) { print 10 } else { print 1 }
|
||||
|
||||
arizotaz@Coltons-MacBook-Pro trivial_c++ % ./trivial test.triv
|
||||
20
|
||||
0
|
||||
1
|
||||
2
|
||||
10
|
||||
|
||||
arizotaz@Coltons-MacBook-Pro trivial_c++ %
|
||||
4
include/errors.h
Normal file
4
include/errors.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
void error(const std::string& msg);
|
||||
39
include/evaluator.h
Normal file
39
include/evaluator.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include "parser.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
struct Function;
|
||||
|
||||
struct Value;
|
||||
|
||||
using Object = std::unordered_map<std::string, Value>;
|
||||
|
||||
struct Value : std::variant<
|
||||
std::nullptr_t,
|
||||
bool,
|
||||
double,
|
||||
std::string,
|
||||
std::vector<Value>,
|
||||
Object,
|
||||
std::shared_ptr<Function>> {
|
||||
using variant::variant;
|
||||
};
|
||||
|
||||
struct EvalResult {
|
||||
Value value;
|
||||
std::string control;
|
||||
};
|
||||
|
||||
struct Function {
|
||||
std::vector<std::string> params;
|
||||
std::shared_ptr<AST> body;
|
||||
std::unordered_map<std::string, Value> env;
|
||||
};
|
||||
|
||||
using Env = std::unordered_map<std::string, Value>;
|
||||
|
||||
EvalResult evaluate(std::shared_ptr<AST> ast, Env& env);
|
||||
21
include/parser.h
Normal file
21
include/parser.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "tokenizer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct AST {
|
||||
std::string tag;
|
||||
std::string value;
|
||||
|
||||
std::shared_ptr<AST> left;
|
||||
std::shared_ptr<AST> right;
|
||||
|
||||
std::vector<std::shared_ptr<AST>> list;
|
||||
|
||||
// function parameters
|
||||
std::vector<std::string> params;
|
||||
};
|
||||
|
||||
std::shared_ptr<AST> parse(std::vector<Token> tokens);
|
||||
12
include/tokenizer.h
Normal file
12
include/tokenizer.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Token {
|
||||
std::string tag;
|
||||
std::string value;
|
||||
int line;
|
||||
int column;
|
||||
};
|
||||
|
||||
std::vector<Token> tokenize(const std::string& input);
|
||||
75
main.cpp
Normal file
75
main.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "include/tokenizer.h"
|
||||
#include "include/parser.h"
|
||||
#include "include/evaluator.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
// ---------------- read entire file ----------------
|
||||
static std::string read_file(const std::string& path) {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Could not open file: " + path);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
// ---------------- execute code ----------------
|
||||
static void execute(const std::string& source, Env& env) {
|
||||
auto tokens = tokenize(source);
|
||||
auto ast = parse(tokens);
|
||||
evaluate(ast, env);
|
||||
}
|
||||
|
||||
// ---------------- REPL ----------------
|
||||
static void repl() {
|
||||
Env env;
|
||||
std::string line;
|
||||
|
||||
while (true) {
|
||||
std::cout << ">> ";
|
||||
if (!std::getline(std::cin, line))
|
||||
break;
|
||||
|
||||
if (line == "exit" || line == "quit")
|
||||
break;
|
||||
|
||||
try {
|
||||
execute(line, env);
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "Error: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- main ----------------
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Env env;
|
||||
|
||||
try {
|
||||
// ---------------- FILE MODE ----------------
|
||||
if (argc > 1) {
|
||||
std::string file_path = argv[1];
|
||||
std::string source = read_file(file_path);
|
||||
|
||||
execute(source, env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------- REPL MODE ----------------
|
||||
repl();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "Fatal Error: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
8
src/errors.cpp
Normal file
8
src/errors.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "../include/errors.h"
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
void error(const std::string& msg)
|
||||
{
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
321
src/evaluator.cpp
Normal file
321
src/evaluator.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include "../include/evaluator.h"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
static bool is_truthy(const Value& v)
|
||||
{
|
||||
if (std::holds_alternative<std::nullptr_t>(v))
|
||||
return false;
|
||||
if (auto b = std::get_if<bool>(&v))
|
||||
return *b;
|
||||
if (auto n = std::get_if<double>(&v))
|
||||
return *n != 0;
|
||||
if (auto s = std::get_if<std::string>(&v))
|
||||
return !s->empty();
|
||||
if (auto a = std::get_if<std::vector<Value>>(&v))
|
||||
return !a->empty();
|
||||
if (auto o = std::get_if<Object>(&v))
|
||||
return !o->empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
static double as_number(const Value& v)
|
||||
{
|
||||
if (auto n = std::get_if<double>(&v))
|
||||
return *n;
|
||||
throw std::runtime_error("Expected number");
|
||||
}
|
||||
|
||||
static std::string as_string(const Value& v)
|
||||
{
|
||||
if (auto s = std::get_if<std::string>(&v))
|
||||
return *s;
|
||||
throw std::runtime_error("Expected string");
|
||||
}
|
||||
|
||||
EvalResult evaluate(std::shared_ptr<AST> ast, Env& env);
|
||||
|
||||
// ======================= builtin =======================
|
||||
|
||||
static Value call_builtin(const std::string& name, const std::vector<Value>& args)
|
||||
{
|
||||
if (name == "length") {
|
||||
if (auto a = std::get_if<std::vector<Value>>(&args[0]))
|
||||
return (double)a->size();
|
||||
if (auto s = std::get_if<std::string>(&args[0]))
|
||||
return (double)s->size();
|
||||
throw std::runtime_error("length() invalid arg");
|
||||
}
|
||||
|
||||
if (name == "head") {
|
||||
auto a = std::get<std::vector<Value>>(args[0]);
|
||||
if (a.empty())
|
||||
return nullptr;
|
||||
return a[0];
|
||||
}
|
||||
|
||||
if (name == "tail") {
|
||||
auto a = std::get<std::vector<Value>>(args[0]);
|
||||
if (a.size() <= 1)
|
||||
return std::vector<Value> { };
|
||||
return std::vector<Value>(a.begin() + 1, a.end());
|
||||
}
|
||||
|
||||
if (name == "input") {
|
||||
std::string s;
|
||||
std::getline(std::cin, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown builtin: " + name);
|
||||
}
|
||||
|
||||
// ======================= evaluator =======================
|
||||
|
||||
EvalResult evaluate(std::shared_ptr<AST> ast, Env& env)
|
||||
{
|
||||
|
||||
// -------- literals --------
|
||||
if (ast->tag == "number")
|
||||
return { std::stod(ast->value), "" };
|
||||
if (ast->tag == "string")
|
||||
return { ast->value, "" };
|
||||
if (ast->tag == "boolean")
|
||||
return { ast->value == "true", "" };
|
||||
if (ast->tag == "null")
|
||||
return { nullptr, "" };
|
||||
|
||||
// -------- identifier --------
|
||||
if (ast->tag == "identifier") {
|
||||
if (env.count(ast->value))
|
||||
return { env[ast->value], "" };
|
||||
throw std::runtime_error("Undefined variable: " + ast->value);
|
||||
}
|
||||
|
||||
// -------- arithmetic --------
|
||||
if (ast->tag == "+") {
|
||||
auto l = evaluate(ast->left, env).value;
|
||||
auto r = evaluate(ast->right, env).value;
|
||||
|
||||
if (l.index() == 3 && r.index() == 3)
|
||||
return { std::get<std::string>(l) + std::get<std::string>(r), "" };
|
||||
|
||||
return { (as_number(l) + as_number(r)), "" };
|
||||
}
|
||||
|
||||
if (ast->tag == "-")
|
||||
return { as_number(evaluate(ast->left, env).value) - as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "*")
|
||||
return { as_number(evaluate(ast->left, env).value) * as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "/")
|
||||
return { as_number(evaluate(ast->left, env).value) / as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "%")
|
||||
return { std::fmod(as_number(evaluate(ast->left, env).value),
|
||||
as_number(evaluate(ast->right, env).value)),
|
||||
"" };
|
||||
|
||||
// -------- comparison --------
|
||||
if (ast->tag == "==")
|
||||
return { evaluate(ast->left, env).value == evaluate(ast->right, env).value, "" };
|
||||
|
||||
if (ast->tag == "!=")
|
||||
return { evaluate(ast->left, env).value != evaluate(ast->right, env).value, "" };
|
||||
|
||||
if (ast->tag == "<")
|
||||
return { as_number(evaluate(ast->left, env).value) < as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == ">")
|
||||
return { as_number(evaluate(ast->left, env).value) > as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "<=")
|
||||
return { as_number(evaluate(ast->left, env).value) <= as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == ">=")
|
||||
return { as_number(evaluate(ast->left, env).value) >= as_number(evaluate(ast->right, env).value), "" };
|
||||
|
||||
// -------- logical --------
|
||||
if (ast->tag == "&&")
|
||||
return { is_truthy(evaluate(ast->left, env).value) && is_truthy(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "||")
|
||||
return { is_truthy(evaluate(ast->left, env).value) || is_truthy(evaluate(ast->right, env).value), "" };
|
||||
|
||||
if (ast->tag == "!")
|
||||
return { !is_truthy(evaluate(ast->left, env).value), "" };
|
||||
|
||||
// -------- list --------
|
||||
if (ast->tag == "list") {
|
||||
std::vector<Value> v;
|
||||
for (auto& x : ast->list)
|
||||
v.push_back(evaluate(x, env).value);
|
||||
return { v, "" };
|
||||
}
|
||||
|
||||
// -------- object --------
|
||||
if (ast->tag == "object") {
|
||||
Object obj;
|
||||
for (auto& kv : ast->list) {
|
||||
auto key = evaluate(kv->left, env).value;
|
||||
auto val = evaluate(kv->right, env).value;
|
||||
obj[as_string(key)] = val;
|
||||
}
|
||||
return { obj, "" };
|
||||
}
|
||||
|
||||
// -------- assignment --------
|
||||
if (ast->tag == "=") {
|
||||
std::string name = ast->left->value;
|
||||
Value val = evaluate(ast->right, env).value;
|
||||
env[name] = val;
|
||||
return { val, "" };
|
||||
}
|
||||
|
||||
// -------- function --------
|
||||
if (ast->tag == "function") {
|
||||
|
||||
auto fn = std::make_shared<Function>();
|
||||
|
||||
fn->params = ast->params;
|
||||
fn->body = ast->right;
|
||||
fn->env = env;
|
||||
|
||||
return { fn, "" };
|
||||
}
|
||||
|
||||
// -------- call --------
|
||||
if (ast->tag == "call") {
|
||||
|
||||
Value fnVal = evaluate(ast->left, env).value;
|
||||
|
||||
auto fn = std::get<std::shared_ptr<Function>>(fnVal);
|
||||
|
||||
Env local = fn->env;
|
||||
|
||||
for (size_t j = 0; j < fn->params.size(); j++) {
|
||||
|
||||
Value arg = nullptr;
|
||||
|
||||
if (j < ast->list.size())
|
||||
arg = evaluate(ast->list[j], env).value;
|
||||
|
||||
local[fn->params[j]] = arg;
|
||||
}
|
||||
|
||||
EvalResult result = evaluate(fn->body, local);
|
||||
|
||||
if (result.control == "return")
|
||||
return { result.value, "" };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// -------- block --------
|
||||
if (ast->tag == "statement_list") {
|
||||
Value last;
|
||||
for (auto& s : ast->list)
|
||||
last = evaluate(s, env).value;
|
||||
return { last, "" };
|
||||
}
|
||||
|
||||
// -------- if --------
|
||||
if (ast->tag == "if") {
|
||||
bool cond = is_truthy(evaluate(ast->left, env).value);
|
||||
|
||||
auto thenBranch = ast->right->left;
|
||||
|
||||
if (cond) {
|
||||
return evaluate(thenBranch, env);
|
||||
} else {
|
||||
if (ast->right->right)
|
||||
return evaluate(ast->right->right, env);
|
||||
}
|
||||
|
||||
return { nullptr, "" };
|
||||
}
|
||||
|
||||
// -------- while --------
|
||||
if (ast->tag == "while") {
|
||||
|
||||
while (is_truthy(evaluate(ast->left, env).value)) {
|
||||
|
||||
EvalResult result = evaluate(ast->right, env);
|
||||
|
||||
if (result.control == "break")
|
||||
break;
|
||||
|
||||
if (result.control == "continue")
|
||||
continue;
|
||||
|
||||
if (!result.control.empty())
|
||||
return result;
|
||||
}
|
||||
|
||||
return { nullptr, "" };
|
||||
}
|
||||
|
||||
// -------- print --------
|
||||
if (ast->tag == "print") {
|
||||
if (!ast->left) {
|
||||
std::cout << std::endl;
|
||||
return { std::string("\n"), "" };
|
||||
}
|
||||
|
||||
Value v = evaluate(ast->left, env).value;
|
||||
|
||||
if (std::holds_alternative<bool>(v)) {
|
||||
std::cout << (std::get<bool>(v) ? "true" : "false") << std::endl;
|
||||
return { std::string(std::get<bool>(v) ? "true" : "false") + "\n", "" };
|
||||
}
|
||||
|
||||
if (std::holds_alternative<double>(v)) {
|
||||
std::cout << std::get<double>(v) << std::endl;
|
||||
return { std::to_string(std::get<double>(v)) + "\n", "" };
|
||||
}
|
||||
|
||||
if (std::holds_alternative<std::string>(v)) {
|
||||
std::cout << std::get<std::string>(v) << std::endl;
|
||||
return { std::get<std::string>(v) + "\n", "" };
|
||||
}
|
||||
|
||||
if (std::holds_alternative<std::nullptr_t>(v)) {
|
||||
std::cout << "null" << std::endl;
|
||||
return { std::string("null\n"), "" };
|
||||
}
|
||||
|
||||
std::cout << "[object]" << std::endl;
|
||||
return { std::string("[object]\n"), "" };
|
||||
}
|
||||
|
||||
if (ast->tag == "return") {
|
||||
|
||||
Value val = nullptr;
|
||||
|
||||
if (ast->left)
|
||||
val = evaluate(ast->left, env).value;
|
||||
|
||||
return { val, "return" };
|
||||
}
|
||||
|
||||
if (ast->tag == "block") {
|
||||
|
||||
Value last = nullptr;
|
||||
|
||||
for (auto& stmt : ast->list) {
|
||||
|
||||
EvalResult result = evaluate(stmt, env);
|
||||
|
||||
if (!result.control.empty())
|
||||
return result;
|
||||
|
||||
last = result.value;
|
||||
}
|
||||
|
||||
return { last, "" };
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown AST tag: " + ast->tag);
|
||||
}
|
||||
391
src/parser.cpp
Normal file
391
src/parser.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#include "../include/parser.h"
|
||||
#include <stdexcept>
|
||||
|
||||
static size_t i = 0;
|
||||
|
||||
std::shared_ptr<AST> parse_block(std::vector<Token>& t);
|
||||
std::shared_ptr<AST> parse_statement(std::vector<Token>& t);
|
||||
std::shared_ptr<AST> parse_block(std::vector<Token>& t);
|
||||
std::shared_ptr<AST> parse_expression(std::vector<Token>& t);
|
||||
|
||||
static Token& cur(std::vector<Token>& t)
|
||||
{
|
||||
if (i >= t.size())
|
||||
throw std::runtime_error("Unexpected end of input");
|
||||
return t[i];
|
||||
}
|
||||
|
||||
static Token& advance(std::vector<Token>& t)
|
||||
{
|
||||
return t[++i];
|
||||
}
|
||||
|
||||
static bool match(std::vector<Token>& t, const std::string& tag)
|
||||
{
|
||||
if (cur(t).tag == tag) {
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// forward declarations
|
||||
std::shared_ptr<AST> parse_expression(std::vector<Token>& t);
|
||||
std::shared_ptr<AST> parse_statement(std::vector<Token>& t);
|
||||
|
||||
// ---------------- primary expressions ----------------
|
||||
std::shared_ptr<AST> parse_primary(std::vector<Token>& t)
|
||||
{
|
||||
Token token = cur(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
|
||||
if (token.tag == "function") {
|
||||
i++; // consume function
|
||||
|
||||
if (!match(t, "("))
|
||||
throw std::runtime_error("Expected '(' after function");
|
||||
|
||||
auto fn = std::make_shared<AST>();
|
||||
fn->tag = "function";
|
||||
|
||||
// parameters
|
||||
if (cur(t).tag != ")") {
|
||||
|
||||
while (true) {
|
||||
|
||||
if (cur(t).tag != "identifier")
|
||||
throw std::runtime_error("Expected parameter name");
|
||||
|
||||
fn->params.push_back(cur(t).value);
|
||||
i++;
|
||||
|
||||
if (cur(t).tag == ",") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match(t, ")"))
|
||||
throw std::runtime_error("Expected ')' after parameters");
|
||||
|
||||
fn->right = parse_block(t);
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
if (token.tag == "identifier") {
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "identifier";
|
||||
node->value = token.value;
|
||||
|
||||
i++;
|
||||
|
||||
// function call
|
||||
if (cur(t).tag == "(") {
|
||||
|
||||
i++; // consume (
|
||||
|
||||
auto call = std::make_shared<AST>();
|
||||
call->tag = "call";
|
||||
call->left = node;
|
||||
|
||||
if (cur(t).tag != ")") {
|
||||
|
||||
while (true) {
|
||||
|
||||
call->list.push_back(parse_expression(t));
|
||||
|
||||
if (cur(t).tag == ",") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match(t, ")"))
|
||||
throw std::runtime_error("Expected ')' after arguments");
|
||||
|
||||
return call;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
if (token.tag == "number" || token.tag == "string" || token.tag == "identifier") {
|
||||
node->tag = token.tag;
|
||||
node->value = token.value;
|
||||
i++;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (token.tag == "(") {
|
||||
i++;
|
||||
auto expr = parse_expression(t);
|
||||
if (cur(t).tag != ")")
|
||||
throw std::runtime_error("Expected ')'");
|
||||
i++;
|
||||
return expr;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unexpected token: " + token.tag);
|
||||
}
|
||||
|
||||
// ---------------- binary expressions (basic precedence) ----------------
|
||||
std::shared_ptr<AST> parse_term(std::vector<Token>& t);
|
||||
std::shared_ptr<AST> parse_factor(std::vector<Token>& t);
|
||||
|
||||
std::shared_ptr<AST> parse_factor(std::vector<Token>& t)
|
||||
{
|
||||
return parse_primary(t);
|
||||
}
|
||||
|
||||
std::shared_ptr<AST> parse_arithmetic(std::vector<Token>& t)
|
||||
{
|
||||
|
||||
auto left = parse_term(t);
|
||||
|
||||
while (
|
||||
cur(t).tag == "+" || cur(t).tag == "-") {
|
||||
|
||||
std::string op = cur(t).tag;
|
||||
i++;
|
||||
|
||||
auto right = parse_term(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = op;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
|
||||
left = node;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
std::shared_ptr<AST> parse_term(std::vector<Token>& t)
|
||||
{
|
||||
auto left = parse_factor(t);
|
||||
|
||||
while (cur(t).tag == "*" || cur(t).tag == "/" || cur(t).tag == "%") {
|
||||
std::string op = cur(t).tag;
|
||||
i++;
|
||||
|
||||
auto right = parse_factor(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = op;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
|
||||
left = node;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
std::shared_ptr<AST> parse_expression(std::vector<Token>& t)
|
||||
{
|
||||
auto left = parse_arithmetic(t);
|
||||
|
||||
while (
|
||||
cur(t).tag == "<" || cur(t).tag == ">" || cur(t).tag == "<=" || cur(t).tag == ">=" || cur(t).tag == "==" || cur(t).tag == "!=") {
|
||||
|
||||
std::string op = cur(t).tag;
|
||||
i++;
|
||||
|
||||
auto right = parse_arithmetic(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = op;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
|
||||
left = node;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
// ---------------- statements ----------------
|
||||
// std::shared_ptr<AST> parse_block(std::vector<Token>& t)
|
||||
// {
|
||||
// if (!match(t, "{"))
|
||||
// throw std::runtime_error("Expected '{'");
|
||||
|
||||
// auto node = std::make_shared<AST>();
|
||||
// node->tag = "statement_list";
|
||||
|
||||
// while (cur(t).tag != "}") {
|
||||
// node->list.push_back(parse_statement(t));
|
||||
// }
|
||||
|
||||
// i++; // consume }
|
||||
|
||||
// return node;
|
||||
// }
|
||||
|
||||
std::shared_ptr<AST> parse_block(std::vector<Token>& t)
|
||||
{
|
||||
|
||||
if (!match(t, "{"))
|
||||
throw std::runtime_error("Expected '{'");
|
||||
|
||||
auto block = std::make_shared<AST>();
|
||||
block->tag = "block";
|
||||
|
||||
while (cur(t).tag != "}") {
|
||||
|
||||
block->list.push_back(parse_statement(t));
|
||||
|
||||
if (cur(t).tag == ";")
|
||||
i++;
|
||||
}
|
||||
|
||||
match(t, "}");
|
||||
|
||||
return block;
|
||||
}
|
||||
std::shared_ptr<AST> parse_while(std::vector<Token>& t)
|
||||
{
|
||||
|
||||
if (!match(t, "while"))
|
||||
throw std::runtime_error("Expected 'while'");
|
||||
|
||||
if (!match(t, "("))
|
||||
throw std::runtime_error("Expected '(' after while");
|
||||
|
||||
auto condition = parse_expression(t);
|
||||
|
||||
if (!match(t, ")"))
|
||||
throw std::runtime_error("Expected ')'");
|
||||
|
||||
auto body = parse_statement(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "while";
|
||||
node->left = condition;
|
||||
node->right = body;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<AST> parse_statement(std::vector<Token>& t)
|
||||
{
|
||||
|
||||
if (cur(t).tag == "identifier" && (i + 1 < t.size()) && t[i + 1].tag == "=") {
|
||||
|
||||
std::string name = cur(t).value;
|
||||
i += 2; // consume identifier and '='
|
||||
|
||||
auto value = parse_expression(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "=";
|
||||
|
||||
node->left = std::make_shared<AST>();
|
||||
node->left->tag = "identifier";
|
||||
node->left->value = name;
|
||||
|
||||
node->right = value;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// -------- print --------
|
||||
if (cur(t).tag == "print") {
|
||||
i++;
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "print";
|
||||
|
||||
if (cur(t).tag != ";" && cur(t).tag != "}")
|
||||
node->left = parse_expression(t);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// -------- if --------
|
||||
if (cur(t).tag == "if") {
|
||||
i++; // consume if
|
||||
|
||||
if (!match(t, "("))
|
||||
throw std::runtime_error("Expected '(' after if");
|
||||
|
||||
auto condition = parse_expression(t);
|
||||
|
||||
if (!match(t, ")"))
|
||||
throw std::runtime_error("Expected ')'");
|
||||
|
||||
auto thenBranch = parse_statement(t);
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "if";
|
||||
node->left = condition;
|
||||
|
||||
// default: no else
|
||||
node->right = std::make_shared<AST>();
|
||||
node->right->tag = "if_body";
|
||||
node->right->left = thenBranch;
|
||||
|
||||
// -------- handle optional else --------
|
||||
if (cur(t).tag == "else") {
|
||||
i++; // consume else
|
||||
auto elseBranch = parse_statement(t);
|
||||
node->right->right = elseBranch;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// -------- while --------
|
||||
if (cur(t).tag == "while")
|
||||
return parse_while(t);
|
||||
|
||||
if (cur(t).tag == "return") {
|
||||
|
||||
i++; // consume return
|
||||
|
||||
auto node = std::make_shared<AST>();
|
||||
node->tag = "return";
|
||||
|
||||
// optional return expression
|
||||
if (cur(t).tag != "}" && cur(t).tag != ";" && cur(t).tag != "EOF") {
|
||||
|
||||
node->left = parse_expression(t);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// -------- block --------
|
||||
if (cur(t).tag == "{") {
|
||||
return parse_block(t);
|
||||
}
|
||||
|
||||
// -------- expression statement --------
|
||||
return parse_expression(t);
|
||||
}
|
||||
|
||||
// ---------------- entry ----------------
|
||||
std::shared_ptr<AST> parse(std::vector<Token> tokens)
|
||||
{
|
||||
i = 0;
|
||||
|
||||
auto root = std::make_shared<AST>();
|
||||
root->tag = "statement_list";
|
||||
|
||||
while (cur(tokens).tag != "") {
|
||||
if (cur(tokens).tag == "")
|
||||
break;
|
||||
root->list.push_back(parse_statement(tokens));
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
110
src/tokenizer.cpp
Normal file
110
src/tokenizer.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "../include/tokenizer.h"
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
|
||||
struct Pattern {
|
||||
std::regex re;
|
||||
std::string tag;
|
||||
};
|
||||
|
||||
static std::vector<Pattern> patterns = {
|
||||
{ std::regex(R"(//[^\n]*)"), "comment" },
|
||||
{ std::regex(R"(\s+)"), "whitespace" },
|
||||
{ std::regex(R"(\d*\.\d+|\d+\.\d*|\d+)"), "number" },
|
||||
{ std::regex(R"("([^"]|"")*")"), "string" },
|
||||
{ std::regex(R"(true|false)"), "boolean" },
|
||||
{ std::regex(R"(null)"), "null" },
|
||||
{ std::regex(R"(function)"), "function" },
|
||||
{ std::regex(R"(return)"), "return" },
|
||||
{ std::regex(R"(extern)"), "extern" },
|
||||
{ std::regex(R"(if)"), "if" },
|
||||
{ std::regex(R"(else)"), "else" },
|
||||
{ std::regex(R"(while)"), "while" },
|
||||
{ std::regex(R"(for)"), "for" },
|
||||
{ std::regex(R"(break)"), "break" },
|
||||
{ std::regex(R"(continue)"), "continue" },
|
||||
{ std::regex(R"(print)"), "print" },
|
||||
{ std::regex(R"(import)"), "import" },
|
||||
{ std::regex(R"(exit)"), "exit" },
|
||||
{ std::regex(R"(and)"), "&&" },
|
||||
{ std::regex(R"(or)"), "||" },
|
||||
{ std::regex(R"(not)"), "!" },
|
||||
{ std::regex(R"([a-zA-Z_][a-zA-Z0-9_]*)"), "identifier" },
|
||||
{ std::regex(R"(\+)"), "+" },
|
||||
{ std::regex(R"(-)"), "-" },
|
||||
{ std::regex(R"(\*)"), "*" },
|
||||
{ std::regex(R"(\/)"), "/" },
|
||||
{ std::regex(R"(\%)"), "%" },
|
||||
{ std::regex(R"(\()"), "(" },
|
||||
{ std::regex(R"(\))"), ")" },
|
||||
{ std::regex(R"(\{)"), "{" },
|
||||
{ std::regex(R"(\})"), "}" },
|
||||
{ std::regex(R"(==)"), "==" },
|
||||
{ std::regex(R"(!=)"), "!=" },
|
||||
{ std::regex(R"(<=)"), "<=" },
|
||||
{ std::regex(R"(>=)"), ">=" },
|
||||
{ std::regex(R"(<)"), "<" },
|
||||
{ std::regex(R"(>)"), ">" },
|
||||
{ std::regex(R"(\|\|)"), "||" },
|
||||
{ std::regex(R"(\|\|)"), "||" },
|
||||
{ std::regex(R"(\!)"), "!" },
|
||||
{ std::regex(R"(=)"), "=" },
|
||||
{ std::regex(R"(\.)"), "." },
|
||||
{ std::regex(R"(\[)"), "[" },
|
||||
{ std::regex(R"(\])"), "]" },
|
||||
{ std::regex(R"(,)"), "," },
|
||||
{ std::regex(R"(:)"), ":" },
|
||||
{ std::regex(R"(;)"), ";" },
|
||||
{ std::regex(R"(.)"), "error" }
|
||||
};
|
||||
|
||||
std::vector<Token> tokenize(const std::string& input)
|
||||
{
|
||||
std::vector<Token> tokens;
|
||||
size_t pos = 0;
|
||||
int line = 1, col = 1;
|
||||
|
||||
while (pos < input.size()) {
|
||||
std::smatch match;
|
||||
bool found = false;
|
||||
|
||||
for (auto& p : patterns) {
|
||||
std::string s = input.substr(pos);
|
||||
if (std::regex_search(s, match, p.re, std::regex_constants::match_continuous)) {
|
||||
std::string value = match.str();
|
||||
|
||||
if (p.tag == "whitespace") {
|
||||
for (char c : value) {
|
||||
if (c == '\n') {
|
||||
line++;
|
||||
col = 1;
|
||||
} else
|
||||
col++;
|
||||
}
|
||||
} else if (p.tag == "error") {
|
||||
throw std::runtime_error("Syntax error near: " + value);
|
||||
} else if (p.tag != "comment") {
|
||||
Token t;
|
||||
t.tag = p.tag;
|
||||
t.value = value;
|
||||
t.line = line;
|
||||
t.column = col;
|
||||
tokens.push_back(t);
|
||||
col += value.size();
|
||||
} else {
|
||||
col += value.size();
|
||||
}
|
||||
|
||||
pos += value.size();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.push_back({ "", "", line, col });
|
||||
return tokens;
|
||||
}
|
||||
Reference in New Issue
Block a user