New tests; update TypeScript implementation; minor spec fixes
This commit is contained in:
parent
9eee54be80
commit
8ff1c9441c
|
@ -35,6 +35,10 @@ export class Bytes implements Preservable<any>, PreserveWritable<any> {
|
|||
}
|
||||
}
|
||||
|
||||
dataview(): DataView {
|
||||
return new DataView(this._view.buffer, this._view.byteOffset, this._view.byteLength);
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this._view.length;
|
||||
}
|
||||
|
@ -179,6 +183,10 @@ export function underlying(b: Bytes | Uint8Array): Uint8Array {
|
|||
return (b instanceof Uint8Array) ? b : b._view;
|
||||
}
|
||||
|
||||
export function dataview(b: Bytes | DataView): DataView {
|
||||
return (b instanceof DataView) ? b : b.dataview();
|
||||
}
|
||||
|
||||
// Uint8Array / TypedArray methods
|
||||
|
||||
export interface Bytes {
|
||||
|
|
|
@ -216,8 +216,8 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
switch (tag) {
|
||||
case Tag.False: return this.state.wrap<T>(false);
|
||||
case Tag.True: return this.state.wrap<T>(true);
|
||||
case Tag.Float: return this.state.wrap<T>(new SingleFloat(this.state.nextbytes(4).getFloat32(0, false)));
|
||||
case Tag.Double: return this.state.wrap<T>(new DoubleFloat(this.state.nextbytes(8).getFloat64(0, false)));
|
||||
case Tag.Float: return this.state.wrap<T>(SingleFloat.fromBytes(this.state.nextbytes(4)));
|
||||
case Tag.Double: return this.state.wrap<T>(DoubleFloat.fromBytes(this.state.nextbytes(8)));
|
||||
case Tag.End: throw new DecodeError("Unexpected Compound end marker");
|
||||
case Tag.Annotation: {
|
||||
const a = this.next();
|
||||
|
@ -294,7 +294,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
nextFloat(): SingleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Float: return new SingleFloat(this.state.nextbytes(4).getFloat32(0, false));
|
||||
case Tag.Float: return SingleFloat.fromBytes(this.state.nextbytes(4));
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
nextDouble(): DoubleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Double: return new DoubleFloat(this.state.nextbytes(8).getFloat64(0, false));
|
||||
case Tag.Double: return DoubleFloat.fromBytes(this.state.nextbytes(8));
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Value } from "./values";
|
|||
import type { GenericEmbedded } from "./embedded";
|
||||
import type { Encoder, Preservable } from "./encoder";
|
||||
import type { Writer, PreserveWritable } from "./writer";
|
||||
import { Bytes, dataview, underlying } from "./bytes";
|
||||
|
||||
export type FloatType = 'Single' | 'Double';
|
||||
export const FloatType = Symbol.for('FloatType');
|
||||
|
@ -19,8 +20,15 @@ export abstract class Float {
|
|||
return stringify(this);
|
||||
}
|
||||
|
||||
abstract toBytes(): Bytes;
|
||||
|
||||
equals(other: any): boolean {
|
||||
return Object.is(other.constructor, this.constructor) && (other.value === this.value);
|
||||
if (!Object.is(other.constructor, this.constructor)) return false;
|
||||
if (Number.isNaN(this.value) && Number.isNaN(other.value)) {
|
||||
return other.toBytes().equals(this.toBytes());
|
||||
} else {
|
||||
return Object.is(other.value, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
hashCode(): number {
|
||||
|
@ -44,24 +52,72 @@ export function floatValue(f: any): number {
|
|||
}
|
||||
}
|
||||
|
||||
export function floatlikeString(f: number): string {
|
||||
if (Object.is(f, -0)) return '-0.0';
|
||||
const s = '' + f;
|
||||
if (s.includes('.') || s.includes('e') || s.includes('E')) return s;
|
||||
return s + '.0';
|
||||
}
|
||||
|
||||
export class SingleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static fromBytes(bs: Bytes | DataView): SingleFloat {
|
||||
const view = dataview(bs);
|
||||
const vf = view.getInt32(0, false);
|
||||
if ((vf & 0x7f800000) === 0x7f800000) {
|
||||
// NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
|
||||
const sign = vf >> 31;
|
||||
const payload = vf & 0x007fffff;
|
||||
const dbs = new Bytes(8);
|
||||
const dview = dataview(dbs);
|
||||
dview.setInt16(0, (sign << 15) | 0x7ff0 | (payload >> 19), false);
|
||||
dview.setInt32(2, (payload & 0x7ffff) << 13, false);
|
||||
return new SingleFloat(dview.getFloat64(0, false));
|
||||
} else {
|
||||
return new SingleFloat(dataview(bs).getFloat32(0, false));
|
||||
}
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | SingleFloat {
|
||||
return Float.isSingle(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__w(v: DataView, offset: number) {
|
||||
if (Number.isNaN(this.value)) {
|
||||
const dbs = new Bytes(8);
|
||||
const dview = dataview(dbs);
|
||||
dview.setFloat64(0, this.value, false);
|
||||
const sign = dview.getInt8(0) >> 7;
|
||||
const payload = (dview.getInt32(1, false) >> 5) & 0x007fffff;
|
||||
const vf = (sign << 31) | 0x7f800000 | payload;
|
||||
v.setInt32(offset, vf, false);
|
||||
} else {
|
||||
v.setFloat32(offset, this.value, false);
|
||||
}
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<any>) {
|
||||
encoder.state.emitbyte(Tag.Float);
|
||||
encoder.state.makeroom(4);
|
||||
encoder.state.view.setFloat32(encoder.state.index, this.value, false);
|
||||
this.__w(encoder.state.view, encoder.state.index);
|
||||
encoder.state.index += 4;
|
||||
}
|
||||
|
||||
toBytes(): Bytes {
|
||||
const bs = new Bytes(4);
|
||||
this.__w(bs.dataview(), 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<any>) {
|
||||
w.state.pieces.push('' + this.value + 'f');
|
||||
if (Number.isFinite(this.value)) {
|
||||
w.state.pieces.push(floatlikeString(this.value) + 'f');
|
||||
} else {
|
||||
w.state.pieces.push('#xf"', this.toBytes().toHex(), '"');
|
||||
}
|
||||
}
|
||||
|
||||
get [FloatType](): 'Single' {
|
||||
|
@ -78,6 +134,10 @@ export class DoubleFloat extends Float implements Preservable<any>, PreserveWrit
|
|||
return this;
|
||||
}
|
||||
|
||||
static fromBytes(bs: Bytes | DataView): DoubleFloat {
|
||||
return new DoubleFloat(dataview(bs).getFloat64(0, false));
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | DoubleFloat {
|
||||
return Float.isDouble(v) ? v : void 0;
|
||||
}
|
||||
|
@ -89,8 +149,18 @@ export class DoubleFloat extends Float implements Preservable<any>, PreserveWrit
|
|||
encoder.state.index += 8;
|
||||
}
|
||||
|
||||
toBytes(): Bytes {
|
||||
const bs = new Bytes(8);
|
||||
bs.dataview().setFloat64(0, this.value, false);
|
||||
return bs;
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<any>) {
|
||||
w.state.pieces.push('' + this.value);
|
||||
if (Number.isFinite(this.value)) {
|
||||
w.state.pieces.push(floatlikeString(this.value));
|
||||
} else {
|
||||
w.state.pieces.push('#xd"', this.toBytes().toHex(), '"');
|
||||
}
|
||||
}
|
||||
|
||||
get [FloatType](): 'Double' {
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
import type { Value } from './values';
|
||||
import { DecodeError, ShortPacket } from './codec';
|
||||
import { Dictionary, Set } from './dictionary';
|
||||
import { strip, unannotate } from './strip';
|
||||
import { Bytes, unhexDigit } from './bytes';
|
||||
import { decode, Decoder, DecoderState, neverEmbeddedTypeDecode } from './decoder';
|
||||
import { strip } from './strip';
|
||||
import { Bytes, underlying, unhexDigit } from './bytes';
|
||||
import { Decoder, DecoderState, neverEmbeddedTypeDecode } from './decoder';
|
||||
import { Record } from './record';
|
||||
import { Annotated, newPosition, Position, updatePosition } from './annotated';
|
||||
import { Double, DoubleFloat, Single, SingleFloat } from './float';
|
||||
import { Double, DoubleFloat, FloatType, Single, SingleFloat } from './float';
|
||||
import { stringify } from './text';
|
||||
import { embed, GenericEmbedded, EmbeddedTypeDecode } from './embedded';
|
||||
|
||||
|
@ -25,6 +25,13 @@ type IntOrFloat = 'int' | 'float';
|
|||
type Numeric = number | SingleFloat | DoubleFloat;
|
||||
type IntContinuation = (kind: IntOrFloat, acc: string) => Numeric;
|
||||
|
||||
export const NUMBER_RE: RegExp = /^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))([fF]?))?$/;
|
||||
// Groups:
|
||||
// 1 - integer part and sign
|
||||
// 2 - decimal part, exponent and Float marker
|
||||
// 3 - decimal part and exponent
|
||||
// 7 - Float marker
|
||||
|
||||
export class ReaderState {
|
||||
buffer: string;
|
||||
pos: Position;
|
||||
|
@ -124,6 +131,22 @@ export class ReaderState {
|
|||
}
|
||||
}
|
||||
|
||||
readHexFloat(precision: FloatType): SingleFloat | DoubleFloat {
|
||||
const pos = this.copyPos();
|
||||
if (this.nextchar() !== '"') {
|
||||
this.error("Missing open-double-quote in hex-encoded floating-point number", pos);
|
||||
}
|
||||
const bs = this.readHexBinary();
|
||||
switch (precision) {
|
||||
case 'Single':
|
||||
if (bs.length !== 4) this.error("Incorrect number of bytes in hex-encoded Float", pos);
|
||||
return SingleFloat.fromBytes(bs);
|
||||
case 'Double':
|
||||
if (bs.length !== 8) this.error("Incorrect number of bytes in hex-encoded Double", pos);
|
||||
return DoubleFloat.fromBytes(bs);
|
||||
}
|
||||
}
|
||||
|
||||
readBase64Binary(): Bytes {
|
||||
let acc = '';
|
||||
while (true) {
|
||||
|
@ -135,67 +158,7 @@ export class ReaderState {
|
|||
return decodeBase64(acc);
|
||||
}
|
||||
|
||||
readIntpart(acc: string, ch: string): Numeric {
|
||||
if (ch === '0') return this.readFracexp('int', acc + ch);
|
||||
return this.readDigit1('int', acc, (kind, acc) => this.readFracexp(kind, acc), ch);
|
||||
}
|
||||
|
||||
readDigit1(kind: IntOrFloat, acc: string, k: IntContinuation, ch?: string): Numeric {
|
||||
if (ch === void 0) ch = this.nextchar();
|
||||
if (ch >= '0' && ch <= '9') return this.readDigit0(kind, acc + ch, k);
|
||||
this.error('Incomplete number', this.pos);
|
||||
}
|
||||
|
||||
readDigit0(kind: IntOrFloat, acc: string, k: IntContinuation): Numeric {
|
||||
while (true) {
|
||||
if (this.atEnd()) break;
|
||||
const ch = this.peek();
|
||||
if (!(ch >= '0' && ch <= '9')) break;
|
||||
this.advance();
|
||||
acc = acc + ch;
|
||||
}
|
||||
return k(kind, acc);
|
||||
}
|
||||
|
||||
readFracexp(kind: IntOrFloat, acc: string): Numeric {
|
||||
if (!this.atEnd() && this.peek() === '.') {
|
||||
this.advance();
|
||||
return this.readDigit1('float', acc + '.', (kind, acc) => this.readExp(kind, acc));
|
||||
}
|
||||
return this.readExp(kind, acc);
|
||||
}
|
||||
|
||||
readExp(kind: IntOrFloat, acc: string): Numeric {
|
||||
const ch = this.atEnd() ? '' : this.peek();
|
||||
if (ch === 'e' || ch === 'E') {
|
||||
this.advance();
|
||||
return this.readSignAndExp(acc + ch);
|
||||
}
|
||||
return this.finishNumber(kind, acc);
|
||||
}
|
||||
|
||||
readSignAndExp(acc: string): Numeric {
|
||||
const ch = this.peek();
|
||||
if (ch === '+' || ch === '-') {
|
||||
this.advance();
|
||||
return this.readDigit1('float', acc + ch, (kind, acc) => this.finishNumber(kind, acc));
|
||||
}
|
||||
return this.readDigit1('float', acc, (kind, acc) => this.finishNumber(kind, acc));
|
||||
}
|
||||
|
||||
finishNumber(kind: IntOrFloat, acc: string): Numeric {
|
||||
const i = parseFloat(acc);
|
||||
if (kind === 'int') return i;
|
||||
const ch = this.atEnd() ? '' : this.peek();
|
||||
if (ch === 'f' || ch === 'F') {
|
||||
this.advance();
|
||||
return Single(i);
|
||||
} else {
|
||||
return Double(i);
|
||||
}
|
||||
}
|
||||
|
||||
readRawSymbol<T>(acc: string): Value<T> {
|
||||
readRawSymbolOrNumber<T>(acc: string): Value<T> {
|
||||
while (true) {
|
||||
if (this.atEnd()) break;
|
||||
const ch = this.peek();
|
||||
|
@ -203,7 +166,20 @@ export class ReaderState {
|
|||
this.advance();
|
||||
acc = acc + ch;
|
||||
}
|
||||
return Symbol.for(acc);
|
||||
const m = NUMBER_RE.exec(acc);
|
||||
if (m) {
|
||||
if (m[2] === void 0) {
|
||||
let v = parseInt(m[1]);
|
||||
if (Object.is(v, -0)) v = 0;
|
||||
return v;
|
||||
} else if (m[7] === '') {
|
||||
return Double(parseFloat(m[1] + m[3]));
|
||||
} else {
|
||||
return Single(parseFloat(m[1] + m[3]));
|
||||
}
|
||||
} else {
|
||||
return Symbol.for(acc);
|
||||
}
|
||||
}
|
||||
|
||||
readStringlike<E, R>(xform: (ch: string) => E,
|
||||
|
@ -355,11 +331,6 @@ export class Reader<T> {
|
|||
const unwrapped = ((): Value<T> => {
|
||||
const c = this.state.nextchar();
|
||||
switch (c) {
|
||||
case '-':
|
||||
return this.state.readIntpart('-', this.state.nextchar());
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
return this.state.readIntpart('', c);
|
||||
case '"':
|
||||
return this.state.readString('"');
|
||||
case '|':
|
||||
|
@ -377,22 +348,13 @@ export class Reader<T> {
|
|||
case 't': return true;
|
||||
case '{': return this.seq(new Set<T>(), (v, s) => s.add(v), '}');
|
||||
case '"': return this.state.readLiteralBinary();
|
||||
case 'x':
|
||||
if (this.state.nextchar() !== '"') {
|
||||
this.state.error('Expected open-quote at start of hex ByteString',
|
||||
startPos);
|
||||
}
|
||||
return this.state.readHexBinary();
|
||||
case '[': return this.state.readBase64Binary();
|
||||
case '=': {
|
||||
const bs = unannotate(this.next());
|
||||
if (!Bytes.isBytes(bs)) this.state.error('ByteString must follow #=',
|
||||
startPos);
|
||||
return decode<T>(bs, {
|
||||
embeddedDecode: this.embeddedType,
|
||||
includeAnnotations: this.state.options.includeAnnotations,
|
||||
});
|
||||
case 'x': switch (this.state.nextchar()) {
|
||||
case '"': return this.state.readHexBinary();
|
||||
case 'f': return this.state.readHexFloat('Single');
|
||||
case 'd': return this.state.readHexFloat('Double');
|
||||
default: this.state.error('Invalid #x syntax', startPos);
|
||||
}
|
||||
case '[': return this.state.readBase64Binary();
|
||||
case '!': return embed(this.embeddedType.fromValue(
|
||||
new Reader<GenericEmbedded>(this.state, genericEmbeddedTypeDecode).next(),
|
||||
this.state.options));
|
||||
|
@ -411,7 +373,7 @@ export class Reader<T> {
|
|||
case ']': this.state.error('Unexpected ]', startPos);
|
||||
case '}': this.state.error('Unexpected }', startPos);
|
||||
default:
|
||||
return this.state.readRawSymbol(c);
|
||||
return this.state.readRawSymbolOrNumber(c);
|
||||
}
|
||||
})();
|
||||
return this.wrap(unwrapped, startPos);
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { Value } from './values';
|
|||
import { Annotated } from './annotated';
|
||||
import { Bytes } from './bytes';
|
||||
import { KeyedDictionary, KeyedSet } from './dictionary';
|
||||
import { Writer, Writable, WriterOptions, EmbeddedWriter, WriterState } from './writer';
|
||||
import { Writer, WriterOptions, EmbeddedWriter, WriterState } from './writer';
|
||||
import { fromJS } from './fromjs';
|
||||
|
||||
export const stringifyEmbeddedWrite: EmbeddedWriter<any> = {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Record, Tuple } from "./record";
|
|||
import type { GenericEmbedded, Embedded, EmbeddedTypeEncode } from "./embedded";
|
||||
import { Encoder, EncoderState } from "./encoder";
|
||||
import type { Value } from "./values";
|
||||
import { NUMBER_RE } from './reader';
|
||||
|
||||
export type Writable<T> =
|
||||
Value<T> | PreserveWritable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
@ -270,8 +271,7 @@ export class Writer<T> {
|
|||
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) {
|
||||
if (/^[-a-zA-Z0-9~!$%^&*?_=+/.]+$/.exec(s) && !NUMBER_RE.exec(s)) {
|
||||
this.state.pieces.push(s);
|
||||
} else {
|
||||
this.state.pieces.push(this.state.escapeStringlike(s, '|'));
|
||||
|
|
Binary file not shown.
|
@ -85,11 +85,15 @@
|
|||
double6: @"Invalid chars" <ParseError "#xd\"12zz56789abcdef0\"">
|
||||
double7: @"Positive infinity" <Test #x"837ff0000000000000" #xd"7ff0000000000000">
|
||||
double8: @"Negative infinity" <Test #x"83fff0000000000000" #xd"fff0000000000000">
|
||||
double9: @"-NaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-NaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+NaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+NaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double9: @"-qNaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-qNaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+qNaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+qNaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double13: @"Bad spacing" <ParseError "#xd\"12345 6789abcdef0\"">
|
||||
double14: @"-sNaN" <Test #x"83fff8000000000001" #xd"fff8000000000001">
|
||||
double15: @"-sNaN" <Test #x"83fff8000000000111" #xd"fff8000000000111">
|
||||
double16: @"+sNaN" <Test #x"837ff8000000000001" #xd"7ff8000000000001">
|
||||
double17: @"+sNaN" <Test #x"837ff8000000000111" #xd"7ff8000000000111">
|
||||
float0: <Test #x"8200000000" 0.0f>
|
||||
float+0: <Test #x"8200000000" +0.0f>
|
||||
float-0: <Test #x"8280000000" -0.0f>
|
||||
|
@ -100,11 +104,15 @@
|
|||
float5: @"Invalid chars" <ParseError "#xf\"12zz5678\"">
|
||||
float6: @"Positive infinity" <Test #x"827f800000" #xf"7f800000">
|
||||
float7: @"Negative infinity" <Test #x"82ff800000" #xf"ff800000">
|
||||
float8: @"+NaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+NaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-NaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-NaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float8: @"+sNaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+sNaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-sNaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-sNaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float12: @"Bad spacing" <ParseError "#xf\"12345 678\"">
|
||||
float13: @"+qNaN" <Test #x"827fc00001" #xf"7fc00001">
|
||||
float14: @"+qNaN" <Test #x"827fc00111" #xf"7fc00111">
|
||||
float15: @"-qNaN" <Test #x"82ffc00001" #xf"ffc00001">
|
||||
float16: @"-qNaN" <Test #x"82ffc00111" #xf"ffc00111">
|
||||
int-257: <Test #x"a1feff" -257>
|
||||
int-256: <Test #x"a1ff00" -256>
|
||||
int-255: <Test #x"a1ff01" -255>
|
||||
|
|
|
@ -85,11 +85,15 @@
|
|||
double6: @"Invalid chars" <ParseError "#xd\"12zz56789abcdef0\"">
|
||||
double7: @"Positive infinity" <Test #x"837ff0000000000000" #xd"7ff0000000000000">
|
||||
double8: @"Negative infinity" <Test #x"83fff0000000000000" #xd"fff0000000000000">
|
||||
double9: @"-NaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-NaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+NaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+NaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double9: @"-qNaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-qNaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+qNaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+qNaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double13: @"Bad spacing" <ParseError "#xd\"12345 6789abcdef0\"">
|
||||
double14: @"-sNaN" <Test #x"83fff8000000000001" #xd"fff8000000000001">
|
||||
double15: @"-sNaN" <Test #x"83fff8000000000111" #xd"fff8000000000111">
|
||||
double16: @"+sNaN" <Test #x"837ff8000000000001" #xd"7ff8000000000001">
|
||||
double17: @"+sNaN" <Test #x"837ff8000000000111" #xd"7ff8000000000111">
|
||||
float0: <Test #x"8200000000" 0.0f>
|
||||
float+0: <Test #x"8200000000" +0.0f>
|
||||
float-0: <Test #x"8280000000" -0.0f>
|
||||
|
@ -100,11 +104,15 @@
|
|||
float5: @"Invalid chars" <ParseError "#xf\"12zz5678\"">
|
||||
float6: @"Positive infinity" <Test #x"827f800000" #xf"7f800000">
|
||||
float7: @"Negative infinity" <Test #x"82ff800000" #xf"ff800000">
|
||||
float8: @"+NaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+NaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-NaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-NaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float8: @"+sNaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+sNaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-sNaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-sNaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float12: @"Bad spacing" <ParseError "#xf\"12345 678\"">
|
||||
float13: @"+qNaN" <Test #x"827fc00001" #xf"7fc00001">
|
||||
float14: @"+qNaN" <Test #x"827fc00111" #xf"7fc00111">
|
||||
float15: @"-qNaN" <Test #x"82ffc00001" #xf"ffc00001">
|
||||
float16: @"-qNaN" <Test #x"82ffc00111" #xf"ffc00111">
|
||||
int-257: <Test #x"a1feff" -257>
|
||||
int-256: <Test #x"a1ff00" -256>
|
||||
int-255: <Test #x"a1ff01" -255>
|
||||
|
|
|
@ -143,7 +143,7 @@ so if a `SymbolOrNumber` also matches the grammar for `Float`, `Double` or
|
|||
`SignedInteger`, then it must be interpreted as one of those, and otherwise
|
||||
it must be interpreted as a bare `Symbol`.
|
||||
|
||||
SymbolOrNumber = *baresymchar
|
||||
SymbolOrNumber = 1*baresymchar
|
||||
baresymchar = ALPHA / DIGIT / sympunct / symuchar
|
||||
sympunct = "~" / "!" / "$" / "%" / "^" / "&" / "*" /
|
||||
"?" / "_" / "=" / "+" / "-" / "/" / "."
|
||||
|
@ -168,7 +168,6 @@ either.[^reading-and-writing-floats-accurately]
|
|||
Double = flt
|
||||
SignedInteger = int
|
||||
|
||||
digit1-9 = %x31-39
|
||||
nat = 1*DIGIT
|
||||
int = ["-"/"+"] nat
|
||||
frac = "." 1*DIGIT
|
||||
|
@ -292,5 +291,22 @@ The text syntax for `Boolean`s, `Symbol`s, and `ByteString`s is
|
|||
directly inspired by [Racket](https://racket-lang.org/)'s lexical
|
||||
syntax.
|
||||
|
||||
## Appendix. Regular expressions for bare symbols and numbers
|
||||
|
||||
When parsing, if a token matches both `SymbolOrNumber` and `Number`, it's a
|
||||
number; use `Float`, `Double` and `SignedInteger` to disambiguate. If it
|
||||
matches `SymbolOrNumber` but not `Number`, it's a "bare" `Symbol`.
|
||||
|
||||
SymbolOrNumber: ^[-a-zA-Z0-9~!$%^&*?_=+/.]+$
|
||||
Number: ^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))([fF]?))?$
|
||||
Float: ^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))[fF])$
|
||||
Double: ^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+)))$
|
||||
SignedInteger: ^([-+]?\d+)$
|
||||
|
||||
When printing, if a symbol matches both `SymbolOrNumber` and `Number` or
|
||||
neither `SymbolOrNumber` nor `Number`, it must be quoted (`|...|`). If it
|
||||
matches `SymbolOrNumber` but not `Number`, it may be printed as a "bare"
|
||||
`Symbol`.
|
||||
|
||||
<!-- Heading to visually offset the footnotes from the main document: -->
|
||||
## Notes
|
||||
|
|
Binary file not shown.
|
@ -85,11 +85,15 @@
|
|||
double6: @"Invalid chars" <ParseError "#xd\"12zz56789abcdef0\"">
|
||||
double7: @"Positive infinity" <Test #x"837ff0000000000000" #xd"7ff0000000000000">
|
||||
double8: @"Negative infinity" <Test #x"83fff0000000000000" #xd"fff0000000000000">
|
||||
double9: @"-NaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-NaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+NaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+NaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double9: @"-qNaN" <Test #x"83fff0000000000001" #xd"fff0000000000001">
|
||||
double10: @"-qNaN" <Test #x"83fff0000000000111" #xd"fff0000000000111">
|
||||
double11: @"+qNaN" <Test #x"837ff0000000000001" #xd"7ff0000000000001">
|
||||
double12: @"+qNaN" <Test #x"837ff0000000000111" #xd"7ff0000000000111">
|
||||
double13: @"Bad spacing" <ParseError "#xd\"12345 6789abcdef0\"">
|
||||
double14: @"-sNaN" <Test #x"83fff8000000000001" #xd"fff8000000000001">
|
||||
double15: @"-sNaN" <Test #x"83fff8000000000111" #xd"fff8000000000111">
|
||||
double16: @"+sNaN" <Test #x"837ff8000000000001" #xd"7ff8000000000001">
|
||||
double17: @"+sNaN" <Test #x"837ff8000000000111" #xd"7ff8000000000111">
|
||||
float0: <Test #x"8200000000" 0.0f>
|
||||
float+0: <Test #x"8200000000" +0.0f>
|
||||
float-0: <Test #x"8280000000" -0.0f>
|
||||
|
@ -100,11 +104,15 @@
|
|||
float5: @"Invalid chars" <ParseError "#xf\"12zz5678\"">
|
||||
float6: @"Positive infinity" <Test #x"827f800000" #xf"7f800000">
|
||||
float7: @"Negative infinity" <Test #x"82ff800000" #xf"ff800000">
|
||||
float8: @"+NaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+NaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-NaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-NaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float8: @"+sNaN" <Test #x"827f800001" #xf"7f800001">
|
||||
float9: @"+sNaN" <Test #x"827f800111" #xf"7f800111">
|
||||
float10: @"-sNaN" <Test #x"82ff800001" #xf"ff800001">
|
||||
float11: @"-sNaN" <Test #x"82ff800111" #xf"ff800111">
|
||||
float12: @"Bad spacing" <ParseError "#xf\"12345 678\"">
|
||||
float13: @"+qNaN" <Test #x"827fc00001" #xf"7fc00001">
|
||||
float14: @"+qNaN" <Test #x"827fc00111" #xf"7fc00111">
|
||||
float15: @"-qNaN" <Test #x"82ffc00001" #xf"ffc00001">
|
||||
float16: @"-qNaN" <Test #x"82ffc00111" #xf"ffc00111">
|
||||
int-257: <Test #x"a1feff" -257>
|
||||
int-256: <Test #x"a1ff00" -256>
|
||||
int-255: <Test #x"a1ff01" -255>
|
||||
|
|
Loading…
Reference in New Issue