# Working with schemas - [Preserves schema specification](https://preserves.gitlab.io/preserves/preserves-schema.html) - [Index of Preserves schema tools](https://preserves.gitlab.io/preserves/doc/schema-tools.html) [preserves-schemac]: https://preserves.gitlab.io/preserves/doc/preserves-schemac.html ## Schema source code: *.prs files Preserves schemas are written in a syntax that (ab)uses Preserves text syntax as a kind of S-expression. Schema source code looks like this: ```preserves-schema version 1 . Present = . Says = . UserStatus = . Status = =here / . TimeStamp = string . ``` Conventionally, schema source code is stored in `*.prs` files. In this example, the source code above is placed in `simpleChatProtocol.prs`. ## Compiling source code to metaschema instances: *.prb files Many of the code generator tools for Preserves schemas require not source code, but instances of the [Preserves metaschema](https://preserves.gitlab.io/preserves/preserves-schema.html#appendix-metaschema). To compile schema source code to metaschema instances, use [preserves-schemac][]: ```shell yarn global add @preserves/schema preserves-schemac .:simpleChatProtocol.prs > simpleChatProtocol.prb ``` Binary-syntax metaschema instances are conventionally stored in `*.prb` files. If you have a whole directory tree of `*.prs` files, you can supply just "`.`" without the "`:`"-prefixed fileglob part. See the [preserves-schemac documentation][preserves-schemac]. Converting the `simpleChatProtocol.prb` file to Preserves text syntax lets us read the metaschema instance corresponding to the source code: ```shell cat simpleChatProtocol.prb | preserves-tool convert ``` The result: ```preserves > ]>> Says: > > ]>> Status: ] [ "away" > ]>> ] ]> TimeStamp: UserStatus: > > ]>> } embeddedType: #f version: 1 }> }> ``` ## Generating support code from metaschema instances Support exists for working with schemas in many languages, including Python, Rust, TypeScript, Racket, and Squeak Smalltalk. ### Python Python doesn't have a separate compilation step: it loads binary metaschema instances at runtime, generating classes on the fly. After `pip install preserves`, load metaschemas with `preserves.schema.load_schema_file`: ```python from preserves import stringify, schema, parse S = schema.load_schema_file('./simpleChatProtocol.prb') P = S.simpleChatProtocol ``` Then, members of `P` are the definitions from `simpleChatProtocol.prs`: ```python >>> P.Present('me') Present {'username': 'me'} >>> stringify(P.Present('me')) '' >>> P.Present.decode(parse('')) Present {'username': 'me'} >>> P.Present.try_decode(parse('')) Present {'username': 'me'} >>> P.Present.try_decode(parse('')) is None True >>> stringify(P.UserStatus('me', P.Status.here())) '' >>> stringify(P.UserStatus('me', P.Status.away('2022-03-08'))) '>' >>> x = P.UserStatus.decode(parse('>')) >>> x.status.VARIANT #away >>> x.status.VARIANT == Symbol('away') True ``` ### Rust Generate Rust definitions corresponding to a metaschema instance with [preserves-schema-rs][]. The best way to use it is to integrate it into your `build.rs` (see [the docs][preserves-schema-rs]), but you can also use it as a standalone command-line tool. [preserves-schema-rs]: https://preserves.gitlab.io/preserves/doc/preserves-schema-rs.html The following command generates a directory `./rs/chat` containing rust sources for a module that expects to be called `chat` in Rust code: ```shell preserves-schema-rs --output-dir rs/chat --prefix chat simpleChatProtocol.prb ``` Representative excerpts from one of the generated files, `./rs/chat/simple_chat_protocol.rs`: ```rust,noplayground pub struct Present { pub username: std::string::String } pub struct Says { pub who: std::string::String, pub what: std::string::String } pub struct UserStatus { pub username: std::string::String, pub status: Status } pub enum Status { Here, Away { since: std::boxed::Box } } pub struct TimeStamp(pub std::string::String); ``` ### TypeScript Generate TypeScript definitions from schema **sources** (not metaschema instances) using [preserves-schema-ts][]. Unlike other code generators, this one understands schema source code directly. [preserves-schema-ts]: https://preserves.gitlab.io/preserves/doc/preserves-schema-ts.html The following command generates a directory `./ts/gen` containing TypeScript sources: ```shell preserves-schema-ts --output ./ts/gen .:simpleChatProtocol.prs ``` Representative excerpts from one of the generated files, `./ts/gen/simpleChatProtocol.ts`: ```typescript export type Present = {"username": string}; export type Says = {"who": string, "what": string}; export type UserStatus = {"username": string, "status": Status}; export type Status = ({"_variant": "here"} | {"_variant": "away", "since": TimeStamp}); export type TimeStamp = string; ``` ### Squeak Smalltalk After loading the `Preserves` package from the [Preserves project SqueakSource page](https://squeaksource.com/Preserves.html), perhaps via ```smalltalk Installer squeaksource project: 'Preserves'; install: 'Preserves'. ``` you can load and compile the bundle using something like ```smalltalk (PreservesSchemaEnvironment fromBundleFile: 'simpleChatProtocol.prb') category: 'Example-Preserves-Schema-SimpleChat'; prefix: 'SimpleChat'; cleanCategoryOnCompile: true; compileBundle. ``` which results in classes whose names are prefixed with `SimpleChat` being created in package `Example-Preserves-Schema-SimpleChat`. Here's a screenshot of a browser showing the generated classes: ![Screenshot of Squeak Browser on class SimpleChatSimpleChatProtocol](<../figures/System Browser: SimpleChatSimpleChatProtocol.png>) Exploring the result of evaluating the following expression, which generates a Smalltalk object in the specified schema, yields the following screenshot: ```smalltalk SimpleChatSimpleChatProtocolStatus away since: (SimpleChatSimpleChatProtocolTimeStamp new value: '2022-03-08') ``` ![Exploration of a SimpleChatSimpleChatProtocolStatus object](<../figures/a SimpleChatSimpleChatProtocolStatus_away.png>) Exploring the result of evaluating the following expression, which generates a Smalltalk object representing the Preserves value corresponding to the value produced in the previous expression, yields the following screenshot: ```smalltalk (SimpleChatSimpleChatProtocolStatus away since: (SimpleChatSimpleChatProtocolTimeStamp new value: '2022-03-08')) asPreserves ``` ![Exploration of a SimpleChatSimpleChatProtocolStatus preserves value object](<../figures/away-2022-03-08.png>) Finally, the following expression parses a valid `Status` string input: ```smalltalk SimpleChatSimpleChatProtocolStatus from: '' parsePreserves orTry: [] ``` If it had been invalid, the answer would have been `nil` (because `[] value` is `nil`).