=encoding utf8
=head1 NAME
std/dump - Structured value dumper for ZuzuScript.
=head1 SYNOPSIS
from std/dump import Dumper;
let text := Dumper.dump(
{ nums: [ 1, 2, 3 ] },
{ pretty: true, sort_keys: true }
);
=head1 IMPLEMENTATION SUPPORT
This module is supported by all implementations of ZuzuScript.
=head1 DESCRIPTION
This module provides a pure-Zuzu C<Dumper> class which serializes
Zuzu values into code-like text.
If a value cannot be realistically dumped (for example a function),
C<Dumper> emits a warning and inserts C<null> in that location.
=head1 EXPORTS
=head2 Classes
=over
=item C<Dumper>
Pure-Zuzu structured value dumper.
=over
=item C<< Dumper.dump(value, options?) >>
Parameters: C<value> is any ZuzuScript value and C<options> is an
optional dictionary. Returns: C<String>. Serializes C<value> into
code-like text.
=back
=back
=head1 COPYRIGHT AND LICENCE
B<< std/dump >> is copyright Toby Inkster.
It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.
=cut
from std/internals import class_name, object_slots, ansi_esc, ref_id;
from std/string import join, substr;
let _ANSI_RESET := ansi_esc() _ "[0m";
let _ANSI_NUMBER := ansi_esc() _ "[33m";
let _ANSI_STRING := ansi_esc() _ "[32m";
let _ANSI_BOOL := ansi_esc() _ "[35m";
let _ANSI_NULL := ansi_esc() _ "[90m";
let _ANSI_PUNC := ansi_esc() _ "[36m";
let _ANSI_KEYWORD := ansi_esc() _ "[34m";
function _is_true (value) {
return value ? true: false;
}
function _warn_unless_quiet ( String msg, Dict cfg ) {
warn msg if not cfg{quiet};
}
function _indent_pad ( depth, cfg ) {
return "" if not cfg{pretty};
let out := "";
let i := 0;
while ( i < depth ) {
out _= " ";
i++;
}
return out;
}
function _colourize ( String text, String tone, Dict cfg ) {
return text if not cfg{colour};
return tone _ text _ _ANSI_RESET;
}
function _punc ( String text, Dict cfg ) {
return _colourize( text, _ANSI_PUNC, cfg );
}
function _quote ( String text, Dict cfg ) {
let s := text;
s := "" if s ≡ null;
let out := "";
let i := 0;
let n := length s;
while ( i < n ) {
let ch := substr( s, i, 1 );
if ( ch ≡ "\\" ) {
out _= "\\\\";
}
else if ( ch ≡ "\"" ) {
out _= "\\\"";
}
else if ( ch ≡ "\n" ) {
out _= "\\n";
}
else if ( ch ≡ "\r" ) {
out _= "\\r";
}
else if ( ch ≡ "\t" ) {
out _= "\\t";
}
else {
out _= ch;
}
i++;
}
return _colourize( "\"" _ out _ "\"", _ANSI_STRING, cfg );
}
function _keys_for ( Dict d, Dict cfg ) {
return cfg{sort_keys} ? d.sorted_keys(): d.keys();
}
function _null_literal ( Dict cfg ) {
return _colourize( "null", _ANSI_NULL, cfg );
}
function _seen_check_and_mark ( value, String label, Dict cfg, Dict state ) {
let id := ref_id(value);
if ( id ≢ null and state{seen}.exists(id) ) {
_warn_unless_quiet( "Dumper: recursive " _ label _ " detected; dumping null", cfg );
return true;
}
if ( id ≢ null ) {
state{seen}.set( id, true );
}
return false;
}
function _dump_value ( value, Dict cfg, Dict state, Number depth ) {
if ( value instanceof Null ) {
return _null_literal(cfg);
}
if ( value instanceof Boolean ) {
return _colourize( value ? "true": "false", _ANSI_BOOL, cfg );
}
if ( value instanceof Number ) {
return _colourize( "" _ value, _ANSI_NUMBER, cfg );
}
if ( value instanceof String ) {
return _quote( value, cfg );
}
if ( value instanceof Array ) {
return _null_literal(cfg) if _seen_check_and_mark( value, "array", cfg, state );
if ( value.length() ≡ 0 ) {
return _punc( "[", cfg ) _ _punc( "]", cfg );
}
let pretty := cfg{pretty};
let sep := pretty ? _punc( ",\n", cfg ): _punc(",", cfg );
let parts := [];
for ( let item in value ) {
parts.push( _dump_value( item, cfg, state, depth + 1 ) );
}
if ( not pretty ) {
return _punc( "[", cfg ) _ join( sep, parts ) _ _punc( "]", cfg );
}
let inner := [];
for ( let p in parts ) {
inner.push( _indent_pad( depth + 1, cfg ) _ p );
}
return _punc( "[\n", cfg ) _ join( sep, inner ) _ _punc( "\n", cfg ) _ _indent_pad( depth, cfg ) _
_punc( "]", cfg );
}
if ( value instanceof Dict ) {
return _null_literal(cfg) if _seen_check_and_mark( value, "dict", cfg, state );
let keys := _keys_for( value, cfg );
if ( keys.length() ≡ 0 ) {
return _punc( "{" , cfg ) _ _punc( "}", cfg );
}
let pretty := cfg{pretty};
let sep := pretty ? _punc( ",\n", cfg ): _punc(",", cfg );
let colon := pretty ? _punc( ": ", cfg ): _punc(":", cfg );
let entries := [];
for ( let key in keys ) {
let dumped := _dump_value( value.get(key), cfg, state, depth + 1 );
entries.push( _quote( key, cfg ) _ colon _ dumped );
}
if ( not pretty ) {
return _punc( "{" , cfg ) _ join( sep, entries ) _ _punc( "}", cfg );
}
let inner := [];
for ( let e in entries ) {
inner.push( _indent_pad( depth + 1, cfg ) _ e );
}
return _punc( "{\n", cfg ) _ join( sep, inner ) _ _punc( "\n", cfg ) _ _indent_pad( depth, cfg ) _
_punc( "}", cfg );
}
if ( value instanceof PairList ) {
return _null_literal(cfg) if _seen_check_and_mark( value, "pairlist", cfg, state );
if ( value.empty ) {
return _punc( "{{" , cfg ) _ _punc( "}}", cfg );
}
let pretty := cfg{pretty};
let sep := pretty ? _punc( ",\n", cfg ): _punc(",", cfg );
let colon := pretty ? _punc( ": ", cfg ): _punc(":", cfg );
let entries := [];
value.for_each_pair( function (p) {
let dumped := _dump_value( p.value, cfg, state, depth + 1 );
entries.push( _quote( p.key, cfg ) _ colon _ dumped );
} );
if ( not pretty ) {
return _punc( "{{" , cfg ) _ join( sep, entries ) _ _punc( "}}", cfg );
}
let inner := [];
for ( let e in entries ) {
inner.push( _indent_pad( depth + 1, cfg ) _ e );
}
return _punc( "{{\n", cfg ) _ join( sep, inner ) _ _punc( "\n", cfg ) _ _indent_pad( depth, cfg ) _
_punc( "}}", cfg );
}
if ( value instanceof Set or value instanceof Bag ) {
let is_set := value instanceof Set;
return _null_literal(cfg)
if _seen_check_and_mark( value, is_set ? "set" : "bag", cfg, state );
let left := is_set ? "<<": "<<<";
let right := is_set ? ">>": ">>>";
let sep := cfg{pretty} ? _punc( ", ", cfg ): _punc(",", cfg );
let items := [];
for ( let item in value ) {
items.push( _dump_value( item, cfg, state, depth + 1 ) );
}
return _punc( left, cfg ) _ join( sep, items ) _ _punc( right, cfg );
}
if ( value instanceof Pair ) {
let pair_value := value{pair};
if ( not( pair_value instanceof Array ) or pair_value.length() < 2 ) {
_warn_unless_quiet( "Dumper: invalid Pair shape; using null", cfg );
return _null_literal(cfg);
}
let first := _dump_value( pair_value[0], cfg, state, depth + 1 );
let second := _dump_value( pair_value[1], cfg, state, depth + 1 );
let body := _punc( "[", cfg ) _ first _ _punc(",", cfg ) _ second _ _punc( "]", cfg );
let kw_new := _colourize( "new", _ANSI_KEYWORD, cfg );
return kw_new _ " Pair" _ _punc("(", cfg ) _ "pair" _ _punc(":", cfg ) _ body _ _punc(")", cfg );
}
if (
value instanceof Function or
value instanceof Class or
typeof value ≡ "Regexp" or
typeof value ≡ "Method"
) {
let t := typeof value;
_warn_unless_quiet( "Dumper: value of type '" _ t _ "' is not dumpable; using null", cfg, );
return _null_literal(cfg);
}
return _null_literal(cfg) if _seen_check_and_mark( value, "object", cfg, state );
let cname := class_name(value);
let slots := object_slots(value);
if ( cname ≡ null or not( slots instanceof Dict ) ) {
_warn_unless_quiet( "Dumper: object internals unavailable; dumping null", cfg );
return _null_literal(cfg);
}
let keys := _keys_for( slots, cfg );
let colon := cfg{pretty} ? _punc( ": ", cfg ): _punc(":", cfg );
let args := [];
for ( let key in keys ) {
args.push( key _ colon _ _dump_value( slots.get(key), cfg, state, depth + 1 ) );
}
let kw_new := _colourize( "new", _ANSI_KEYWORD, cfg );
if ( args.length() ≡ 0 ) {
return kw_new _ " " _ cname _ _punc( "()", cfg );
}
if ( not cfg{pretty} ) {
return kw_new _ " " _ cname _ _punc("(", cfg ) _ join( _punc(",", cfg ), args ) _ _punc(")", cfg );
}
let inner := [];
for ( let arg in args ) {
inner.push( _indent_pad( depth + 1, cfg ) _ arg );
}
return kw_new _ " " _ cname _ _punc( "(\n", cfg ) _ join( _punc( ",\n", cfg ), inner ) _ _punc( "\n",
cfg ) _ _indent_pad( depth, cfg ) _ _punc(")", cfg );
}
class Dumper {
static method dump ( value, options? ) {
let cfg := { pretty: false, sort_keys: false, colour: false, quiet: false };
if ( options instanceof Dict ) {
cfg{pretty} := _is_true( options{pretty} ) if "pretty" in options;
cfg{sort_keys} := _is_true( options{sort_keys} ) if "sort_keys" in options;
cfg{colour} := _is_true( options{colour} ) if "colour" in options;
cfg{quiet} := _is_true( options{quiet} ) if "quiet" in options;
}
return _dump_value( value, cfg, { seen: {} }, 0 );
}
}
std/dump
Standard Library source code
Structured value dumper for ZuzuScript.
Module
- Name
std/dump- Area
- Standard Library
- Source
modules/std/dump.zzm