Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | 1x 1x 14x 13x 6x 8x 14x 14x 27x 14x 18x 36x 18x 18x 30x 36x 30x 30x 30x 60x 14x 14x 7x 7x 7x 14x 18x 18x 18x 36x 36x 28x 8x 4x 8x 4x 13x 13x 4x 6x 6x 4x 4x 4x 4x 8x 4x 8x 8x | import { ASTNode } from './ast'; import { hashObject } from './utils'; // A NodeSpec declares the types of the fields of an ASTNode. // It is used to compute hashes, to validate nodes (to prevent the construction // of an ill-formed AST), and to deal with some edits. // Its constructor expects a list of: // // - required: an ASTNode. // - optional: either an ASTNode, or `null`. // - list: an array of ASTNodes. // - value: an ordinary value that does not contain an ASTNode. // nodeSpec :: Array<ChildSpec> -> NodeSpec export function nodeSpec(childSpecs) { return new NodeSpec(childSpecs); } // required :: String -> ChildSpec export function required(fieldName) { return new Required(fieldName); } // optional :: String -> ChildSpec export function optional(fieldName) { return new Optional(fieldName); } // list :: String -> ChildSpec export function list(fieldName) { return new List(fieldName); } // value :: any -> ChildSpec export function value(fieldName) { return new Value(fieldName); } class NodeSpec { constructor(childSpecs) { Iif (!(childSpecs instanceof Array)) { throw new Error("NodeSpec: expected to receive an array of required/optional/list specs."); } for (const childSpec of childSpecs) { Iif (!(childSpec instanceof ChildSpec)) { throw new Error("NodeSpec: all child specs must be created by one of the functions: required/optional/list."); } } this.childSpecs = childSpecs; } validate(node) { for (const childSpec of this.childSpecs) { childSpec.validate(node); } } hash(node) { let hashes = new HashIterator(node, this); return hashObject([node.type, [...hashes]]); } children(node) { return new ChildrenIterator(node, this); } fieldNames() { return this.childSpecs.map((spec) => spec.fieldName); } } class ChildrenIterator { constructor(parent, nodeSpec) { this.parent = parent; this.nodeSpec = nodeSpec; } *[Symbol.iterator]() { for (let spec of this.nodeSpec.childSpecs) { if (spec instanceof Value) continue; let field = this.parent[spec.fieldName]; if (field instanceof ASTNode) { yield field; } else Eif (field instanceof Array) { for (let elem of field) { yield elem; } } } } } class HashIterator { constructor(parent, nodeSpec) { this.parent = parent; this.nodeSpec = nodeSpec; } *[Symbol.iterator]() { for (let spec of this.nodeSpec.childSpecs) { let field = this.parent[spec.fieldName]; if (spec instanceof Value) { yield hashObject(field); } else if (spec instanceof List) { for (let elem of field) { yield elem.hash; } } else { yield (field == null)? hashObject(null) : field.hash; } } } } class ChildSpec {} export class Required extends ChildSpec { constructor(fieldName) { super(); this.fieldName = fieldName; } validate(parent) { Iif (!(parent[this.fieldName] instanceof ASTNode)) { throw new Error(`Expected the required field '${this.fieldName}' of '${parent.type}' to contain an ASTNode.`); } } } export class Optional extends ChildSpec { constructor(fieldName) { super(); this.fieldName = fieldName; } validate(parent) { let child = parent[this.fieldName]; if (child !== null && !(child instanceof ASTNode)) { throw new Error(`Expected the optional field '${this.fieldName}' of '${parent.type}' to contain an ASTNode or null.`); } } } export class List extends ChildSpec { constructor(fieldName) { super(); this.fieldName = fieldName; } validate(parent) { let array = parent[this.fieldName]; let valid = true; Eif (array instanceof Array) { for (const elem of array) { Iif (!(elem instanceof ASTNode)) valid = false; } } else { valid = false; } Iif (!valid) { throw new Error(`Expected the listy field '${this.fieldName}' of '${parent.type}' to contain an array of ASTNodes.`); } } } export class Value extends ChildSpec { constructor(fieldName) { super(); this.fieldName = fieldName; } validate(_parent) { // Any value is valid, even `undefined`, so there's nothing to check. } } |