gitoriaLog in with ident

ident

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Branchmain5fdbb6b2ident mission 009: report — scratch folder notemremain/lib/jsoncheck.hl

4.3 KB

  1. // jsoncheck.hl — WORKAROUND for hybriel ticket #12 (no soft JSON.parse): JSON.parse throws on
  2. // invalid input and a Hybriel program cannot catch it, so the framework answers 500 with the
  3. // source location. The API therefore checks the body's JSON SYNTAX by hand first (RFC 8259
  4. // grammar, nesting depth capped at 64, NO \u surrogate escapes — hl's JSON.parse refuses
  5. // them, even valid pairs) and answers 400. REMOVE this file and its one call in
  6. // api.hl (readBody) once JSON.parse has a failure path (#12).
  7. //
  8. // jsonErrorAt(text) → -1 when `text` is one valid JSON value (surrounded by whitespace),
  9. // else the 0-based character position of the first offence.
  10. static maxDepth = 64
  11. static isWs = (c) => { return c == 32 || c == 9 || c == 10 || c == 13 }
  12. static isDigit = (c) => { return c >= 48 && c <= 57 }
  13. static isHex = (c) => { return (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102) }
  14. static hexValue = (c) => { return c <= 57 ? c - 48 : (c <= 70 ? c - 55 : c - 87) }
  15. // the 4 hex digits at `at` as a number, or -1
  16. static hexAt = (s, at) => {
  17. if (at + 4 > s.length) { return -1 }
  18. let v = 0
  19. let k = 0
  20. while (k < 4) {
  21. let c = s.charCodeAt(at + k)
  22. if (!isHex(c)) { return -1 }
  23. v = v * 16 + hexValue(c)
  24. k = k + 1
  25. }
  26. return v
  27. }
  28. static skipWs = (s, at) => {
  29. let i = at
  30. while (i < s.length && isWs(s.charCodeAt(i))) { i = i + 1 }
  31. return i
  32. }
  33. // each scanner answers the position after what it read, or -(position + 1) of an offence
  34. static bad = (i) => { return -(i + 1) }
  35. static scanString = (s, at) => {
  36. let i = at
  37. i = i + 1
  38. while (i < s.length) {
  39. let c = s.charCodeAt(i)
  40. if (c == 34) { return i + 1 }
  41. if (c < 32) { return bad(i) }
  42. if (c == 92) {
  43. i = i + 1
  44. if (i >= s.length) { return bad(i) }
  45. let e = s.charCodeAt(i)
  46. if (e == 117) {
  47. let u = hexAt(s, i + 1)
  48. if (u < 0) { return bad(i) }
  49. // hl's JSON.parse refuses EVERY surrogate escape (\uD800-\uDFFF), even a valid
  50. // pair like \ud83d\ude00 (hybriel issue, mission 004) — refuse it here: 400, not 500.
  51. // Send such characters as raw UTF-8 instead (JS JSON.stringify does).
  52. if (u >= 55296 && u <= 57343) { return bad(i) }
  53. i = i + 4
  54. } else if (!(e == 34 || e == 92 || e == 47 || e == 98 || e == 102 || e == 110 || e == 114 || e == 116)) {
  55. return bad(i)
  56. }
  57. }
  58. i = i + 1
  59. }
  60. return bad(i)
  61. }
  62. static scanDigits = (s, at) => {
  63. let i = at
  64. start = i
  65. while (i < s.length && isDigit(s.charCodeAt(i))) { i = i + 1 }
  66. return i == start ? bad(i) : i
  67. }
  68. static scanNumber = (s, at) => {
  69. let i = at
  70. if (s.charCodeAt(i) == 45) { i = i + 1 }
  71. if (i >= s.length || !isDigit(s.charCodeAt(i))) { return bad(i) }
  72. if (s.charCodeAt(i) == 48) { i = i + 1 } else { i = scanDigits(s, i) }
  73. if (i < s.length && s.charCodeAt(i) == 46) {
  74. i = scanDigits(s, i + 1)
  75. if (i < 0) { return i }
  76. }
  77. if (i < s.length && (s.charCodeAt(i) == 101 || s.charCodeAt(i) == 69)) {
  78. i = i + 1
  79. if (i < s.length && (s.charCodeAt(i) == 43 || s.charCodeAt(i) == 45)) { i = i + 1 }
  80. i = scanDigits(s, i)
  81. }
  82. return i
  83. }
  84. static scanWord = (s, i, word) => {
  85. return s.slice(i, i + word.length) == word ? i + word.length : bad(i)
  86. }
  87. static scanValue = (s, at, depth) => {
  88. let i = at
  89. i = skipWs(s, i)
  90. if (i >= s.length) { return bad(i) }
  91. c = s.charCodeAt(i)
  92. if (c == 34) { return scanString(s, i) }
  93. if (c == 45 || isDigit(c)) { return scanNumber(s, i) }
  94. if (c == 116) { return scanWord(s, i, 'true') }
  95. if (c == 102) { return scanWord(s, i, 'false') }
  96. if (c == 110) { return scanWord(s, i, 'null') }
  97. if (c == 123 || c == 91) {
  98. if (depth >= maxDepth) { return bad(i) }
  99. close = c == 123 ? 125 : 93
  100. i = skipWs(s, i + 1)
  101. if (i < s.length && s.charCodeAt(i) == close) { return i + 1 }
  102. while (true) {
  103. if (c == 123) {
  104. i = skipWs(s, i)
  105. if (i >= s.length || s.charCodeAt(i) != 34) { return bad(i) }
  106. i = scanString(s, i)
  107. if (i < 0) { return i }
  108. i = skipWs(s, i)
  109. if (i >= s.length || s.charCodeAt(i) != 58) { return bad(i) }
  110. i = i + 1
  111. }
  112. i = scanValue(s, i, depth + 1)
  113. if (i < 0) { return i }
  114. i = skipWs(s, i)
  115. if (i >= s.length) { return bad(i) }
  116. let d = s.charCodeAt(i)
  117. if (d == close) { return i + 1 }
  118. if (d != 44) { return bad(i) }
  119. i = i + 1
  120. }
  121. }
  122. return bad(i)
  123. }
  124. static jsonErrorAt = (text) => {
  125. let i = scanValue(text, 0, 0)
  126. if (i < 0) { return -i - 1 }
  127. i = skipWs(text, i)
  128. return i < text.length ? i : -1
  129. }

