$TestsOnly$ define TestData { @value String name @value String description @value Bool boolean create (name, description, boolean) { return TestData{ name, description, boolean } } name () { return name } description () { return description } boolean () { return boolean } // From TestCompare. testCompare (actual, report) { \ MultiChecker.new(report) .tryCheck( title: "name", actual.name(), CheckValue:equals(name)) .tryCheck( title: "description", actual.description(), CheckValue:equals(description)) .tryCheck( title: "boolean", actual.boolean(), CheckValue:equals(boolean)) } } define TestDataParser { // Mark @category members as read-only, to avoid accidental assignment. $ReadOnlyExcept[]$ // Mark @category members as hidden, since they should not be used directly. $Hidden[whitespace, sentenceChars, sentence, quote, quotedSentence, token, acronym, aardvark]$ refines Parser @category Parser whitespace <- SequenceOfParser.create(" \n\t", min: 1, max: 0) `Parse.or` Parse.error("Expected whitespace") @category String sentenceChars <- "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "., !?-" @category Parser sentence <- SequenceOfParser.create(sentenceChars, min: 0, max: 0) @category Parser quote <- CharParser.create('"') @category Parser quotedSentence <- quote `Parse.right` sentence `Parse.left` quote @category Parser token <- SequenceOfParser.create("ABCDEFGHIJKLMNOPQRSTUVWXYZ_", min: 1, max: 0) @category Parser sentenceOrToken <- quotedSentence `Parse.or` token `Parse.left` whitespace @category Parser acronym <- StringParser.create("acronym") `Parse.right` Parse.const(true) @category Parser aardvark <- StringParser.create("aardvark") `Parse.right` Parse.const(false) @category Parser acronymOrAardvark <- Parse.try(acronym) `Parse.or` aardvark `Parse.left` whitespace @category Parser fileStart <- StringParser.create("file_start") `Parse.left` whitespace @category Parser fileEnd <- StringParser.create("file_end") `Parse.left` whitespace @category Parser nameTag <- StringParser.create("name:") `Parse.left` whitespace @category Parser descriptionTag <- StringParser.create("description:") `Parse.left` whitespace @category Parser aWordTag <- StringParser.create("a_word:") `Parse.left` whitespace run (contextOld) { ParseContext context <- contextOld context <- context.run(fileStart) context <- context.run(nameTag) context, ErrorOr name <- context.runAndGet(sentenceOrToken) context <- context.run(descriptionTag) context, ErrorOr description <- context.runAndGet(sentenceOrToken) context <- context.run(aWordTag) context, ErrorOr boolean <- context.runAndGet(acronymOrAardvark) context <- context.run(fileEnd) if (context.hasAnyError()) { return context.convertError() } else { return context.setValue( // Since TestData.create specifies labels, we _must_ use them here. ErrorOr:value(TestData.create( name: name.getValue(), description: description.getValue(), boolean: boolean.getValue()))) } } create () { return delegate -> #self } }