@temperlang/std 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/temporal.js.map CHANGED
@@ -1 +1 @@
1
- { "version": 3, "file": "js/std/temporal.js", "sourceRoot": "std/", "sources": [ "temporal.temper.md", "json.temper.md" ], "sourcesContent": [ "# Temporal\n\nWe're creating an initial Date type to help with developing\nTemper's machinery to connect to existing Date types in\ntarget languages.\n\nSome facts about the Gregorian calendar.\n\n /** Indexed by the month number: 1 = January */\n let daysInMonth = [\n 0,\n /* January */ 31,\n /* February */ 28, // Special case leap days\n /* March */ 31,\n /* April */ 30,\n /* May */ 31,\n /* June */ 30,\n /* July */ 31,\n /* August */ 31,\n /* September */ 30,\n /* October */ 31,\n /* November */ 30,\n /* December */ 31,\n ];\n\n let isLeapYear(year: Int): Boolean {\n year % 4 == 0 \u0026\u0026 (year % 100 != 0 || year % 400 == 0)\n }\n\n let { StringBuilder } = import(\u0022./strings.temper.md\u0022);\n\n /**\n * If the decimal representation of \\|num\\| is longer than [minWidth],\n * then appends that representation.\n * Otherwise any sign for [num] followed by enough zeroes to bring the\n * whole length up to [minWidth].\n *\n * ```temper\n * // When the width is greater than the decimal's length,\n * // we pad to that width.\n * \u00220123\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(4, 123, sb);\n * sb.toString()\n * }\n *\n * // When the width is the same or lesser, we just use the string form.\n * \u0022123\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(2, 123, sb);\n * sb.toString()\n * }\n *\n * // The sign is always on the left.\n * \u0022-01\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(3, -1, sb);\n * sb.toString()\n * }\n * ```\n */\n let padTo(minWidth: Int, num: Int, sb: StringBuilder): Void {\n let decimal = num.toString(10);\n var decimalIndex = String.begin;\n let decimalEnd = decimal.end;\n if (decimal[decimalIndex] == char'-') {\n sb.append(\u0022-\u0022);\n decimalIndex = decimal.next(decimalIndex);\n }\n var nNeeded = minWidth - decimal.countBetween(decimalIndex, decimalEnd);\n while (nNeeded \u003e 0) {\n sb.append('0');\n nNeeded -= 1;\n }\n sb.appendBetween(decimal, decimalIndex, decimalEnd);\n }\n\n // Relates months (one-indexed) to numbers used in day-of-week\n // computations non-leapy.\n let dayOfWeekLookupTableLeapy: List\u003cInt\u003e = [\n 0, // Not a month\n 0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6,\n ];\n let dayOfWeekLookupTableNotLeapy: List\u003cInt\u003e = [\n 0, // Not a month\n 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5,\n ];\n\nHere's just enough of a Date type to get us started.\n\n /**\n * A Date identifies a day in the proleptic Gregorian calendar.\n * It is unconnected to a time of day or a timezone.\n */\n @json\n @connected(\u0022Date\u0022)\n export class Date {\n /** The year. 1900 means 1900. */\n @connected(\u0022Date::getYear\u0022)\n public year: Int;\n /** The month of the year in [1, 12]. */\n @connected(\u0022Date::getMonth\u0022)\n public month: Int;\n /**\n * The day of the month in [1, 31]\n * additionally constrained by the length of [month].\n */\n @connected(\u0022Date::getDay\u0022)\n public day: Int;\n\n @connected(\u0022Date::constructor\u0022)\n public constructor(year: Int, month: Int, day: Int): Void | Bubble {\n if (1 \u003c= month \u0026\u0026 month \u003c= 12 \u0026\u0026\n 1 \u003c= day \u0026\u0026 (\n (month != 2 || day != 29)\n ? day \u003c= daysInMonth[month]\n : isLeapYear(year))) {\n this.year = year;\n this.month = month;\n this.day = day;\n } else {\n bubble();\n }\n }\n\n /** An ISO 8601 Date string with dashes like \u00222000-12-31\u0022. */\n @connected(\u0022Date::toString\u0022)\n public toString(): String {\n let sb = new StringBuilder();\n padTo(4, year, sb);\n sb.append(\u0022-\u0022);\n padTo(2, month, sb);\n sb.append(\u0022-\u0022);\n padTo(2, day, sb);\n return sb.toString();\n }\n\n /** Parses a Date from an ISO 8601 Date string with dashes like \u00222000-12-21\u0022. */\n @connected(\u0022Date::fromIsoString\u0022)\n public static fromIsoString(isoString: String): Date | Bubble {\n let end = isoString.end;\n var strIndex = isoString.prev(isoString.prev(end));\n // strIndex at '^'\n // YYYY-MM-^DD\n let beforeDay = strIndex;\n strIndex = isoString.prev(strIndex);\n // YYYY-MM^-DD\n let afterMonth = strIndex;\n if (!isoString.hasIndex(afterMonth) || isoString[strIndex] != char'-') {\n bubble();\n }\n strIndex = isoString.prev(isoString.prev(strIndex));\n // YYYY-^MM-DD\n let beforeMonth = strIndex;\n strIndex = isoString.prev(strIndex);\n // YYYY^-MM-DD\n if (isoString[strIndex] != char'-' ||\n !isoString.hasAtLeast(String.begin, strIndex, 4)) {\n bubble();\n }\n let day = isoString.slice(beforeDay, end) .toInt(10);\n let month = isoString.slice(beforeMonth, afterMonth).toInt(10);\n let year = isoString.slice(String.begin, strIndex) .toInt(10);\n return new Date(year, month, day);\n }\n\n /**\n * The count of whole years between the two dates.\n *\n * Think of this as floor of the magnitude of a range:\n *\n * ⌊ [start, end] ⌋\n *\n * If you think of it as subtraction, you have to reverse\n * the order of arguments.\n *\n * ⌊ end - start ⌋, NOT ⌊ start - end ⌋\n *\n * \u0022Whole year\u0022 is based on month/day calculations, not\n * day-of-year. This means that there is one full year\n * between 2020-03-01 and 2021-03-01 even though, because\n * February of 2020 has 29 days, 2020-03-01 is the 61st\n * day of 2020 but 2021-03-01 is only the 60th day of\n * that year.\n */\n @connected(\u0022Date::yearsBetween\u0022)\n public static let yearsBetween(start: Date, end: Date): Int {\n let yearDelta = end.year - start.year;\n let monthDelta = end.month - start.month;\n yearDelta - (\n // If the end month/day is before the start's then we\n // don't have a full year.\n (monthDelta \u003c 0 || monthDelta == 0 \u0026\u0026 end.day \u003c start.day)\n ? 1 : 0)\n }\n\n /** Today's date in UTC */\n // TODO: take a zone\n @connected(\u0022Date::today\u0022)\n public static let today(): Date;\n\n /**\n * ISO 8601 weekday number.\n *\n * | Number | Weekday |\n * | ------ | -------- |\n * | 1 | Monday |\n * | 2 | Tuesday |\n * | 3 | Monday |\n * | 4 | Thursday |\n * | 5 | Friday |\n * | 6 | Saturday |\n * | 7 | Sunday |\n */\n @connected(\u0022Date::getDayOfWeek\u0022)\n public get dayOfWeek(): Int {\n // Gauss's method.\n let y = year;\n let c = if (y \u003e= 0) { y / 100 } else { -(-y / 100) };\n let yy = y - (c * 100);\n // See note below about avoiding negative modulus to see why\n // some of the offsets differ from Wikipedia's rendering of\n // Gauss's formula.\n let janFirst = (8 + 5*((yy + 3) % 4) + 3*(yy - 1) + 5*(c % 4)) % 7;\n let table = if (isLeapYear(y)) {\n dayOfWeekLookupTableLeapy\n } else {\n dayOfWeekLookupTableNotLeapy\n };\n let monthOffset = table[month];\n // Gauss's method produces a number in 0..6 but\n // ISO assigns 1..7 where all values are the same\n // except that Sunday is 7 instead of 0.\n // Below we do (day + 6) since that is equivalent to\n // (day - 1) where we end up % 7 but avoids any chance\n // of a negative left operand to `%`.\n let gaussWeekday = (janFirst + (day + 6) + monthOffset) % 7;\n gaussWeekday == 0 ? 7 : gaussWeekday\n }\n\n public encodeToJson(p: JsonProducer): Void {\n p.stringValue(toString());\n }\n\n public static decodeFromJson(\n t: JsonSyntaxTree,\n ic: InterchangeContext,\n ): Date | Bubble {\n fromIsoString(t.as\u003cJsonString\u003e().content)\n }\n };\n\nDates marshal to and from JSON via the ISO string form.\n\n let {\n InterchangeContext,\n JsonProducer,\n JsonString,\n JsonSyntaxTree\n } = import(\u0022./json\u0022);\n\nTODO: an auto-balancing Date builder.\nOther temporal values\nDay of week\n", "# JSON support\n\nThe `@json` decorator for Temper classes supports converting values to\nand from the JSON data interchange format.\n\nThis module provides JSON value representations to support\nunmarshalling, converting a JSON text to domain value(s), and it\nprovides a JSON builder to support marshalling domain values to JSON.\n\n## Standard\n\nFor the purposes of this document, \u0022JSON\u0022 is defined by [ECMA-404],\n\u0022*The JSON data interchange syntax*.\u0022\n\n## Context\n\nProducers and consumers may need to engage in [content negotiation].\n\nFor example, a marshaller might benefit from having access to a version\nheader from a Web API request to distinguish clients that can accept the\nlatest version from those that need a deprecated version.\n\n export interface InterchangeContext {\n public getHeader(headerName: String): String | Null;\n }\n\nAnd for convenience, here's a blank interchange context.\n\n export class NullInterchangeContext extends InterchangeContext {\n public getHeader(headerName: String): String | Null { null }\n\n public static instance = new NullInterchangeContext();\n }\n\n## Marshalling support\n\n export interface JsonProducer {\n public interchangeContext: InterchangeContext;\n\n // A JSON object value is specified by a start-object\n // event, zero or more property key/value pairs\n // (see below) and an end-object event.\n\n public startObject(): Void;\n public endObject(): Void;\n\n // Within the start and end of an object\n // you may have zero or more pairs of\n // a property key followed by a nested value.\n\n public objectKey(key: String): Void;\n\n // To emit an array, start it, emit\n // zero or more nested values, then end it.\n\n public startArray(): Void;\n public endArray(): Void;\n\n // Emitters for simple values\n\n public nullValue(): Void;\n public booleanValue(x: Boolean): Void;\n\n // Numeric values come in several flavours\n\n public intValue(x: Int): Void;\n public float64Value(x: Float64): Void;\n /**\n * A number that fits the JSON number grammar to allow\n * interchange of numbers that are not easily represntible\n * using numeric types that Temper connects to.\n */\n public numericTokenValue(x: String): Void;\n\n public stringValue(x: String): Void;\n }\n\nThis API does allow for specifying malformed JSON outputs as below.\nImplementations must allow for such specifications until allowing\nfor observation of the produced output.\nA streaming JSON implementation may treat flushing a portion of\noutput to an output channel as an observation and expose an error\nthen.\n\nAn implementation may choose to treat a number of top-level values\nother than one (`[] []`) as well-formed or malformed, for example to\nsupport related interchange formats like [JSON Lines].\n\nAn implementation may use a variety of strategies to represent\ntraditionally unrepresentible values like special floating point\n*NaN* (not a number) and ±infinities.\n\nAn implementation may use a variety of strategies to deal with\na valid JSON string value (whether used as a property key or a value)\nthat is not a [scalar value string], for example, because it contains a\nUTF-16 surrogate that is not part of a well-formed surrogate pair.\n\n```temper inert\n//// Malformed object\nmyJsonProducer1.startObject();\n// no end for object or array\n\n// misisng start for object or array\nmyJsonProducer2.endObject();\n\n// mismatch start and end\nmyJsonProducer3.startArray();\nmyJsonProducer3.endObject();\n\n// malformed properties\nmyJsonProducer4.startObject();\n// missing key\nmyJsonProducer4.nullValue();\nmyJsonProducer4.endObject();\n\nmyJsonProducer5.startObject();\nmyJsonProducer5.objectKey(\u0022k\u0022);\n// missing value\nmyJsonProducer5.endObject();\n\n//// Malformed values\n// questionable numbers\nmyJsonProducer6.float64Value(NaN);\nmyJsonProducer7.float64Value(Infinity);\nmyJsonProducer8.numericTokenValue(\u00221.\u0022);\n// questionable strings\nmyJsonProducer9.stringValue(\u0022\\uD800\u0022);\n\n//// No unique top-level value (see MAY above)\nmyJsonProducer10.nullValue();\nmyJsonProducer11.nullValue();\n```\n\n## Unmarshalling support\n\nUnmarshalling involves turning JSON source texts (which specify state)\ninto domain values (with state and behaviour).\n\nIt is convenient to have a syntax tree for JSON. Often, unmarshalling\ninvolves looking at select object properties to figure out how to interpret\nthe whole, by trying rules like the below:\n\n- if it has keys `x` and `y`, delegate to the *Point2d* unmarshaller,\n- or if it has keys `radius` and `centroid`, delegate to the *Circle* unmarshaller,\n- ...\n\nThere may not be an order of keys that makes it easy to consume a stream of\nevents to implement those rules, and expecting all producers to use the same\nkey order would lead to brittle unmarshalling.\n\n export sealed interface JsonSyntaxTree {\n public produce(p: JsonProducer): Void;\n }\n\n export class JsonObject(\n public properties: Map\u003cString, List\u003cJsonSyntaxTree\u003e\u003e,\n ) extends JsonSyntaxTree {\n\n /**\n * The JSON value tree associated with the given property key or null\n * if there is no such value.\n *\n * The properties map contains a list of sub-trees because JSON\n * allows duplicate properties. ECMA-404 §6 notes (emphasis added):\n *\n * \u003e The JSON syntax does not impose any restrictions on the strings\n * \u003e used as names, **does not require that name strings be unique**,\n * \u003e and does not assign any significance to the ordering of\n * \u003e name/value pairs.\n *\n * When widely used JSON parsers need to relate a property key\n * to a single value, they tend to prefer the last key/value pair\n * from a JSON object. For example:\n *\n * JS:\n *\n * JSON.parse('{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}').x === 'last'\n *\n * Python:\n *\n * import json\n * json.loads('{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}')['x'] == 'last'\n *\n * C#:\n *\n * using System.Text.Json;\n * \t\tJsonDocument d = JsonDocument.Parse(\n * \t\t\t\u0022\u0022\u0022\n * \t\t\t{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}\n * \t\t\t\u0022\u0022\u0022\n * \t\t);\n * \t\td.RootElement.GetProperty(\u0022x\u0022).GetString() == \u0022last\u0022\n */\n public propertyValueOrNull(propertyKey: String): JsonSyntaxTree | Null {\n let treeList = this.properties.getOr(propertyKey, []);\n\n let lastIndex = treeList.length - 1;\n if (lastIndex \u003e= 0) {\n treeList[lastIndex]\n } else {\n null\n }\n }\n\n public propertyValueOrBubble(propertyKey: String): JsonSyntaxTree | Bubble {\n propertyValueOrNull(propertyKey).as\u003cJsonSyntaxTree\u003e()\n }\n\n public produce(p: JsonProducer): Void {\n p.startObject();\n properties.forEach { (k: String, vs: List\u003cJsonSyntaxTree\u003e);;\n vs.forEach { (v: JsonSyntaxTree);;\n p.objectKey(k);\n v.produce(p);\n }\n }\n p.endObject();\n }\n }\n\n export class JsonArray(\n public elements: List\u003cJsonSyntaxTree\u003e,\n ) extends JsonSyntaxTree {\n\n public produce(p: JsonProducer): Void {\n p.startArray();\n elements.forEach { (v: JsonSyntaxTree);;\n v.produce(p);\n }\n p.endArray();\n }\n }\n\n export class JsonBoolean(\n public content: Boolean,\n ) extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.booleanValue(content);\n }\n }\n\n export class JsonNull extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.nullValue();\n }\n }\n\n export class JsonString(\n public content: String,\n ) extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.stringValue(content);\n }\n }\n\n export sealed interface JsonNumeric extends JsonSyntaxTree {\n public asJsonNumericToken(): String;\n\n public asInt(): Int | Bubble;\n public asFloat64(): Float64 | Bubble;\n }\n\n export class JsonInt(\n public content: Int,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.intValue(content);\n }\n\n public asJsonNumericToken(): String {\n content.toString()\n }\n\n public asInt(): Int { content }\n\n public asFloat64(): Float64 | Bubble { content.toFloat64() }\n }\n\n export class JsonFloat64(\n public content: Float64,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.float64Value(content);\n }\n\n public asJsonNumericToken(): String {\n content.toString()\n }\n\n public asInt(): Int | Bubble { content.toInt() }\n\n public asFloat64(): Float64 { content }\n }\n\n export class JsonNumericToken(\n public content: String,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.numericTokenValue(content);\n }\n\n public asJsonNumericToken(): String {\n content\n }\n\n public asInt(): Int | Bubble {\n content.toInt() orelse content.toFloat64().toInt()\n }\n\n public asFloat64(): Float64 | Bubble { content.toFloat64() }\n }\n\nThe *produce* method allows a JSON syntax tree to describe itself to a\nJSON producer so JSON syntax trees, while useful during unmarshalling\ncan also help with marshalling.\n\n## Producing JSON text\n\nA JSON producer that appends to an internal buffer lets us produce\nJSON source text.\n\n // A state machine lets us figure out when to insert commas.\n let JSON_STATE_OPEN_OBJECT = 0; // Last was \u0022{\u0022\n let JSON_STATE_AFTER_KEY = 1; // Last was property key\n let JSON_STATE_AFTER_PROPERTY = 2; // Last was property value\n let JSON_STATE_OPEN_ARRAY = 3; // Last was \u0022[\u0022\n let JSON_STATE_AFTER_ELEMENT = 4; // Last was array element\n let JSON_STATE_NO_VALUE = 5;\n let JSON_STATE_ONE_VALUE = 6;\n\n export class JsonTextProducer extends JsonProducer {\n public interchangeContext: InterchangeContext;\n private buffer: StringBuilder;\n private stack: ListBuilder\u003cInt\u003e;\n private var wellFormed: Boolean;\n\n public constructor(\n interchangeContext: InterchangeContext = NullInterchangeContext.instance\n ): Void {\n this.interchangeContext = interchangeContext;\n this.buffer = new StringBuilder();\n this.stack = new ListBuilder\u003cInt\u003e();\n this.stack.add(JSON_STATE_NO_VALUE);\n this.wellFormed = true;\n }\n\n private state(): Int {\n stack.getOr(stack.length - 1, -1)\n }\n\n private beforeValue(): Void {\n let currentState = state();\n when (currentState) {\n JSON_STATE_OPEN_ARRAY -\u003e\n stack[stack.length - 1] = JSON_STATE_AFTER_ELEMENT;\n JSON_STATE_AFTER_ELEMENT -\u003e buffer.append(\u0022,\u0022);\n JSON_STATE_AFTER_KEY -\u003e\n stack[stack.length - 1] = JSON_STATE_AFTER_PROPERTY;\n JSON_STATE_NO_VALUE -\u003e\n stack[stack.length - 1] = JSON_STATE_ONE_VALUE;\n JSON_STATE_ONE_VALUE, JSON_STATE_AFTER_PROPERTY -\u003e\n wellFormed = false;\n }\n }\n\n public startObject(): Void {\n beforeValue();\n buffer.append(\u0022{\u0022);\n stack.add(JSON_STATE_OPEN_OBJECT);\n }\n\n public endObject(): Void {\n buffer.append(\u0022}\u0022);\n let currentState = state();\n if (JSON_STATE_OPEN_OBJECT == currentState ||\n JSON_STATE_AFTER_PROPERTY == currentState) {\n stack.removeLast();\n } else {\n wellFormed = false;\n }\n }\n\n public objectKey(key: String): Void {\n let currentState = state();\n when (currentState) {\n JSON_STATE_OPEN_OBJECT -\u003e void;\n JSON_STATE_AFTER_PROPERTY -\u003e buffer.append(\u0022,\u0022);\n else -\u003e wellFormed = false;\n }\n encodeJsonString(key, buffer);\n buffer.append(\u0022:\u0022);\n if (currentState \u003e= 0) {\n stack[stack.length - 1] = JSON_STATE_AFTER_KEY;\n }\n }\n\n public startArray(): Void {\n beforeValue();\n buffer.append(\u0022[\u0022);\n stack.add(JSON_STATE_OPEN_ARRAY);\n }\n\n public endArray(): Void {\n buffer.append(\u0022]\u0022);\n let currentState = state();\n if (JSON_STATE_OPEN_ARRAY == currentState ||\n JSON_STATE_AFTER_ELEMENT == currentState) {\n stack.removeLast();\n } else {\n wellFormed = false;\n }\n }\n\n public nullValue(): Void {\n beforeValue();\n buffer.append(\u0022null\u0022);\n }\n\n public booleanValue(x: Boolean): Void {\n beforeValue();\n buffer.append(x ? \u0022true\u0022 : \u0022false\u0022);\n }\n\n public intValue(x: Int): Void {\n beforeValue();\n buffer.append(x.toString());\n }\n\n public float64Value(x: Float64): Void {\n beforeValue();\n buffer.append(x.toString());\n }\n\n public numericTokenValue(x: String): Void {\n // TODO check syntax of x and maybe set malformed\n beforeValue();\n buffer.append(x);\n }\n\n public stringValue(x: String): Void {\n beforeValue();\n encodeJsonString(x, buffer);\n }\n\n public toJsonString(): String | Bubble {\n if (wellFormed \u0026\u0026 stack.length == 1 \u0026\u0026 state() == JSON_STATE_ONE_VALUE) {\n return buffer.toString();\n } else {\n bubble();\n }\n }\n }\n\n let encodeJsonString(x: String, buffer: StringBuilder): Void {\n buffer.append(\u0022\\\u0022\u0022);\n var i = String.begin;\n var emitted = i;\n while (x.hasIndex(i)) {\n // The choice of which code-points to escape and which\n // escape sequences to use is the same as those made in\n // ES262 § 25.5.2.3: QuoteJSONString\n // as long as the string has no orphaned surrogates.\n // This means that the output will work in many parsers\n // while still allowing use of JSON.stringify in JavaScript\n // where keeping code-size low by not shipping a JSON encoder\n // is important.\n\n let cp = x[i];\n let replacement = when (cp) {\n char'\\b' -\u003e \u0022\\\\b\u0022;\n char'\\t' -\u003e \u0022\\\\t\u0022;\n char'\\n' -\u003e \u0022\\\\n\u0022;\n char'\\f' -\u003e \u0022\\\\f\u0022;\n char'\\r' -\u003e \u0022\\\\r\u0022;\n char'\u0022' -\u003e \u0022\\\\\\\u0022\u0022;\n char'\\\\' -\u003e \u0022\\\\\\\\\u0022;\n else -\u003e\n // Control characters and non-USV code-points.\n if (cp \u003c 0x20 || 0xD800 \u003c= cp \u0026\u0026 cp \u003c= 0xDFFF) {\n \u0022\\\\u\u0022\n } else {\n \u0022\u0022\n };\n };\n\n let nextI = x.next(i);\n\n if (replacement != \u0022\u0022) {\n buffer.appendBetween(x, emitted, i);\n buffer.append(replacement);\n if (replacement == \u0022\\\\u\u0022) {\n encodeHex4(cp, buffer);\n }\n emitted = nextI;\n }\n\n i = nextI;\n }\n buffer.appendBetween(x, emitted, i);\n buffer.append(\u0022\\\u0022\u0022);\n }\n\n let hexDigits = [\n '0', '1', '2', '3', '4', '5', '6', '7',\n '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',\n ];\n\n let encodeHex4(cp: Int, buffer: StringBuilder): Void {\n let b0 = (cp / 0x1000) \u0026 0xf;\n let b1 = (cp / 0x100) \u0026 0xf;\n let b2 = (cp / 0x10) \u0026 0xf;\n let b3 = cp \u0026 0xf;\n buffer.append(hexDigits[b0]);\n buffer.append(hexDigits[b1]);\n buffer.append(hexDigits[b2]);\n buffer.append(hexDigits[b3]);\n }\n\n## Parsing JSON\n\nJSON tokens, like `{` and `\u0022foo\u0022` correspond fairly closely to events\nlike *JsonProducer.startObject* and *JsonProducer.stringValue* respectively.\n\n*JsonSyntaxTree* knows how to describe itself to a *JsonProducer*, but we can\nalso craft a *JsonProducer* that constructs a syntax tree.\n\nFirst, we need a way to explain syntax errors, ideally in a way that lets\na *JsonProducer* represent both the valid JSON or the error.\n\n export interface JsonParseErrorReceiver {\n public explainJsonError(explanation: String): Void;\n }\n\nNow, we are ready to build a *JsonProducer* that produces a syntax tree given\nvalid JSON, but if given a string that is not valid JSON, it has a syntax error.\n\n export class JsonSyntaxTreeProducer extends JsonProducer \u0026 JsonParseErrorReceiver {\n private stack: ListBuilder\u003cListBuilder\u003cJsonSyntaxTree\u003e\u003e;\n private var error: String | Null;\n public get interchangeContext(): InterchangeContext {\n NullInterchangeContext.instance\n }\n\n public constructor() {\n stack = new ListBuilder\u003cListBuilder\u003cJsonSyntaxTree\u003e\u003e();\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n error = null;\n }\n\n private storeValue(v: JsonSyntaxTree): Void {\n if (!stack.isEmpty) {\n stack[stack.length - 1].add(v);\n }\n }\n\n public startObject(): Void {\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n }\n\n public endObject(): Void {\n if (stack.isEmpty) {\n return;\n }\n let ls = stack.removeLast();\n // In the common case, there are no duplicate keys.\n let m = new MapBuilder\u003cString, List\u003cJsonSyntaxTree\u003e\u003e();\n // But we need a way to accumulate them when there are duplicate keys.\n var multis: MapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e | Null = null;\n for (var i = 0, n = ls.length \u0026 -2; i \u003c n;) {\n let keyTree = ls[i++];\n if (!keyTree.is\u003cJsonString\u003e()) { break }\n let key = keyTree.as\u003cJsonString\u003e().content;\n let value = ls[i++];\n\n if (m.has(key)) {\n if (multis == null) {\n multis = new MapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e();\n }\n let mb = multis.as\u003cMapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e\u003e();\n if (!mb.has(key)) {\n mb[key] = m[key].toListBuilder();\n }\n mb[key].add(value);\n } else {\n m[key] = [value];\n }\n }\n\n let multis = multis; // lock it in down here for inference\n if (multis != null) {\n multis.forEach { (k: String, vs: ListBuilder\u003cJsonSyntaxTree\u003e);;\n m[k] = vs.toList();\n }\n }\n\n storeValue(new JsonObject(m.toMap()));\n }\n\n public objectKey(key: String): Void {\n storeValue(new JsonString(key));\n }\n\n public startArray(): Void {\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n }\n\n public endArray(): Void {\n if (stack.isEmpty) {\n return;\n }\n let ls = stack.removeLast();\n storeValue(new JsonArray(ls.toList()));\n }\n\n public nullValue(): Void {\n storeValue(new JsonNull());\n }\n\n public booleanValue(x: Boolean): Void {\n storeValue(new JsonBoolean(x));\n }\n\n public intValue(x: Int): Void {\n storeValue(new JsonInt(x));\n }\n\n public float64Value(x: Float64): Void {\n storeValue(new JsonFloat64(x));\n }\n\n public numericTokenValue(x: String): Void {\n storeValue(new JsonNumericToken(x));\n }\n\n public stringValue(x: String): Void {\n storeValue(new JsonString(x));\n }\n\n public toJsonSyntaxTree(): JsonSyntaxTree | Bubble {\n if (stack.length != 1 || error != null) { bubble() }\n let ls = stack[0];\n if (ls.length != 1) { bubble() }\n ls[0]\n }\n\n public get jsonError(): String | Null { error }\n\n public explainJsonError(error: String): Void {\n this.error = error;\n }\n }\n\nSome helpers let us route errors:\n\n let expectedTokenError(\n sourceText: String,\n i: StringIndex,\n out: JsonProducer,\n shortExplanation: String,\n ): Void {\n let gotten = if (sourceText.hasIndex(i)) {\n \u0022`\u0024{ sourceText.slice(i, sourceText.end) }`\u0022\n } else {\n \u0022end-of-file\u0022\n };\n storeJsonError(out, \u0022Expected \u0024{shortExplanation}, but got \u0024{gotten}\u0022);\n }\n\n let storeJsonError(out: JsonProducer, explanation: String): Void {\n if (out.is\u003cJsonParseErrorReceiver\u003e()) {\n let errorReceiver = out.as\u003cJsonParseErrorReceiver\u003e();\n errorReceiver.explainJsonError(explanation);\n }\n }\n\nNext, a JSON string parser that drives a *JsonProducer*.\n\n export let parseJsonToProducer(sourceText: String, out: JsonProducer): Void {\n var i = String.begin;\n let afterValue = parseJsonValue(sourceText, i, out);\n if (afterValue.is\u003cStringIndex\u003e()) { // else parseJsonValue must have stored an error\n i = skipJsonSpaces(sourceText, afterValue.as\u003cStringIndex\u003e());\n if (sourceText.hasIndex(i) \u0026\u0026 out.is\u003cJsonParseErrorReceiver\u003e()) {\n storeJsonError(out, \u0022Extraneous JSON `\u0024{sourceText.slice(i, sourceText.end)}`\u0022);\n }\n }\n }\n\nA recursive descent parser without backtracking works just fine for JSON because the\nnext non-space character perfectly predicts the next production.\nAll `// \u003e ` comments are followed by quotes from ECMA-404 2nd edition.\nEach parsing helper takes the *StringIndex* before parsing and returns\nthe *StringIndex* at the end of the content it parsed, or maybe *null* if parsing\nfailed.\n\n // \u003e Insignificant whitespace is allowed before or after any token.\n // \u003e Whitespace is any sequence of one or more of the following code\n // \u003e points:\n // \u003e character tabulation (U+0009),\n // \u003e line feed (U+000A),\n // \u003e carriage return (U+000D), and\n // \u003e space (U+0020).\n let skipJsonSpaces(sourceText: String, var i: StringIndex): StringIndex {\n while (sourceText.hasIndex(i)) {\n when (sourceText[i]) {\n 0x9, 0xA, 0xD, 0x20 -\u003e void;\n else -\u003e break;\n }\n i = sourceText.next(i);\n }\n return i;\n }\n\n let parseJsonValue(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n i = skipJsonSpaces(sourceText, i);\n if (!sourceText.hasIndex(i)) {\n expectedTokenError(sourceText, i, out, \u0022JSON value\u0022);\n return StringIndex.none\n }\n when (sourceText[i]) {\n char'{' -\u003e parseJsonObject(sourceText, i, out);\n char'[' -\u003e parseJsonArray(sourceText, i, out);\n char'\u0022' -\u003e parseJsonString(sourceText, i, out);\n char't', char'f' -\u003e parseJsonBoolean(sourceText, i, out);\n char'n' -\u003e parseJsonNull(sourceText, i, out);\n else -\u003e parseJsonNumber(sourceText, i, out);\n }\n }\n\nFor a JSON object, parsing looks for a '{', then optionally a property.\nAfter a property, if there is a ',' parsing looks for another property.\nAfter the last property or lone '{', there must be a '}'.\n\n let parseJsonObject(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'{') {\n expectedTokenError(sourceText, i, out, \u0022'{'\u0022);\n return StringIndex.none;\n }\n out.startObject();\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] != char'}') {\n while (true) {\n let afterKey = parseJsonString(sourceText, i, out);\n if (!afterKey.is\u003cStringIndex\u003e()) { return StringIndex.none; }\n i = skipJsonSpaces(sourceText, afterKey.as\u003cStringIndex\u003e());\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char':') {\n i = sourceText.next(i);\n let afterPropertyValue = parseJsonValue(sourceText, i, out);\n if (!afterPropertyValue.is\u003cStringIndex\u003e()) {\n return StringIndex.none;\n }\n i = afterPropertyValue.as\u003cStringIndex\u003e();\n } else {\n expectedTokenError(sourceText, i, out, \u0022':'\u0022);\n return StringIndex.none;\n }\n\n i = skipJsonSpaces(sourceText, i);\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char',') {\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n continue;\n } else {\n break;\n }\n }\n }\n\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char'}') {\n out.endObject();\n return sourceText.next(i);\n } else {\n expectedTokenError(sourceText, i, out, \u0022'}'\u0022);\n return StringIndex.none;\n }\n }\n\nFor a JSON array, parsing looks for a '[', and an optional element value.\nAfter each element value, if there is a ',', parsing looks for another element\nvalue. After the last element value or lone '[', there must be a ']'.\n\n let parseJsonArray(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'[') {\n expectedTokenError(sourceText, i, out, \u0022'['\u0022);\n return StringIndex.none;\n }\n out.startArray();\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] != char']') {\n while (true) {\n let afterElementValue = parseJsonValue(sourceText, i, out);\n if (!afterElementValue.is\u003cStringIndex\u003e()) {\n return StringIndex.none;\n }\n i = afterElementValue.as\u003cStringIndex\u003e();\n\n i = skipJsonSpaces(sourceText, i);\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char',') {\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n continue;\n } else {\n break;\n }\n }\n }\n\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char']') {\n out.endArray();\n return sourceText.next(i);\n } else {\n expectedTokenError(sourceText, i, out, \u0022']'\u0022);\n return StringIndex.none;\n }\n }\n\nA string consists of double-quotes with a select group of C-style escape sequences.\nThe characters that are allowed unescaped inside the quotes are any except ASCII\ncontrol characters.\n\n\u003e To escape a code point that is not in the Basic Multilingual Plane,\n\u003e the character may be represented as a twelve-character sequence,\n\u003e encoding the UTF-16 surrogate pair corresponding to the code point.\n\nThis suggests that when only one of a surrogate pair is escaped, that it is\ntreated as multiple code points. This implementation collapses them into\na single code-point for consistency with backends with native UTF-16 strings\nwhere such a distinction is not possible to represent.\n\n let parseJsonString(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'\u0022') {\n expectedTokenError(sourceText, i, out, '\u0022');\n return StringIndex.none;\n }\n i = sourceText.next(i);\n let sb = new StringBuilder();\n\n // Hold onto lead surrogates until we find out whether there's a trailing\n // surrogate or not.\n var leadSurrogate: Int = -1;\n\n var consumed = i;\n while (sourceText.hasIndex(i)) {\n let cp = sourceText[i];\n if (cp == char'\u0022') { break }\n var iNext = sourceText.next(i);\n let end = sourceText.end;\n\n // Emit anything between consumed and i if there\n // is a pending surrogate or escaped characters.\n var needToFlush = false;\n\n // Decode one code-point or UTF-16 surrogate\n var decodedCp = if (cp != char'\\\\') {\n cp\n } else {\n needToFlush = true;\n if (!sourceText.hasIndex(iNext)) {\n expectedTokenError(sourceText, iNext, out, \u0022escape sequence\u0022);\n return StringIndex.none;\n }\n let esc0 = sourceText[iNext];\n iNext = sourceText.next(iNext);\n when (esc0) {\n char'\u0022', char'\\\\', char'/' -\u003e esc0;\n char'b' -\u003e char'\\b';\n char'f' -\u003e char'\\f';\n char'n' -\u003e char'\\n';\n char'r' -\u003e char'\\r';\n char't' -\u003e char'\\t';\n char'u' -\u003e do {\n let hex: Int = if (sourceText.hasAtLeast(iNext, end, 4)) {\n let startHex = iNext;\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n decodeHexUnsigned(sourceText, startHex, iNext)\n } else {\n -1\n };\n if (hex \u003c 0) {\n expectedTokenError(sourceText, iNext, out, \u0022four hex digits\u0022);\n return StringIndex.none;\n }\n hex\n }\n else -\u003e do {\n expectedTokenError(sourceText, iNext, out, \u0022escape sequence\u0022);\n return StringIndex.none;\n }\n }\n };\n\n // If we have two surrogates, combine them into one code-point.\n // If we have a lead surrogate, make sure we can wait.\n if (leadSurrogate \u003e= 0) {\n needToFlush = true;\n let lead = leadSurrogate;\n if (0xDC00 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDFFF) {\n leadSurrogate = -1;\n decodedCp = (\n 0x10000 +\n (((lead - 0xD800) * 0x400) | (decodedCp - 0xDC00))\n );\n }\n } else if (0xD800 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDBFF) {\n needToFlush = true;\n }\n\n // Consume characters from sourceText onto sb if it's timely\n // to do so.\n if (needToFlush) {\n sb.appendBetween(sourceText, consumed, i);\n if (leadSurrogate \u003e= 0) {\n // Not combined with a trailing surrogate.\n sb.appendCodePoint(leadSurrogate);\n }\n if (0xD800 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDBFF) {\n leadSurrogate = decodedCp;\n } else {\n leadSurrogate = -1;\n sb.appendCodePoint(decodedCp);\n }\n consumed = iNext;\n }\n\n i = iNext;\n }\n\n if (!sourceText.hasIndex(i) || sourceText[i] != char'\u0022') {\n expectedTokenError(sourceText, i, out, '\u0022');\n StringIndex.none\n } else {\n if (leadSurrogate \u003e= 0) {\n sb.appendCodePoint(leadSurrogate);\n } else {\n sb.appendBetween(sourceText, consumed, i);\n }\n i = sourceText.next(i); // Consume closing quote\n\n out.stringValue(sb.toString());\n i\n }\n }\n\n let decodeHexUnsigned(\n sourceText: String, start: StringIndex, limit: StringIndex\n ): Int {\n var n = 0;\n var i = start;\n while (i.compareTo(limit) \u003c 0) {\n let cp = sourceText[i];\n let digit = if (char'0' \u003c= cp \u0026\u0026 cp \u003c= char'0') {\n cp - char'0'\n } else if (char'A' \u003c= cp \u0026\u0026 cp \u003c= char'F') {\n cp - char'A' + 10\n } else if (char'a' \u003c= cp \u0026\u0026 cp \u003c= char'f') {\n cp - char'a' + 10\n } else {\n return -1;\n }\n n = (n * 16) + digit;\n i = sourceText.next(i);\n }\n n\n }\n\n let parseJsonBoolean(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n let ch0 = if (sourceText.hasIndex(i)) {\n sourceText[i]\n } else {\n 0\n };\n let end = sourceText.end;\n\n let keyword: String | Null;\n let n: Int;\n when (ch0) {\n char'f' -\u003e do { keyword = \u0022false\u0022; n = 5 };\n char't' -\u003e do { keyword = \u0022true\u0022; n = 4 };\n else -\u003e do { keyword = null; n = 0 };\n }\n\n if (keyword != null \u0026\u0026 sourceText.hasAtLeast(i, end, n)) {\n let after = afterSubstring(sourceText, i, keyword.as\u003cString\u003e());\n if (after.is\u003cStringIndex\u003e()) {\n out.booleanValue(n == 4);\n return after;\n }\n }\n\n expectedTokenError(sourceText, i, out, \u0022`false` or `true`\u0022);\n return StringIndex.none;\n }\n\n let parseJsonNull(\n sourceText: String, i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n let after = afterSubstring(sourceText, i, \u0022null\u0022);\n\n if (after.is\u003cStringIndex\u003e()) {\n out.nullValue();\n return after.as\u003cStringIndex\u003e()\n }\n\n expectedTokenError(sourceText, i, out, \u0022`null`\u0022);\n return StringIndex.none;\n }\n\n let afterSubstring(\n string: String,\n inString: StringIndex,\n substring: String\n ): StringIndexOption {\n var i = inString;\n var j = String.begin;\n while (substring.hasIndex(j)) {\n if (!string.hasIndex(i)) {\n return StringIndex.none;\n }\n if (string[i] != substring[j]) {\n return StringIndex.none;\n }\n i = string.next(i);\n j = substring.next(j);\n }\n i\n }\n\nAs usual, the number grammar is the single largest sub-grammar.\nWe accumulate an integer portion separately from a decimal portion.\nIf either of those is past the common representability limits for *Int*\nor *Float64*, then we use the lossless numeric syntax tree variant.\n\n let parseJsonNumber(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n var isNegative = false;\n let startOfNumber = i;\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char'-') {\n isNegative = true;\n i = sourceText.next(i);\n }\n\n // Find the whole portion, the portion before any fraction\n // or exponent.\n // 0 | [1-9][0-9]*\n let digit0 = sourceText.hasIndex(i) ? sourceText[i] : -1;\n if (digit0 \u003c char'0' || char'9' \u003c digit0) {\n // parseJsonValue comes here for any unrecognized code-points\n let error = !isNegative \u0026\u0026 digit0 != char'.'\n ? \u0022JSON value\u0022\n : \u0022digit\u0022;\n expectedTokenError(sourceText, i, out, error);\n return StringIndex.none;\n }\n i = sourceText.next(i);\n var nDigits = 1;\n var tentativeValue = (digit0 - char'0').toFloat64();\n if (char'0' != digit0) {\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nDigits += 1;\n tentativeValue = tentativeValue * 10.0 +\n (possibleDigit - char'0').toFloat64();\n } else {\n break;\n }\n }\n }\n\n // optional fraction component\n // '.' [0-9]+\n var nDigitsAfterPoint = 0;\n if (sourceText.hasIndex(i) \u0026\u0026 char'.' == sourceText[i]) {\n i = sourceText.next(i);\n let afterPoint = i;\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nDigits += 1;\n nDigitsAfterPoint += 1;\n tentativeValue = tentativeValue * 10.0 +\n (possibleDigit - char'0').toFloat64();\n } else {\n break;\n }\n }\n if (i == afterPoint) {\n // ECMA-404 does not allow \u00220.\u0022 with no digit after the point.\n expectedTokenError(sourceText, i, out, \u0022digit\u0022);\n return StringIndex.none;\n }\n }\n\n // optional exponent\n // [eE] [+\\-]? [0-9]+\n var nExponentDigits = 0;\n if (sourceText.hasIndex(i) \u0026\u0026 char'e' == (sourceText[i] | 32)) {\n i = sourceText.next(i);\n if (!sourceText.hasIndex(i)) {\n expectedTokenError(sourceText, i, out, \u0022sign or digit\u0022);\n return StringIndex.none;\n }\n let afterE = sourceText[i];\n if (afterE == char'+' || afterE == char'-') {\n i = sourceText.next(i);\n }\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nExponentDigits += 1;\n } else {\n break;\n }\n }\n if (nExponentDigits == 0) {\n expectedTokenError(sourceText, i, out, \u0022exponent digit\u0022);\n return StringIndex.none;\n }\n }\n let afterExponent = i;\n\n if (nExponentDigits == 0 \u0026\u0026 nDigitsAfterPoint == 0) {\n // An integer literal.\n let value = isNegative ? -tentativeValue : tentativeValue;\n if (nDigits \u003c= 10 \u0026\u0026 -2147483648.0 \u003c= value \u0026\u0026 value \u003c= 2147483647.0) {\n // Guaranteed representible int value.\n out.intValue(value.toInt());\n return i;\n }\n }\n\n let numericTokenString = sourceText.slice(startOfNumber, i);\n var doubleValue = NaN;\n if (nExponentDigits != 0 || nDigitsAfterPoint != 0) {\n do {\n doubleValue = numericTokenString.toFloat64();\n } orelse do {\n // Fall back to numeric token below\n ;\n }\n }\n\n if (doubleValue != -Infinity \u0026\u0026 doubleValue != Infinity \u0026\u0026\n doubleValue != NaN) {\n out.float64Value(doubleValue);\n } else {\n out.numericTokenValue(numericTokenString);\n }\n return i;\n }\n\nAs a convenience, the *parseJson* helper just creates a tree producer,\ncalls the parser and gets the tree.\n\n export let parseJson(sourceText: String): JsonSyntaxTree | Bubble {\n let p = new JsonSyntaxTreeProducer();\n parseJsonToProducer(sourceText, p);\n // TODO: if there is a syntax error, produce it.\n p.toJsonSyntaxTree()\n }\n\n## Type adapters\n\nType adapters allow converting values of a type to and from JSON.\nSee the `@json` type decorator for details which makes sure that\nthe decorated type has a static method that gets an adapter for the\ntype.\n\n export interface JsonAdapter\u003cT\u003e {\n public encodeToJson(x: T, p: JsonProducer): Void;\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): T | Bubble;\n }\n\nOur intrinsic types, like *Boolean* need json adapters. Static extensions\nlet us make *Boolean.jsonAdapter()* work as if it were built in.\n\n class BooleanJsonAdapter extends JsonAdapter\u003cBoolean\u003e {\n public encodeToJson(x: Boolean, p: JsonProducer): Void {\n p.booleanValue(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Boolean | Bubble {\n t.as\u003cJsonBoolean\u003e().content\n }\n }\n\n @staticExtension(Boolean, \u0022jsonAdapter\u0022)\n export let booleanJsonAdapter(): JsonAdapter\u003cBoolean\u003e {\n new BooleanJsonAdapter()\n }\n\n class Float64JsonAdapter extends JsonAdapter\u003cFloat64\u003e {\n public encodeToJson(x: Float64, p: JsonProducer): Void {\n p.float64Value(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Float64 | Bubble {\n t.as\u003cJsonFloat64\u003e().content\n }\n }\n\n @staticExtension(Float64, \u0022jsonAdapter\u0022)\n export let float64JsonAdapter(): JsonAdapter\u003cFloat64\u003e {\n new Float64JsonAdapter()\n }\n\n class IntJsonAdapter extends JsonAdapter\u003cInt\u003e {\n public encodeToJson(x: Int, p: JsonProducer): Void {\n p.intValue(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Int | Bubble {\n t.as\u003cJsonInt\u003e().content\n }\n }\n\n @staticExtension(Int, \u0022jsonAdapter\u0022)\n export let intJsonAdapter(): JsonAdapter\u003cInt\u003e {\n new IntJsonAdapter()\n }\n\n class StringJsonAdapter extends JsonAdapter\u003cString\u003e {\n public encodeToJson(x: String, p: JsonProducer): Void {\n p.stringValue(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): String | Bubble {\n t.as\u003cJsonString\u003e().content\n }\n }\n\n @staticExtension(String, \u0022jsonAdapter\u0022)\n export let stringJsonAdapter(): JsonAdapter\u003cString\u003e {\n new StringJsonAdapter()\n }\n\n class ListJsonAdapter\u003cT\u003e(\n private adapterForT: JsonAdapter\u003cT\u003e,\n ) extends JsonAdapter\u003cList\u003cT\u003e\u003e {\n public encodeToJson(x: List\u003cT\u003e, p: JsonProducer): Void {\n p.startArray();\n for (let el of x) {\n adapterForT.encodeToJson(el, p);\n }\n p.endArray();\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): List\u003cT\u003e | Bubble {\n let b = new ListBuilder\u003cT\u003e();\n let elements = t.as\u003cJsonArray\u003e().elements;\n let n = elements.length;\n var i = 0;\n while (i \u003c n) {\n let el = elements[i];\n i += 1;\n b.add(adapterForT.decodeFromJson(el, ic));\n }\n b.toList()\n }\n }\n\n @staticExtension(List, \u0022jsonAdapter\u0022)\n export let listJsonAdapter\u003cT\u003e(adapterForT: JsonAdapter\u003cT\u003e): JsonAdapter\u003cList\u003cT\u003e\u003e {\n new ListJsonAdapter\u003cT\u003e(adapterForT)\n }\n\n export class OrNullJsonAdapter\u003cT\u003e(\n private adapterForT: JsonAdapter\u003cT\u003e,\n ) extends JsonAdapter\u003cT | Null\u003e {\n public encodeToJson(x: T | Null, p: JsonProducer): Void {\n if (x == null) {\n p.nullValue();\n } else {\n adapterForT.encodeToJson(x, p);\n }\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): T | Null | Bubble {\n if (t.is\u003cJsonNull\u003e()) {\n null\n } else {\n adapterForT.decodeFromJson(t, ic)\n }\n }\n }\n\n\n## Dependencies\n\nThis module needs *StringBuilder* to support constructing strings of JSON.\n\n let { StringBuilder } = import(\u0022./strings.temper.md\u0022);\n\n[EMCA-404]: https://ecma-international.org/publications-and-standards/standards/ecma-404/\n[content negotiation]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation\n[JSON Lines]: https://jsonlines.org/\n[scalar value string]: https://infra.spec.whatwg.org/#scalar-value-string\n" ], "names": [ "JsonProducer", "JsonSyntaxTree", "InterchangeContext", "JsonString", "DateJsonAdapter", "JsonAdapter", "x", "p", "encodeToJson", "t", "ic", "decodeFromJson", "this", "jsonAdapter", "daysInMonth", "isLeapYear", "year", "return", "padTo", "minWidth", "num", "sb", "decimal", "decimalIndex", "decimalEnd", "nNeeded", "dayOfWeekLookupTableLeapy", "dayOfWeekLookupTableNotLeapy" ], "mappings": "ADSI;ADAA;ADAA;ADqFK;ADAA,EDAAA,YDAA,IDAAA,gBDAA,EDAAC,cDAA,IDAAA,kBDAA,EDAAC,kBDAA,IDAAA,sBDAA,CDqKD,CDAAC,UDAA,IDAAA;ADAA,MDAA,ADrKC,aDqKD;AD1PJ;ADAA;ADAA,0BDAA;ADTJ,MDAAC,mBDAA,kBD8FS,CDAA,ADikCYC,eDAA,CDAA,AD/pCrB;ADAA,oFD8FS;AD9FT,cD8FS,CDAAC,KDAA,EDAAC,KDAA;ADAA,IDAAC,gBDAA,CDAAF,KDAA,EDAAC,KDAA;ADAA;ADAA,GDAA;AD9FT,8HD8FS;AD9FT,gBD8FS,CDAAE,KDAA,EDAAC,MDAA;ADAA,WDAAC,kBDAA,CDAAF,KDAA,EDAAC,MDAA;ADAA,GDAA;AD9FT;ADAA;ADAA;ADAA;ADAA,CD8FS;AD9FT,sEDyPO;ADTM,+EDEN;ADFM,SDAAF,gBDAY,ADAZ,CDAaI,QDAA,EDAAL,KDAe,CDElC,ADF0C;ADC3B,WDAU,EDAA,ADAV,CDAAK,QDAQ,CDAE,4BDAA;ADAxB,EDAAL,KDAC,CDAC,WDAW,CDAC,KDAU,CDAC;ADAA;ADAC;ADGd,oHDKb;ADLa,SDAAI,kBDAc,ADAd,CDCZF,KDAiB,CDCjB,CDAAC,MDAsB,CDGvB,ADFgB;ADCD,WDAkB,CDAA;ADAlB,SDAkB,ADAb,uBDAU,CDAA,ADAfD,KDAC,CDAI,CDAAN,cDAU,CDAG,CDAA;ADAlB,WDA0B,EDAA,ADA1B,MDAkB,CDAQ,QDAA;ADAxC,oDDAyC,CDAA,ADA3B,KDA0B,CDAC;ADAA,CDC1C;ADzPP,kDD8FS;AD9FT,SDAAU,eD8FS;ADAA,aDAAT,mBDAA;ADAA,CDAA;ADrFD,4BDAW;ADAf,KDAI,CDAAU,eDAW,EDAG,ODcjB,CDAA,ADdiB,MDcjB,CDAA,ADdiB,CDChB,CDAC,CDCe,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCnB;ADED,yDDEC;ADFD,QDAI,CDAAC,cDAU,ADAd,CDAeC,QDAS,CDEvB,ADFkC,EDAA;ADAR,MDAAC,UDAO;ADCM,WDAU,CDAA;ADA/C,MDAK,aDAC,CDAA,ADAND,QDAI,CDAG,EDAC,CDAA,ADAR,IDAa,ADAD,EDAC;ADAK,QDAK,aDAC,CDAA,ADANA,QDAI,CDAG,IDAG,CDAA,ADAV,IDAe,ADAD,EDAC;ADAA,MDAAC,UDAA;ADAA;ADAI,aDAU,ADAL,cDAC,CDAA,ADAND,QDAI,CDAG,IDAG,EDAA;ADAV,MDAAC,UDAA,EDAe,ADAf,MDAU,ADAV,IDAe,ADAD,EDAC;ADAA,KDAA;ADAlC;ADAmC,IDAAA,UDAA;ADAA,GDAA;ADCvD,QDAA,ADF0B,CDAAA,UDAA;ADE1B;ADkCD,8GDcC;ADdD,QDAI,CDAAC,SDAK,ADAT,CDAUC,YDAa,CDAE,CDAAC,ODAQ,CDAE,CDAAC,MDAiB,CDcnD,ADd2D;ADMzC,WDA0B,CDAA;ADL3C,ODAI,CDAAC,YDAO,EDAG,CDAAF,ODAG,CDAA,ADAH,QDAgB,CDAA,ADAH,EDAE,CDAC;ADC3B,KDAC,CDAAG,iBDAY,EDAG,EDAY;ADC/B,ODAI,CDAAC,eDAU,EDAG,CDAAF,YDAO,CDAA,ADAP,MDAW;ADCxB,YDAqB,CDAA;ADArB,UDAqB,ADAd,gBDAA,ADAPA,YDAO,CDAC,CDAAC,iBDAY,CDAC,CDAA;ADAzB,MDAI,MDAqB,ADArB,IDAgC,ADAP,GDAO;ADClC,IDAAF,MDAE,GDAQ,ODAI;ADCC,WDA0B,ADAlB,gBDAI,CDAA,ADAZC,YDAO,CDAM,CDAAC,iBDAY,CDAC,CDAA;ADAzC,IDAAA,iBDAY,ADAZ,EDAyC,ADA1B,MDA0B;ADC1C;ADCwB,YDA8C,EDAA,ADAtC,wBDAY,CDAA,ADApBD,YDAO,CDAc,CDAAC,iBDAY,CDAE,CDAAC,eDAU,CDAC,CDAA;ADApE,KDAC,CDAAC,YDAO,EDAG,CDAAN,YDAQ,ADAR,EDAW,ODA8C;ADCvE,SDAOM,YDAO,ADAP,EDAW,ADAD,EDAC;ADChB,IDAAJ,MDAE,GDAQ,ODAI;ADCd,IDAAI,YDAO,ADAP,EDAY,ADAZ,CDAAA,YDAO,EDAI,EDAC;ADCb;ADCD,EDAAJ,MDAE,GDAe,IDAAC,YDAO,WDAEC,iBDAY,CDAE,CDAAC,eDAU,CDAC;ADAA;ADAC;ADKvB,4BDAS;ADAxC,KDAI,CDAAE,8BDAyB,EDAc,ODG1C,CDAA,ADH0C,MDG1C,CDAA,ADH0C,CDCzC,CDAC,CDCD,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDCnC;ADCiC,4BDAS;ADA3C,KDAI,CDAAC,iCDA4B,EDAc,ODG7C,CDAA,ADH6C,MDG7C,CDAA,ADH6C,CDC5C,CDAC,CDCD,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDCnC" }
1
+ { "version": 3, "file": "js/std/temporal.js", "sourceRoot": "std/", "sources": [ "temporal/temporal.temper.md", "json/json.temper.md" ], "sourcesContent": [ "# Temporal\n\nWe're creating an initial Date type to help with developing\nTemper's machinery to connect to existing Date types in\ntarget languages.\n\nSome facts about the Gregorian calendar.\n\n /** Indexed by the month number: 1 = January */\n let daysInMonth = [\n 0,\n /* January */ 31,\n /* February */ 28, // Special case leap days\n /* March */ 31,\n /* April */ 30,\n /* May */ 31,\n /* June */ 30,\n /* July */ 31,\n /* August */ 31,\n /* September */ 30,\n /* October */ 31,\n /* November */ 30,\n /* December */ 31,\n ];\n\n let isLeapYear(year: Int): Boolean {\n year % 4 == 0 \u0026\u0026 (year % 100 != 0 || year % 400 == 0)\n }\n\n /**\n * If the decimal representation of \\|num\\| is longer than [minWidth],\n * then appends that representation.\n * Otherwise any sign for [num] followed by enough zeroes to bring the\n * whole length up to [minWidth].\n *\n * ```temper\n * // When the width is greater than the decimal's length,\n * // we pad to that width.\n * \u00220123\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(4, 123, sb);\n * sb.toString()\n * }\n *\n * // When the width is the same or lesser, we just use the string form.\n * \u0022123\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(2, 123, sb);\n * sb.toString()\n * }\n *\n * // The sign is always on the left.\n * \u0022-01\u0022 == do {\n * let sb = new StringBuilder();\n * padTo(3, -1, sb);\n * sb.toString()\n * }\n * ```\n */\n let padTo(minWidth: Int, num: Int, sb: StringBuilder): Void {\n let decimal = num.toString(10);\n var decimalIndex = String.begin;\n let decimalEnd = decimal.end;\n if (decimalIndex \u003c decimalEnd \u0026\u0026 decimal[decimalIndex] == char'-') {\n sb.append(\u0022-\u0022);\n decimalIndex = decimal.next(decimalIndex);\n }\n var nNeeded = minWidth - decimal.countBetween(decimalIndex, decimalEnd);\n while (nNeeded \u003e 0) {\n sb.append('0');\n nNeeded -= 1;\n }\n sb.appendBetween(decimal, decimalIndex, decimalEnd);\n }\n\n // Relates months (one-indexed) to numbers used in day-of-week\n // computations non-leapy.\n let dayOfWeekLookupTableLeapy: List\u003cInt\u003e = [\n 0, // Not a month\n 0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6,\n ];\n let dayOfWeekLookupTableNotLeapy: List\u003cInt\u003e = [\n 0, // Not a month\n 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5,\n ];\n\nHere's just enough of a Date type to get us started.\n\n /**\n * A Date identifies a day in the proleptic Gregorian calendar.\n * It is unconnected to a time of day or a timezone.\n */\n @json\n @connected(\u0022Date\u0022)\n export class Date {\n /** The year. 1900 means 1900. */\n @connected(\u0022Date::getYear\u0022)\n public year: Int;\n /** The month of the year in [1, 12]. */\n @connected(\u0022Date::getMonth\u0022)\n public month: Int;\n /**\n * The day of the month in [1, 31]\n * additionally constrained by the length of [month].\n */\n @connected(\u0022Date::getDay\u0022)\n public day: Int;\n\n @connected(\u0022Date::constructor\u0022)\n public constructor(year: Int, month: Int, day: Int): Void throws Bubble {\n if (1 \u003c= month \u0026\u0026 month \u003c= 12 \u0026\u0026\n 1 \u003c= day \u0026\u0026 (\n if (month != 2 || day != 29) {\n day \u003c= daysInMonth[month]\n } else {\n isLeapYear(year)\n })) {\n this.year = year;\n this.month = month;\n this.day = day;\n } else {\n bubble();\n }\n }\n\n /** An ISO 8601 Date string with dashes like \u00222000-12-31\u0022. */\n @connected(\u0022Date::toString\u0022)\n public toString(): String {\n let sb = new StringBuilder();\n padTo(4, year, sb);\n sb.append(\u0022-\u0022);\n padTo(2, month, sb);\n sb.append(\u0022-\u0022);\n padTo(2, day, sb);\n return sb.toString();\n }\n\n /** Parses a Date from an ISO 8601 Date string with dashes like \u00222000-12-21\u0022. */\n @connected(\u0022Date::fromIsoString\u0022)\n public static fromIsoString(isoString: String): Date throws Bubble {\n let end = isoString.end;\n var strIndex = isoString.prev(isoString.prev(end));\n // strIndex at '^'\n // YYYY-MM-^DD\n let beforeDay = strIndex;\n strIndex = isoString.prev(strIndex);\n // YYYY-MM^-DD\n let afterMonth = strIndex;\n if (!isoString.hasIndex(afterMonth) || isoString[strIndex] != char'-') {\n bubble();\n }\n strIndex = isoString.prev(isoString.prev(strIndex));\n // YYYY-^MM-DD\n let beforeMonth = strIndex;\n strIndex = isoString.prev(strIndex);\n // YYYY^-MM-DD\n if (isoString[strIndex] != char'-' ||\n !isoString.hasAtLeast(String.begin, strIndex, 4)) {\n bubble();\n }\n let day = isoString.slice(beforeDay, end) .toInt32(10);\n let month = isoString.slice(beforeMonth, afterMonth).toInt32(10);\n let year = isoString.slice(String.begin, strIndex) .toInt32(10);\n return new Date(year, month, day);\n }\n\n /**\n * The count of whole years between the two dates.\n *\n * Think of this as floor of the magnitude of a range:\n *\n * ⌊ [start, end] ⌋\n *\n * If you think of it as subtraction, you have to reverse\n * the order of arguments.\n *\n * ⌊ end - start ⌋, NOT ⌊ start - end ⌋\n *\n * \u0022Whole year\u0022 is based on month/day calculations, not\n * day-of-year. This means that there is one full year\n * between 2020-03-01 and 2021-03-01 even though, because\n * February of 2020 has 29 days, 2020-03-01 is the 61st\n * day of 2020 but 2021-03-01 is only the 60th day of\n * that year.\n */\n @connected(\u0022Date::yearsBetween\u0022)\n public static let yearsBetween(start: Date, end: Date): Int {\n let yearDelta = end.year - start.year;\n let monthDelta = end.month - start.month;\n yearDelta - (\n // If the end month/day is before the start's then we\n // don't have a full year.\n if (monthDelta \u003c 0 || monthDelta == 0 \u0026\u0026 end.day \u003c start.day) {\n 1\n } else {\n 0\n })\n }\n\n /** Today's date in UTC */\n // TODO: take a zone\n @connected(\u0022Date::today\u0022)\n public static let today(): Date;\n\n /**\n * ISO 8601 weekday number.\n *\n * | Number | Weekday |\n * | ------ | -------- |\n * | 1 | Monday |\n * | 2 | Tuesday |\n * | 3 | Monday |\n * | 4 | Thursday |\n * | 5 | Friday |\n * | 6 | Saturday |\n * | 7 | Sunday |\n */\n @connected(\u0022Date::getDayOfWeek\u0022)\n public get dayOfWeek(): Int {\n // Gauss's method.\n let y = year;\n let c = if (y \u003e= 0) { y / 100 } else { -(-y / 100) };\n let yy = y - (c * 100);\n // See note below about avoiding negative modulus to see why\n // some of the offsets differ from Wikipedia's rendering of\n // Gauss's formula.\n let janFirst = (8 + 5*((yy + 3) % 4) + 3*(yy - 1) + 5*(c % 4)) % 7;\n let table = if (isLeapYear(y)) {\n dayOfWeekLookupTableLeapy\n } else {\n dayOfWeekLookupTableNotLeapy\n };\n let monthOffset = table[month];\n // Gauss's method produces a number in 0..6 but\n // ISO assigns 1..7 where all values are the same\n // except that Sunday is 7 instead of 0.\n // Below we do (day + 6) since that is equivalent to\n // (day - 1) where we end up % 7 but avoids any chance\n // of a negative left operand to `%`.\n let gaussWeekday = (janFirst + (day + 6) + monthOffset) % 7;\n if (gaussWeekday == 0) { 7 } else { gaussWeekday }\n }\n\n public encodeToJson(p: JsonProducer): Void {\n p.stringValue(toString());\n }\n\n public static decodeFromJson(\n t: JsonSyntaxTree,\n ic: InterchangeContext,\n ): Date throws Bubble {\n Date.fromIsoString((t as JsonString).content)\n }\n };\n\nDates marshal to and from JSON via the ISO string form.\n\n let {\n InterchangeContext,\n JsonProducer,\n JsonString,\n JsonSyntaxTree\n } = import(\u0022../json\u0022);\n\nTODO: an auto-balancing Date builder.\nOther temporal values\nDay of week\n", "# JSON support\n\nThe `@json` decorator for Temper classes supports converting values to\nand from the JSON data interchange format.\n\nThis module provides JSON value representations to support\nunmarshalling, converting a JSON text to domain value(s), and it\nprovides a JSON builder to support marshalling domain values to JSON.\n\n## Standard\n\nFor the purposes of this document, \u0022JSON\u0022 is defined by [ECMA-404],\n\u0022*The JSON data interchange syntax*.\u0022\n\n## Context\n\nProducers and consumers may need to engage in [content negotiation].\n\nFor example, a marshaller might benefit from having access to a version\nheader from a Web API request to distinguish clients that can accept the\nlatest version from those that need a deprecated version.\n\n export interface InterchangeContext {\n public getHeader(headerName: String): String?;\n }\n\nAnd for convenience, here's a blank interchange context.\n\n export class NullInterchangeContext extends InterchangeContext {\n public getHeader(headerName: String): String? { null }\n\n public static instance = new NullInterchangeContext();\n }\n\n## Marshalling support\n\n export interface JsonProducer {\n public interchangeContext: InterchangeContext;\n\n // A JSON object value is specified by a start-object\n // event, zero or more property key/value pairs\n // (see below) and an end-object event.\n\n public startObject(): Void;\n public endObject(): Void;\n\n // Within the start and end of an object\n // you may have zero or more pairs of\n // a property key followed by a nested value.\n\n public objectKey(key: String): Void;\n\n // To emit an array, start it, emit\n // zero or more nested values, then end it.\n\n public startArray(): Void;\n public endArray(): Void;\n\n // Emitters for simple values\n\n public nullValue(): Void;\n public booleanValue(x: Boolean): Void;\n\n // Numeric values come in several flavours\n\n public int32Value(x: Int): Void;\n public int64Value(x: Int64): Void;\n public float64Value(x: Float64): Void;\n /**\n * A number that fits the JSON number grammar to allow\n * interchange of numbers that are not easily representible\n * using numeric types that Temper connects to.\n */\n public numericTokenValue(x: String): Void;\n\n public stringValue(x: String): Void;\n // TODO: stringValueToken(...) that stores string content that\n // does not decode to a USVString for JSON that conveys\n // byte data as byte pairs with embedded, mismatched surrogates.\n\n public get parseErrorReceiver(): JsonParseErrorReceiver? { null }\n }\n\nThis API does allow for specifying malformed JSON outputs as below.\nImplementations must allow for such specifications until allowing\nfor observation of the produced output.\nA streaming JSON implementation may treat flushing a portion of\noutput to an output channel as an observation and expose an error\nthen.\n\nAn implementation may choose to treat a number of top-level values\nother than one (`[] []`) as well-formed or malformed, for example to\nsupport related interchange formats like [JSON Lines].\n\nAn implementation may use a variety of strategies to represent\ntraditionally unrepresentable values like special floating point\n*NaN* (not a number) and ±infinities.\n\nTODO: allow an implementation to use a variety of strategies to deal with\na valid JSON string value (whether used as a property key or a value)\nthat is not a [scalar value string], for example, because it contains a\nUTF-16 surrogate that is not part of a well-formed surrogate pair.\n\n```temper inert\n//// Malformed object\nmyJsonProducer1.startObject();\n// no end for object or array\n\n// missing start for object or array\nmyJsonProducer2.endObject();\n\n// mismatch start and end\nmyJsonProducer3.startArray();\nmyJsonProducer3.endObject();\n\n// malformed properties\nmyJsonProducer4.startObject();\n// missing key\nmyJsonProducer4.nullValue();\nmyJsonProducer4.endObject();\n\nmyJsonProducer5.startObject();\nmyJsonProducer5.objectKey(\u0022k\u0022);\n// missing value\nmyJsonProducer5.endObject();\n\n//// Malformed values\n// questionable numbers\nmyJsonProducer6.float64Value(NaN);\nmyJsonProducer7.float64Value(Infinity);\nmyJsonProducer8.numericTokenValue(\u00221.\u0022);\n// questionable strings\nmyJsonProducer9.stringTokenValue(\u0022\\uD800\u0022);\n\n//// No unique top-level value (see MAY above)\nmyJsonProducer10.nullValue();\nmyJsonProducer10.nullValue();\n```\n\n## Unmarshalling support\n\nUnmarshalling involves turning JSON source texts (which specify state)\ninto domain values (with state and behaviour).\n\nIt is convenient to have a syntax tree for JSON. Often, unmarshalling\ninvolves looking at select object properties to figure out how to interpret\nthe whole, by trying rules like the below:\n\n- if it has keys `x` and `y`, delegate to the *Point2d* unmarshaller,\n- or if it has keys `radius` and `centroid`, delegate to the *Circle* unmarshaller,\n- ...\n\nThere may not be an order of keys that makes it easy to consume a stream of\nevents to implement those rules, and expecting all producers to use the same\nkey order would lead to brittle unmarshalling.\n\n export sealed interface JsonSyntaxTree {\n public produce(p: JsonProducer): Void;\n }\n\n export class JsonObject(\n public properties: Map\u003cString, List\u003cJsonSyntaxTree\u003e\u003e,\n ) extends JsonSyntaxTree {\n\n /**\n * The JSON value tree associated with the given property key or null\n * if there is no such value.\n *\n * The properties map contains a list of sub-trees because JSON\n * allows duplicate properties. ECMA-404 §6 notes (emphasis added):\n *\n * \u003e The JSON syntax does not impose any restrictions on the strings\n * \u003e used as names, **does not require that name strings be unique**,\n * \u003e and does not assign any significance to the ordering of\n * \u003e name/value pairs.\n *\n * When widely used JSON parsers need to relate a property key\n * to a single value, they tend to prefer the last key/value pair\n * from a JSON object. For example:\n *\n * JS:\n *\n * JSON.parse('{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}').x === 'last'\n *\n * Python:\n *\n * import json\n * json.loads('{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}')['x'] == 'last'\n *\n * C#:\n *\n * using System.Text.Json;\n * \t\tJsonDocument d = JsonDocument.Parse(\n * \t\t\t\u0022\u0022\u0022\n * \t\t\t{\u0022x\u0022:\u0022first\u0022,\u0022x\u0022:\u0022last\u0022}\n * \t\t\t\u0022\u0022\u0022\n * \t\t);\n * \t\td.RootElement.GetProperty(\u0022x\u0022).GetString() == \u0022last\u0022\n */\n public propertyValueOrNull(propertyKey: String): JsonSyntaxTree? {\n let treeList = this.properties.getOr(propertyKey, []);\n\n let lastIndex = treeList.length - 1;\n if (lastIndex \u003e= 0) {\n treeList[lastIndex]\n } else {\n null\n }\n }\n\n public propertyValueOrBubble(propertyKey: String): JsonSyntaxTree throws Bubble {\n propertyValueOrNull(propertyKey) as JsonSyntaxTree\n }\n\n public produce(p: JsonProducer): Void {\n p.startObject();\n properties.forEach { k: String, vs: List\u003cJsonSyntaxTree\u003e =\u003e\n vs.forEach { v: JsonSyntaxTree =\u003e\n p.objectKey(k);\n v.produce(p);\n }\n }\n p.endObject();\n }\n }\n\n export class JsonArray(\n public elements: List\u003cJsonSyntaxTree\u003e,\n ) extends JsonSyntaxTree {\n\n public produce(p: JsonProducer): Void {\n p.startArray();\n elements.forEach { v: JsonSyntaxTree =\u003e\n v.produce(p);\n }\n p.endArray();\n }\n }\n\n export class JsonBoolean(\n public content: Boolean,\n ) extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.booleanValue(content);\n }\n }\n\n export class JsonNull extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.nullValue();\n }\n }\n\n export class JsonString(\n public content: String,\n ) extends JsonSyntaxTree {\n public produce(p: JsonProducer): Void {\n p.stringValue(content);\n }\n }\n\n export sealed interface JsonNumeric extends JsonSyntaxTree {\n public asJsonNumericToken(): String;\n\n public asInt32(): Int throws Bubble;\n public asInt64(): Int64 throws Bubble;\n public asFloat64(): Float64 throws Bubble;\n }\n\n export let JsonInt = JsonInt32;\n\n export class JsonInt32(\n public content: Int,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.int32Value(content);\n }\n\n public asJsonNumericToken(): String {\n content.toString()\n }\n\n public asInt32(): Int { content }\n\n public asInt64(): Int64 { content.toInt64() }\n\n public asFloat64(): Float64 { content.toFloat64() }\n }\n\n export class JsonInt64(\n public content: Int64,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.int64Value(content);\n }\n\n public asJsonNumericToken(): String {\n content.toString()\n }\n\n public asInt32(): Int throws Bubble { content.toInt32() }\n\n public asInt64(): Int64 { content }\n\n public asFloat64(): Float64 throws Bubble { content.toFloat64() }\n }\n\n export class JsonFloat64(\n public content: Float64,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.float64Value(content);\n }\n\n public asJsonNumericToken(): String {\n content.toString()\n }\n\n public asInt32(): Int throws Bubble { content.toInt32() }\n\n public asInt64(): Int64 throws Bubble { content.toInt64() }\n\n public asFloat64(): Float64 { content }\n }\n\n export class JsonNumericToken(\n public content: String,\n ) extends JsonNumeric {\n public produce(p: JsonProducer): Void {\n p.numericTokenValue(content);\n }\n\n public asJsonNumericToken(): String {\n content\n }\n\n public asInt32(): Int throws Bubble {\n // TODO Prohibit nonzero fractional values.\n content.toInt32() orelse content.toFloat64().toInt32()\n }\n\n public asInt64(): Int64 throws Bubble {\n // TODO Prohibit nonzero fractional values.\n content.toInt64() orelse content.toFloat64().toInt64()\n }\n\n public asFloat64(): Float64 throws Bubble { content.toFloat64() }\n }\n\nThe *produce* method allows a JSON syntax tree to describe itself to a\nJSON producer so JSON syntax trees, while useful during unmarshalling\ncan also help with marshalling.\n\n## Producing JSON text\n\nA JSON producer that appends to an internal buffer lets us produce\nJSON source text.\n\n // A state machine lets us figure out when to insert commas.\n let JSON_STATE_OPEN_OBJECT = 0; // Last was \u0022{\u0022\n let JSON_STATE_AFTER_KEY = 1; // Last was property key\n let JSON_STATE_AFTER_PROPERTY = 2; // Last was property value\n let JSON_STATE_OPEN_ARRAY = 3; // Last was \u0022[\u0022\n let JSON_STATE_AFTER_ELEMENT = 4; // Last was array element\n let JSON_STATE_NO_VALUE = 5;\n let JSON_STATE_ONE_VALUE = 6;\n\n export class JsonTextProducer extends JsonProducer {\n public interchangeContext: InterchangeContext;\n private buffer: StringBuilder;\n private stack: ListBuilder\u003cInt\u003e;\n private var wellFormed: Boolean;\n\n public constructor(\n interchangeContext: InterchangeContext = NullInterchangeContext.instance\n ): Void {\n this.interchangeContext = interchangeContext;\n this.buffer = new StringBuilder();\n this.stack = new ListBuilder\u003cInt\u003e();\n this.stack.add(JSON_STATE_NO_VALUE);\n this.wellFormed = true;\n }\n\n private state(): Int {\n stack.getOr(stack.length - 1, -1)\n }\n\n private beforeValue(): Void {\n let currentState = state();\n when (currentState) {\n JSON_STATE_OPEN_ARRAY -\u003e\n stack[stack.length - 1] = JSON_STATE_AFTER_ELEMENT;\n JSON_STATE_AFTER_ELEMENT -\u003e buffer.append(\u0022,\u0022);\n JSON_STATE_AFTER_KEY -\u003e\n stack[stack.length - 1] = JSON_STATE_AFTER_PROPERTY;\n JSON_STATE_NO_VALUE -\u003e\n stack[stack.length - 1] = JSON_STATE_ONE_VALUE;\n JSON_STATE_ONE_VALUE, JSON_STATE_AFTER_PROPERTY -\u003e\n wellFormed = false;\n }\n }\n\n public startObject(): Void {\n beforeValue();\n buffer.append(\u0022{\u0022);\n stack.add(JSON_STATE_OPEN_OBJECT);\n }\n\n public endObject(): Void {\n buffer.append(\u0022}\u0022);\n let currentState = state();\n if (JSON_STATE_OPEN_OBJECT == currentState ||\n JSON_STATE_AFTER_PROPERTY == currentState) {\n stack.removeLast();\n } else {\n wellFormed = false;\n }\n }\n\n public objectKey(key: String): Void {\n let currentState = state();\n when (currentState) {\n JSON_STATE_OPEN_OBJECT -\u003e void;\n JSON_STATE_AFTER_PROPERTY -\u003e buffer.append(\u0022,\u0022);\n else -\u003e wellFormed = false;\n }\n encodeJsonString(key, buffer);\n buffer.append(\u0022:\u0022);\n if (currentState \u003e= 0) {\n stack[stack.length - 1] = JSON_STATE_AFTER_KEY;\n }\n }\n\n public startArray(): Void {\n beforeValue();\n buffer.append(\u0022[\u0022);\n stack.add(JSON_STATE_OPEN_ARRAY);\n }\n\n public endArray(): Void {\n buffer.append(\u0022]\u0022);\n let currentState = state();\n if (JSON_STATE_OPEN_ARRAY == currentState ||\n JSON_STATE_AFTER_ELEMENT == currentState) {\n stack.removeLast();\n } else {\n wellFormed = false;\n }\n }\n\n public nullValue(): Void {\n beforeValue();\n buffer.append(\u0022null\u0022);\n }\n\n public booleanValue(x: Boolean): Void {\n beforeValue();\n buffer.append(if (x) { \u0022true\u0022 } else { \u0022false\u0022 });\n }\n\n public int32Value(x: Int): Void {\n beforeValue();\n buffer.append(x.toString());\n }\n\n public int64Value(x: Int64): Void {\n beforeValue();\n buffer.append(x.toString());\n }\n\n public float64Value(x: Float64): Void {\n beforeValue();\n buffer.append(x.toString());\n }\n\n public numericTokenValue(x: String): Void {\n // TODO check syntax of x and maybe set malformed\n beforeValue();\n buffer.append(x);\n }\n\n public stringValue(x: String): Void {\n beforeValue();\n encodeJsonString(x, buffer);\n }\n\n public toJsonString(): String throws Bubble {\n if (wellFormed \u0026\u0026 stack.length == 1 \u0026\u0026 state() == JSON_STATE_ONE_VALUE) {\n return buffer.toString();\n } else {\n bubble();\n }\n }\n }\n\n let encodeJsonString(x: String, buffer: StringBuilder): Void {\n buffer.append(\u0022\\\u0022\u0022);\n var i = String.begin;\n var emitted = i;\n while (x.hasIndex(i)) {\n // The choice of which code-points to escape and which\n // escape sequences to use is the same as those made in\n // ES262 § 25.5.2.3: QuoteJSONString\n // as long as the string has no orphaned surrogates.\n // This means that the output will work in many parsers\n // while still allowing use of JSON.stringify in JavaScript\n // where keeping code-size low by not shipping a JSON encoder\n // is important.\n\n let cp = x[i];\n let replacement = when (cp) {\n char'\\b' -\u003e \u0022\\\\b\u0022;\n char'\\t' -\u003e \u0022\\\\t\u0022;\n char'\\n' -\u003e \u0022\\\\n\u0022;\n char'\\f' -\u003e \u0022\\\\f\u0022;\n char'\\r' -\u003e \u0022\\\\r\u0022;\n char'\u0022' -\u003e \u0022\\\\\\\u0022\u0022;\n char'\\\\' -\u003e \u0022\\\\\\\\\u0022;\n else -\u003e\n // Control characters and non-USV code-points.\n if (cp \u003c 0x20 || 0xD800 \u003c= cp \u0026\u0026 cp \u003c= 0xDFFF) {\n \u0022\\\\u\u0022\n } else {\n \u0022\u0022\n };\n };\n\n let nextI = x.next(i);\n\n if (replacement != \u0022\u0022) {\n buffer.appendBetween(x, emitted, i);\n buffer.append(replacement);\n if (replacement == \u0022\\\\u\u0022) {\n encodeHex4(cp, buffer);\n }\n emitted = nextI;\n }\n\n i = nextI;\n }\n buffer.appendBetween(x, emitted, i);\n buffer.append(\u0022\\\u0022\u0022);\n }\n\n let hexDigits = [\n '0', '1', '2', '3', '4', '5', '6', '7',\n '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',\n ];\n\n let encodeHex4(cp: Int, buffer: StringBuilder): Void {\n let b0 = (cp / 0x1000) \u0026 0xf;\n let b1 = (cp / 0x100) \u0026 0xf;\n let b2 = (cp / 0x10) \u0026 0xf;\n let b3 = cp \u0026 0xf;\n buffer.append(hexDigits[b0]);\n buffer.append(hexDigits[b1]);\n buffer.append(hexDigits[b2]);\n buffer.append(hexDigits[b3]);\n }\n\n## Parsing JSON\n\nJSON tokens, like `{` and `\u0022foo\u0022` correspond fairly closely to events\nlike *JsonProducer.startObject* and *JsonProducer.stringValue* respectively.\n\n*JsonSyntaxTree* knows how to describe itself to a *JsonProducer*, but we can\nalso craft a *JsonProducer* that constructs a syntax tree.\n\nFirst, we need a way to explain syntax errors, ideally in a way that lets\na *JsonProducer* represent both the valid JSON or the error.\n\n export interface JsonParseErrorReceiver {\n public explainJsonError(explanation: String): Void;\n }\n\nNow, we are ready to build a *JsonProducer* that produces a syntax tree given\nvalid JSON, but if given a string that is not valid JSON, it has a syntax error.\n\n export class JsonSyntaxTreeProducer extends JsonProducer \u0026 JsonParseErrorReceiver {\n private stack: ListBuilder\u003cListBuilder\u003cJsonSyntaxTree\u003e\u003e;\n private var error: String?;\n public get interchangeContext(): InterchangeContext {\n NullInterchangeContext.instance\n }\n\n public constructor() {\n stack = new ListBuilder\u003cListBuilder\u003cJsonSyntaxTree\u003e\u003e();\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n error = null;\n }\n\n private storeValue(v: JsonSyntaxTree): Void {\n if (!stack.isEmpty) {\n stack[stack.length - 1].add(v);\n }\n }\n\n public startObject(): Void {\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n }\n\n public endObject(): Void {\n if (stack.isEmpty) {\n return;\n }\n let ls = stack.removeLast();\n // In the common case, there are no duplicate keys.\n let m = new MapBuilder\u003cString, List\u003cJsonSyntaxTree\u003e\u003e();\n // But we need a way to accumulate them when there are duplicate keys.\n var multis: MapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e? = null;\n for (var i = 0, n = ls.length \u0026 -2; i \u003c n;) {\n let keyTree = ls[i++];\n if (!(keyTree is JsonString)) { break }\n let key = (keyTree as JsonString orelse panic()).content;\n let value = ls[i++];\n\n if (m.has(key)) {\n if (multis == null) {\n multis = new MapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e();\n }\n let mb = multis as MapBuilder\u003cString, ListBuilder\u003cJsonSyntaxTree\u003e\u003e orelse panic();\n if (!mb.has(key)) {\n mb[key] = (m[key] orelse panic()).toListBuilder();\n }\n (mb[key] orelse panic()).add(value);\n } else {\n m[key] = [value];\n }\n }\n\n let multis = multis; // lock it in down here for inference\n if (multis != null) {\n multis.forEach { k: String, vs: ListBuilder\u003cJsonSyntaxTree\u003e =\u003e\n m[k] = vs.toList();\n }\n }\n\n storeValue(new JsonObject(m.toMap()));\n }\n\n public objectKey(key: String): Void {\n storeValue(new JsonString(key));\n }\n\n public startArray(): Void {\n stack.add(new ListBuilder\u003cJsonSyntaxTree\u003e());\n }\n\n public endArray(): Void {\n if (stack.isEmpty) {\n return;\n }\n let ls = stack.removeLast();\n storeValue(new JsonArray(ls.toList()));\n }\n\n public nullValue(): Void {\n storeValue(new JsonNull());\n }\n\n public booleanValue(x: Boolean): Void {\n storeValue(new JsonBoolean(x));\n }\n\n public int32Value(x: Int): Void {\n storeValue(new JsonInt(x));\n }\n\n public int64Value(x: Int64): Void {\n storeValue(new JsonInt64(x));\n }\n\n public float64Value(x: Float64): Void {\n storeValue(new JsonFloat64(x));\n }\n\n public numericTokenValue(x: String): Void {\n storeValue(new JsonNumericToken(x));\n }\n\n public stringValue(x: String): Void {\n storeValue(new JsonString(x));\n }\n\n public toJsonSyntaxTree(): JsonSyntaxTree throws Bubble {\n if (stack.length != 1 || error != null) { bubble() }\n let ls = stack[0];\n if (ls.length != 1) { bubble() }\n ls[0]\n }\n\n public get jsonError(): String? { error }\n\n public get parseErrorReceiver(): JsonParseErrorReceiver { this }\n\n public explainJsonError(error: String): Void {\n this.error = error;\n }\n }\n\nSome helpers let us route errors:\n\n let expectedTokenError(\n sourceText: String,\n i: StringIndex,\n out: JsonProducer,\n shortExplanation: String,\n ): Void {\n let gotten = if (sourceText.hasIndex(i)) {\n \u0022`\u0024{ sourceText.slice(i, sourceText.end) }`\u0022\n } else {\n \u0022end-of-file\u0022\n };\n storeJsonError(out, \u0022Expected \u0024{shortExplanation}, but got \u0024{gotten}\u0022);\n }\n\n let storeJsonError(out: JsonProducer, explanation: String): Void {\n out.parseErrorReceiver?.explainJsonError(explanation);\n }\n\nNext, a JSON string parser that drives a *JsonProducer*.\n\n export let parseJsonToProducer(sourceText: String, out: JsonProducer): Void {\n var i = String.begin;\n let afterValue = parseJsonValue(sourceText, i, out);\n if (afterValue is StringIndex) { // else parseJsonValue must have stored an error\n i = skipJsonSpaces(sourceText, afterValue);\n if (sourceText.hasIndex(i) \u0026\u0026 out.parseErrorReceiver != null) {\n storeJsonError(out, \u0022Extraneous JSON `\u0024{sourceText.slice(i, sourceText.end)}`\u0022);\n }\n }\n }\n\nA recursive descent parser without backtracking works just fine for JSON because the\nnext non-space character perfectly predicts the next production.\nAll `// \u003e ` comments are followed by quotes from ECMA-404 2nd edition.\nEach parsing helper takes the *StringIndex* before parsing and returns\nthe *StringIndex* at the end of the content it parsed, or maybe *null* if parsing\nfailed.\n\n // \u003e Insignificant whitespace is allowed before or after any token.\n // \u003e Whitespace is any sequence of one or more of the following code\n // \u003e points:\n // \u003e character tabulation (U+0009),\n // \u003e line feed (U+000A),\n // \u003e carriage return (U+000D), and\n // \u003e space (U+0020).\n let skipJsonSpaces(sourceText: String, var i: StringIndex): StringIndex {\n while (sourceText.hasIndex(i)) {\n when (sourceText[i]) {\n 0x9, 0xA, 0xD, 0x20 -\u003e void;\n else -\u003e break;\n }\n i = sourceText.next(i);\n }\n return i;\n }\n\n let parseJsonValue(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n i = skipJsonSpaces(sourceText, i);\n if (!sourceText.hasIndex(i)) {\n expectedTokenError(sourceText, i, out, \u0022JSON value\u0022);\n return StringIndex.none\n }\n when (sourceText[i]) {\n char'{' -\u003e parseJsonObject(sourceText, i, out);\n char'[' -\u003e parseJsonArray(sourceText, i, out);\n char'\u0022' -\u003e parseJsonString(sourceText, i, out);\n char't', char'f' -\u003e parseJsonBoolean(sourceText, i, out);\n char'n' -\u003e parseJsonNull(sourceText, i, out);\n else -\u003e parseJsonNumber(sourceText, i, out);\n }\n }\n\nFor a JSON object, parsing looks for a '{', then optionally a property.\nAfter a property, if there is a ',' parsing looks for another property.\nAfter the last property or lone '{', there must be a '}'.\n\n let parseJsonObject(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption { do {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'{') {\n expectedTokenError(sourceText, i, out, \u0022'{'\u0022);\n return StringIndex.none;\n }\n out.startObject();\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] != char'}') {\n while (true) {\n let keyBuffer = new StringBuilder();\n let afterKey = parseJsonStringTo(sourceText, i, keyBuffer, out);\n if (!(afterKey is StringIndex)) { return StringIndex.none; }\n out.objectKey(keyBuffer.toString());\n i = skipJsonSpaces(sourceText, afterKey as StringIndex orelse panic());\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char':') {\n i = sourceText.next(i);\n let afterPropertyValue = parseJsonValue(sourceText, i, out);\n if (!(afterPropertyValue is StringIndex)) {\n return StringIndex.none;\n }\n i = afterPropertyValue as StringIndex;\n } else {\n expectedTokenError(sourceText, i, out, \u0022':'\u0022);\n return StringIndex.none;\n }\n\n i = skipJsonSpaces(sourceText, i);\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char',') {\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n continue;\n } else {\n break;\n }\n }\n }\n\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char'}') {\n out.endObject();\n return sourceText.next(i);\n } else {\n expectedTokenError(sourceText, i, out, \u0022'}'\u0022);\n return StringIndex.none;\n }\n } orelse panic() } // See https://github.com/temperlang/temper/issues/203\n\nFor a JSON array, parsing looks for a '\\[', and an optional element value.\nAfter each element value, if there is a ',', parsing looks for another element\nvalue. After the last element value or lone '\\[', there must be a '\\]'.\n\n let parseJsonArray(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption { do {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'[') {\n expectedTokenError(sourceText, i, out, \u0022'['\u0022);\n return StringIndex.none;\n }\n out.startArray();\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] != char']') {\n while (true) {\n let afterElementValue = parseJsonValue(sourceText, i, out);\n if (!(afterElementValue is StringIndex)) {\n return StringIndex.none;\n }\n i = afterElementValue as StringIndex;\n\n i = skipJsonSpaces(sourceText, i);\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char',') {\n i = skipJsonSpaces(sourceText, sourceText.next(i));\n continue;\n } else {\n break;\n }\n }\n }\n\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char']') {\n out.endArray();\n return sourceText.next(i);\n } else {\n expectedTokenError(sourceText, i, out, \u0022']'\u0022);\n return StringIndex.none;\n }\n } orelse panic() } // See https://github.com/temperlang/temper/issues/203\n\nA string consists of double-quotes with a select group of C-style escape sequences.\nThe characters that are allowed unescaped inside the quotes are any except ASCII\ncontrol characters.\n\n\u003e To escape a code point that is not in the Basic Multilingual Plane,\n\u003e the character may be represented as a twelve-character sequence,\n\u003e encoding the UTF-16 surrogate pair corresponding to the code point.\n\nThis suggests that when only one of a surrogate pair is escaped, that it is\ntreated as multiple code points. This implementation collapses them into\na single code-point for consistency with backends with native UTF-16 strings\nwhere such a distinction is not possible to represent.\n\n let parseJsonString(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n let sb = new StringBuilder();\n let after = parseJsonStringTo(sourceText, i, sb, out);\n if (after is StringIndex) {\n out.stringValue(sb.toString());\n }\n return after;\n }\n\n let parseJsonStringTo(\n sourceText: String, var i: StringIndex, sb: StringBuilder,\n errOut: JsonProducer\n ): StringIndexOption {\n if (!sourceText.hasIndex(i) || sourceText[i] != char'\u0022') {\n expectedTokenError(sourceText, i, errOut, '\u0022');\n return StringIndex.none;\n }\n i = sourceText.next(i);\n\n // Hold onto lead surrogates until we find out whether there's a trailing\n // surrogate or not.\n var leadSurrogate: Int = -1;\n\n var consumed = i;\n while (sourceText.hasIndex(i)) {\n let cp = sourceText[i];\n if (cp == char'\u0022') { break }\n var iNext = sourceText.next(i);\n let end = sourceText.end;\n\n // Emit anything between consumed and i if there\n // is a pending surrogate or escaped characters.\n var needToFlush = false;\n\n // Decode one code-point or UTF-16 surrogate\n var decodedCp = if (cp != char'\\\\') {\n cp\n } else {\n needToFlush = true;\n if (!sourceText.hasIndex(iNext)) {\n expectedTokenError(sourceText, iNext, errOut, \u0022escape sequence\u0022);\n return StringIndex.none;\n }\n let esc0 = sourceText[iNext];\n iNext = sourceText.next(iNext);\n when (esc0) {\n char'\u0022', char'\\\\', char'/' -\u003e esc0;\n char'b' -\u003e char'\\b';\n char'f' -\u003e char'\\f';\n char'n' -\u003e char'\\n';\n char'r' -\u003e char'\\r';\n char't' -\u003e char'\\t';\n char'u' -\u003e do {\n let hex: Int = if (sourceText.hasAtLeast(iNext, end, 4)) {\n let startHex = iNext;\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n iNext = sourceText.next(iNext);\n decodeHexUnsigned(sourceText, startHex, iNext)\n } else {\n -1\n };\n if (hex \u003c 0) {\n expectedTokenError(sourceText, iNext, errOut, \u0022four hex digits\u0022);\n return StringIndex.none;\n }\n hex\n }\n else -\u003e do {\n expectedTokenError(sourceText, iNext, errOut, \u0022escape sequence\u0022);\n return StringIndex.none;\n }\n }\n };\n\n // If we have two surrogates, combine them into one code-point.\n // If we have a lead surrogate, make sure we can wait.\n if (leadSurrogate \u003e= 0) {\n needToFlush = true;\n let lead = leadSurrogate;\n if (0xDC00 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDFFF) {\n leadSurrogate = -1;\n decodedCp = (\n 0x10000 +\n (((lead - 0xD800) * 0x400) | (decodedCp - 0xDC00))\n );\n }\n } else if (0xD800 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDBFF) {\n needToFlush = true;\n }\n\n // Consume characters from sourceText onto sb if it's timely\n // to do so.\n if (needToFlush) {\n sb.appendBetween(sourceText, consumed, i);\n if (leadSurrogate \u003e= 0) {\n // Not combined with a trailing surrogate.\n sb.appendCodePoint(leadSurrogate) orelse panic();\n }\n if (0xD800 \u003c= decodedCp \u0026\u0026 decodedCp \u003c= 0xDBFF) {\n leadSurrogate = decodedCp;\n } else {\n leadSurrogate = -1;\n sb.appendCodePoint(decodedCp) orelse panic();\n }\n consumed = iNext;\n }\n\n i = iNext;\n }\n\n if (!sourceText.hasIndex(i) || sourceText[i] != char'\u0022') {\n expectedTokenError(sourceText, i, errOut, '\u0022');\n StringIndex.none\n } else {\n if (leadSurrogate \u003e= 0) {\n sb.appendCodePoint(leadSurrogate) orelse panic();\n } else {\n sb.appendBetween(sourceText, consumed, i);\n }\n i = sourceText.next(i); // Consume closing quote\n i\n }\n }\n\n let decodeHexUnsigned(\n sourceText: String, start: StringIndex, limit: StringIndex\n ): Int {\n var n = 0;\n var i = start;\n while (i.compareTo(limit) \u003c 0) {\n let cp = sourceText[i];\n let digit = if (char'0' \u003c= cp \u0026\u0026 cp \u003c= char'0') {\n cp - char'0'\n } else if (char'A' \u003c= cp \u0026\u0026 cp \u003c= char'F') {\n cp - char'A' + 10\n } else if (char'a' \u003c= cp \u0026\u0026 cp \u003c= char'f') {\n cp - char'a' + 10\n } else {\n return -1;\n }\n n = (n * 16) + digit;\n i = sourceText.next(i);\n }\n n\n }\n\n let parseJsonBoolean(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n let ch0 = if (sourceText.hasIndex(i)) {\n sourceText[i]\n } else {\n 0\n };\n let end = sourceText.end;\n\n let keyword: String?;\n let n: Int;\n when (ch0) {\n char'f' -\u003e do { keyword = \u0022false\u0022; n = 5 };\n char't' -\u003e do { keyword = \u0022true\u0022; n = 4 };\n else -\u003e do { keyword = null; n = 0 };\n }\n\n if (keyword != null) {\n if (sourceText.hasAtLeast(i, end, n)) {\n let after = afterSubstring(sourceText, i, keyword);\n if (after is StringIndex) {\n out.booleanValue(n == 4);\n return after;\n }\n }\n }\n\n expectedTokenError(sourceText, i, out, \u0022`false` or `true`\u0022);\n return StringIndex.none;\n }\n\n let parseJsonNull(\n sourceText: String, i: StringIndex, out: JsonProducer\n ): StringIndexOption {\n let after = afterSubstring(sourceText, i, \u0022null\u0022);\n\n if (after is StringIndex) {\n out.nullValue();\n return after;\n }\n\n expectedTokenError(sourceText, i, out, \u0022`null`\u0022);\n return StringIndex.none;\n }\n\n let afterSubstring(\n string: String,\n inString: StringIndex,\n substring: String\n ): StringIndexOption {\n var i = inString;\n var j = String.begin;\n while (substring.hasIndex(j)) {\n if (!string.hasIndex(i)) {\n return StringIndex.none;\n }\n if (string[i] != substring[j]) {\n return StringIndex.none;\n }\n i = string.next(i);\n j = substring.next(j);\n }\n i\n }\n\nAs usual, the number grammar is the single largest sub-grammar.\nWe accumulate an integer portion separately from a decimal portion.\nIf either of those is past the common representability limits for *Int*\nor *Float64*, then we use the lossless numeric syntax tree variant.\n\n let parseJsonNumber(\n sourceText: String, var i: StringIndex, out: JsonProducer\n ): StringIndexOption { do {\n var isNegative = false;\n let startOfNumber = i;\n if (sourceText.hasIndex(i) \u0026\u0026 sourceText[i] == char'-') {\n isNegative = true;\n i = sourceText.next(i);\n }\n\n // Find the whole portion, the portion before any fraction\n // or exponent.\n // 0 | [1-9][0-9]*\n let digit0 = if (sourceText.hasIndex(i)) { sourceText[i] } else { -1 };\n if (digit0 \u003c char'0' || char'9' \u003c digit0) {\n // parseJsonValue comes here for any unrecognized code-points\n let error = if (!isNegative \u0026\u0026 digit0 != char'.') {\n \u0022JSON value\u0022\n } else {\n \u0022digit\u0022\n };\n expectedTokenError(sourceText, i, out, error);\n return StringIndex.none;\n }\n i = sourceText.next(i);\n var nDigits = 1;\n var tentativeFloat64 = (digit0 - char'0').toFloat64();\n var tentativeInt64 = (digit0 - char'0').toInt64();\n var overflowInt64 = false;\n if (char'0' != digit0) {\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nDigits += 1;\n let nextDigit = possibleDigit - char'0';\n tentativeFloat64 = tentativeFloat64 * 10.0 + nextDigit.toFloat64();\n let oldInt64 = tentativeInt64;\n tentativeInt64 = tentativeInt64 * 10i64 + nextDigit.toInt64();\n if (tentativeInt64 \u003c oldInt64) {\n // Overflow happened.\n if (\n minInt64 - oldInt64 == -nextDigit.toInt64() \u0026\u0026\n isNegative \u0026\u0026\n oldInt64 \u003e 0i64\n ) {\n // Ok because we landed exactly once on min int64.\n } else {\n overflowInt64 = true;\n }\n }\n } else {\n break;\n }\n }\n }\n\n // optional fraction component\n // '.' [0-9]+\n var nDigitsAfterPoint = 0;\n if (sourceText.hasIndex(i) \u0026\u0026 char'.' == sourceText[i]) {\n i = sourceText.next(i);\n let afterPoint = i;\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nDigits += 1;\n nDigitsAfterPoint += 1;\n tentativeFloat64 = tentativeFloat64 * 10.0 +\n (possibleDigit - char'0').toFloat64();\n } else {\n break;\n }\n }\n if (i == afterPoint) {\n // ECMA-404 does not allow \u00220.\u0022 with no digit after the point.\n expectedTokenError(sourceText, i, out, \u0022digit\u0022);\n return StringIndex.none;\n }\n }\n\n // optional exponent\n // [eE] [+\\-]? [0-9]+\n var nExponentDigits = 0;\n if (sourceText.hasIndex(i) \u0026\u0026 char'e' == (sourceText[i] | 32)) {\n i = sourceText.next(i);\n if (!sourceText.hasIndex(i)) {\n expectedTokenError(sourceText, i, out, \u0022sign or digit\u0022);\n return StringIndex.none;\n }\n let afterE = sourceText[i];\n if (afterE == char'+' || afterE == char'-') {\n i = sourceText.next(i);\n }\n while (sourceText.hasIndex(i)) {\n let possibleDigit = sourceText[i];\n if (char'0' \u003c= possibleDigit \u0026\u0026 possibleDigit \u003c= char'9') {\n i = sourceText.next(i);\n nExponentDigits += 1;\n } else {\n break;\n }\n }\n if (nExponentDigits == 0) {\n expectedTokenError(sourceText, i, out, \u0022exponent digit\u0022);\n return StringIndex.none;\n }\n }\n let afterExponent = i;\n\n // TODO Allow exponents within range and/or explicit zero fractions for ints.\n if (nExponentDigits == 0 \u0026\u0026 nDigitsAfterPoint == 0 \u0026\u0026 !overflowInt64) {\n // An integer literal.\n let value = if (isNegative) { -tentativeInt64 } else { tentativeInt64 };\n if (-0x8000_0000_i64 \u003c= value \u0026\u0026 value \u003c= 0x7fff_ffff_i64) {\n // Guaranteed representable int value.\n out.int32Value(value.toInt32Unsafe());\n } else {\n out.int64Value(value);\n }\n return i;\n }\n\n let numericTokenString = sourceText.slice(startOfNumber, i);\n var doubleValue = NaN;\n if (nExponentDigits != 0 || nDigitsAfterPoint != 0) {\n do {\n doubleValue = numericTokenString.toFloat64();\n } orelse do {\n // Fall back to numeric token below\n ;\n }\n }\n\n if (doubleValue != -Infinity \u0026\u0026 doubleValue != Infinity \u0026\u0026\n doubleValue != NaN) {\n out.float64Value(doubleValue);\n } else {\n out.numericTokenValue(numericTokenString);\n }\n return i;\n } orelse panic() } // See https://github.com/temperlang/temper/issues/203\n\n // TODO Define as `Int64.minValue`.\n let minInt64 = -0x8000_0000_0000_0000_i64;\n\nAs a convenience, the *parseJson* helper just creates a tree producer,\ncalls the parser and gets the tree.\n\n export let parseJson(sourceText: String): JsonSyntaxTree throws Bubble {\n let p = new JsonSyntaxTreeProducer();\n parseJsonToProducer(sourceText, p);\n // TODO: if there is a syntax error, produce it.\n p.toJsonSyntaxTree()\n }\n\n## Type adapters\n\nType adapters allow converting values of a type to and from JSON.\nSee the `@json` type decorator for details which makes sure that\nthe decorated type has a static method that gets an adapter for the\ntype.\n\n export interface JsonAdapter\u003cT\u003e {\n public encodeToJson(x: T, p: JsonProducer): Void;\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): T throws Bubble;\n }\n\nOur intrinsic types, like *Boolean* need json adapters. Static extensions\nlet us make *Boolean.jsonAdapter()* work as if it were built in.\n\n class BooleanJsonAdapter extends JsonAdapter\u003cBoolean\u003e {\n public encodeToJson(x: Boolean, p: JsonProducer): Void {\n p.booleanValue(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Boolean throws Bubble {\n (t as JsonBoolean).content\n }\n }\n\n @staticExtension(Boolean, \u0022jsonAdapter\u0022)\n export let booleanJsonAdapter(): JsonAdapter\u003cBoolean\u003e {\n new BooleanJsonAdapter()\n }\n\n class Float64JsonAdapter extends JsonAdapter\u003cFloat64\u003e {\n public encodeToJson(x: Float64, p: JsonProducer): Void {\n p.float64Value(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Float64 throws Bubble {\n (t as JsonNumeric).asFloat64()\n }\n }\n\n @staticExtension(Float64, \u0022jsonAdapter\u0022)\n export let float64JsonAdapter(): JsonAdapter\u003cFloat64\u003e {\n new Float64JsonAdapter()\n }\n\n class Int32JsonAdapter extends JsonAdapter\u003cInt\u003e {\n public encodeToJson(x: Int, p: JsonProducer): Void {\n p.int32Value(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Int throws Bubble {\n (t as JsonNumeric).asInt32()\n }\n }\n\n @staticExtension(Int, \u0022jsonAdapter\u0022)\n export let int32JsonAdapter(): JsonAdapter\u003cInt\u003e {\n new Int32JsonAdapter()\n }\n\n class Int64JsonAdapter extends JsonAdapter\u003cInt64\u003e {\n public encodeToJson(x: Int64, p: JsonProducer): Void {\n p.int64Value(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): Int64 throws Bubble {\n (t as JsonNumeric).asInt64()\n }\n }\n\n @staticExtension(Int64, \u0022jsonAdapter\u0022)\n export let int64JsonAdapter(): JsonAdapter\u003cInt64\u003e {\n new Int64JsonAdapter()\n }\n\n class StringJsonAdapter extends JsonAdapter\u003cString\u003e {\n public encodeToJson(x: String, p: JsonProducer): Void {\n p.stringValue(x);\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): String throws Bubble {\n (t as JsonString).content\n }\n }\n\n @staticExtension(String, \u0022jsonAdapter\u0022)\n export let stringJsonAdapter(): JsonAdapter\u003cString\u003e {\n new StringJsonAdapter()\n }\n\n class ListJsonAdapter\u003cT\u003e(\n private adapterForT: JsonAdapter\u003cT\u003e,\n ) extends JsonAdapter\u003cList\u003cT\u003e\u003e {\n public encodeToJson(x: List\u003cT\u003e, p: JsonProducer): Void {\n p.startArray();\n for (let el of x) {\n adapterForT.encodeToJson(el, p);\n }\n p.endArray();\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): List\u003cT\u003e throws Bubble {\n let b = new ListBuilder\u003cT\u003e();\n let elements = (t as JsonArray).elements;\n let n = elements.length;\n var i = 0;\n while (i \u003c n) {\n let el = elements[i];\n i += 1;\n b.add(adapterForT.decodeFromJson(el, ic));\n }\n b.toList()\n }\n }\n\n @staticExtension(List, \u0022jsonAdapter\u0022)\n export let listJsonAdapter\u003cT\u003e(adapterForT: JsonAdapter\u003cT\u003e): JsonAdapter\u003cList\u003cT\u003e\u003e {\n new ListJsonAdapter\u003cT\u003e(adapterForT)\n }\n\n export class OrNullJsonAdapter\u003cT\u003e(\n private adapterForT: JsonAdapter\u003cT\u003e,\n ) extends JsonAdapter\u003cT?\u003e {\n public encodeToJson(x: T?, p: JsonProducer): Void {\n if (x == null) {\n p.nullValue();\n } else {\n adapterForT.encodeToJson(x, p);\n }\n }\n public decodeFromJson(t: JsonSyntaxTree, ic: InterchangeContext): T? throws Bubble {\n if (t is JsonNull) {\n null\n } else {\n adapterForT.decodeFromJson(t, ic)\n }\n }\n }\n\n[ECMA-404]: https://ecma-international.org/publications-and-standards/standards/ecma-404/\n[content negotiation]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation\n[JSON Lines]: https://jsonlines.org/\n[scalar value string]: https://infra.spec.whatwg.org/#scalar-value-string\n" ], "names": [ "JsonProducer", "JsonSyntaxTree", "InterchangeContext", "JsonAdapter", "JsonString", "DateJsonAdapter", "x", "p", "encodeToJson", "t", "ic", "decodeFromJson", "this", "jsonAdapter", "daysInMonth", "isLeapYear", "year", "return", "padTo", "minWidth", "num", "sb", "decimal", "decimalIndex", "decimalEnd", "nNeeded", "dayOfWeekLookupTableLeapy", "dayOfWeekLookupTableNotLeapy" ], "mappings": "AD4FS;ADAA,EDAAA,YDAA,uBDAAC,cDAA,yBDAAC,kBDAA,4BDAA,A,C,W,qBD+JmB,CDAAE,UDAe,GDAA,ADAf;ADAe,MDAA,A;ADlPvC;ADAA;ADAA,0BDAA;A,M,oB,oBDwuCiBD,gBDAA,CDAA,A;A;A,cDrpCZ,CDAAG,MDAA,EDAAC,MDAA;ADAA,IDAAC,iBDAA,CDAAF,MDAA,EDAAC,MDAA;ADAA;ADAA,GDAA;A;A,gBDAA,CDAAE,MDAA,EDAAC,ODAA;ADAA,WDAAC,mBDAA,CDAAF,MDAA,EDAAC,ODAA;ADAA,GDAA;A;A;A;A;A;ADEQ,yDD8JV;ADTM,kFDEN;ADFM,SDAAF,iBDAY,ADAZ,CDAaI,SDAA,EDAAL,MDAe,CDElC,ADF0C;ADC3B,YDAU,EDAA,ADAV,CDAAK,SDAQ,CDAE,4BDAA;ADAxB,EDAAL,MDAC,CDAC,WDAW,CDAC,MDAU,CDAC;ADAA;ADAC;ADGd,wHDKb;ADLa,SDAAI,mBDAc,ADAd,CDCZF,MDAiB,CDCjB,CDAAC,ODAsB,CDGvB,ADFqB;ADCA,YDAe,CDAA;ADAf,UDAe,ADAV,wBDAU,CDAA,ADAfD,MDAC,CDAI,CDAAL,eDAU,EDAA;ADAnC,oDDA6C,CDAA,ADAzB,MDAe,CDAS,QDAC;ADAA,CDC9C;A;A,S,gBDhKE;ADAA,aDAAC,oBDAA;ADAA,CDAA;ADnFD,4BDAW;ADAf,KDAI,CDAAS,gBDAW,EDAG,ODcjB,CDAA,ADdiB,MDcjB,CDAA,ADdiB,CDChB,CDAC,CDCe,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCF,GDAE,CDCnB;ADED,0DDEC;ADFD,QDAI,CDAAC,eDAU,ADAd,CDAeC,SDAS,CDEvB,ADFkC,EDAA;ADAR,MDAAC,WDAO;ADCM,YDAU,CDAA;ADA/C,MDAK,cDAC,CDAA,ADAND,SDAI,CDAG,EDAC,CDAA,ADAR,IDAa,ADAD,EDAC;ADAK,QDAK,cDAC,CDAA,ADANA,SDAI,CDAG,IDAG,CDAA,ADAV,IDAe,ADAD,EDAC;ADAA,MDAAC,WDAA;ADAA;ADAI,cDAU,ADAL,eDAC,CDAA,ADAND,SDAI,CDAG,IDAG,EDAA;ADAV,MDAAC,WDAA,EDAe,ADAf,ODAU,ADAV,IDAe,ADAD,EDAC;ADAA,KDAA;ADAlC;ADAmC,IDAAA,WDAA;ADAA,GDAA;ADCvD,QDAA,ADF0B,CDAAA,WDAA;ADE1B;ADgCD,iHDcC;ADdD,QDAI,CDAAC,UDAK,ADAT,CDAUC,aDAa,CDAE,CDAAC,QDAQ,CDAE,CDAAC,ODAiB,CDcnD,ADd2D;ADIzB,YDAqB;ADErC,YDA0B,CDAA;ADFV,YDAgC,CDAA;ADHjE,ODAI,CDAAC,YDAO,EDAG,CDAAF,QDAG,CDAA,ADAH,QDAgB,CDAA,ADAH,EDAE,CDAC;ADC3B,KDAC,CDAAG,iBDAY,EDAG,EDAY;ADC/B,ODAI,CDAAC,eDAU,EDAG,CDAAF,YDAO,CDAA,ADAP,MDAW;ADCxB,MDAAC,iBDAY,EDAG,CDAAC,eDAU;ADAI,YDAqB,ADAd,gBDAA,ADAPF,YDAO,CDAC,CDAAC,iBDAY,CDAC,CDAA;ADArB,YDAgC,ADAhC,ODAqB,ADArB,IDAgC,ADAP,GDAO;ADAA;ADAA;ADAA,GDAA;ADAjE,MDAI;ADCF,IDAAF,ODAE,GDAQ,ODAI;ADCC,YDA0B,ADAlB,gBDAI,CDAA,ADAZC,YDAO,CDAM,CDAAC,iBDAY,CDAC,CDAA;ADAzC,IDAAA,iBDAY,ADAZ,EDAyC,ADA1B,ODA0B;ADC1C;ADCwB,YDA8C,EDAA,ADAtC,wBDAY,CDAA,ADApBD,YDAO,CDAc,CDAAC,iBDAY,CDAE,CDAAC,eDAU,CDAC,CDAA;ADApE,KDAC,CDAAC,YDAO,EDAG,CDAAN,aDAQ,ADAR,EDAW,ODA8C,ADAzD,EDAyD,ADAzD,EDAyD;ADCvE,SDAOM,YDAO,ADAP,EDAW,ADAD,EDAC;ADChB,IDAAJ,ODAE,GDAQ,ODAI;ADCd,IDAAI,YDAO,ADAP,EDAY,ADAZ,CDAAA,YDAO,ADAP,EDAW,EDAC,ADAZ,EDAY,ADAZ,EDAY;ADCb;ADCD,EDAAJ,ODAE,GDAe,IDAAC,YDAO,WDAEC,iBDAY,CDAE,CDAAC,eDAU,CDAC;ADAA;ADAC;ADKvB,4BDAS;ADAxC,KDAI,CDAAE,8BDAyB,EDAc,ODG1C,CDAA,ADH0C,MDG1C,CDAA,ADH0C,CDCzC,CDAC,CDCD,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDCnC;ADCiC,4BDAS;ADA3C,KDAI,CDAAC,iCDA4B,EDAc,ODG7C,CDAA,ADH6C,MDG7C,CDAA,ADH6C,CDC5C,CDAC,CDCD,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDAE,EDAC,CDCnC" }
package/testing.js CHANGED
@@ -1,193 +1,225 @@
1
1
  import {
2
- globalConsole as globalConsole__320, type as type__348, listBuilderAdd as listBuilderAdd_327, listBuilderToList as listBuilderToList_337, listedJoin as listedJoin_346, pairConstructor as pairConstructor_363, listedMap as listedMap_364, listedGet as listedGet_372
3
- } from "@temperlang/core";
4
- import {
5
- strict as strict__333
2
+ strict as strict__11
6
3
  } from "assert";
