working-with-schemas.md

This commit is contained in:
Tony Garnock-Jones 2022-03-08 14:53:53 +01:00
parent 174fd891ef
commit e2974fef7d
15 changed files with 980 additions and 1 deletions

View File

@ -77,7 +77,7 @@
- [Overview](./guide/index.md)
- [Preserves](./guide/preserves.md)
- [Working with schemas]()
- [Working with schemas](./guide/working-with-schemas.md)
- [Language-neutral protocols]()
- [Rust]()
- [Python]()

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

4
src/guide/schemas/.envrc Normal file
View File

@ -0,0 +1,4 @@
[ -d .venv ] || python -m venv .venv
. .venv/bin/activate
pip install -U preserves
PATH=$PATH:~/src/preserves/implementations/rust/target/debug

1
src/guide/schemas/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.venv

View File

@ -0,0 +1,15 @@
all: simpleChatProtocol.prb rs ts
simpleChatProtocol.prb: simpleChatProtocol.prs
preserves-schemac .:$< > $@
clean:
rm -f simpleChatProtocol.prb
rm -rf rs
rm -rf ts
rs: simpleChatProtocol.prb
preserves-schema-rs --output-dir rs/chat --prefix chat simpleChatProtocol.prb
ts: simpleChatProtocol.prs
preserves-schema-ts --output ./ts/gen .:simpleChatProtocol.prs

View File

@ -0,0 +1,38 @@
from preserves import stringify, schema, parse, Symbol
S = schema.load_schema_file('./simpleChatProtocol.prb')
P = S.simpleChatProtocol
def hook_for_doctests():
'''
>>> P.Present('me')
Present {'username': 'me'}
>>> stringify(P.Present('me'))
'<Present "me">'
>>> P.Present.decode(parse('<Present "me">'))
Present {'username': 'me'}
>>> P.Present.try_decode(parse('<Present "me">'))
Present {'username': 'me'}
>>> P.Present.try_decode(parse('<NotPresent "me">')) is None
True
>>> stringify(P.UserStatus('me', P.Status.here()))
'<Status "me" here>'
>>> stringify(P.UserStatus('me', P.Status.away('2022-03-08')))
'<Status "me" <away "2022-03-08">>'
>>> x = P.UserStatus.decode(parse('<Status "me" <away "2022-03-08">>'))
>>> x.status.VARIANT
#away
>>> x.status.VARIANT == Symbol('away')
True
'''
pass
if __name__ == '__main__':
import doctest
doctest.testmod()

View File

@ -0,0 +1,25 @@
pub mod simple_chat_protocol;
use preserves_schema::support as _support;
use _support::preserves;
#[allow(non_snake_case)]
pub struct Language<N: preserves::value::NestedValue> {
pub LIT_0_PRESENT: N /* Present */,
pub LIT_1_SAYS: N /* Says */,
pub LIT_4_STATUS: N /* Status */,
pub LIT_3_AWAY: N /* away */,
pub LIT_2_HERE: N /* here */
}
impl<N: preserves::value::NestedValue> Default for Language<N> {
fn default() -> Self {
Language {
LIT_0_PRESENT: /* Present */ _support::decode_lit(&vec![179, 7, 80, 114, 101, 115, 101, 110, 116]).unwrap(),
LIT_1_SAYS: /* Says */ _support::decode_lit(&vec![179, 4, 83, 97, 121, 115]).unwrap(),
LIT_4_STATUS: /* Status */ _support::decode_lit(&vec![179, 6, 83, 116, 97, 116, 117, 115]).unwrap(),
LIT_3_AWAY: /* away */ _support::decode_lit(&vec![179, 4, 97, 119, 97, 121]).unwrap(),
LIT_2_HERE: /* here */ _support::decode_lit(&vec![179, 4, 104, 101, 114, 101]).unwrap()
}
}
}

View File

