Documentation pages
Errors
SultanC treats failure as a value. This page specifies how errors are carried (نتيجة / result and اختياري /
option), passed up with ؟ / ?, described with the std.error contract (فشل / Error), and reported when a
program stops. It also covers the optional language versions of an error message and the locale values that choose
among them. It is for programmers who write fallible code and for contributors working on std and the runtime
failure path.
The layers
| Layer | What | Where |
|---|---|---|
| Carry | نتيجة T, E / result T, E and اختياري T / option T |
compiler (tools/generate/registry/type_registry.def) |
| Pass up | the propagation operator ؟ / ? |
compiler, frontend expansion (compiler/frontend/desugar/propagation.sn) |
| Stop | the stop primitive إنهيار / panic: print a message and end the program |
library/std/process/panic.sn; the compiler knows it never returns |
| Contract | the فشل / Error value, its constructors and accessors |
library/std/error/ |
| Helpers | notes, wrapping, chain search, developer rendering, converters from typed std errors, context helpers for ؟ |
library/std/error/ |
| Language versions | optional per-language texts of one error, locale values, BCP 47 tags, direction isolation | library/std/error/failure.sn, library/std/text/ (language.sn, system_language.sn, bidi.sn) |
Only the first layers live in the compiler: the carrying types, the ؟ expansion, and the knowledge that the stop
primitive never returns. Compiler diagnostics are a separate system. iSpeak and compiler/diagnostics/** own every compiler, runtime-trap and
interpreter message, and the frames a program prints when it ends on an error or stops (see
Runtime failures of a program). std.error imports nothing from the compiler and
defines no SULTANC- code, diagnostic record, severity or message registry. The runner's architecture suite
(tests/runner/suites/architecture.sn) fails if a file under library/std/error/ mentions compiler.,
compiler/ or SULTANC-.
Design rules
- Any type can be an error.
نتيجة T, Etakes anyE. Typed error enums such asخطأ_تحليل_عددandخطأ_جسونremain the primary API of std.فشلis the common shape for code that collects errors of different origins, and it is never required. - Code never inspects message text. It decides on a typed enum or on a
فشل's identity, never on its message. - One author's message per
فشل. The message is the author's text in the author's language, and every function except the locale-aware ones works on it alone. Only the frame around it follows the program's language. Language versions are optional additions that only the functions taking a locale read. - Patterns use the canonical Arabic variant names of typed std errors, also in English source
(
examples/en/errors/results.sn).
Results and options
result T, E holds result.ok(value) (نتيجة.نجح) or result.err(error) (نتيجة.خطأ), and option T holds
option.some(value) (اختياري.موجود) or option.none (اختياري.لا_شيء). match takes either apart, and ؟
passes the error or the absence up.
import std.io.println;
import std.text.number;
def divide(a: int, b: int): result int, string {
if (b == 0) { return result.err("division by zero"); };
return result.ok(a / b);
}
def first_item(values: &vector int): option int {
if ((length(*values) as uint) == 0) { return option.none(); };
return option.some((*values)[0]);
}
def main(): int {
match divide(84, 2) with
| result.ok(q) -> { println(int_to_text(q)); }
| result.err(message) -> { println(message); };
let empty: vector int = [];
match first_item(&empty) with
| option.some(v) -> { println(int_to_text(v)); }
| option.none -> { println("no items"); };
return 0;
}
Propagation: ?
EXPR? passes an error, or an absence, up to the caller. It is accepted only as the last postfix of a statement's
whole right-hand side, in four forms:
| Form | Meaning on success |
|---|---|
let x: T = EXPR?; |
x takes the value |
x := EXPR?; |
the place takes the value |
return EXPR?; |
the value is returned |
EXPR?; |
the value is dropped |
Anywhere else, inside a call argument or as an operand for instance, ؟ is an unexpected token
(SULTANC-PARSE-SYNTAX). ؟ (U+061F) and ? are two spellings of one token (QUESTION_OPERATOR,
tools/generate/registry/token_registry.def).
def divide(a: int, b: int): result int, string {
if (b == 0) { return result.err("division by zero"); };
return result.ok(a / b);
}
def half_quotient(a: int, b: int): result int, string {
let q: int = divide(a, b)?;
return result.ok(q / 2);
}
The typing rules are:
EXPR: result T, Ein a function returningresult U, EgivesT. On an error the function returnsresult.err(e). The error type must be the sameE: nothing converts it, because there are no traits, so a different error type is reported at the؟, naming both types (SULTANC-CALL-ARGUMENT-TYPE-MISMATCH). Convert explicitly with the context helpers.EXPR: option Tin a function returningoption UgivesT. Onoption.nonethe function returnsoption.none().- In a function that returns neither,
؟is a return type mismatch at the؟(SULTANC-TYPE-RETURN-MISMATCH, naming the function's type and the expression's). An option inside a function returning a result isSULTANC-PATTERN-INVALIDat the؟. return EXPR?;returnsTitself, soTmust be the function's result type, as in Rust.
The arms follow the return type as written. option … gives option arms, and result … or a named type (an alias
of a result) gives result arms. An alias of an option is not recognized, so write option … in the signature of a
function that uses ? on options.
How it is built
compiler/frontend/desugar/propagation.sn expands every marked statement right after parsing, before name
resolution, so names, types, patterns, the ownership flow (SSF), MSIR, every backend and the interpreter see
ordinary statements. For let x: T = EXPR?; in a function returning a result, the expansion is:
let x: T;
match EXPR with
| hidden -> {
match hidden with
| result.ok(value) -> { x := value; }
| result.err(error) -> { return result.err(error); };
};
EXPRis bound first to a hidden owned local, by an irrefutable binding pattern whose type is the expression's own, and the dispatching match is on that local, never on the call result.- Each arm binds one payload, and the early return is the ordinary return, so the drop frame releases every owned
local exactly once.
tests/{ar,en}/language/propagation/drops.snreturns early 10^5 times from a function holding text, a vector and a box, and checks that the allocator's live bytes are equal before and after. - The expansion is SultanC text appended after the unit's own text and parsed by the ordinary parser, as decorator
expansion is, so every generated name has a source position.
EXPRand an assignment's target keep their own positions; they are spliced in from the original statement. The hidden names start withانتشار__. - The source map records each generated region and the
؟it stands for (compiler/source/source_map.sn,إحالة_نص_مولد), so a diagnostic inside the expansion is reported at the؟.
The C Stage0 implements the same expansion in its parser (bootstrap/src/frontend/parser/parser_statement_propagation.c):
a hidden temporary of inferred type, then the match. The compiler and the std modules it imports use ؟, for
example compiler/driver/package_graph.sn and std.toml.parser.
The std.error module
| Module | Arabic import | Contents |
|---|---|---|
std.error.failure |
المكتبة.أخطاء.failure |
the فشل value and its whole API |
std.error.from_os |
المكتبة.أخطاء.from_os |
converters for std.fs, std.io and std.process |
std.error.from_number |
المكتبة.أخطاء.from_number |
converter for std.text.number |
std.error.from_json |
المكتبة.أخطاء.from_json |
converter for std.json |
std.error.from_net |
المكتبة.أخطاء.from_net |
converters for std.net |
std.error.from_command |
المكتبة.أخطاء.from_command |
converter for std.process.command's خطأ_أمر (domain std.process, kind تشغيل, دخل_خرج or انتظار, cause = the classified system error) |
std.error.from_duration |
المكتبة.أخطاء.from_duration |
converter for std.time.duration_text's خطأ_مدة (domain std.time) |
std.error.from_fmt |
المكتبة.أخطاء.from_fmt |
converter for std.fmt's خطأ_تنسيق (domain std.fmt, kind = the variant name, the byte offset, argument index or writer status as a note) |
std.error.context |
المكتبة.أخطاء.context |
the context helpers for ؟ |
std.text.language |
المكتبة.نصوص.language |
the locale value لغة / Locale, BCP 47 tags, Accept-Language |
std.text.system_language |
المكتبة.نصوص.system_language |
the locale of the program's environment |
std.text.bidi |
المكتبة.نصوص.bidi |
direction isolation |
أخطاء is the Arabic spelling of the error package (declared in library/std/std.sn; see
Modules and imports). The C Stage0 resolves the Arabic package names from
the same file (bootstrap/src/support/path/source_import_path.c).
The value
A فشل (library/std/error/failure.sn) holds:
- an identity
(النطاق, النوع), domain and kind. The domain names the owner, a module path such asstd.fsor a package such aspayments. The kind is a stable machine name inside that domain, such asغير_موجود. An empty kind means an ad-hoc error with no identity. This is the idea of gRPC'sErrorInfo (domain, reason)and the RFC 9457type; - a message for developers: one sentence in the author's language, without a final period;
- an optional cause, another
فشل, so errors form a chain; - notes, in the order they were added (Python's
add_note); - optional language versions of the message (see Language versions).
The value is opaque. Use the functions below, not its fields, so that fields can be added later. It owns text, a
vector and a box, so it is destroyed automatically like any owning value, and nothing needs releasing by hand.
Copies are explicit: the استنسخ / clone primitive or انسخ_فشل. Drop glue releases the text, the notes vector
and the boxed cause chain, and clone glue copies them.
API
| Arabic | English | Signature | Meaning |
|---|---|---|---|
فشل |
Error |
type | the error value |
فشل_من |
error_new |
(الرسالة: نص): فشل |
ad-hoc error, empty identity (Go errors.New) |
فشل_بهوية |
error_with_kind |
(النطاق: نص, النوع: نص, الرسالة: نص): فشل |
error with an identity |
فشل_بسبب |
error_caused_by |
(الرسالة: نص, السبب: فشل): فشل |
ad-hoc error wrapping a cause, which is moved in (Go %w) |
فشل_بهوية_وسبب |
error_with_kind_caused_by |
(النطاق: نص, النوع: نص, الرسالة: نص, السبب: فشل): فشل |
error with an identity wrapping a cause |
انسخ_فشل |
error_copy |
(&فشل): فشل |
independent deep copy, causes included; the same result as استنسخ, in a form Stage0 compiles |
رسالة_فشل |
error_message |
(&فشل): نص |
a copy of the message |
نطاق_فشل |
error_domain |
(&فشل): نص |
a copy of the domain |
نوع_فشل |
error_kind |
(&فشل): نص |
a copy of the kind |
له_سبب |
error_has_cause |
(&فشل): منطقي |
whether there is a cause |
سبب_فشل |
error_cause |
(&فشل): اختياري فشل |
a copy of the direct cause |
ملاحظات_فشل |
error_notes |
(&فشل): متجه نص |
a copy of the notes |
هل_من_نوع |
error_is |
(&فشل, النطاق: &نص, النوع: &نص): منطقي |
whether this error or any cause in its chain has the identity (Go errors.Is) |
أضف_ملاحظة |
error_add_note |
(&متغير فشل, الملاحظة: نص): فراغ |
appends a note; the identity and message do not change (PEP 678) |
صف_فشل |
error_describe |
(&فشل): نص |
developer rendering, below |
اطبع_فشل |
error_print |
(&فشل): فراغ |
writes صف_فشل and a newline to standard error |
Accessors return owned copies, because Stage0 cannot return references. هل_من_نوع never matches an empty kind: an
ad-hoc error has no identity to match.
import std.error.failure;
import std.io.println;
def main(): int {
let root: Error = error_with_kind("std.fs", "not_found", "file not found");
let wrapped: mutable Error = error_with_kind_caused_by("app", "config", "could not load the configuration", root);
error_add_note(&mutable wrapped, "path: /etc/app.toml");
println(error_describe(&wrapped));
let domain: string = "std.fs";
let kind: string = "not_found";
if (error_is(&wrapped, &domain, &kind)) { println("found in chain"); };
return 0;
}
Developer rendering
صف_فشل gives the message, then each note on a line starting ملاحظة: , then for each cause in the chain a line
starting بسبب: with the cause's message, followed by that cause's notes. Lines are separated by \n, with no
newline at the end. The two connector words are fixed text owned by std, in Arabic, the project's default language.
They are not iSpeak text, and صف_فشل never changes them, so an English author's messages appear between Arabic
connectors. صف_فشل_بلغة chooses the connectors by locale.
أثناء بدء الخادم
ملاحظة: مسار الإعدادات من SULTANC_CONFIG
بسبب: تعذر قراءة الإعدادات
بسبب: ملف الإعدادات فارغ
ملاحظة: الحجم 0 بايت
Converters from typed std errors
Each converter takes its typed error by reference, the shape of a converter function value (دالة(&E): فشل). The
domain is the module, the kind is the canonical Arabic name of the variant, and the message is a fixed Arabic
sentence. Details that vary are notes: the numeric status, the byte offset, the byte position.
| Converter | English | From | Domain | Kinds | Note |
|---|---|---|---|---|---|
فشل_من_فئة_خطأ_نظام |
error_from_os_error_class |
&فئة_خطأ_نظام |
std.fs |
the 11 variants of فئة_خطأ_نظام |
none |
فشل_من_خطأ_ملف |
error_from_fs_error |
&عدد, a std.fs status |
std.fs |
as above | رمز الحالة: N |
فشل_من_خطأ_دخل_خرج |
error_from_io_error |
&عدد, a std.io status |
std.io |
as above | رمز الحالة: N |
فشل_من_خطأ_عملية |
error_from_process_error |
&عدد, a std.process status |
std.process |
as above | رمز الحالة: N |
فشل_من_خطأ_تحليل_عدد |
error_from_integer_parse_error |
&خطأ_تحليل_عدد |
std.text.number |
نص_فارغ, إشارة_بلا_أرقام, رقم_غير_صالح, تجاوز, سالب_لطبيعي |
موضع البايت: N for رقم_غير_صالح |
فشل_من_خطأ_جسون |
error_from_json_error |
&خطأ_جسون |
std.json |
the 9 variants of صنف_خطأ_جسون |
إزاحة البايت: N |
فشل_من_خطأ_تحليل_آيبي |
error_from_ip_parse_error |
&خطأ_تحليل_آيبي |
std.net |
صيغة_غير_صالحة, جزء_خارج_المدى |
none |
فشل_من_خطأ_تحليل_عنوان_مقبس |
error_from_socket_address_parse_error |
&خطأ_تحليل_عنوان_مقبس |
std.net |
صيغة_غير_صالحة, عنوان_غير_صالح, منفذ_غير_صالح |
none |
فشل_من_خطأ_تنسيق |
error_from_fmt_error |
&خطأ_تنسيق |
std.fmt |
the 11 variants of خطأ_تنسيق |
موضع البايت: N, رقم الوسيط: N or رمز الحالة: N |
std.fs, std.io and std.process report the inherited numeric runtime status (عدد, negative on failure), not an
enum. Their converters classify it with std.fs's صنف_خطأ_نظام, the target's file-error classifier, so the kind is
a فئة_خطأ_نظام variant (-2 gives غير_موجود on Linux and macOS). Like that classifier, they are not meant for
socket statuses. The two std.net enums share the kind صيغة_غير_صالحة, which means invalid syntax in either.
std.fs's links, permissions, attributes, canonical paths and temporary files, and std.env.variables, return
نتيجة T, فئة_خطأ_نظام, converted with فشل_من_فئة_خطأ_نظام. std.process.command returns خطأ_أمر
(فشل_من_خطأ_أمر), and std.time.duration_text returns خطأ_مدة (فشل_من_خطأ_مدة). std.net.socket_names keeps
std.net's numeric statuses.
examples/{ar,en}/errors/error_values.sn shows a whole program.
Context helpers
std.error.context provides plain functions for a result in passing, made to be used with ؟. Each takes the
result by value and moves the success value through untouched, so an owning T (text, a vector) needs no plain
bound.
| Arabic | English | Signature | Meaning |
|---|---|---|---|
مع_سياق |
with_context |
<T>(نتيجة T, فشل, الرسالة: نص): نتيجة T, فشل |
on an error, a new ad-hoc فشل with this message whose cause is the error |
إلى_فشل |
to_error |
<T, E>(نتيجة T, E, حول: دالة(&E): فشل): نتيجة T, فشل |
a typed error converted by a converter function value, such as فشل_من_خطأ_تحليل_عدد |
مع_ملاحظة |
with_note |
<T>(نتيجة T, فشل, الملاحظة: نص): نتيجة T, فشل |
on an error, the same error with one more note |
import std.error.failure;
import std.error.context;
import std.error.from_number;
import std.text.number;
def read_port(text: string): result int, Error {
let port: int = to_error(parse_int(&text), error_from_integer_parse_error)?;
return result.ok(port);
}
def main(): result int, Error {
let port: int = with_context(read_port("80x"), "while starting the server")?;
return result.ok(port);
}
Constraints from Stage0
std.error.failure, std.error.from_os, std.error.from_number and std.error.context stay within what the C
Stage0 compiles: no copy primitive (hence انسخ_فشل), no reference returns, no references in records or mutable
locals, no calls in vector literals and no top-level constants. tests/{ar,en}/stdlib/error/stage0_contract.sn
checks this when the runner is given a Stage0 binary in SULTANC_STAGE0_BIN. std.error.from_json and
std.error.from_net cannot meet it, because std.json and std.net.socket_address already use forms Stage0
rejects. The compiler does not import std.error.
Language versions
Language versions are optional. An author may add the message of a فشل in other languages, each under a BCP 47
tag, and code that knows the reader's language passes a locale value and gets the matching text. Nothing else
changes: the identity, the author's message, صف_فشل, هل_من_نوع, the context helpers and the frame main prints
all work as before. There is no global locale. A locale is a value passed to the function that needs it, so a server
answers each request in its own language.
import std.error.failure;
import std.text.language;
import std.io.println;
def main(): int {
let failure: mutable Error = error_with_kind("settings", "empty", "الملف فارغ");
error_add_language_version(&mutable failure, "en", "the settings file is empty");
let header: string = "fr-CH, fr;q=0.9, en;q=0.8";
let request: Locale = locale_from_accept_language(&header);
println(error_text_in(&failure, &request));
return 0;
}
examples/{ar,en}/errors/languages.sn shows a whole program.
Versions on a فشل
The versions are a private field of فشل: a list of (tag, text) pairs in the order added.
| Arabic | English | Signature | Meaning |
|---|---|---|---|
أضف_نسخة_لغة |
error_add_language_version |
(&متغير فشل, الوسم: نص, النص: نص): فراغ |
adds the message in a language. The tag is stored in canonical case (en_gb becomes en-GB), and a second version under the same tag replaces the first. The tag is not validated: an ill-formed tag is never found by a lookup, but can still be the first version |
نص_فشل_بلغة |
error_text_in |
(&فشل, &لغة): نص |
the message of this error for a locale (lookup below); causes are not included |
صف_فشل_بلغة |
error_describe_in |
(&فشل, &لغة): نص |
صف_فشل for a locale (below) |
اطبع_فشل_بلغة |
error_print_in |
(&فشل, &لغة): فراغ |
writes صف_فشل_بلغة and a newline to standard error |
نص_فشل_بلغة follows the RFC 4647 section 3.4 lookup:
- For each tag of the locale, most preferred first, it takes the version under exactly that tag, then under each
shorter form of it down to its primary language (
ar-SAtoar;zh-Hant-TWtozh-Hanttozh). A singleton left at the end is dropped with its subtag (en-GB-x-testtoen-GB). - Otherwise it returns the author's message.
- When that is empty, it returns the first version added.
A tag matches only a version as specific or less specific, so a locale en does not find a version en-GB.
Comparison is case-insensitive, since both sides are in canonical case. For a locale of one tag of one or two
subtags, the order is exact tag, base language, author's message, first version.
صف_فشل_بلغة is صف_فشل with three differences:
- The message of every error in the chain is chosen by
نص_فشل_بلغةwith the same locale, so a chain reads in one language as far as its versions go. - The connector words follow the locale:
ملاحظة:andبسبب:when its primary language isaror it has no tag, andnote:andcaused by:for any other language. They are fixed std text, not iSpeak and not a translation table. - A message or note after a connector whose direction differs from the connectors' (a Latin message after
بسبب:, an Arabic one aftercaused by:) is wrapped in U+2068 … U+2069 (std.text.bidi). The first line, which has no connector, is left as it is.
Notes have no versions: they are details (a path, a status) in the author's text. With an Arabic locale and no
Arabic version, صف_فشل_بلغة gives exactly صف_فشل for Arabic text.
Locales: std.text.language
| Arabic | English | Signature | Meaning |
|---|---|---|---|
لغة |
Locale |
type | a language priority list (RFC 4647 section 2.3): one tag, or the ranges of an Accept-Language header, most preferred first; possibly no tag. Opaque |
لغة_من_وسم |
locale_from_tag |
(&نص): لغة |
a locale of one tag, in canonical case, spaces around it dropped, _ accepted; a text that is not a well-formed tag gives a locale without a tag |
لغة_من_قبول_اللغة |
locale_from_accept_language |
(&نص): لغة |
the ranges of an HTTP Accept-Language header (RFC 9110 section 12.5.4) ordered by q, highest first, equal weights in the order written. A range of weight 0, the wildcard *, a range that is not a well-formed tag and an element with a malformed weight (q=1.5, q=.5, more than three decimals) are left out; other parameters are ignored |
وسوم_لغة |
locale_tags |
(&لغة): متجه نص |
a copy of the tags, most preferred first |
وسم_لغة |
locale_tag |
(&لغة): نص |
the most preferred tag, or empty |
اللغة_الأساسية |
locale_language |
(&لغة): نص |
the primary language of the most preferred tag (ar-SA gives ar), or empty |
انسخ_لغة |
locale_copy |
(&لغة): لغة |
a copy; the same as استنسخ, in a form Stage0 compiles |
fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5 gives fr-CH, fr, en.
BCP 47 tags (RFC 5646) are implemented once in std for any user:
| Arabic | English | Signature | Meaning |
|---|---|---|---|
وسم_لغة_صالح |
is_language_tag |
(&نص): منطقي |
whether the text is a well-formed tag: language (2 to 8 letters, with up to three extended subtags after 2 to 3 letters), script (4 letters), region (2 letters or 3 digits), variants, extensions, private use; or a private-use tag x-…. _ is accepted as a separator. Registration is not checked, and the irregular grandfathered tags (i-klingon) are not accepted |
طبع_وسم_لغة |
normalize_language_tag |
(&نص): نص |
canonical case with - (RFC 5646 section 2.1.1): lower case, a region in upper case, a script in title case, all lower case after a singleton (sr_latn_rs gives sr-Latn-RS) |
اللغة_الأساسية_لوسم |
language_tag_base |
(&نص): نص |
the primary language subtag in lower case |
اختصر_وسم_لغة |
truncate_language_tag |
(&نص): نص |
one RFC 4647 lookup fallback step (zh-Hant-CN gives zh-Hant; ar gives empty) |
The environment's locale: std.text.system_language
| Arabic | English | Signature | Meaning |
|---|---|---|---|
لغة_من_البيئة |
locale_from_environment |
(): لغة |
the program's locale: SULTANC_LANG, then LC_ALL, LC_MESSAGES, LANG, then the language the program was compiled with (--lang, written by the compiler into std.process.failure_text), then Arabic. Always one tag |
A value is read as described under Runtime failures of a program: a tag or a
POSIX locale, with C, POSIX, empty and ill-formed values skipped. The runtime-failure path uses this same
function for the language of its frames, so std has one reading of these variables. It reads the environment on
every call; the result is a value, not a cache.
Direction isolation: std.text.bidi
| Arabic | English | Signature | Meaning |
|---|---|---|---|
اتجاه_نص |
text_direction |
type: محايد / neutral, من_اليسار_إلى_اليمين / left_to_right, من_اليمين_إلى_اليسار / right_to_left |
a direction |
اتجاه_النص |
text_direction_of |
(&نص): اتجاه_نص |
the direction of the first strong character, skipping what lies inside isolates (UAX #9 P2 to P3): the direction a first-strong isolate gives the text |
اعزل_اتجاه |
bidi_isolate |
(&نص): نص |
U+2068 (first-strong isolate), the text, U+2069 (pop directional isolate) |
اعزل_اتجاه_في |
bidi_isolate_in |
(&نص, السياق: اتجاه_نص): نص |
the text isolated when its direction is strong and differs from the context's; otherwise an unchanged copy |
A character's direction comes from script ranges, not from the Unicode Bidi_Class table. Hebrew, Arabic, Syriac, Thaana, NKo and the other right-to-left blocks are right-to-left, apart from their digits and combining marks. Latin, Greek, Cyrillic, Armenian, the Indic and the East Asian scripts are left-to-right. Everything else, digits and punctuation included, is neutral. Patterns use the Arabic variant names.
The compiler's iSpeak keeps its own isolation of quoted facts in Arabic terminal diagnostics
(compiler/diagnostics/output/terminal.sn): it isolates backtick-quoted spans while copying. std does not depend on
iSpeak and holds none of its tables.
English versions of std's own errors
Every converter in std.error.from_os, from_number, from_json and from_net adds an English version (en) of
its Arabic message. The message, identity and notes are unchanged, and notes stay as written (رمز الحالة: -2).
std.text.language, std.text.bidi, std.text.system_language and the language functions of
std.error.failure are within the C Stage0's language, and tests/{ar,en}/stdlib/error/stage0_contract.sn
calls each of them.
Message catalogs
For messages that are not errors (validation messages, rejection pages, UI text), std.text.messages keeps a
catalog: a message identifier with one template per language tag, named arguments in braces ({field}), and CLDR
plural forms selected by a count (Arabic's zero, one, two, few, many and other; English's one and other). The
reader's languages are a Locale (for a request, std.http.locale http_request_locale reads Accept-Language);
lookup tries each preferred tag and its shorter forms, then the catalog's default language. An argument whose
direction differs from the message's is isolated (std.text.bidi). tests/*/stdlib/behavior/text_messages.sn
covers it.
Stopping a program
panic
import std.process.panic;
def divide(a: int, b: int): int {
if (b != 0) { return a / b; };
panic("division by zero");
}
إنهيار(الرسالة: نص) (std.process.panic) prints the frame "the program stopped" and the message, then ends the
process with status 1 through the std process exit, as اخرج / exit does. It is a std function, not a compiler
primitive. انهيار without a hamza names the same identifier.
- Never returning. The frontend identifies
إنهيارof the trusted std moduleprocess/panic.snby its RootID and module key, the way it authenticates the Linux kernel contract, and never by a spelling a user module could copy. It hands the symbol to MSIR lowering. A call statement to it ends its block in a trap, as a compile-time failure does, so a function returning a value needs no return after the call. The C Stage0 does the same (bootstrap/src/semantic/never_return.c): it identifiesإنهيارby the canonical path oflibrary/std/process/panic.snunder the project root, ends the path in its flow check and emitsunreachableafter the call. - Abort semantics. The process ends at once. Owned values that are still alive are not dropped, as with Rust's
panic=abort, and the operating system reclaims the memory.
Assertions
تأكد / assert and تأكد_برسالة / assert_with_message (std.debug.assert) stop the program through the same
path: the frame "the program stopped" with the reason "an assertion failed", the optional message under it, and
status 1. Compile-time evaluation specifies them.
Runtime failures of a program
A program prints why it ends through one path, std.process.failure (library/std/process/failure.sn), for native
executables (x86_64, arm64), wasm32, the LLVM backend and the interpreter alike. The output is the frame of a
runtime-failure identity, then the payload on the lines under it, on standard error.
| Identity | Frame (Arabic / English) | When |
|---|---|---|
SULTANC-RUNTIME-PROGRAM-ERROR |
خطأ[…]: انتهى البرنامج بخطأ / error[…]: the program ended with an error |
main returned an error; payload: its صف_فشل text |
SULTANC-RUNTIME-PROGRAM-STOPPED |
خطأ[…]: توقف البرنامج / error[…]: the program stopped |
إنهيار / panic; payload: its message |
SULTANC-RUNTIME-PROGRAM-STOPPED with the reason runtime.assertion-failed |
… — فشل تأكيد. / … — An assertion failed. |
a failed تأكد / assert; then --> <file>:<line>:<column>, the place of the call (@@track_caller); payload: the optional message |
The frames are iSpeak's. The two identities and the reason are registered like every diagnostic
(compiler/diagnostics/identity.sn, reason.sn, registry.sn, both locales), and a frame is the headline a
diagnostic of that identity has on a terminal. A program cannot run iSpeak, so the compiler renders the frames in
both languages (compiler/driver/program_failure_text.sn) and writes them, at every compilation, as the text of the
std module std.process.failure_text (a source overlay on library/std/process/failure_text.sn), together with the
language the program is compiled with. The file's own text is the Arabic rendering, which the C Stage0 and tools
read; tests/{ar,en}/ispeak/check_program_failure_text.sn checks it against the compiler's rendering. std holds no
table of its own.
The program's language is that of لغة_من_البيئة() (std.text.system_language), the one place std reads these
variables. It is the first of:
SULTANC_LANG;LC_ALL,LC_MESSAGES,LANG;- the
--langthe program was compiled with (with no--lang, Arabic; with--lang=auto, the compiler's host locale); - Arabic.
A value is a BCP 47 tag (en-US) or a POSIX locale (ar_SA.UTF-8, de_DE@euro, read as ar-SA, de-DE). A
locale whose primary language is ar gives Arabic frames, and any other language gives English. An empty value, C,
C.…, POSIX and a value that is not a language tag carry no language, and the next source is read, so a
LANG=C.UTF-8 CI machine falls through to the compiled language.
main returning an error
import std.error.failure;
import std.error.context;
import std.error.from_number;
import std.text.number;
def read_port(text: string): result int, Error {
let port: int = to_error(parse_int(&text), error_from_integer_parse_error)?;
return result.ok(port);
}
def main(): result int, Error {
let port: int = with_context(read_port("80x"), "while starting the server")?;
return result.ok(port);
}
On نتيجة.نجح(n) the program exits with status n. On نتيجة.خطأ(f) it prints the frame "the program ended with an
error" and صف_فشل(&f), then exits with status 1. With SULTANC_LANG=en the output of the program above is:
error[SULTANC-RUNTIME-PROGRAM-ERROR]: the program ended with an error
while starting the server
بسبب: في النص محرف ليس رقما عشريا
ملاحظة: موضع البايت: 2
compiler/frontend/desugar/entry_result.sn does this in the frontend. In the root module, an entry function
(الرئيسية / main) without parameters whose result is written نتيجة T, E keeps its body under a hidden name
(نقطة_دخول__…), and the entry becomes an ordinary function returning T. It matches the hidden function's result,
returns the value, or calls أبلغ_عن_خطأ_نقطة_الدخول and returns 1. The generated text imports
std.process.failure. Every backend's entry adapter (backend/target/executable_entry_query_context.sn, the LLVM
entry.sn) and the interpreter thus see an entry with an exit status, and the entry check
(SULTANC-ENTRYPOINT-INVALID-SIGNATURE) applies to T: a T that is not an exit status, or an E that is not
فشل, is reported at the written result type. The error is moved into the report and dropped on the normal return
path, so the program ends cleanly.
The interpreter
sultanc run executes the same std code, so it prints the same frames. The interpreter gives a program the
language variables of its environment (SULTANC_LANG, LC_ALL, LC_MESSAGES, LANG) as its startup environment;
other variables are absent. A raw Linux exit_group ends the interpretation with the status, so exit, panic and
assertions end an interpreted program with the native status.
Tests
The tests come in Arabic and English pairs, and the English one spells ?.
tests/{ar,en}/language/propagation/covers the four positions on results and options (native, interpreter and Stage0 agree), drop correctness over 10^5 early returns, and the rejections in a disallowed position, in a function returning no result, and on an option in a result function.tests/{ar,en}/diagnostics/e2e/propagation_error_type.*checks a different error type, reported at the؟with both types, in exact Arabic and English output.tests/{ar,en}/stdlib/error/covers constructors and accessors, a three-level chain searched at each level, notes order, the exactصف_فشلtext,استنسخandانسخ_فشلround trips, every variant of every converted enum, the context helpers, the language lookup, Accept-Language parsing, the chain in several languages, bidirectional isolation, the Stage0 contract, and flat memory (rss_flat.snandlanguage_rss_flat.snrun 10^5 chains and check that the allocator's live bytes are equal before and after; the interpreter does not use the std allocator, so these run native only).tests/{ar,en}/runtime/failure/coversmainreturning an error (SULTANC_LANG=aranden) and success, andpanicand failed assertions in both program languages, with the exact standard error, exit status, and native and interpreter agreement. The runner's// بيئة:header sets the program's environment and// خطأ-قياسي-ملف:names the expected standard error.tests/{ar,en}/ispeak/check_program_failure_text.snchecks the words module against the compiler's rendering.
Not yet supported
compile_errorwith aفشلis not available;compile_errortakes text.- Catalog files, translation tools and
.ar()-style sugar are out of scope. - The payload of the frame
mainprints isصف_فشل, the author's messages. Printing it withصف_فشل_بلغةand the program's locale is not done. - An alias of an option type is not recognized by
؟.