The purpose of this project is to create a development platform for small to medium software projects and proof of concept software projects. The system will offer:
- Rust-inspired language syntax
- integrated dataframes with SQL-like grammar for queries
- integrated REST webservices
- integrated testing framework
Oxide is the spiritual successor to Lollypop, a multi-paradigm language also featuring integrated dataframes with SQL-like grammar for queries, but built for the JVM and developed in the Scala programming language.
- The REPL is now available, and allows you to issue commands directly to the server.
- The database server is also now available and supports basic CRUD operations via REST for:
cargo build --release
You'll find the executables in ./target/release/
:
oxide_repl
is the Oxide REST client / REPLoxide_server
is the Oxide REST Server
To run the tests (~ 330 tests at the time of writing):
cargo test
The Oxide REPL is now available, and with it, you can issue commands directly to the server. Oxide can evaluate basic expressions:
$ oxide
Welcome to Oxide REPL. Enter "q!" to quit.
oxide.public[0]> 5 + 9
[0] i64 in 17.0 millis
14
oxide.public[1]> (2 * 7) + 12
[1] i64 in 12.9 millis
26
Use the range operator (..) to creates slices (array-like structures):
oxide.public[2]> 1..7
[2] Array in 8.0 millis
[1,2,3,4,5,6]
Use the factorial operator (¡):
oxide.public[3]> 5¡
[3] f64 in 5.3 millis
120.0
Use the exponent operators (², ³, .., ⁹):
oxide.public[4]> 5²
[4] i64 in 5.5 millis
25
oxide.public[5]> 7³
[5] i64 in 6.1 millis
343
Use SQL-like updates and queries to create and manage data collections:
Welcome to Oxide REPL. Enter "q!" to quit.
oxide.public[0]> drop table ns("ldaniels.securities.stocks")
[0] Boolean in 9.6 millis
true
oxide.public[1]> create table ns("ldaniels.securities.stocks") (
symbol: String(8),
exchange: String(8),
last_sale: f64
)
[1] Boolean in 9.5 millis
true
oxide.public[2]> append ns("interpreter.reverse.stocks")
from { symbol: "ABC", exchange: "AMEX", last_sale: 12.49 }
[2] RowsAffected in 9.2 millis
1
oxide.public[3]> append ns("interpreter.reverse.stocks")
from [
{ symbol: "TRX", exchange: "OTCBB", last_sale: 0.0076 },
{ symbol: "BOOM", exchange: "NYSE", last_sale: 56.88 },
{ symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }
]
[3] RowsAffected in 13.8 millis
3
oxide.public[4]> reverse from ns("interpreter.reverse.stocks")
[4] Table ~ 4 row(s) in 10.1 millis
|-------------------------------|
| symbol | exchange | last_sale |
|-------------------------------|
| JET | NASDAQ | 32.12 |
| BOOM | NYSE | 56.88 |
| TRX | OTCBB | 0.0076 |
| ABC | AMEX | 12.49 |
|-------------------------------|
The following command will create a new table in the a.b.stocks
namespace:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"columns": [{
"name": "symbol",
"param_type": "String(4)",
"default_value": null
}, {
"name": "exchange",
"param_type": "String(4)",
"default_value": null
}, {
"name": "lastSale",
"param_type": "f64",
"default_value": null
}],
"indices": [],
"partitions": []
}' \
http://0.0.0.0:8080/a/b/stocks
The following command will delete the existing table in the a.b.stocks
namespace:
curl -X DELETE http://0.0.0.0:8080/a/b/stocks
server response:
1
In this example we insert/overwrite a row into a new or existing table in the a.b.stocks
namespace:
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"columns": [{
"name": "symbol",
"value": "ABC"
}, {
"name": "exchange",
"value": "NYSE"
}, {
"name": "lastSale",
"value": 56.17
}],
"indices": [],
"partitions": []
}' \
http://0.0.0.0:8080/a/b/stocks/100
server response:
1
The following command will retrieve the content at offset 100
from the a.b.stocks
table:
curl -X GET http://0.0.0.0:8080/a/b/stocks/100
server response:
{
"id": 100,
"columns": [{
"name": "symbol",
"value": "ABC"
}, {
"name": "exchange",
"value": "NYSE"
}, {
"name": "lastSale",
"value": 56.17
}]
}
The following command will delete the existing table in the a.b.stocks
namespace:
curl -X DELETE http://0.0.0.0:8080/a/b/stocks/100
server response:
1
cal::now()
2025-01-25T08:11:50.493Z
import cal now():::day_of()
25
import cal now():::hour12()
12
import cal now():::hour24()
0
import cal now():::minute_of()
11
import cal now():::month_of()
50
import cal now():::second_of()
50
import cal now():::year_of()
2025
io::create_file("quote.json", { symbol: "TRX", exchange: "NYSE", last_sale: 45.32 })
52
io::exists("quote.json")
true
import io, util file := "temp_secret.txt" file:::create_file(md5("keepthissecret")) file:::read_text_file()
47338bd5f35bbb239092c36e30775b4a
io::stderr("Goodbye Cruel World")
Ack
io::stdout("Hello World")
Ack
import kungfu assert(matches([ 1 "a" "b" "c" ], [ 1 "a" "b" "c" ]))
true
import kungfu feature("Matches function", { "Compare Array contents: Equal": fn(ctx) => { assert(matches( [ 1 "a" "b" "c" ], [ 1 "a" "b" "c" ])) }, "Compare Array contents: Not Equal": fn(ctx) => { assert(!matches( [ 1 "a" "b" "c" ], [ 0 "x" "y" "z" ])) }, "Compare JSON contents (in sequence)": fn(ctx) => { assert(matches( { first: "Tom" last: "Lane" }, { first: "Tom" last: "Lane" })) }, "Compare JSON contents (out of sequence)": fn(ctx) => { assert(matches( { scores: [82 78 99], id: "A1537" }, { id: "A1537", scores: [82 78 99] })) } })
|--------------------------------------------------------------------------------------------------------------------------| | id | level | item | passed | result | |--------------------------------------------------------------------------------------------------------------------------| | 0 | 0 | Matches function | true | Ack | | 1 | 1 | Compare Array contents: Equal | true | Ack | | 2 | 2 | assert(matches([1, "a", "b", "c"], [1, "a", "b", "c"])) | true | true | | 3 | 1 | Compare Array contents: Not Equal | true | Ack | | 4 | 2 | assert(!matches([1, "a", "b", "c"], [0, "x", "y", "z"])) | true | true | | 5 | 1 | Compare JSON contents (in sequence) | true | Ack | | 6 | 2 | assert(matches({first: "Tom", last: "Lane"}, {first: "Tom", last: "Lane"})) | true | true | | 7 | 1 | Compare JSON contents (out of sequence) | true | Ack | | 8 | 2 | assert(matches({scores: [82, 78, 99], id: "A1537"}, {id: "A1537", scores: [82, 78, 99]})) | true | true | |--------------------------------------------------------------------------------------------------------------------------|
import kungfu::matches a := { scores: [82, 78, 99], first: "Tom", last: "Lane" } b := { last: "Lane", first: "Tom", scores: [82, 78, 99] } matches(a, b)
true
kungfu::type_of([12, 76, 444])
Array(3)
create table ns("platform.os.call") ( symbol: String(8), exchange: String(8), last_sale: f64 ) os::call("chmod", "777", oxide::home())
os::clear()
Ack
import str cur_dir := os::current_dir() prefix := iff(cur_dir:::ends_with("core"), "../..", ".") path_str := prefix + "/demoes/language/include_file.oxide" include path_str
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 0 | ABC | AMEX | 12.49 | | 1 | BOOM | NYSE | 56.88 | | 2 | JET | NASDAQ | 32.12 | |------------------------------------|
os::env()
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | id | key | value | |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 0 | CARGO | /Users/ldaniels/.rustup/toolchains/stable-aarch64-apple-darwin/bin/cargo | | 1 | CARGO_HOME | /Users/ldaniels/.cargo | | 2 | CARGO_MANIFEST_DIR | /Users/ldaniels/GitHub/oxide/src/core | | 3 | CARGO_PKG_AUTHORS | | | 4 | CARGO_PKG_DESCRIPTION | | | 5 | CARGO_PKG_HOMEPAGE | | | 6 | CARGO_PKG_LICENSE | | | 7 | CARGO_PKG_LICENSE_FILE | | | 8 | CARGO_PKG_NAME | core | | 9 | CARGO_PKG_README | | | 10 | CARGO_PKG_REPOSITORY | | | 11 | CARGO_PKG_RUST_VERSION | | | 12 | CARGO_PKG_VERSION | 0.1.0 | | 13 | CARGO_PKG_VERSION_MAJOR | 0 | | 14 | CARGO_PKG_VERSION_MINOR | 1 | | 15 | CARGO_PKG_VERSION_PATCH | 0 | | 16 | CARGO_PKG_VERSION_PRE | | | 17 | COMMAND_MODE | unix2003 | | 18 | DYLD_FALLBACK_LIBRARY_PATH | /Users/ldaniels/GitHub/oxide/target/debug/build/zstd-sys-8327af3d7ec62fa6/out:/Users/ldaniels/GitHub/oxide/target/debug/deps:/Users/ldaniels/GitHub/oxide/target/debug:/Users/ldaniels/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib:/Users/ldaniels/.rustup/toolchains/stable-aarch64-apple-darwin/lib:/Users/ldaniels/lib:/usr/local/lib:/usr/lib | | 19 | HOME | /Users/ldaniels | | 20 | IDEA_INITIAL_DIRECTORY | / | | 21 | JAVA_HOME | /Users/ldaniels/.sdkman/candidates/java/current | | 22 | LC_CTYPE | en_US.UTF-8 | | 23 | LOGNAME | ldaniels | | 24 | OLDPWD | / | | 25 | PATH | /Users/ldaniels/.sdkman/candidates/java/current/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Users/ldaniels/.cargo/bin:/opt/homebrew/bin:. | | 26 | PWD | /Users/ldaniels/GitHub/oxide | | 27 | RUSTC | /Users/ldaniels/.cargo/bin/rustc | | 28 | RUSTC_BOOTSTRAP | 1 | | 29 | RUSTUP_HOME | /Users/ldaniels/.rustup | | 30 | RUSTUP_TOOLCHAIN | stable-aarch64-apple-darwin | | 31 | RUST_BACKTRACE | short | | 32 | RUST_RECURSION_COUNT | 1 | | 33 | SDKMAN_CANDIDATES_API | https://api.sdkman.io/2 | | 34 | SDKMAN_CANDIDATES_DIR | /Users/ldaniels/.sdkman/candidates | | 35 | SDKMAN_DIR | /Users/ldaniels/.sdkman | | 36 | SDKMAN_PLATFORM | darwinarm64 | | 37 | SHELL | /bin/zsh | | 38 | SSH_AUTH_SOCK | /private/tmp/com.apple.launchd.7o9VuADAti/Listeners | | 39 | TERM | ansi | | 40 | TMPDIR | /var/folders/ld/hwrvzn011w79gftyb6vj8mg40000gn/T/ | | 41 | USER | ldaniels | | 42 | XPC_FLAGS | 0x0 | | 43 | XPC_SERVICE_NAME | application.com.jetbrains.intellij.505803.58851138 | | 44 | __CFBundleIdentifier | com.jetbrains.intellij | | 45 | __CF_USER_TEXT_ENCODING | 0x1F5:0x0:0x0 | |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
code := oxide::compile("2 ** 4") code()
16
oxide::debug("2 ** 4")
Pow(Literal(Number(I64Value(2))), Literal(Number(I64Value(4))))
a := 'Hello ' b := 'World' oxide::eval("a + b")
Hello World
from oxide::help() limit 3
|------------------------------------------------------------------------------------------------| | id | name | module | signature | description | returns | |------------------------------------------------------------------------------------------------| | 0 | url_encode | www | www::url_encode(s: String) | Encodes a URL string | String | | 1 | url_decode | www | www::url_decode(s: String) | Decodes a URL-encoded string | String | | 2 | serve | www | www::serve(n: u32) | Starts a local HTTP service | Ack | |------------------------------------------------------------------------------------------------|
from oxide::history() limit 3
|-------------------------------------------------------------------| | id | session_id | user_id | cpu_time_ms | input | |-------------------------------------------------------------------| | 0 | 1736823002231 | 501 | 2.081 | import oxide; help() | | 1 | 1736823002627 | 501 | 3.596 | import oxide; help() | | 2 | 1736827273725 | 501 | 0.971 | import oxide; help() | |-------------------------------------------------------------------|
oxide::home()
./oxide_db
oxide::println("Hello World")
Ack
oxide::reset()
Ack
oxide::uuid()
4c0ef86d-1828-4ce9-ac9f-f7a9338f8267
oxide::version()
0.3
str::ends_with('Hello World', 'World')
true
str::format("This {} the {}", "is", "way")
This is the way
str::index_of('The little brown fox', 'brown')
11
str::join(['1', 5, 9, '13'], ', ')
1, 5, 9, 13
str::left('Hello World', 5)
Hello
str::len('The little brown fox')
20
str::right('Hello World', 5)
World
str::split('Hello,there World', ' ,')
|------------| | id | value | |------------| | 0 | Hello | | 1 | there | | 2 | World | |------------|
str::starts_with('Hello World', 'World')
false
str::substring('Hello World', 0, 5)
Hello
str::to_string(125.75)
125.75
[+] stocks := ns("platform.compact.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "DMX", exchange: "NYSE", last_sale: 99.99 }, { symbol: "UNO", exchange: "OTC", last_sale: 0.2456 }, { symbol: "BIZ", exchange: "NYSE", last_sale: 23.66 }, { symbol: "GOTO", exchange: "OTC", last_sale: 0.1428 }, { symbol: "ABC", exchange: "AMEX", last_sale: 11.11 }, { symbol: "BOOM", exchange: "NASDAQ", last_sale: 0.0872 }, { symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }] ~> stocks [+] delete from stocks where last_sale > 1.0 [+] from stocks
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 1 | UNO | OTC | 0.2456 | | 3 | GOTO | OTC | 0.1428 | | 5 | BOOM | NASDAQ | 0.0872 | |------------------------------------|
tools::describe({ symbol: "BIZ", exchange: "NYSE", last_sale: 23.66 })
|----------------------------------------------------------| | id | name | type | default_value | is_nullable | |----------------------------------------------------------| | 0 | symbol | String(3) | BIZ | true | | 1 | exchange | String(4) | NYSE | true | | 2 | last_sale | f64 | 23.66 | true | |----------------------------------------------------------|
[+] stocks := ns("platform.fetch.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 12.49 }, { symbol: "BOOM", exchange: "NYSE", last_sale: 56.88 }, { symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }] ~> stocks [+] tools::fetch(stocks, 2)
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 2 | JET | NASDAQ | 32.12 | |------------------------------------|
import tools [+] stocks := ns("platform.pop.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 12.49 }, { symbol: "BOOM", exchange: "NYSE", last_sale: 56.88 }, { symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }] ~> stocks [+] stocks::pop(stocks)
Illegal structure platform.pop.stocks
import tools [+] stocks := ns("platform.push.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 12.49 }, { symbol: "BOOM", exchange: "NYSE", last_sale: 56.88 }, { symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }] ~> stocks [+] stocks::push({ symbol: "XYZ", exchange: "NASDAQ", last_sale: 24.78 }) [+] stocks
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 0 | ABC | AMEX | 12.49 | | 1 | BOOM | NYSE | 56.88 | | 2 | JET | NASDAQ | 32.12 | |------------------------------------|
import tools to_table(reverse(['cat', 'dog', 'ferret', 'mouse']))
|-------------| | id | value | |-------------| | 0 | mouse | | 1 | ferret | | 2 | dog | | 3 | cat | |-------------|
[+] import tools [+] stocks := ns("platform.scan.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 12.33 }, { symbol: "UNO", exchange: "OTC", last_sale: 0.2456 }, { symbol: "BIZ", exchange: "NYSE", last_sale: 9.775 }, { symbol: "GOTO", exchange: "OTC", last_sale: 0.1442 }, { symbol: "XYZ", exchange: "NYSE", last_sale: 0.0289 }] ~> stocks [+] delete from stocks where last_sale > 1.0 [+] stocks:::scan()
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 0 | ABC | AMEX | 12.33 | | 1 | UNO | OTC | 0.2456 | | 2 | BIZ | NYSE | 9.775 | | 3 | GOTO | OTC | 0.1442 | | 4 | XYZ | NYSE | 0.0289 | |------------------------------------|
tools::to_array("Hello")
|------------| | id | value | |------------| | 0 | H | | 1 | e | | 2 | l | | 3 | l | | 4 | o | |------------|
import tools::to_csv [+] stocks := ns("platform.csv.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 11.11 }, { symbol: "UNO", exchange: "OTC", last_sale: 0.2456 }, { symbol: "BIZ", exchange: "NYSE", last_sale: 23.66 }, { symbol: "GOTO", exchange: "OTC", last_sale: 0.1428 }, { symbol: "BOOM", exchange: "NASDAQ", last_sale: 0.0872 }] ~> stocks stocks:::to_csv()
|-----------------------------| | id | value | |-----------------------------| | 0 | "ABC","AMEX",11.11 | | 1 | "UNO","OTC",0.2456 | | 2 | "BIZ","NYSE",23.66 | | 3 | "GOTO","OTC",0.1428 | | 4 | "BOOM","NASDAQ",0.0872 | |-----------------------------|
import tools::to_json [+] stocks := ns("platform.json.stocks") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "ABC", exchange: "AMEX", last_sale: 11.11 }, { symbol: "UNO", exchange: "OTC", last_sale: 0.2456 }, { symbol: "BIZ", exchange: "NYSE", last_sale: 23.66 }, { symbol: "GOTO", exchange: "OTC", last_sale: 0.1428 }, { symbol: "BOOM", exchange: "NASDAQ", last_sale: 0.0872 }] ~> stocks stocks:::to_json()
|---------------------------------------------------------------| | id | value | |---------------------------------------------------------------| | 0 | {"symbol":"ABC","exchange":"AMEX","last_sale":11.11} | | 1 | {"symbol":"UNO","exchange":"OTC","last_sale":0.2456} | | 2 | {"symbol":"BIZ","exchange":"NYSE","last_sale":23.66} | | 3 | {"symbol":"GOTO","exchange":"OTC","last_sale":0.1428} | | 4 | {"symbol":"BOOM","exchange":"NASDAQ","last_sale":0.0872} | |---------------------------------------------------------------|
tools::to_table(['cat', 'dog', 'ferret', 'mouse'])
|-------------| | id | value | |-------------| | 0 | cat | | 1 | dog | | 2 | ferret | | 3 | mouse | |-------------|
util::base64('Hello World')
SGVsbG8gV29ybGQ=
(0b1011 & 0b1101):::to_binary()
'to_binary' is not a function (undefined)
util::hex('Hello World')
48656c6c6f20576f726c64
util::md5('Hello World')
b10a8db164e0754105b7a99be72e3fe5
util::to_ascii(177)
±
util::to_date(177)
1970-01-01T00:00:00.177Z
util::to_f32(4321)
4321
util::to_f64(4321)
4321
util::to_i8(88)
88
util::to_i16(88)
88
util::to_i32(88)
88
util::to_i64(88)
88
util::to_i128(88)
88
util::to_u8(88)
88
util::to_u16(88)
88
util::to_u32(88)
88
util::to_u64(88)
88
util::to_u128(88)
00000000-0000-0000-0000-000000000058
www::url_decode('http%3A%2F%2Fshocktrade.com%3Fname%3Dthe%20hero%26t%3D9998')
http://shocktrade.com?name=the hero&t=9998
www::url_encode('http://shocktrade.com?name=the hero&t=9998')
http%3A%2F%2Fshocktrade.com%3Fname%3Dthe%20hero%26t%3D9998
[+] www::serve(8822) [+] stocks := ns("platform.www.quotes") [+] table(symbol: String(8), exchange: String(8), last_sale: f64) ~> stocks [+] [{ symbol: "XINU", exchange: "NYSE", last_sale: 8.11 }, { symbol: "BOX", exchange: "NYSE", last_sale: 56.88 }, { symbol: "JET", exchange: "NASDAQ", last_sale: 32.12 }, { symbol: "ABC", exchange: "AMEX", last_sale: 12.49 }, { symbol: "MIU", exchange: "OTCBB", last_sale: 2.24 }] ~> stocks GET "http://localhost:8822/platform/www/quotes/1/4"
|------------------------------------| | id | symbol | exchange | last_sale | |------------------------------------| | 0 | BOX | NYSE | 56.88 | | 1 | JET | NASDAQ | 32.12 | | 2 | ABC | AMEX | 12.49 | |------------------------------------|
Remote Procedure Call (RPC) is a feature that allows Oxide to evaluate expressions across remote peers.
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"code": "5 + 5"
}' \
http://0.0.0.0:8080/rpc
server response:
10.0