Pragmas and feature settings

A pragma is a set of module-scoped switches that tune how the reader and compiler treat your source. Koskari’s optional features – bracket-call checking, name-resolution mode, layout reading, and so on – are all controlled through the pragma. A pragma affects only the code that comes after it in the same module, and never leaks into modules you import.

Setting a pragma

Use the pragma form. Each setting is a bracketed list: a slash-namespaced key followed by its value(s). A bare [key] uses the setting’s default value.

(pragma [compiler/call-brackets 'error]
        [compiler/resolution 'scheme]
        [reader/brackets False])
  • Values are ordinary expressions, so a literal symbol must be quoted – 'error, 'scheme, 'warn. Booleans (True / False), numbers, and strings are self-carrying and need no quote, and a computed value (a variable or call) works too.

  • The change is forward-only: it applies to everything read and compiled after the form, not retroactively, and not to modules you import.

  • You may set a pragma more than once; a later setting replaces the earlier one.

Reading a pragma

The same pragma form reads the current state:

(pragma compiler/call-brackets)   ; the current value of one setting
(pragma)                          ; the whole pragma object

And help describes it:

(help pragma)                        ; current values + every setting's default
(help (pragma compiler/call-brackets))   ; one setting: its signature and doc

(help pragma) lists the current values alongside every registered setting and its default; (help (pragma KEY)) shows the accepted arguments and documentation for a single setting.

A second read form exists for code that runs during compilation:

(active-pragma)                          ; the pragma of the module being compiled
(active-pragma compiler/enable-asserts)  ; one of its settings

(pragma KEY) reads the pragma of the module the form is written in. A macro body, though, is a closure over the module that defined it, while it runs while some other module compiles – so inside a macro, (pragma KEY) would answer for the wrong module. (active-pragma KEY) reads the pragma the compiler has bound for the current compilation turn instead, so a macro’s expansion is governed by the settings of the module using it. Outside a compilation turn no pragma is bound, and (active-pragma) answers a default pragma carrying every setting’s baseline value.

What each setting means

Settings are grouped by what they affect: the reader (reader/*), the compiler (compiler/*), and the pragma machinery itself (pragma/*).

Reader

reader/index-brackets, reader/dot-attrs, and reader/string-segments govern the three suffix syntaxes; Grammar states their rules, and turning one off makes its character inert in the reader.

  • reader/brackets, default on: whether [ ] reads into a distinct bracket-pair value that remembers its origin, rather than a plain pair. Off makes [ ] and ( ) completely indistinguishable. See Brackets and the call convention.

  • reader/index-brackets, default on: whether data["bar"] reads as (item data "bar").

  • reader/dot-attrs, default on: whether obj.value reads as (attr obj value).

  • reader/string-segments, default on: whether a - on a string literal’s closing quote continues it into the next quoted segment.

  • reader/unary, default on: whether -, +, and ~ attached to the front of a name, a digit, or a delimited form read as the unary call: -x is (- x), +x is (+ x), ~(f y) is (~ (f y)). A signed number keeps its sign as part of the literal, and -> or a bare operator stay ordinary symbols.

  • reader/layout, default on: whether indentation-based layout blocks are read. Off leaves only s-expression syntax.

  • reader/implicit-calls, default on: whether several values on one layout line form an implicit call. Off makes more than one value on a line an error.

  • reader/indent, default error: what the layout reader does with a content line whose indent does not match its block: the first line of a body sets the body’s indent, every later line must start at that column, and a top-level line starts at column 0. error raises a ReaderSyntaxError, warn emits a SyntaxWarning and reads the line as a sibling form, ignore reads it as a sibling form silently.

  • reader/fences, default on: whether triple-backtick fences read into compile/apply-fence forms. Off, backtick keeps only its quote and quasiquote readings. See compile/apply-fence.

  • reader/decorators, default on: whether leading @ layout lines read as decorators, lowering the decorated form to compile/decorate. Off, @ has no layout meaning and reads through the ordinary atom rules. This gates the reader surface only; (decorate ...) remains available as a macro regardless.

Compiler

  • compiler/resolution, default koskari: how writes to names resolve. The value is required, one of:

    • koskari – strict: a write to an undeclared name is a compile error.

    • scheme – an unsatisfied write promotes to a module global.

    • python – a first write creates an implicit local unless declared global.

  • compiler/call-brackets, default warn: what happens when a bracket-pair [ ] reaches call position, where ( ) is conventionally expected. One of:

    • warn – emit a SyntaxWarning.

    • error – raise a compile error.

    • ignore – nothing; the convention is unenforced.

    A bare [compiler/call-brackets] selects warn. See Brackets and the call convention.

  • compiler/stray-colon, default warn: what happens when a bare : symbol is compiled as a name. Nothing binds :, so this is always a mistake; it most often comes from a layout form half-converted to s-expressions, where the trailing block colon of

    for-each [x (xs)]:
        body
    

    survives the rewrite as (for-each [x (xs)]: body) – which reads as (for-each [x (xs)] : body) and fails at runtime with a NameError. One of:

    • warn – emit a SyntaxWarning.

    • error – raise a compile error.

    • allow – nothing; the : compiles as an ordinary name lookup.

    A bare [compiler/stray-colon] selects warn. A trailing colon attached to a name is a keyword, not a stray colon: (when foo: (foo)) passes the keyword foo:, which compiler/keyword-literals covers instead.

  • compiler/stray-hash, default warn: what happens when a bare # symbol is compiled as a name. A # prefixes a collection opener (#(, #[, #{) or a syntax quote (#', #` ); followed by a space or the end of the form it reads as the symbol #, so # [1 2] is a name lookup of # followed by a list rather than the list literal #[1 2]. One of:

    • warn – emit a SyntaxWarning.

    • error – raise a compile error.

    • allow – nothing; the # compiles as an ordinary name lookup.

    A bare [compiler/stray-hash] selects warn. A # that begins a longer name, such as #tuple, is not a stray hash, and a quoted '# is data and never flagged.

  • compiler/keyword-literals, default warn: what happens when an unquoted keyword is compiled as a literal value. A keyword evaluates to itself, which is legal but is more often a typo: (if foo: 1 2) tests the keyword foo: for truthiness – always true – rather than reading the variable foo. One of:

    • warn – emit a SyntaxWarning.

    • error – raise a compile error.

    • allow – nothing; the keyword compiles to itself.

    Quote the keyword to state that the literal is what you meant; 'foo: compiles through the quote special and is always exempt. Two other uses are structural rather than value positions, so they are never flagged: keyword arguments ((print 1 sep: "-")) and keyword markers consumed by a form ((cond [(> x 1) 'big] [else: 'small])).

  • compiler/enable-asserts, default on: whether (assert ...) forms expand to their check. Off, an assert compiles away entirely: neither the condition nor the message is compiled or evaluated, mirroring Python’s -O behavior. The gate is consulted as each assert is compiled, so a change affects only the asserts that appear after it. See compile/assert.

  • compiler/bare-control-words, default convert: what happens when a bare return, continue, break, raise, yield, or pass symbol is compiled. These statement words are call forms in Koskari, and their bare forms expand the lone symbol into its call form – return becomes (return) – matching the Python-style spelling. pass expands to (begin), a no-op that evaluates to None. One of:

    • convert – expand silently.

    • warn – emit a SyntaxWarning, then expand.

    • error – refuse with a compile error naming the corrective spelling.

    Each module answers for its own setting: the gate reads the pragma of the module being compiled, so a strict module does not harden its neighbors. The cost of conversion is that these six names always expand and can never name a variable. See compile/return, compile/continue, compile/break, compile/raise, compile/yield, and compile/begin (pass).

  • compiler/rewrite-limit, default 32: how many macro-expansion rewrites a single form may take before the compiler reports it as divergent. A macro that expands into an ever-growing form stops with a compile error instead of hanging. An expansion that repeats an earlier form is reported immediately as a cycle, regardless of this setting. 0 removes the limit; legitimate expansion chains are only a few rewrites deep, so the default leaves generous headroom.

  • compiler/tco-enable, default on: whether tail calls are optimized.

  • compiler/convert-fast, default on: whether the compiler lowers local variable access to its fast form. Off disables that optimization.

  • compiler/compress, default off: reserved for a future bytecode compression pass; currently inert.

  • compiler/cache-function, default on: whether function and lambda bodies attach a per-site cache to attribute and method-call instructions, so a repeated access with the same receiver type skips the resolution walk. On is the baseline; the reused code that loops flow through is where it pays. See Attribute and method caching.

  • compiler/cache-expression, default off: the same caching for top-level module and REPL expressions. Off by default because top-level code usually runs once as it loads, so a cache would not amortize. A bare [compiler/cache-expression] turns it on. See Attribute and method caching.

  • compiler/protect-code, default off: whether each compiled code object that compiler/pack-module leaves alone has its bytecode sealed read-only the moment it is built. On, a stray or hostile write to that bytecode faults instead of corrupting the interpreter, at the cost of a full memory page per code object. A bare [compiler/protect-code] turns it on. See Code sealing.

  • compiler/pack-module, default on: whether the function code a module compiles while it loads is sealed read-only when the module finishes loading. On – the baseline – a loaded module’s functions are read-only at no per-object cost. Top-level expressions are not covered.

    (pragma [compiler/pack-module False])
    

    See Code sealing for the model and its tradeoffs.

Pragma machinery

  • pragma/strict-settings, default on: when on, naming a setting that is not registered is an error; off downgrades it to a warning.

  • pragma/strict-watchers, default on: when on, an error while applying a pragma change is raised; off downgrades it to a warning.

See also