|
| 1 | +/* |
| 2 | + * Lightweight source checks used by the interactive exercises. |
| 3 | + * |
| 4 | + * These remain regular expressions rather than a Java parser, but the helpers |
| 5 | + * below make equivalent refinements explicit: operands and boolean clauses may |
| 6 | + * be reordered, and a refined local/parameter may be written as either `_` or |
| 7 | + * its declared name. |
| 8 | + */ |
| 9 | + |
| 10 | +const whitespace = "\\s*"; |
| 11 | + |
| 12 | +function optionalParens(pattern) { |
| 13 | + return `(?:${pattern}|\\(${whitespace}${pattern}${whitespace}\\))`; |
| 14 | +} |
| 15 | + |
| 16 | +function comparison(left, operator, right) { |
| 17 | + return `${left}${whitespace}${operator}${whitespace}${right}`; |
| 18 | +} |
| 19 | + |
| 20 | +function orderedComparison(value, operator, bound, reversedOperator) { |
| 21 | + return `(?:${comparison(value, operator, bound)}|${comparison(bound, reversedOperator, value)})`; |
| 22 | +} |
| 23 | + |
| 24 | +function equality(left, right) { |
| 25 | + return `(?:${comparison(left, "==", right)}|${comparison(right, "==", left)})`; |
| 26 | +} |
| 27 | + |
| 28 | +function conjunction(first, second) { |
| 29 | + const left = optionalParens(first); |
| 30 | + const right = optionalParens(second); |
| 31 | + return optionalParens(`(?:${left}${whitespace}&&${whitespace}${right}|${right}${whitespace}&&${whitespace}${left})`); |
| 32 | +} |
| 33 | + |
| 34 | +function permutations(values) { |
| 35 | + if (values.length <= 1) return [values]; |
| 36 | + return values.flatMap((value, index) => |
| 37 | + permutations(values.filter((_, candidateIndex) => candidateIndex !== index)).map((rest) => [value, ...rest]), |
| 38 | + ); |
| 39 | +} |
| 40 | + |
| 41 | +function disjunction(values) { |
| 42 | + const alternatives = permutations(values).map((items) => |
| 43 | + items.map(optionalParens).join(`${whitespace}\\|\\|${whitespace}`), |
| 44 | + ); |
| 45 | + return optionalParens(`(?:${alternatives.join("|")})`); |
| 46 | +} |
| 47 | + |
| 48 | +function quoted(expression) { |
| 49 | + return `"${whitespace}${expression}${whitespace}"`; |
| 50 | +} |
| 51 | + |
| 52 | +function refinement(expression, declaration) { |
| 53 | + return `@Refinement${whitespace}\\(${whitespace}${quoted(expression)}${whitespace}\\)${whitespace}${declaration}`; |
| 54 | +} |
| 55 | + |
| 56 | +function namedArgument(name, expression) { |
| 57 | + return `${name}${whitespace}=${whitespace}${quoted(expression)}`; |
| 58 | +} |
| 59 | + |
| 60 | +function stateRefinement(argumentsPattern, declaration) { |
| 61 | + return `@StateRefinement${whitespace}\\(${whitespace}${argumentsPattern}${whitespace}\\)${whitespace}${declaration}`; |
| 62 | +} |
| 63 | + |
| 64 | +function transition(from, to, declaration) { |
| 65 | + const fromArgument = namedArgument("from", from); |
| 66 | + const toArgument = namedArgument("to", to); |
| 67 | + const argumentsPattern = `(?:${fromArgument}${whitespace},${whitespace}${toArgument}|${toArgument}${whitespace},${whitespace}${fromArgument})`; |
| 68 | + return stateRefinement(argumentsPattern, declaration); |
| 69 | +} |
| 70 | + |
| 71 | +function fromWithOptionalSameTo(state, declaration) { |
| 72 | + const fromArgument = namedArgument("from", state); |
| 73 | + const toArgument = namedArgument("to", state); |
| 74 | + const argumentsPattern = `(?:${fromArgument}|${fromArgument}${whitespace},${whitespace}${toArgument}|${toArgument}${whitespace},${whitespace}${fromArgument})`; |
| 75 | + return stateRefinement(argumentsPattern, declaration); |
| 76 | +} |
| 77 | + |
| 78 | +const rgbValue = "(?:_|red)"; |
| 79 | +const rgbLower = orderedComparison(rgbValue, ">=", "0", "<="); |
| 80 | +const rgbUpper = orderedComparison(rgbValue, "<=", "255", ">="); |
| 81 | + |
| 82 | +const lowValue = "(?:_|low)"; |
| 83 | +const lowBeforeHigh = orderedComparison(lowValue, "<=", "high", ">="); |
| 84 | + |
| 85 | +const midpointNormalLower = orderedComparison("_", ">=", "low", "<="); |
| 86 | +const midpointNormalUpper = orderedComparison("_", "<=", "high", ">="); |
| 87 | +const midpointReversedLower = orderedComparison("_", ">=", "high", "<="); |
| 88 | +const midpointReversedUpper = orderedComparison("_", "<=", "low", ">="); |
| 89 | +const midpointRange = `(?:${conjunction(midpointNormalLower, midpointNormalUpper)}|${conjunction(midpointReversedLower, midpointReversedUpper)})`; |
| 90 | + |
| 91 | +const state = (name) => `${name}${whitespace}\\(${whitespace}this${whitespace}\\)`; |
| 92 | +const unconnected = state("unconnected"); |
| 93 | +const bound = state("bound"); |
| 94 | +const connected = state("connected"); |
| 95 | +const closed = state("closed"); |
| 96 | +const notClosed = `!${whitespace}${closed}`; |
| 97 | +const anyOpenState = `(?:${notClosed}|${disjunction([unconnected, bound, connected])})`; |
| 98 | + |
| 99 | +const sizeNow = `size${whitespace}\\(${whitespace}this${whitespace}\\)`; |
| 100 | +const sizeOld = `size${whitespace}\\(${whitespace}old${whitespace}\\(${whitespace}this${whitespace}\\)${whitespace}\\)`; |
| 101 | +const zeroSize = equality(sizeNow, "0"); |
| 102 | +const positiveSize = orderedComparison(sizeNow, ">", "0", "<"); |
| 103 | +const incrementedSize = equality(sizeNow, `(?:${comparison(sizeOld, "\\+", "1")}|${comparison("1", "\\+", sizeOld)})`); |
| 104 | +const decrementedSize = equality(sizeNow, comparison(sizeOld, "-", "1")); |
| 105 | + |
| 106 | +export const validationRegexes = Object.freeze({ |
| 107 | + rgb: Object.freeze({ |
| 108 | + refinement: refinement(conjunction(rgbLower, rgbUpper), `int\\s+red\\b`), |
| 109 | + assignment: "int\\s+red\\s*=\\s*(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\s*;", |
| 110 | + }), |
| 111 | + midpoint: Object.freeze({ |
| 112 | + result: refinement(midpointRange, "public\\s+static\\s+int\\s+midpoint\\b"), |
| 113 | + bounds: refinement(lowBeforeHigh, "int\\s+low\\b"), |
| 114 | + }), |
| 115 | + socket: Object.freeze({ |
| 116 | + bind: transition(unconnected, bound, "public\\s+void\\s+bind\\b"), |
| 117 | + connect: transition(bound, connected, "public\\s+void\\s+connect\\b"), |
| 118 | + sendUrgentData: fromWithOptionalSameTo(connected, "public\\s+void\\s+sendUrgentData\\b"), |
| 119 | + close: transition(anyOpenState, closed, "public\\s+void\\s+close\\b"), |
| 120 | + }), |
| 121 | + stack: Object.freeze({ |
| 122 | + constructor: stateRefinement(namedArgument("to", zeroSize), "public\\s+void\\s+Stack\\b"), |
| 123 | + push: stateRefinement(namedArgument("to", incrementedSize), "public\\s+E\\s+push\\b"), |
| 124 | + pop: transition(positiveSize, decrementedSize, "public\\s+E\\s+pop\\b"), |
| 125 | + peek: stateRefinement(namedArgument("from", positiveSize), "public\\s+E\\s+peek\\b"), |
| 126 | + }), |
| 127 | +}); |
0 commit comments