oxedyne/fe2o3/fe2o3_sbj/src/kinds.rs
36.5 KiB, 13 runs
created by r1870400018:22220, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! The v0 node vocabulary, and the schema each kind obeys. |
| 2 | //! |
| 3 | //! A node is a JDAT `usr` daticle: a `u16` kind code followed by the node's payload. The code sits |
| 4 | //! in front of the payload on the wire, so a decoder knows what a node is before it reads a byte of |
| 5 | //! it, and an unknown or forbidden kind is refused without inspecting its contents. |
| 6 | |
| 7 | use crate::{ |
| 8 | SCHEMA_APP, |
| 9 | SCHEMA_CARD, |
| 10 | SCHEMA_CHROME, |
| 11 | SCHEMA_DOC, |
| 12 | SCHEMA_POST, |
| 13 | }; |
| 14 | |
| 15 | use oxedyne_fe2o3_core::prelude::*; |
| 16 | use oxedyne_fe2o3_jdat::prelude::*; |
| 17 | |
| 18 | /// The type a field must carry, checked against the decoded daticle. |
| 19 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 20 | pub enum FieldType { |
| 21 | /// A UTF-8 string. |
| 22 | Str, |
| 23 | /// An unsigned 8-bit integer. |
| 24 | U8, |
| 25 | /// A signed 8-bit integer. |
| 26 | I8, |
| 27 | /// An unsigned 32-bit integer. |
| 28 | U32, |
| 29 | /// A boolean. |
| 30 | Bool, |
| 31 | /// A fixed 32-byte string, the width of a v0 content hash. |
| 32 | Hash32, |
| 33 | /// A typed link address: a single-entry map naming a `name` or a `hash`. See [`check_address`]. |
| 34 | Address, |
| 35 | /// A non-empty list of nodes, walked and validated like any other, and carried in a field rather |
| 36 | /// than in `children`. It is what a `surface` names its semantic alternative by (§4.2). |
| 37 | Nodes, |
| 38 | } |
| 39 | |
| 40 | /// The schema a payload declares, and with it the vocabulary the payload is held to (`SPEC.md` §4.2). |
| 41 | /// |
| 42 | /// The schema is the enforcement. "A document is never a program" is not a rule written on top of the |
| 43 | /// format; it is the fact that `oxeweb/doc/0` admits the kinds 1 to 13 and nothing else, that the |
| 44 | /// schema sits in the envelope, and that the envelope is signed (§1.3), so a payload cannot be |
| 45 | /// re-labelled into a vocabulary its author never claimed. |
| 46 | /// |
| 47 | /// Each schema's admitted set is closed. A chrome tree may carry an `edit`, because the address bar |
| 48 | /// is one; an application's tree may carry an `edit` and a `surface`, because a game's pane is one; |
| 49 | /// and a document may carry neither, because a document that could name either would be a program. |
| 50 | /// |
| 51 | /// A schema fixes two vocabularies, not one: the node kinds it admits ([`Schema::admits`]) and the |
| 52 | /// style properties it admits ([`Schema::admits_style`]). Both are closed, and both are the schema's |
| 53 | /// alone. What a property MEANS, and the width it is written at, are the same in every schema; only |
| 54 | /// whether a tree may name it at all depends on which schema the envelope declared. |
| 55 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 56 | pub enum Schema { |
| 57 | /// An oxeweb document: the kinds 1 to 13, neither reserved kind, and the seven document style |
| 58 | /// properties. |
| 59 | Doc, |
| 60 | /// The browser's own chrome: the document kinds and style properties, the `edit` node, and the |
| 61 | /// interface style properties. |
| 62 | Chrome, |
| 63 | /// An application's tree: everything a chrome admits, and the `surface` node. |
| 64 | App, |
| 65 | } |
| 66 | |
| 67 | impl Schema { |
| 68 | |
| 69 | /// The name an envelope declares this schema by. |
| 70 | pub fn name(&self) -> &'static str { |
| 71 | match self { |
| 72 | Self::Doc => SCHEMA_DOC, |
| 73 | Self::Chrome => SCHEMA_CHROME, |
| 74 | Self::App => SCHEMA_APP, |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | /// The schema a name declares, or an error naming the schema it was handed. |
| 79 | /// |
| 80 | /// A payload declaring a schema this build does not read is refused rather than read as though it |
| 81 | /// were one of these, since the container carries any schema at all (§1.2) and a reader that |
| 82 | /// validates three must refuse the rest. |
| 83 | /// |
| 84 | /// These are the schemas whose payload is a node tree. The container carries others whose payload |
| 85 | /// is not — a post and a card are flat records — and those are reached through `doc::Payload` |
| 86 | /// rather than here, because there is no tree in them for this validator to walk. |
| 87 | pub fn from_name(name: &str) -> Outcome<Self> { |
| 88 | match name { |
| 89 | SCHEMA_DOC => Ok(Self::Doc), |
| 90 | SCHEMA_CHROME => Ok(Self::Chrome), |
| 91 | SCHEMA_APP => Ok(Self::App), |
| 92 | _ => Err(err!( |
| 93 | "Schema '{}' is none of the node-tree schemas this validator reads: '{}', '{}', \ |
| 94 | and '{}'. A payload that is a record rather than a tree, such as '{}' or '{}', is \ |
| 95 | read through `doc::read_artefact` and written through `doc::write_artefact`.", |
| 96 | name, SCHEMA_DOC, SCHEMA_CHROME, SCHEMA_APP, SCHEMA_POST, SCHEMA_CARD; |
| 97 | Invalid, Input, Mismatch)), |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | /// Whether this schema admits the given reserved kind. |
| 102 | /// |
| 103 | /// This is the whole of the reserved kinds' admission rule, and it is a table rather than a |
| 104 | /// special case anywhere else: a surface is refused in a document because `oxeweb/doc/0` does not |
| 105 | /// admit it here, and for no other reason. |
| 106 | pub fn admits(&self, reserved: ReservedKind) -> bool { |
| 107 | match (self, reserved) { |
| 108 | (Self::Doc, _) => false, |
| 109 | (Self::Chrome, ReservedKind::Edit) => true, |
| 110 | (Self::Chrome, ReservedKind::Icon) => true, |
| 111 | (Self::Chrome, ReservedKind::Surface) => false, |
| 112 | (Self::App, _) => true, |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /// Whether this schema admits the given style property (§4.4). |
| 117 | /// |
| 118 | /// This is the whole of the style vocabulary's admission rule, and it is a table for the same |
| 119 | /// reason [`Schema::admits`] is one: `grid` is refused in a document because `oxeweb/doc/0` does |
| 120 | /// not admit it here, and for no other reason. |
| 121 | /// |
| 122 | /// The v0 document vocabulary is frozen at the eight properties of [`STYLE_FIELDS`]. A published |
| 123 | /// document cannot name an interface property, whatever a later reader learns to draw, so a |
| 124 | /// document's appearance is settled by the vocabulary it was published under. |
| 125 | pub fn admits_style(&self, prop: &StyleField) -> bool { |
| 126 | match (self, prop.scope) { |
| 127 | (Self::Doc, StyleScope::Doc) => true, |
| 128 | (Self::Doc, StyleScope::Interface) => false, |
| 129 | (Self::Chrome, _) => true, |
| 130 | (Self::App, _) => true, |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /// The style-record property of the given name that this schema admits, if it admits one. |
| 135 | /// |
| 136 | /// A property this schema does not admit comes back as `None` exactly as an unknown one does, so |
| 137 | /// no caller can reach a property by name without passing the admission rule first. A caller that |
| 138 | /// must tell the two apart, to say which it refused and why, asks [`known_style_field`] as well. |
| 139 | pub fn style_field(&self, name: &str) -> Option<&'static StyleField> { |
| 140 | match known_style_field(name) { |
| 141 | Some(field) if self.admits_style(field) => Some(field), |
| 142 | _ => None, |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | /// The tree this schema describes, as an error message names it. |
| 147 | pub fn tree_label(&self) -> &'static str { |
| 148 | match self { |
| 149 | Self::Doc => "a document", |
| 150 | Self::Chrome => "a chrome tree", |
| 151 | Self::App => "an application tree", |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | /// The vocabulary this schema admits, as an error message spells it. |
| 156 | pub fn admits_label(&self) -> &'static str { |
| 157 | match self { |
| 158 | Self::Doc => "the kinds 1 to 13 and no others", |
| 159 | Self::Chrome => "the kinds 1 to 13 and the reserved kinds 'edit' and 'icon', and no \ |
| 160 | others", |
| 161 | Self::App => "the kinds 1 to 13 and the reserved kinds 'edit', 'surface' and 'icon', \ |
| 162 | and no others", |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | /// The style vocabulary this schema admits, as an error message spells it. |
| 167 | pub fn style_admits_label(&self) -> &'static str { |
| 168 | match self { |
| 169 | Self::Doc => "the eight document properties 'fill', 'size', 'lang', 'dir', 'bg', \ |
| 170 | 'pad', 'align' and 'radius', and no others", |
| 171 | Self::Chrome => "the eight document properties and the interface properties 'grid', \ |
| 172 | 'pack', 'grow', 'max', 'border', 'shadow', 'nowrap' and 'ends', and no others", |
| 173 | Self::App => "the eight document properties and the interface properties 'grid', \ |
| 174 | 'pack', 'grow', 'max', 'border', 'shadow', 'nowrap' and 'ends', and no others", |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /// One field of a node's payload map. |
| 180 | #[derive(Clone, Copy, Debug)] |
| 181 | pub struct Field { |
| 182 | /// The map key. |
| 183 | pub name: &'static str, |
| 184 | /// The type the value must carry. |
| 185 | pub typ: FieldType, |
| 186 | /// Whether the field may be omitted. |
| 187 | pub opt: bool, |
| 188 | } |
| 189 | |
| 190 | /// What a node may contain. |
| 191 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 192 | pub enum Content { |
| 193 | /// No children at all. |
| 194 | None, |
| 195 | /// Flow content: sections, paragraphs, headings, lists, boxes, images. |
| 196 | Flow, |
| 197 | /// Inline content: text runs, emphasis, links. |
| 198 | Inline, |
| 199 | /// List items only. |
| 200 | Items, |
| 201 | } |
| 202 | |
| 203 | /// The v0 node kinds. Deliberately small; growth is a versioned decision. |
| 204 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 205 | pub enum NodeKind { |
| 206 | /// Root of a document. |
| 207 | Doc, |
| 208 | /// A titled division. |
| 209 | Section, |
| 210 | /// A paragraph. |
| 211 | Para, |
| 212 | /// A heading, levels 1 to 6. |
| 213 | Heading, |
| 214 | /// An ordered or unordered list. |
| 215 | List, |
| 216 | /// One item of a list. |
| 217 | Item, |
| 218 | /// A generic structural box. |
| 219 | Boxx, |
| 220 | /// An image, referenced by content hash. |
| 221 | Image, |
| 222 | /// A run of text. Its payload is a string rather than a map. |
| 223 | Text, |
| 224 | /// Emphasised inline content. |
| 225 | Emph, |
| 226 | /// A link to a name or a hash address. |
| 227 | Link, |
| 228 | /// A preserved run of source code. |
| 229 | Code, |
| 230 | /// A block quotation. |
| 231 | Quote, |
| 232 | } |
| 233 | |
| 234 | impl NodeKind { |
| 235 | |
| 236 | /// The wire code for this kind. |
| 237 | pub fn code(&self) -> u16 { |
| 238 | match self { |
| 239 | Self::Doc => 1, |
| 240 | Self::Section => 2, |
| 241 | Self::Para => 3, |
| 242 | Self::Heading => 4, |
| 243 | Self::List => 5, |
| 244 | Self::Item => 6, |
| 245 | Self::Boxx => 7, |
| 246 | Self::Image => 8, |
| 247 | Self::Text => 9, |
| 248 | Self::Emph => 10, |
| 249 | Self::Link => 11, |
| 250 | Self::Code => 12, |
| 251 | Self::Quote => 13, |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /// The kind for a wire code, or an error naming the code the document schema does not admit. |
| 256 | /// |
| 257 | /// A reserved code (§4.2) is not a document kind, so it is an error here like any other code |
| 258 | /// outside 1..=13. The error says which of the two it is, because the two are refused for |
| 259 | /// opposite reasons: an unknown code may be admitted by a fallback (§4.5), and a reserved code |
| 260 | /// never is. |
| 261 | pub fn from_code(code: u16) -> Outcome<Self> { |
| 262 | match code { |
| 263 | 1 => Ok(Self::Doc), |
| 264 | 2 => Ok(Self::Section), |
| 265 | 3 => Ok(Self::Para), |
| 266 | 4 => Ok(Self::Heading), |
| 267 | 5 => Ok(Self::List), |
| 268 | 6 => Ok(Self::Item), |
| 269 | 7 => Ok(Self::Boxx), |
| 270 | 8 => Ok(Self::Image), |
| 271 | 9 => Ok(Self::Text), |
| 272 | 10 => Ok(Self::Emph), |
| 273 | 11 => Ok(Self::Link), |
| 274 | 12 => Ok(Self::Code), |
| 275 | 13 => Ok(Self::Quote), |
| 276 | _ => match ReservedKind::from_code(code) { |
| 277 | Some(reserved) => Err(err!( |
| 278 | "Node kind code {} is the reserved kind '{}', which is legal in {} and never \ |
| 279 | in a document (SPEC.md §4.2).", code, reserved.label(), reserved.legal_in(); |
| 280 | Invalid, Input)), |
| 281 | None => Err(err!( |
| 282 | "Unknown node kind code {}, the v0 document vocabulary runs from 1 to 13, and \ |
| 283 | the codes 14 to 16 are reserved.", code; |
| 284 | Invalid, Input)), |
| 285 | }, |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | /// The label used in JDAT's text form, e.g. `(heading|{..})`. |
| 290 | pub fn label(&self) -> &'static str { |
| 291 | match self { |
| 292 | Self::Doc => "doc", |
| 293 | Self::Section => "section", |
| 294 | Self::Para => "para", |
| 295 | Self::Heading => "heading", |
| 296 | Self::List => "list", |
| 297 | Self::Item => "item", |
| 298 | Self::Boxx => "box", |
| 299 | Self::Image => "image", |
| 300 | Self::Text => "text", |
| 301 | Self::Emph => "emph", |
| 302 | Self::Link => "link", |
| 303 | Self::Code => "code", |
| 304 | Self::Quote => "quote", |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | /// The fields this kind's payload map must and may carry. |
| 309 | pub fn fields(&self) -> &'static [Field] { |
| 310 | match self { |
| 311 | Self::Doc => &[ |
| 312 | Field { name: "title", typ: FieldType::Str, opt: false }, |
| 313 | Field { name: "lang", typ: FieldType::Str, opt: false }, |
| 314 | ], |
| 315 | Self::Section => &[ |
| 316 | Field { name: "title", typ: FieldType::Str, opt: true }, |
| 317 | ], |
| 318 | Self::Para => &[], |
| 319 | Self::Heading => &[ |
| 320 | Field { name: "level", typ: FieldType::U8, opt: false }, |
| 321 | ], |
| 322 | Self::List => &[ |
| 323 | Field { name: "ordered", typ: FieldType::Bool, opt: false }, |
| 324 | ], |
| 325 | Self::Item => &[], |
| 326 | // A box declares no fields of its own. The universal `style` field is not listed here, |
| 327 | // nor on any other kind, because §4.4 gives it to every map payload alike, and both the |
| 328 | // canonical check and the validator handle it before they consult this table. |
| 329 | Self::Boxx => &[], |
| 330 | Self::Image => &[ |
| 331 | Field { name: "hash", typ: FieldType::Hash32, opt: false }, |
| 332 | Field { name: "alt", typ: FieldType::Str, opt: false }, |
| 333 | Field { name: "w", typ: FieldType::U32, opt: true }, |
| 334 | Field { name: "h", typ: FieldType::U32, opt: true }, |
| 335 | ], |
| 336 | Self::Text => &[], |
| 337 | Self::Emph => &[ |
| 338 | Field { name: "strong", typ: FieldType::Bool, opt: false }, |
| 339 | ], |
| 340 | Self::Link => &[ |
| 341 | Field { name: "to", typ: FieldType::Address, opt: false }, |
| 342 | ], |
| 343 | Self::Code => &[ |
| 344 | Field { name: "lang", typ: FieldType::Str, opt: true }, |
| 345 | Field { name: "text", typ: FieldType::Str, opt: false }, |
| 346 | ], |
| 347 | Self::Quote => &[ |
| 348 | Field { name: "cite", typ: FieldType::Str, opt: true }, |
| 349 | ], |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | /// What this kind may contain. |
| 354 | pub fn content(&self) -> Content { |
| 355 | match self { |
| 356 | Self::Doc => Content::Flow, |
| 357 | Self::Section => Content::Flow, |
| 358 | Self::Para => Content::Inline, |
| 359 | Self::Heading => Content::Inline, |
| 360 | Self::List => Content::Items, |
| 361 | Self::Item => Content::Flow, |
| 362 | Self::Boxx => Content::Flow, |
| 363 | Self::Image => Content::None, |
| 364 | Self::Text => Content::None, |
| 365 | Self::Emph => Content::Inline, |
| 366 | Self::Link => Content::Inline, |
| 367 | Self::Code => Content::None, |
| 368 | Self::Quote => Content::Flow, |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | /// Whether this kind is flow content, permitted where a section's children go. |
| 373 | pub fn is_flow(&self) -> bool { |
| 374 | matches!(self, |
| 375 | Self::Section | Self::Para | Self::Heading | Self::List | Self::Boxx | Self::Image |
| 376 | | Self::Code | Self::Quote) |
| 377 | } |
| 378 | |
| 379 | /// Whether this kind is inline content, permitted inside a paragraph. |
| 380 | pub fn is_inline(&self) -> bool { |
| 381 | matches!(self, Self::Text | Self::Emph | Self::Link) |
| 382 | } |
| 383 | |
| 384 | /// Whether a child of the given kind may appear inside this one. |
| 385 | pub fn allows(&self, child: &Self) -> bool { |
| 386 | match self.content() { |
| 387 | Content::None => false, |
| 388 | Content::Flow => child.is_flow(), |
| 389 | Content::Inline => child.is_inline(), |
| 390 | Content::Items => matches!(child, Self::Item), |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | /// Whether this kind's payload is a bare string rather than a map. Only `text` is. |
| 395 | pub fn payload_is_str(&self) -> bool { |
| 396 | matches!(self, Self::Text) |
| 397 | } |
| 398 | |
| 399 | /// Whether this kind must carry at least one child, the `+` of the SPEC §4.2 vocabulary. |
| 400 | /// |
| 401 | /// A list with no items is a construction error rather than intent, so the format refuses it. A |
| 402 | /// document or section may be empty, since a stub with only a title is a legitimate thing to |
| 403 | /// publish. |
| 404 | pub fn requires_child(&self) -> bool { |
| 405 | matches!(self, Self::List) |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /// A node kind the format reserves, and which the document schema admits nowhere (`SPEC.md` §4.2). |
| 410 | /// |
| 411 | /// These are the engine's own facilities: an editable text field, a pane an application paints, and |
| 412 | /// the engine's own icons. The chrome's address bar and an application's form field reach the same |
| 413 | /// `edit`, and a document may name none of the three, which is what makes "a document is never a |
| 414 | /// program" structural rather than conventional. Which trees may name them is [`Schema::admits`], and |
| 415 | /// nothing else. |
| 416 | /// |
| 417 | /// An `icon` is reserved for a reason of its own, and it is not that an icon is dangerous. A document |
| 418 | /// carries its pictures as an `image`, which is a content hash: the picture is the author's, it is |
| 419 | /// held, and it is the same picture wherever it is read. An icon is the opposite -- a name the tree |
| 420 | /// reaches the *reader's* drawing by. A document naming one would be letting whichever reader opened |
| 421 | /// it supply its content, and two readers would show two documents. Style may be the reader's, because |
| 422 | /// style is how a thing looks; content may not, because content is what the author said. |
| 423 | /// |
| 424 | /// The codes are known here rather than left outside the vocabulary on purpose. A code this version |
| 425 | /// has never heard of may be admitted by a fallback (§4.5), because the reader cannot know what it |
| 426 | /// means and a fallback lets it render something faithful anyway. A reserved code is the opposite |
| 427 | /// case: the reader knows exactly what it has been handed, and knows the schema it was handed under |
| 428 | /// does not admit it, so it refuses it whether or not a fallback is offered. Knowing the code is what |
| 429 | /// makes that refusal possible, and what stops a document smuggling a `surface` past a reader that |
| 430 | /// has not yet learnt what code 15 means. |
| 431 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 432 | pub enum ReservedKind { |
| 433 | /// An editable text field, code 14. |
| 434 | Edit, |
| 435 | /// A pane an application paints, code 15. |
| 436 | Surface, |
| 437 | /// One of the engine's own icons, code 16. |
| 438 | Icon, |
| 439 | } |
| 440 | |
| 441 | impl ReservedKind { |
| 442 | |
| 443 | /// The wire code this kind is reserved at. |
| 444 | pub fn code(&self) -> u16 { |
| 445 | match self { |
| 446 | Self::Edit => 14, |
| 447 | Self::Surface => 15, |
| 448 | Self::Icon => 16, |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | /// The reserved kind a wire code names, or `None` if the code reserves nothing. |
| 453 | pub fn from_code(code: u16) -> Option<Self> { |
| 454 | match code { |
| 455 | 14 => Some(Self::Edit), |
| 456 | 15 => Some(Self::Surface), |
| 457 | 16 => Some(Self::Icon), |
| 458 | _ => None, |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | /// The label an error message names this kind by. |
| 463 | pub fn label(&self) -> &'static str { |
| 464 | match self { |
| 465 | Self::Edit => "edit", |
| 466 | Self::Surface => "surface", |
| 467 | Self::Icon => "icon", |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | /// The label with its indefinite article, so that a refusal reads as English. |
| 472 | pub fn with_article(&self) -> &'static str { |
| 473 | match self { |
| 474 | Self::Edit => "an edit", |
| 475 | Self::Surface => "a surface", |
| 476 | Self::Icon => "an icon", |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /// The trees this kind is legal in, which never include a document. |
| 481 | pub fn legal_in(&self) -> &'static str { |
| 482 | match self { |
| 483 | Self::Edit => "a chrome or an application tree", |
| 484 | Self::Surface => "an application tree", |
| 485 | Self::Icon => "a chrome or an application tree", |
| 486 | } |
| 487 | } |
| 488 | |
| 489 | /// The fields this kind's payload map must and may carry. |
| 490 | /// |
| 491 | /// An `edit` carries the name the shell keeps its state under, and the hint it shows while it is |
| 492 | /// empty. The name is required, since a field the shell cannot key state by can never hold any. |
| 493 | /// |
| 494 | /// A `surface` carries the content hash of the application module it is a pane for, and the |
| 495 | /// semantic alternative that stands in for that application. It carries **no geometry at all**: no |
| 496 | /// width, no height, no minimum, no aspect. A surface is laid out like any box, by the host, in |
| 497 | /// the flow of the tree, and an application learns the size it was given rather than stating the |
| 498 | /// size it wants. An application that could size itself could size itself over the trust band, and |
| 499 | /// the band's whole guarantee would be gone; a field the format does not have cannot be asked for. |
| 500 | /// |
| 501 | /// The universal `style` field (§4.4) is legal on both, as it is on every map payload, and is not |
| 502 | /// listed here for the same reason it is not listed on a document kind. |
| 503 | pub fn fields(&self) -> &'static [Field] { |
| 504 | match self { |
| 505 | Self::Edit => &[ |
| 506 | Field { name: "name", typ: FieldType::Str, opt: false }, |
| 507 | Field { name: "placeholder", typ: FieldType::Str, opt: true }, |
| 508 | ], |
| 509 | Self::Surface => &[ |
| 510 | Field { name: "app", typ: FieldType::Hash32, opt: false }, |
| 511 | Field { name: KEY_ALT, typ: FieldType::Nodes, opt: false }, |
| 512 | ], |
| 513 | Self::Icon => &[ |
| 514 | Field { name: "name", typ: FieldType::Str, opt: false }, |
| 515 | ], |
| 516 | } |
| 517 | } |
| 518 | |
| 519 | /// What this kind may contain, which is nothing: every reserved kind is a leaf. |
| 520 | pub fn content(&self) -> Content { |
| 521 | Content::None |
| 522 | } |
| 523 | |
| 524 | /// The content class a parent must admit to hold this kind, which is where it may sit. |
| 525 | /// |
| 526 | /// This is not [`Self::content`] read backwards. That says what a kind may hold; this says what |
| 527 | /// may hold it, and the two are independent -- every reserved kind is a leaf, and they do not all |
| 528 | /// sit in the same place. |
| 529 | /// |
| 530 | /// An `edit` and a `surface` are blocks: a field is a line of its own and a pane is a rectangle, |
| 531 | /// and each sits exactly where a `box` may. An `icon` is a glyph, and sits exactly where a `text` |
| 532 | /// run may -- in the line, on the baseline, at the size and in the colour of whatever it stands |
| 533 | /// beside. A bar's button is an icon and a word next to each other, and an icon that were flow |
| 534 | /// content could only ever be a block above the word. |
| 535 | pub fn sits_in(&self) -> Content { |
| 536 | match self { |
| 537 | Self::Edit => Content::Flow, |
| 538 | Self::Surface => Content::Flow, |
| 539 | Self::Icon => Content::Inline, |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | /// Why this kind carries no children, as a refusal spells it. |
| 544 | pub fn no_children(&self) -> &'static str { |
| 545 | match self { |
| 546 | Self::Edit => "an editable field is one line of text with a caret in it, and not a \ |
| 547 | container", |
| 548 | Self::Surface => "a surface's content is the application's, and its 'alt' is what stands \ |
| 549 | in for the application when it is not running. A surface is a leaf that happens to \ |
| 550 | be alive", |
| 551 | Self::Icon => "an icon is a glyph the engine draws, and a glyph holds nothing", |
| 552 | } |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | /// The icons the engine draws, which is the whole of what an `icon` node may name (§4.2). |
| 557 | /// |
| 558 | /// The set is closed, and a name outside it is refused rather than drawn as a gap. An icon is not |
| 559 | /// content the tree supplies but a name the tree *reaches* the engine's own drawing by, so a name the |
| 560 | /// engine has never heard of is a caller's mistake, not a payload's, and there is nothing faithful to |
| 561 | /// render in its place. That is the same reason a reserved code is refused where an unknown code may |
| 562 | /// be admitted by a fallback (§4.5): a fallback exists for what the reader cannot know, and the reader |
| 563 | /// knows exactly which icons it has. |
| 564 | /// |
| 565 | /// The names are browser actions, which is the second reason a document cannot name one: `back` means |
| 566 | /// nothing in a document, and a document that could say it would be reaching for a facility it does |
| 567 | /// not have. |
| 568 | /// |
| 569 | /// Growing this set is a versioned decision, as growing the kinds is. A chrome naming an icon this |
| 570 | /// build does not draw is a bug in the chrome, and the validator is where it should be caught. |
| 571 | pub const ICON_NAMES: &[&str] = &[ |
| 572 | "back", // Back to the previously held document. |
| 573 | "forward", // Forward again, after going back. |
| 574 | "home", // To the library, which is the home. |
| 575 | "add", // Open another tab. |
| 576 | "close", // Close this tab. |
| 577 | "find", // Search what is held: by name, by hash, by author. |
| 578 | "reload", // Reload the current document. |
| 579 | "menu", // Open the browser's menu. |
| 580 | "more", // Open the overflow of a control that holds more than it shows. |
| 581 | "page", // A document standing in for a tab whose favicon is not yet known. |
| 582 | "panel", // Toggle the side panel rail. |
| 583 | "bookmark", // Mark the held document, or open the marks. |
| 584 | "download", // Save the held document, or open what has been saved. |
| 585 | "history", // The documents held before this one, in time. |
| 586 | "note", // Annotate the held document. |
| 587 | "tile", // Split the view into tiled panes. |
| 588 | "grip", // The drag handle of a dockable bar: a two-by-three of dots. |
| 589 | "minimise", // Send the window to the taskbar. |
| 590 | "maximise", // Fill the screen with the window. |
| 591 | "restore", // Return a maximised window to its former size. |
| 592 | ]; |
| 593 | |
| 594 | /// Whether the engine draws an icon of this name. |
| 595 | pub fn known_icon(name: &str) -> bool { |
| 596 | ICON_NAMES.contains(&name) |
| 597 | } |
| 598 | |
| 599 | /// The icon set an error message spells, so a refusal names what it would have taken. |
| 600 | pub fn icon_names_label() -> String { |
| 601 | ICON_NAMES.join("', '") |
| 602 | } |
| 603 | |
| 604 | /// The map key under which a node's children are carried. |
| 605 | pub const KEY_CHILDREN: &'static str = "children"; |
| 606 | /// The map key under which a node names a document style (§4.4). Permitted on any map payload. |
| 607 | pub const KEY_STYLE: &'static str = "style"; |
| 608 | /// The `doc` node's map key for the document style table (§4.4). |
| 609 | pub const KEY_STYLES: &'static str = "styles"; |
| 610 | /// The map key under which an unknown kind carries its fallback (§4.5). |
| 611 | pub const KEY_FALLBACK: &'static str = "fallback"; |
| 612 | /// The `surface` node's map key for its semantic alternative (§4.2). |
| 613 | /// |
| 614 | /// The alternative is inert content, always: it is what a screen reader reads, what a search indexes, |
| 615 | /// and what the reader sees when the application is not running, was granted no drawing capability, |
| 616 | /// or was stopped for misbehaving. It is not the same key as an `image`'s `alt`, which is a string; |
| 617 | /// this one is a non-empty list of nodes. |
| 618 | pub const KEY_ALT: &'static str = "alt"; |
| 619 | /// The `link` node's map key for its typed address (§4.3). |
| 620 | pub const KEY_TO: &'static str = "to"; |
| 621 | /// The address-map key selecting a NAMES name. |
| 622 | pub const ADDR_NAME: &'static str = "name"; |
| 623 | /// The address-map key selecting a content hash. |
| 624 | pub const ADDR_HASH: &'static str = "hash"; |
| 625 | |
| 626 | /// Returns the children of a node payload, or an empty slice if it declares none. |
| 627 | pub fn children_of(payload: &Dat) -> Outcome<Vec<Dat>> { |
| 628 | match payload { |
| 629 | Dat::Map(map) => { |
| 630 | match map.get(&dat!(KEY_CHILDREN)) { |
| 631 | None => Ok(Vec::new()), |
| 632 | Some(Dat::List(v)) => Ok(v.clone()), |
| 633 | Some(d) => Err(err!( |
| 634 | "Node children must be a list, found {:?}.", d.kind(); |
| 635 | Invalid, Input)), |
| 636 | } |
| 637 | }, |
| 638 | _ => Ok(Vec::new()), |
| 639 | } |
| 640 | } |
| 641 | |
| 642 | // ┌───────────────────────────────────────────────────────────────────────────┐ |
| 643 | // │ STYLING (§4.4) │ |
| 644 | // └───────────────────────────────────────────────────────────────────────────┘ |
| 645 | |
| 646 | /// The semantic palette names a colour property may carry. The reader's theme resolves them. |
| 647 | /// |
| 648 | /// `line` is the rule between one region and the next: a colour quieter than the muted ink, for a surface |
| 649 | /// that draws its own edge. An outline in a text colour reads as a box drawn AROUND something rather than |
| 650 | /// as the edge OF it, so a border wants a name of its own. |
| 651 | pub const PALETTE: [&'static str; 5] = ["ink", "muted", "accent", "bg", "line"]; |
| 652 | /// The text-direction values. |
| 653 | pub const DIRECTIONS: [&'static str; 2] = ["ltr", "rtl"]; |
| 654 | /// The alignment values. |
| 655 | pub const ALIGNMENTS: [&'static str; 4] = ["start", "center", "end", "justify"]; |
| 656 | |
| 657 | /// How a style-record property's value is checked. |
| 658 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 659 | pub enum StyleCheck { |
| 660 | /// A palette name (`str`), one of [`PALETTE`]. |
| 661 | Palette, |
| 662 | /// A type scale step (`i8`); 0 is the reader's base size. |
| 663 | ScaleStep, |
| 664 | /// A language tag (`str`), BCP-47. |
| 665 | Lang, |
| 666 | /// A direction (`str`), one of [`DIRECTIONS`]. |
| 667 | Direction, |
| 668 | /// A spacing scale index (`u8`). |
| 669 | Spacing, |
| 670 | /// An alignment (`str`), one of [`ALIGNMENTS`]. |
| 671 | Alignment, |
| 672 | /// A grid's minimum tile width, as a PERCENTAGE of a base size (`u16`), so a measure that does not |
| 673 | /// fall on a whole em can still be named. |
| 674 | Tile, |
| 675 | /// A share of the room a packed row has left over (`u8`); 0 takes none of it. |
| 676 | Share, |
| 677 | /// A border: a palette name and a width in pixels, as a two-element list. See [`check_border`]. |
| 678 | Border, |
| 679 | /// How far a node is lifted off the surface behind it (`u8`); 0 is lying flat on it. |
| 680 | Elevation, |
| 681 | } |
| 682 | |
| 683 | /// Which schemas a style property belongs to (§4.4). |
| 684 | /// |
| 685 | /// It is the property's own scope rather than a list kept on the schema, so a property is written |
| 686 | /// down once, beside the check its value is held to, and cannot be admitted somewhere its author |
| 687 | /// never wrote. |
| 688 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 689 | pub enum StyleScope { |
| 690 | /// The v0 document vocabulary, which every schema admits and which is frozen. |
| 691 | Doc, |
| 692 | /// The interface vocabulary: admitted by a chrome and an application tree, and by no document. |
| 693 | Interface, |
| 694 | } |
| 695 | |
| 696 | impl StyleScope { |
| 697 | |
| 698 | /// The trees a property of this scope is legal in, as an error message names them. |
| 699 | pub fn legal_in(&self) -> &'static str { |
| 700 | match self { |
| 701 | Self::Doc => "any tree", |
| 702 | Self::Interface => "a chrome or an application tree", |
| 703 | } |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | /// One property of a style record. |
| 708 | #[derive(Clone, Copy, Debug)] |
| 709 | pub struct StyleField { |
| 710 | /// The map key. |
| 711 | pub name: &'static str, |
| 712 | /// How the value is checked. |
| 713 | pub check: StyleCheck, |
| 714 | /// Whether the property flows down to descendants until overridden. |
| 715 | pub inherited: bool, |
| 716 | /// The schemas that admit the property. See [`Schema::admits_style`]. |
| 717 | pub scope: StyleScope, |
| 718 | } |
| 719 | |
| 720 | /// The document style vocabulary (§4.4): the eight properties `oxeweb/doc/0` admits, and no others. |
| 721 | /// |
| 722 | /// **This list is frozen.** A published document is signed under the vocabulary of its schema, and |
| 723 | /// its address is the hash of its bytes, so the set of properties it could ever have named is settled |
| 724 | /// the day it is published. Growing this list would change what an existing document is allowed to |
| 725 | /// mean; growth belongs in a schema version, or in [`INTERFACE_STYLE_FIELDS`], which no document may |
| 726 | /// name. |
| 727 | /// |
| 728 | /// # It grew once, before v0 was published, and this is what was asked |
| 729 | /// |
| 730 | /// `radius` was an interface property and is now a document one. The question put was whether a corner |
| 731 | /// radius is a property a DOCUMENT legitimately has, and the answer turns on three things. |
| 732 | /// |
| 733 | /// It decorates a surface the document already draws. A style may name `bg` and `pad`, so a tinted, |
| 734 | /// padded box is already a thing a document makes; `radius` says what shape that box's corners are. It |
| 735 | /// adds no element, no geometry and no authority. |
| 736 | /// |
| 737 | /// It has a meaning in prose. That is what tells it from the six it left behind -- `grid`, `pack`, |
| 738 | /// `grow`, `max`, `nowrap`, `ends` are the layout of a BAR and a SHELF and mean nothing in a document, |
| 739 | /// while a soft-cornered callout, pull-quote or listing is ordinary typography and has been for forty |
| 740 | /// years. A property whose absence every document would have to work around forever, for a rule that |
| 741 | /// was not protecting anything, is a property in the wrong list. |
| 742 | /// |
| 743 | /// And the rule it seemed to break is not the rule that does the work. "A document must not dress as an |
| 744 | /// interface" is real, but what enforces it is ADDRESSING: the band and the bars are painted into |
| 745 | /// pixmaps a document is never handed, so it cannot draw there whatever it names. A document with `bg`, |
| 746 | /// `pad`, `fill` and `align` can already draw a filled box with a word in it -- a corner is a rounding, |
| 747 | /// not the difference between plausible and implausible -- and a document that drew a perfect forgery |
| 748 | /// of a control could still make nothing happen by it, because a document has no script and a link goes |
| 749 | /// to an address or nowhere. §4.4 says as much in its own words: a rounded corner is not a capability. |
| 750 | /// |
| 751 | /// **`border` and `shadow` stayed where they are, and the distinction is the point.** A shadow is |
| 752 | /// ELEVATION -- the language of a thing that lifts toward the hand -- and a paragraph claiming to float |
| 753 | /// above its page is a document dressing as an interface in exactly the way the rule means. A border is |
| 754 | /// the line round a control with an edge, which is the spec's own phrase for what a chrome draws. It was |
| 755 | /// not asked about and it is not moved. One property moved because one property was argued. |
| 756 | pub const STYLE_FIELDS: [StyleField; 8] = [ |
| 757 | StyleField { name: "fill", check: StyleCheck::Palette, inherited: true, scope: StyleScope::Doc }, |
| 758 | StyleField { name: "size", check: StyleCheck::ScaleStep, inherited: true, scope: StyleScope::Doc }, |
| 759 | StyleField { name: "lang", check: StyleCheck::Lang, inherited: true, scope: StyleScope::Doc }, |
| 760 | StyleField { name: "dir", check: StyleCheck::Direction, inherited: true, scope: StyleScope::Doc }, |
| 761 | StyleField { name: "bg", check: StyleCheck::Palette, inherited: false, scope: StyleScope::Doc }, |
| 762 | StyleField { name: "pad", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Doc }, |
| 763 | StyleField { name: "align", check: StyleCheck::Alignment, inherited: false, scope: StyleScope::Doc }, |
| 764 | StyleField { name: "radius", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Doc }, |
| 765 | ]; |
| 766 | |
| 767 | /// The interface style vocabulary (§4.4): what a chrome or an application tree may name, and a |
| 768 | /// document may not. |
| 769 | /// |
| 770 | /// The browser's own chrome is an SBJ tree laid out by the same engine as a document, and it is a |
| 771 | /// real interface: a library of tiles, a navigation bar, a control with an edge. These are what it |
| 772 | /// takes to draw one, and they are here rather than in [`STYLE_FIELDS`] because a document is not an |
| 773 | /// interface and must not be able to dress as one. |
| 774 | /// |
| 775 | /// Every one of them is self-only. A property that cannot flow down cannot reach a node that did not |
| 776 | /// name it, so admitting three more of them widens what a chrome may say about itself and nothing |
| 777 | /// else. |
| 778 | pub const INTERFACE_STYLE_FIELDS: [StyleField; 8] = [ |
| 779 | StyleField { name: "grid", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface }, |
| 780 | StyleField { name: "pack", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface }, |
| 781 | StyleField { name: "grow", check: StyleCheck::Share, inherited: false, scope: StyleScope::Interface }, |
| 782 | StyleField { name: "max", check: StyleCheck::Tile, inherited: false, scope: StyleScope::Interface }, |
| 783 | StyleField { name: "border", check: StyleCheck::Border, inherited: false, scope: StyleScope::Interface }, |
| 784 | StyleField { name: "shadow", check: StyleCheck::Elevation, inherited: false, scope: StyleScope::Interface }, |
| 785 | StyleField { name: "nowrap", check: StyleCheck::Share, inherited: false, scope: StyleScope::Interface }, |
| 786 | StyleField { name: "ends", check: StyleCheck::Spacing, inherited: false, scope: StyleScope::Interface }, |
| 787 | ]; |
| 788 | |
| 789 | /// Returns the style-record property of the given name in ANY schema's vocabulary, if it is one. |
| 790 | /// |
| 791 | /// This answers what a property IS -- its width, its check, its scope -- and never whether a tree may |
| 792 | /// name it. It exists for the canonical encoding check, which has no schema and needs none: a |
| 793 | /// property's wire type does not depend on who may name it, so `grid` is a `u8` in every tree, and |
| 794 | /// pinning that width is the same work in all three schemas. |
| 795 | /// |
| 796 | /// **A validator must not call this.** Admission is [`Schema::style_field`], which asks this and then |
| 797 | /// applies the schema's rule; calling this instead would admit `grid` in a document. |
| 798 | pub fn known_style_field(name: &str) -> Option<&'static StyleField> { |
| 799 | STYLE_FIELDS.iter() |
| 800 | .chain(INTERFACE_STYLE_FIELDS.iter()) |
| 801 | .find(|f| f.name == name) |
| 802 | } |
| 803 | |
| 804 | /// A decoded, validated border: the line's palette name, and how thick it is. |
| 805 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 806 | pub struct Border<'a> { |
| 807 | /// The palette name the line is drawn in. The reader's theme resolves it, as it does any other. |
| 808 | pub colour: &'a str, |
| 809 | /// The width of the line, in pixels. |
| 810 | pub width: u8, |
| 811 | } |
| 812 | |
| 813 | /// Checks a style's `border` value: a two-element list of a palette name and a width in pixels (§4.4). |
| 814 | /// |
| 815 | /// A border is one property and not two: a colour with no width draws nothing, and a width with no |
| 816 | /// colour draws nothing, so the two are named together or not at all, and a half-written border |
| 817 | /// cannot be spelt. |
| 818 | /// |
| 819 | /// The width is in pixels rather than on the spacing scale that `pad` and `radius` ride, because a |
| 820 | /// border is a boundary and not a measure of room: a hairline is one pixel at any text size. |
| 821 | pub fn check_border(d: &Dat) -> Outcome<Border<'_>> { |
| 822 | let list = match d { |
| 823 | Dat::List(list) => list, |
| 824 | other => return Err(err!( |
| 825 | "A border is a palette name and a width in pixels, written as a two-element list, \ |
| 826 | e.g. [\"muted\", (u8|1)]; found a {:?}.", other.kind(); |
| 827 | Invalid, Input)), |
| 828 | }; |
| 829 | let (colour, width) = match list.as_slice() { |
| 830 | [Dat::Str(colour), Dat::U8(width)] => (colour.as_str(), *width), |
| 831 | _ => return Err(err!( |
| 832 | "A border is a palette name and a width in pixels, in that order, written as a \ |
| 833 | two-element list, e.g. [\"muted\", (u8|1)]; found a list of {} element(s).", list.len(); |
| 834 | Invalid, Input)), |
| 835 | }; |
| 836 | if !PALETTE.contains(&colour) { |
| 837 | return Err(err!( |
| 838 | "A border is drawn in a palette colour ({}), but one names '{}'.", |
| 839 | PALETTE.join(", "), colour; |
| 840 | Invalid, Input)); |
| 841 | } |
| 842 | Ok(Border { |
| 843 | colour, |
| 844 | width, |
| 845 | }) |
| 846 | } |
| 847 | |
| 848 | // ┌───────────────────────────────────────────────────────────────────────────┐ |
| 849 | // │ LINK ADDRESSES (§4.3) │ |
| 850 | // └───────────────────────────────────────────────────────────────────────────┘ |
| 851 | |
| 852 | /// A decoded, validated link address. |
| 853 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 854 | pub enum Address { |
| 855 | /// A NAMES name. |
| 856 | Name(String), |
| 857 | /// A 32-byte content hash. |
| 858 | Hash(Vec<u8>), |
| 859 | } |
| 860 | |
| 861 | /// Checks a `link`'s `to` value: a map with exactly one entry, `name` (str) or `hash` (b32). |
| 862 | /// |
| 863 | /// Returning the typed address means the decoder tells a name from a hash, and a malformed target |
| 864 | /// is refused here rather than misread by the renderer. |
| 865 | pub fn check_address(d: &Dat) -> Outcome<Address> { |
| 866 | let map = match d { |
| 867 | Dat::Map(map) => map, |
| 868 | other => return Err(err!( |
| 869 | "A link address is a single-entry map, found a {:?}.", other.kind(); |
| 870 | Invalid, Input)), |
| 871 | }; |
| 872 | if map.len() != 1 { |
| 873 | return Err(err!( |
| 874 | "A link address is a single-entry map naming a name or a hash, found {} entries.", |
| 875 | map.len(); |
| 876 | Invalid, Input)); |
| 877 | } |
| 878 | match map.get(&dat!(ADDR_NAME)) { |
| 879 | Some(Dat::Str(s)) => return Ok(Address::Name(s.clone())), |
| 880 | Some(other) => return Err(err!( |
| 881 | "A link address \"name\" is a str, found a {:?}.", other.kind(); |
| 882 | Invalid, Input)), |
| 883 | None => (), |
| 884 | } |
| 885 | match map.get(&dat!(ADDR_HASH)) { |
| 886 | Some(Dat::B32(b)) => Ok(Address::Hash(b.to_vec())), |
| 887 | Some(other) => Err(err!( |
| 888 | "A link address \"hash\" is a b32, found a {:?}.", other.kind(); |
| 889 | Invalid, Input)), |
| 890 | None => Err(err!( |
| 891 | "A link address names \"name\" or \"hash\", found neither."; Invalid, Input)), |
| 892 | } |
| 893 | } |