packages feed

zeolite-lang 0.24.0.1 → 0.24.1.0

raw patch · 31 files changed

+2801/−450 lines, 31 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- CompilerCxx.Procedure: compileLazyInit :: (Ord c, Show c, CollectErrorsM m, CompilerContext c m [String] a) => DefinedMember c -> CompilerState a m ()
+ CompilerCxx.Procedure: compileLazyInit :: (Ord c, Show c, CollectErrorsM m, CompilerContext c m [String] a) => CategoryName -> DefinedMember c -> CompilerState a m ()

Files

ChangeLog.md view
@@ -1,5 +1,41 @@ # Revision history for zeolite-lang +## 0.24.1.0  -- 2024-01-07++### Language++* **[fix]** Fixes parsing of `<` expressions when the left side is an explicit+  type conversion, e.g., `"123"?String < "456"`. Previously, the `<` was assumed+  to be the start of a param substitution.++  Note that this shouldn't ever be needed because `<` only supports built-in+  `concrete` types. (Any value with a union/intersection type convertible to one+  of those types already simplifies to that type, making the union/intersection+  superfluous.)++* **[new]** Adds the `<||` operator to use the left value unless it's `empty`.++```text+optional Int value <- empty+// value is preferred, but 123 is used because value is empty.+Int other <- value <|| 123+```++* **[behavior]** Adds source context to traces for crashes during initialization+  of `@category` members.++### Libraries++* **[fix]** Fixes `CheckSequence:using` (in `lib/testing`) when using an empty+  list.++* **[new]** Adds `AppendH` helpers to `lib/util`.++### Examples++* Adds `example/highlighter`, as both a code example and a highlighted-HTML+  generator for Zeolite source files.+ ## 0.24.0.1  -- 2023-12-10  ### Language
base/category-source.hpp view
@@ -190,7 +190,12 @@     BoxedValue::Present(result) \         ? TypeValue::Call(result, func, args) \         : ReturnTuple(count); \-    })+  })++#define TYPE_VALUE_LEFT_UNLESS_EMPTY(left, right) ({ \+    const BoxedValue result = left; \+    BoxedValue::Present(result) ? result : (right); \+  })  class TypeValue {  public:
example/highlighter/README.md view
@@ -1,10 +1,12 @@ # Zeolite Code Highlighter -This example is a work in progress and doesn't do anything yet. Eventually it-will generate syntax-highlighted HTML from Zeolite source files.+This example is a fully-functional syntax highlighter for Zeolite source files. -The module should still build and the tests should pass, however:+**You can see the highlighting of the code in this example+[here](https://ta0kira.github.io/zeolite/example/highlighter/).** +To build and test the example code:+ ```shell # This is just to locate the example code. Not for normal use! ZEOLITE_PATH=$(zeolite --get-path)@@ -14,4 +16,10 @@  # Run the unit tests. zeolite -p "$ZEOLITE_PATH" -t example/highlighter+```++To generate an HTML file for Zeolite source files (after building the example):++```shell+example/highlighter/ZeoliteHighlight "Your Page Title" your-source-file.0rx ```
example/highlighter/main.0rx view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry+Copyright 2023-2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -21,7 +21,134 @@ }  define ZeoliteHighlight {+  $ReadOnlyExcept[]$++  @category ZeoliteParseContext zeoliteSourceContext <- ZeoliteParseContext.new()+      .include<ZeoliteWhitespace>()+      .include<ZeoliteLineComment>()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteUpperSymbol>()+      // Must be before ZeoliteLowerSymbol.+      .include<ZeoliteTestcase>()+      // Must be before ZeoliteOperator. (Due to : in labels.)+      .include<ZeoliteLowerSymbol>()+      .include<ZeoliteScopeQualifier>()+      // Must be before ZeoliteExtras. (Due to \ in escapes.)+      .include<ZeoliteNumber>()+      .include<ZeoliteOperator>()+      .include<ZeoliteParamName>()+      .include<ZeoliteStringLiteral>()+      .include<ZeoliteCharLiteral>()+      .include<ZeoliteBraceSection>()+      .include<ZeoliteParenSection>()+      .include<ZeoliteSquareSection>()+      .include<ZeolitePragma>()+      .include<ZeoliteExtras>()+   run () {-    // TODO+    if (Argv.global().size() < 3) {+      \ BasicOutput.stderr()+          .append(Argv.global().readAt(0))+          .append(" [page title] [.0r? input files]\n")+      exit(1)+    }+    \ writeHtmlStart(title: Argv.global().readAt(1))+    traverse (Counter.builder().start(2).limit(Argv.global().size()).done() -> Int i) {+      String filename <- Argv.global().readAt(i)+      $ReadOnly[filename]$+      scoped {+        optional String content <- loadFile(filename: filename)+      } in if (`present` content) {+        \ processFile(filename: filename, content: `require` content)+      }+    }+    \ writeHtmlEnd()+  }++  @type writeHtmlStart (String title:) -> ()+  writeHtmlStart (title) {+    Formatted escapedTitle <- HtmlContent.escape(content: title)+    // TODO: Use HtmlContent to format this stuff?+    \ BasicOutput.stdout()+        .append("<!DOCTYPE html>")+        .append("<html>")+        .append("<head>")+        .append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />")+        .append("<meta name=\"Generator\" content=\"ZeoliteHighlighter\" />")+        .append("<title>").append(escapedTitle).append("</title>")+        .append("</head>")+        .append("<body>")+        .append("<h1>").append(escapedTitle).append("</h1>\n")+    \ writeCommand()+    \ BasicOutput.stdout().append("<hr>")+  }++  @type writeCommand () -> ()+  writeCommand () {+    \ BasicOutput.stdout()+        .append("<p>This file was generated by <a href=\"https://github.com/ta0kira/zeolite/tree/master/example/highlighter\" style=\"font-family:monospace;\">ZeoliteHighlight</a>.</p>")+  }++  @type writeHtmlEnd () -> ()+  writeHtmlEnd () {+    \ BasicOutput.stdout()+        .append("</body>")+        .append("</html>")+  }++  @type processFile (String filename:, String content:) -> ()+  processFile (filename, content) {+    TextStream input <- TextStream.new(content)+    $Hidden[content]$+    DefaultOrder<ZeoliteParsed> output <- defer+    \ StreamTokenizer:new(context: zeoliteSourceContext, tokenizer: zeoliteSourceContext.defaultTokenizer())+        .tokenizeAll(input, (output <- Vector<ZeoliteParsed>.new()))++    if (input.atEnd()) {+      \ BasicOutput.stderr()+          .append("Successfully parsed file ")+          .append(filename)+          .append(":\n")+    } else {+      \ BasicOutput.stderr()+          .append("Incomplete parse of file ")+          .append(filename)+          .append(" at line ")+          .append(input.currentLine())+          .append(" char ")+          .append(input.currentChar())+          .append(":\n")+    }++    ZeoliteHtmlFormatter formatter <- ZeoliteHtmlFormatter.new()+    \ BasicOutput.stdout().append("<h2>").append(HtmlContent.escape(content: filename)).append("</h2>\n")+    \ BasicOutput.stdout().append("<pre style=\"background:#fafffa;\">\n")+    traverse (output.defaultOrder() -> ZeoliteParsed parsed) {+      \ BasicOutput.stdout().append(parsed.formatWith(formatter))+    }+    \ BasicOutput.stdout().append("</pre>\n")+  }++  @type loadFile (String filename:) -> (optional String)+  loadFile (filename) {+    \ BasicOutput.stderr()+        .append("Reading file ")+        .append(filename)+        .append("...\n")+    scoped {+      RawFileReader input <- RawFileReader.open(filename)+    } cleanup {+      \ input.freeResource()+    } in if (`present` input.getFileError()) {+      \ BasicOutput.stderr()+          .append("Error reading file ")+          .append(filename)+          .append(": ")+          .append(`require` input.getFileError())+          .append("\n")+      return empty+    } else {+      return TextReader.readAll(input)+    }   } }
+ example/highlighter/src/html.0rp view
@@ -0,0 +1,34 @@+/* -----------------------------------------------------------------------------+Copyright 2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$ModuleOnly$++concrete HtmlContent {+  refines Formatted++  @type section (String open:, String close:) -> ([Append<HtmlContent> & Build<HtmlContent>])+  @type tag (String name:) -> (HtmlContent)+  @type escape (String content:) -> (HtmlContent)+  @type raw (String verified:) -> (HtmlContent)+}++concrete ZeoliteHtmlFormatter {+  refines TokenFormatter<String, String, HtmlContent>++  @type new () -> (ZeoliteHtmlFormatter)+}
+ example/highlighter/src/html.0rx view
@@ -0,0 +1,206 @@+/* -----------------------------------------------------------------------------+Copyright 2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++define HtmlContent {+  $ReadOnlyExcept[]$++  @category KVReader<Char, String> charEscapes <- HashedMap<Char, String>.new()+      .set('"', "&quot;")+      .set('#', "&num;")+      .set('$', "&dollar;")+      .set('%', "&percnt;")+      .set('&', "&amp;")+      .set('\'', "&apos;")+      .set('<', "&lt;")+      .set('>', "&gt;")++  @value String content++  formatted () {+    return content+  }++  section (open, close) {+    return delegate -> `HtmlSectionBuilder.new`+  }++  tag (name) {+    return #self{ String.builder()+      .append("<")+      .append(name)+      .append(" />")+      .build() }+  }++  escape (content) {+    [Append<Formatted> & Build<String>] builder <- String.builder()+    traverse (content.defaultOrder() -> Char c) {+      scoped {+        optional String replacement <- charEscapes.get(c)+      } in if (`present` replacement) {+        \ builder.append(`require` replacement)+      } else {+        \ builder.append(c)+      }+    }+    return #self{ builder.build() }+  }++  raw (verified) {+    return #self{ verified }+  }+}++define ZeoliteHtmlFormatter {+  $ReadOnlyExcept[]$++  @category KVReader<String, String> labelToStyle <- HashedMap<String, String>.new()+      .set("ZeoliteError", "color:red; font-weight:bold; text-decoration:underline;")++      .set("ZeoliteBlockComment", "color:DarkGray; font-style:italic;")+      .set("ZeoliteLineComment", "color:DarkGray; font-style:italic;")+      .set("ZeolitePragma", "color:DarkSalmon; font-style:italic;")+      .set("ZeolitePragmaName", "color:DarkSalmon; font-style:italic; font-weight:bold;")+      .set("ZeolitePragmaArg", "color:DarkSalmon; font-style:normal;")++      .set("ZeoliteArgLabel", "color:green;")+      .set("ZeoliteAssignment", "color:blue;")+      .set("ZeoliteDiscard", "color:blue;")+      .set("ZeoliteFunctionCall", "color:blue;")+      .set("ZeoliteTick", "color:blue;")++      .set("ZeoliteBuiltinCategory", "color:DarkBlue; font-weight:bold;")+      .set("ZeoliteBuiltinConstant", "color:DarkCyan; font-weight:bold;")+      .set("ZeoliteIgnore", "color:DarkCyan; font-weight:bold;")+      .set("ZeoliteBuiltinFunction", "color:DarkGreen; font-weight:bold;")+      .set("ZeoliteBuiltinParam", "color:DarkBlue; font-weight:bold;")+      .set("ZeoliteCategoryName", "color:DarkBlue;")+      .set("ZeoliteParamName", "color:DarkBlue;")++      .set("ZeoliteCharLiteral", "color:DarkViolet;")+      .set("ZeoliteStringLiteral", "color:DeepPink;")+      .set("ZeoliteEscapedChar", "color:DarkMagenta;")+      .set("ZeoliteOperator", "color:BlueViolet;")++      .set("ZeoliteNumber", "color:GoldenRod;")+      .set("ZeoliteEscapedNumber", "color:DarkMagenta;")++      .set("ZeoliteContainKeyword", "color:Indigo; font-weight:bold;")+      .set("ZeoliteScopeQualifier", "color:Indigo; font-weight:bold;")+      .set("ZeoliteTypeKeyword", "color:Crimson;")+      .set("ZeoliteControlKeyword", "font-weight:bold;")+      .set("ZeoliteStorageKeyword", "font-style:italic;")++      .set("ZeoliteTestcase", "background:PaleTurquoise;")+      .set("ZeoliteTestcaseKeyword", "color:green;")++  @category SetReader<String> labelsUsingDefault <- HashedSet<String>.new()+      .append("ZeoliteBraceSection")+      .append("ZeoliteCloseDelim")+      .append("ZeoliteComma")+      .append("ZeoliteDoubleQuote")+      .append("ZeoliteFunctionOrVariableName")+      .append("ZeoliteOpenDelim")+      .append("ZeoliteParenSection")+      .append("ZeolitePragmaArgClose")+      .append("ZeolitePragmaArgOpen")+      .append("ZeolitePragmaDelim")+      .append("ZeoliteQuotedChars")+      .append("ZeoliteSingleQuote")+      .append("ZeoliteSquareSection")+      .append("ZeoliteTestcaseSection")+      .append("ZeoliteWhitespace")++  new () {+    return delegate -> #self+  }++  formatLeaf (label, value) {+    scoped {+      optional String style <- getStyle(label)+    } in if (`present` style) {+      return HtmlContent+          .section(open: buildSpanTag(`require` style), close: "</span>")+          .append(HtmlContent.escape(content: value))+          .build()+    } else {+      return HtmlContent.escape(content: value)+    }+  }++  createSection (label) {+    scoped {+      optional String style <- getStyle(label)+    } in if (`present` style) {+      return HtmlContent.section(open: buildSpanTag(`require` style), close: "</span>")+    } else {+      return HtmlContent.section(open: "", close: "")+    }+  }++  @type getStyle (String) -> (optional String)+  getStyle (label) (style) {+    style <- labelToStyle.get(label)+    if (! `present` style && ! labelsUsingDefault.member(label)) {+      \ BasicOutput.stderr()+          .append("HTML style for label \"")+          .append(label)+          .append("\" not found!\n")+    }+  }++  @type buildSpanTag (String) -> (String)+  buildSpanTag (style) {+    return String.builder()+        .append("<span style=\"")+        .append(style)+        .append("\">")+        .build()+  }+}++concrete HtmlSectionBuilder {+  refines Append<HtmlContent>+  refines Build<HtmlContent>++  @type new (String open:, String close:) -> (HtmlSectionBuilder)+}++define HtmlSectionBuilder {+  $ReadOnlyExcept[]$++  @value [Append<Formatted> & Build<String>] content+  @value String close++  new (open, close) {+    return #self{ String.builder().append(open), close }+  }++  append (newContent) {+    \ delegate -> `content.append`+    return self+  }++  build () {+    // We need a new builder so that we don't append close multiple times.+    return HtmlContent.raw(verified: String.builder()+        .append(content.build())+        .append(close)+        .build())+  }+}
example/highlighter/src/tokenizer.0rp view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry+Copyright 2023-2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -18,55 +18,69 @@  $ModuleOnly$ -@value interface TextToken<|#l> {-  immutable+// Tokenizes with context #c and generates values of type #v.+@value interface Tokenizer<#c|#v> {+  tokenize (TextStream, #c) -> (optional #v)+} -  exportWith<#f> (TextFormatter<#l, #f>) -> (#f)+// Supports generic formatting based on label type #l and content type #x.+@value interface FormattedToken<|#l, #x> {+  formatWith<#f> (TokenFormatter<#l, #x, #f>) -> (#f) } -@value interface TextFormatter<#l|#f> {-  format (String content:, #l label:) -> (#f)+// Generates #f values by formatting with label type #l and content type #x.+@value interface TokenFormatter<#l, #x|#f|> {+  formatLeaf (#l label:, #x value:) -> (#f)+  createSection (#l label:) -> ([Append<#f> & Build<#f>]) } +// Input adapter for parsing text input. concrete TextStream {   @type new (String) -> (TextStream) -  @value current () -> (optional Char)-  @value forward () -> (TextStream)-  @value reset () -> (TextStream)-  @value take () -> (String)-  @value preview () -> (String)-  @value atEnd () -> (Bool)-}+  @value current     () -> (optional Char)+  @value tokenSize   () -> (Int)+  @value preview     () -> (String)+  @value currentLine () -> (Int)+  @value currentChar () -> (Int)+  @value atEnd       () -> (Bool) -@value interface Tokenizer<#t, #v> {-  tokenize (TextStream, KVReader<#t, Tokenizer<#t, #v>>) -> (optional #v, optional #t, Int)+  @value forward () -> (TextStream)+  @value reset   () -> (TextStream)+  @value take    () -> (String) } -concrete MultiTokenizer<#t, #v> {-  refines Tokenizer<#t, #v>--  visibility Tokenizer<#t, #v>--  @type new () -> (#self)+// Tokenizes all content in a TextStream.+concrete StreamTokenizer<#c|#v> {+  @category new<#c, #v>  (#c context:, Tokenizer<#c, #v> tokenizer:) -> (StreamTokenizer<#c, #v>) -  @value include (#t) -> (#self)+  @value tokenizeAll (TextStream, Append<#v>) -> (#self) } -concrete StreamTokenizer<#t, #v> {-  @category new<#t, #v>-    #v requires TextToken<any>-  (KVReader<#t, Tokenizer<#t, #v>> tokenizers:, Tokenizer<#t, #v> default:) -> (StreamTokenizer<#t, #v>)+// Serializes parsed data without formatting.+concrete UnformattedFormatter {+  refines TokenFormatter<any, Formatted, String> -  @value tokenizeAll (TextStream, Append<#v>) -> (#self)+  @type new () -> (#self) } +// Helpers for reasoning about Char values. concrete CharType {-  @type lower (Char) -> (Bool)-  @type upper (Char) -> (Bool)-  @type digit (Char) -> (Bool)-  @type alphaNum (Char) -> (Bool)+  @type lower      (Char) -> (Bool)+  @type upper      (Char) -> (Bool)+  @type digit      (Char) -> (Bool)+  @type binDigit   (Char) -> (Bool)+  @type octDigit   (Char) -> (Bool)+  @type hexDigit   (Char) -> (Bool)+  @type alphaNum   (Char) -> (Bool)   @type whitespace (Char) -> (Bool)-  @type oneOf (Char, String) -> (Bool)++  @type binChars () -> (SetReader<Char>)+  @type octChars () -> (SetReader<Char>)+  @type decChars () -> (SetReader<Char>)+  @type hexChars () -> (SetReader<Char>)++  @type oneOf (Char, DefaultOrder<Char>) -> (Bool)+   @type escapeBreaks (DefaultOrder<Char>) -> (String) }
example/highlighter/src/tokenizer.0rx view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry+Copyright 2023-2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -22,9 +22,11 @@   @value String source   @value Int tokenStart   @value Int tokenEnd+  @value Int currentLine+  @value Int currentChar    new (source) {-    return TextStream{ source, 0, 0 }+    return TextStream{ source, 0, 0, 1, 1 }   }    current () (char) {@@ -34,6 +36,10 @@     }   } +  tokenSize () {+    return tokenEnd-tokenStart+  }+   forward () {     if (tokenEnd < source.size()) {       tokenEnd <- tokenEnd+1@@ -48,72 +54,66 @@    take () {     cleanup {+      while (tokenStart < tokenEnd) {+        if (source.readAt(tokenStart) == '\n') {+          currentChar <- 1+          currentLine <- currentLine+1+        } else {+          currentChar <- currentChar+1+        }+      } update {+        tokenStart <- tokenStart+1+      }       tokenStart <- tokenEnd     } in return preview()   }    preview () {-    return source.subSequence(tokenStart, tokenEnd-tokenStart)+    return source.subSequence(tokenStart, tokenSize())   } +  currentLine () {+    return currentLine+  }++  currentChar () {+    return currentChar+  }+   atEnd () {     return tokenStart >= source.size()   } } -define MultiTokenizer {-  $ReadOnlyExcept[]$--  @value [DefaultOrder<#t> & Append<#t>] types-+define UnformattedFormatter {   new () {-    return #self{ Vector<#t>.new() }+    return delegate -> #self   } -  include (type) {-    \ types.append(type)-    return self+  formatLeaf (_, value) {+    return value.formatted()   } -  tokenize (input, tokenizers) (token, nextType, popCount) {-    token <- empty-    nextType <- empty-    popCount <- 0-    traverse (types.defaultOrder() -> #t type) {-      scoped {-        optional Tokenizer<#t, #v> tokenizer <- tokenizers.get(type)-      } in if (`present` tokenizer) {-        token, nextType, popCount <- require(tokenizer).tokenize(input, tokenizers)-      }-    } update {-      if (`present` token) {-        break-      }-    }+  createSection (_) {+    return String.builder()   } }  define StreamTokenizer {   $ReadOnlyExcept[]$ -  @value KVReader<#t, Tokenizer<#t, #v>> tokenizers-  @value Tokenizer<#t, #v> defaultTokenizer+  @value #c context+  @value Tokenizer<#c, #v> tokenizer -  new (tokenizers, defaultTokenizer) {-    return delegate -> StreamTokenizer<#t, #v>+  new (context, tokenizer) {+    return delegate -> StreamTokenizer<#c, #v>   }    tokenizeAll (input, output) {     scoped {-      Stack<Tokenizer<#t, #v>> tokenizerStack <- Vector<Tokenizer<#t, #v>>.new().push(defaultTokenizer)-      Tokenizer<#t, #v> tokenizer <- defaultTokenizer       optional #v token <- empty-      optional #t nextType <- empty-      Int popCount <- 0     } in while (!input.atEnd()) {-      $Hidden[tokenizerStack,defaultTokenizer]$-      $ReadOnly[tokenizer]$-      token, nextType, popCount <- tokenizer.tokenize(input, tokenizers)+      token <- input `tokenizer.tokenize` context     } update {       // Process new token.       if (`present` token) {@@ -122,26 +122,6 @@         // Tokenizing failed => let the caller deal with the rest of the input.         break       }-      // Pop tokenizers.-      while (popCount > 0 && tokenizerStack.size() > 0) {-        tokenizer <- tokenizerStack.pop()-      } update {-        popCount <- popCount-1-      }-      if (popCount > 0) {-        tokenizer <- defaultTokenizer-        popCount <- 0-      }-      // Set new tokenizer.-      scoped {-        optional Tokenizer<#t, #v> nextTokenizer <- empty-        if (`present` nextType) {-          nextTokenizer <- tokenizers.get(`require` nextType)-        }-      } in if (`present` nextTokenizer) {-        \ tokenizerStack.push(tokenizer)-        tokenizer <- `require` nextTokenizer-      }     }     return self   }@@ -155,7 +135,18 @@       .set('\n', "\\n")       .set('\r', "\\r")       .set('\"', "\\\"")+      .set('\\', "\\\\") +  @category SetReader<Char> binChars <- HashedSet<Char>.new() `AppendH:from` "01".defaultOrder()++  @category SetReader<Char> octChars <- HashedSet<Char>.new() `AppendH:from` "01234567".defaultOrder()++  @category SetReader<Char> decChars <- HashedSet<Char>.new() `AppendH:from` "0123456789".defaultOrder()++  @category SetReader<Char> hexChars <- HashedSet<Char>.new() `AppendH:from` "0123456789abcdefABCDEF".defaultOrder()++  @category SetReader<Char> whitespaceChars <- HashedSet<Char>.new() `AppendH:from` "\n\t\r ".defaultOrder()+   lower (c) {     return c >= 'a' && c <= 'z'   }@@ -165,15 +156,43 @@   }    digit (c) {-    return c >= '0' && c <= '9'+    return `decChars.member` c   } +  binDigit (c) {+    return `binChars.member` c+  }++  octDigit (c) {+    return `octChars.member` c+  }++  hexDigit (c) {+    return `hexChars.member` c+  }+   alphaNum (c) {     return lower(c) || upper(c) || digit(c)   }    whitespace (c) {-    return c `oneOf` "\n\t\r "+    return `whitespaceChars.member` c+  }++  binChars () {+    return binChars+  }++  octChars () {+    return octChars+  }++  decChars () {+    return decChars+  }++  hexChars () {+    return hexChars   }    oneOf (c, allowed) (match) {
+ example/highlighter/src/zeolite-parsing.0rp view
@@ -0,0 +1,94 @@+/* -----------------------------------------------------------------------------+Copyright 2023 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$ModuleOnly$++// Hierarchical parsed data from Zeolite source files.+concrete ZeoliteParsed {+  refines Formatted+  refines FormattedToken<String, String>+  refines TestCompare<ZeoliteParsed>++  visibility Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @type leaf (String label:, String content:) -> (ZeoliteParsed)+  @type section (String label:, DefaultOrder<ZeoliteParsed>) -> (ZeoliteParsed)+}++// Singleton tokenizer with a name.+@type interface NamedTokenizer {+  tokenizer () -> (Tokenizer<ZeoliteParseContext, ZeoliteParsed>)+  tokenizerName () -> (String)+}++// Context used when parsing Zeolite source files.+concrete ZeoliteParseContext {+  refines KVReader<String, Tokenizer<ZeoliteParseContext, ZeoliteParsed>>++  @type new () -> (ZeoliteParseContext)++  @value defaultTokenizer () -> (Tokenizer<ZeoliteParseContext, ZeoliteParsed>)++  @value include<#t>+    #t defines NamedTokenizer+  () -> (ZeoliteParseContext)+}++// Delegate to a tokenizer by name.+concrete UseNamedTokenizer {+  @type new<#t>+    #t defines NamedTokenizer+  () -> (Tokenizer<ZeoliteParseContext, ZeoliteParsed>)+}++// Attempts multiple tokenizers in order.+concrete TokenAlternatives {+  refines Append<Tokenizer<ZeoliteParseContext, ZeoliteParsed>>+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @type new () -> (TokenAlternatives)+}++// Zeolite symbol parsing.+concrete ZeoliteSymbol {+  // Parses symbol and returns the stream forwarded to the end of the symbol.++  @type parseUpperSymbol     (TextStream) -> (optional TextStream)+  @type parseLowerSymbol     (TextStream) -> (optional TextStream)+  @type parseParamName       (TextStream) -> (optional TextStream)+  @type parseScopeQualifier  (TextStream) -> (optional TextStream)+  @type parseOperatorSymbols (TextStream) -> (optional TextStream)++  // Caller must check for \ first. Stream isn't reset!+  @type parseEscapedChar (TextStream) -> (optional TextStream)++  // Returns a type label if the token is in the given set.++  @type tryControlKeyword  (String) -> (optional String)+  @type tryTypeKeyword     (String) -> (optional String)+  @type tryContainKeyword  (String) -> (optional String)+  @type tryStorageKeyword  (String) -> (optional String)+  @type tryScopeQualifier  (String) -> (optional String)+  @type tryTestcaseKeyword (String) -> (optional String)+  @type tryBuiltinCategory (String) -> (optional String)+  @type tryBuiltinFunction (String) -> (optional String)+  @type tryBuiltinConstant (String) -> (optional String)+  @type tryBuiltinParam    (String) -> (optional String)+  @type tryAssignment      (String) -> (optional String)+  @type tryFunctionCall    (String) -> (optional String)+}
+ example/highlighter/src/zeolite-parsing.0rx view
@@ -0,0 +1,467 @@+/* -----------------------------------------------------------------------------+Copyright 2023-2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++define ZeoliteParsed {+  $ReadOnlyExcept[]$++  @value String label+  @value optional String content+  @value DefaultOrder<ZeoliteParsed> subsections++  leaf (label, content) {+    return #self{ label, content, AlwaysEmpty.default() }+  }++  section (label, subsections) {+    return #self{ label, empty, subsections }+  }++  testCompare (actual, report) {+    \ MultiChecker.new(report)+        .check(+            title: "label",+            actual.label(),+            CheckValue:equals(label))+        &.tryCheck(+            title: "content",+            actual.content(),+            CheckValue:equals(content))+        &.tryCheck(+            title: "subsections",+            actual.subsections(),+            CheckSequence:matches(CheckSequence:using(subsections)))+  }++  formatted () {+    [Append<Formatted> & Build<String>] builder <- String.builder()+        .append("ZeoliteParsed{label:")+        .append(label)+    if (`present` content) {+      \ builder+          .append(",content:\"")+          .append(CharType.escapeBreaks(`require` content))+          .append("\"")+    } else {+        traverse (subsections.defaultOrder() -> Formatted subsection) {+        \ builder+            .append(",")+            .append(subsection)+      }+    }+    \ builder.append("}")+    return builder.build()+  }++  formatWith (formatter) {+    if (`present` content) {+      return formatter.formatLeaf(label: label, value: `require` content)+    } else {+      [Append<#f> & Build<#f>] builder <- formatter.createSection(label: label)+      traverse (subsections.defaultOrder() -> ZeoliteParsed subsection) {+        \ builder.append(subsection.formatWith(formatter))+      }+      return builder.build()+    }+  }++  @value label () -> (String)+  label () {+    return label+  }++  @value content () -> (optional String)+  content () {+    return content+  }++  @value subsections () -> (DefaultOrder<ZeoliteParsed>)+  subsections () {+    return subsections+  }+}++define UseNamedTokenizer {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @value String name++  new () {+    return #self{ #t.tokenizerName() }+  }++  tokenize (input, context) {+    return delegate -> `context.get(name)&.tokenize`+  }+}++define TokenAlternatives {+  $ReadOnlyExcept[]$++  @value Vector<Tokenizer<ZeoliteParseContext, ZeoliteParsed>> tokenizers++  new () {+    return #self{ Vector<Tokenizer<ZeoliteParseContext, ZeoliteParsed>>.new() }+  }++  append (tokenizer) {+    \ delegate -> `tokenizers.append`+    return self+  }++  tokenize (input, context) (token) {+    token <- empty+    traverse (tokenizers.defaultOrder() -> Tokenizer<ZeoliteParseContext, ZeoliteParsed> tokenizer) {+      token <- tokenizer.tokenize(input, context)+    } update {+      if (`present` token) {+        break+      }+    }+  }+}++define ZeoliteParseContext {+  $ReadOnlyExcept[]$++  @value TokenAlternatives defaultTokenizer+  @value HashedMap<String, Tokenizer<ZeoliteParseContext, ZeoliteParsed>> tokenizers++  new () {+    return #self{ TokenAlternatives.new(), HashedMap<String, Tokenizer<ZeoliteParseContext, ZeoliteParsed>>.new() }+  }++  defaultTokenizer () {+    return defaultTokenizer+  }++  include () {+    \ #t.tokenizerName() `tokenizers.set` #t.tokenizer()+    \ defaultTokenizer.append(UseNamedTokenizer.new<#t>())+    return self+  }++  get (k) {+    return delegate -> `tokenizers.get`+  }+}++define ZeoliteSymbol {+  $ReadOnlyExcept[]$++  @category SetReader<String> controlKeywords <- HashedSet<String>.new()+      .append("break")+      .append("cleanup")+      .append("continue")+      .append("defer")+      .append("delegate")+      .append("elif")+      .append("else")+      .append("if")+      .append("in")+      .append("return")+      .append("scoped")+      .append("traverse")+      .append("update")+      .append("while")++  @category SetReader<String> typeKeywords <- HashedSet<String>.new()+      .append("allows")+      .append("defines")+      .append("immutable")+      .append("refines")+      .append("requires")+      .append("visibility")++  @category SetReader<String> containKeywords <- HashedSet<String>.new()+      .append("concrete")+      .append("interface")+      .append("define")+      .append("testcase")++  @category SetReader<String> storageKeywords <- HashedSet<String>.new()+      .append("optional")+      .append("weak")++  @category SetReader<String> scopeQualifiers <- HashedSet<String>.new()+      .append("@category")+      .append("@type")+      .append("@value")+      .append("unittest")++  @category SetReader<String> testcaseKeywords <- HashedSet<String>.new()+      .append("args")+      .append("compiler")+      .append("compiles")+      .append("error")+      .append("exclude")+      .append("failure")+      .append("require")+      .append("stderr")+      .append("stdout")+      .append("success")+      .append("timeout")++  @category SetReader<String> builtinCategries <- HashedSet<String>.new()+      .append("all")+      .append("any")+      .append("Append")+      .append("AsBool")+      .append("AsChar")+      .append("AsFloat")+      .append("AsInt")+      .append("Bool")+      .append("Bounded")+      .append("Build")+      .append("Char")+      .append("CharBuffer")+      .append("Container")+      .append("Default")+      .append("Duplicate")+      .append("Equals")+      .append("Float")+      .append("Formatted")+      .append("Hashed")+      .append("Identifier")+      .append("Int")+      .append("LessThan")+      .append("Order")+      .append("Pointer")+      .append("ReadAt")+      .append("String")+      .append("SubSequence")+      .append("Testcase")+      .append("WriteAt")++  @category SetReader<String> builtinFunctions <- HashedSet<String>.new()+      .append("exit")+      .append("fail")+      .append("identify")+      .append("present")+      .append("reduce")+      .append("require")+      .append("strong")+      .append("typename")++  @category SetReader<String> builtinConstants <- HashedSet<String>.new()+      .append("empty")+      .append("false")+      .append("true")+      .append("self")++  @category SetReader<String> builtinParams <- HashedSet<String>.new()+      .append("#self")++  @category SetReader<String> assignmentOperators <- HashedSet<String>.new()+      .append("<-")+      .append("<-|")+      .append("<->")+      .append("->")++  @category SetReader<String> functionCallOperators <- HashedSet<String>.new()+      .append(":")+      .append(".")+      .append("&.")+      .append("?")++  @category SetReader<Char> operatorChars <- HashedSet<Char>.new() `AppendH:from` ":.!%^&*-+|<>?=".defaultOrder()++  @category SetReader<Char> escapedChars <- HashedSet<Char>.new() `AppendH:from` "'\"?\\abfnrtv".defaultOrder()++  parseUpperSymbol (input) {+    \ input.reset()+    optional Char current <- input.current()+    if (! `present` current || !CharType.upper(`require` current)) {+      return empty+    }+    while (`present` current && CharType.alphaNum(`require` current)) {+      current <- input.forward().current()+    }+    return input+  }++  parseLowerSymbol (input) {+    \ input.reset()+    optional Char current <- input.current()+    if (! `present` current || !CharType.lower(`require` current)) {+      return empty+    }+    while (`present` current && CharType.alphaNum(`require` current)) {+      current <- input.forward().current()+    }+    return input+  }++  parseParamName (input) {+    \ input.reset()+    optional Char current <- input.current()+    if (! `present` current || `require` current != '#') {+      return empty+    }+    current <- input.forward().current()+    if (! `present` current || !CharType.lower(`require` current)) {+      return empty+    }+    while (`present` current && CharType.alphaNum(`require` current)) {+      current <- input.forward().current()+    }+    return input+  }++  parseScopeQualifier (input) {+    \ input.reset()+    optional Char current <- input.current()+    if (! `present` current || `require` current != '@') {+      return empty+    }+    current <- input.forward().current()+    while (`present` current && CharType.alphaNum(`require` current)) {+      current <- input.forward().current()+    }+    return input+  }++  parseOperatorSymbols (input) (output) {+    \ input.reset()+    output <- empty+    optional Char current <- input.current()+    while (`present` current && `operatorChars.member` `require` current) {+      current <- input.forward().current()+      output <- input+    }+  }++  parseEscapedChar (input) {+    scoped {+      optional Char current <- input.current()+    } in if (! `present` current) {+      return empty+    } elif (`require` current == 'x') {+      // Hex escaped char.+      traverse (Counter.zeroIndexed(2) -> _) {+        current <- input.forward().current()+        if (! `present` current || !CharType.hexDigit(`require` current)) {+          return empty+        }+      }+      \ input.forward()+    } elif (CharType.octDigit(`require` current)) {+      // Oct escaped char.+      traverse (Counter.zeroIndexed(2) -> _) {+        current <- input.forward().current()+        if (! `present` current || !CharType.octDigit(`require` current)) {+          return empty+        }+      }+      \ input.forward()+    } elif (`escapedChars.member` `require` current) {+      // Single escaped char.+      \ input.forward()+    } else {+      // Invalid escaped char.+      \ input.forward()+      return empty+    }+    return input+  }++  tryControlKeyword (value) (type) {+    type <- empty+    if (`controlKeywords.member` value) {+      type <- "ZeoliteControlKeyword"+    }+  }++  tryTypeKeyword (value) (type) {+    type <- empty+    if (`typeKeywords.member` value) {+      type <- "ZeoliteTypeKeyword"+    }+  }++  tryContainKeyword (value) (type) {+    type <- empty+    if (`containKeywords.member` value) {+      type <- "ZeoliteContainKeyword"+    }+  }++  tryStorageKeyword (value) (type) {+    type <- empty+    if (`storageKeywords.member` value) {+      type <- "ZeoliteStorageKeyword"+    }+  }++  tryScopeQualifier (value) (type) {+    type <- empty+    if (`scopeQualifiers.member` value) {+      type <- "ZeoliteScopeQualifier"+    }+  }++  tryTestcaseKeyword (value) (type) {+    type <- empty+    if (`testcaseKeywords.member` value) {+      type <- "ZeoliteTestcaseKeyword"+    }+  }++  tryBuiltinCategory (value) (type) {+    type <- empty+    if (`builtinCategries.member` value) {+      type <- "ZeoliteBuiltinCategory"+    }+  }++  tryBuiltinFunction (value) (type) {+    type <- empty+    if (`builtinFunctions.member` value) {+      type <- "ZeoliteBuiltinFunction"+    }+  }++  tryBuiltinConstant (value) (type) {+    type <- empty+    if (`builtinConstants.member` value) {+      type <- "ZeoliteBuiltinConstant"+    }+  }++  tryAssignment (value) (type) {+    type <- empty+    if (`assignmentOperators.member` value) {+      type <- "ZeoliteAssignment"+    }+  }++  tryBuiltinParam (value) (type) {+    type <- empty+    if (`builtinParams.member` value) {+      type <- "ZeoliteBuiltinParam"+    }+  }++  tryFunctionCall (value) (type) {+    type <- empty+    if (`functionCallOperators.member` value) {+      type <- "ZeoliteFunctionCall"+    }+  }+}
+ example/highlighter/src/zeolite-tokenizing.0rp view
@@ -0,0 +1,94 @@+/* -----------------------------------------------------------------------------+Copyright 2023-2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++$ModuleOnly$++concrete ZeoliteWhitespace {+  defines NamedTokenizer+}++concrete ZeoliteLineComment {+  defines NamedTokenizer+}++concrete ZeoliteBlockComment {+  defines NamedTokenizer+}++concrete ZeoliteUpperSymbol {+  defines NamedTokenizer+}++concrete ZeoliteLowerSymbol {+  defines NamedTokenizer+}++concrete ZeoliteScopeQualifier {+  defines NamedTokenizer+}++concrete ZeoliteOperator {+  defines NamedTokenizer+}++concrete ZeoliteParamName {+  defines NamedTokenizer+}++concrete ZeoliteStringLiteral {+  defines NamedTokenizer+}++concrete ZeoliteCharLiteral {+  defines NamedTokenizer+}++concrete ZeoliteNumber {+  defines NamedTokenizer+}++concrete ZeoliteBraceSection {+  defines NamedTokenizer+}++concrete ZeoliteParenSection {+  defines NamedTokenizer+}++concrete ZeoliteSquareSection {+  defines NamedTokenizer+}++concrete ZeolitePragma {+  defines NamedTokenizer+}++concrete ZeoliteExtras {+  defines NamedTokenizer+}++// NOTE: Uses the following if available:+// - ZeoliteStringLiteral (required)+// - ZeoliteLowerSymbol (required)+// - ZeoliteSquareSection+// - ZeoliteUpperSymbol+// - ZeoliteNumber+// - ZeoliteOperator+concrete ZeoliteTestcase {+  defines NamedTokenizer+}
+ example/highlighter/src/zeolite-tokenizing.0rx view
@@ -0,0 +1,688 @@+/* -----------------------------------------------------------------------------+Copyright 2023-2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++define ZeoliteWhitespace {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    optional Char current <- input.current()+    while (`present` current && CharType.whitespace(`require` current)) {+      current <- input.forward().current()+    }+    scoped {+      String content <- input.take()+    } in if (content.size() > 0) {+      token <- ZeoliteParsed.leaf(label: tokenizerName(), content: content)+    }+  }+}++define ZeoliteLineComment {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    \ input.forward().forward()+    if (input.preview() != "//") {+      return _+    }+    optional Char current <- input.current()+    while (`present` current && !CharType.oneOf(`require` current, "\r\n")) {+      current <- input.forward().current()+    }+    token <- ZeoliteParsed.leaf(label: tokenizerName(), content: input.take())+  }+}++define ZeoliteBlockComment {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    \ input.forward().forward()+    if (input.preview() != "/*") {+      return _+    }+    scoped {+      optional Char current <- empty+      Bool starFound <- false+    } in while (`present` (current <- input.current())) {+      if (starFound && `require` current == '/') {+        \ input.forward()+        break+      } else {+        starFound <- `require` current == '*'+      }+    } update {+      \ input.forward()+    }+    token <- ZeoliteParsed.leaf(label: tokenizerName(), content: input.take())+  }+}++define ZeoliteUpperSymbol {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, _) {+    optional String content <- ZeoliteSymbol.parseUpperSymbol(input)&.take()+    if (! `present` content) {+      return empty+    } else {+      String type <-  ZeoliteSymbol.tryBuiltinCategory(`require` content)+                  <|| "ZeoliteCategoryName"+      return ZeoliteParsed.leaf(label: type, content: `require` content)+    }+  }+}++define ZeoliteLowerSymbol {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, _) {+    optional String content <- ZeoliteSymbol.parseLowerSymbol(input)&.preview()+    if (! `present` content) {+      return empty+    } else {+      String type <-  ZeoliteSymbol.tryControlKeyword(`require` content)+                  <|| ZeoliteSymbol.tryTypeKeyword(`require` content)+                  <|| ZeoliteSymbol.tryContainKeyword(`require` content)+                  <|| ZeoliteSymbol.tryStorageKeyword(`require` content)+                  <|| ZeoliteSymbol.tryScopeQualifier(`require` content)+                  <|| ZeoliteSymbol.tryBuiltinCategory(`require` content)+                  <|| ZeoliteSymbol.tryBuiltinFunction(`require` content)+                  <|| ZeoliteSymbol.tryBuiltinConstant(`require` content)+                  <|| tryLabel(input)+                  <|| "ZeoliteFunctionOrVariableName"+      $Hidden[content]$+      return ZeoliteParsed.leaf(label: type, content: input.take())+    }+  }++  @type tryLabel (TextStream) -> (optional String)+  tryLabel (input) {+    if (`present` input.current() && `require` input.current() == ':') {+      \ input.forward()+      return "ZeoliteArgLabel"+    } else {+      return empty+    }+  }+}++define ZeoliteScopeQualifier {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, _) {+    optional String content <- ZeoliteSymbol.parseScopeQualifier(input)&.take()+    if (! `present` content) {+      return empty+    } else {+      String type <-  ZeoliteSymbol.tryScopeQualifier(`require` content)+                  <|| "ZeoliteError"+      return ZeoliteParsed.leaf(label: type, content: `require` content)+    }+  }+}++define ZeoliteOperator {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, _) {+    optional String content <- ZeoliteSymbol.parseOperatorSymbols(input)&.take()+    if (! `present` content) {+      return empty+    } else {+      String type <-  ZeoliteSymbol.tryAssignment(`require` content)+                  <|| ZeoliteSymbol.tryFunctionCall(`require` content)+                  <|| "ZeoliteOperator"+      return ZeoliteParsed.leaf(label: type, content: `require` content)+    }+  }+}++define ZeoliteParamName {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) {+    optional String content <- ZeoliteSymbol.parseParamName(input)&.take()+    if (! `present` content) {+      return empty+    } else {+      String type <-  ZeoliteSymbol.tryBuiltinParam(`require` content)+                  <|| "ZeoliteParamName"+      return ZeoliteParsed.leaf(label: type, content: `require` content)+    }+  }+}++concrete ZeoliteTestcaseKeyword {+  defines Default+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>+}++define ZeoliteTestcaseKeyword {+  default () {+    return delegate -> #self+  }++  tokenize (input, context) (token) {+    token <- empty+    scoped {+      optional String content <- ZeoliteSymbol.parseLowerSymbol(input)&.preview()+    } in if (! `present` content) {+      return empty+    } else {+      optional String type <- ZeoliteSymbol.tryTestcaseKeyword(`require` content)+      $Hidden[content]$+      if (`present` type) {+        token <- ZeoliteParsed.leaf(label: `require` type, content: input.take())+      }+    }+  }+}++define ZeoliteStringLiteral {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    if (input.atEnd() || `require` input.current() != '"') {+      return _+    }+    Vector<ZeoliteParsed> subsections <- Vector<ZeoliteParsed>.new()+    // Setting this early still allows building it incrementally while also+    // allowing an early return if something is missing.+    token <- ZeoliteParsed.section(label: tokenizerName(), subsections)+    \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: input.forward().take()))++    scoped {+      optional Char current <- empty+    } in while (!input.atEnd() && `present` (current <- input.current())) {+      if (`require` current == '\"') {+        \ addCurrentChars(input, subsections)+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: input.forward().take()))+        return _+      } elif (`require` current == '\\') {+        \ addCurrentChars(input, subsections)+        if (`present` ZeoliteSymbol.parseEscapedChar(input.forward())) {+          \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: input.take()))+        } else {+          \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteError", content: input.take()))+        }+      } else {+        \ input.forward()+      }+    }+    \ addCurrentChars(input, subsections)+  }++  @type addCurrentChars (TextStream, Append<ZeoliteParsed>) -> ()+  addCurrentChars (input, output) {+    if (input.tokenSize() > 0) {+      \ output.append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: input.take()))+    }+  }+}++define ZeoliteCharLiteral {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    if (input.atEnd() || `require` input.current() != '\'') {+      return _+    }+    Vector<ZeoliteParsed> subsections <- Vector<ZeoliteParsed>.new()+    // Setting this early still allows building it incrementally while also+    // allowing an early return if something is missing.+    token <- ZeoliteParsed.section(label: tokenizerName(), subsections)+    \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: input.forward().take()))++    optional Char current <- input.current()+    if (! `present` current) {+      return _+    } elif (`require` current == '\\') {+      if (`present` ZeoliteSymbol.parseEscapedChar(input.forward())) {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: input.take()))+      } else {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteError", content: input.take()))+      }+    } elif (`require` current == '\'') {+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteError", content: input.forward().take()))+    } else {+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: input.forward().take()))+    }+    if (`present` (current <- input.current())) {+      if (`require` current == '\'') {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: input.forward().take()))+      } else {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteError", content: input.forward().take()))+      }+    }+  }+}++define ZeoliteNumber {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    optional Char current <- input.current()+    if (! `present` current) {+      return _+    } elif (CharType.digit(`require` current)) {+      token <- parseNumber(input, escaped: false, CharType.decChars())+    } elif (`require` current `CharType.oneOf` "-+") {+      \ input.forward()+      token <- parseNumber(input, escaped: false, CharType.decChars())+    } elif (`require` current != '\\') {+      return _+    } else {+      current <- input.forward().current()+      if (! `present` current) {+        return _+      } elif (`require` current `CharType.oneOf` "bB") {+        \ input.forward()+        token <- parseNumber(input, escaped: true, CharType.binChars())+      } elif (`require` current `CharType.oneOf` "oO") {+      \ input.forward()+        token <- parseNumber(input, escaped: true, CharType.octChars())+      } elif (`require` current `CharType.oneOf` "dD") {+      \ input.forward()+        token <- parseNumber(input, escaped: true, CharType.decChars())+      } elif (`require` current `CharType.oneOf` "xX") {+      \ input.forward()+        token <- parseNumber(input, escaped: true, CharType.hexChars())+      } else {+        return _+      }+    }+  }++  @type parseNumber (TextStream, Bool escaped:, SetReader<Char>) -> (optional ZeoliteParsed)+  parseNumber (input, escaped, allowed) {+    Bool isEmpty <- true+    optional Char current <- empty+    while (`present` (current <- input.current()) && `allowed.member` `require` current) {+      \ input.forward()+      isEmpty <- false+    }+    if (isEmpty) {+      return empty+    } elif (`present` (current <- input.current()) && `require` current == '.') {+      \ input.forward()+      while (`present` (current <- input.current()) && `allowed.member` `require` current) {+        \ input.forward()+      }+    }+    if (escaped) {+      return ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: input.take())+    } elif (`present` current && `require` current `CharType.oneOf` "eE") {+      \ input.forward()+      if (`present` (current <- input.current()) && `require` current `CharType.oneOf` "-+") {+        \ input.forward()+      }+      while (`present` (current <- input.current()) && `allowed.member` `require` current) {+        \ input.forward()+      }+    }+    return ZeoliteParsed.leaf(label: "ZeoliteNumber", content: input.take())+  }+}++define ZeoliteBraceSection {+  tokenizer () {+    return ZeoliteDelimSection.new(name: tokenizerName(), open: '{', close: '}', empty)+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }+}++define ZeoliteParenSection {+  tokenizer () {+    return ZeoliteDelimSection.new(name: tokenizerName(), open: '(', close: ')', empty)+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }+}++define ZeoliteSquareSection {+  tokenizer () {+    return ZeoliteDelimSection.new(name: tokenizerName(), open: '[', close: ']', empty)+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }+}++define ZeolitePragma {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    if (input.atEnd() || `require` input.current() != '$') {+      return _+    }+    Vector<ZeoliteParsed> subsections <- Vector<ZeoliteParsed>.new()+    token <- ZeoliteParsed.section(label: tokenizerName(), subsections)+    \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: input.forward().take()))++    if (! `present` ZeoliteSymbol.parseUpperSymbol(input)) {+      return _+    } else {+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaName", content: input.take()))+    }++    optional Char current <- input.current()+    if (! `present` current) {+      return _+    } elif (`require` current == '[') {+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaArgOpen", content: input.forward().take()))+      while (`present` (current <- input.current()) && `require` current != ']') {+        \ input.forward()+      }+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaArg", content: input.take()))+      if (`present` (current <- input.current())) {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaArgClose", content: input.forward().take()))+      } else {+        return _+      }+    }++    if (`present` (current <- input.current())) {+      if (`require` current == '$') {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: input.forward().take()))+      } else {+        \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteError", content: input.forward().take()))+      }+    }+  }+}++define ZeoliteExtras {+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    \ input.reset()+    optional Char current <- input.current()+    if (! `present` current) {+      return empty+    } elif (`require` current == '_') {+      \ input.forward()+      return ZeoliteParsed.leaf(label: "ZeoliteIgnore", content: input.take())+    } elif (`require` current == '\\') {+      \ input.forward()+      return ZeoliteParsed.leaf(label: "ZeoliteDiscard", content: input.take())+    } elif (`require` current == '`') {+      \ input.forward()+      return ZeoliteParsed.leaf(label: "ZeoliteTick", content: input.take())+    } elif (`require` current == ',') {+      \ input.forward()+      return ZeoliteParsed.leaf(label: "ZeoliteComma", content: input.take())+    } elif (`require` current `CharType.oneOf` ";#@") {+      \ input.forward()+      return ZeoliteParsed.leaf(label: "ZeoliteError", content: input.take())+    } else {+      return empty+    }+  }+}++define ZeoliteTestcase {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @category Tokenizer<ZeoliteParseContext, ZeoliteParsed> testcaseSpecs <- TokenAlternatives.new()+      .append(ZeoliteOptionalSeparator.default())+      .append(UseNamedTokenizer.new<ZeoliteStringLiteral>())+      .append(UseNamedTokenizer.new<ZeoliteSquareSection>())+      .append(ZeoliteTestcaseKeyword.default())+      .append(UseNamedTokenizer.new<ZeoliteUpperSymbol>())+      .append(UseNamedTokenizer.new<ZeoliteLowerSymbol>())+      .append(UseNamedTokenizer.new<ZeoliteNumber>())+      .append(UseNamedTokenizer.new<ZeoliteOperator>())++  tokenizer () {+    return delegate -> #self+  }++  tokenizerName () {+    return typename<#self>().formatted()+  }++  tokenize (input, context) (token) {+    scoped {+      optional String content <- ZeoliteSymbol.parseLowerSymbol(input)&.preview()+    } in if (! `present` content || require(content) != "testcase") {+      return empty+    }++    Vector<ZeoliteParsed> subsections <- Vector<ZeoliteParsed>.new()+    // Setting this early still allows building it incrementally while also+    // allowing an early return if something is missing.+    token <- ZeoliteParsed.section(label: tokenizerName(), subsections)+    \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteContainKeyword", content: input.take()))++    \ ZeoliteOptionalSeparator.parseAny(input, context, subsections)+    // Testcase description string.+    scoped {+      optional ZeoliteParsed name <- delegate -> `ZeoliteStringLiteral.tokenizer().tokenize`+    } in if (`present` name) {+      \ subsections.append(`require` name)+    } else {+      return _+    }+    \ ZeoliteOptionalSeparator.parseAny(input, context, subsections)++    // Testcase specs.+    \ StreamTokenizer:new(context: context, tokenizer: ZeoliteDelimSection.new(name: "ZeoliteTestcaseSection", open: '{', close: '}', testcaseSpecs))+        .tokenizeAll(input, subsections)+  }+}++concrete ZeoliteOptionalSeparator {+  defines Default+  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  visibility Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @type parseAny (TextStream, ZeoliteParseContext, Append<ZeoliteParsed>) -> ()+}++define ZeoliteOptionalSeparator {+  $ReadOnlyExcept[]$++  @category Tokenizer<ZeoliteParseContext, ZeoliteParsed> tokenizer <- TokenAlternatives.new()+      .append(UseNamedTokenizer.new<ZeoliteLineComment>())+      .append(UseNamedTokenizer.new<ZeoliteBlockComment>())+      .append(UseNamedTokenizer.new<ZeoliteWhitespace>())++  parseAny (input, context, output) {+    \ StreamTokenizer:new(context: context, tokenizer: default()).tokenizeAll(input, output)+  }++  default () {+    return delegate -> #self+  }++  tokenize (input, output) {+    return delegate -> `tokenizer.tokenize`+  }+}++concrete ZeoliteDelimSection {+  @type new (String name:, Char open:, Char close:, optional Tokenizer<ZeoliteParseContext, ZeoliteParsed>) -> (Tokenizer<ZeoliteParseContext, ZeoliteParsed>)+}++define ZeoliteDelimSection {+  $ReadOnlyExcept[]$++  refines Tokenizer<ZeoliteParseContext, ZeoliteParsed>++  @value String name+  @value Char open+  @value Char close+  @value optional Tokenizer<ZeoliteParseContext, ZeoliteParsed> tokenizer++  new (name, open, close, tokenizer) {+    return delegate -> #self+  }++  tokenize (input, context) (token) {+    \ input.reset()+    token <- empty+    if (input.atEnd() || `require` input.current() != open) {+      return _+    }+    Vector<ZeoliteParsed> subsections <- Vector<ZeoliteParsed>.new()+    token <- ZeoliteParsed.section(label: name, subsections)+    \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim", content: input.forward().take()))++    \ StreamTokenizer:new(context: context, tokenizer: tokenizer <|| context.defaultTokenizer()).tokenizeAll(input, subsections)+    \ input.reset()+    if (!input.atEnd() && `require` input.current() == close) {+      \ subsections.append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim", content: input.forward().take()))+    }+  }+}
− example/highlighter/src/zeolite.0rp
@@ -1,72 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at--    http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--$ModuleOnly$--concrete ZeoliteToken {-  refines Formatted-  refines TestCompare<ZeoliteToken>-  refines TextToken<String>--  visibility Tokenizer<String, ZeoliteToken>--  @type new (String content:, String label:) -> (optional ZeoliteToken)-}--@type interface NamedTokenizer {-  tokenizer () -> (Tokenizer<String, ZeoliteToken>)-  tokenizerName () -> (String)-}--concrete ZeoliteTokenizers {-  refines KVReader<String, Tokenizer<String, ZeoliteToken>>--  @type new () -> (ZeoliteTokenizers)--  @value include<#t>-    #t defines NamedTokenizer-  () -> (ZeoliteTokenizers)-}--concrete ZeoliteSelectFrom {-  refines Tokenizer<String, ZeoliteToken>--  visibility Tokenizer<String, ZeoliteToken>--  @type new () -> (ZeoliteSelectFrom)--  @value include<#t>-    #t defines NamedTokenizer-  () -> (ZeoliteSelectFrom)-}--concrete ZeoliteWhitespace {-  defines NamedTokenizer-}--concrete ZeoliteLineComment {-  defines NamedTokenizer-}--concrete ZeoliteCategoryName {-  defines NamedTokenizer-}--concrete ZeoliteParamName {-  defines NamedTokenizer-}
− example/highlighter/src/zeolite.0rx
@@ -1,214 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at--    http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--define ZeoliteToken {-  @value String content-  @value String label--  new (content, label) {-    if (content.size() < 1) {-      return empty-    } else {-      return delegate -> #self-    }-  }--  exportWith (formatter) {-    return formatter.format(content: content, label: label)-  }--  testCompare (actual, report) {-    \ MultiChecker.new(report)-        .tryCheck(-            title: "content",-            actual.content(),-            CheckValue:equals(content))-        .tryCheck(-            title: "label",-            actual.label(),-            CheckValue:equals(label))-  }--  formatted () {-    return String.builder()-        .append("ZeoliteToken{\"")-        .append(CharType.escapeBreaks(content))-        .append("\",")-        .append(label)-        .append("}")-        .build()-  }--  @value content () -> (String)-  content () {-    return content-  }--  @value label () -> (String)-  label () {-    return label-  }-}--define ZeoliteTokenizers {-  $ReadOnlyExcept[]$--  @value HashedMap<String, Tokenizer<String, ZeoliteToken>> tokenizers--  new () {-    return #self{ HashedMap<String, Tokenizer<String, ZeoliteToken>>.new() }-  }--  include () {-    \ #t.tokenizerName() `tokenizers.set` #t.tokenizer()-    return self-  }--  get (k) {-    return delegate -> `tokenizers.get`-  }-}--define ZeoliteSelectFrom {-  $ReadOnlyExcept[]$--  @value MultiTokenizer<String, ZeoliteToken> selector--  new () {-    return #self{ MultiTokenizer<String, ZeoliteToken>.new() }-  }--  include () {-    \ selector.include(#t.tokenizerName())-    return self-  }--  tokenize (input, tokenizers) {-    return delegate -> `selector.tokenize`-  }-}--define ZeoliteWhitespace {-  refines Tokenizer<String, ZeoliteToken>--  tokenizer () {-    return #self{ }-  }--  tokenizerName () {-    return typename<#self>().formatted()-  }--  tokenize (input, tokenizers) (token, nextType, popCount) {-    \ input.reset()-    token <- empty-    nextType <- empty-    popCount <- 0-    optional Char current <- input.current()-    while (`present` current && CharType.whitespace(`require` current)) {-      current <- input.forward().current()-    }-    token <- ZeoliteToken.new(content: input.take(), label: tokenizerName())-  }-}--define ZeoliteLineComment {-  refines Tokenizer<String, ZeoliteToken>--  tokenizer () {-    return #self{ }-  }--  tokenizerName () {-    return typename<#self>().formatted()-  }--  tokenize (input, tokenizers) (token, nextType, popCount) {-    \ input.reset()-    token <- empty-    nextType <- empty-    popCount <- 0-    \ input.forward().forward()-    if (input.preview() != "//") {-      return _-    }-    optional Char current <- input.current()-    while (`present` current && !CharType.oneOf(`require` current, "\r\n")) {-      current <- input.forward().current()-    }-    token <- ZeoliteToken.new(content: input.take(), label: tokenizerName())-  }-}--define ZeoliteCategoryName {-  refines Tokenizer<String, ZeoliteToken>--  tokenizer () {-    return #self{ }-  }--  tokenizerName () {-    return typename<#self>().formatted()-  }--  tokenize (input, tokenizers) (token, nextType, popCount) {-    \ input.reset()-    token <- empty-    nextType <- empty-    popCount <- 0-    optional Char current <- input.current()-    if (! `present` current || !CharType.upper(`require` current)) {-      return _-    }-    while (`present` current && CharType.alphaNum(`require` current)) {-      current <- input.forward().current()-    }-    token <- ZeoliteToken.new(content: input.take(), label: tokenizerName())-  }-}--define ZeoliteParamName {-  refines Tokenizer<String, ZeoliteToken>--  tokenizer () {-    return #self{ }-  }--  tokenizerName () {-    return typename<#self>().formatted()-  }--  tokenize (input, tokenizers) (token, nextType, popCount) {-    \ input.reset()-    token <- empty-    nextType <- empty-    popCount <- 0-    optional Char current <- input.current()-    if (! `present` current || `require` current != '#') {-      return _-    }-    current <- input.forward().current()-    if (! `present` current || !CharType.lower(`require` current)) {-      return _-    }-    while (`present` current && CharType.alphaNum(`require` current)) {-      current <- input.forward().current()-    }-    token <- ZeoliteToken.new(content: input.take(), label: tokenizerName())-  }-}
+ example/highlighter/test/tokenizer.0rt view
@@ -0,0 +1,67 @@+/* -----------------------------------------------------------------------------+Copyright 2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++testcase "input tests" {+  success TestChecker+}++unittest integration {+  TextStream input <- TextStream.new("123\n4")++  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals('1')+  \ input.preview() `Matches:with` CheckValue:equals("")+  \ input.currentLine() `Matches:with` CheckValue:equals(1)+  \ input.currentChar() `Matches:with` CheckValue:equals(1)++  \ input.forward().forward()+  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals('3')+  \ input.preview() `Matches:with` CheckValue:equals("12")+  \ input.currentLine() `Matches:with` CheckValue:equals(1)+  \ input.currentChar() `Matches:with` CheckValue:equals(1)++  \ input.take() `Matches:with` CheckValue:equals("12")+  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals('3')+  \ input.preview() `Matches:with` CheckValue:equals("")+  \ input.currentLine() `Matches:with` CheckValue:equals(1)+  \ input.currentChar() `Matches:with` CheckValue:equals(3)++  \ input.forward().forward()+  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals('4')+  \ input.preview() `Matches:with` CheckValue:equals("3\n")++  \ input.reset()+  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals('3')+  \ input.preview() `Matches:with` CheckValue:equals("")+  \ input.currentLine() `Matches:with` CheckValue:equals(1)+  \ input.currentChar() `Matches:with` CheckValue:equals(3)++  \ input.forward().forward().forward()+  \ input.atEnd() `Matches:with` CheckValue:equals(false)+  \ input.current() `Matches:with` CheckValue:equals(empty?Char)+  \ input.preview() `Matches:with` CheckValue:equals("3\n4")++  \ input.take() `Matches:with` CheckValue:equals("3\n4")+  \ input.atEnd() `Matches:with` CheckValue:equals(true)+  \ input.currentLine() `Matches:with` CheckValue:equals(2)+  \ input.currentChar() `Matches:with` CheckValue:equals(2)+}
+ example/highlighter/test/zeolite-tokenizing.0rt view
@@ -0,0 +1,568 @@+/* -----------------------------------------------------------------------------+Copyright 2023-2024 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++testcase "parsing tests" {+  success TestChecker+}++concrete ParsingTester {+  @type new (ZeoliteParseContext) -> (ParsingTester)++  @value checkParsesAs (String, DefaultOrder<ZeoliteParsed>) -> ()+}++unittest parseZeoliteWhitespace {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteWhitespace>())++  \ " \t \r\n" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace", content: " \t \r\n"))++  \ "   test" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace", content: "   "))+}++unittest parseZeoliteLineComment {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteLineComment>()+      .include<ZeoliteWhitespace>())++  \ "//12345\n//54321" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteLineComment", content: "//12345"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",  content: "\n"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteLineComment", content: "//54321"))++  \ "/ /" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteBlockComment {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteWhitespace>())++  \ "/*/12345\n54321* */" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*/12345\n54321* */"))++  \ "/*comment*/  " `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*comment*/"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace", content: "  "))+  \ "/*forever " `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*forever "))++  \ "/ *" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteUpperSymbol {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteUpperSymbol>())++  \ "Hello123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName", content: "Hello123"))+  \ "String" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinCategory", content: "String"))++  \ "Hello_123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName", content: "Hello"))++  \ "hello" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "123Hello" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteLowerSymbol {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteLowerSymbol>())++  \ "hello123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionOrVariableName", content: "hello123"))+  \ "hello123:" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteArgLabel", content: "hello123:"))+  \ "cleanup" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteControlKeyword", content: "cleanup"))+  \ "immutable" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteTypeKeyword", content: "immutable"))+  \ "concrete" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteContainKeyword", content: "concrete"))+  \ "optional" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteStorageKeyword", content: "optional"))+  \ "unittest" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteScopeQualifier", content: "unittest"))+  \ "any" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinCategory", content: "any"))+  \ "identify" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinFunction", content: "identify"))+  \ "empty" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinConstant", content: "empty"))++  \ "hello_123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionOrVariableName", content: "hello"))+  // Only has a special label in testcase.+  \ "compiler" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionOrVariableName", content: "compiler"))+  \ "empty123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionOrVariableName", content: "empty123"))+  \ "identify:" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinFunction", content: "identify"))++  \ "Hello" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "123hello" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteScopeQualifier {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteScopeQualifier>())++  \ "@value" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteScopeQualifier", content: "@value"))+  \ "@type" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteScopeQualifier", content: "@type"))+  \ "@category" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteScopeQualifier", content: "@category"))++  \ "@value something" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteScopeQualifier", content: "@value"))+  \ "@local" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "@local"))+}++unittest parseZeoliteOperator {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteOperator>())++  \ "<-" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteAssignment", content: "<-"))+  \ "<-|" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteAssignment", content: "<-|"))+  \ "<->" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteAssignment", content: "<->"))+  \ "->" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteAssignment", content: "->"))+  \ ":" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionCall", content: ":"))+  \ "." `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionCall", content: "."))+  \ "&." `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionCall", content: "&."))+  \ "?" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionCall", content: "?"))+  \ "<<" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteOperator", content: "<<"))+  \ ":.!%^&*-+|<>?=" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteOperator", content: ":.!%^&*-+|<>?="))++  \ "<-||" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteOperator", content: "<-||"))+  \ "&.call()" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteFunctionCall", content: "&."))+}++unittest parseZeoliteParamName {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteParamName>())++  \ "#param123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteParamName", content: "#param123"))+  \ "#self" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteBuiltinParam", content: "#self"))++  \ "#param_123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteParamName", content: "#param"))+  \ "#self123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteParamName", content: "#self123"))++  \ "param" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "#Param" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "#123param" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteStringLiteral {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteStringLiteral>())++  \ "\"hello\\n\\t\\0123\\xABC\\Q \\09c goodbye\"" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteStringLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "hello"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\n"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\t"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\012"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "3"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\xAB"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "C"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError",       content: "\\Q"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError",       content: "\\0"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "9c goodbye"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))))++  \ "\"forever" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteStringLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "forever"))))+}++unittest parseZeoliteCharLiteral {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteCharLiteral>())++  \ "'a'" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "a"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))))+  \ "'\\''" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))))+  \ "'\\n'" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\n"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))))+  \ "'\\xAB'" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\xAB"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))))+  \ "'\\Q'" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "\\Q"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))))++  \ "''" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "'"))))+  \ "'forever" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteCharLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteSingleQuote", content: "'"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "f"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "o"))))+}++unittest parseZeoliteNumber {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteNumber>())++  \ "123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123"))+  \ "123.456" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123.456"))+  \ "123.456E123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123.456E123"))+  \ "+123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "+123"))+  \ "-123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "-123"))+  \ "123.456E+123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123.456E+123"))+  \ "123.456E-123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123.456E-123"))++  \ "\\b101.010" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\b101.010"))+  \ "\\o123.456" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\o123.456"))+  \ "\\d123.456" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\d123.456"))+  \ "\\x123.abc" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\x123.abc"))++  \ "123A" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "123"))+  \ "\\b0123" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\b01"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "23"))+  \ "\\o12389" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\o123"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteNumber", content: "89"))+  \ "\\d123A" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\d123"))+  \ "\\xABCqq" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedNumber", content: "\\xABC"))++  \ "-" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "+" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "\\" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "\\b" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "\\o" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "\\d" `tester.checkParsesAs` AlwaysEmpty.default()+  \ "\\h" `tester.checkParsesAs` AlwaysEmpty.default()+}++unittest parseZeoliteBraceSection {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteBraceSection>()+      .include<ZeoliteWhitespace>())++  \ "{ /*hello*/ }" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteBraceSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "{"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*hello*/"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",   content: "}"))))++  \ "{ /*forever*/" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteBraceSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "{"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*forever*/"))))+}++unittest parseZeoliteParenSection {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteParenSection>()+      .include<ZeoliteWhitespace>())++  \ "( /*hello*/ )" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteParenSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "("))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*hello*/"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",   content: ")"))))++  \ "( /*forever*/" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteParenSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "("))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*forever*/"))))+}++unittest parseZeoliteSquareSection {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteSquareSection>()+      .include<ZeoliteWhitespace>())++  \ "[ /*hello*/ ]" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteSquareSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "["))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*hello*/"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",   content: "]"))))++  \ "[ /*forever*/" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteSquareSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "["))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*forever*/"))))+}++unittest parseZeoliteTestcase {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteWhitespace>()+      .include<ZeoliteLineComment>()+      .include<ZeoliteBlockComment>()+      .include<ZeoliteStringLiteral>()+      .include<ZeoliteSquareSection>()+      .include<ZeoliteNumber>()+      .include<ZeoliteOperator>()+      // ZeoliteTestcase also uses the above if available.+      .include<ZeoliteTestcase>()+      // These should be after ZeoliteTestcase.+      .include<ZeoliteUpperSymbol>()+      .include<ZeoliteLowerSymbol>())++  String data <- "testcase \"my test\" {+    success Checker<[A&B]>  // <- enables lib/testing support+    require compiler \"something\"+    timeout 30+  }"++  \ data `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteTestcase", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteContainKeyword", content: "testcase"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",     content: " "))+          .append(ZeoliteParsed.section(label: "ZeoliteStringLiteral", Vector<ZeoliteParsed>.new()+              .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))+              .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "my test"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace", content: " "))+          .append(ZeoliteParsed.section(label: "ZeoliteTestcaseSection", Vector<ZeoliteParsed>.new()+              .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",       content: "{"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: "\n    "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteTestcaseKeyword", content: "success"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: " "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName",    content: "Checker"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteOperator",        content: "<"))+              .append(ZeoliteParsed.section(label: "ZeoliteSquareSection", Vector<ZeoliteParsed>.new()+                    .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "["))+                    .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName", content: "A"))+                    .append(ZeoliteParsed.leaf(label: "ZeoliteOperator",     content: "&"))+                    .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName", content: "B"))+                    .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",   content: "]"))))+              .append(ZeoliteParsed.leaf(label: "ZeoliteOperator",        content: ">"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: "  "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteLineComment",     content: "// <- enables lib/testing support"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: "\n    "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteTestcaseKeyword", content: "require"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: " "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteTestcaseKeyword", content: "compiler"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: " "))+              .append(ZeoliteParsed.section(label: "ZeoliteStringLiteral", Vector<ZeoliteParsed>.new()+                  .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))+                  .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "something"))+                  .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: "\n    "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteTestcaseKeyword", content: "timeout"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: " "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteNumber",          content: "30"))+              .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",      content: "\n  "))+              .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",      content: "}"))))))++  \ "testcase { }" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteTestcase", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteContainKeyword", content: "testcase"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",     content: " "))))+}++unittest parseZeolitePragma {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeolitePragma>())++  \ "$NoArgs$" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: "$"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaName",  content:  "NoArgs"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: "$"))))+  \ "$SomeArgs[a, b, 123]$" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim",    content: "$"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaName",     content:  "SomeArgs"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaArgOpen",  content: "["))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaArg",      content:  "a, b, 123"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaArgClose", content: "]"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim",    content: "$"))))++  \ "$NoArgs" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: "$"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaName",  content:  "NoArgs"))))+  \ "$foo$" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: "$"))))+  \ "$NoArgs#" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim", content: "$"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaName",  content:  "NoArgs"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteError",       content: "#"))))+  \ "$SomeArgs[args" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeolitePragma", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaDelim",    content: "$"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaName",     content:  "SomeArgs"))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaArgOpen",  content: "["))+          .append(ZeoliteParsed.leaf(label: "ZeolitePragmaArg",      content:  "args"))))+}++unittest parseZeoliteExtras {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteExtras>())++  \ "_" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteIgnore", content: "_"))+  \ "\\" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteDiscard", content: "\\"))+  \ "`" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteTick", content: "`"))+  \ "," `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteComma", content: ","))+  \ ";" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: ";"))+  \ "#" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "#"))+  \ "@" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.leaf(label: "ZeoliteError", content: "@"))+}++unittest integration {+  ParsingTester tester <- ParsingTester.new(ZeoliteParseContext.new()+      .include<ZeoliteWhitespace>()+      .include<ZeoliteLineComment>()+      .include<ZeoliteBlockComment>()+      .include<ZeolitePragma>()+      .include<ZeoliteStringLiteral>()+      .include<ZeoliteCharLiteral>()+      .include<ZeoliteTestcase>()+      .include<ZeoliteUpperSymbol>()+      .include<ZeoliteLowerSymbol>()+      .include<ZeoliteParamName>()+      .include<ZeoliteBraceSection>())++  \ "{Category #param}\"string\\n\" /*/ done*/\n" `tester.checkParsesAs` Vector<ZeoliteParsed>.new()+      .append(ZeoliteParsed.section(label: "ZeoliteBraceSection", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteOpenDelim",    content: "{"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteCategoryName", content: "Category"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+          .append(ZeoliteParsed.leaf(label: "ZeoliteParamName",    content: "#param"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteCloseDelim",   content: "}"))))+      .append(ZeoliteParsed.section(label: "ZeoliteStringLiteral", Vector<ZeoliteParsed>.new()+          .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))+          .append(ZeoliteParsed.leaf(label: "ZeoliteQuotedChars", content: "string"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteEscapedChar", content: "\\n"))+          .append(ZeoliteParsed.leaf(label: "ZeoliteDoubleQuote", content: "\""))))+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: " "))+      .append(ZeoliteParsed.leaf(label: "ZeoliteBlockComment", content: "/*/ done*/"))+      .append(ZeoliteParsed.leaf(label: "ZeoliteWhitespace",   content: "\n"))+}++define ParsingTester {+  $ReadOnlyExcept[]$++  @value ZeoliteParseContext context++  new (context) {+    return delegate -> #self+  }++  checkParsesAs (originalData, expected) {+    TextStream input <- TextStream.new(originalData)+    DefaultOrder<ZeoliteParsed> output <- defer+    \ StreamTokenizer:new(context: context, tokenizer: context.defaultTokenizer())+        .tokenizeAll(input, (output <- Vector<ZeoliteParsed>.new()))++    // Match output.+    \ output `Matches:tryWith` CheckSequence:matches(CheckSequence:using(expected))++    // Serialize output.+    [Append<Formatted> & Build<String>] builder <- String.builder()+    UnformattedFormatter formatter <- UnformattedFormatter.new()+    traverse (output.defaultOrder() -> ZeoliteParsed parsed) {+      \ builder.append(parsed.formatWith(formatter))+    }++    // Append unparsed remainder.+    while (`present` input.current()) {+      \ input.forward()+    }+    // This will be included in the output of failing tests.+    \ BasicOutput.stderr()+        .append("Remaining content: \"")+        .append(CharType.escapeBreaks(input.preview()))+        .append("\"\n")+    \ builder.append(input.take())++    // Check that all data is accounted for.+    \ builder.build() `Matches:tryWith` CheckValue:equals(originalData)+  }+}
− example/highlighter/test/zeolite.0rt
@@ -1,49 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2023 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at--    http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--testcase "working tests" {-  success TestChecker-}--unittest test {-  ZeoliteTokenizers allTokenizers <- ZeoliteTokenizers.new()-      .include<ZeoliteWhitespace>()-      .include<ZeoliteLineComment>()-      .include<ZeoliteCategoryName>()-      .include<ZeoliteParamName>()-  ZeoliteSelectFrom categoryOrParam <- ZeoliteSelectFrom.new()-      .include<ZeoliteWhitespace>()-      .include<ZeoliteLineComment>()-      .include<ZeoliteCategoryName>()-      .include<ZeoliteParamName>()--  TextStream input <- TextStream.new("Category #param // done")-  Vector<ZeoliteToken> output <- Vector<ZeoliteToken>.new()--  \ StreamTokenizer:new(tokenizers: allTokenizers, default: categoryOrParam).tokenizeAll(input, output)--  DefaultOrder<ZeoliteToken> expected <- Vector<ZeoliteToken>.new()-      .append(`require` ZeoliteToken.new(content: "Category", label: "ZeoliteCategoryName"))-      .append(`require` ZeoliteToken.new(content: " ", label: "ZeoliteWhitespace"))-      .append(`require` ZeoliteToken.new(content: "#param", label: "ZeoliteParamName"))-      .append(`require` ZeoliteToken.new(content: " ", label: "ZeoliteWhitespace"))-      .append(`require` ZeoliteToken.new(content: "// done", label: "ZeoliteLineComment"))--  \ input.atEnd() `Matches:with` CheckValue:equals(true)-  \ output `Matches:with` CheckSequence:matches(CheckSequence:using(expected))-}
lib/testing/src/check-sequence.0rx view
@@ -54,7 +54,13 @@   }    defaultOrder () {-    return #self{ original, original.defaultOrder() }+    scoped {+      optional Order<#x> order <- original.defaultOrder()+    } in if (`present` order) {+     return #self{ original, order }+    } else {+      return empty+    }   }    get () {
lib/testing/test/check-sequence.0rt view
@@ -176,13 +176,19 @@   success TestChecker } -unittest test {+unittest matchingValues {   ValueList<TestValue> actual <- ValueList<TestValue>.new()       .append(TestValue.default())       .append(TestValue.new(456, "something", empty))   ValueList<TestValue> expected <- ValueList<TestValue>.new()       .append(TestValue.default())       .append(TestValue.new(456, "something", empty))+  \ actual `Matches:with` CheckSequence:matches(CheckSequence:using(expected))+}++unittest bothEmpty {+  ValueList<TestValue> actual <- ValueList<TestValue>.new()+  ValueList<TestValue> expected <- ValueList<TestValue>.new()   \ actual `Matches:with` CheckSequence:matches(CheckSequence:using(expected)) } 
lib/util/helpers.0rp view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry+Copyright 2021,2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -239,4 +239,12 @@   #xx defines LessThan<#x>    @type new () -> (LessThan2<#x>)+}++// Helpers for Append<#x>.+concrete AppendH {+  // Appends all items in order.+  @category from<#x, #a>+    #a requires Append<#x>+  (#a, optional Order<#x>) -> (#a) }
lib/util/src/helpers.0rx view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry+Copyright 2021,2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -232,5 +232,14 @@    lessThan2 (x, y) {     return x `#xx.lessThan` y+  }+}++define AppendH {+  from (output, input) {+    traverse (input -> #x value) {+      \ output.append(value)+    }+    return output   } }
lib/util/test/helpers.0rt view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2021,2023 Kevin P. Barry+Copyright 2021,2023-2024 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -212,6 +212,11 @@     expected <- expected+1   }   \ expected `Matches:with` CheckValue:equals(5)+}++unittest appendHFrom {+  [Append<Formatted> & Build<String>] output <- String.builder() `AppendH:from` "123".defaultOrder() `AppendH:from` "abc".defaultOrder()+  \ output.build() `Matches:with` CheckValue:equals("123abc") }  concrete ValueOrder {
src/CompilerCxx/Code.hs view
@@ -102,6 +102,9 @@ startInitTracing :: CategoryName -> SymbolScope -> String startInitTracing t s = "TRACE_FUNCTION(" ++ show (show t ++ " " ++ show s ++ " init") ++ ")" +startLazyTracing :: CategoryName -> SymbolScope -> String+startLazyTracing t s = "TRACE_FUNCTION(" ++ show (show t ++ " " ++ show s ++ " init") ++ ")"+ startTestTracing :: FunctionName -> String startTestTracing f = "TRACE_FUNCTION(" ++ show ("unittest " ++ show f) ++ ")" 
src/CompilerCxx/CxxFiles.hs view
@@ -593,7 +593,7 @@    inlineCategoryConstructor t d tm em = do     ctx <- getContextForInit testing tm em t d CategoryScope-    initMembers <- runDataCompiler (sequence $ map compileLazyInit members) ctx+    initMembers <- runDataCompiler (sequence $ map (compileLazyInit $ getCategoryName t) members) ctx     let initMembersStr = intercalate ", " $ cdOutput initMembers     let initColon = if null initMembersStr then "" else " : "     return $ mconcat [
src/CompilerCxx/Procedure.hs view
@@ -46,6 +46,7 @@  import Base.CompilerError import Base.GeneralType+import Base.Mergeable (mergeAny) import Base.MergeTree import Base.Positional import Compilation.CompilerState@@ -413,9 +414,9 @@  compileLazyInit :: (Ord c, Show c, CollectErrorsM m,                    CompilerContext c m [String] a) =>-  DefinedMember c -> CompilerState a m ()-compileLazyInit (DefinedMember _ _ _ _ Nothing) = return ()-compileLazyInit (DefinedMember c _ t1 n (Just e)) = resetBackgroundM $ do+  CategoryName -> DefinedMember c -> CompilerState a m ()+compileLazyInit _ (DefinedMember _ _ _ _ Nothing) = return ()+compileLazyInit t0 (DefinedMember c _ t1 n (Just e)) = resetBackgroundM $ do   (ts,e') <- compileExpression e   when (length (pValues ts) /= 1) $     compilerErrorM $ "Expected single return in initializer" ++ formatFullContextBrace (getExpressionContext e)@@ -424,7 +425,14 @@   let Positional [t2] = ts   lift $ (checkValueAssignment r fa t2 t1) <??     "In initialization of " ++ show n ++ " at " ++ formatFullContext c-  csWrite [variableName n ++ "([this]() { return " ++ writeStoredVariable t1 e' ++ "; })"]+  let maybeTrace = setTraceContext c+  let trace = case maybeTrace of+                   [v] -> " " ++ v+                   _ -> ""+  -- NOTE: This needs to be on one line, due to how multiple member inits are concatenated.+  csWrite [variableName n ++ "([this]() { " ++ startInitTracing t0 CategoryScope ++ trace +++      " return " ++ writeStoredVariable t1 e' ++ "; })"+    ]  compileVoidExpression :: (Ord c, Show c, CollectErrorsM m,                          CompilerContext c m [String] a) =>@@ -706,6 +714,12 @@     bind t1' t2'     where       bind t1 t2+        | o == "<||" = do+          when (not $ isOptionalValue t1) $ do+            compilerErrorM $ "<|| requires the left expression to be optional but got " ++ show t1+          when (isWeakValue t2) $ do+            compilerErrorM $ "<|| requires the right expression to be not be weak but got " ++ show t2+          compileOptionalOr (t1,e1) (t2,e2)         | o `Set.member` comparison && isIdentifierRequiredValue t1 && isIdentifierRequiredValue t2 = do           return (Positional [boolRequiredValue],glueInfix PrimIdentifier PrimBool e1 o e2)         | t1 /= t2 =@@ -772,6 +786,11 @@     compilerErrorM $ "Function call requires one return but got " ++ formatTypes ts ++ formatFullContextBrace c2   formatTypes [] = "none"   formatTypes ts = intercalate ", " (map show ts)+  compileOptionalOr (t1,e1) (t2,e2) = do+    let t' = combineTypes t1 t2+    let code = WrappedSingle $ "TYPE_VALUE_LEFT_UNLESS_EMPTY(" ++ useAsUnwrapped e1 ++ ", " ++ useAsUnwrapped e2 ++ ")"+    return (Positional [t'], code)+  combineTypes (ValueType _ t1) (ValueType s t2) = ValueType s (mergeAny [t1,t2])  forceOptionalReturns :: [c] -> ScopedFunction c -> ScopedFunction c forceOptionalReturns c0 (ScopedFunction c n t s v as rs ps fs ms) =
src/Parser/Procedure.hs view
@@ -35,6 +35,7 @@ import qualified Data.Set as Set  import Base.CompilerError+import Base.GeneralType (singleType) import Base.Positional import Parser.Common import Parser.Pragma@@ -43,6 +44,7 @@ import Parser.TypeInstance () import Types.Procedure import Types.TypeCategory+import Types.TypeInstance (TypeInstanceOrParam(..), TypeInstance(..))   instance ParseFromSource (ExecutableProcedure SourceContext) where@@ -339,7 +341,7 @@   o <- op   return $ NamedOperator [c] o where     op = labeled "binary operator" $ foldr (<|>) empty $ map (try . operator) ops-    ops = compareInfix ++ logicalInfix ++ addInfix ++ subInfix ++ multInfix ++ divInfix ++ bitwiseInfix ++ bitshiftInfix+    ops = compareInfix ++ logicalInfix ++ addInfix ++ subInfix ++ multInfix ++ divInfix ++ bitwiseInfix ++ bitshiftInfix ++ optionalOrInfix  compareInfix :: [String] compareInfix = ["==","!=","<","<=",">",">="]@@ -365,11 +367,14 @@ bitshiftInfix :: [String] bitshiftInfix = [">>","<<"] +optionalOrInfix :: [String]+optionalOrInfix = ["<||"]+ leftAssocInfix :: [String] leftAssocInfix = addInfix ++ subInfix ++ multInfix ++ divInfix ++ bitwiseInfix ++ bitshiftInfix  rightAssocInfix :: [String]-rightAssocInfix = logicalInfix+rightAssocInfix = logicalInfix ++ optionalOrInfix  nonAssocInfix :: [String] nonAssocInfix = compareInfix@@ -729,7 +734,7 @@       return $ IntegerLiteral [c] unsigned d  instance ParseFromSource (ValueOperation SourceContext) where-  sourceParser = valueCall <|> conversion <|> selectReturn where+  sourceParser = conversion <|> valueCall <|> selectReturn where     valueCall = labeled "function call" $ do       c <- getSourceContext       o <- (valueSymbolGet >> return AlwaysCall) <|> (valueSymbolMaybeGet >> return CallUnlessEmpty)@@ -739,8 +744,18 @@     conversion = labeled "type conversion" $ do       c <- getSourceContext       inferredParam-      t <- sourceParser -- NOTE: Should not need try here.+      t <- typeInstance <|> sourceParser       return $ TypeConversion [c] t+    -- This is the same as the TypeInstance ParseFromSource implementation+    -- except that it uses try for params. This is necessary for < and <|| when+    -- the left side is converted to a category without params.+    typeInstance = labeled "type instance" $ do+      n <- sourceParser+      as <- labeled "type args" $ try args <|> return []+      return $ singleType $ JustTypeInstance $ TypeInstance n (Positional as)+    args = between (sepAfter $ string "<")+                   (sepAfter $ string ">")+                   (sepBy sourceParser (sepAfter $ string ","))     selectReturn = labeled "return selection" $ do       c <- getSourceContext       sepAfter_ (string_ "{")
src/Test/Procedure.hs view
@@ -284,9 +284,22 @@     checkShortParseFail "x <- \\o123.",     checkShortParseFail "x <- \\o123.456E1", +    checkShortParseSuccess "x <-| 123",     checkShortParseFail "Int x <-| 123",     checkShortParseFail "_ <-| 123",     checkShortParseFail "x, y <-| 123",++    checkShortParseSuccess "\\ x <|| y",+    checkShortParseSuccess "\\ empty?Int <|| y",+    checkShortParseSuccess "\\ 123 <|| 456",+    checkShortParseSuccess "\\ empty <|| 'a'",+    checkShortParseFail "Int x <|| 'a'",+    checkShortParseFail "x, y <|| 'a'",+    checkShortParseFail "\\ _ <|| 'a'",++    checkShortParseSuccess "\\ \"123\"?String < \"456\"",+    checkShortParseSuccess "\\ \"123\"?ReadAt < Char>",+    checkShortParseSuccess "\\ \"123\"?String < String.default()",      checkParsesAs "'\"'"                   (\e -> case e of
tests/conversions.0rt view
@@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------------Copyright 2020-2021 Kevin P. Barry+Copyright 2020-2021,2023 Kevin P. Barry  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.@@ -53,6 +53,14 @@ unittest convertedCall {   Value value <- Value.create()   \ value?Base1.call()+}++unittest convertWithLessThan {+  \ Testing.checkEquals("123"?String < "456", true)+}++unittest convertWithParamSubstitution {+  \ Testing.checkEquals("123"?ReadAt<Char>.readAt(1), '2') }  concrete Helper {
tests/delegation.0rt view
@@ -292,3 +292,62 @@     return delegate -> Value   } }+++testcase "delegate handles optional with &." {+  success+}++concrete Helper {+  @type testEmpty (Int) -> ()+  @type testNonEmpty (Int) -> ()+  @type call (Bool fake:) -> (optional String)+}++define Helper {+  testEmpty (position) {+    \ Testing.checkEquals(delegate -> `Helper.call(fake: true)&.readAt`, empty)+  }++  testNonEmpty (position) {+    \ Testing.checkEquals(delegate -> `Helper.call(fake: false)&.readAt`, "123".readAt(position))+  }++  call (fake) {+    if (fake) {+      return empty+    } else {+      return "123"+    }+  }+}++unittest testEmpty {+  \ Helper.testEmpty(1)+}++unittest testNonEmpty {+  \ Helper.testNonEmpty(1)+}+++testcase "delegate applies optional with &." {+  error+  require compiler "value"+  require compiler "modifier"+}++concrete Helper {+  @type test (Int) -> ()+  @type call () -> (optional String)+}++define Helper {+  test (position) {+    Char value <- delegate -> `Helper.call()&.readAt`+  }++  call () {+    return empty+  }+}
tests/modified-storage.0rt view
@@ -395,6 +395,114 @@ }  +testcase "optional or" {+  success+}++concrete Helper {+  @type notCalled () -> (all)+  @type getOnce () -> (optional Int)+}++define Helper {+  @category Bool used <- false++  notCalled () {+    fail("should not be called")+  }++  getOnce () {+    if (used) {+      fail("already called")+    }+    used <- true+    return 123+  }+}++unittest bothBoxedEmpty {+  optional String value <- empty+  \ Testing.checkEquals(value <|| "message", "message")+}++unittest bothBoxedNotEmpty {+  optional String value <- "other"+  \ Testing.checkEquals(value <|| "message", "other")+}++unittest leftUnboxedEmpty {+  optional Int value <- empty+  \ Testing.checkEquals((value <|| "message").formatted(), "message")+}++unittest leftUnboxedNotEmpty {+  optional Int value <- 123+  \ Testing.checkEquals((value <|| "message").formatted(), "123")+}++unittest rightUnboxedEmpty {+  optional String value <- empty+  \ Testing.checkEquals((value <|| 123).formatted(), "123")+}++unittest rightUnboxedNotEmpty {+  optional String value <- "message"+  \ Testing.checkEquals((value <|| 123).formatted(), "message")+}++unittest leftEvaluatedOnce {+  \ Testing.checkEquals(Helper.getOnce() <|| 456, 123)+}++unittest shortCircuit {+  optional String value <- "other"+  \ Testing.checkEquals(value <|| Helper.notCalled(), "other")+}+++testcase "optional-or required return type" {+  error+  require "\[Int\|String\]"+  exclude "optional"+}++unittest test {+  Int value <- empty?Int <|| "message"+}+++testcase "optional-or optional return type" {+  error+  require "modifier"+  exclude "\[Int\|String\]"+}++unittest test {+  Formatted value <- empty?Int <|| empty?String+}+++testcase "optional-or not allowed with left required" {+  error+  require "optional.+Int"+}++unittest test {+  \ 123 <|| 456+}+++testcase "optional-or not allowed with right weak" {+  error+  require "weak Int"+}++unittest test {+  weak Int value <- empty+  \ empty?Int <|| value+}++ testcase "conditional assignment" {   success }
zeolite-lang.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.2  name:                zeolite-lang-version:             0.24.0.1+version:             0.24.1.0 synopsis:            Zeolite is a statically-typed, general-purpose programming language.  description:@@ -59,7 +59,7 @@ license-file:        LICENSE author:              Kevin P. Barry maintainer:          Kevin P. Barry <ta0kira@gmail.com>-copyright:           (c) Kevin P. Barry 2019-2023+copyright:           (c) Kevin P. Barry 2019-2024 category:            Compiler build-type:          Simple