tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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 [])