replace leading _ for private symbols with keyword hide
This commit is contained in:
+2
-1
@@ -15,7 +15,8 @@ roadmap and milestone history.
|
|||||||
- file-local relative imports, import aliases, and qualified member access
|
- file-local relative imports, import aliases, and qualified member access
|
||||||
- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories,
|
- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories,
|
||||||
named types, and globals retain their original declaration or storage identity
|
named types, and globals retain their original declaration or storage identity
|
||||||
- native top-level declarations beginning with `_` are visible only within their source file; locals, fields, parameters, and C declarations are unaffected
|
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
||||||
|
leading underscores are ordinary identifier characters, and imports are always file-local
|
||||||
- relative `.h` imports as synthetic C header package namespaces
|
- relative `.h` imports as synthetic C header package namespaces
|
||||||
- root `main` validation with trap executable recovery for missing or unusable entry points
|
- root `main` validation with trap executable recovery for missing or unusable entry points
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,15 @@ mem :: import "@std/mem"
|
|||||||
value :: math.sum(other_math.value, 1)
|
value :: math.sum(other_math.value, 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Top-level declarations are public by default. Prefix a declaration with `hide`
|
||||||
|
to keep it local to its source file; leading underscores have no visibility
|
||||||
|
meaning. Imports are always file-local and cannot be hidden or re-exported:
|
||||||
|
|
||||||
|
```bro
|
||||||
|
hide helper func() i32 { return 42 }
|
||||||
|
hide State :: struct { value i32 }
|
||||||
|
```
|
||||||
|
|
||||||
Current prototype features:
|
Current prototype features:
|
||||||
|
|
||||||
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
||||||
|
|||||||
@@ -810,7 +810,7 @@
|
|||||||
|
|
||||||
33. explicit I/O provider (implemented)
|
33. explicit I/O provider (implemented)
|
||||||
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
|
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
|
||||||
- the compiler supplies a file-hidden macOS provider through an external no-argument C wrapper
|
- the compiler supplies a `hide system` macOS provider through an external no-argument C wrapper
|
||||||
- readers and writers pair an explicit provider with `stdin`, `stdout`, or `stderr`
|
- readers and writers pair an explicit provider with `stdin`, `stdout`, or `stderr`
|
||||||
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
|
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
|
||||||
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing
|
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing
|
||||||
@@ -819,8 +819,8 @@
|
|||||||
- bare qualified aliases use `Name :: alias package.Member` without adding a keyword
|
- bare qualified aliases use `Name :: alias package.Member` without adding a keyword
|
||||||
- functions/type factories, named types, and globals transparently retain the target identity;
|
- functions/type factories, named types, and globals transparently retain the target identity;
|
||||||
mutable global aliases therefore share the original storage
|
mutable global aliases therefore share the original storage
|
||||||
- aliases resolve transitively at load time, consume their file-local import, preserve leading-
|
- aliases resolve transitively at load time, consume their file-local import, preserve explicit
|
||||||
underscore visibility, and diagnose missing, hidden, unavailable, ambiguous, cyclic, or
|
`hide` visibility, and diagnose missing, hidden, unavailable, ambiguous, cyclic, or
|
||||||
conflicting targets
|
conflicting targets
|
||||||
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
||||||
|
|
||||||
@@ -828,7 +828,24 @@
|
|||||||
- pointer sigils are highlighted as operators
|
- pointer sigils are highlighted as operators
|
||||||
- type-factory calls in type positions and struct literals are highlighted as functions
|
- type-factory calls in type positions and struct literals are highlighted as functions
|
||||||
|
|
||||||
36. is it currently possible to access a subpackage from a parent package? if not, maybe it should be
|
36. transitive package namespaces (spike completed; no language change)
|
||||||
|
- imports remain file-local implementation details, including explicitly named imports such as
|
||||||
|
`rl :: import "@vendor/raylib"`; naming an import only chooses its local qualifier
|
||||||
|
- packages expose declarations, not their imports, so imported namespaces never become public or
|
||||||
|
transitively reachable package members
|
||||||
|
- callers import each package they use directly; subdirectory layout does not create namespaces
|
||||||
|
- this keeps package lookup shallow and deterministic and avoids overloading import aliases with
|
||||||
|
declaration visibility
|
||||||
|
|
||||||
|
36.5. explicit `hide` file-local declarations (implemented)
|
||||||
|
- `hide name ...` gives any named top-level function, global, native/C record, union, enum,
|
||||||
|
opaque/distinct type, or declaration/type alias the existing file-local semantics
|
||||||
|
- declarations remain public by default; a leading underscore is an ordinary identifier and `_`
|
||||||
|
remains the write-only sink
|
||||||
|
- hidden native and C declarations with the same name may coexist in separate files, while public
|
||||||
|
collisions are still diagnosed
|
||||||
|
- `hide` is reserved for named top-level declarations and is rejected on imports, locals,
|
||||||
|
parameters, fields, and anonymous declarations
|
||||||
|
|
||||||
37. add a debug package in `std` that provides debugging utilities
|
37. add a debug package in `std` that provides debugging utilities
|
||||||
- add `debug.print` function making use of `std/io` to print values to the console
|
- add `debug.print` function making use of `std/io` to print values to the console
|
||||||
|
|||||||
@@ -966,7 +966,7 @@ configure_io_main :: proc(checker: ^Checker) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider_name := symbol.intern(checker.symbols, "_system")
|
provider_name := symbol.intern(checker.symbols, "system")
|
||||||
provider := ast.INVALID_FUNCTION
|
provider := ast.INVALID_FUNCTION
|
||||||
for function, function_id in checker.ast_module.functions {
|
for function, function_id in checker.ast_module.functions {
|
||||||
if function.pkg != io_package || function.name != provider_name {
|
if function.pkg != io_package || function.name != provider_name {
|
||||||
@@ -986,7 +986,7 @@ configure_io_main :: proc(checker: ^Checker) {
|
|||||||
checker.template_diagnostics[main_template] = source.add(
|
checker.template_diagnostics[main_template] = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
main.span,
|
main.span,
|
||||||
"@std/io does not provide the required '_system func() Io' startup implementation",
|
"@std/io does not provide the required 'hide system func() Io' startup implementation",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "distinct": return .Keyword_Distinct
|
case "distinct": return .Keyword_Distinct
|
||||||
case "alias": return .Keyword_Alias
|
case "alias": return .Keyword_Alias
|
||||||
case "import": return .Keyword_Import
|
case "import": return .Keyword_Import
|
||||||
|
case "hide": return .Keyword_Hide
|
||||||
case "return": return .Keyword_Return
|
case "return": return .Keyword_Return
|
||||||
case "try": return .Keyword_Try
|
case "try": return .Keyword_Try
|
||||||
case "catch": return .Keyword_Catch
|
case "catch": return .Keyword_Catch
|
||||||
|
|||||||
+65
-28
@@ -26,6 +26,7 @@ Parser :: struct {
|
|||||||
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
|
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
|
||||||
// allow struct literals.
|
// allow struct literals.
|
||||||
no_struct_literal: bool,
|
no_struct_literal: bool,
|
||||||
|
hidden_names: [dynamic]symbol.Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_EXPRESSION_NESTING :: 256
|
MAX_EXPRESSION_NESTING :: 256
|
||||||
@@ -37,9 +38,31 @@ token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
|
|||||||
return parser.source_file.text[int(tok.span.start):int(tok.span.end)]
|
return parser.source_file.text[int(tok.span.start):int(tok.span.end)]
|
||||||
}
|
}
|
||||||
|
|
||||||
file_hidden_name :: proc(parser: ^Parser, tok: token.Token) -> bool {
|
file_hidden_name :: proc(parser: ^Parser, name: symbol.Id) -> bool {
|
||||||
name := token_text(parser, tok)
|
for hidden in parser.hidden_names {
|
||||||
return len(name) > 1 && name[0] == '_'
|
if hidden == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_hidden_names :: proc(parser: ^Parser) {
|
||||||
|
depth := 0
|
||||||
|
for item, index in parser.tokens.items {
|
||||||
|
#partial switch item.kind {
|
||||||
|
case .Left_Brace:
|
||||||
|
depth += 1
|
||||||
|
case .Right_Brace:
|
||||||
|
depth = max(depth-1, 0)
|
||||||
|
case .Keyword_Hide:
|
||||||
|
if depth == 0 && index+1 < len(parser.tokens.items) &&
|
||||||
|
parser.tokens.items[index+1].kind == .Identifier {
|
||||||
|
append(&parser.hidden_names, parser.tokens.items[index+1].symbol)
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span_from :: proc(first, last: source.Span) -> source.Span {
|
span_from :: proc(first, last: source.Span) -> source.Span {
|
||||||
@@ -396,7 +419,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
u32(name.symbol),
|
u32(name.symbol),
|
||||||
u32(qualifier),
|
u32(qualifier),
|
||||||
u32(parser.file),
|
u32(parser.file),
|
||||||
!symbol.is_valid(qualifier) && file_hidden_name(parser, name),
|
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
|
||||||
)
|
)
|
||||||
if current(parser).kind == .Left_Paren {
|
if current(parser).kind == .Left_Paren {
|
||||||
call := parse_call(parser, qualifier, first, name, 0, false)
|
call := parse_call(parser, qualifier, first, name, 0, false)
|
||||||
@@ -2192,7 +2215,7 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: bool) {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
||||||
@@ -2223,7 +2246,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
pkg=parser.pkg,
|
pkg=parser.pkg,
|
||||||
file=parser.file,
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
file_hidden=!c_abi && file_hidden_name(parser, name),
|
file_hidden=file_hidden,
|
||||||
has_body=false,
|
has_body=false,
|
||||||
variadic=variadic,
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
@@ -2242,7 +2265,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
pkg=parser.pkg,
|
pkg=parser.pkg,
|
||||||
file=parser.file,
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
file_hidden=!c_abi && file_hidden_name(parser, name),
|
file_hidden=file_hidden,
|
||||||
has_body=true,
|
has_body=true,
|
||||||
variadic=variadic,
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
@@ -2388,9 +2411,9 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
|||||||
return types.union_anonymous(&parser.module.type_store, fields[:], tag)
|
return types.union_anonymous(&parser.module.type_store, fields[:], tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
|
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: bool, is_union := false) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=!c_layout && file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||||
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
|
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
|
||||||
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
|
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
|
||||||
tag := types.INVALID
|
tag := types.INVALID
|
||||||
@@ -2452,9 +2475,9 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_opaque :: proc(parser: ^Parser, name: token.Token) {
|
parse_opaque :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||||
if !types.define_record(&parser.module.type_store, id, nil, false, true, false) {
|
if !types.define_record(&parser.module.type_store, id, nil, false, true, false) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
@@ -2480,10 +2503,10 @@ synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Ty
|
|||||||
return types.enum_anonymous(&parser.module.type_store, members, types.U16)
|
return types.enum_anonymous(&parser.module.type_store, members, types.U16)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
parse_distinct :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
child := parse_type(parser)
|
child := parse_type(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||||
if !types.define_distinct(&parser.module.type_store, id, child) {
|
if !types.define_distinct(&parser.module.type_store, id, child) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
@@ -2493,7 +2516,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
saved := parser.cursor
|
saved := parser.cursor
|
||||||
if current(parser).kind == .Identifier && peek(parser).kind == .Dot {
|
if current(parser).kind == .Identifier && peek(parser).kind == .Dot {
|
||||||
@@ -2510,7 +2533,7 @@ parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
pkg=parser.pkg,
|
pkg=parser.pkg,
|
||||||
file=parser.file,
|
file=parser.file,
|
||||||
target_pkg=ast.INVALID_PACKAGE,
|
target_pkg=ast.INVALID_PACKAGE,
|
||||||
file_hidden=file_hidden_name(parser, name),
|
file_hidden=file_hidden,
|
||||||
valid=true,
|
valid=true,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -2521,7 +2544,7 @@ parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
}
|
}
|
||||||
parser.cursor = saved
|
parser.cursor = saved
|
||||||
child := parse_type(parser)
|
child := parse_type(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||||
if !types.define_alias(&parser.module.type_store, id, child) {
|
if !types.define_alias(&parser.module.type_store, id, child) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
@@ -2650,7 +2673,7 @@ parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type {
|
|||||||
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
|
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
explicit_backing := false
|
explicit_backing := false
|
||||||
backing := types.INVALID
|
backing := types.INVALID
|
||||||
@@ -2668,7 +2691,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||||
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
|
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
@@ -2764,13 +2787,18 @@ parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parse_top_level :: proc(parser: ^Parser) {
|
parse_top_level :: proc(parser: ^Parser) {
|
||||||
|
hide_token, file_hidden := allow(parser, .Keyword_Hide)
|
||||||
if current(parser).kind == .Keyword_Import {
|
if current(parser).kind == .Keyword_Import {
|
||||||
|
if file_hidden {
|
||||||
|
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
||||||
|
}
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
parse_import(parser, token.Token{}, start)
|
parse_import(parser, token.Token{}, start)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if current(parser).kind != .Identifier {
|
if current(parser).kind != .Identifier {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
|
message := "expected a declaration name after 'hide'" if file_hidden else "expected a top-level declaration"
|
||||||
|
source.add(parser.diagnostics, current(parser).span, message)
|
||||||
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
}
|
}
|
||||||
@@ -2783,6 +2811,9 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
advance(parser)
|
advance(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind == .Keyword_Import {
|
if current(parser).kind == .Keyword_Import {
|
||||||
|
if file_hidden {
|
||||||
|
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
||||||
|
}
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
parse_import(parser, name, start)
|
parse_import(parser, name, start)
|
||||||
return
|
return
|
||||||
@@ -2791,7 +2822,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
}
|
}
|
||||||
if current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func {
|
if current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func {
|
||||||
c_abi := current(parser).kind == .Keyword_C_Func
|
c_abi := current(parser).kind == .Keyword_C_Func
|
||||||
parse_function(parser, name, c_abi)
|
parse_function(parser, name, c_abi, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
type_syntax := types.INVALID
|
type_syntax := types.INVALID
|
||||||
@@ -2812,32 +2843,32 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
source.add(parser.diagnostics, span_from(name.span, current(parser).span),
|
source.add(parser.diagnostics, span_from(name.span, current(parser).span),
|
||||||
"function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'")
|
"function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'")
|
||||||
c_abi := current(parser).kind == .Keyword_C_Func
|
c_abi := current(parser).kind == .Keyword_C_Func
|
||||||
parse_function(parser, name, c_abi)
|
parse_function(parser, name, c_abi, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon &&
|
if operator.kind == .Colon_Colon &&
|
||||||
(current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) {
|
(current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) {
|
||||||
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
|
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque {
|
||||||
parse_opaque(parser, name)
|
parse_opaque(parser, name, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union {
|
||||||
parse_struct(parser, name, false, is_union=true)
|
parse_struct(parser, name, false, file_hidden, is_union=true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
|
||||||
parse_enum(parser, name)
|
parse_enum(parser, name, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
|
||||||
parse_distinct(parser, name)
|
parse_distinct(parser, name, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
|
||||||
parse_alias(parser, name)
|
parse_alias(parser, name, file_hidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2848,7 +2879,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
name=name.symbol,
|
name=name.symbol,
|
||||||
pkg=parser.pkg,
|
pkg=parser.pkg,
|
||||||
file=parser.file,
|
file=parser.file,
|
||||||
file_hidden=file_hidden_name(parser, name),
|
file_hidden=file_hidden,
|
||||||
type=type_syntax,
|
type=type_syntax,
|
||||||
immutable=operator.kind == .Colon_Colon,
|
immutable=operator.kind == .Colon_Colon,
|
||||||
expr=expr,
|
expr=expr,
|
||||||
@@ -2869,6 +2900,9 @@ parse :: proc(
|
|||||||
diagnostics=diagnostics,
|
diagnostics=diagnostics,
|
||||||
module=ast.init_module(allocator),
|
module=ast.init_module(allocator),
|
||||||
}
|
}
|
||||||
|
parser.hidden_names.allocator = allocator
|
||||||
|
defer delete(parser.hidden_names)
|
||||||
|
collect_hidden_names(&parser)
|
||||||
skip_newlines(&parser)
|
skip_newlines(&parser)
|
||||||
for current(&parser).kind != .Eof {
|
for current(&parser).kind != .Eof {
|
||||||
parse_top_level(&parser)
|
parse_top_level(&parser)
|
||||||
@@ -2893,6 +2927,9 @@ parse_into :: proc(
|
|||||||
pkg=pkg,
|
pkg=pkg,
|
||||||
file=file,
|
file=file,
|
||||||
}
|
}
|
||||||
|
parser.hidden_names.allocator = module.allocator
|
||||||
|
defer delete(parser.hidden_names)
|
||||||
|
collect_hidden_names(&parser)
|
||||||
skip_newlines(&parser)
|
skip_newlines(&parser)
|
||||||
for current(&parser).kind != .Eof {
|
for current(&parser).kind != .Eof {
|
||||||
parse_top_level(&parser)
|
parse_top_level(&parser)
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Distinct,
|
Keyword_Distinct,
|
||||||
Keyword_Alias,
|
Keyword_Alias,
|
||||||
Keyword_Import,
|
Keyword_Import,
|
||||||
|
Keyword_Hide,
|
||||||
Keyword_Return,
|
Keyword_Return,
|
||||||
Keyword_Try,
|
Keyword_Try,
|
||||||
Keyword_Catch,
|
Keyword_Catch,
|
||||||
|
|||||||
+37
-11
@@ -7353,13 +7353,38 @@ imports_are_file_local :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
leading_underscore_declarations_are_file_hidden :: proc(t: ^testing.T) {
|
hide_is_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) {
|
||||||
|
cases := [?]string{
|
||||||
|
`hide import "../dep"`,
|
||||||
|
`hide dep :: import "../dep"`,
|
||||||
|
`hide func() void {}`,
|
||||||
|
`main func(hide value i32) void {}`,
|
||||||
|
`Box :: struct { hide i32 }`,
|
||||||
|
`main func() void { hide value i32 = 1 }`,
|
||||||
|
}
|
||||||
|
for text in cases {
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect(t, len(diagnostics.items) > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
hide_declarations_are_file_hidden :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-hidden-declarations"
|
output := "/tmp/brolang-test-hidden-declarations"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_package("examples/packages/hidden_valid/app", output)
|
status := compiler_core.compile_package("examples/packages/hidden_valid/app", output)
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 7)
|
testing.expect_value(t, state.exit_code, 9)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7382,11 +7407,11 @@ file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) {
|
|||||||
found_import := false
|
found_import := false
|
||||||
found_collision := false
|
found_collision := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
found_sibling = found_sibling || strings.contains(diagnostic.message, "unresolved function '_sibling'")
|
found_sibling = found_sibling || strings.contains(diagnostic.message, "unresolved function 'sibling'")
|
||||||
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unresolved global '_sibling_value'")
|
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unresolved global 'sibling_value'")
|
||||||
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type '_Sibling'")
|
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'Sibling'")
|
||||||
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member '_secret'")
|
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member 'secret'")
|
||||||
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function '_collision'")
|
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
|
||||||
}
|
}
|
||||||
testing.expect(t, found_sibling)
|
testing.expect(t, found_sibling)
|
||||||
testing.expect(t, found_sibling_value)
|
testing.expect(t, found_sibling_value)
|
||||||
@@ -7513,8 +7538,8 @@ RenamedBox :: alias dep.Box
|
|||||||
RenamedPoint :: alias dep.Point
|
RenamedPoint :: alias dep.Point
|
||||||
counter :: alias dep.counter
|
counter :: alias dep.counter
|
||||||
answer :: alias dep.answer
|
answer :: alias dep.answer
|
||||||
_local_answer :: alias dep.answer
|
hide local_answer_alias :: alias dep.answer
|
||||||
local_answer func() i32 { return _local_answer() }
|
local_answer func() i32 { return local_answer_alias() }
|
||||||
Scalar :: alias i32
|
Scalar :: alias i32
|
||||||
MaybePoint :: alias ?@dep.Point
|
MaybePoint :: alias ?@dep.Point
|
||||||
Concrete :: alias dep.Box(i32)
|
Concrete :: alias dep.Box(i32)
|
||||||
@@ -7567,14 +7592,14 @@ declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, os.make_directory(directory) == nil)
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
}
|
}
|
||||||
dep_text := `visible func() i32 { return 1 }
|
dep_text := `visible func() i32 { return 1 }
|
||||||
_hidden func() i32 { return 2 }
|
hide hidden_target func() i32 { return 2 }
|
||||||
ambiguous func() i32 { return 3 }
|
ambiguous func() i32 { return 3 }
|
||||||
ambiguous i32 :: 4
|
ambiguous i32 :: 4
|
||||||
`
|
`
|
||||||
facade_text := `dep :: import "../dep"
|
facade_text := `dep :: import "../dep"
|
||||||
gone :: import "../gone"
|
gone :: import "../gone"
|
||||||
missing :: alias dep.missing
|
missing :: alias dep.missing
|
||||||
hidden :: alias dep._hidden
|
hidden :: alias dep.hidden_target
|
||||||
unknown :: alias nope.visible
|
unknown :: alias nope.visible
|
||||||
unavailable :: alias gone.visible
|
unavailable :: alias gone.visible
|
||||||
ambiguous :: alias dep.ambiguous
|
ambiguous :: alias dep.ambiguous
|
||||||
@@ -10589,6 +10614,7 @@ main func() i32 {
|
|||||||
@(test)
|
@(test)
|
||||||
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) {
|
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) {
|
||||||
testing.expect(t, token.is_keyword(.Keyword_Func))
|
testing.expect(t, token.is_keyword(.Keyword_Func))
|
||||||
|
testing.expect(t, token.is_keyword(.Keyword_Hide))
|
||||||
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
|
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
|
||||||
testing.expect(t, !token.is_keyword(.Identifier))
|
testing.expect(t, !token.is_keyword(.Identifier))
|
||||||
testing.expect(t, !token.is_keyword(.Underscore))
|
testing.expect(t, !token.is_keyword(.Underscore))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
_sibling func() i32 {
|
hide sibling func() i32 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_Sibling :: struct {
|
hide Sibling :: struct {
|
||||||
value i32
|
value i32
|
||||||
}
|
}
|
||||||
|
|
||||||
_sibling_value :: 1
|
hide sibling_value :: 1
|
||||||
|
|
||||||
_collision c_func() i32 {
|
collision c_func() i32 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import "../dep"
|
import "../dep"
|
||||||
|
|
||||||
_collision func() i32 {
|
hide collision func() i32 {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
read_sibling func(value _Sibling) i32 {
|
read_sibling func(value Sibling) i32 {
|
||||||
return value.value + _sibling_value
|
return value.value + sibling_value
|
||||||
}
|
}
|
||||||
|
|
||||||
read_sibling_value func() i32 {
|
read_sibling_value func() i32 {
|
||||||
return _sibling_value
|
return sibling_value
|
||||||
}
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
return _sibling() + dep._secret() + read_sibling(_Sibling { value = 1 }) + read_sibling_value()
|
return sibling() + dep.secret() + read_sibling(Sibling { value = 1 }) + read_sibling_value()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
_secret func() i32 {
|
hide secret c_func() i32 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
_Thing :: struct {
|
hide helper func() i32 {
|
||||||
|
thing Thing = Thing { value = value }
|
||||||
|
return thing.value
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Thing :: struct {
|
||||||
value i32
|
value i32
|
||||||
}
|
}
|
||||||
|
|
||||||
_value :: 1
|
hide Local_Union :: union {
|
||||||
|
value i32
|
||||||
_helper func() i32 {
|
|
||||||
thing _Thing = _Thing { value = _value }
|
|
||||||
return thing.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hide Local_Enum :: enum {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Local_Opaque :: opaque
|
||||||
|
hide Local_Distinct :: distinct i32
|
||||||
|
hide Local_Alias :: alias i32
|
||||||
|
|
||||||
|
hide value :: 1
|
||||||
|
hide mutable_value i32 = 1
|
||||||
|
|
||||||
_foreign c_func() i32 {
|
_foreign c_func() i32 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -17,6 +30,15 @@ _C_Record :: c_struct {
|
|||||||
value c_int
|
value c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
from_a func() i32 {
|
hide local_foreign c_func() i32 {
|
||||||
return _helper()
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Local_C_Record :: c_struct {
|
||||||
|
value c_int
|
||||||
|
}
|
||||||
|
|
||||||
|
from_a func() i32 {
|
||||||
|
record Local_C_Record = Local_C_Record { value = 0 }
|
||||||
|
return helper() + local_foreign() + i32(record.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,37 @@
|
|||||||
_Thing :: struct {
|
import "../dep"
|
||||||
|
|
||||||
|
hide Thing :: struct {
|
||||||
value i32
|
value i32
|
||||||
}
|
}
|
||||||
|
|
||||||
_value :: 2
|
hide Local_Union :: union {
|
||||||
|
value i32
|
||||||
|
}
|
||||||
|
|
||||||
_helper func() i32 {
|
hide Local_Enum :: enum {
|
||||||
thing _Thing = _Thing { value = _value }
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Local_Opaque :: opaque
|
||||||
|
hide Local_Distinct :: distinct i32
|
||||||
|
hide Local_Alias :: alias i32
|
||||||
|
|
||||||
|
hide value :: 2
|
||||||
|
hide mutable_value i32 = 2
|
||||||
|
|
||||||
|
hide helper func() i32 {
|
||||||
|
thing Thing = Thing { value = value }
|
||||||
return thing.value
|
return thing.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hide local_foreign c_func() i32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
hide Local_C_Record :: c_struct {
|
||||||
|
value c_int
|
||||||
|
}
|
||||||
|
|
||||||
Box :: struct {
|
Box :: struct {
|
||||||
_value i32
|
_value i32
|
||||||
}
|
}
|
||||||
@@ -16,5 +39,5 @@ Box :: struct {
|
|||||||
from_b func(_input i32) i32 {
|
from_b func(_input i32) i32 {
|
||||||
_local Box = Box { _value = _input }
|
_local Box = Box { _value = _input }
|
||||||
record _C_Record = _C_Record { value = 0 }
|
record _C_Record = _C_Record { value = 0 }
|
||||||
return _helper() + _local._value + _foreign() + i32(record.value)
|
return helper() + _local._value + _foreign() + i32(record.value) + dep._visible()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
_visible func() i32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
@@ -2,25 +2,25 @@ arraylist :: import "@std/arraylist"
|
|||||||
mem :: import "@std/mem"
|
mem :: import "@std/mem"
|
||||||
std :: import "@std"
|
std :: import "@std"
|
||||||
|
|
||||||
_fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
hide fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
_fail_realloc func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
hide fail_realloc func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
_fail_free func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
|
hide fail_free func(_ ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
|
||||||
|
|
||||||
_fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||||
alloc = _fail_alloc,
|
alloc = fail_alloc,
|
||||||
realloc = _fail_realloc,
|
realloc = fail_realloc,
|
||||||
free = _fail_free,
|
free = fail_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
_fail_allocator mem.Allocator :: mem.Allocator {
|
hide fail_allocator mem.Allocator :: mem.Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_fail_vtable,
|
vtable = &fail_vtable,
|
||||||
}
|
}
|
||||||
|
|
||||||
run func() i32 ! mem.AllocError {
|
run func() i32 ! mem.AllocError {
|
||||||
@@ -59,7 +59,7 @@ run func() i32 ! mem.AllocError {
|
|||||||
}
|
}
|
||||||
if (empty_values.items.len != 1) return 8
|
if (empty_values.items.len != 1) return 8
|
||||||
|
|
||||||
failed arraylist.ArrayList(i32) = arraylist.init(i32, _fail_allocator)
|
failed arraylist.ArrayList(i32) = arraylist.init(i32, fail_allocator)
|
||||||
failed_as_expected bool = false
|
failed_as_expected bool = false
|
||||||
arraylist.append(&failed, 1) catch |_| {
|
arraylist.append(&failed, 1) catch |_| {
|
||||||
failed_as_expected = true
|
failed_as_expected = true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
io :: import "@std/io"
|
io :: import "@std/io"
|
||||||
|
|
||||||
_read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
|
hide read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
|
||||||
if buffer.len == 0 {
|
if buffer.len == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -12,52 +12,52 @@ _read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.Re
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
_read_too_much func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
|
hide read_too_much func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
|
||||||
return buffer.len + 1
|
return buffer.len + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_read_eof func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError {
|
hide read_eof func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
_write_short func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
hide write_short func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
||||||
if bytes.len > 2 {
|
if bytes.len > 2 {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
return bytes.len
|
return bytes.len
|
||||||
}
|
}
|
||||||
|
|
||||||
_write_none func(_ ?*mut anyopaque, _ io.WriteStream, _ []u8) usize ! io.WriteError {
|
hide write_none func(_ ?*mut anyopaque, _ io.WriteStream, _ []u8) usize ! io.WriteError {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
_write_too_much func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
hide write_too_much func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
||||||
return bytes.len + 1
|
return bytes.len + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_ok_vtable io.IoVTable :: io.IoVTable {
|
hide ok_vtable io.IoVTable :: io.IoVTable {
|
||||||
read = _read_ok,
|
read = read_ok,
|
||||||
write = _write_short,
|
write = write_short,
|
||||||
}
|
}
|
||||||
|
|
||||||
_read_bad_vtable io.IoVTable :: io.IoVTable {
|
hide read_bad_vtable io.IoVTable :: io.IoVTable {
|
||||||
read = _read_too_much,
|
read = read_too_much,
|
||||||
write = _write_short,
|
write = write_short,
|
||||||
}
|
}
|
||||||
|
|
||||||
_eof_vtable io.IoVTable :: io.IoVTable {
|
hide eof_vtable io.IoVTable :: io.IoVTable {
|
||||||
read = _read_eof,
|
read = read_eof,
|
||||||
write = _write_short,
|
write = write_short,
|
||||||
}
|
}
|
||||||
|
|
||||||
_write_none_vtable io.IoVTable :: io.IoVTable {
|
hide write_none_vtable io.IoVTable :: io.IoVTable {
|
||||||
read = _read_ok,
|
read = read_ok,
|
||||||
write = _write_none,
|
write = write_none,
|
||||||
}
|
}
|
||||||
|
|
||||||
_write_bad_vtable io.IoVTable :: io.IoVTable {
|
hide write_bad_vtable io.IoVTable :: io.IoVTable {
|
||||||
read = _read_ok,
|
read = read_ok,
|
||||||
write = _write_too_much,
|
write = write_too_much,
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_for func(vtable @io.IoVTable) io.Reader {
|
reader_for func(vtable @io.IoVTable) io.Reader {
|
||||||
@@ -76,39 +76,39 @@ writer_for func(vtable @io.IoVTable) io.Writer {
|
|||||||
|
|
||||||
rejects_bad_read func() bool {
|
rejects_bad_read func() bool {
|
||||||
buffer [1]mut u8 = [0]
|
buffer [1]mut u8 = [0]
|
||||||
_ = io.read(reader_for(&_read_bad_vtable), buffer[..]) catch |err| {
|
_ = io.read(reader_for(&read_bad_vtable), buffer[..]) catch |err| {
|
||||||
return err == .read_failed
|
return err == .read_failed
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
rejects_no_progress func() bool {
|
rejects_no_progress func() bool {
|
||||||
io.write_all(writer_for(&_write_none_vtable), "x") catch |err| {
|
io.write_all(writer_for(&write_none_vtable), "x") catch |err| {
|
||||||
return err == .no_progress
|
return err == .no_progress
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
rejects_bad_write func() bool {
|
rejects_bad_write func() bool {
|
||||||
_ = io.write(writer_for(&_write_bad_vtable), "x") catch |err| {
|
_ = io.write(writer_for(&write_bad_vtable), "x") catch |err| {
|
||||||
return err == .write_failed
|
return err == .write_failed
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
main func(system io.Io) i32 {
|
main func(system std.Io) i32 {
|
||||||
buffer [2]mut u8 = [0, 0]
|
buffer [2]mut u8 = [0, 0]
|
||||||
count usize :: io.read(reader_for(&_ok_vtable), buffer[..]) catch 0
|
count usize :: io.read(reader_for(&ok_vtable), buffer[..]) catch 0
|
||||||
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
|
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
eof usize :: io.read(reader_for(&_eof_vtable), buffer[..]) catch 1
|
eof usize :: io.read(reader_for(&eof_vtable), buffer[..]) catch 1
|
||||||
empty_read usize :: io.read(reader_for(&_read_bad_vtable), buffer[0..0]) catch 1
|
empty_read usize :: io.read(reader_for(&read_bad_vtable), buffer[0..0]) catch 1
|
||||||
empty_write usize :: io.write(writer_for(&_write_bad_vtable), "") catch 1
|
empty_write usize :: io.write(writer_for(&write_bad_vtable), "") catch 1
|
||||||
if eof != 0 or empty_read != 0 or empty_write != 0 {
|
if eof != 0 or empty_read != 0 or empty_write != 0 {
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
io.write_all(writer_for(&_ok_vtable), "partial") catch |_| {
|
io.write_all(writer_for(&ok_vtable), "partial") catch |_| {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
|
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
mem :: import "@std/mem"
|
mem :: import "@std/mem"
|
||||||
|
|
||||||
_probe_count func(context ?*mut anyopaque) void {
|
hide probe_count func(context ?*mut anyopaque) void {
|
||||||
if context |raw| {
|
if context |raw| {
|
||||||
counts *mut usize :: ptrcast!(usize, raw)
|
counts *mut usize :: ptrcast!(usize, raw)
|
||||||
counts[0] += 1
|
counts[0] += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_probe_alloc func(context ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
hide probe_alloc func(context ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||||
_probe_count(context)
|
probe_count(context)
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
_probe_realloc func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
hide probe_realloc func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||||
_probe_count(context)
|
probe_count(context)
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
_probe_free func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
|
hide probe_free func(context ?*mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
|
||||||
_probe_count(context)
|
probe_count(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
_probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||||
alloc = _probe_alloc,
|
alloc = probe_alloc,
|
||||||
realloc = _probe_realloc,
|
realloc = probe_realloc,
|
||||||
free = _probe_free,
|
free = probe_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
typed_allocator_test func() i32 {
|
typed_allocator_test func() i32 {
|
||||||
@@ -34,11 +34,11 @@ typed_allocator_test func() i32 {
|
|||||||
second_calls [1]mut usize = [0]
|
second_calls [1]mut usize = [0]
|
||||||
first_allocator mem.Allocator :: mem.Allocator {
|
first_allocator mem.Allocator :: mem.Allocator {
|
||||||
context = (&first_calls).ptr,
|
context = (&first_calls).ptr,
|
||||||
vtable = &_probe_vtable,
|
vtable = &probe_vtable,
|
||||||
}
|
}
|
||||||
second_allocator mem.Allocator :: mem.Allocator {
|
second_allocator mem.Allocator :: mem.Allocator {
|
||||||
context = (&second_calls).ptr,
|
context = (&second_calls).ptr,
|
||||||
vtable = &_probe_vtable,
|
vtable = &probe_vtable,
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = mem.raw_alloc(first_allocator, 1, 1)
|
_ = mem.raw_alloc(first_allocator, 1, 1)
|
||||||
|
|||||||
+7
-7
@@ -74,7 +74,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||||
request usize = buffer.len
|
request usize = buffer.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
if request > maximum {
|
if request > maximum {
|
||||||
@@ -91,7 +91,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||||
fd c_int :: c_int(stream)
|
fd c_int :: c_int(stream)
|
||||||
request usize = bytes.len
|
request usize = bytes.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
@@ -109,14 +109,14 @@ _system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! Wr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_vtable IoVTable :: IoVTable {
|
hide system_vtable IoVTable :: IoVTable {
|
||||||
read = _system_read,
|
read = system_read,
|
||||||
write = _system_write,
|
write = system_write,
|
||||||
}
|
}
|
||||||
|
|
||||||
_system func() Io {
|
hide system func() Io {
|
||||||
return Io {
|
return Io {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_system_vtable,
|
vtable = &system_vtable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-24
@@ -41,25 +41,25 @@ eql func($T type, left, right []T) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_empty_storage [1]mut u64 = [0]
|
hide empty_storage [1]mut u64 = [0]
|
||||||
|
|
||||||
_empty_slice func($T type, count usize) []mut T {
|
hide empty_slice func($T type, count usize) []mut T {
|
||||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||||
return pointer[..count]
|
return pointer[..count]
|
||||||
}
|
}
|
||||||
|
|
||||||
empty func($T type) []mut T {
|
empty func($T type) []mut T {
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size usize :: sizeof!(T)
|
||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, count)
|
return empty_slice(T, count)
|
||||||
}
|
}
|
||||||
if count > divtrunc!(maxval!(usize), element_size) {
|
if count > divtrunc!(maxval!(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
@@ -79,12 +79,12 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
}
|
}
|
||||||
if new_count == 0 {
|
if new_count == 0 {
|
||||||
free(allocator, memory)
|
free(allocator, memory)
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size usize :: sizeof!(T)
|
||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, new_count)
|
return empty_slice(T, new_count)
|
||||||
}
|
}
|
||||||
if new_count > divtrunc!(maxval!(usize), element_size) {
|
if new_count > divtrunc!(maxval!(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
@@ -116,9 +116,9 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||||
|
|
||||||
_power_of_two func(value usize) bool {
|
hide power_of_two func(value usize) bool {
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -135,12 +135,12 @@ _power_of_two func(value usize) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
|||||||
return ptrcast!(u8, memory[0])
|
return ptrcast!(u8, memory[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +164,11 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if memory |old_memory| {
|
if memory |old_memory| {
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
new_memory ?*mut u8 = c_alloc(none, new_size, alignment)
|
||||||
if new_memory |new_bytes| {
|
if new_memory |new_bytes| {
|
||||||
copy_size usize = old_size
|
copy_size usize = old_size
|
||||||
if new_size < copy_size {
|
if new_size < copy_size {
|
||||||
@@ -183,20 +183,20 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
return new_memory
|
return new_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
return _c_alloc(none, new_size, alignment)
|
return c_alloc(none, new_size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
hide c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||||
c.free(memory)
|
c.free(memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_vtable AllocatorVTable :: AllocatorVTable {
|
hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||||
alloc = _c_alloc,
|
alloc = c_alloc,
|
||||||
realloc = _c_realloc,
|
realloc = c_realloc,
|
||||||
free = _c_free,
|
free = c_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
c_allocator Allocator :: Allocator {
|
c_allocator Allocator :: Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_c_vtable,
|
vtable = &c_vtable,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import "io"
|
||||||
import "arraylist"
|
import "arraylist"
|
||||||
|
|
||||||
|
Io :: alias io.Io
|
||||||
ArrayList :: alias arraylist.ArrayList
|
ArrayList :: alias arraylist.ArrayList
|
||||||
|
|||||||
+19
-19
@@ -20,17 +20,17 @@ Token :: struct {
|
|||||||
kind Kind
|
kind Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
_is_alpha func(value u8) bool {
|
hide is_alpha func(value u8) bool {
|
||||||
return value == '_' or
|
return value == '_' or
|
||||||
value >= 'a' and value <= 'z' or
|
value >= 'a' and value <= 'z' or
|
||||||
value >= 'A' and value <= 'Z'
|
value >= 'A' and value <= 'Z'
|
||||||
}
|
}
|
||||||
|
|
||||||
_is_digit func(value u8) bool {
|
hide is_digit func(value u8) bool {
|
||||||
return value >= '0' and value <= '9'
|
return value >= '0' and value <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
_word_kind func(word []u8) Kind {
|
hide word_kind func(word []u8) Kind {
|
||||||
# ponytail: enough keywords for the demo; add the full language set when a parser needs it.
|
# ponytail: enough keywords for the demo; add the full language set when a parser needs it.
|
||||||
if mem.eql(word, "func") or mem.eql(word, "void") {
|
if mem.eql(word, "func") or mem.eql(word, "void") {
|
||||||
return .keyword
|
return .keyword
|
||||||
@@ -38,7 +38,7 @@ _word_kind func(word []u8) Kind {
|
|||||||
return .identifier
|
return .identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
_append func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
hide append_token func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
||||||
try arraylist.append(tokens, Token {
|
try arraylist.append(tokens, Token {
|
||||||
start = start,
|
start = start,
|
||||||
length = end - start,
|
length = end - start,
|
||||||
@@ -54,19 +54,19 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
|||||||
if value == ' ' or value == '\t' or value == '\r' {
|
if value == ' ' or value == '\t' or value == '\r' {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
} else if value == '\n' {
|
} else if value == '\n' {
|
||||||
try _append(tokens, .newline, cursor, cursor + 1)
|
try append_token(tokens, .newline, cursor, cursor + 1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
} else if value == '#' {
|
} else if value == '#' {
|
||||||
while cursor < source.len and source[cursor] != '\n' : cursor += 1 {}
|
while cursor < source.len and source[cursor] != '\n' : cursor += 1 {}
|
||||||
} else if _is_alpha(value) {
|
} else if is_alpha(value) {
|
||||||
start usize :: cursor
|
start usize :: cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
while cursor < source.len and (_is_alpha(source[cursor]) or _is_digit(source[cursor])) : cursor += 1 {}
|
while cursor < source.len and (is_alpha(source[cursor]) or is_digit(source[cursor])) : cursor += 1 {}
|
||||||
try _append(tokens, _word_kind(source[start..cursor]), start, cursor)
|
try append_token(tokens, word_kind(source[start..cursor]), start, cursor)
|
||||||
} else if _is_digit(value) {
|
} else if is_digit(value) {
|
||||||
start usize :: cursor
|
start usize :: cursor
|
||||||
while cursor < source.len and _is_digit(source[cursor]) : cursor += 1 {}
|
while cursor < source.len and is_digit(source[cursor]) : cursor += 1 {}
|
||||||
try _append(tokens, .integer, start, cursor)
|
try append_token(tokens, .integer, start, cursor)
|
||||||
} else if value == '"' {
|
} else if value == '"' {
|
||||||
start usize :: cursor
|
start usize :: cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -78,9 +78,9 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
|||||||
}
|
}
|
||||||
if cursor < source.len and source[cursor] == '"' {
|
if cursor < source.len and source[cursor] == '"' {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
try _append(tokens, .string, start, cursor)
|
try append_token(tokens, .string, start, cursor)
|
||||||
} else {
|
} else {
|
||||||
try _append(tokens, .invalid, start, cursor)
|
try append_token(tokens, .invalid, start, cursor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
start usize :: cursor
|
start usize :: cursor
|
||||||
@@ -88,14 +88,14 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
|||||||
if value == ':' and cursor < source.len and source[cursor] == ':' {
|
if value == ':' and cursor < source.len and source[cursor] == ':' {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
try _append(tokens, .punctuation, start, cursor)
|
try append_token(tokens, .punctuation, start, cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try _append(tokens, .eof, cursor, cursor)
|
try append_token(tokens, .eof, cursor, cursor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_kind_name func(kind Kind) *c_char {
|
hide kind_name func(kind Kind) *c_char {
|
||||||
return match kind {
|
return match kind {
|
||||||
.invalid: "invalid"
|
.invalid: "invalid"
|
||||||
.eof: "eof"
|
.eof: "eof"
|
||||||
@@ -108,8 +108,8 @@ _kind_name func(kind Kind) *c_char {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_print_token func(source []u8, token Token) void {
|
hide print_token func(source []u8, token Token) void {
|
||||||
_ = c.printf("%-11s", _kind_name(token.kind))
|
_ = c.printf("%-11s", kind_name(token.kind))
|
||||||
if token.length != 0 {
|
if token.length != 0 {
|
||||||
_ = c.printf(" `")
|
_ = c.printf(" `")
|
||||||
i usize = 0
|
i usize = 0
|
||||||
@@ -149,7 +149,7 @@ main func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for tokens.items |token| {
|
for tokens.items |token| {
|
||||||
_print_token(source, token)
|
print_token(source, token)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||||
request usize = buffer.len
|
request usize = buffer.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
if request > maximum {
|
if request > maximum {
|
||||||
@@ -91,7 +91,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||||
fd c_int :: c_int(stream)
|
fd c_int :: c_int(stream)
|
||||||
request usize = bytes.len
|
request usize = bytes.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
@@ -109,14 +109,14 @@ _system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! Wr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_system_vtable IoVTable :: IoVTable {
|
hide system_vtable IoVTable :: IoVTable {
|
||||||
read = _system_read,
|
read = system_read,
|
||||||
write = _system_write,
|
write = system_write,
|
||||||
}
|
}
|
||||||
|
|
||||||
_system func() Io {
|
hide system func() Io {
|
||||||
return Io {
|
return Io {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_system_vtable,
|
vtable = &system_vtable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,25 +41,25 @@ eql func($T type, left, right []T) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_empty_storage [1]mut u64 = [0]
|
hide empty_storage [1]mut u64 = [0]
|
||||||
|
|
||||||
_empty_slice func($T type, count usize) []mut T {
|
hide empty_slice func($T type, count usize) []mut T {
|
||||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||||
return pointer[..count]
|
return pointer[..count]
|
||||||
}
|
}
|
||||||
|
|
||||||
empty func($T type) []mut T {
|
empty func($T type) []mut T {
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size usize :: sizeof!(T)
|
||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, count)
|
return empty_slice(T, count)
|
||||||
}
|
}
|
||||||
if count > divtrunc!(maxval!(usize), element_size) {
|
if count > divtrunc!(maxval!(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
@@ -79,12 +79,12 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
}
|
}
|
||||||
if new_count == 0 {
|
if new_count == 0 {
|
||||||
free(allocator, memory)
|
free(allocator, memory)
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size usize :: sizeof!(T)
|
||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, new_count)
|
return empty_slice(T, new_count)
|
||||||
}
|
}
|
||||||
if new_count > divtrunc!(maxval!(usize), element_size) {
|
if new_count > divtrunc!(maxval!(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
@@ -116,9 +116,9 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||||
|
|
||||||
_power_of_two func(value usize) bool {
|
hide power_of_two func(value usize) bool {
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -135,12 +135,12 @@ _power_of_two func(value usize) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
|||||||
return ptrcast!(u8, memory[0])
|
return ptrcast!(u8, memory[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +164,11 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if memory |old_memory| {
|
if memory |old_memory| {
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
new_memory ?*mut u8 = c_alloc(none, new_size, alignment)
|
||||||
if new_memory |new_bytes| {
|
if new_memory |new_bytes| {
|
||||||
copy_size usize = old_size
|
copy_size usize = old_size
|
||||||
if new_size < copy_size {
|
if new_size < copy_size {
|
||||||
@@ -183,20 +183,20 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
return new_memory
|
return new_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
return _c_alloc(none, new_size, alignment)
|
return c_alloc(none, new_size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
hide c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||||
c.free(memory)
|
c.free(memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_vtable AllocatorVTable :: AllocatorVTable {
|
hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||||
alloc = _c_alloc,
|
alloc = c_alloc,
|
||||||
realloc = _c_realloc,
|
realloc = c_realloc,
|
||||||
free = _c_free,
|
free = c_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
c_allocator Allocator :: Allocator {
|
c_allocator Allocator :: Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_c_vtable,
|
vtable = &c_vtable,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,21 +27,21 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
|||||||
allocator.vtable.free(allocator.context, memory, size, alignment)
|
allocator.vtable.free(allocator.context, memory, size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
_empty_storage [1]mut u64 = [0]
|
hide empty_storage [1]mut u64 = [0]
|
||||||
|
|
||||||
_empty_slice func($T type, count usize) []mut T {
|
hide empty_slice func($T type, count usize) []mut T {
|
||||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||||
return pointer[..count]
|
return pointer[..count]
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return _empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size usize :: sizeof!(T)
|
||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, count)
|
return empty_slice(T, count)
|
||||||
}
|
}
|
||||||
if count > maxval!(usize) / element_size {
|
if count > maxval!(usize) / element_size {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
@@ -61,9 +61,9 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||||
|
|
||||||
_power_of_two func(value usize) bool {
|
hide power_of_two func(value usize) bool {
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -80,12 +80,12 @@ _power_of_two func(value usize) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,8 +98,8 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
|||||||
return ptrcast!(u8, memory[0])
|
return ptrcast!(u8, memory[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||||
if _power_of_two(alignment) == false {
|
if power_of_two(alignment) == false {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +109,11 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if memory |old_memory| {
|
if memory |old_memory| {
|
||||||
if alignment <= _malloc_alignment {
|
if alignment <= malloc_alignment {
|
||||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
new_memory ?*mut u8 = c_alloc(none, new_size, alignment)
|
||||||
if new_memory |new_bytes| {
|
if new_memory |new_bytes| {
|
||||||
copy_size usize = old_size
|
copy_size usize = old_size
|
||||||
if new_size < copy_size {
|
if new_size < copy_size {
|
||||||
@@ -128,20 +128,20 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
|||||||
return new_memory
|
return new_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
return _c_alloc(none, new_size, alignment)
|
return c_alloc(none, new_size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
hide c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||||
c.free(memory)
|
c.free(memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
_c_vtable AllocatorVTable :: AllocatorVTable {
|
hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||||
alloc = _c_alloc,
|
alloc = c_alloc,
|
||||||
realloc = _c_realloc,
|
realloc = c_realloc,
|
||||||
free = _c_free,
|
free = c_free,
|
||||||
}
|
}
|
||||||
|
|
||||||
c_allocator Allocator :: Allocator {
|
c_allocator Allocator :: Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
vtable = &_c_vtable,
|
vtable = &c_vtable,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ module.exports = grammar({
|
|||||||
),
|
),
|
||||||
|
|
||||||
function_declaration: $ => seq(
|
function_declaration: $ => seq(
|
||||||
|
optional('hide'),
|
||||||
field('name', $.identifier),
|
field('name', $.identifier),
|
||||||
field('kind', choice('func', 'c_func')),
|
field('kind', choice('func', 'c_func')),
|
||||||
field('parameters', $.parameter_list),
|
field('parameters', $.parameter_list),
|
||||||
@@ -64,6 +65,7 @@ module.exports = grammar({
|
|||||||
),
|
),
|
||||||
|
|
||||||
type_declaration: $ => prec(1, seq(
|
type_declaration: $ => prec(1, seq(
|
||||||
|
optional('hide'),
|
||||||
field('name', $.identifier),
|
field('name', $.identifier),
|
||||||
'::',
|
'::',
|
||||||
repeat($._newline),
|
repeat($._newline),
|
||||||
@@ -79,6 +81,7 @@ module.exports = grammar({
|
|||||||
)),
|
)),
|
||||||
|
|
||||||
global_constant_declaration: $ => seq(
|
global_constant_declaration: $ => seq(
|
||||||
|
optional('hide'),
|
||||||
field('name', $.identifier),
|
field('name', $.identifier),
|
||||||
optional(field('type', $.type)),
|
optional(field('type', $.type)),
|
||||||
'::',
|
'::',
|
||||||
@@ -87,6 +90,7 @@ module.exports = grammar({
|
|||||||
),
|
),
|
||||||
|
|
||||||
global_variable_declaration: $ => seq(
|
global_variable_declaration: $ => seq(
|
||||||
|
optional('hide'),
|
||||||
field('name', $.identifier),
|
field('name', $.identifier),
|
||||||
optional(field('type', $.type)),
|
optional(field('type', $.type)),
|
||||||
'=',
|
'=',
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"distinct"
|
"distinct"
|
||||||
"alias"
|
"alias"
|
||||||
"import"
|
"import"
|
||||||
|
"hide"
|
||||||
"return"
|
"return"
|
||||||
"try"
|
"try"
|
||||||
"catch"
|
"catch"
|
||||||
|
|||||||
@@ -103,6 +103,18 @@
|
|||||||
"function_declaration": {
|
"function_declaration": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "hide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -225,6 +237,18 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "hide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -287,6 +311,18 @@
|
|||||||
"global_constant_declaration": {
|
"global_constant_declaration": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "hide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -335,6 +371,18 @@
|
|||||||
"global_variable_declaration": {
|
"global_variable_declaration": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "hide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "FIELD",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
|
|||||||
@@ -2576,6 +2576,10 @@
|
|||||||
"type": "func",
|
"type": "func",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "hide",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "i16",
|
"type": "i16",
|
||||||
"named": false
|
"named": false
|
||||||
|
|||||||
+124751
-122484
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,16 @@ Core syntax
|
|||||||
|
|
||||||
io :: import "@std/io"
|
io :: import "@std/io"
|
||||||
|
|
||||||
|
hide private_value :: 1
|
||||||
|
|
||||||
|
hide private_counter i32 = 0
|
||||||
|
|
||||||
|
hide Private :: opaque
|
||||||
|
|
||||||
|
hide private_sum func(a, b i32) i32 {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
Status :: enum {
|
Status :: enum {
|
||||||
ok
|
ok
|
||||||
bad
|
bad
|
||||||
@@ -30,6 +40,38 @@ sum func(a, b i32) i32 ! Status {
|
|||||||
(identifier)
|
(identifier)
|
||||||
(string
|
(string
|
||||||
(string_content)))
|
(string_content)))
|
||||||
|
(global_constant_declaration
|
||||||
|
(identifier)
|
||||||
|
(expression
|
||||||
|
(integer)))
|
||||||
|
(global_variable_declaration
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(expression
|
||||||
|
(integer)))
|
||||||
|
(type_declaration
|
||||||
|
(identifier)
|
||||||
|
(opaque_type))
|
||||||
|
(function_declaration
|
||||||
|
(identifier)
|
||||||
|
(parameter_list
|
||||||
|
(parameter
|
||||||
|
(identifier)
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(builtin_type))))
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(return_statement
|
||||||
|
(expression
|
||||||
|
(binary_expression
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(expression
|
||||||
|
(identifier))))))))
|
||||||
(type_declaration
|
(type_declaration
|
||||||
(identifier)
|
(identifier)
|
||||||
(enum_type
|
(enum_type
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
hide helper func() void {}
|
||||||
|
# <- keyword
|
||||||
|
|
||||||
main func() void {
|
main func() void {
|
||||||
_ = sizeof!(i32)
|
_ = sizeof!(i32)
|
||||||
# ^^^^^^ function.builtin
|
# ^^^^^^ function.builtin
|
||||||
|
|||||||
Reference in New Issue
Block a user