amoskeag-rb 0.4.0-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +100 -0
- data/LICENSE +22 -0
- data/README.md +336 -0
- data/lib/amoskeag-rb/3.1/amoskeag_native.so +0 -0
- data/lib/amoskeag-rb/3.2/amoskeag_native.so +0 -0
- data/lib/amoskeag-rb/3.3/amoskeag_native.so +0 -0
- data/lib/amoskeag-rb/3.4/amoskeag_native.so +0 -0
- data/lib/amoskeag-rb/version.rb +6 -0
- data/lib/amoskeag-rb.rb +136 -0
- metadata +150 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0a949a31b6d11220496d214f06316cefae2b117e421bb31c6825781e92bd5835
|
|
4
|
+
data.tar.gz: '09b52fa870f9ec48e3b1ec718733f2c11e1d2c4690b21a7e0fe5d5c477ebdcb7'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a82e87a46dcba64b84eb27b81101c2da844a27ed5fb10ed6a8633f78b70d630c7e983b0974fe228e3f3d45e697459ab9b99ec887fb0d98b4cbef7e59f4bef137
|
|
7
|
+
data.tar.gz: '0978a7b9183716b210d2356b13d2bb64227131a7bef0fcd86934b31ae789b46bcce7263e99fb69491b344daf8e898499f5e8335b04a10aef3487a37cf1805c19'
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the amoskeag-rb gem will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2026-10-30
|
|
9
|
+
|
|
10
|
+
0.3.0 was tagged but never published to RubyGems because its release build
|
|
11
|
+
failed. 0.4.0 ships everything listed under 0.3.0 plus the fixes below.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- `rb_sys` runtime dependency raised to `~> 0.9.117` (source-gem installs only).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Precompiled Linux and Windows gems failed to build: the cross-compile images
|
|
18
|
+
were selected from the old `rb_sys` constraint and their cargo could not read
|
|
19
|
+
`Cargo.lock` version 4.
|
|
20
|
+
- `x86_64-darwin` gem build never ran because its CI runner had been retired.
|
|
21
|
+
- Flaky thread-safety tests that depended on thread completion order.
|
|
22
|
+
|
|
23
|
+
## [0.3.0] - 2026-09-25
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Exact `Integer` and `BigDecimal` support. Ruby Integers and BigDecimals passed
|
|
27
|
+
in as data stay exact, and Amoskeag integer and decimal results come back as
|
|
28
|
+
`Integer` and `BigDecimal` instead of widening to `Float`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Source gem failed to compile (`non-exhaustive patterns: AmoskeagValue::Decimal
|
|
32
|
+
and AmoskeagValue::Integer not covered`). The amoskeag git dependency was not
|
|
33
|
+
pinned and the shipped member-level `Cargo.lock` was ignored by the workspace,
|
|
34
|
+
so installs built against upstream HEAD. The dependency is now pinned to a
|
|
35
|
+
fixed `rev` and the workspace `Cargo.lock` ships in the gem.
|
|
36
|
+
- Precompiled platform gems announced in 0.2.0 were never published because the
|
|
37
|
+
build workflow and cross-compile config were not committed. They are now.
|
|
38
|
+
- `extconf.rb` only passes `-static-libgcc` when the linker driver supports it.
|
|
39
|
+
|
|
40
|
+
## [0.2.0] - 2026-06-19
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- Precompiled, statically-linked native gems for every supported platform, so
|
|
44
|
+
`gem install amoskeag-rb` downloads a ready-to-run binary — no Rust toolchain,
|
|
45
|
+
no `cargo`, no compile step. Targets: `x86_64-linux`, `aarch64-linux`,
|
|
46
|
+
`x86_64-linux-musl`, `aarch64-linux-musl`, `x64-mingw-ucrt`, `arm64-darwin`,
|
|
47
|
+
and `x86_64-darwin`, each built against Ruby 3.1–3.4.
|
|
48
|
+
- `misc/native.yml` as the single source of truth for build platforms + ABIs.
|
|
49
|
+
- `rakelib/cross.rake` with `rake gem:<platform>` / `gem:cross` tasks (rb-sys-dock)
|
|
50
|
+
for local cross-builds.
|
|
51
|
+
- GitHub Actions workflow (`.github/workflows/gem.yml`) that tests, builds every
|
|
52
|
+
platform gem plus a source fallback, and publishes to RubyGems on tag via
|
|
53
|
+
Trusted Publishing (OIDC).
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- Build switched from `Rake::ExtensionTask` to `RbSys::ExtensionTask`; the Rust
|
|
57
|
+
crate is now a Cargo workspace member (root `Cargo.toml`) so cross-compilation
|
|
58
|
+
and `cargo metadata` resolve cleanly.
|
|
59
|
+
- The native binary loads from a per-ABI path (`lib/amoskeag-rb/<ruby>/`), with a
|
|
60
|
+
fallback for source-gem installs.
|
|
61
|
+
- The source gem links libstdc++/libgcc statically on Linux for portability.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- The extension loader no longer relies on a development-only relative path that
|
|
65
|
+
failed for installed gems.
|
|
66
|
+
|
|
67
|
+
## [0.1.0] - 2025-01-20
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- Initial release of amoskeag-rb gem
|
|
71
|
+
- Native Ruby bindings to Amoskeag DSL
|
|
72
|
+
- FFI layer using statically compiled Rust library
|
|
73
|
+
- Core API methods:
|
|
74
|
+
- `Amoskeag.compile(source, symbols)` - Compile programs with symbol validation
|
|
75
|
+
- `Amoskeag.evaluate(program, data)` - Evaluate compiled programs
|
|
76
|
+
- `Amoskeag.eval_expression(source, data, symbols)` - Compile and evaluate in one step
|
|
77
|
+
- Full support for all Amoskeag data types:
|
|
78
|
+
- Numbers, Strings, Booleans, Nil
|
|
79
|
+
- Arrays and Dictionaries
|
|
80
|
+
- Symbols with compile-time validation
|
|
81
|
+
- Ruby-friendly API with automatic type conversion
|
|
82
|
+
- Thread-safe compiled programs
|
|
83
|
+
- Comprehensive error handling with CompileError and EvalError
|
|
84
|
+
- Complete documentation and examples
|
|
85
|
+
- JSON-based marshalling between Ruby and native code
|
|
86
|
+
|
|
87
|
+
### Features
|
|
88
|
+
- Business Rules Engine capabilities
|
|
89
|
+
- Template Engine functionality
|
|
90
|
+
- Spreadsheet Formula support
|
|
91
|
+
- 60+ standard library functions
|
|
92
|
+
- Excel-compatible financial functions
|
|
93
|
+
- Safe navigation (nil-safe property access)
|
|
94
|
+
- Pure functional evaluation (no side effects)
|
|
95
|
+
- Security-first design (immune to code injection)
|
|
96
|
+
|
|
97
|
+
[0.4.0]: https://github.com/durable-oss/amoskeag-rb/releases/tag/v0.4.0
|
|
98
|
+
[0.3.0]: https://github.com/durable-oss/amoskeag-rb/releases/tag/v0.3.0
|
|
99
|
+
[0.2.0]: https://github.com/durable-oss/amoskeag-rb/releases/tag/v0.2.0
|
|
100
|
+
[0.1.0]: https://github.com/durable-oss/amoskeag-rb/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Durable Programming, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Amoskeag Ruby Gem
|
|
2
|
+
|
|
3
|
+
Native Ruby bindings for [Amoskeag](https://github.com/durable-oss/amoskeag) - a secure, purely functional DSL for business rules, templates, and spreadsheet formulas.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Security-First Design**: Immune to code injection attacks (SSTI, RCE)
|
|
8
|
+
- **Purely Functional**: No side effects, no mutation, no I/O
|
|
9
|
+
- **Static Validation**: Symbols and functions validated at compile-time
|
|
10
|
+
- **High Performance**: Native Rust implementation with zero-copy FFI
|
|
11
|
+
- **Rich Standard Library**: 60+ built-in functions for strings, numbers, collections, and finance
|
|
12
|
+
- **Multiple Use Cases**:
|
|
13
|
+
- Business Rules Engine (insurance, loans, eligibility)
|
|
14
|
+
- Template Engine (secure alternative to ERB)
|
|
15
|
+
- Spreadsheet Formulas (Excel-compatible financial functions)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- Ruby 2.7 or higher
|
|
22
|
+
- Rust and Cargo (for building the native extension)
|
|
23
|
+
|
|
24
|
+
### Install from source
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
gem build amoskeag-rb.gemspec
|
|
28
|
+
gem install amoskeag-rb-0.1.0.gem
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or add to your Gemfile:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem 'amoskeag-rb', path: 'path/to/amoskeag-rb'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
require 'amoskeag-rb'
|
|
41
|
+
|
|
42
|
+
# Basic arithmetic
|
|
43
|
+
Amoskeag.eval_expression("2 + 2", {})
|
|
44
|
+
# => 4.0
|
|
45
|
+
|
|
46
|
+
# Using variables
|
|
47
|
+
Amoskeag.eval_expression("user.age * 2", { "user" => { "age" => 25 } })
|
|
48
|
+
# => 50.0
|
|
49
|
+
|
|
50
|
+
# String operations with pipe
|
|
51
|
+
Amoskeag.eval_expression('"hello world" | upcase', {})
|
|
52
|
+
# => "HELLO WORLD"
|
|
53
|
+
|
|
54
|
+
# Business rules with symbols
|
|
55
|
+
code = "if user.age >= 18 :adult else :minor end"
|
|
56
|
+
Amoskeag.eval_expression(code, { "user" => { "age" => 25 } }, [:adult, :minor])
|
|
57
|
+
# => :adult
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Core Concepts
|
|
61
|
+
|
|
62
|
+
### Compile Once, Evaluate Many
|
|
63
|
+
|
|
64
|
+
For best performance, compile programs once and evaluate them multiple times:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# Compile the program (with symbol validation)
|
|
68
|
+
program = Amoskeag.compile(
|
|
69
|
+
"if score >= 90 :A else :B end",
|
|
70
|
+
[:A, :B, :C, :D, :F]
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Evaluate with different data
|
|
74
|
+
Amoskeag.evaluate(program, { "score" => 95 }) # => :A
|
|
75
|
+
Amoskeag.evaluate(program, { "score" => 85 }) # => :B
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Data Types
|
|
79
|
+
|
|
80
|
+
Amoskeag supports seven fundamental types:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# Numbers (64-bit float)
|
|
84
|
+
Amoskeag.eval_expression("42", {}) # => 42.0
|
|
85
|
+
|
|
86
|
+
# Strings
|
|
87
|
+
Amoskeag.eval_expression('"hello"', {}) # => "hello"
|
|
88
|
+
|
|
89
|
+
# Booleans
|
|
90
|
+
Amoskeag.eval_expression("true and false", {}) # => false
|
|
91
|
+
|
|
92
|
+
# Nil
|
|
93
|
+
Amoskeag.eval_expression("nil", {}) # => nil
|
|
94
|
+
|
|
95
|
+
# Arrays
|
|
96
|
+
Amoskeag.eval_expression("[1, 2, 3]", {}) # => [1.0, 2.0, 3.0]
|
|
97
|
+
|
|
98
|
+
# Dictionaries (hashes)
|
|
99
|
+
Amoskeag.eval_expression('{"name": "Alice", "age": 30}', {})
|
|
100
|
+
# => {"name" => "Alice", "age" => 30.0}
|
|
101
|
+
|
|
102
|
+
# Symbols (validated at compile-time)
|
|
103
|
+
Amoskeag.eval_expression(":approved", {}, [:approved, :denied])
|
|
104
|
+
# => :approved
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Safe Navigation
|
|
108
|
+
|
|
109
|
+
Missing keys return `nil` instead of raising errors:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
Amoskeag.eval_expression("user.address.street", { "user" => {} })
|
|
113
|
+
# => nil
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
### Business Rules Engine
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# Define eligibility rules
|
|
122
|
+
rules = <<~AMOSKEAG
|
|
123
|
+
let applicant = {
|
|
124
|
+
"age": user.age,
|
|
125
|
+
"state": user.state,
|
|
126
|
+
"credit_score": user.credit_score
|
|
127
|
+
}
|
|
128
|
+
in let restricted_states = ["FL", "LA", "TX"]
|
|
129
|
+
in
|
|
130
|
+
if contains(restricted_states, applicant.state)
|
|
131
|
+
:deny
|
|
132
|
+
else if applicant.age < 18
|
|
133
|
+
:deny
|
|
134
|
+
else if applicant.credit_score < 650
|
|
135
|
+
:conditional
|
|
136
|
+
else
|
|
137
|
+
:approve
|
|
138
|
+
end
|
|
139
|
+
AMOSKEAG
|
|
140
|
+
|
|
141
|
+
# Compile once
|
|
142
|
+
program = Amoskeag.compile(rules, [:approve, :deny, :conditional])
|
|
143
|
+
|
|
144
|
+
# Evaluate for different users
|
|
145
|
+
user1 = { "age" => 25, "state" => "CA", "credit_score" => 720 }
|
|
146
|
+
Amoskeag.evaluate(program, { "user" => user1 }) # => :approve
|
|
147
|
+
|
|
148
|
+
user2 = { "age" => 30, "state" => "FL", "credit_score" => 750 }
|
|
149
|
+
Amoskeag.evaluate(program, { "user" => user2 }) # => :deny
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Template Engine
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
template = <<~AMOSKEAG
|
|
156
|
+
"Hello, " + user.name + "! " +
|
|
157
|
+
if user.premium
|
|
158
|
+
"Welcome to Premium. "
|
|
159
|
+
else
|
|
160
|
+
"Upgrade to Premium today! "
|
|
161
|
+
end +
|
|
162
|
+
"You have " + user.messages | string + " new messages."
|
|
163
|
+
AMOSKEAG
|
|
164
|
+
|
|
165
|
+
program = Amoskeag.compile(template, [])
|
|
166
|
+
|
|
167
|
+
data = {
|
|
168
|
+
"user" => {
|
|
169
|
+
"name" => "Alice",
|
|
170
|
+
"premium" => true,
|
|
171
|
+
"messages" => 5
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
Amoskeag.evaluate(program, data)
|
|
176
|
+
# => "Hello, Alice! Welcome to Premium. You have 5 new messages."
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Financial Calculations
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# Monthly payment for a $250,000 loan at 4.5% APR for 30 years
|
|
183
|
+
# pmt(rate, nper, pv, type_) - type_ is 0 for end-of-period payments
|
|
184
|
+
formula = "pmt(rate / 12, years * 12, -principal, 0) | round(2)"
|
|
185
|
+
program = Amoskeag.compile(formula, [])
|
|
186
|
+
|
|
187
|
+
data = {
|
|
188
|
+
"rate" => 0.045,
|
|
189
|
+
"years" => 30,
|
|
190
|
+
"principal" => 250000
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
Amoskeag.evaluate(program, data)
|
|
194
|
+
# => 1266.71
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Array and Collection Operations
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# Calculate average of filtered values
|
|
201
|
+
code = <<~AMOSKEAG
|
|
202
|
+
let nums = [95, 82, 67, 88, 91, 73]
|
|
203
|
+
in let passing = nums | filter(lambda(x) { x >= 70 })
|
|
204
|
+
in passing | avg | round(2)
|
|
205
|
+
AMOSKEAG
|
|
206
|
+
|
|
207
|
+
Amoskeag.eval_expression(code, {})
|
|
208
|
+
# => 85.8
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Standard Library
|
|
212
|
+
|
|
213
|
+
Amoskeag includes 60+ built-in functions organized into categories:
|
|
214
|
+
|
|
215
|
+
### String Functions
|
|
216
|
+
- `upcase`, `downcase`, `capitalize`
|
|
217
|
+
- `strip`, `lstrip`, `rstrip`
|
|
218
|
+
- `split`, `join`, `replace`
|
|
219
|
+
- `truncate`, `prepend`, `append`
|
|
220
|
+
|
|
221
|
+
### Numeric Functions
|
|
222
|
+
- `abs`, `ceil`, `floor`, `round`
|
|
223
|
+
- `max`, `min`, `clamp`
|
|
224
|
+
- `plus`, `minus`, `times`, `divided_by`
|
|
225
|
+
|
|
226
|
+
### Collection Functions
|
|
227
|
+
- `size`, `first`, `last`, `at`
|
|
228
|
+
- `contains`, `sort`, `reverse`
|
|
229
|
+
- `sum`, `avg`, `max`, `min`
|
|
230
|
+
- `keys`, `values`
|
|
231
|
+
|
|
232
|
+
### Logic Functions
|
|
233
|
+
- `if_then_else`, `choose`
|
|
234
|
+
- `is_number`, `is_string`, `is_boolean`, `is_nil`, `is_array`, `is_dictionary`
|
|
235
|
+
- `coalesce`, `default`
|
|
236
|
+
|
|
237
|
+
### Financial Functions (Excel-compatible)
|
|
238
|
+
- **Time Value**: `pmt`, `pv`, `fv`, `nper`, `rate`
|
|
239
|
+
- **Investment**: `npv`, `irr`, `mirr`
|
|
240
|
+
- **Depreciation**: `sln`, `ddb`, `db`
|
|
241
|
+
- **Payment Components**: `ipmt`, `ppmt`, `cumipmt`, `cumprinc`
|
|
242
|
+
|
|
243
|
+
## Error Handling
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
begin
|
|
247
|
+
# Undefined symbol will raise CompileError
|
|
248
|
+
Amoskeag.compile(":invalid_symbol", [:valid, :symbols])
|
|
249
|
+
rescue Amoskeag::CompileError => e
|
|
250
|
+
puts "Compilation failed: #{e.message}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
begin
|
|
254
|
+
# Missing variable will raise EvalError
|
|
255
|
+
program = Amoskeag.compile("missing_var", [])
|
|
256
|
+
Amoskeag.evaluate(program, {})
|
|
257
|
+
rescue Amoskeag::EvalError => e
|
|
258
|
+
puts "Evaluation failed: #{e.message}"
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Performance Tips
|
|
263
|
+
|
|
264
|
+
1. **Compile once, evaluate many**: Compilation has overhead, so reuse compiled programs
|
|
265
|
+
2. **Use native types**: Ruby Symbols map directly to Amoskeag Symbols with zero overhead
|
|
266
|
+
3. **Keep data shallow**: Deep nesting requires more JSON serialization
|
|
267
|
+
4. **Avoid string concatenation in loops**: Use `join` function instead
|
|
268
|
+
|
|
269
|
+
## Thread Safety
|
|
270
|
+
|
|
271
|
+
Compiled programs are immutable and thread-safe. You can safely evaluate the same program from multiple threads concurrently:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
program = Amoskeag.compile("x * 2", [])
|
|
275
|
+
|
|
276
|
+
threads = 10.times.map do |i|
|
|
277
|
+
Thread.new do
|
|
278
|
+
Amoskeag.evaluate(program, { "x" => i })
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
results = threads.map(&:value)
|
|
283
|
+
# => [0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Building from Source
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# Clone the repository
|
|
290
|
+
git clone https://github.com/durable-oss/amoskeag-rb.git
|
|
291
|
+
cd amoskeag-rb
|
|
292
|
+
|
|
293
|
+
# Build and install the gem
|
|
294
|
+
gem build amoskeag-rb.gemspec
|
|
295
|
+
gem install amoskeag-rb-0.1.0.gem
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Development
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Build the native extension
|
|
302
|
+
cd amoskeag
|
|
303
|
+
ruby extconf.rb
|
|
304
|
+
make
|
|
305
|
+
|
|
306
|
+
# Run tests
|
|
307
|
+
rake test
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Architecture
|
|
311
|
+
|
|
312
|
+
The gem consists of four layers:
|
|
313
|
+
|
|
314
|
+
1. **Rust Core**: Pure Rust implementation of the Amoskeag compiler and interpreter (from [durable-oss/amoskeag](https://github.com/durable-oss/amoskeag))
|
|
315
|
+
2. **FFI Layer** (`amoskeag/src/lib.rs`): C-compatible FFI bindings using static library
|
|
316
|
+
3. **Ruby Extension** (`amoskeag/amoskeag_native.c`): Native Ruby C extension
|
|
317
|
+
4. **Ruby Wrapper** (`lib/amoskeag-rb.rb`): Idiomatic Ruby API with proper marshalling
|
|
318
|
+
|
|
319
|
+
Data flows through JSON serialization at the Ruby/C boundary for simplicity and type safety.
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT
|
|
324
|
+
|
|
325
|
+
## Contributing
|
|
326
|
+
|
|
327
|
+
Contributions are welcome! Please open issues or pull requests on the [GitHub repository](https://github.com/durable-oss/amoskeag-rb).
|
|
328
|
+
|
|
329
|
+
## Support
|
|
330
|
+
|
|
331
|
+
- GitHub Issues: https://github.com/durable-oss/amoskeag-rb/issues
|
|
332
|
+
- Documentation: https://github.com/durable-oss/amoskeag-rb
|
|
333
|
+
|
|
334
|
+
## Credits
|
|
335
|
+
|
|
336
|
+
Amoskeag is built by [Durable Programming](https://github.com/durable-oss) with a focus on security, correctness, and developer experience.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/amoskeag-rb.rb
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'amoskeag-rb/version'
|
|
5
|
+
|
|
6
|
+
# Load the native extension.
|
|
7
|
+
#
|
|
8
|
+
# Precompiled platform gems install the binary under a per-ABI directory
|
|
9
|
+
# (lib/amoskeag-rb/<ruby_major.minor>/amoskeag_native.so) so a single gem can
|
|
10
|
+
# carry binaries for several Ruby versions. We try that first, then fall back to
|
|
11
|
+
# the flat layouts used by a freshly `rake compile`d source checkout.
|
|
12
|
+
begin
|
|
13
|
+
ruby_abi = RUBY_VERSION[/\d+\.\d+/]
|
|
14
|
+
require_relative "amoskeag-rb/#{ruby_abi}/amoskeag_native"
|
|
15
|
+
rescue LoadError
|
|
16
|
+
begin
|
|
17
|
+
require_relative 'amoskeag-rb/amoskeag_native'
|
|
18
|
+
rescue LoadError
|
|
19
|
+
require 'amoskeag_native'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Amoskeag - A secure, functional DSL for business rules
|
|
24
|
+
#
|
|
25
|
+
# The native extension defines:
|
|
26
|
+
# - Amoskeag::Error < StandardError
|
|
27
|
+
# - Amoskeag::CompileError < Amoskeag::Error
|
|
28
|
+
# - Amoskeag::EvalError < Amoskeag::Error
|
|
29
|
+
# - Amoskeag::Program (compiled program class)
|
|
30
|
+
# - Amoskeag.compile(source, symbols = []) -> Program
|
|
31
|
+
# - Amoskeag.evaluate(program, data) -> Object
|
|
32
|
+
# - Amoskeag.eval_expression(source, data, symbols = []) -> Object
|
|
33
|
+
|
|
34
|
+
module Amoskeag
|
|
35
|
+
class << self
|
|
36
|
+
# Alias native methods for potential future wrapper use
|
|
37
|
+
alias compile_native compile
|
|
38
|
+
alias evaluate_native evaluate
|
|
39
|
+
alias eval_expression_native eval_expression
|
|
40
|
+
|
|
41
|
+
# Unwrap symbols from their hash representation at the top level only
|
|
42
|
+
# This allows expression results like `:adult` to be returned as symbols
|
|
43
|
+
# while keeping symbols nested in data structures in wrapped format
|
|
44
|
+
def unwrap_symbols(value)
|
|
45
|
+
case value
|
|
46
|
+
when Hash
|
|
47
|
+
# Check if this is a wrapped symbol at the top level
|
|
48
|
+
if value.keys == ["__symbol__"]
|
|
49
|
+
value["__symbol__"].to_sym
|
|
50
|
+
else
|
|
51
|
+
# Don't unwrap nested symbols - they're part of data structures
|
|
52
|
+
value
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Recursively wrap symbols for native processing
|
|
60
|
+
def wrap_symbols(value)
|
|
61
|
+
case value
|
|
62
|
+
when Symbol
|
|
63
|
+
{ "__symbol__" => value.to_s }
|
|
64
|
+
when Hash
|
|
65
|
+
value.transform_values { |v| wrap_symbols(v) }
|
|
66
|
+
when Array
|
|
67
|
+
value.map { |v| wrap_symbols(v) }
|
|
68
|
+
else
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Numeric classes the native extension knows how to convert.
|
|
74
|
+
#
|
|
75
|
+
# BigDecimal maps to Amoskeag's exact base-10 Decimal, but bigdecimal is not
|
|
76
|
+
# loaded by default and this gem does not force it on host programs. The
|
|
77
|
+
# class is only included once something else has required it.
|
|
78
|
+
def scalar_types
|
|
79
|
+
types = [String, Integer, Float, TrueClass, FalseClass, NilClass, Symbol]
|
|
80
|
+
types << BigDecimal if defined?(BigDecimal)
|
|
81
|
+
types
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Validate that data is JSON-serializable
|
|
85
|
+
def validate_data(data, path = "data")
|
|
86
|
+
case data
|
|
87
|
+
when *scalar_types
|
|
88
|
+
# These are valid types (note: not all Numeric — only the ones the
|
|
89
|
+
# native extension can convert)
|
|
90
|
+
data
|
|
91
|
+
when Hash
|
|
92
|
+
data.each do |key, value|
|
|
93
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
94
|
+
raise ArgumentError, "#{path} key must be String or Symbol, got #{key.class}"
|
|
95
|
+
end
|
|
96
|
+
validate_data(value, "#{path}[#{key.inspect}]")
|
|
97
|
+
end
|
|
98
|
+
data
|
|
99
|
+
when Array
|
|
100
|
+
data.each_with_index do |item, idx|
|
|
101
|
+
validate_data(item, "#{path}[#{idx}]")
|
|
102
|
+
end
|
|
103
|
+
data
|
|
104
|
+
else
|
|
105
|
+
# Invalid type - include path information
|
|
106
|
+
raise ArgumentError, "#{path} contains invalid type #{data.class}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Override the compile method (no wrapping needed, just validation)
|
|
112
|
+
def self.compile(source, symbols = [])
|
|
113
|
+
compile_native(source, symbols)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Override the evaluate method to wrap/unwrap symbols and validate data
|
|
117
|
+
def self.evaluate(program, data)
|
|
118
|
+
validate_data(data)
|
|
119
|
+
result = evaluate_native(program, data)
|
|
120
|
+
unwrap_symbols(result)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Override the eval_expression method to wrap/unwrap symbols and validate data
|
|
124
|
+
def self.eval_expression(source, data, symbols = [])
|
|
125
|
+
validate_data(data)
|
|
126
|
+
result = eval_expression_native(source, data, symbols)
|
|
127
|
+
unwrap_symbols(result)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Override Program.new to raise NoMethodError
|
|
131
|
+
class Program
|
|
132
|
+
def self.new(*)
|
|
133
|
+
raise NoMethodError, "undefined method `new' for Amoskeag::Program"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: amoskeag-rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: x86_64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Durable Programming
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: json
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake-compiler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.2'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.2'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: yard
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.9'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.9'
|
|
83
|
+
description: |
|
|
84
|
+
Amoskeag is a purely functional, statically-validated Domain-Specific Language (DSL)
|
|
85
|
+
designed for high-security, sandboxed evaluation. It's perfect for:
|
|
86
|
+
|
|
87
|
+
- Business Rules Engines (insurance underwriting, loan approval)
|
|
88
|
+
- Template Engines (secure alternative to ERB with more power than Liquid)
|
|
89
|
+
- Spreadsheet Formula Engines (Excel-like calculations)
|
|
90
|
+
|
|
91
|
+
This gem provides native Ruby bindings to the Amoskeag library, compiled from Rust
|
|
92
|
+
for maximum performance and security.
|
|
93
|
+
email:
|
|
94
|
+
- djberube@durableprogramming.com
|
|
95
|
+
executables: []
|
|
96
|
+
extensions: []
|
|
97
|
+
extra_rdoc_files: []
|
|
98
|
+
files:
|
|
99
|
+
- CHANGELOG.md
|
|
100
|
+
- LICENSE
|
|
101
|
+
- README.md
|
|
102
|
+
- lib/amoskeag-rb.rb
|
|
103
|
+
- lib/amoskeag-rb/3.1/amoskeag_native.so
|
|
104
|
+
- lib/amoskeag-rb/3.2/amoskeag_native.so
|
|
105
|
+
- lib/amoskeag-rb/3.3/amoskeag_native.so
|
|
106
|
+
- lib/amoskeag-rb/3.4/amoskeag_native.so
|
|
107
|
+
- lib/amoskeag-rb/version.rb
|
|
108
|
+
homepage: https://github.com/durable-oss/amoskeag-rb
|
|
109
|
+
licenses:
|
|
110
|
+
- MIT
|
|
111
|
+
metadata:
|
|
112
|
+
homepage_uri: https://github.com/durable-oss/amoskeag-rb
|
|
113
|
+
source_code_uri: https://github.com/durable-oss/amoskeag-rb
|
|
114
|
+
changelog_uri: https://github.com/durable-oss/amoskeag-rb/blob/main/CHANGELOG.md
|
|
115
|
+
post_install_message: |2+
|
|
116
|
+
|
|
117
|
+
Thank you for installing amoskeag-rb!
|
|
118
|
+
|
|
119
|
+
Amoskeag is a secure, purely functional DSL for business rules, templates,
|
|
120
|
+
and spreadsheet formulas. It's designed to be immune to code injection
|
|
121
|
+
attacks while providing powerful expression evaluation capabilities.
|
|
122
|
+
|
|
123
|
+
Get started:
|
|
124
|
+
require 'amoskeag-rb'
|
|
125
|
+
result = Amoskeag.eval_expression("2 + 2", {}) # => 4.0
|
|
126
|
+
|
|
127
|
+
Documentation: https://github.com/durable-oss/amoskeag-rb
|
|
128
|
+
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '3.1'
|
|
137
|
+
- - "<"
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: 3.5.dev
|
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
requirements: []
|
|
146
|
+
rubygems_version: 3.5.23
|
|
147
|
+
signing_key:
|
|
148
|
+
specification_version: 4
|
|
149
|
+
summary: Ruby bindings for Amoskeag - a secure, functional DSL for business rules
|
|
150
|
+
test_files: []
|