mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
Time lib and http
This commit is contained in:
@@ -157,6 +157,23 @@ Build builtins:
|
||||
`examples/build.vrmm` for a tour (targets: `main`, `multi`, `test`, `bench`,
|
||||
`deploy`, `clean`).
|
||||
|
||||
## Standard library modules
|
||||
|
||||
The toolchain ships a small stdlib in `lib/`, imported by name like in V
|
||||
(`import os` resolves against `lib/` and `vendor/` from any directory).
|
||||
Module functions are namespaced: `os.exists()`, `json.encode()`, ...
|
||||
|
||||
| module | functions |
|
||||
| --- | --- |
|
||||
| `os` | filesystem + process: `exists`, `is_dir`, `is_file`, `mkdir`, `remove`, `copy`, `list_dir`, `glob`, `join`, `base`, `dir`, `read_lines`, `write_lines`, `cwd`, `env`, `exec` |
|
||||
| `json` | `encode`, `decode`, `pretty` — objects become structs, integral numbers decode as ints, `null` ⇄ `none` |
|
||||
| `strings` | `length`, `lines`, `split`, `join`, `replace`, `contains`, `starts_with`, `ends_with`, `upper`, `lower`, `trim`, `pad`, `pad_left`, `repeat`, `format`, `capitalize` |
|
||||
| `math` | `abs`, `min`, `max`, `floor`, `ceil`, `round`, `sqrt`, `pow`, `clamp`, `sign`, `pi` |
|
||||
| `http` | `get(url)`, `post(url, data)`, `get_text(url)` — return `{status, body}`; network failures throw (catchable), HTTP errors (404) are normal responses |
|
||||
| `time` | `now()` (epoch s), `ms()` (epoch ms), `format(t, "YYYY-MM-DD HH:mm:ss")`, `date(t)`, `clock(t)`, `parse("2026-08-25 13:36:45")` — Moment-style tokens |
|
||||
|
||||
Examples: `examples/imports.vr`, `examples/http.vr`, `examples/time.vr`.
|
||||
|
||||
## The VuurRaaf language
|
||||
|
||||
A small, V-flavored language. Values are 64-bit integers, 64-bit floats,
|
||||
@@ -260,6 +277,10 @@ fn main() {
|
||||
- bitwise operators: `& | ^ ~ << >>`
|
||||
- host builtins: `read_file` / `write_file`, `args()`, `getenv` / `setenv`,
|
||||
`exit`, `sleep`, `time()`, `type(x)`, `str(x)`, `int(x)`, `split` / `join`
|
||||
- HTTP: `http_get(url)` / `http_post(url, data)` return `{status, body}`
|
||||
structs and throw on network failure (catchable) — or use `import http`
|
||||
- date/time: `now()` (epoch s), `time_ms()`, `format_time(t, "YYYY-MM-DD")`,
|
||||
`parse_time(s)` (ISO timestamps) — or use `import time`
|
||||
- JSON: `json_encode(x)` / `json_decode(s)` — objects become structs, arrays
|
||||
become arrays, integral numbers decode as ints, `null` decodes to `none`
|
||||
- `none`: a literal for "no value" (JSON null); `x == none` compares,
|
||||
|
||||
+16
-1
@@ -232,12 +232,17 @@ fn (mut c Checker) check_stmt(st Stmt) ! {
|
||||
c.loop_depth--
|
||||
}
|
||||
.for_in_stmt {
|
||||
// for x in EnumName { ... } iterates the enum's variants
|
||||
if st.expr.kind == .ident && st.expr.name in c.enums {
|
||||
c.types[st.target] = TypeInfo{ kind: .enum_t, name: st.expr.name }
|
||||
} else {
|
||||
seq := c.check_expr(st.expr)!
|
||||
// iterate enums and arrays; unknown is allowed (dynamic)
|
||||
if seq.kind == .int_t || seq.kind == .float_t || seq.kind == .bool_t || seq.kind == .none_t {
|
||||
return error('cannot iterate a ${type_name(seq.kind)} (line ${st.line})')
|
||||
}
|
||||
c.types[st.target] = TypeInfo{ kind: .unknown }
|
||||
}
|
||||
if st.idx_target.len > 0 {
|
||||
c.types[st.idx_target] = TypeInfo{ kind: .int_t }
|
||||
}
|
||||
@@ -328,9 +333,14 @@ fn (mut c Checker) check_expr(e Expr) !TypeInfo {
|
||||
TypeInfo{ kind: .unknown }
|
||||
}
|
||||
.field {
|
||||
// enum variant: Color.red — the base is the enum type name, not a
|
||||
// variable, so it must be resolved before check_expr on the base
|
||||
if e.left.kind == .ident && e.left.name in c.enums {
|
||||
return TypeInfo{ kind: .enum_t, name: e.left.name }
|
||||
}
|
||||
base := c.check_expr(*e.left)!
|
||||
c.expect_struct_like(base, 'field access', e.line)!
|
||||
// enum variant: Color.red → enum_t
|
||||
// enum variant on an enum-typed receiver: Color.red → enum_t
|
||||
if base.kind == .enum_t {
|
||||
return TypeInfo{ kind: .enum_t, name: base.name }
|
||||
}
|
||||
@@ -599,6 +609,11 @@ fn builtin_result_type(name string) TypeInfo {
|
||||
'build_glob', 'build_ls' { TypeInfo{ kind: .array_t } }
|
||||
'build_run', 'build_test', 'build_bench', 'build_clean', 'build_exec_status',
|
||||
'build_exists', 'build_mkdir', 'build_rm', 'build_copy' { TypeInfo{ kind: .int_t } }
|
||||
// HTTP client: returns a {status, body} struct
|
||||
'http_get', 'http_post' { TypeInfo{ kind: .struct_t } }
|
||||
// date/time
|
||||
'now', 'time_ms', 'parse_time' { TypeInfo{ kind: .int_t } }
|
||||
'format_time' { TypeInfo{ kind: .string_t } }
|
||||
else { TypeInfo{ kind: .unknown } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,6 +974,14 @@ fn builtin_spec(name string) (int, int) {
|
||||
'build_is_dir' { native_build_is_dir, 1 }
|
||||
'cwd' { native_cwd, 0 }
|
||||
'json_pretty' { native_json_pretty, 1 }
|
||||
// HTTP client
|
||||
'http_get' { native_http_get, 1 }
|
||||
'http_post' { native_http_post, 2 }
|
||||
// date/time
|
||||
'now' { native_now, 0 }
|
||||
'time_ms' { native_time_ms, 0 }
|
||||
'format_time' { native_format_time, 2 }
|
||||
'parse_time' { native_parse_time, 1 }
|
||||
else { -1, 0 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,3 +141,13 @@ const native_repeat = 164
|
||||
const native_build_is_dir = 165
|
||||
const native_cwd = 166
|
||||
const native_json_pretty = 167
|
||||
|
||||
// HTTP client builtins
|
||||
const native_http_get = 168
|
||||
const native_http_post = 169
|
||||
|
||||
// date/time builtins
|
||||
const native_now = 170
|
||||
const native_time_ms = 171
|
||||
const native_format_time = 172
|
||||
const native_parse_time = 173
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// http.vr — the HTTP client (needs network access).
|
||||
// vr run examples/http.vr
|
||||
//
|
||||
// http.get / http.post return a struct with `status` (int) and `body`
|
||||
// (string). Network failures throw and can be caught with try/catch;
|
||||
// HTTP-level errors (404, 500) come back as normal responses.
|
||||
|
||||
import http
|
||||
import json
|
||||
|
||||
fn main() {
|
||||
let res = http.get("https://httpbin.org/get")
|
||||
println("status: " + str(res.status))
|
||||
|
||||
let data = json.decode(res.body)
|
||||
println("your IP (from httpbin): " + str(data.origin))
|
||||
|
||||
let post = http.post("https://httpbin.org/post", "name=vuurraaf")
|
||||
let echo = json.decode(post.body)
|
||||
println("echoed: " + str(echo.data))
|
||||
|
||||
// a 404 is a response, not an exception
|
||||
let nf = http.get("https://httpbin.org/status/404")
|
||||
println("not found status: " + str(nf.status))
|
||||
|
||||
// network failures are catchable
|
||||
try {
|
||||
http.get("http://127.0.0.1:1/nope")
|
||||
println("unreachable")
|
||||
} catch e {
|
||||
println("caught connection error")
|
||||
}
|
||||
println("done")
|
||||
}
|
||||
+75
-1
@@ -5,6 +5,15 @@ import os
|
||||
import json
|
||||
import strings
|
||||
import math
|
||||
import http
|
||||
import time
|
||||
|
||||
enum Suit {
|
||||
hearts
|
||||
diamonds
|
||||
clubs
|
||||
spades
|
||||
}
|
||||
|
||||
fn add(a, b) {
|
||||
return a + b
|
||||
@@ -310,7 +319,7 @@ fn test_module_imports() {
|
||||
}
|
||||
assert os.base("a/b/c.vr") == "c.vr"
|
||||
assert os.dir("a/b/c.vr") == "a/b"
|
||||
assert len(os.glob("lib/*.vr")) == 4
|
||||
assert len(os.glob("lib/*.vr")) == 6
|
||||
let tmp = os.join(os.cwd(), "tmp_test.txt")
|
||||
os.write_lines(tmp, ["x", "y"])
|
||||
assert len(os.read_lines(tmp)) == 2
|
||||
@@ -381,6 +390,71 @@ fn test_closures() {
|
||||
assert tag("warn") == "[warn]"
|
||||
}
|
||||
|
||||
fn test_enums() {
|
||||
// variant values
|
||||
assert Suit.hearts == 0
|
||||
assert Suit.spades == 3
|
||||
// enum.count()
|
||||
assert Suit.hearts.count() == 4
|
||||
// enum.to_string()
|
||||
assert Suit.spades.to_string() == "spades"
|
||||
// enums in expressions
|
||||
let next = Suit.hearts + 1
|
||||
assert next == 1
|
||||
// enum iteration
|
||||
let names = []
|
||||
for s in Suit {
|
||||
names = push(names, s.to_string())
|
||||
}
|
||||
assert len(names) == 4
|
||||
assert names[0] == "hearts"
|
||||
assert names[3] == "spades"
|
||||
// match on enum values
|
||||
assert suit_code(Suit.clubs) == "c"
|
||||
assert suit_code(Suit.hearts) == "h"
|
||||
}
|
||||
|
||||
fn suit_code(s) {
|
||||
match s {
|
||||
Suit.hearts { return "h" }
|
||||
Suit.clubs { return "c" }
|
||||
else { return "?" }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_http_errors() {
|
||||
// offline-safe: connecting to a closed local port fails fast
|
||||
try {
|
||||
http.get("http://127.0.0.1:1/nope")
|
||||
assert false
|
||||
} catch e {
|
||||
assert len(e) > 0
|
||||
}
|
||||
}
|
||||
|
||||
fn test_time() {
|
||||
let t = time.now()
|
||||
assert t > 1700000000
|
||||
// format produces fixed-width strings
|
||||
let day = time.date(t)
|
||||
assert len(day) == 10
|
||||
assert day[4] == "-"[0]
|
||||
let full = time.format(t, "YYYY-MM-DD HH:mm:ss")
|
||||
assert len(full) == 19
|
||||
// parse round-trips within a second
|
||||
let back = time.parse(full)
|
||||
assert abs(back - t) <= 1
|
||||
// ms is more precise than seconds
|
||||
assert time.ms() > t * 1000
|
||||
// invalid input throws
|
||||
try {
|
||||
time.parse("not a date")
|
||||
assert false
|
||||
} catch e {
|
||||
assert len(e) > 0
|
||||
}
|
||||
}
|
||||
|
||||
fn test_failing() {
|
||||
// this one is meant to fail — shows up in `vr test` output
|
||||
assert 1 == 2
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// time.vr — date/time helpers.
|
||||
// vr run examples/time.vr
|
||||
//
|
||||
// Tokens follow the Moment.js style: YYYY, MM, DD, HH, mm, ss, with
|
||||
// literals like "-" and ":". All values are Unix epoch seconds.
|
||||
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
let now = time.now()
|
||||
println("epoch seconds: " + str(now))
|
||||
println("date: " + time.date(now))
|
||||
println("clock: " + time.clock(now))
|
||||
println("full: " + time.format(now, "YYYY-MM-DD HH:mm:ss"))
|
||||
println("custom: " + time.format(now, "MM/DD/YYYY"))
|
||||
|
||||
// parse a timestamp back to epoch seconds
|
||||
let parsed = time.parse("2026-08-25 12:00:00")
|
||||
println("parsed epoch: " + str(parsed))
|
||||
|
||||
// milliseconds for timing
|
||||
let start = time.ms()
|
||||
let x = 0
|
||||
for i in 0..100000 {
|
||||
x = x + i
|
||||
}
|
||||
let elapsed = time.ms() - start
|
||||
println("loop took " + str(elapsed) + "ms (checksum " + str(x) + ")")
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// http.vr — HTTP client (part of the VuurRaaf stdlib).
|
||||
//
|
||||
// import http
|
||||
// let res = http.get("https://api.example.com/status")
|
||||
// println(str(res.status))
|
||||
// println(res.body)
|
||||
//
|
||||
// http.get / http.post return a struct with `status` (int) and `body`
|
||||
// (string). Network failures (DNS, refused, timeout) throw, so they can be
|
||||
// caught with try/catch; HTTP-level errors (404, 500) are normal responses.
|
||||
|
||||
fn get(url) {
|
||||
return http_get(url)
|
||||
}
|
||||
|
||||
fn post(url, data) {
|
||||
return http_post(url, data)
|
||||
}
|
||||
|
||||
// get_text returns just the response body as a string.
|
||||
fn get_text(url) {
|
||||
return http_get(url).body
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// time.vr — date/time helpers (part of the VuurRaaf stdlib).
|
||||
//
|
||||
// import time
|
||||
// println(time.format(time.now(), "YYYY-MM-DD HH:mm:ss"))
|
||||
// println(time.format(time.now(), "YYYY-MM-DD"))
|
||||
//
|
||||
// Tokens follow the Moment.js style: YYYY (year), MM (month), DD (day),
|
||||
// HH (24h hour), mm (minute), ss (second), plus literals like "-", ":".
|
||||
// All values are Unix epoch seconds. `time.parse` accepts common ISO
|
||||
// timestamps like "2026-08-25 14:03:22".
|
||||
|
||||
fn now() {
|
||||
return now()
|
||||
}
|
||||
|
||||
fn ms() {
|
||||
return time_ms()
|
||||
}
|
||||
|
||||
fn format(t, spec) {
|
||||
return format_time(t, spec)
|
||||
}
|
||||
|
||||
fn parse(s) {
|
||||
return parse_time(s)
|
||||
}
|
||||
|
||||
// date(t) formats just the calendar day: YYYY-MM-DD.
|
||||
fn date(t) {
|
||||
return format_time(t, "YYYY-MM-DD")
|
||||
}
|
||||
|
||||
// clock(t) formats just the wall clock: HH:mm:ss.
|
||||
fn clock(t) {
|
||||
return format_time(t, "HH:mm:ss")
|
||||
}
|
||||
+43
@@ -11,6 +11,7 @@ import os
|
||||
import math
|
||||
import rand
|
||||
import time
|
||||
import net.http
|
||||
import obj
|
||||
import compiler
|
||||
import assembler
|
||||
@@ -647,12 +648,54 @@ fn (mut v Vm) native(id int, _argc int) ! {
|
||||
s := v.json_pretty_value(x, 0) or { return error('json_pretty: ${err.msg()}') }
|
||||
v.push(v.alloc_str(s))!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// HTTP client
|
||||
native_http_get {
|
||||
url := v.pop_str()!
|
||||
resp := http.get(url) or { return error('http_get: ${err.msg()}') }
|
||||
v.push_http_response(resp)!
|
||||
}
|
||||
native_http_post {
|
||||
data := v.pop_str()!
|
||||
url := v.pop_str()!
|
||||
resp := http.post(url, data) or { return error('http_post: ${err.msg()}') }
|
||||
v.push_http_response(resp)!
|
||||
}
|
||||
// -------------------------------------------------------------------
|
||||
// date/time
|
||||
native_now {
|
||||
v.push(v.enc_int(time.now().unix()))!
|
||||
}
|
||||
native_time_ms {
|
||||
v.push(v.enc_int(time.now().unix_milli()))!
|
||||
}
|
||||
native_format_time {
|
||||
spec := v.pop_str()!
|
||||
t := v.dec_int(v.pop()!)
|
||||
v.push(v.alloc_str(time.unix(t).custom_format(spec)))!
|
||||
}
|
||||
native_parse_time {
|
||||
s := v.pop_str()!
|
||||
t := time.parse(s) or { return error('parse_time: ${err.msg()}') }
|
||||
v.push(v.enc_int(t.unix()))!
|
||||
}
|
||||
else {
|
||||
return error('unknown native builtin ${id}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// push_http_response wraps an HTTP response as a {status, body} struct value
|
||||
// so scripts can read res.status and res.body.
|
||||
fn (mut v Vm) push_http_response(resp http.Response) ! {
|
||||
body_h := v.alloc_str(resp.body)
|
||||
mut fields := []Field{len: 2}
|
||||
fields[0] = Field{ name: 'status', val: v.enc_int(i64(resp.status_code)) }
|
||||
fields[1] = Field{ name: 'body', val: body_h }
|
||||
v.structs << StructVal{ fields: fields }
|
||||
v.push(v.mkstruct_handle(v.structs.len - 1))!
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// format() — a small printf-style formatter: %d %i %f %s %x %X %% with
|
||||
// optional width, left-align (-), zero-padding (0), and precision (.N).
|
||||
|
||||
@@ -140,3 +140,13 @@ const native_repeat = 164
|
||||
const native_build_is_dir = 165
|
||||
const native_cwd = 166
|
||||
const native_json_pretty = 167
|
||||
|
||||
// HTTP client builtins
|
||||
const native_http_get = 168
|
||||
const native_http_post = 169
|
||||
|
||||
// date/time builtins
|
||||
const native_now = 170
|
||||
const native_time_ms = 171
|
||||
const native_format_time = 172
|
||||
const native_parse_time = 173
|
||||
|
||||
Reference in New Issue
Block a user