Skip to content

Commit c517bcc

Browse files
Implement definitions in checker.js (#470)
One problem with regexes is that by default they aren't able to have named definitions like BNF. Solve this by implementing "definitions". This only adds a few more lines, but now we have a powerful way to create definitions, while still being fast in execution. Signed-off-by: David A. Wheeler <[email protected]>
1 parent 4f81264 commit c517bcc

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

docs/labs/checker.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ let correctRe = []; // Array of compiled regex of correct answer
1010
let expected = []; // Array of an expected (correct) answer
1111
let info = {}; // General info
1212
let hints = []; // Array of hint objects
13+
let page_definitions = {}; // Definitions used when preprocessing regexes
1314

1415
// This array contains the default pattern preprocessing commands, in order.
1516
// We process every pattern through these (in order) to create a final regex
@@ -57,6 +58,18 @@ function trimNewlines(s) {
5758
.replace(/[\n\r]+$/, ''));
5859
}
5960

61+
/**
62+
* Apply all page_definitions to string s (which is presumably a regex
63+
* in string form). These are simple text replacements.
64+
*/
65+
function processDefinitions(s) {
66+
let result = s;
67+
for (definition in page_definitions) {
68+
result = result.replaceAll(definition, page_definitions[definition]);
69+
};
70+
return result;
71+
}
72+
6073
/**
6174
* Escape unsafe HTML, e.g., & becomes &amp;
6275
*/
@@ -107,12 +120,16 @@ function showDebugOutput(debugOutput, alwaysAlert = true) {
107120

108121
/**
109122
* Given take a regex string, preprocess it (using our array of
110-
* preprocessing regexes), and return a processed regex as a String.
123+
* definitions and preprocessing regexes),
124+
* and return a processed regex as a String.
111125
* @regexString - String to be converted into a compiled Regex
112126
* @fullMatch - require full match (insert "^" at beginning, "$" at end).
113127
*/
114128
function processRegexToString(regexString, fullMatch = true) {
115-
let processedRegexString = regexString;
129+
// Replace all definitions. This makes regexes much easier to use,
130+
// as we can now defined named fragments.
131+
let processedRegexString = processDefinitions(regexString);
132+
// Preprocess. This lets us define what whitespace etc. means.
116133
for (preprocessRegex of preprocessRegexes) {
117134
processedRegexString = processedRegexString.replace(
118135
preprocessRegex[0], preprocessRegex[1]
@@ -440,14 +457,21 @@ function processInfo(configurationInfo) {
440457

441458
const allowedInfoFields = new Set([
442459
'hints', 'successes', 'failures', 'correct', 'expected',
443-
'preprocessing', 'preprocessingTests', 'debug']);
460+
'definitions', 'preprocessing', 'preprocessingTests', 'debug']);
444461
let usedFields = new Set(Object.keys(info));
445462
let forbiddenFields = setDifference(usedFields, allowedInfoFields);
446463
if (forbiddenFields.size != 0) {
447464
showDebugOutput(
448465
`Unknown field(s) in info: ` +
449466
Array.from(forbiddenFields).join(' '));
450467
}
468+
if (info.definitions) {
469+
for (let definition of info.definitions) {
470+
// Preprocess with all existing definitions
471+
newValue = trimNewlines(processDefinitions(definition.value));
472+
page_definitions[definition.term] = newValue;
473+
}
474+
}
451475

452476
// Set up pattern preprocessing, if set. ADVANCED USERS ONLY.
453477
// This must be done *before* we load & process any other patterns.

docs/labs/create_checker.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,40 @@ lab and each different natural language.
558558
For each button you should set the `title` attribute for the
559559
given language.
560560

561+
### Advanced use: Definitions
562+
563+
Regular expressions make it easy to describe many patterns.
564+
However, it's sometimes useful to give certain sequences names, or
565+
use the same sequence in different circumstances.
566+
567+
Checker allows you to define named terms, and then use them in a regular
568+
expression.
569+
This is done in the `definitions` section, which is a sequence
570+
of a `term` name and its corresponding `value`.
571+
Any use of the same term in a later definition or a regular expression
572+
will replaced by its current definition.
573+
Leading and trailing whitespace in the value is removed.
574+
575+
Here's an example:
576+
577+
~~~~yaml
578+
definitions:
579+
- term: RETURN0
580+
value: |
581+
return \s+ 0 ;
582+
- term: RETURN0
583+
value: |
584+
(RETURN0|\{ RETURN0 \})
585+
~~~~
586+
587+
The first entry defines `RETURN0` as the value `\s+ 0 ;`
588+
so any future use of RETURN0 will be replaced by that.
589+
The next entry uses the *same* term name, and declares it to be
590+
`(RETURN0|\{ RETURN0 \})`.
591+
The result is that the new value for `RETURN0` will be
592+
`(\s+ 0 ;|\{ \s+ 0 ; \})` - enabling us to have
593+
an expression <i>optionally</i> surrounded by curly braces.
594+
561595
### Advanced use: Select preprocessing commands (e.g., for other languages)
562596

563597
For most programming languages the default regex preprocessing

docs/labs/oob1.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@
2828
\s*
2929
if \s+ \(
3030
(1 \+ 2 \+ 16|19) > s -> s3 -> rrec \. length \)
31-
\s+ return \s+ 0 ;
31+
\s+ RETURN0
3232
\s*
3333
</script>
3434
<script id="correct1" type="plain/text">
3535
\s*
3636
if \s+ \( (1 \+ 2|3) \+ payload \+ 16 > s -> s3 -> rrec \. length \)
37-
\s+ return \s+ 0 ;
37+
\s+ RETURN0
3838
\s*
3939
</script>
4040
<!--
@@ -57,6 +57,13 @@
5757
- absent: |
5858
\(
5959
text: Need "(...)" around the condition after an if statement.
60+
definitions:
61+
- term: RETURN0
62+
value: |
63+
return \s+ 0 ;
64+
- term: RETURN0
65+
value: |
66+
(RETURN0|\{ RETURN0 \})
6067
# - present: "import"
6168
# text: Yes, many JavaScript implementations support an import statement.
6269
# However, in this exercise we will use a require form. Please use that

0 commit comments

Comments
 (0)