Javascript pretty-printer

This commit is contained in:
Tony Garnock-Jones 2022-01-26 14:15:35 +01:00
parent 6cd8cb2c37
commit 0129901dab
15 changed files with 433 additions and 183 deletions

View File

@ -1,10 +1,9 @@
import { Encoder } from "./encoder";
import { Tag } from "./constants";
import { Value } from "./values";
import { is, isAnnotated, IsPreservesAnnotated } from "./is";
import { stringify } from "./text";
import { GenericEmbedded } from "./embedded";
import type { Preservable } from "./encoder";
import type { GenericEmbedded } from "./embedded";
import type { Value } from "./values";
import type { Encoder, Preservable } from "./encoder";
import type { Writer, PreserveWritable } from "./writer";
export interface Position {
line?: number;
@ -53,7 +52,7 @@ export function formatPosition(p: Position | null | string): string {
}
}
export class Annotated<T = GenericEmbedded> implements Preservable<T> {
export class Annotated<T = GenericEmbedded> implements Preservable<T>, PreserveWritable<T> {
readonly annotations: Array<Value<T>>;
readonly pos: Position | null;
readonly item: Value<T>;
@ -82,6 +81,22 @@ export class Annotated<T = GenericEmbedded> implements Preservable<T> {
encoder.push(this.item);
}
__preserve_text_on__(w: Writer<T>): void {
if (w.includeAnnotations) {
const flat = this.annotations.length <= 1;
for (const a of this.annotations) {
w.state.pieces.push("@");
w.push(a);
if (flat) {
w.state.pieces.push(" ");
} else {
w.state.writeIndentSpace();
}
}
}
w.push(this.item);
}
equals(other: any): boolean {
return is(this.item, Annotated.isAnnotated(other) ? other.item : other);
}
@ -90,15 +105,6 @@ export class Annotated<T = GenericEmbedded> implements Preservable<T> {
// return hash(this.item);
// }
toString(): string {
return this.asPreservesText();
}
asPreservesText(): string {
const anns = this.annotations.map((a) => '@' + stringify(a)).join(' ');
return (anns ? anns + ' ' : anns) + stringify(this.item);
}
get [IsPreservesAnnotated](): boolean {
return true;
}

View File

@ -1,7 +1,8 @@
import { Tag } from './constants';
import { GenericEmbedded } from './embedded';
import { Encoder, Preservable } from './encoder';
import { Value } from './values';
import { GenericEmbedded } from './embedded';
import { Writer, PreserveWritable } from './writer';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
@ -10,7 +11,7 @@ export const IsPreservesBytes = Symbol.for('IsPreservesBytes');
export type BytesLike = Bytes | Uint8Array;
export class Bytes implements Preservable<never> {
export class Bytes implements Preservable<any>, PreserveWritable<any> {
readonly _view: Uint8Array;
constructor(maybeByteIterable: any = new Uint8Array()) {
@ -122,10 +123,6 @@ export class Bytes implements Preservable<never> {
return textDecoder.decode(this._view);
}
toString(): string {
return this.asPreservesText();
}
__as_preserve__<T = GenericEmbedded>(): Value<T> {
return this;
}
@ -134,26 +131,6 @@ export class Bytes implements Preservable<never> {
return Bytes.isBytes(v) ? v : void 0;
}
asPreservesText(): string {
return '#"' + this.__asciify() + '"';
}
__asciify(): string {
const pieces = [];
const v = this._view;
for (let i = 0; i < v.length; i++) {
const b = v[i];
if (b === 92 || b === 34) {
pieces.push('\\' + String.fromCharCode(b));
} else if (b >= 32 && b <= 126) {
pieces.push(String.fromCharCode(b));
} else {
pieces.push('\\x' + hexDigit(b >> 4) + hexDigit(b & 15));
}
}
return pieces.join('');
}
toHex(): string {
var nibbles = [];
for (let i = 0; i < this.length; i++) {
@ -168,12 +145,16 @@ export class Bytes implements Preservable<never> {
return this.toHex();
}
__preserve_on__(encoder: Encoder<never>) {
__preserve_on__(encoder: Encoder<any>) {
encoder.state.emitbyte(Tag.ByteString);
encoder.state.varint(this.length);
encoder.state.emitbytes(this._view);
}
__preserve_text_on__(w: Writer<any>) {
w.state.writeBytes(this._view);
}
get [IsPreservesBytes](): boolean {
return true;
}

View File

@ -1,16 +1,19 @@
import { Encoder, canonicalEncode, canonicalString } from "./encoder";
import { Tag } from "./constants";
import { FlexMap, FlexSet, _iterMap } from "./flex";
import { stringify } from "./text";
import { Value } from "./values";
import { Bytes } from './bytes';
import { GenericEmbedded } from "./embedded";
import type { Preservable } from "./encoder";
import type { Writer, PreserveWritable } from "./writer";
import { annotations, Annotated } from "./annotated";
export type DictionaryType = 'Dictionary' | 'Set';
export const DictionaryType = Symbol.for('DictionaryType');
export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V> implements Preservable<T> {
export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V>
implements Preservable<T>, PreserveWritable<T>
{
get [DictionaryType](): DictionaryType {
return 'Dictionary';
}
@ -34,21 +37,10 @@ export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends
return result;
}
asPreservesText(): string {
return '{' +
Array.from(_iterMap(this.entries(), ([k, v]) =>
stringify(k) + ': ' + stringify(v))).join(', ') +
'}';
}
clone(): KeyedDictionary<K, V, T> {
return new KeyedDictionary(this);
}
toString(): string {
return this.asPreservesText();
}
get [Symbol.toStringTag]() { return 'Dictionary'; }
__preserve_on__(encoder: Encoder<T>) {
@ -72,6 +64,22 @@ export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends
encoder.state.emitbyte(Tag.End);
}
}
__preserve_text_on__(w: Writer<T>) {
w.state.writeSeq('{', '}', this.entries(), ([k, v]) => {
w.push(k);
if (Annotated.isAnnotated<T>(v) && (annotations(v).length > 1) && w.state.isIndenting) {
w.state.pieces.push(':');
w.state.indentCount++;
w.state.writeIndent();
w.push(v);
w.state.indentCount--;
} else {
w.state.pieces.push(': ');
w.push(v as unknown as Value<T>); // Suuuuuuuper unsound
}
});
}
}
export class Dictionary<T = GenericEmbedded, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
@ -84,7 +92,9 @@ export class Dictionary<T = GenericEmbedded, V = Value<T>> extends KeyedDictiona
}
}
export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K> implements Preservable<T> {
export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K>
implements Preservable<T>, PreserveWritable<T>
{
get [DictionaryType](): DictionaryType {
return 'Set';
}
@ -107,16 +117,6 @@ export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K
return result;
}
toString(): string {
return this.asPreservesText();
}
asPreservesText(): string {
return '#{' +
Array.from(_iterMap(this.values(), stringify)).join(', ') +
'}';
}
clone(): KeyedSet<K, T> {
return new KeyedSet(this);
}
@ -132,6 +132,10 @@ export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K
encoder.encodevalues(Tag.Set, this);
}
}
__preserve_text_on__(w: Writer<T>) {
w.state.writeSeq('#{', '}', this, vv => w.push(vv));
}
}
export class Set<T = GenericEmbedded> extends KeyedSet<Value<T>, T> {

View File

@ -1,5 +1,5 @@
import type { EncoderState } from "./encoder";
import type { DecoderState } from "./decoder";
import type { EncoderState } from "./encoder";
import type { Value } from "./values";
import { ReaderStateOptions } from "./reader";
@ -26,12 +26,8 @@ export class Embedded<T> {
return isEmbedded<T>(other) && is(this.embeddedValue, other.embeddedValue);
}
asPreservesText(): string {
try {
return '#!' + (this.embeddedValue as any).asPreservesText();
} catch {
return '#!' + (this.embeddedValue as any).toString();
}
toString(): string {
return '#!' + (this.embeddedValue as any).toString();
}
__as_preserve__<R>(): T extends R ? Value<R> : never {
@ -62,7 +58,7 @@ export class GenericEmbedded {
return typeof other === 'object' && 'generic' in other && is(this.generic, other.generic);
}
asPreservesText(): string {
return this.generic.asPreservesText();
toString(): string {
return this.generic.toString();
}
}

View File

@ -1,34 +1,15 @@
import { GenericEmbedded, EmbeddedType, EmbeddedTypeDecode, EmbeddedTypeEncode } from "./embedded";
import { Encoder, EncoderState, identityEmbeddedTypeEncode } from "./encoder";
import { identityEmbeddedTypeEncode } from "./encoder";
import { genericEmbeddedTypeDecode, ReaderStateOptions } from "./reader";
import { genericEmbeddedTypeEncode, neverEmbeddedTypeEncode } from "./writer";
import { Value } from "./values";
import { DecoderState, neverEmbeddedTypeDecode } from "./decoder";
export const genericEmbeddedTypeEncode: EmbeddedTypeEncode<GenericEmbedded> = {
encode(s: EncoderState, v: GenericEmbedded): void {
new Encoder(s, this).push(v.generic);
},
toValue(v: GenericEmbedded): Value<GenericEmbedded> {
return v.generic;
}
};
import type { GenericEmbedded, EmbeddedType, EmbeddedTypeDecode } from "./embedded";
export const genericEmbeddedType: EmbeddedType<GenericEmbedded> =
Object.assign({},
genericEmbeddedTypeDecode,
genericEmbeddedTypeEncode);
export const neverEmbeddedTypeEncode: EmbeddedTypeEncode<never> = {
encode(_s: EncoderState, _v: never): void {
throw new Error("Embeddeds not permitted encoding Preserves document");
},
toValue(_v: never): Value<GenericEmbedded> {
throw new Error("Embeddeds not permitted encoding Preserves document");
}
};
export const neverEmbeddedType: EmbeddedType<never> =
Object.assign({},
neverEmbeddedTypeDecode,

View File

@ -4,6 +4,7 @@ import { Value } from "./values";
import { EncodeError } from "./codec";
import { Record, Tuple } from "./record";
import { GenericEmbedded, EmbeddedTypeEncode } from "./embedded";
import type { Embedded } from "./embedded";
export type Encodable<T> =
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
@ -248,15 +249,14 @@ export class Encoder<T = object> {
for (let i of v) { this.push(i); }
this.state.emitbyte(Tag.End);
}
else if (Array.isArray(v)) {
else if (isIterable<Value<T>>(v)) {
this.encodevalues(Tag.Sequence, v);
}
else if (isIterable<Value<T>>(v)) {
this.encodevalues(Tag.Sequence, v as Iterable<Value<T>>);
}
else {
this.state.emitbyte(Tag.Embedded);
this.embeddedEncode.encode(this.state, v.embeddedValue);
((v: Embedded<T>) => {
this.state.emitbyte(Tag.Embedded);
this.embeddedEncode.encode(this.state, v.embeddedValue);
})(v);
}
return this; // for chaining
}

View File

@ -1,7 +1,9 @@
import { Encoder, Preservable } from "./encoder";
import { Tag } from "./constants";
import { stringify } from "./text";
import { Value } from "./values";
import { GenericEmbedded } from "./embedded";
import type { GenericEmbedded } from "./embedded";
import type { Encoder, Preservable } from "./encoder";
import type { Writer, PreserveWritable } from "./writer";
export type FloatType = 'Single' | 'Double';
export const FloatType = Symbol.for('FloatType');
@ -14,7 +16,7 @@ export abstract class Float {
}
toString() {
return this.asPreservesText();
return stringify(this);
}
equals(other: any): boolean {
@ -25,7 +27,6 @@ export abstract class Float {
return (this.value | 0); // TODO: something better?
}
abstract asPreservesText(): string;
abstract get [FloatType](): FloatType;
static isFloat = (x: any): x is Float => x?.[FloatType] !== void 0;
@ -43,7 +44,7 @@ export function floatValue(f: any): number {
}
}
export class SingleFloat extends Float implements Preservable<any> {
export class SingleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
__as_preserve__<T = GenericEmbedded>(): Value<T> {
return this;
}
@ -59,12 +60,12 @@ export class SingleFloat extends Float implements Preservable<any> {
encoder.state.index += 4;
}
get [FloatType](): 'Single' {
return 'Single';
__preserve_text_on__(w: Writer<any>) {
w.state.pieces.push('' + this.value + 'f');
}
asPreservesText(): string {
return '' + this.value + 'f';
get [FloatType](): 'Single' {
return 'Single';
}
}
@ -72,7 +73,7 @@ export function Single(value: number | Float): SingleFloat {
return new SingleFloat(value);
}
export class DoubleFloat extends Float implements Preservable<any> {
export class DoubleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
__as_preserve__<T = GenericEmbedded>(): Value<T> {
return this;
}
@ -88,12 +89,12 @@ export class DoubleFloat extends Float implements Preservable<any> {
encoder.state.index += 8;
}
get [FloatType](): 'Double' {
return 'Double';
__preserve_text_on__(w: Writer<any>) {
w.state.pieces.push('' + this.value);
}
asPreservesText(): string {
return '' + this.value;
get [FloatType](): 'Double' {
return 'Double';
}
}

View File

@ -3,15 +3,13 @@
import { Annotated } from './annotated';
import { Bytes } from './bytes';
import { Set, Dictionary } from './dictionary';
import { Record } from './record';
import { stringify } from './text';
import * as util from 'util';
[Bytes, Annotated, Set, Dictionary].forEach((C) => {
(C as any).prototype[util.inspect.custom] =
function (_depth: any, _options: any) {
return this.asPreservesText();
return stringify(this, { indent: 2 });
};
});
Record.fallbackToString = util.inspect;

View File

@ -59,7 +59,7 @@ export class ReaderState {
if (this.atEnd()) {
this.buffer = data;
} else {
this.buffer = this.buffer.substr(this.index) + data;
this.buffer = this.buffer.substring(this.index) + data;
}
this.discarded += this.index;
this.index = 0;

View File

@ -1,6 +1,7 @@
import { GenericEmbedded } from "./embedded";
import { is } from "./is";
import { Value } from "./values";
import { Writer } from "./writer";
export type Tuple<T> = Array<T> | [T];
@ -50,10 +51,6 @@ export namespace Record {
return Array.isArray(x) && 'label' in x;
}
export function fallbackToString (_f: Value<any>): string {
return '<unprintable_preserves_field_value>';
}
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
{
@ -83,19 +80,3 @@ export namespace Record {
};
}
}
Array.prototype.asPreservesText = function (): string {
if ('label' in (this as any)) {
const r = this as Record<Value, Tuple<Value>, GenericEmbedded>;
return '<' + r.label.asPreservesText() + (r.length > 0 ? ' ': '') +
r.map(f => {
try {
return f.asPreservesText();
} catch (e) {
return Record.fallbackToString(f);
}
}).join(' ') + '>';
} else {
return '[' + this.map(i => i.asPreservesText()).join(', ') + ']';
}
};

View File

@ -18,3 +18,4 @@ export * from './record';
export * from './strip';
export * from './text';
export * from './values';
export * from './writer';

View File

@ -1,15 +1,13 @@
import type { GenericEmbedded } from './embedded';
import type { Value } from './values';
export function stringify(x: any): string {
if (typeof x?.asPreservesText === 'function') {
return x.asPreservesText();
} else {
try {
return JSON.stringify(x);
} catch (_e) {
return ('' + x).asPreservesText();
}
}
import { Annotated } from './annotated';
import { Bytes } from './bytes';
import { KeyedDictionary, KeyedSet } from './dictionary';
import { Writer, Writable, WriterOptions } from './writer';
export function stringify<T = GenericEmbedded>(x: any, options?: WriterOptions<T>): string {
return Writer.stringify(x as Writable<T>, options);
}
export function preserves<T>(pieces: TemplateStringsArray, ...values: Value<T>[]): string {
@ -21,32 +19,6 @@ export function preserves<T>(pieces: TemplateStringsArray, ...values: Value<T>[]
return result.join('');
}
declare global {
interface Object { asPreservesText(): string; }
}
Object.defineProperty(Object.prototype, 'asPreservesText', {
enumerable: false,
writable: true,
value: function(): string {
return JSON.stringify(this);
}
[Annotated, Bytes, KeyedDictionary, KeyedSet].forEach((C) => {
C.prototype.toString = function () { return stringify(this); };
});
Boolean.prototype.asPreservesText = function (): string {
return this ? '#t' : '#f';
};
Number.prototype.asPreservesText = function (): string {
return '' + this;
};
String.prototype.asPreservesText = function (): string {
return JSON.stringify(this);
};
Symbol.prototype.asPreservesText = function (): string {
// TODO: escaping
return this.description ?? '||';
};

View File

@ -0,0 +1,328 @@
import { isAnnotated } from './is';
import { Record, Tuple } from "./record";
import type { GenericEmbedded, Embedded, EmbeddedTypeEncode } from "./embedded";
import { Encoder, EncoderState } from "./encoder";
import type { Value } from "./values";
export type Writable<T> =
Value<T> | PreserveWritable<T> | Iterable<Value<T>> | ArrayBufferView;
export interface PreserveWritable<T> {
__preserve_text_on__(writer: Writer<T>): void;
}
export function isPreserveWritable<T>(v: any): v is PreserveWritable<T> {
return typeof v === 'object' && v !== null && '__preserve_text_on__' in v && typeof v.__preserve_text_on__ === 'function';
}
function isIterable<T>(v: any): v is Iterable<T> {
return typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function';
}
export const genericEmbeddedTypeEncode: EmbeddedTypeEncode<GenericEmbedded> = {
encode(s: EncoderState, v: GenericEmbedded): void {
new Encoder(s, this).push(v.generic);
},
toValue(v: GenericEmbedded): Value<GenericEmbedded> {
return v.generic;
}
};
export const neverEmbeddedTypeEncode: EmbeddedTypeEncode<never> = {
encode(_s: EncoderState, _v: never): void {
throw new Error("Embeddeds not permitted encoding Preserves document");
},
toValue(_v: never): Value<GenericEmbedded> {
throw new Error("Embeddeds not permitted encoding Preserves document");
}
};
export interface WriterStateOptions {
includeAnnotations?: boolean;
indent?: number;
maxBinaryAsciiLength?: number;
maxBinaryAsciiProportion?: number;
}
export interface WriterOptions<T> extends WriterStateOptions {
embeddedEncode?: EmbeddedTypeEncode<T>;
}
export class WriterState {
pieces: string[] = [];
options: WriterStateOptions;
indentDelta: string;
indentCount = 0;
constructor (options: WriterStateOptions) {
this.options = options;
this.indentDelta = ' '.repeat(options.indent ?? 0);
}
get isIndenting(): boolean {
return this.indentDelta.length > 0;
}
get includeAnnotations(): boolean {
return this.options.includeAnnotations ?? true;
}
writeIndent() {
if (this.isIndenting) {
this.pieces.push('\n');
for (let i = 0; i < this.indentCount; i++) {
this.pieces.push(this.indentDelta);
}
}
}
writeIndentSpace() {
if (this.isIndenting) {
this.writeIndent();
} else {
this.pieces.push(' ');
}
}
escapeStringlikeChar(c: string, k: (c: string) => string = (c) => c): string {
switch (c) {
case "\\": return "\\\\";
case "\x08": return "\\b";
case "\x0c": return "\\f";
case "\x0a": return "\\n";
case "\x0d": return "\\r";
case "\x09": return "\\t";
default: return k(c);
}
}
escapeStringlike(s: string, quoteChar: string): string {
let buf = quoteChar;
for (let c of s) {
buf = buf + ((c === quoteChar) ? "\\" + quoteChar : this.escapeStringlikeChar(c));
}
return buf + quoteChar;
}
writeSeq<V>(opener: string, closer: string, vs: Iterable<V>, appender: (v: V) => void) {
let iter = vs[Symbol.iterator]();
this.pieces.push(opener);
const first_i = iter.next();
if (first_i.done !== true) {
const first_v = first_i.value;
const second_i = iter.next();
if (second_i.done === true) {
appender(first_v);
} else {
this.indentCount++;
this.writeIndent();
appender(first_v);
this.writeIndentSpace();
appender(second_i.value);
let i: IteratorResult<V>;
while ((i = iter.next()).done !== true) {
this.writeIndentSpace();
appender(i.value);
}
this.indentCount--;
this.writeIndent();
}
}
this.pieces.push(closer);
}
writeBytes(bs: Uint8Array) {
const limit = this.options.maxBinaryAsciiLength ?? 1024;
const proportion = this.options.maxBinaryAsciiProportion ?? 0.75;
if (bs.length >= limit) {
this.writeBase64(bs);
} else {
let count = 0;
let sampleSize = Math.min(bs.length, limit);
for (let i = 0; i < sampleSize; i++) {
const b = bs[i];
switch (b) {
case 9:
case 10:
case 13:
count++;
break;
default:
if (b >= 32 && b <= 126) {
count++;
}
break;
}
}
if (sampleSize === 0 || (count / sampleSize) >= proportion) {
this.writeBinaryStringlike(bs);
} else {
this.writeBase64(bs);
}
}
}
writeBase64(bs: Uint8Array) {
this.pieces.push("#[", encodeBase64(bs), "]");
}
writeBinaryStringlike(bs: Uint8Array) {
let buf = '#"';
for (let b of bs) {
if (b === 0x22) {
buf = buf + '\\"';
} else {
buf = buf + this.escapeStringlikeChar(String.fromCharCode(b), c => {
if ((b >= 0x20 && b <= 0x7e) && (b !== 0x5c)) {
return c;
} else {
return '\\x' + ('0' + b.toString(16)).slice(-2);
}
});
}
}
this.pieces.push(buf + '"');
}
couldBeFlat<T>(vs: Writable<T>[]): boolean {
let seenCompound = false;
for (let v of vs) {
if (Array.isArray(v) || Set.isSet(v) || Map.isMap(v)) {
if (seenCompound) return false;
seenCompound = true;
}
if (this.includeAnnotations && isAnnotated(v) && v.annotations.length > 1) {
return false;
}
}
return true;
}
}
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
export function encodeBase64(bs: Uint8Array): string {
let s = '';
let buffer = 0;
let bitcount = 0;
for (let b of bs) {
buffer = ((buffer & 0x3f) << 8) | b;
bitcount += 8;
while (bitcount >= 6) {
bitcount -= 6;
const v = (buffer >> bitcount) & 0x3f;
s = s + BASE64[v];
}
}
if (bitcount > 0) {
const v = (buffer << (6 - bitcount)) & 0x3f;
s = s + BASE64[v];
}
return s;
}
export class Writer<T> {
state: WriterState;
embeddedType: EmbeddedTypeEncode<T>;
constructor(state: WriterState, embeddedType: EmbeddedTypeEncode<T>);
constructor(options?: WriterOptions<T>);
constructor(
state_or_options: (WriterState | WriterOptions<T>) = {},
embeddedType?: EmbeddedTypeEncode<T>
) {
if (state_or_options instanceof WriterState) {
this.state = state_or_options;
this.embeddedType = embeddedType!;
} else {
this.state = new WriterState(state_or_options);
this.embeddedType = state_or_options.embeddedEncode ?? neverEmbeddedTypeEncode;
}
}
static stringify<T>(v: Writable<T>, options?: WriterOptions<T>): string {
const w = new Writer(options);
w.push(v);
return w.contents();
}
contents(): string {
return this.state.pieces.join('');
}
get includeAnnotations(): boolean {
return this.state.includeAnnotations;
}
push(v: Writable<T>) {
switch (typeof v) {
case 'boolean':
this.state.pieces.push(v ? '#t' : '#f');
break;
case 'string':
this.state.pieces.push(this.state.escapeStringlike(v, '"'));
break;
case 'symbol': {
const s = v.description!;
// FIXME: This regular expression is conservatively correct, but Anglo-chauvinistic.
const m = /^[a-zA-Z~!$%^&*?_=+/.][-a-zA-Z~!$%^&*?_=+/.0-9]*$/.exec(s);
if (m) {
this.state.pieces.push(s);
} else {
this.state.pieces.push(this.state.escapeStringlike(s, '|'));
}
break;
}
case 'number':
this.state.pieces.push('' + v);
break;
case 'object':
if (isPreserveWritable<unknown>(v)) {
v.__preserve_text_on__(this);
}
else if (isPreserveWritable<T>(v)) {
v.__preserve_text_on__(this);
}
else if (ArrayBuffer.isView(v)) {
if (v instanceof Uint8Array) {
this.state.writeBytes(v);
} else {
const bs = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
this.state.writeBytes(bs);
}
}
else if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v)) {
const flat = this.state.couldBeFlat(v);
this.state.pieces.push('<');
this.push(v.label);
if (!flat) this.state.indentCount++;
for (let i of v) {
if (flat) {
this.state.pieces.push(' ');
} else {
this.state.writeIndentSpace();
}
this.push(i);
}
if (!flat) this.state.indentCount--;
this.state.pieces.push('>');
}
else if (isIterable(v)) {
this.state.writeSeq('[', ']', v, vv => this.push(vv));
}
else {
((v: Embedded<T>) => {
this.state.pieces.push('#!');
new Writer(this.state, genericEmbeddedTypeEncode)
.push(this.embeddedType.toValue(v.embeddedValue));
})(v);
}
break;
default:
throw new Error(`Internal error: unhandled in Preserves Writer.push for ${v}`);
}
return this; // for chaining
}
}

View File

@ -1,3 +1,4 @@
import { stringify } from '@preserves/core';
import * as M from './meta';
export function checkSchema(schema: M.Schema): (
@ -121,7 +122,7 @@ class Checker {
this.checkNamedPattern(
scope,
M.promoteNamedSimplePattern(np),
`entry ${key.asPreservesText()} in dictionary in ${context}`));
`entry ${stringify(key)} in dictionary in ${context}`));
break;
}
})(p.value);

View File

@ -1,4 +1,4 @@
import { Dictionary, KeyedSet, FlexSet, Position, stringify, is } from "@preserves/core";
import { Dictionary, KeyedSet, FlexSet, Position, stringify } from "@preserves/core";
import { refPosition } from "../reader";
import * as M from "../meta";
import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq, opseq } from "./block";
@ -58,7 +58,7 @@ export class ModuleContext {
literal(v: M.Input): Item {
let varname = this.literals.get(v);
if (varname === void 0) {
varname = M.jsId('$' + v.asPreservesText(), () => '__lit' + this.literals.size);
varname = M.jsId('$' + stringify(v), () => '__lit' + this.literals.size);
this.literals.set(v, varname);
}
return varname;