Skip to content

Commit 0467203

Browse files
committed
compile time binary/unary expressions
1 parent 344f77f commit 0467203

File tree

7 files changed

+244
-24
lines changed

7 files changed

+244
-24
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.21)
22
project(rift)
33

44
include(cmake/CPM.cmake)
5-
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
5+
CPMAddPackage("gh:fmtlib/fmt#64db979")
66
CPMAddPackage("gh:geode-sdk/result#4106d12")
77

88
set(CMAKE_CXX_STANDARD 20)

include/rift/nodes/node.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ namespace rift {
6262
return node.operator<<(os);
6363
}
6464

65+
void setCodeSegment(size_t fromIndex, size_t toIndex) noexcept {
66+
m_fromIndex = fromIndex;
67+
m_toIndex = toIndex;
68+
}
69+
70+
[[nodiscard]] size_t fromIndex() const noexcept { return m_fromIndex; }
71+
[[nodiscard]] size_t toIndex() const noexcept { return m_toIndex; }
72+
6573
protected:
6674
Type m_type = Type::Segment;
6775
size_t m_fromIndex, m_toIndex;

include/rift/nodes/value.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ namespace rift {
1919
return m_value;
2020
}
2121

22+
void setValue(Value&& value) noexcept {
23+
m_value = std::move(value);
24+
}
25+
2226
private:
2327
Value m_value;
2428
};

include/rift/optimizer.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#pragma once
2+
#ifndef RIFT_OPTIMIZER_HPP
3+
#define RIFT_OPTIMIZER_HPP
4+
5+
#include <memory>
6+
#include <Geode/Result.hpp>
7+
8+
#include "token.hpp"
9+
#include "errors/compile.hpp"
10+
#include "nodes/node.hpp"
11+
12+
namespace rift::optimizer {
13+
using OptimizerResult = geode::Result<std::unique_ptr<Node>, std::string>;
14+
15+
/// @brief Try to fold a binary operation if possible.
16+
/// @param lhs The left-hand side node.
17+
/// @param rhs The right-hand side node.
18+
/// @param op The operator type.
19+
/// @param start The start index of the node.
20+
/// @param end The end index of the node.
21+
/// @return Either a folded ValueNode or a BinaryNode if folding is not possible.
22+
OptimizerResult tryFoldBinary(
23+
std::unique_ptr<Node>&& lhs, std::unique_ptr<Node>&& rhs, TokenType op,
24+
size_t start, size_t end
25+
) noexcept;
26+
27+
/// @brief Try to fold a unary operation if possible.
28+
/// @param node The unary node to fold.
29+
/// @param op The operator type.
30+
/// @param start The start index of the node.
31+
/// @param end The end index of the node.
32+
/// @return Either a folded ValueNode or the original node if folding is not possible.
33+
OptimizerResult tryFoldUnary(
34+
std::unique_ptr<Node>&& node, TokenType op,
35+
size_t start, size_t end
36+
) noexcept;
37+
}
38+
39+
#endif // RIFT_OPTIMIZER_HPP

