~/bend-docscommunity

src/rules/correctness/escape.bend source

src/rules/correctness/escape.bend on the hub · documented module

# rule escape: a string or char literal with `\0` then a digit, as in# `"\033[1m"`. Bend's escapes are \n \t \r \0 \\ \' \" and \u{..}; it has no# octal, so `\033` is NUL and then the chars "33" (nohzafk esc_bad: the bytes# 00 33 33, not ESC), with no error. Write the code point, `\u{1B}`. Other# unknown escapes (\x1b, \e) the checker rejects on its own.import Baseimport ../../src.bend as Srcimport ../../finding.bend as Fimport ../../syntax/lex.bend as Leximport ../../lazy/lazy.bend as Lazy# a literal's char as the rule reads it: a backslash, a `0`, another digit,# or anything else. Read once, so the scan below matches constructors, not# char literals.type Sym is Data:  SSlash{}  SZero{}  SDigit{c: Char}  SOther{c: Char}# a char as a Symdef sym(cc: Char) -> Sym:  match cc:    case Chr{+x}:      Bool.pick(Sym, U32.is_eq(x, 92), SSlash{},        Bool.pick(Sym, U32.is_eq(x, 48), SZero{},          Bool.pick(Sym, Bool.and(U32.is_ge(x, 49), U32.is_le(x, 57)), SDigit{Chr{x}}, SOther{Chr{x}})))# a literal's chars as Symsdef syms(cs: List<&2, Char>) -> List<&2, Sym>:  match cs:    case Nil{}:      Nil{}    case Con{c, t}:      sym(c) <> syms(t)# the Sym's char when it is an octal digitdef octal(ss: Sym) -> Maybe<&2, Char>:  match ss:    case SZero{}:      Some{'0'}    case SDigit{Chr{+x}}:      Bool.pick(Maybe<&2, Char>, U32.is_le(x, 55), Some{Chr{x}}, None{})    case other:      None{}# keep the octal digit, and those after itdef run.if(mm: Maybe<&2, Char>, rest: List<&2, Char>) -> List<&2, Char>:  match mm:    case None{}:      Nil{}    case Some{c}:      c <> rest# the octal digits (at most two) right after the `\0`, in orderdef run(ss: List<&2, Sym>, nn: Nat) -> List<&2, Char>:  match ss nn:    case Nil{} m:      Nil{}    case Con{s, t} 0n:      Nil{}    case Con{s, t} 1n+p:      run.if(octal(s), run(t, p))# the digits' octal valuedef value(cs: List<&2, Char>, +acc: U32) -> U32:  match cs:    case Nil{}:      acc    case Con{Chr{+x}, t}:      value(t, ((acc * 8) + (x - 48) : U32))# a hex digitdef hexd(+nn: U32) -> String:  String.from_list([Chr{Bool.pick(U32, U32.is_lt(nn, 10), (nn + 48 : U32), (nn + 55 : U32))}])# a value under 256 in hex, without leading zerosdef hex(+nn: U32) -> String:  Bool.pick(String, U32.is_lt(nn, 16), hexd(nn), hexd((nn / 16 : U32)) ++ hexd((nn % 16 : U32)))# the finding for `\0` at a column, the digits after it in dsdef finding(+ds: List<&2, Char>, +path: String, +line: U32, +col: U32) -> F.Finding:  +digits = String.from_list(ds)  Lazy.stop(F.Finding, List.is_empty(&2, Char, ds),    F.Finding{path, line, col, 3, "escape",      "\\0 followed by digits is a NUL character and then those digits, not an octal escape; write \\u{..} instead."},    _u => F.Finding{path, line, col, (U32.from_nat(String.length(digits)) + 2 : U32), "escape",      "\\0" ++ digits ++ " is a NUL character followed by \"" ++ digits ++ "\", not an octal escape; write \\u{" ++ hex(value(ds, 0)) ++ "}."})# after a `\0` at a column: a finding when a digit followsdef after(ss: List<&2, Sym>, +path: String, +line: U32, +col: U32, more: List<&2, F.Finding>) -> List<&2, F.Finding>:  match ss:    case Con{SZero{}, t}:      finding(run(SZero{} <> t, 2n), path, line, col) <> more    case Con{SDigit{d}, t}:      finding(run(SDigit{d} <> t, 2n), path, line, col) <> more    case other:      more# a literal's Syms from a column; an escape is its backslash and one chardef scan(ss: List<&2, Sym>, +path: String, +line: U32, +col: U32) -> List<&2, F.Finding>:  match ss:    case Con{SSlash{}, Con{SZero{}, +rest}}:      after(rest, path, line, col, scan(rest, path, line, (col + 2 : U32)))    case Con{SSlash{}, Con{e, rest}}:      scan(rest, path, line, (col + 2 : U32))    case Con{s, rest}:      scan(rest, path, line, (col + 1 : U32))    case Nil{}:      Nil{}def check.go(toks: List<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>:  match toks:    case Con{Lex.Tok{Lex.TStr{}, t, l, c}, rest}:      List.append(&2, F.Finding, scan(syms(String.to_list(t)), path, l, c), check.go(rest, path))    case Con{Lex.Tok{Lex.TChar{}, t, l, c}, rest}:      List.append(&2, F.Finding, scan(syms(String.to_list(t)), path, l, c), check.go(rest, path))    case Con{h, rest}:      check.go(rest, path)    case Nil{}:      Nil{}# the ruledef check(ss: Src.Src) -> List<&2, F.Finding>:  Src.Src{path, text, toks, tree, bound, items} = ss  check.go(toks, path)