Branches

  • mainmain branch

Latest commits

  • 5fdbb6b2ident mission 009: report — scratch folder notemre
  • 74235815ident mission 009 (4/4): docs (README files map + same-output test, STATUS, LOG), report, tests/letcount.py + realdata-baseline/comparemre
  • fe183516ident mission 009 (3/4): let only where reassigned — 293 never-reassigned lets are plain declarations; kept: reassigned, loop bodies, names of a file member, a name declared twice in one function; same outputmre
  • d2e7f91bident mission 009 (2/4): one lib file per topic (login, accounts, identities, apps, invites, selector, notify + helpers, util), function routes as thin wrappers in lib/api.hl, project.hl = the map; same outputmre
  • 91017164ident mission 009 (1/4): file moves — the root .hl files into lib/ (api.hl → lib/api-helpers.hl), styles.hl → components/styles.hl; imports adjusted, no other changemre
  • f8bdcbc2ident: Hybriel master 06617221 (plugin allocators 3a781359 + 413f60e4, mpackdb 2cb7ae5e, http1 773de63e); all gates greenmre
  • ff78726cident: Hybriel master 190aa11d (fc838894 GC correctness, #127, #126 closure scopes); gates all greenmre
  • a3a7d21aident: Hybriel master 8efba065 (#126 GC by bytes, #48 lambda params copy); session-writing lambdas take &sessionmre
  • 98226b41antcolony#40: mission references point to the moved missionsmre
  • ff805b9aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • 51a7bcdfident: Hybriel master 73267707 (#122); /code uses the new page() signature; pending address passed as parameter; once-checksmre
  • 836f644fident#24: installable app (manifest, service worker, data-free offline /start), own iconmre
  • 8bebbbf2deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • cc063ea2deploy.sh: never send .git or .gitignore to Byrodinmre
  • 81b15b7bState of 2026-09-27, before the move to gitoriamre