src/optimizer.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#include <rift/optimizer.hpp>
2+
3+
#include <rift/nodes/binary.hpp>
4+
#include <rift/nodes/unary.hpp>
5+
#include <rift/nodes/value.hpp>
6+
7+
namespace rift::optimizer {
8+
9+
OptimizerResult tryFoldBinary(
10+
std::unique_ptr<Node>&& lhs, std::unique_ptr<Node>&& rhs, TokenType op,
11+
size_t start, size_t end
12+
) noexcept {
13+
// Check if both nodes are ValueNodes
14+
if (lhs->type() == Node::Type::Value && rhs->type() == Node::Type::Value) {
15+
// Perform the operation and return a new ValueNode
16+
auto& lhsValue = static_cast<ValueNode const&>(*lhs).value();
17+
auto& rhsValue = static_cast<ValueNode const&>(*rhs).value();
18+
19+
#define CASE(Type, op) \
20+
case TokenType::Type: { \
21+
auto lhsPtr = static_cast<ValueNode*>(lhs.get()); \
22+
lhsPtr->setValue(lhsValue op rhsValue); \
23+
lhsPtr->setCodeSegment(start, end); \
24+
return geode::Ok(std::move(lhs)); \
25+
}
26+
#define CASE_UNWRAP(Type, op) \
27+
case TokenType::Type: { \
28+
auto res = lhsValue op rhsValue; \
29+
if (res.isErr()) { \
30+
return geode::Err(fmt::format("UnfoldingError: {}", res.unwrapErr())); \
31+
} \
32+
auto lhsPtr = static_cast<ValueNode*>(lhs.get()); \
33+
lhsPtr->setValue(std::move(res.unwrap())); \
34+
lhsPtr->setCodeSegment(start, end); \
35+
return geode::Ok(std::move(lhs)); \
36+
}
37+
38+
switch (op) {
39+
// Math operators
40+
CASE_UNWRAP(PLUS, +)
41+
CASE_UNWRAP(MINUS, -)
42+
CASE_UNWRAP(STAR, *)
43+
CASE_UNWRAP(SLASH, /)
44+
CASE_UNWRAP(PERCENT, %)
45+
CASE_UNWRAP(CARET, ^)
46+
47+
// Comparison operators
48+
CASE(EQUAL_EQUAL, ==)
49+
CASE(NOT_EQUAL, !=)
50+
CASE(LESS, <)
51+
CASE(GREATER, >)
52+
CASE(LESS_EQUAL, <=)
53+
CASE(GREATER_EQUAL, >=)
54+
55+
// Logical operators
56+
CASE(AND, &&)
57+
CASE(OR, ||)
58+
59+
default: {
60+
return geode::Err("OptimizerError: Unknown binary operator");
61+
}
62+
}
63+
}
64+
65+
#undef CASE
66+
#undef CASE_UNWRAP
67+
68+
// If folding is not possible, return a new BinaryNode
69+
return geode::Ok(std::make_unique<BinaryNode>(
70+
std::move(lhs),
71+
op,
72+
std::move(rhs),
73+
start, end
74+
));
75+
}
76+
77+
OptimizerResult tryFoldUnary(
78+
std::unique_ptr<Node>&& node, TokenType op,
79+
size_t start, size_t end
80+
) noexcept {
81+
// Check if the node is a ValueNode
82+
if (node->type() == Node::Type::Value) {
83+
// Perform the operation and return a new ValueNode
84+
auto& value = static_cast<ValueNode const&>(*node).value();
85+
#define CASE(Type, op) \
86+
case TokenType::Type: { \
87+
auto lhsPtr = static_cast<ValueNode*>(node.get()); \
88+
lhsPtr->setValue(op value); \
89+
lhsPtr->setCodeSegment(start, end); \
90+
return geode::Ok(std::move(node)); \
91+
}
92+
93+
switch (op) {
94+
// Math operators
95+
CASE(PLUS, +)
96+
CASE(MINUS, -)
97+
CASE(NOT, !)
98+
99+
default: {
100+
return geode::Err("OptimizerError: Unknown unary operator");
101+
}
102+
}
103+
}
104+
105+
// If folding is not possible, create a new UnaryNode
106+
return geode::Ok(std::make_unique<UnaryNode>(
107+
op,
108+
std::move(node),
109+
start, end
110+
));
111+
}
112+
113+
}

src/parser.cpp

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <rift/nodes/unary.hpp>
1515
#include <rift/nodes/value.hpp>
1616

