"Contract for stateful iterators, tokenizers etc which have the concept of a 'current position'." shared interface Positioned { "The position (in characters) within the input." shared formal Integer position; "The line number within the input." shared formal Integer line; "The column number within the current line." shared formal Integer column; "A string descriptor of the current position." shared String location => "``line``:``column`` (line:column)"; } "Contract for a tokenizer" shared abstract class Tokenizer() satisfies Positioned { variable Integer index = 0; variable Integer line_ = 1; variable Integer column_ = 1; shared actual Integer position => index; shared actual Integer line => line_; shared actual Integer column => column_; "Whether there is another character" shared formal Boolean hasMore; "The character at the current index, or throw" shared formal Character character(); "Move to the next character" shared void moveOne() { value c = character(); switch(c) case('\n'){ line_++; column_ = 1; } case('\r'){ column_ = 1; } else { column_++; } index++; } "Consume characters until the first non-whitespace" shared void eatSpaces(){ while(hasMore && isSpace(character())){ moveOne(); } } "Consume characters until the given character occurs" shared void eatSpacesUntil(Character c){ eatSpaces(); eat(c); } "If the current [[character]] is not the given character then return false. Otherwise [[moveOne]] and return true." shared Boolean check(Character c){ if(character() != c){ return false; } moveOne(); return true; } "If the current character is not the given character then throw, otherwise [[moveOne]]" shared void eat(Character c){ if(character() != c){ throw unexpectedCharacter(c); } moveOne(); } "The character at the current index, and move one" shared Character eatChar(){ Character c = character(); moveOne(); return c; } """true if the given character is a space, newline (`\n`), carriage return (`\r`) or a horizontal tab (`\t`). """ shared Boolean isSpace(Character c) => c == ' ' || c == '\n' || c == '\r' || c == '\t'; "true if the given character is `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8` or `9`." shared Boolean isDigit(Character c){ Integer codePoint = c.integer; return codePoint >= '0'.integer && codePoint <= '9'.integer; } shared ParseException exception(String message) => ParseException(message, line, column); shared ParseException unexpectedEnd=> exception( "Unexpected end of input"); shared ParseException unexpectedCharacter(Character|String? expected) => exception( "Expected `` expected else "end of input" `` but got `` character()``"); } "An implementation of Tokenizer using a String" shared class StringTokenizer(String chars) extends Tokenizer() { variable value count = -1; value size = chars.size; value it = chars.iterator(); variable Character|Finished? char = null; "Whether there is another character" shared actual Boolean hasMore => position < size; "The character at the current index, or throw" shared actual Character character() { while(count < position) { value c = it.next(); if(is Finished c){ throw unexpectedEnd; } count++; char = c; } assert(is Character last = char); return last; } }