Skip to content

Commit 60c9a83

Browse files
authored
Create README.md
1 parent 5f68961 commit 60c9a83

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed

README.md

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
# RIFT Library
2+
#### Runtime Interpreted Formatting Toolkit
3+
4+
RIFT is a lightweight scripting library designed for robust, extendable string formatting and simple expression evaluation, offering unique advantages such as flexibility, seamless API integration, and support for advanced data manipulation.
5+
6+
---
7+
8+
## Key Features
9+
10+
- **Script Compilation**: Parse and compile source scripts into reusable objects.
11+
- **Dynamic Evaluation**: Execute scripts with variable inputs.
12+
- **Custom Value System**: Seamlessly handle multiple data types like strings, numbers, arrays, and objects.
13+
- **Error Handling**: Structured error types for compilation and runtime.
14+
- **Integration**: Simple integration into C++ projects via an intuitive API.
15+
16+
## Table of Contents
17+
18+
- [Installation](#installation)
19+
- [Getting Started](#getting-started)
20+
- [Compile a Script](#compile-a-script)
21+
- [Quick Evaluation](#quick-evaluation)
22+
- [Quick Formatting](#quick-formatting)
23+
- [Value System](#value-system)
24+
- [Example: Creating Values](#example-creating-values)
25+
- [Example: Type Checking](#example-type-checking)
26+
- [Custom Functions](#custom-functions)
27+
- [Registering Custom Functions](#registering-custom-functions)
28+
- [Example: Registering a Custom Function](#example-registering-a-custom-function)
29+
- [Error Handling](#error-handling)
30+
- [Example: Handling Errors](#example-handling-errors)
31+
- [Contributing](#contributing)
32+
- [License](#license)
33+
---
34+
35+
## Installation
36+
37+
RIFT uses CMake for integration, making it simple to include in your project. Follow the steps below:
38+
39+
1. Add RIFT to your project using CMake (you have to clone the repository first):
40+
41+
```cmake
42+
add_subdirectory(path/to/rift)
43+
target_link_libraries(${PROJECT_NAME} rift)
44+
```
45+
46+
3. Alternatively, you can use CPM:
47+
48+
```cmake
49+
CPMAddPackage("gh:EclipseMenu/rift@v2")
50+
target_link_libraries(${PROJECT_NAME} rift)
51+
```
52+
53+
4. Include the RIFT headers in your source files:
54+
55+
```cpp
56+
#include <rift.hpp>
57+
```
58+
59+
---
60+
61+
## Getting Started
62+
63+
### Compile a Script
64+
65+
Use `rift::compile` to compile a script into a `Script` object.
66+
This will give you a unique pointer to a `rift::Script` object that holds the precomputed AST of the script. You can then evaluate the script with different variables without recompiling it, saving time and resources (especially for complex scripts).
67+
68+
```cpp
69+
#include <rift.hpp>
70+
71+
auto source = "Hello, {name}!";
72+
auto result = rift::compile(source);
73+
if (result) {
74+
auto script = std::move(result.unwrap()); // script is a unique_ptr<rift::Script>
75+
// You can now store the script and reuse it later
76+
77+
// Evaluate the script with variables
78+
rift::Object variables = { {"name", "John"} };
79+
auto res = script->run(variables);
80+
if (res) {
81+
auto value = res.unwrap();
82+
std::cout << value << std::endl; // Output: Hello, John!
83+
} else {
84+
auto error = res.unwrapErr();
85+
std::cerr << error.prettyPrint(source) << std::endl;
86+
// note that RuntimeErrors do not have the source code, so you need to pass it manually.
87+
// you can also just use error.message(), if you just want the error message
88+
}
89+
} else {
90+
auto error = result.unwrapErr();
91+
std::cerr << error.prettyPrint() << std::endl;
92+
// prettyPrint() will make a human-readable error message
93+
// with an arrow pointing to the error location
94+
}
95+
```
96+
97+
There's also a way to compile a script in 'direct mode', which changes the behavior of the parser and turns it into an expression parser. This means that you don't need to use `{}` to enclose the script. In this mode, you can get the result directly as a `rift::Value` object:
98+
99+
```cpp
100+
auto source = "2 + 2 * 2";
101+
auto result = rift::compile(source, true);
102+
if (result) {
103+
auto script = std::move(result.unwrap());
104+
auto res = script->eval();
105+
if (res) {
106+
auto value = res.unwrap();
107+
std::cout << value.toInteger() << std::endl; // Output: 6
108+
} else {
109+
auto error = res.unwrapErr();
110+
std::cerr << error.prettyPrint(source) << std::endl;
111+
}
112+
} else {
113+
auto error = result.unwrapErr();
114+
std::cerr << error.prettyPrint() << std::endl;
115+
}
116+
```
117+
118+
119+
### Quick Evaluation
120+
121+
You can use `rift::evaluate` to compile and evaluate a script with direct mode, in a single step.
122+
Note that this is less efficient than compiling the script once and reusing it, but can be used if you only need to evaluate the script once:
123+
124+
```cpp
125+
rift::Object variables = { {"a", 10}, {"b", 20} };
126+
auto result = rift::evaluate("a + b", variables);
127+
if (result) {
128+
auto value = result.unwrap();
129+
std::cout << value.toInteger() << std::endl; // Output: 30
130+
} else {
131+
auto error = result.unwrapErr();
132+
// Handle evaluation error
133+
}
134+
```
135+
136+
### Quick Formatting
137+
138+
Similar to `rift::evaluate`, you can use `rift::format` to compile and format a string with variables:
139+
140+
```cpp
141+
rift::Object variables = { {"name", "John"}, {"age", 30} };
142+
auto result = rift::format("Hello, {name}! You are {age} years old.", variables);
143+
if (result) {
144+
auto formattedString = result.unwrap();
145+
std::cout << formattedString << std::endl; // Output: Hello, John! You are 30 years old.
146+
} else {
147+
auto error = result.error();
148+
// Handle formatting error
149+
}
150+
```
151+
152+
---
153+
154+
## Value System
155+
156+
RIFT provides the `rift::Value` class for handling dynamic values in scripts. Supported types are grouped into categories for better clarity:
157+
158+
- **Primitives**: `Null`, `String`, `Integer`, `Float`, `Boolean`
159+
160+
- **Collections**: `Array`, `Object`
161+
162+
### Example: Creating Values
163+
164+
```cpp
165+
rift::Value stringValue = "Hello, World!";
166+
rift::Value intValue = 42;
167+
rift::Value arrayValue = rift::Array{1, 2, 3, "four"};
168+
rift::Value objectValue = rift::Object{{"key", "value"}};
169+
```
170+
171+
### Example: Type Checking
172+
173+
```cpp
174+
if (value.isString()) {
175+
std::string str = value.getString();
176+
}
177+
if (value.isObject()) {
178+
auto obj = value.getObject();
179+
}
180+
```
181+
182+
---
183+
184+
## Custom Functions
185+
186+
RIFT allows you to extend its functionality by defining custom functions. You can register functions that can be called within scripts, providing additional behavior and logic. These functions are registered in the global configuration and can be invoked just like built-in RIFT functions.
187+
188+
### Registering Custom Functions
189+
190+
To add a custom function to the RIFT library, use the `Config::registerFunction` method. This allows you to bind a C++ function to a name, making it available for use in your scripts.
191+
192+
Alternatively, you can create a function wrapper using `Config::makeFunction`, which handles argument unwrapping and return value wrapping.
193+
194+
#### Function Signature
195+
196+
Custom functions must follow the signature:
197+
198+
```cpp
199+
geode::Result<Value>(std::span<Value const>)
200+
```
201+
202+
Where:
203+
- `std::span<rift::Value const>` is a span of the function arguments.
204+
- `geode::Result<rift::Value>` is the return type, indicating success with the result value, or an error.
205+
206+
### Example: Registering a Custom Function
207+
208+
Here’s an example of how to register a custom function named `multiply` that multiplies two integers:
209+
210+
```cpp
211+
// You can use the function signature directly
212+
// makeFunction will handle argument unwrapping and return value wrapping,
213+
// so you don't need to do it manually
214+
int64_t multiply(int64_t a, int64_t b) {
215+
return a * b;
216+
}
217+
218+
// In some cases, when for example you don't know the number of arguments,
219+
// you can use the direct function signature, and handle the arguments manually
220+
rift::RuntimeFuncResult divide(std::span<rift::Value const> args) {
221+
if (args.size() != 2) {
222+
return geode::Err("Function 'divide' requires exactly 2 arguments");
223+
}
224+
if (!args[0].isInteger() || !args[1].isInteger()) {
225+
return geode::Err("Function 'divide' requires integer arguments");
226+
}
227+
auto a = args[0].toInteger();
228+
auto b = args[1].toInteger();
229+
if (b == 0) {
230+
return geode::Err("Division by zero");
231+
}
232+
return a / b;
233+
}
234+
235+
int main() {
236+
// makeFunction will handle argument unwrapping and return value wrapping, and then store the function in the global configuration
237+
rift::Config::get().makeFunction("multiply", multiply);
238+
// registerFunction will simply store the function in the global configuration
239+
rift::Config::get().registerFunction("divide", divide);
240+
241+
// Example script
242+
rift::Object variables = { {"x", 3}, {"y", 4} };
243+
auto result1 = rift::evaluate("multiply(x, y)", variables);
244+
std::cout << result1.unwrap().toInteger() << std::endl; // Output: 12
245+
246+
auto result2 = rift::evaluate("divide(10, 2)");
247+
std::cout << result2.unwrap().toInteger() << std::endl; // Output: 5
248+
}
249+
```
250+
251+
---
252+
253+
## Error Handling
254+
255+
RIFT uses `geode::Result` for error handling. Common errors include:
256+
257+
- **CompileError**: Issues during script compilation.
258+
- **RuntimeError**: Errors during script execution.
259+
260+
Both error types hold the same information, except that `RuntimeError` does not have the source code. You can use the `prettyPrint` method to get a human-readable error message.
261+
262+
### Example: Handling Errors
263+
264+
```cpp
265+
auto result = rift::evaluate("abc(123) + 4"); // Invalid function call
266+
if (!result) {
267+
auto error = result.unwrapErr();
268+
std::cerr << error.prettyPrint() << std::endl;
269+
/** Output:
270+
* RuntimeError: Function 'abc' not found
271+
* abc(123) + 4
272+
* ^^^^^^^^
273+
*/
274+
275+
// You can also get the error message directly
276+
std::cerr << error.message() << std::endl;
277+
// Output:
278+
// RuntimeError: Function 'abc' not found
279+
}
280+
```
281+
282+
---
283+
284+
## Contributing
285+
286+
Contributions are welcome! Feel free to submit issues or pull requests to improve the library.
287+
288+
## License
289+
290+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

0 commit comments

Comments
 (0)