mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
a standard library in Finch: path, time, proc, json
import json import path import time import proc -- found in std/ after the program's own files, so a program's own json.fch always wins. path: join, base, dir, ext, stem, parts, clean, is_abs. time: now, millis, sleep, date, local, format, days_in_month (clock_gettime, strftime through C import). proc: run, output (an error with the command's output when it fails), env, pid, cwd. json: parse (errors with line and column), stringify, pretty, new_*, get/at/set/push/has/names and str_or/int_or/float_or/bool_or; \u escapes with surrogate pairs. The C importer now leaves a plain (non-const) char * parameter a ptr[char], a buffer C writes into; only const char * is text going in. str(ptr) and str([]u8) are in the self-hosted compiler. boot.sh: 40 passed, 0 failed.
This commit is contained in:
parent
04eb0cce55
commit
ea6d2a57e5
13 changed files with 929 additions and 9 deletions
|
|
@ -193,7 +193,7 @@ fn visitField(CXCursor field, ptr data) -> i32 {
|
||||||
} else if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl {
|
} else if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl {
|
||||||
f.hidden = true // unions are kept as opaque bytes
|
f.hidden = true // unions are kept as opaque bytes
|
||||||
} else {
|
} else {
|
||||||
m := mapType(c.imp, ft, false)
|
m := mapType(c.imp, ft, false, false)
|
||||||
if m.ok {
|
if m.ok {
|
||||||
f.ty = m.ty
|
f.ty = m.ty
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -242,7 +242,9 @@ fn sized(int size, bool signed) -> Mapped {
|
||||||
|
|
||||||
// C type -> Finch type. Not ok (with a reason) if Finch can't express it.
|
// C type -> Finch type. Not ok (with a reason) if Finch can't express it.
|
||||||
// `signature`: a char * is a str (a function's parameters and result); anywhere else it stays a ptr[char].
|
// `signature`: a char * is a str (a function's parameters and result); anywhere else it stays a ptr[char].
|
||||||
fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
|
// A parameter that is a plain `char *` (not const) is a buffer C writes into, and stays a ptr[char];
|
||||||
|
// only `const char *` is text going in. A result that is a char * is always text coming out.
|
||||||
|
fn mapType(ptr[Importer] imp, CXType type, bool signature, bool param) -> Mapped {
|
||||||
t := clang_getCanonicalType(type)
|
t := clang_getCanonicalType(type)
|
||||||
size := int(clang_Type_getSizeOf(t))
|
size := int(clang_Type_getSizeOf(t))
|
||||||
k := t.kind
|
k := t.kind
|
||||||
|
|
@ -273,12 +275,14 @@ fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
|
||||||
if k == CXType_Pointer {
|
if k == CXType_Pointer {
|
||||||
pointee := clang_getCanonicalType(clang_getPointeeType(t))
|
pointee := clang_getCanonicalType(clang_getPointeeType(t))
|
||||||
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && signature {
|
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && signature {
|
||||||
return ok(ast.simple("str")) // char* in a signature is a C string
|
if !param || clang_isConstQualifiedType(pointee) != 0 {
|
||||||
|
return ok(ast.simple("str")) // a C string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto || pointee.kind == CXType_FunctionNoProto {
|
if pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto || pointee.kind == CXType_FunctionNoProto {
|
||||||
return ok(ast.simple("ptr")) // an opaque `ptr` you can pass around
|
return ok(ast.simple("ptr")) // an opaque `ptr` you can pass around
|
||||||
}
|
}
|
||||||
inner := mapType(imp, pointee, false)
|
inner := mapType(imp, pointee, false, false)
|
||||||
if !inner.ok || inner.ty.kind == "void" || inner.ty.kind == "fixed" {
|
if !inner.ok || inner.ty.kind == "void" || inner.ty.kind == "fixed" {
|
||||||
return ok(ast.simple("ptr"))
|
return ok(ast.simple("ptr"))
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +299,7 @@ fn mapType(ptr[Importer] imp, CXType type, bool signature) -> Mapped {
|
||||||
return ok(ast.Type(kind: "named", name: name, module: "C"))
|
return ok(ast.Type(kind: "named", name: name, module: "C"))
|
||||||
}
|
}
|
||||||
if k == CXType_ConstantArray {
|
if k == CXType_ConstantArray {
|
||||||
inner := mapType(imp, clang_getArrayElementType(t), false)
|
inner := mapType(imp, clang_getArrayElementType(t), false, false)
|
||||||
if !inner.ok {
|
if !inner.ok {
|
||||||
return inner
|
return inner
|
||||||
}
|
}
|
||||||
|
|
@ -447,7 +451,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
|
||||||
imp.out.fns[name] = f
|
imp.out.fns[name] = f
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r := mapType(imp, clang_getResultType(ft), true)
|
r := mapType(imp, clang_getResultType(ft), true, false)
|
||||||
if r.ok {
|
if r.ok {
|
||||||
f.ret = r.ty
|
f.ret = r.ty
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -458,7 +462,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
|
||||||
if f.unsupported.len > 0 {
|
if f.unsupported.len > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
p := mapType(imp, clang_getArgType(ft, u32(k)), true)
|
p := mapType(imp, clang_getArgType(ft, u32(k)), true, true)
|
||||||
if p.ok {
|
if p.ok {
|
||||||
f.params.push(p.ty)
|
f.params.push(p.ty)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -471,7 +475,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
|
||||||
if clang_Cursor_getStorageClass(c) != CX_SC_Extern || imp.out.globals.has(name) {
|
if clang_Cursor_getStorageClass(c) != CX_SC_Extern || imp.out.globals.has(name) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g := mapType(imp, clang_getCursorType(c), false)
|
g := mapType(imp, clang_getCursorType(c), false, false)
|
||||||
if g.ok {
|
if g.ok {
|
||||||
imp.out.globals[name] = g.ty
|
imp.out.globals[name] = g.ty
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3729,6 +3729,14 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
||||||
} else if k == "bool" {
|
} else if k == "bool" {
|
||||||
z := op(g, "zext i1 " + v.v + " to i32")
|
z := op(g, "zext i1 " + v.v + " to i32")
|
||||||
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
|
emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")")
|
||||||
|
} else if k == "ptr" {
|
||||||
|
// a C string, copied
|
||||||
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||||
|
emit(g, "call void @finch_str_from_c(ptr " + out + ", ptr " + v.v + ")")
|
||||||
|
} else if k == "array" && (v.ty.elem[0].kind == "u8" || v.ty.elem[0].kind == "char") {
|
||||||
|
// bytes become text
|
||||||
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||||
|
emit(g, "call void @finch_str_from_bytes(ptr " + out + ", ptr " + spill(g, v) + ")")
|
||||||
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" || k == "enum" {
|
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" || k == "enum" {
|
||||||
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
||||||
emitFormat(g, out, v.v, v.ty, false)
|
emitFormat(g, out, v.v, v.ty, false)
|
||||||
|
|
@ -4430,6 +4438,7 @@ fn runtimeDecls() -> str {
|
||||||
"declare i32 @printf(ptr, ...)",
|
"declare i32 @printf(ptr, ...)",
|
||||||
"declare i32 @fflush(ptr)",
|
"declare i32 @fflush(ptr)",
|
||||||
"declare void @finch_str_from_c(ptr, ptr)",
|
"declare void @finch_str_from_c(ptr, ptr)",
|
||||||
|
"declare void @finch_str_from_bytes(ptr, ptr)",
|
||||||
"declare void @exit(i32) noreturn",
|
"declare void @exit(i32) noreturn",
|
||||||
"declare void @free(ptr)",
|
"declare void @free(ptr)",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,21 @@ struct Loader {
|
||||||
[]ast.Program progs
|
[]ast.Program progs
|
||||||
[]str files
|
[]str files
|
||||||
[]str loaded
|
[]str loaded
|
||||||
|
str std // the standard library's folder, or ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// import json: the file next to the one that imports it, else the standard library's.
|
||||||
|
// A program's own json.fch wins over the library's.
|
||||||
|
fn findModule(ptr[Loader] l, str from, str name) -> str {
|
||||||
|
local := dirOf(from) + "/" + name + ".fch"
|
||||||
|
if file_exists(local) || l.std.len == 0 {
|
||||||
|
return local
|
||||||
|
}
|
||||||
|
shared := l.std + "/" + name + ".fch"
|
||||||
|
if file_exists(shared) {
|
||||||
|
return shared
|
||||||
|
}
|
||||||
|
return local
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(ptr[Loader] l, str path, str module) {
|
fn load(ptr[Loader] l, str path, str module) {
|
||||||
|
|
@ -60,7 +75,7 @@ fn load(ptr[Loader] l, str path, str module) {
|
||||||
for name in imports {
|
for name in imports {
|
||||||
if !l.loaded.contains(name) {
|
if !l.loaded.contains(name) {
|
||||||
l.loaded.push(name)
|
l.loaded.push(name)
|
||||||
load(l, dirOf(path) + "/" + name + ".fch", name)
|
load(l, findModule(l, path, name), name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +164,11 @@ fn main([]str args) -> int {
|
||||||
}
|
}
|
||||||
|
|
||||||
loader := Loader()
|
loader := Loader()
|
||||||
|
for cand in [dirOf(args[0]) + "/../std", dirOf(args[0]) + "/std", "std"] {
|
||||||
|
if loader.std.len == 0 && file_exists(cand + "/path.fch") {
|
||||||
|
loader.std = cand
|
||||||
|
}
|
||||||
|
}
|
||||||
load(addr(loader), file, "")
|
load(addr(loader), file, "")
|
||||||
|
|
||||||
// C headers, from every file; their folders are searched first for "local.h"
|
// C headers, from every file; their folders are searched first for "local.h"
|
||||||
|
|
|
||||||
518
std/json.fch
Normal file
518
std/json.fch
Normal file
|
|
@ -0,0 +1,518 @@
|
||||||
|
// JSON: read it, build it, write it.
|
||||||
|
//
|
||||||
|
// import json
|
||||||
|
//
|
||||||
|
// doc := try json.parse("{\"name\": \"Finch\", \"tags\": [\"fast\", \"small\"], \"stars\": 5}")
|
||||||
|
// print(doc.get("name").str_or("")) Finch
|
||||||
|
// print(doc.get("tags").at(1).str_or("")) small
|
||||||
|
// print(doc.get("stars").int_or(0)) 5
|
||||||
|
// print(doc.get("missing").is_null()) true (a missing key is null, so chains never fail)
|
||||||
|
//
|
||||||
|
// out := json.new_object()
|
||||||
|
// out.set("ok", json.new_bool(true))
|
||||||
|
// list := json.new_list()
|
||||||
|
// list.push(json.new_number(1.5))
|
||||||
|
// out.set("values", list)
|
||||||
|
// print(json.stringify(out)) {"ok":true,"values":[1.5]}
|
||||||
|
// print(json.pretty(out)) the same, over several lines
|
||||||
|
|
||||||
|
enum Kind { Null, Bool, Number, Text, List, Object }
|
||||||
|
|
||||||
|
// One JSON value. `kind` says which of the other fields it uses: flag for Bool, num for Number,
|
||||||
|
// text for Text, items for List, and items with keys (the same length, in the same order) for Object.
|
||||||
|
struct Json {
|
||||||
|
Kind kind
|
||||||
|
bool flag
|
||||||
|
float num
|
||||||
|
str text
|
||||||
|
[]Json items
|
||||||
|
[]str keys
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_null() -> Json {
|
||||||
|
return Json(kind: Kind.Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_bool(bool b) -> Json {
|
||||||
|
return Json(kind: Kind.Bool, flag: b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_number(float n) -> Json {
|
||||||
|
return Json(kind: Kind.Number, num: n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_text(str s) -> Json {
|
||||||
|
return Json(kind: Kind.Text, text: s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_list() -> Json {
|
||||||
|
return Json(kind: Kind.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_object() -> Json {
|
||||||
|
return Json(kind: Kind.Object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- looking inside ----------
|
||||||
|
|
||||||
|
fn Json.is_null() -> bool {
|
||||||
|
return self.kind == Kind.Null
|
||||||
|
}
|
||||||
|
|
||||||
|
// the number of elements of a list, of keys of an object, of bytes of a text; 0 for the rest
|
||||||
|
fn Json.len() -> int {
|
||||||
|
return match self.kind {
|
||||||
|
List, Object -> self.items.len
|
||||||
|
Text -> self.text.len
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Json.has(str key) -> bool {
|
||||||
|
return self.kind == Kind.Object && self.keys.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value of a key of an object; null if there is none (or this is not an object)
|
||||||
|
fn Json.get(str key) -> Json {
|
||||||
|
if self.kind != Kind.Object {
|
||||||
|
return new_null()
|
||||||
|
}
|
||||||
|
i := self.keys.find(key)
|
||||||
|
if i < 0 {
|
||||||
|
return new_null()
|
||||||
|
}
|
||||||
|
return self.items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// the element of a list; null if there is none
|
||||||
|
fn Json.at(int i) -> Json {
|
||||||
|
if self.kind != Kind.List || i < 0 || i >= self.items.len {
|
||||||
|
return new_null()
|
||||||
|
}
|
||||||
|
return self.items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value if it is of that kind, else what was given
|
||||||
|
fn Json.str_or(str fallback) -> str {
|
||||||
|
if self.kind == Kind.Text {
|
||||||
|
return self.text
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Json.float_or(float fallback) -> float {
|
||||||
|
if self.kind == Kind.Number {
|
||||||
|
return self.num
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Json.int_or(int fallback) -> int {
|
||||||
|
if self.kind == Kind.Number {
|
||||||
|
return int(self.num)
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Json.bool_or(bool fallback) -> bool {
|
||||||
|
if self.kind == Kind.Bool {
|
||||||
|
return self.flag
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// the keys of an object, in the order they were written
|
||||||
|
fn Json.names() -> []str {
|
||||||
|
return self.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- building ----------
|
||||||
|
|
||||||
|
// add to the end of a list
|
||||||
|
fn Json.push(Json v) {
|
||||||
|
self.items.push(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// give a key a value in an object; a key that is already there is replaced
|
||||||
|
fn Json.set(str key, Json v) {
|
||||||
|
i := self.keys.find(key)
|
||||||
|
if i >= 0 {
|
||||||
|
self.items[i] = v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.keys.push(key)
|
||||||
|
self.items.push(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- writing ----------
|
||||||
|
|
||||||
|
fn quote(str s) -> str {
|
||||||
|
[]str parts = ["\""]
|
||||||
|
for c in s {
|
||||||
|
n := int(c)
|
||||||
|
if c == '"' {
|
||||||
|
parts.push("\\\"")
|
||||||
|
} else if c == '\\' {
|
||||||
|
parts.push("\\\\")
|
||||||
|
} else if c == '\n' {
|
||||||
|
parts.push("\\n")
|
||||||
|
} else if c == '\r' {
|
||||||
|
parts.push("\\r")
|
||||||
|
} else if c == '\t' {
|
||||||
|
parts.push("\\t")
|
||||||
|
} else if n < 32 {
|
||||||
|
parts.push("\\u00" + hexdigit(n / 16) + hexdigit(n % 16))
|
||||||
|
} else {
|
||||||
|
parts.push(str(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push("\"")
|
||||||
|
return parts.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hexdigit(int n) -> str {
|
||||||
|
digits := "0123456789abcdef"
|
||||||
|
return str(digits[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_text(float n) -> str {
|
||||||
|
if n == float(int(n)) && n > -1000000000000000.0 && n < 1000000000000000.0 {
|
||||||
|
return str(int(n))
|
||||||
|
}
|
||||||
|
return str(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Writer {
|
||||||
|
[]str out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(Json j, str indent, str pad, ptr[Writer] w) {
|
||||||
|
nl := ""
|
||||||
|
inner := ""
|
||||||
|
sep := ","
|
||||||
|
colon := ":"
|
||||||
|
if indent.len > 0 {
|
||||||
|
nl = "\n"
|
||||||
|
inner = pad + indent
|
||||||
|
colon = ": "
|
||||||
|
}
|
||||||
|
match j.kind {
|
||||||
|
Null -> w.out.push("null")
|
||||||
|
Bool -> {
|
||||||
|
if j.flag {
|
||||||
|
w.out.push("true")
|
||||||
|
} else {
|
||||||
|
w.out.push("false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Number -> w.out.push(number_text(j.num))
|
||||||
|
Text -> w.out.push(quote(j.text))
|
||||||
|
List -> {
|
||||||
|
if j.items.len == 0 {
|
||||||
|
w.out.push("[]")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.out.push("[" + nl)
|
||||||
|
for i, item in j.items {
|
||||||
|
if i > 0 {
|
||||||
|
w.out.push(sep + nl)
|
||||||
|
}
|
||||||
|
w.out.push(inner)
|
||||||
|
write(item, indent, inner, w)
|
||||||
|
}
|
||||||
|
w.out.push(nl + pad + "]")
|
||||||
|
}
|
||||||
|
Object -> {
|
||||||
|
if j.items.len == 0 {
|
||||||
|
w.out.push("{}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.out.push("{" + nl)
|
||||||
|
for i, item in j.items {
|
||||||
|
if i > 0 {
|
||||||
|
w.out.push(sep + nl)
|
||||||
|
}
|
||||||
|
w.out.push(inner + quote(j.keys[i]) + colon)
|
||||||
|
write(item, indent, inner, w)
|
||||||
|
}
|
||||||
|
w.out.push(nl + pad + "}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value as text on one line
|
||||||
|
fn stringify(Json j) -> str {
|
||||||
|
w := Writer()
|
||||||
|
write(j, "", "", addr(w))
|
||||||
|
return w.out.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value as text over several lines, indented by two spaces
|
||||||
|
fn pretty(Json j) -> str {
|
||||||
|
w := Writer()
|
||||||
|
write(j, " ", "", addr(w))
|
||||||
|
return w.out.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- reading ----------
|
||||||
|
|
||||||
|
struct Reader {
|
||||||
|
str src
|
||||||
|
int i
|
||||||
|
}
|
||||||
|
|
||||||
|
// the line and column of a position, for a message
|
||||||
|
fn where(ptr[Reader] r) -> str {
|
||||||
|
line := 1
|
||||||
|
col := 1
|
||||||
|
for k in 0..r.i {
|
||||||
|
if k < r.src.len && r.src[k] == '\n' {
|
||||||
|
line += 1
|
||||||
|
col = 1
|
||||||
|
} else {
|
||||||
|
col += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "line " + str(line) + ", column " + str(col)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(ptr[Reader] r) -> char {
|
||||||
|
if r.i < r.src.len {
|
||||||
|
return r.src[r.i]
|
||||||
|
}
|
||||||
|
return char(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_space(ptr[Reader] r) {
|
||||||
|
while r.i < r.src.len && (r.src[r.i] == ' ' || r.src[r.i] == '\n' || r.src[r.i] == '\t' || r.src[r.i] == '\r') {
|
||||||
|
r.i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unexpected(ptr[Reader] r, str wanted) -> str {
|
||||||
|
if r.i >= r.src.len {
|
||||||
|
return "json: the text ends where " + wanted + " should be (" + where(r) + ")"
|
||||||
|
}
|
||||||
|
return "json: found '" + str(r.src[r.i]) + "' where " + wanted + " should be (" + where(r) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
// does `word` come next?
|
||||||
|
fn comes(ptr[Reader] r, str word) -> bool {
|
||||||
|
return r.src.len - r.i >= word.len && r.src.sub(r.i, r.i + word.len) == word
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_word(ptr[Reader] r, str word) -> ! {
|
||||||
|
if !comes(r, word) {
|
||||||
|
return error(unexpected(r, "'" + word + "'"))
|
||||||
|
}
|
||||||
|
r.i += word.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_value(char c) -> int {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
return int(c) - int('0')
|
||||||
|
}
|
||||||
|
if c >= 'a' && c <= 'f' {
|
||||||
|
return int(c) - int('a') + 10
|
||||||
|
}
|
||||||
|
if c >= 'A' && c <= 'F' {
|
||||||
|
return int(c) - int('A') + 10
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// a code point as UTF-8
|
||||||
|
fn utf8(int cp) -> str {
|
||||||
|
[]u8 b
|
||||||
|
if cp < 128 {
|
||||||
|
b.push(u8(cp))
|
||||||
|
} else if cp < 2048 {
|
||||||
|
b.push(u8(192 + cp / 64))
|
||||||
|
b.push(u8(128 + cp % 64))
|
||||||
|
} else if cp < 65536 {
|
||||||
|
b.push(u8(224 + cp / 4096))
|
||||||
|
b.push(u8(128 + (cp / 64) % 64))
|
||||||
|
b.push(u8(128 + cp % 64))
|
||||||
|
} else {
|
||||||
|
b.push(u8(240 + cp / 262144))
|
||||||
|
b.push(u8(128 + (cp / 4096) % 64))
|
||||||
|
b.push(u8(128 + (cp / 64) % 64))
|
||||||
|
b.push(u8(128 + cp % 64))
|
||||||
|
}
|
||||||
|
return str(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_hex4(ptr[Reader] r) -> int! {
|
||||||
|
n := 0
|
||||||
|
for k in 0..4 {
|
||||||
|
if r.i >= r.src.len || hex_value(r.src[r.i]) < 0 {
|
||||||
|
return error(unexpected(r, "a hexadecimal digit"))
|
||||||
|
}
|
||||||
|
n = n * 16 + hex_value(r.src[r.i])
|
||||||
|
r.i += 1
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_string(ptr[Reader] r) -> str! {
|
||||||
|
r.i += 1 // the opening quote
|
||||||
|
[]str parts
|
||||||
|
start := r.i
|
||||||
|
while true {
|
||||||
|
if r.i >= r.src.len {
|
||||||
|
return error("json: a text is never closed (" + where(r) + ")")
|
||||||
|
}
|
||||||
|
c := r.src[r.i]
|
||||||
|
if c == '"' {
|
||||||
|
parts.push(r.src.sub(start, r.i))
|
||||||
|
r.i += 1
|
||||||
|
return parts.join("")
|
||||||
|
}
|
||||||
|
if int(c) < 32 {
|
||||||
|
return error("json: a line break or control character inside a text (" + where(r) + ")")
|
||||||
|
}
|
||||||
|
if c != '\\' {
|
||||||
|
r.i += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts.push(r.src.sub(start, r.i))
|
||||||
|
r.i += 1
|
||||||
|
e := peek(r)
|
||||||
|
r.i += 1
|
||||||
|
if e == 'n' {
|
||||||
|
parts.push("\n")
|
||||||
|
} else if e == 't' {
|
||||||
|
parts.push("\t")
|
||||||
|
} else if e == 'r' {
|
||||||
|
parts.push("\r")
|
||||||
|
} else if e == 'b' {
|
||||||
|
parts.push(utf8(8))
|
||||||
|
} else if e == 'f' {
|
||||||
|
parts.push(utf8(12))
|
||||||
|
} else if e == '"' || e == '\\' || e == '/' {
|
||||||
|
parts.push(str(e))
|
||||||
|
} else if e == 'u' {
|
||||||
|
cp := try read_hex4(r)
|
||||||
|
// a pair of 😀 stands for one character above U+FFFF
|
||||||
|
if cp >= 55296 && cp < 56320 && comes(r, "\\u") {
|
||||||
|
r.i += 2
|
||||||
|
low := try read_hex4(r)
|
||||||
|
cp = 65536 + (cp - 55296) * 1024 + (low - 56320)
|
||||||
|
}
|
||||||
|
parts.push(utf8(cp))
|
||||||
|
} else {
|
||||||
|
return error("json: unknown escape \\" + str(e) + " in a text (" + where(r) + ")")
|
||||||
|
}
|
||||||
|
start = r.i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_number(ptr[Reader] r) -> Json! {
|
||||||
|
start := r.i
|
||||||
|
if peek(r) == '-' {
|
||||||
|
r.i += 1
|
||||||
|
}
|
||||||
|
while r.i < r.src.len && ((r.src[r.i] >= '0' && r.src[r.i] <= '9') || r.src[r.i] == '.' || r.src[r.i] == 'e' || r.src[r.i] == 'E' || r.src[r.i] == '+' || r.src[r.i] == '-') {
|
||||||
|
r.i += 1
|
||||||
|
}
|
||||||
|
n := float(r.src.sub(start, r.i)) or {
|
||||||
|
r.i = start
|
||||||
|
return error(unexpected(r, "a number"))
|
||||||
|
}
|
||||||
|
return new_number(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_value(ptr[Reader] r) -> Json! {
|
||||||
|
skip_space(r)
|
||||||
|
c := peek(r)
|
||||||
|
if r.i >= r.src.len {
|
||||||
|
return error(unexpected(r, "a value"))
|
||||||
|
}
|
||||||
|
if c == '{' {
|
||||||
|
obj := new_object()
|
||||||
|
r.i += 1
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) == '}' {
|
||||||
|
r.i += 1
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) != '"' {
|
||||||
|
return error(unexpected(r, "a key in quotes"))
|
||||||
|
}
|
||||||
|
key := try read_string(r)
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) != ':' {
|
||||||
|
return error(unexpected(r, "':'"))
|
||||||
|
}
|
||||||
|
r.i += 1
|
||||||
|
v := try read_value(r)
|
||||||
|
obj.set(key, v)
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) == ',' {
|
||||||
|
r.i += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if peek(r) == '}' {
|
||||||
|
r.i += 1
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
return error(unexpected(r, "',' or '}'"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c == '[' {
|
||||||
|
list := new_list()
|
||||||
|
r.i += 1
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) == ']' {
|
||||||
|
r.i += 1
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
v := try read_value(r)
|
||||||
|
list.push(v)
|
||||||
|
skip_space(r)
|
||||||
|
if peek(r) == ',' {
|
||||||
|
r.i += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if peek(r) == ']' {
|
||||||
|
r.i += 1
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
return error(unexpected(r, "',' or ']'"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
s := try read_string(r)
|
||||||
|
return new_text(s)
|
||||||
|
}
|
||||||
|
if c == 't' {
|
||||||
|
try expect_word(r, "true")
|
||||||
|
return new_bool(true)
|
||||||
|
}
|
||||||
|
if c == 'f' {
|
||||||
|
try expect_word(r, "false")
|
||||||
|
return new_bool(false)
|
||||||
|
}
|
||||||
|
if c == 'n' {
|
||||||
|
try expect_word(r, "null")
|
||||||
|
return new_null()
|
||||||
|
}
|
||||||
|
if c == '-' || (c >= '0' && c <= '9') {
|
||||||
|
return try read_number(r)
|
||||||
|
}
|
||||||
|
return error(unexpected(r, "a value"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// the JSON in `text`; an error with the line and column where it went wrong
|
||||||
|
fn parse(str text) -> Json! {
|
||||||
|
r := Reader(src: text)
|
||||||
|
v := try read_value(addr(r))
|
||||||
|
skip_space(addr(r))
|
||||||
|
if r.i < r.src.len {
|
||||||
|
return error(unexpected(addr(r), "the end of the text"))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
128
std/path.fch
Normal file
128
std/path.fch
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
// Paths, as text. Elements are separated by "/".
|
||||||
|
//
|
||||||
|
// import path
|
||||||
|
// path.join("a", "b.txt") a/b.txt
|
||||||
|
// path.base("/tmp/x/a.tar.gz") a.tar.gz path.stem(...) a.tar path.ext(...) .gz
|
||||||
|
// path.dir("/tmp/x/a.txt") /tmp/x
|
||||||
|
// path.clean("a//b/../c/./d") a/c/d
|
||||||
|
|
||||||
|
// a and b with one separator between; an absolute b replaces a
|
||||||
|
fn join(str a, str b) -> str {
|
||||||
|
if a.len == 0 {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
if b.len == 0 {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
if b[0] == '/' {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
if a[a.len - 1] == '/' {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_abs(str p) -> bool {
|
||||||
|
return p.len > 0 && p[0] == '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last element: "a/b.txt" -> "b.txt", "a/b/" -> "b", "" -> ".", "/" -> "/"
|
||||||
|
fn base(str p) -> str {
|
||||||
|
if p.len == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
end := p.len
|
||||||
|
while end > 1 && p[end - 1] == '/' {
|
||||||
|
end -= 1
|
||||||
|
}
|
||||||
|
if end == 1 && p[0] == '/' {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
start := end
|
||||||
|
while start > 0 && p[start - 1] != '/' {
|
||||||
|
start -= 1
|
||||||
|
}
|
||||||
|
return p.sub(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything before the last element: "a/b.txt" -> "a", "b.txt" -> ".", "/b" -> "/"
|
||||||
|
fn dir(str p) -> str {
|
||||||
|
end := p.len
|
||||||
|
while end > 1 && p[end - 1] == '/' {
|
||||||
|
end -= 1
|
||||||
|
}
|
||||||
|
i := end
|
||||||
|
while i > 0 && p[i - 1] != '/' {
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
while i > 1 && p[i - 1] == '/' {
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
return p.sub(0, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the part of the last element from its last dot: "a.tar.gz" -> ".gz"; "" when there is none
|
||||||
|
fn ext(str p) -> str {
|
||||||
|
b := base(p)
|
||||||
|
i := b.len
|
||||||
|
while i > 0 && b[i - 1] != '.' {
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
if i <= 1 { // no dot, or a name that only starts with one (.bashrc)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return b.sub(i - 1, b.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last element without its extension
|
||||||
|
fn stem(str p) -> str {
|
||||||
|
b := base(p)
|
||||||
|
e := ext(p)
|
||||||
|
return b.sub(0, b.len - e.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the elements of a path: "/a/b/c" -> ["a", "b", "c"]
|
||||||
|
fn parts(str p) -> []str {
|
||||||
|
[]str out
|
||||||
|
for piece in p.split("/") {
|
||||||
|
if piece.len > 0 {
|
||||||
|
out.push(piece)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// the shortest path with the same meaning: no "//", no ".", and ".." resolved where it can be
|
||||||
|
fn clean(str p) -> str {
|
||||||
|
if p.len == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
abs := is_abs(p)
|
||||||
|
[]str out
|
||||||
|
for piece in parts(p) {
|
||||||
|
if piece == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if piece == ".." {
|
||||||
|
if out.len > 0 && out[out.len - 1] != ".." {
|
||||||
|
out.pop()
|
||||||
|
} else if !abs {
|
||||||
|
out.push("..")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.push(piece)
|
||||||
|
}
|
||||||
|
joined := out.join("/")
|
||||||
|
if abs {
|
||||||
|
return "/" + joined
|
||||||
|
}
|
||||||
|
if joined.len == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return joined
|
||||||
|
}
|
||||||
48
std/proc.fch
Normal file
48
std/proc.fch
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Programs: run a command, read what it printed, look at the environment.
|
||||||
|
//
|
||||||
|
// import proc
|
||||||
|
// code := proc.run("make") the exit code; what the command prints goes to the screen
|
||||||
|
// text := try proc.output("git describe") what the command printed; an error if it failed
|
||||||
|
// proc.env("HOME", "/") an environment variable, or the second value if it is not set
|
||||||
|
import "stdlib.h"
|
||||||
|
import "unistd.h"
|
||||||
|
import "time.h"
|
||||||
|
|
||||||
|
// run a command; its output goes to the screen. Gives its exit code.
|
||||||
|
fn run(str command) -> int {
|
||||||
|
return shell(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run a command and give what it printed. Fails, with its output as the message, if it exits with an error.
|
||||||
|
fn output(str command) -> str! {
|
||||||
|
tmp := "/tmp/finch_out_" + str(getpid()) + "_" + str(int(time(null)))
|
||||||
|
code := shell("(" + command + ") > '" + tmp + "' 2>&1")
|
||||||
|
text := read_file(tmp) or ""
|
||||||
|
delete_file(tmp)
|
||||||
|
if code != 0 {
|
||||||
|
return error("'" + command + "' exited with code " + str(code) + ": " + text.trim())
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env(str name, str fallback) -> str {
|
||||||
|
v := getenv(name)
|
||||||
|
if v.len == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid() -> int {
|
||||||
|
return int(getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// the folder the program is running in
|
||||||
|
fn cwd() -> str {
|
||||||
|
[]char buf
|
||||||
|
for i in 0..4096 {
|
||||||
|
buf.push(char(0))
|
||||||
|
}
|
||||||
|
getcwd(buf.ptr, 4096)
|
||||||
|
return str(buf.ptr)
|
||||||
|
}
|
||||||
88
std/time.fch
Normal file
88
std/time.fch
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Time: the clock, sleeping, and dates.
|
||||||
|
//
|
||||||
|
// import time
|
||||||
|
// start := time.millis() milliseconds from a clock that only goes forward (to measure with)
|
||||||
|
// time.sleep(250) wait a quarter of a second
|
||||||
|
// time.now() seconds since 1970-01-01 00:00 UTC
|
||||||
|
// d := time.date(time.now()) the parts: d.year, d.month, d.day, d.hour, d.minute, d.second
|
||||||
|
// time.format(time.now(), "%Y-%m-%d %H:%M") as text, in the computer's time zone
|
||||||
|
import "time.h"
|
||||||
|
|
||||||
|
struct Date {
|
||||||
|
int year
|
||||||
|
int month // 1 to 12
|
||||||
|
int day // 1 to 31
|
||||||
|
int hour
|
||||||
|
int minute
|
||||||
|
int second
|
||||||
|
int weekday // 0 is Sunday
|
||||||
|
int yearday // 1 to 366
|
||||||
|
}
|
||||||
|
|
||||||
|
// seconds since 1970-01-01 00:00 UTC
|
||||||
|
fn now() -> int {
|
||||||
|
return int(time(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
// milliseconds on a clock that only goes forward: for measuring how long something took
|
||||||
|
fn millis() -> int {
|
||||||
|
timespec ts
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, addr(ts))
|
||||||
|
return int(ts.tv_sec) * 1000 + int(ts.tv_nsec) / 1000000
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(int ms) {
|
||||||
|
timespec ts
|
||||||
|
ts.tv_sec = ms / 1000
|
||||||
|
ts.tv_nsec = (ms % 1000) * 1000000
|
||||||
|
nanosleep(addr(ts), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parts(tm t) -> Date {
|
||||||
|
return Date(year: int(t.tm_year) + 1900, month: int(t.tm_mon) + 1, day: int(t.tm_mday), hour: int(t.tm_hour),
|
||||||
|
minute: int(t.tm_min), second: int(t.tm_sec), weekday: int(t.tm_wday), yearday: int(t.tm_yday) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the date and time of a moment, in UTC
|
||||||
|
fn date(int unix) -> Date {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
gmtime_r(addr(stamp), addr(t))
|
||||||
|
return parts(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the date and time of a moment, in the computer's time zone
|
||||||
|
fn local(int unix) -> Date {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
localtime_r(addr(stamp), addr(t))
|
||||||
|
return parts(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a moment as text, in the computer's time zone: %Y year, %m month, %d day, %H hour, %M minute, %S second
|
||||||
|
// (the rest of C's strftime works too: %A weekday name, %B month name, ...)
|
||||||
|
fn format(int unix, str pattern) -> str {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
localtime_r(addr(stamp), addr(t))
|
||||||
|
[]char buf
|
||||||
|
for i in 0..256 {
|
||||||
|
buf.push(char(0))
|
||||||
|
}
|
||||||
|
strftime(buf.ptr, 256, pattern, addr(t))
|
||||||
|
return str(buf.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the number of days in a month (month is 1 to 12)
|
||||||
|
fn days_in_month(int year, int month) -> int {
|
||||||
|
if month == 2 {
|
||||||
|
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
|
||||||
|
return 29
|
||||||
|
}
|
||||||
|
return 28
|
||||||
|
}
|
||||||
|
if month == 4 || month == 6 || month == 9 || month == 11 {
|
||||||
|
return 30
|
||||||
|
}
|
||||||
|
return 31
|
||||||
|
}
|
||||||
35
tests/new/std_json.fch
Normal file
35
tests/new/std_json.fch
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
fn main() -> ! {
|
||||||
|
doc := try json.parse("{\"name\": \"Finch\", \"tags\": [\"fast\", \"small\"], \"stars\": 5, \"pi\": 3.25, \"ok\": true, \"none\": null, \"nested\": {\"deep\": [1, [2, 3]]}}")
|
||||||
|
print(doc.get("name").str_or(""), doc.get("tags").at(1).str_or(""), doc.get("stars").int_or(0), doc.get("pi").float_or(0.0))
|
||||||
|
print(doc.get("ok").bool_or(false), doc.get("none").is_null(), doc.get("missing").is_null(), doc.has("name"), doc.has("nope"))
|
||||||
|
print(doc.get("nested").get("deep").at(1).at(0).int_or(-1), doc.get("tags").len(), doc.names())
|
||||||
|
print(json.stringify(doc))
|
||||||
|
|
||||||
|
out := json.new_object()
|
||||||
|
out.set("greeting", json.new_text("hi \"there\"\n"))
|
||||||
|
list := json.new_list()
|
||||||
|
list.push(json.new_number(1))
|
||||||
|
list.push(json.new_number(2.5))
|
||||||
|
list.push(json.new_bool(false))
|
||||||
|
list.push(json.new_null())
|
||||||
|
out.set("values", list)
|
||||||
|
out.set("greeting", json.new_text("replaced"))
|
||||||
|
out.set("empty", json.new_object())
|
||||||
|
print(json.stringify(out))
|
||||||
|
print(json.pretty(out))
|
||||||
|
|
||||||
|
again := try json.parse(json.pretty(out))
|
||||||
|
print(json.stringify(again) == json.stringify(out))
|
||||||
|
print((try json.parse("\"caf\\u00e9 \\ud83d\\ude00 \\/\"")).str_or(""))
|
||||||
|
print((try json.parse(" [ ] ")).len(), (try json.parse("-12.5e1")).float_or(0.0))
|
||||||
|
|
||||||
|
for bad in ["{\"a\": }", "[1, 2", "{\"a\" 1}", "tru", "[1] x", "\"open", "", "[1,\n oops]"] {
|
||||||
|
json.parse(bad) or {
|
||||||
|
print(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
print("parsed?!", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
tests/new/std_json.out
Normal file
26
tests/new/std_json.out
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
Finch small 5 3.25
|
||||||
|
true true true true false
|
||||||
|
2 2 ["name", "tags", "stars", "pi", "ok", "none", "nested"]
|
||||||
|
{"name":"Finch","tags":["fast","small"],"stars":5,"pi":3.25,"ok":true,"none":null,"nested":{"deep":[1,[2,3]]}}
|
||||||
|
{"greeting":"replaced","values":[1,2.5,false,null],"empty":{}}
|
||||||
|
{
|
||||||
|
"greeting": "replaced",
|
||||||
|
"values": [
|
||||||
|
1,
|
||||||
|
2.5,
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"empty": {}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
café 😀 /
|
||||||
|
0 -125
|
||||||
|
json: found '}' where a value should be (line 1, column 7)
|
||||||
|
json: the text ends where ',' or ']' should be (line 1, column 6)
|
||||||
|
json: found '1' where ':' should be (line 1, column 6)
|
||||||
|
json: found 't' where 'true' should be (line 1, column 1)
|
||||||
|
json: found 'x' where the end of the text should be (line 1, column 5)
|
||||||
|
json: a text is never closed (line 1, column 6)
|
||||||
|
json: the text ends where a value should be (line 1, column 1)
|
||||||
|
json: found 'o' where a value should be (line 2, column 3)
|
||||||
10
tests/new/std_path.fch
Normal file
10
tests/new/std_path.fch
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import path
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
print(path.join("a", "b.txt"), path.join("a/", "b"), path.join("", "x"), path.join("a", "/abs"))
|
||||||
|
print(path.base("/tmp/x/a.tar.gz"), path.base("a/b/"), path.base(""), path.base("/"))
|
||||||
|
print(path.dir("/tmp/x/a.txt"), path.dir("b.txt"), path.dir("/b"), path.dir("a/b/"))
|
||||||
|
print(path.ext("a.tar.gz"), path.ext("noext"), path.ext(".bashrc"), path.stem("/x/a.tar.gz"))
|
||||||
|
print(path.clean("a//b/../c/./d"), path.clean("/../a"), path.clean("../../a"), path.clean(""), path.clean("a/.."))
|
||||||
|
print(path.parts("/a//b/c/"), path.is_abs("/a"), path.is_abs("a"))
|
||||||
|
}
|
||||||
6
tests/new/std_path.out
Normal file
6
tests/new/std_path.out
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
a/b.txt a/b x /abs
|
||||||
|
a.tar.gz b . /
|
||||||
|
/tmp/x . / a
|
||||||
|
.gz a.tar
|
||||||
|
a/c/d /a ../../a . .
|
||||||
|
["a", "b", "c"] true false
|
||||||
19
tests/new/std_time_proc.fch
Normal file
19
tests/new/std_time_proc.fch
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import time
|
||||||
|
import proc
|
||||||
|
|
||||||
|
fn main() -> ! {
|
||||||
|
d := time.date(86400 * 365 + 3661)
|
||||||
|
print(d.year, d.month, d.day, d.hour, d.minute, d.second, d.weekday, d.yearday)
|
||||||
|
print(time.days_in_month(2024, 2), time.days_in_month(2023, 2), time.days_in_month(1900, 2), time.days_in_month(2000, 2), time.days_in_month(2024, 4))
|
||||||
|
a := time.millis()
|
||||||
|
time.sleep(30)
|
||||||
|
waited := time.millis() - a
|
||||||
|
print(waited >= 25 && waited < 500)
|
||||||
|
print(time.now() > 1700000000, time.format(0, "%Y") == "1970" || time.format(0, "%Y") == "1969")
|
||||||
|
|
||||||
|
print((try proc.output("echo hello; echo world")).trim())
|
||||||
|
print(proc.run("exit 3"))
|
||||||
|
bad := proc.output("echo oops; exit 2") or err
|
||||||
|
print(bad)
|
||||||
|
print(proc.env("FINCH_SURELY_NOT_SET", "fallback"), proc.pid() > 0, proc.cwd().len > 0)
|
||||||
|
}
|
||||||
9
tests/new/std_time_proc.out
Normal file
9
tests/new/std_time_proc.out
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
1971 1 1 1 1 1 5 1
|
||||||
|
29 28 28 29 30
|
||||||
|
true
|
||||||
|
true true
|
||||||
|
hello
|
||||||
|
world
|
||||||
|
3
|
||||||
|
'echo oops; exit 2' exited with code 2: oops
|
||||||
|
fallback true true
|
||||||
Loading…
Reference in a new issue