17+
#include <rift/optimizer.hpp>
18+
1719
namespace rift {
1820

1921
/** Parser syntax:
@@ -294,12 +296,20 @@ namespace rift {
294296

295297
auto lhs = std::move(comparisons.top());
296298
comparisons.pop();
297-
comparisons.push(std::make_unique<BinaryNode>(
299+
auto opt = optimizer::tryFoldBinary(
298300
std::move(lhs),
299-
type,
300301
std::move(rhs.unwrap()),
302+
type,
301303
start, m_currentToken.toIndex
302-
));
304+
);
305+
if (opt.isErr()) {
306+
return geode::Err(CompileError(
307+
std::string(m_lexer.m_source),
308+
std::move(opt.unwrapErr()),
309+
start, m_currentToken.toIndex
310+
));
311+
}
312+
comparisons.push(std::move(opt.unwrap()));
303313
}
304314

305315
if (comparisons.size() == 1) {
@@ -335,12 +345,20 @@ namespace rift {
335345
return rhs;
336346
}
337347

338-
return geode::Ok(std::make_unique<BinaryNode>(
348+
auto opt = optimizer::tryFoldBinary(
339349
std::move(res.unwrap()),
340-
op.type,
341350
std::move(rhs.unwrap()),
351+
op.type,
342352
start, m_currentToken.toIndex
343-
));
353+
);
354+
if (opt.isErr()) {
355+
return geode::Err(CompileError(
356+
std::string(m_lexer.m_source),
357+
std::move(opt.unwrapErr()),
358+
start, m_currentToken.toIndex
359+
));
360+
}
361+
return geode::Ok(std::move(opt.unwrap()));
344362
}
345363

346364
default: {
@@ -370,12 +388,20 @@ namespace rift {
370388

371389
auto lhs = std::move(terms.top());
372390
terms.pop();
373-
terms.push(std::make_unique<BinaryNode>(
391+
auto opt = optimizer::tryFoldBinary(
374392
std::move(lhs),
375-
type,
376393
std::move(rhs.unwrap()),
394+
type,
377395
start, m_currentToken.toIndex
378-
));
396+
);
397+
if (opt.isErr()) {
398+
return geode::Err(CompileError(
399+
std::string(m_lexer.m_source),
400+
std::move(opt.unwrapErr()),
401+
start, m_currentToken.toIndex
402+
));
403+
}
404+
terms.push(std::move(opt.unwrap()));
379405
}
380406

381407
if (terms.size() == 1) {
@@ -410,12 +436,20 @@ namespace rift {
410436

411437
auto lhs = std::move(terms.top());
412438
terms.pop();
413-
terms.push(std::make_unique<BinaryNode>(
439+
auto opt = optimizer::tryFoldBinary(
414440
std::move(lhs),
415-
type,
416441
std::move(rhs.unwrap()),
442+
type,
417443
start, m_currentToken.toIndex
418-
));
444+
);
445+
if (opt.isErr()) {
446+
return geode::Err(CompileError(
447+
std::string(m_lexer.m_source),
448+
std::move(opt.unwrapErr()),
449+
start, m_currentToken.toIndex
450+
));
451+
}
452+
terms.push(std::move(opt.unwrap()));
419453
}
420454

421455
if (terms.size() == 1) {
@@ -445,11 +479,19 @@ namespace rift {
445479
return res;
446480
}
447481

448-
return geode::Ok(std::make_unique<UnaryNode>(
449-
op.type,
482+
auto opt = optimizer::tryFoldUnary(
450483
std::move(res.unwrap()),
484+
op.type,
451485
start, m_currentToken.toIndex
452-
));
486+
);
487+
if (opt.isErr()) {
488+
return geode::Err(CompileError(
489+
std::string(m_lexer.m_source),
490+
std::move(opt.unwrapErr()),
491+
start, m_currentToken.toIndex
492+
));
493+
}
494+
return geode::Ok(std::move(opt.unwrap()));
453495
}
454496

455497
default: break;
@@ -467,12 +509,20 @@ namespace rift {
467509
return rhs;
468510
}
469511

470-
return geode::Ok(std::make_unique<BinaryNode>(
512+
auto opt = optimizer::tryFoldBinary(
471513
std::move(res.unwrap()),
472-
TokenType::CARET,
473514
std::move(rhs.unwrap()),
515+
TokenType::CARET,
474516
start, m_currentToken.toIndex
475-
));
517+
);
518+
if (opt.isErr()) {
519+
return geode::Err(CompileError(
520+
std::string(m_lexer.m_source),
521+
std::move(opt.unwrapErr()),
522+
start, m_currentToken.toIndex
523+
));
524+
}
525+
return geode::Ok(std::move(opt.unwrap()));
476526
}
477527

478528
return res;
@@ -618,16 +668,23 @@ namespace rift {
618668
return res;
619669
}
620670

621-
auto binary = std::make_unique<BinaryNode>(
671+
auto opt = optimizer::tryFoldBinary(
622672
std::make_unique<IdentifierNode>(name),
623-
static_cast<TokenType>(op.type - TokenType::PLUS_ASSIGN + TokenType::PLUS),
624673
std::move(res.unwrap()),
674+
static_cast<TokenType>(op.type - TokenType::PLUS_ASSIGN + TokenType::PLUS),
625675
name.fromIndex, m_currentToken.toIndex
626676
);
677+
if (opt.isErr()) {
678+
return geode::Err(CompileError(
679+
std::string(m_lexer.m_source),
680+
std::move(opt.unwrapErr()),
681+
name.fromIndex, m_currentToken.toIndex
682+
));
683+
}
627684

628685
auto assign = std::make_unique<AssignNode>(
629686
std::move(name.value),
630-
std::move(binary),
687+
std::move(opt.unwrap()),
631688
name.fromIndex,
632689
m_currentToken.toIndex
633690
);

test/main.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#include <rift.hpp>
2+
#include <fmt/color.h>
23
#include <fmt/format.h>
34

4-
#include "fmt/color.h"
5-
65
static size_t TEST_COUNT = 0;
76
static size_t TEST_FAILED = 0;
87
static size_t TEST_PASSED = 0;

0 commit comments

Comments
 (0)