relon_parser/directive.rs
1//! Directive name constants and shape-by-name lookup.
2//!
3//! `#name` directives are the structural / declarative attributes of
4//! the language (imports, schemas, defaults, error messages, brand,
5//! internal-binding markers, the `#main(...)` entry signature). Host-registered only;
6//! no user-definable `#`. Each name dispatches to one of five fixed
7//! [`DirectiveShape`] forms — the CST parser uses
8//! [`directive_shape`] to choose its production path, and the typed
9//! lowering uses the same lookup to interpret the directive's
10//! parsed body.
11//!
12//! This module used to host a full winnow-based combinator parser as
13//! well; P6 retired the runtime parser in favour of the rowan CST
14//! walker in `lower.rs`. Only the lookup table + name constants
15//! survive — they're re-exported by `relon-analyzer` and
16//! `relon-evaluator` so every layer dispatches off the same string
17//! literals.
18
19use crate::DirectiveShape;
20
21/// Canonical directive names. Centralizing the strings here lets
22/// downstream crates (`relon-analyzer`, `relon-evaluator`) refer to the
23/// same identifiers without maintaining their own private mirrors.
24pub const INTERNAL: &str = "internal";
25pub const DEFAULT: &str = "default";
26pub const EXPECT: &str = "expect";
27pub const MSG: &str = "msg";
28pub const ERROR: &str = "error";
29pub const BRAND: &str = "brand";
30pub const SCHEMA: &str = "schema";
31pub const ENUM: &str = "enum";
32pub const IMPORT: &str = "import";
33pub const MAIN: &str = "main";
34/// `#relaxed` — bare file-level directive that opts the module out
35/// of strict inference. Strict is the analyzer's default: every
36/// value must have a statically inferable type, and sites the
37/// analyzer can't classify (uninferrable spread sources, dynamic
38/// keys without a hint, untyped closure parameters, native fns with
39/// no signature, …) surface as errors. A `#relaxed` directive at
40/// the file level lets those positions stay silent (the runtime
41/// still type-checks them on the way out). The opt-out propagates
42/// across `#import` from the *entry* module: a relaxed entry
43/// analyses every reachable import in relaxed mode too, so a strict
44/// library doesn't tighten a relaxed entry by accident.
45pub const RELAXED: &str = "relaxed";
46/// `#unstrict` — exact synonym for [`RELAXED`]. Both names are
47/// accepted so authors can pick whichever reads more naturally next
48/// to other directives.
49pub const UNSTRICT: &str = "unstrict";
50/// Phase A of the trait-bound / schema-method system: a method-level
51/// pragma `#derive <Constraint>` declares the following method is the
52/// witness for the named built-in constraint (e.g. `Equatable`,
53/// `Comparable`). Body shape is a single bare identifier (the
54/// constraint name). Registered globally so the parser accepts it; the
55/// analyzer enforces that it only appears immediately above a method
56/// inside a `with { ... }` block.
57pub const DERIVE: &str = "derive";
58/// Schema-level (or, in rare cases, method-level) pragma
59/// `#no_auto_derive <Constraint>` opts the schema out of structural
60/// auto-derivation for the named constraint (e.g. opt out of the
61/// default `JsonProjectable` derivation for an internal-only schema).
62pub const NO_AUTO_DERIVE: &str = "no_auto_derive";
63/// Method-level pragma `#native` declares the method's body lives in
64/// host Rust (registered through the schema-method host API). The
65/// parser leaves the method's body empty when this pragma is present;
66/// the analyzer cross-checks against the host registry.
67pub const NATIVE: &str = "native";
68/// Schema-rooted Phase A.1: `#extend X with { ... }` adds methods to
69/// an already-declared schema X (built-in or user). Same parser shape
70/// as `#schema` (NameBody), distinguished from `#schema` by the
71/// directive name. Visibility is tied to the file's `#import` chain
72/// (decision 9). Cannot re-declare X — only extend its method table.
73///
74/// Note: method-level `#internal` is the existing [`INTERNAL`] directive
75/// reused — `#internal` already serves as a field-level marker that
76/// keeps a dict-body binding visible to siblings but hidden from the
77/// outer projection. In a `with { ... }` block, the same `#internal`
78/// directive marks a method as schema-internal (only callable from
79/// other method bodies on the same schema).
80pub const EXTEND: &str = "extend";
81
82/// Directive name → expected shape. Dispatch happens by name; unknown
83/// `#name` produces a parse error.
84pub const DIRECTIVE_SHAPES: &[(&str, DirectiveShape)] = &[
85 (INTERNAL, DirectiveShape::Bare),
86 (DEFAULT, DirectiveShape::Value),
87 (EXPECT, DirectiveShape::Value),
88 (MSG, DirectiveShape::Value),
89 (ERROR, DirectiveShape::Value),
90 (BRAND, DirectiveShape::Value),
91 (SCHEMA, DirectiveShape::NameBody),
92 (ENUM, DirectiveShape::Enum),
93 (IMPORT, DirectiveShape::Import),
94 (MAIN, DirectiveShape::Main),
95 (RELAXED, DirectiveShape::Bare),
96 (UNSTRICT, DirectiveShape::Bare),
97 // Trait-bound / schema-method pragmas (Phase A): parsed globally,
98 // semantic placement enforced by the analyzer.
99 (DERIVE, DirectiveShape::Value),
100 (NO_AUTO_DERIVE, DirectiveShape::Value),
101 (NATIVE, DirectiveShape::Bare),
102 (EXTEND, DirectiveShape::NameBody),
103];
104
105/// Look up a directive's expected shape by name. Returns `None` for
106/// unknown directives.
107pub fn directive_shape(name: &str) -> Option<DirectiveShape> {
108 DIRECTIVE_SHAPES
109 .iter()
110 .find_map(|(n, s)| (*n == name).then_some(*s))
111}
112
113/// True when an `#import` path looks like a URL the remote resolver
114/// chain knows how to handle (`http://` / `https://`). Centralized
115/// here so every layer that classifies import paths — the analyzer's
116/// `--require-hash` scoping, the evaluator's `RemoteHttpResolver`
117/// gating, and the facade's sandboxed-posture short-circuit (including
118/// the wasm32 build, which never links the resolver) — agrees on the
119/// exact same prefix set.
120pub fn is_remote_url(path: &str) -> bool {
121 path.starts_with("https://") || path.starts_with("http://")
122}