7
- /** @type {Console_321} */
8
- const console_319 = globalConsole__320;
9
- export class Test extends type__348() {
4
+ import {
5
+ type as type__25, listBuilderAdd as listBuilderAdd_5, listBuilderToList as listBuilderToList_15, listedJoin as listedJoin_23, pairConstructor as pairConstructor_40, listedMap as listedMap_41, listedReduceFrom as listedReduceFrom_52, stringSplit as stringSplit_57, listedGet as listedGet_62
6
+ } from "@temperlang/core";
7
+ export class Test extends type__25() {
10
8
  /**
11
- * @param {boolean} success_323
12
- * @param {() => string} message_324
9
+ * @param {boolean} success_1
10
+ * @param {() => string} message_2
13
11
  */
14
- assert(success_323, message_324) {
15
- let t_325;
16
- if (! success_323) {
17
- this.#_passing_326 = false;
18
- t_325 = message_324();
19
- listBuilderAdd_327(this.#_messages_328, t_325);
12
+ assert(success_1, message_2) {
13
+ let t_3;
14
+ if (! success_1) {
15
+ this.#_passing_4 = false;
16
+ t_3 = message_2();
17
+ listBuilderAdd_5(this.#_messages_6, t_3);
20
18
  }
21
19
  return;
22
20
  }
23
21
  /**
24
- * @param {boolean} success_330
25
- * @param {() => string} message_331
22
+ * @param {boolean} success_8
23
+ * @param {() => string} message_9
26
24
  * @returns {void}
27
25
  */
28
- assertHard(success_330, message_331) {
29
- this.assert(success_330, message_331);
30
- if (! success_330) {
31
- this.#_failedOnAssert_332 = true;
32
- strict__333.fail(this.messagesCombined());
26
+ assertHard(success_8, message_9) {
27
+ this.assert(success_8, message_9);
28
+ if (! success_8) {
29
+ this.#_failedOnAssert_10 = true;
30
+ strict__11.fail(this.messagesCombined());
33
31
  }
34
32
  return;
35
33
  }
36
34
  /** @returns {void} */
37
35
  softFailToHard() {
38
36
  if (this.hasUnhandledFail) {
39
- this.#_failedOnAssert_332 = true;
40
- strict__333.fail(this.messagesCombined());
37
+ this.#_failedOnAssert_10 = true;
38
+ strict__11.fail(this.messagesCombined());
41
39
  }
42
40
  return;
43
41
  }
44
42
  /** @returns {boolean} */
45
43
  get passing() {
46
- return this.#_passing_326;
44
+ return this.#_passing_4;
47
45
  }
48
46
  /** @returns {Array<string>} */
49
47
  messages() {
50
- return listBuilderToList_337(this.#_messages_328);
48
+ return listBuilderToList_15(this.#_messages_6);
51
49
  }
52
50
  /** @returns {boolean} */
53
51
  get failedOnAssert() {
54
- return this.#_failedOnAssert_332;
52
+ return this.#_failedOnAssert_10;
55
53
  }
56
54
  /** @returns {boolean} */
57
55
  get hasUnhandledFail() {
58
- let t_340;
59
- if (this.#_failedOnAssert_332) {
60
- t_340 = true;
56
+ let t_18;
57
+ if (this.#_failedOnAssert_10) {
58
+ t_18 = true;
61
59
  } else {
62
- t_340 = this.#_passing_326;
60
+ t_18 = this.#_passing_4;
63
61
  }
64
- return ! t_340;
62
+ return ! t_18;
65
63
  }
66
64
  /** @returns {string | null} */
67
65
  messagesCombined() {
68
- let return_342;
69
- let t_343;
70
- if (! this.#_messages_328.length) {
71
- return_342 = null;
66
+ let return_20;
67
+ if (! this.#_messages_6.length) {
68
+ return_20 = null;
72
69
  } else {
73
- function fn_344(it_345) {
74
- return it_345;
70
+ function fn_21(it_22) {
71
+ return it_22;
75
72
  }
76
- t_343 = listedJoin_346(this.#_messages_328, ", ", fn_344);
77
- return_342 = t_343;
73
+ return_20 = listedJoin_23(this.#_messages_6, ", ", fn_21);
78
74
  }
79
- return return_342;
75
+ return return_20;
80
76
  }
81
77
  /** @type {boolean} */
82
- #_failedOnAssert_332;
78
+ #_failedOnAssert_10;
83
79
  /** @type {boolean} */
84
- #_passing_326;
80
+ #_passing_4;
85
81
  /** @type {Array<string>} */
86
- #_messages_328;
82
+ #_messages_6;
87
83
  constructor() {
88
84
  super ();
89
- this.#_failedOnAssert_332 = false;
90
- this.#_passing_326 = true;
91
- let t_347 = [];
92
- this.#_messages_328 = t_347;
85
+ this.#_failedOnAssert_10 = false;
86
+ this.#_passing_4 = true;
87
+ let t_24 = [];
88
+ this.#_messages_6 = t_24;
93
89
  return;
94
90
  }
95
91
  };
96
- /** @type {Type_349} */
97
- export const TestName = "String: Type";
98
- /** @type {Type_349} */
99
- export const TestFun = "fn (Test): (Void | Bubble): Type";
100
- /** @type {Type_349} */
101
- export const TestCase = "Pair\u003cString, fn (Test): (Void | Bubble)\u003e: Type";
102
- /** @type {Type_349} */
103
- export const TestFailureMessage = "String: Type";
104
- /** @type {Type_349} */
105
- export const TestResult = "Pair\u003cString, List\u003cString\u003e\u003e: Type";
106
92
  /**
107
- * @param {Array<Pair_365<string, (arg0: Test) => void>>} testCases_350
108
- * @returns {Array<Pair_365<string, Array<string>>>}
93
+ * @param {Array<Pair_42<string, (arg0: Test) => void>>} testCases_26
94
+ * @returns {Array<Pair_42<string, Array<string>>>}
109
95
  */
110
- export function processTestCases(testCases_350) {
111
- function fn_351(testCase_352) {
112
- let t_353;
113
- let t_354;
114
- let t_355;
115
- const key_356 = testCase_352.key;
116
- const fun_357 = testCase_352.value;
117
- const test_358 = new Test();
118
- let hadBubble_359;
96
+ export function processTestCases(testCases_26) {
97
+ function fn_27(testCase_28) {
98
+ let t_29;
99
+ let t_30;
100
+ let t_31;
101
+ let t_32;
102
+ const key_33 = testCase_28.key;
103
+ const fun_34 = testCase_28.value;
104
+ const test_35 = new Test();
105
+ let hadBubble_36 = false;
119
106
  try {
120
- fun_357(test_358);
121
- hadBubble_359 = false;
107
+ fun_34(test_35);
122
108
  } catch {
123
- hadBubble_359 = true;
109
+ hadBubble_36 = true;
110
+ }
111
+ const messages_37 = test_35.messages();
112
+ let failures_38;
113
+ if (test_35.passing) {
114
+ t_31 = ! hadBubble_36;
115
+ } else {
116
+ t_31 = false;
124
117
  }
125
- const messages_360 = test_358.messages();
126
- let failures_361;
127
- if (test_358.passing) {
128
- failures_361 = Object.freeze([]);
118
+ if (t_31) {
119
+ failures_38 = Object.freeze([]);
129
120
  } else {
130
- if (hadBubble_359) {
131
- t_353 = test_358.failedOnAssert;
132
- t_355 = ! t_353;
121
+ if (hadBubble_36) {
122
+ t_29 = test_35.failedOnAssert;
123
+ t_32 = ! t_29;
133
124
  } else {
134
- t_355 = false;
125
+ t_32 = false;
135
126
  }
136
- if (t_355) {
137
- const allMessages_362 = messages_360.slice();
138
- listBuilderAdd_327(allMessages_362, "Bubble");
139
- t_354 = listBuilderToList_337(allMessages_362);
140
- failures_361 = t_354;
127
+ if (t_32) {
128
+ const allMessages_39 = messages_37.slice();
129
+ listBuilderAdd_5(allMessages_39, "Bubble");
130
+ t_30 = listBuilderToList_15(allMessages_39);
131
+ failures_38 = t_30;
141
132
  } else {
142
- failures_361 = messages_360;
133
+ failures_38 = messages_37;
143
134
  }
144
135
  }
145
- return pairConstructor_363(key_356, failures_361);
136
+ return pairConstructor_40(key_33, failures_38);
146
137
  }
147
- return listedMap_364(testCases_350, fn_351);
138
+ return listedMap_41(testCases_26, fn_27);
148
139
  };
149
- /** @param {Array<Pair_365<string, Array<string>>>} testResults_366 */
150
- export function reportTestResults(testResults_366) {
151
- let t_367;
152
- let t_368;
153
- let t_369;
154
- let t_370;
155
- let i_371 = 0;
140
+ /**
141
+ * @param {Array<Pair_42<string, Array<string>>>} testResults_43
142
+ * @param {(arg0: string) => void} writeLine_44
143
+ */
144
+ export function reportTestResults(testResults_43, writeLine_44) {
145
+ let t_45;
146
+ writeLine_44("\u003ctestsuites\u003e");
147
+ const total_46 = testResults_43.length.toString();
148
+ function fn_47(fails_48, testResult_49) {
149
+ let t_50;
150
+ if (! testResult_49.value.length) {
151
+ t_50 = 0;
152
+ } else {
153
+ t_50 = 1;
154
+ }
155
+ return fails_48 + t_50 | 0;
156
+ }
157
+ const fails_51 = listedReduceFrom_52(testResults_43, 0, fn_47).toString();
158
+ const totals_53 = "tests='" + total_46 + "' failures='" + fails_51 + "'";
159
+ writeLine_44(" \u003ctestsuite name='suite' " + totals_53 + " time='0.0'\u003e");
160
+ function escape_54(s_55) {
161
+ let t_56 = stringSplit_57(s_55, "'");
162
+ function fn_58(x_59) {
163
+ return x_59;
164
+ }
165
+ return listedJoin_23(t_56, "\u0026apos;", fn_58);
166
+ }
167
+ let i_60 = 0;
156
168
  while (true) {
157
- t_367 = testResults_366.length;
158
- if (!(i_371 < t_367)) {
169
+ t_45 = testResults_43.length;
170
+ if (!(i_60 < t_45)) {
159
171
  break;
160
172
  }
161
- t_370 = listedGet_372(testResults_366, i_371);
162
- const testResult_373 = t_370;
163
- const failureMessages_374 = testResult_373.value;
164
- if (! failureMessages_374.length) {
165
- t_368 = testResult_373.key;
166
- console_319.log(String(t_368) + ": Passed");
173
+ const testResult_61 = listedGet_62(testResults_43, i_60);
174
+ const failureMessages_63 = testResult_61.value;
175
+ const name_64 = escape_54(testResult_61.key);
176
+ const basics_65 = "name='" + name_64 + "' classname='" + name_64 + "' time='0.0'";
177
+ if (! failureMessages_63.length) {
178
+ writeLine_44(" \u003ctestcase " + basics_65 + " /\u003e");
167
179
  } else {
168
- function fn_375(it_376) {
169
- return it_376;
180
+ writeLine_44(" \u003ctestcase " + basics_65 + "\u003e");
181
+ function fn_66(it_67) {
182
+ return it_67;
170
183
  }
171
- const message_377 = listedJoin_346(failureMessages_374, ", ", fn_375);
172
- t_369 = testResult_373.key;
173
- console_319.log(String(t_369) + ": Failed " + message_377);
184
+ const message_68 = escape_54(listedJoin_23(failureMessages_63, ", ", fn_66));
185
+ writeLine_44(" \u003cfailure message='" + message_68 + "' /\u003e");
186
+ writeLine_44(" \u003c/testcase\u003e");
174
187
  }
175
- i_371 = i_371 + 1;
188
+ i_60 = i_60 + 1 | 0;
176
189
  }
190
+ writeLine_44(" \u003c/testsuite\u003e");
191
+ writeLine_44("\u003c/testsuites\u003e");
177
192
  return;
178
193
  };
179
- /** @param {Array<Pair_365<string, (arg0: Test) => void>>} testCases_378 */
180
- export function runTestCases(testCases_378) {
181
- reportTestResults(processTestCases(testCases_378));
182
- return;
194
+ /**
195
+ * @param {Array<Pair_42<string, (arg0: Test) => void>>} testCases_69
196
+ * @returns {string}
197
+ */
198
+ export function runTestCases(testCases_69) {
199
+ const report_70 = [""];
200
+ let t_71 = processTestCases(testCases_69);
201
+ function fn_72(line_73) {
202
+ report_70[0] += line_73;
203
+ report_70[0] += "\n";
204
+ return;
205
+ }
206
+ reportTestResults(t_71, fn_72);
207
+ return report_70[0];
183
208
  };
184
209
  /**
185
- * @param {(arg0: Test) => void} testFun_379
210
+ * @param {(arg0: Test) => void} testFun_74
186
211
  * @returns {void}
187
212
  */
188
- export function runTest(testFun_379) {
189
- const test_380 = new Test();
190
- testFun_379(test_380);
191
- test_380.softFailToHard();
213
+ export function runTest(testFun_74) {
214
+ const test_75 = new Test();
215
+ try {
216
+ testFun_74(test_75);
217
+ } catch {
218
+ function fn_76() {
219
+ return "bubble during test running";
220
+ }
221
+ test_75.assert(false, fn_76);
222
+ }
223
+ test_75.softFailToHard();
192
224
  return;
193
225
  };