lexer.tri (6308B)
1 !import base * 2 !import parser * 3 !export * 4 5 -- Located character-scanning tools. Input is the canonical character list. 6 -- Input characters and position naturals are caller-validated; these are not 7 -- text-decoding or arbitrary-tree validation functions. LF alone advances line; 8 -- columns/offsets count source characters, not UTF-8 bytes or display cells. 9 -- A scan returns a parser reply whose payload is (state, endPosition). 10 -- The remainder starts with the first character that failed the predicate. 11 lexerPosition line column offset = pair line (pair column offset) 12 lexerPositionLine position = fst position 13 lexerPositionColumn position = fst (snd position) 14 lexerPositionOffset position = snd (snd position) 15 lexerStart = lexerPosition 1 1 0 16 17 lexerAdvance position character = 18 lazyBool 19 (_ : lexerPosition (succ (lexerPositionLine position)) 1 20 (succ (lexerPositionOffset position))) 21 (_ : lexerPosition (lexerPositionLine position) 22 (succ (lexerPositionColumn position)) 23 (succ (lexerPositionOffset position))) 24 (equal? character 10) 25 26 lexerToken kind payload start end = pair kind (pair payload (pair start end)) 27 lexerTokenKind token = fst token 28 lexerTokenPayload token = fst (snd token) 29 lexerTokenStart token = fst (snd (snd token)) 30 lexerTokenEnd token = snd (snd (snd token)) 31 32 -- The callbacks and starting position are caller-owned. Capture them once; 33 -- stopping leaves the whole first nonmatching suffix and never calls update. 34 -- Keep the cheap list eliminator in this hot loop: patterned dispatch measured 35 -- worse here. Local recursion still eliminates the explicit-self plumbing. 36 lexerScanWhile input position state predicate update = (let rec 37 walk remaining end accumulated consumed = (lazyList 38 (_ : parserReplyOk (pair accumulated end) remaining consumed false) 39 (character rest : lazyBool 40 (_ : walk rest (lexerAdvance end character) (update accumulated character) true) 41 (_ : parserReplyOk (pair accumulated end) remaining consumed false) 42 (predicate character)) 43 remaining) 44 in walk input position state false) 45 46 -- Transform the accumulated state without changing the location, remainder, 47 -- or consumption flags. Useful for reversing a name after scanning it. 48 lexerMapScan transform reply = 49 parserMatchReply 50 (diagnostic rest consumed committed : 51 parserReplyFail diagnostic rest consumed committed) 52 (stateAndEnd rest consumed committed : 53 parserReplyOk (pair (transform (fst stateAndEnd)) (snd stateAndEnd)) 54 rest consumed committed) 55 reply 56 57 -- A successful scan can be used as a trivia skip without inspecting its 58 -- accumulator. This destructures only the fixed-format reply; no rescan. 59 lexerRemainingAndEnd reply = 60 pair (parserReplyRest reply) (snd (parserReplyPayload reply)) 61 62 -- Ordered, single-character lexical alternatives. Each rule is a pair of 63 -- predicate and handler; the first matching handler owns its input and any 64 -- failure. This is dispatch, not implicit backtracking over failed scans. 65 lexerDispatch rules fallback character = (let rec 66 choose remaining = (case remaining of 67 [(predicate, handler) | rest] = (lazyBool 68 (_ : handler character) 69 (_ : choose rest) 70 (predicate character)) 71 _ = fallback character) 72 in choose rules) 73 74 -- Turn a completed state scan into one located token. `classify state` 75 -- supplies (kind, payload); `next` is the enclosing lexer continuation. 76 -- Emit/resume are entered after the outer lexer has consumed a prefix; failure 77 -- therefore reports consumed=true even when the inner scan consumed nothing. 78 lexerEmitScan scan start reversed classify next = 79 parserMatchReply 80 (diagnostic rest consumed committed : 81 parserReplyFail diagnostic rest true committed) 82 (stateAndEnd rest consumed committed : 83 matchPair 84 (kind payload : 85 next rest (snd stateAndEnd) 86 (pair (lexerToken kind payload start (snd stateAndEnd)) reversed)) 87 (classify (fst stateAndEnd))) 88 scan 89 90 -- Consume trivia and resume the outer lexer at its first unconsumed input. 91 lexerResumeScan scan next = 92 parserMatchReply 93 (diagnostic rest consumed committed : 94 parserReplyFail diagnostic rest true committed) 95 (stateAndEnd rest consumed committed : next rest (snd stateAndEnd)) 96 scan 97 98 -- Scan until a two-character terminator, leaving everything after it intact. 99 -- The supplied diagnostic describes EOF before the terminator. This is a 100 -- non-nesting primitive; callers that require nesting need an explicit state. 101 lexerSkipUntilPair input position first second missing = (let rec 102 walk remaining end consumed = (case remaining of 103 [] = parserReplyFail missing remaining consumed false 104 [character | rest] = (let advanced = lexerAdvance end character in 105 lazyBool 106 (_ : case rest of 107 [next | tail] = (lazyBool 108 (_ : parserReplyOk (pair t (lexerAdvance advanced next)) tail true false) 109 (_ : walk rest advanced true) 110 (equal? next second)) 111 _ = walk rest advanced true) 112 (_ : walk rest advanced true) 113 (equal? character first)) 114 _ = parserReplyFail missing remaining consumed false) 115 in walk input position false) 116 117 -- Opening delimiter already consumed. Escapes advance by two source cells, 118 -- not by the decoded character; invalid escapes retain the escape-led suffix. 119 lexerScanQuoted input position close escape decode missing invalid = (let rec 120 walk remaining end reversed = (case remaining of 121 [] = parserReplyFail missing remaining true false 122 [character | rest] = (let advanced = lexerAdvance end character in 123 lazyBool 124 (_ : parserReplyOk (pair (reverse reversed) advanced) rest true false) 125 (_ : lazyBool 126 (_ : case rest of 127 [escaped | tail] = (lazyMaybe 128 (_ : parserReplyFail (invalid escaped advanced) remaining true false) 129 (decoded : walk tail (lexerAdvance advanced escaped) (pair decoded reversed)) 130 (decode escaped)) 131 _ = parserReplyFail missing remaining true false) 132 (_ : walk rest advanced (pair character reversed)) 133 (equal? character escape)) 134 (equal? character close)) 135 _ = parserReplyFail missing remaining true false) 136 in walk input position [])