@ -0,0 +1,337 @@
#![allow(unused_parens)]
#![allow(unused_imports)]
use std::convert::TryFrom;
use preserves_schema::support as _support;
use _support::Deserialize;
use _support::Parse;
use _support::Unparse;
use _support::preserves;
use preserves::value::Domain;
use preserves::value::NestedValue;
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]
pub struct Present {
pub username: std::string::String
}
impl preserves::value::Domain for Present {}
impl<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>> _support::Deserialize<'de, _Value, R> for Present {
fn deserialize(r: &mut R) -> std::result::Result<Self, _support::ParseError> {
r.open_record(None)?;
let mut _tmp0 = _support::B::Type::default();
_tmp0.shift(Some(_support::B::Item::RecordLabel));
r.boundary(&_tmp0)?;
match r.next_token(true)? {
preserves::value::Token::Atom(v) => match v.value() {
preserves::value::Value::Symbol(w) if w == "Present" => {}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Present"))?,
}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Present"))?,
}
let _tmp1 = ();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp2 = r.next_str()?.into_owned();
r.ensure_complete(_tmp0, &_support::B::Item::RecordField)?;
Ok(Present {username: _tmp2})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Parse<_L, _Value> for Present {
fn parse(_ctxt: _L, value: &_Value) -> std::result::Result<Self, _support::ParseError> {
let _tmp0 = value.value().to_record(None)?;
if _tmp0.label() != &<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_0_PRESENT { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Present")); }
let _tmp1 = ();
if _tmp0.fields().len() < 1 { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Present")); }
let _tmp2 = (&_tmp0.fields()[0]).value().to_string()?;
Ok(Present {username: _tmp2.clone()})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Unparse<_L, _Value> for Present {
fn unparse(&self, _ctxt: _L) -> _Value {
let Present {username: _tmp0} = self;
{
let mut _tmp1 = preserves::value::Record(vec![(&<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_0_PRESENT).clone()]);
_tmp1.fields_vec_mut().push(preserves::value::Value::from(_tmp0).wrap());
_tmp1.finish().wrap()
}
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]
pub struct Says {
pub who: std::string::String,
pub what: std::string::String
}
impl preserves::value::Domain for Says {}
impl<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>> _support::Deserialize<'de, _Value, R> for Says {
fn deserialize(r: &mut R) -> std::result::Result<Self, _support::ParseError> {
r.open_record(None)?;
let mut _tmp0 = _support::B::Type::default();
_tmp0.shift(Some(_support::B::Item::RecordLabel));
r.boundary(&_tmp0)?;
match r.next_token(true)? {
preserves::value::Token::Atom(v) => match v.value() {
preserves::value::Value::Symbol(w) if w == "Says" => {}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Says"))?,
}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Says"))?,
}
let _tmp1 = ();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp2 = r.next_str()?.into_owned();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp3 = r.next_str()?.into_owned();
r.ensure_complete(_tmp0, &_support::B::Item::RecordField)?;
Ok(Says {who: _tmp2, what: _tmp3})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Parse<_L, _Value> for Says {
fn parse(_ctxt: _L, value: &_Value) -> std::result::Result<Self, _support::ParseError> {
let _tmp0 = value.value().to_record(None)?;
if _tmp0.label() != &<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_1_SAYS { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Says")); }
let _tmp1 = ();
if _tmp0.fields().len() < 2 { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Says")); }
let _tmp2 = (&_tmp0.fields()[0]).value().to_string()?;
let _tmp3 = (&_tmp0.fields()[1]).value().to_string()?;
Ok(Says {who: _tmp2.clone(), what: _tmp3.clone()})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Unparse<_L, _Value> for Says {
fn unparse(&self, _ctxt: _L) -> _Value {
let Says {who: _tmp0, what: _tmp1} = self;
{
let mut _tmp2 = preserves::value::Record(vec![(&<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_1_SAYS).clone()]);
_tmp2.fields_vec_mut().push(preserves::value::Value::from(_tmp0).wrap());
_tmp2.fields_vec_mut().push(preserves::value::Value::from(_tmp1).wrap());
_tmp2.finish().wrap()
}
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]
pub enum Status {
Here,
Away {
since: std::boxed::Box<TimeStamp>
}
}
impl preserves::value::Domain for Status {}
fn read_status_here<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>>(r: &mut R) -> std::result::Result<Status, _support::ParseError> {
match r.next_token(true)? {
preserves::value::Token::Atom(v) => match v.value() {
preserves::value::Value::Symbol(w) if w == "here" => {}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::here"))?,
}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::here"))?,
}
let _tmp0 = ();
Ok(Status::Here)
}
fn read_status_away<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>>(r: &mut R) -> std::result::Result<Status, _support::ParseError> {
r.open_record(None)?;
let mut _tmp0 = _support::B::Type::default();
_tmp0.shift(Some(_support::B::Item::RecordLabel));
r.boundary(&_tmp0)?;
match r.next_token(true)? {
preserves::value::Token::Atom(v) => match v.value() {
preserves::value::Value::Symbol(w) if w == "away" => {}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::away"))?,
}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::away"))?,
}
let _tmp1 = ();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp2 = TimeStamp::deserialize(r)?;
r.ensure_complete(_tmp0, &_support::B::Item::RecordField)?;
Ok(Status::Away {since: std::boxed::Box::new(_tmp2)})
}
impl<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>> _support::Deserialize<'de, _Value, R> for Status {
fn deserialize(r: &mut R) -> std::result::Result<Self, _support::ParseError> {
let _mark = r.mark()?;
match read_status_here(r) { Err(e) if e.is_conformance_error() => r.restore(&_mark)?, result => return result }
match read_status_away(r) { Err(e) if e.is_conformance_error() => r.restore(&_mark)?, result => return result }
Err(_support::ParseError::conformance_error("simpleChatProtocol.Status"))
}
}
fn parse_status_here<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
>(_ctxt: _L, value: &_Value) -> std::result::Result<Status, _support::ParseError> {
if value != &<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_2_HERE { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::here")); }
let _tmp0 = ();
Ok(Status::Here)
}
fn parse_status_away<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
>(_ctxt: _L, value: &_Value) -> std::result::Result<Status, _support::ParseError> {
let _tmp0 = value.value().to_record(None)?;
if _tmp0.label() != &<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_3_AWAY { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::away")); }
let _tmp1 = ();
if _tmp0.fields().len() < 1 { return Err(_support::ParseError::conformance_error("simpleChatProtocol.Status::away")); }
let _tmp2 = TimeStamp::parse(_ctxt, (&_tmp0.fields()[0]))?;
Ok(Status::Away {since: std::boxed::Box::new(_tmp2)})
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Parse<_L, _Value> for Status {
fn parse(_ctxt: _L, value: &_Value) -> std::result::Result<Self, _support::ParseError> {
if let Ok(r) = parse_status_here(_ctxt, value) { return Ok(r); }
if let Ok(r) = parse_status_away(_ctxt, value) { return Ok(r); }
Err(_support::ParseError::conformance_error("simpleChatProtocol.Status"))
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Unparse<_L, _Value> for Status {
fn unparse(&self, _ctxt: _L) -> _Value {
match self {
Status::Here => (&<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_2_HERE).clone(),
Status::Away {since: _tmp0} => {
let mut _tmp1 = preserves::value::Record(vec![(&<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_3_AWAY).clone()]);
_tmp1.fields_vec_mut().push(_tmp0.as_ref().unparse(_ctxt));
_tmp1.finish().wrap()
},
}
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]
pub struct TimeStamp(pub std::string::String);
impl preserves::value::Domain for TimeStamp {}
impl<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>> _support::Deserialize<'de, _Value, R> for TimeStamp {
fn deserialize(r: &mut R) -> std::result::Result<Self, _support::ParseError> {
let _tmp0 = r.next_str()?.into_owned();
Ok(TimeStamp(_tmp0))
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Parse<_L, _Value> for TimeStamp {
fn parse(_ctxt: _L, value: &_Value) -> std::result::Result<Self, _support::ParseError> {
let _tmp0 = value.value().to_string()?;
Ok(TimeStamp(_tmp0.clone()))
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Unparse<_L, _Value> for TimeStamp {
fn unparse(&self, _ctxt: _L) -> _Value {
let TimeStamp(_tmp0) = self;
preserves::value::Value::from(_tmp0).wrap()
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Hash)]
pub struct UserStatus {
pub username: std::string::String,
pub status: Status
}
impl preserves::value::Domain for UserStatus {}
impl<'de, _Value: preserves::value::NestedValue, R: _support::Reader<'de, _Value>> _support::Deserialize<'de, _Value, R> for UserStatus {
fn deserialize(r: &mut R) -> std::result::Result<Self, _support::ParseError> {
r.open_record(None)?;
let mut _tmp0 = _support::B::Type::default();
_tmp0.shift(Some(_support::B::Item::RecordLabel));
r.boundary(&_tmp0)?;
match r.next_token(true)? {
preserves::value::Token::Atom(v) => match v.value() {
preserves::value::Value::Symbol(w) if w == "Status" => {}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.UserStatus"))?,
}
_ => return Err(_support::ParseError::conformance_error("simpleChatProtocol.UserStatus"))?,
}
let _tmp1 = ();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp2 = r.next_str()?.into_owned();
_tmp0.shift(Some(_support::B::Item::RecordField));
r.boundary(&_tmp0)?;
let _tmp3 = Status::deserialize(r)?;
r.ensure_complete(_tmp0, &_support::B::Item::RecordField)?;
Ok(UserStatus {username: _tmp2, status: _tmp3})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Parse<_L, _Value> for UserStatus {
fn parse(_ctxt: _L, value: &_Value) -> std::result::Result<Self, _support::ParseError> {
let _tmp0 = value.value().to_record(None)?;
if _tmp0.label() != &<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_4_STATUS { return Err(_support::ParseError::conformance_error("simpleChatProtocol.UserStatus")); }
let _tmp1 = ();
if _tmp0.fields().len() < 2 { return Err(_support::ParseError::conformance_error("simpleChatProtocol.UserStatus")); }
let _tmp2 = (&_tmp0.fields()[0]).value().to_string()?;
let _tmp3 = Status::parse(_ctxt, (&_tmp0.fields()[1]))?;
Ok(UserStatus {username: _tmp2.clone(), status: _tmp3})
}
}
impl<
'a,
_L: Copy + Into<&'a chat::Language<_Value>>,
_Value: preserves::value::NestedValue + 'a
> _support::Unparse<_L, _Value> for UserStatus {
fn unparse(&self, _ctxt: _L) -> _Value {
let UserStatus {username: _tmp0, status: _tmp1} = self;
{
let mut _tmp2 = preserves::value::Record(vec![(&<_L as Into<&'a chat::Language<_Value>>>::into(_ctxt).LIT_4_STATUS).clone()]);
_tmp2.fields_vec_mut().push(preserves::value::Value::from(_tmp0).wrap());
_tmp2.fields_vec_mut().push(_tmp1.unparse(_ctxt));
_tmp2.finish().wrap()
}
}
}

View File

@ -0,0 +1,13 @@
<schema {
definitions: {
Present: <rec <lit Present> <tuple [
<named username <atom String>>
]>>
Says: <rec <lit Says> <tuple [
<named who <atom String>>
<named what <atom String>>
]>>
}
embeddedType: #f
version: 1
}>

View File

@ -0,0 +1,2 @@
´³bundle·µ³simpleChatProtocol„´³schema·³version³ definitions·³Says´³rec´³lit³Says„´³tupleµ´³named³who´³atom³String„„´³named³what´³atom³String„„„„„³Status´³orµµ±here´³lit³here„„µ±away´³rec´³lit³away„´³tupleµ´³named³since´³refµ„³ TimeStamp„„„„„„„„³Present´³rec´³lit³Present„´³tupleµ´³named³username´³atom³String„„„„„³ TimeStamp´³atom³String„³
UserStatus´³rec´³lit³Status„´³tupleµ´³named³username´³atom³String„„´³named³status´³refµ„³Status„„„„„„³ embeddedType€„„„„

View File

@ -0,0 +1,6 @@
version 1 .
Present = <Present @username string> .
Says = <Says @who string @what string> .
UserStatus = <Status @username string @status Status> .
Status = =here / <away @since TimeStamp> .
TimeStamp = string .

View File

@ -0,0 +1,283 @@
import * as _ from "@preserves/core";
export const $Present = _.Symbol.for("Present");
export const $Says = _.Symbol.for("Says");
export const $Status = _.Symbol.for("Status");
export const $away = _.Symbol.for("away");
export const $here = _.Symbol.for("here");
let __schema: _.Value | null = null;
export function _schema() {
if (__schema === null) {
__schema = _.decode<_.GenericEmbedded>(_.Bytes.fromHex("b4b306736368656d61b7b30776657273696f6e91b30b646566696e6974696f6e73b7b30453617973b4b303726563b4b3036c6974b3045361797384b4b3057475706c65b5b4b3056e616d6564b30377686fb4b30461746f6db306537472696e678484b4b3056e616d6564b30477686174b4b30461746f6db306537472696e678484848484b306537461747573b4b3026f72b5b5b10468657265b4b3036c6974b304686572658484b5b10461776179b4b303726563b4b3036c6974b3046177617984b4b3057475706c65b5b4b3056e616d6564b30573696e6365b4b303726566b584b30954696d655374616d708484848484848484b30750726573656e74b4b303726563b4b3036c6974b30750726573656e7484b4b3057475706c65b5b4b3056e616d6564b308757365726e616d65b4b30461746f6db306537472696e678484848484b30954696d655374616d70b4b30461746f6db306537472696e6784b30a55736572537461747573b4b303726563b4b3036c6974b30653746174757384b4b3057475706c65b5b4b3056e616d6564b308757365726e616d65b4b30461746f6db306537472696e678484b4b3056e616d6564b306737461747573b4b303726566b584b306537461747573848484848484b30c656d62656464656454797065808484"));
};
return __schema;
}
export const _imports = {}
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;
export function Present(username: string): (
Present &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {return {"username": username, __as_preserve__() {return fromPresent(this);}};}
Present.schema = function () {
return {schema: _schema(), imports: _imports, definitionName: _.Symbol.for("Present")};
}
export function Says({who, what}: {who: string, what: string}): (Says & {__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}) {return {"who": who, "what": what, __as_preserve__() {return fromSays(this);}};}
Says.schema = function () {
return {schema: _schema(), imports: _imports, definitionName: _.Symbol.for("Says")};
}
export function UserStatus({username, status}: {username: string, status: Status}): (
UserStatus &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
return {
"username": username,
"status": status,
__as_preserve__() {return fromUserStatus(this);}
};
}
UserStatus.schema = function () {
return {
schema: _schema(),
imports: _imports,
definitionName: _.Symbol.for("UserStatus")
};
}
export namespace Status {
export function here(): (
Status &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {return {"_variant": "here", __as_preserve__() {return fromStatus(this);}};};
here.schema = function () {
return {
schema: _schema(),
imports: _imports,
definitionName: _.Symbol.for("Status"),
variant: _.Symbol.for("here")
};
};
export function away(since: TimeStamp): (
Status &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
return {
"_variant": "away",
"since": since,
__as_preserve__() {return fromStatus(this);}
};
};
away.schema = function () {
return {
schema: _schema(),
imports: _imports,
definitionName: _.Symbol.for("Status"),
variant: _.Symbol.for("away")
};
};
}
export function TimeStamp(value: string): TimeStamp {return value;}
TimeStamp.schema = function () {
return {
schema: _schema(),
imports: _imports,
definitionName: _.Symbol.for("TimeStamp")
};
}
export function asPresent<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): (
Present &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let result = toPresent(v);
if (result === void 0) throw new TypeError(`Invalid Present: ${_.stringify(v)}`);
return result;
}
export function toPresent<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | (
Present &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let result: undefined | (
Present &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
);
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
let _tmp0: ({}) | undefined;
_tmp0 = _.is(v.label, $Present) ? {} : void 0;
if (_tmp0 !== void 0) {
let _tmp1: (string) | undefined;
_tmp1 = typeof v[0] === 'string' ? v[0] : void 0;
if (_tmp1 !== void 0) {result = {"username": _tmp1, __as_preserve__() {return fromPresent(this);}};};
};
};
return result;
}
Present.__from_preserve__ = toPresent;
export function fromPresent<_embedded = _.GenericEmbedded>(_v: Present): _.Value<_embedded> {return _.Record($Present, [_v["username"]]);}
export function asSays<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): (Says & {__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}) {
let result = toSays(v);
if (result === void 0) throw new TypeError(`Invalid Says: ${_.stringify(v)}`);
return result;
}
export function toSays<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | (Says & {__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}) {
let result: undefined | (Says & {__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>});
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
let _tmp0: ({}) | undefined;
_tmp0 = _.is(v.label, $Says) ? {} : void 0;
if (_tmp0 !== void 0) {
let _tmp1: (string) | undefined;
_tmp1 = typeof v[0] === 'string' ? v[0] : void 0;
if (_tmp1 !== void 0) {
let _tmp2: (string) | undefined;
_tmp2 = typeof v[1] === 'string' ? v[1] : void 0;
if (_tmp2 !== void 0) {
result = {"who": _tmp1, "what": _tmp2, __as_preserve__() {return fromSays(this);}};
};
};
};
};
return result;
}
Says.__from_preserve__ = toSays;
export function fromSays<_embedded = _.GenericEmbedded>(_v: Says): _.Value<_embedded> {return _.Record($Says, [_v["who"], _v["what"]]);}
export function asUserStatus<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): (
UserStatus &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let result = toUserStatus(v);
if (result === void 0) throw new TypeError(`Invalid UserStatus: ${_.stringify(v)}`);
return result;
}
export function toUserStatus<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | (
UserStatus &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let result: undefined | (
UserStatus &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
);
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
let _tmp0: ({}) | undefined;
_tmp0 = _.is(v.label, $Status) ? {} : void 0;
if (_tmp0 !== void 0) {
let _tmp1: (string) | undefined;
_tmp1 = typeof v[0] === 'string' ? v[0] : void 0;
if (_tmp1 !== void 0) {
let _tmp2: (Status) | undefined;
_tmp2 = toStatus(v[1]);
if (_tmp2 !== void 0) {
result = {
"username": _tmp1,
"status": _tmp2,
__as_preserve__() {return fromUserStatus(this);}
};
};
};
};
};
return result;
}
UserStatus.__from_preserve__ = toUserStatus;
export function fromUserStatus<_embedded = _.GenericEmbedded>(_v: UserStatus): _.Value<_embedded> {
return _.Record($Status, [_v["username"], fromStatus<_embedded>(_v["status"])]);
}
export function asStatus<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): (
Status &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let result = toStatus(v);
if (result === void 0) throw new TypeError(`Invalid Status: ${_.stringify(v)}`);
return result;
}
export function toStatus<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | (
Status &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
) {
let _tmp0: ({}) | undefined;
let result: undefined | (
Status &
{__as_preserve__<_embedded = _.GenericEmbedded>(): _.Value<_embedded>}
);
_tmp0 = _.is(v, $here) ? {} : void 0;
if (_tmp0 !== void 0) {result = {"_variant": "here", __as_preserve__() {return fromStatus(this);}};};
if (result === void 0) {
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
let _tmp1: ({}) | undefined;
_tmp1 = _.is(v.label, $away) ? {} : void 0;
if (_tmp1 !== void 0) {
let _tmp2: (TimeStamp) | undefined;
_tmp2 = toTimeStamp(v[0]);
if (_tmp2 !== void 0) {
result = {
"_variant": "away",
"since": _tmp2,
__as_preserve__() {return fromStatus(this);}
};
};
};
};
};
return result;
}
export namespace Status {export const __from_preserve__ = toStatus;}
export function fromStatus<_embedded = _.GenericEmbedded>(_v: Status): _.Value<_embedded> {
switch (_v._variant) {
case "here": {return $here;};
case "away": {return _.Record($away, [fromTimeStamp<_embedded>(_v["since"])]);};
};
}
export function asTimeStamp<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): TimeStamp {
let result = toTimeStamp(v);
if (result === void 0) throw new TypeError(`Invalid TimeStamp: ${_.stringify(v)}`);
return result;
}
export function toTimeStamp<_embedded = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | TimeStamp {
let _tmp0: (string) | undefined;
let result: undefined | TimeStamp;
_tmp0 = typeof v === 'string' ? v : void 0;
if (_tmp0 !== void 0) {result = _tmp0;};
return result;
}
TimeStamp.__from_preserve__ = toTimeStamp;
export function fromTimeStamp<_embedded = _.GenericEmbedded>(_v: TimeStamp): _.Value<_embedded> {return _v;}

View File

@ -0,0 +1,255 @@
# 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 = <Present @username string> .
Says = <Says @who string @what string> .
UserStatus = <Status @username string @status Status> .
Status = =here / <away @since TimeStamp> .
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
<bundle {
[
simpleChatProtocol
]: <schema {
definitions: {
Present: <rec <lit Present> <tuple [
<named username <atom String>>
]>>
Says: <rec <lit Says> <tuple [
<named who <atom String>>
<named what <atom String>>
]>>
Status: <or [
[
"here"
<lit here>
]
[
"away"
<rec <lit away> <tuple [
<named since <ref [] TimeStamp>>
]>>
]
]>
TimeStamp: <atom String>
UserStatus: <rec <lit Status> <tuple [
<named username <atom String>>
<named status <ref [] Status>>
]>>
}
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'))
'<Present "me">'
>>> P.Present.decode(parse('<Present "me">'))
Present {'username': 'me'}
>>> P.Present.try_decode(parse('<Present "me">'))
Present {'username': 'me'}
>>> P.Present.try_decode(parse('<NotPresent "me">')) is None
True
>>> stringify(P.UserStatus('me', P.Status.here()))
'<Status "me" here>'
>>> stringify(P.UserStatus('me', P.Status.away('2022-03-08')))
'<Status "me" <away "2022-03-08">>'
>>> x = P.UserStatus.decode(parse('<Status "me" <away "2022-03-08">>'))
>>> 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<TimeStamp>
}
}
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: '<away "2022-03-08">' parsePreserves
orTry: []
```
If it had been invalid, the answer would have been `nil` (because `[] value` is `nil`).