1 import Base 2 3 # Quote-aware scanning of a line (spec appendix B.3): where the first unquoted 4 # colon or delimiter is, and how to cut a row on its delimiter. 5 6 # Scanner state: 0 outside quotes, 1 inside, 2 inside after a backslash. 7 def sel.u(+b: Bool, +x: U32, +y: U32) -> U32: 8 match b: 9 case True{}: 10 x 11 case False{}: 12 y 13 14 def qnext.pick(+st: U32, +quote: Bool, +bslash: Bool) -> U32: 15 sel.u(U32.is_eq(st, 0), sel.u(quote, 1, 0), 16 sel.u(U32.is_eq(st, 1), sel.u(bslash, 2, sel.u(quote, 0, 1)), 1)) 17 18 def qnext(+st: U32, +c: U32) -> U32: 19 qnext.pick(st, U32.is_eq(c, 34), U32.is_eq(c, 92)) 20 21 # Sentinel index for "not found". 22 def none() -> U32: 23 4294967295 24 25 def found(+hit: Bool, +i: U32, +later: U32) -> U32: 26 sel.u(hit, i, later) 27 28 # Index of the first `target` outside quotes, or `none()`. 29 def find_unq(+s: String, +target: U32, +st: U32, +i: U32) -> U32: 30 match s: 31 case SNil{}: 32 none() 33 case SCon{Chr{c}, t}: 34 found(U32.is_eq(st, 0) && U32.is_eq(c, target), i, find_unq(t, target, qnext(st, c), (i + 1 : U32))) 35 36 # Put a character in front of the token being built, or start a new token. 37 def split.put(+c: U32, +sep: Bool, +r: List<&2, String>) -> List<&2, String>: 38 match sep: 39 case True{}: 40 Con{SNil{}, r} 41 case False{}: 42 match r: 43 case Con{h, rest}: 44 Con{SCon{Chr{c}, h}, rest} 45 case Nil{}: 46 Nil{} 47 48 def split.go(+s: String, +d: U32, +st: U32) -> List<&2, String>: 49 match s: 50 case SNil{}: 51 Con{SNil{}, Nil{}} 52 case SCon{Chr{c}, t}: 53 split.put(c, U32.is_eq(st, 0) && U32.is_eq(c, d), split.go(t, d, qnext(st, c))) 54 55 # Cut on the delimiter `d` outside quotes. Always at least one token. 56 def split(+s: String, +d: U32) -> List<&2, String>: 57 split.go(s, d, 0) 58 59 # Index of the first `target`, ignoring quotes, or `none()`. 60 def find(+s: String, +target: U32, +i: U32) -> U32: 61 match s: 62 case SNil{}: 63 none() 64 case SCon{Chr{c}, t}: 65 found(U32.is_eq(c, target), i, find(t, target, (i + 1 : U32)))