[{"data":1,"prerenderedAt":7475},["ShallowReactive",2],{"page-\u002Flua\u002F16-exercises-and-projects":3},{"id":4,"title":5,"body":6,"description":17,"extension":7465,"meta":7466,"navigation":63,"path":7467,"seo":7468,"stem":7473,"__hash__":7474},"content\u002Flua\u002F16-exercises-and-projects.md","[object Object]",{"type":7,"value":8,"toc":7454},"minimark",[9,14,18,23,3347,3351,7450],[10,11,13],"h1",{"id":12},"_16-capstone-projects-exercises","16 — Capstone: Projects & Exercises",[15,16,17],"p",{},"These projects integrate the concepts from all previous chapters into complete, working systems. Each is production-shaped: error handling, edge cases, and real-world patterns — not toy snippets.",[19,20,22],"h2",{"id":21},"project-1-json-parser-patterns-recursion","Project 1: JSON Parser (Patterns + Recursion)",[24,25,28],"code-wrapper",{"language":26,"filename":27},"lua","projects\u002Fjson_parser.lua",[29,30,34],"pre",{"className":31,"code":32,"language":26,"meta":33,"style":33},"language-lua shiki shiki-themes github-light github-dark","-- Minimal JSON parser using Lua patterns + recursive descent\n-- Supports: objects, arrays, strings, numbers, booleans, null\n-- Does NOT support: comments, trailing commas (strict JSON)\n\nlocal json = {}\n\n-- Skip whitespace\nlocal function skip_ws(s, i)\n  while i \u003C= #s and s:sub(i, i):match(\"%s\") do i = i + 1 end\n  return i\nend\n\n-- Parse a value: dispatch by first character\nlocal function parse_value(s, i)  -- forward declared, filled below\n  error(\"parse_value not initialized\", 2)\nend\n\n-- Parse string: handle escape sequences\nlocal function parse_string(s, i)\n  assert(s:sub(i, i) == '\"', \"expected string\")\n  i = i + 1\n  local parts = {}\n  while i \u003C= #s do\n    local c = s:sub(i, i)\n    if c == '\"' then\n      return table.concat(parts), i + 1  -- end of string, new position\n    elseif c == \"\\\\\" then\n      i = i + 1\n      local esc = s:sub(i, i)\n      local escapes = {['\"']='\"', ['\\\\']='\\\\', ['\u002F']='\u002F', b='\\b', f='\\f', n='\\n', r='\\r', t='\\t'}\n      if escapes[esc] then\n        parts[#parts + 1] = escapes[esc]\n      elseif esc == \"u\" then\n        -- Unicode escape: \\uXXXX → UTF-8 bytes\n        local hex = s:sub(i + 1, i + 4)\n        local cp = tonumber(hex, 16)\n        if not cp then error(\"invalid unicode escape: \" .. hex) end\n        parts[#parts + 1] = utf8.char(cp)\n        i = i + 4\n      else\n        error(\"invalid escape: \\\\\" .. esc)\n      end\n      i = i + 1\n    else\n      parts[#parts + 1] = c\n      i = i + 1\n    end\n  end\n  error(\"unterminated string\")\nend\n\n-- Parse number: integer, float, negative, scientific\nlocal function parse_number(s, i)\n  local start = i\n  if s:sub(i, i) == \"-\" then i = i + 1 end  -- negative sign\n  while i \u003C= #s and s:sub(i, i):match(\"[%d%.eE%+%-]\") do\n    -- careful: don't consume commas or brackets that look like signs\n    if s:sub(i, i):match(\"[eE]\") and i + 1 \u003C= #s and s:sub(i + 1, i + 1):match(\"[%d%+%-]\") then\n      i = i + 1  -- skip exponent sign\n    end\n    i = i + 1\n  end\n  local num_str = s:sub(start, i - 1)\n  local n = tonumber(num_str)\n  if not n then error(\"invalid number: \" .. num_str) end\n  return n, i\nend\n\n-- Parse object: { \"key\": value, ... }\nlocal function parse_object(s, i)\n  assert(s:sub(i, i) == \"{\", \"expected object\")\n  i = skip_ws(s, i + 1)\n  local obj = {}\n  if s:sub(i, i) == \"}\" then return obj, i + 1 end  -- empty object\n  while true do\n    i = skip_ws(s, i)\n    assert(s:sub(i, i) == '\"', \"expected key string\")\n    local key, i = parse_string(s, i)\n    i = skip_ws(s, i)\n    assert(s:sub(i, i) == \":\", \"expected colon\")\n    i = skip_ws(s, i + 1)\n    local value\n    value, i = parse_value(s, i)\n    obj[key] = value\n    i = skip_ws(s, i)\n    local c = s:sub(i, i)\n    if c == \"}\" then return obj, i + 1\n    elseif c == \",\" then i = skip_ws(s, i + 1)\n    else error(\"expected ',' or '}' in object at \" .. i) end\n  end\nend\n\n-- Parse array: [ value, value, ... ]\nlocal function parse_array(s, i)\n  assert(s:sub(i, i) == \"[\", \"expected array\")\n  i = skip_ws(s, i + 1)\n  local arr = {}\n  if s:sub(i, i) == \"]\" then return arr, i + 1 end  -- empty array\n  local idx = 1\n  while true do\n    local value\n    value, i = parse_value(s, i)\n    arr[idx] = value\n    idx = idx + 1\n    i = skip_ws(s, i)\n    local c = s:sub(i, i)\n    if c == \"]\" then return arr, i + 1\n    elseif c == \",\" then i = skip_ws(s, i + 1)\n    else error(\"expected ',' or ']' at \" .. i) end\n  end\nend\n\n-- Dispatch\nlocal parsers = {\n  ['\"'] = parse_string,\n  ['{'] = parse_object,\n  ['['] = parse_array,\n  ['t'] = function(s, i)  -- true\n    assert(s:sub(i, i + 3) == \"true\", \"expected true\")\n    return true, i + 4\n  end,\n  ['f'] = function(s, i)  -- false\n    assert(s:sub(i, i + 4) == \"false\", \"expected false\")\n    return false, i + 5\n  end,\n  ['n'] = function(s, i)  -- null → nil\n    assert(s:sub(i, i + 3) == \"null\", \"expected null\")\n    return nil, i + 4  -- NOTE: JSON null → Lua nil (cannot distinguish from missing)\n  end,\n}\n\n-- parse_value: dispatch table for clean O(1) selection\nparse_value = function(s, i)\n  i = skip_ws(s, i)\n  local c = s:sub(i, i)\n  local parser = parsers[c]\n  if parser then return parser(s, i) end\n  if c:match(\"[%-%d]\") then return parse_number(s, i) end\n  error(\"unexpected character at \" .. i .. \": \" .. c)\nend\n\n-- Public API: decode JSON string to Lua table\nfunction json.decode(s)\n  local value, i = parse_value(s, 1)\n  i = skip_ws(s, i)\n  if i \u003C= #s then error(\"trailing data at \" .. i) end\n  return value\nend\n\n-- Public API: encode Lua table to JSON string\nfunction json.encode(val, pretty)\n  local indent = pretty and \"  \" or \"\"\n  local function encode(v, level)\n    local t = type(v)\n    if t == \"nil\" then return \"null\"\n    elseif t == \"boolean\" then return v and \"true\" or \"false\"\n    elseif t == \"number\" then\n      if math.type(v) == \"integer\" or v == math.floor(v) then\n        return tostring(v)\n      end\n      return string.format(\"%.17g\", v)  -- full float precision\n    elseif t == \"string\" then\n      return string.format(\"%q\", v):gsub(\"\\\\\\n\", \"\\\\n\")  -- %q handles most escaping\n    elseif t == \"table\" then\n      local parts = {}\n      -- detect array vs object\n      local is_array = true\n      local max_idx = 0\n      for k in pairs(v) do\n        if type(k) == \"number\" and k == math.floor(k) and k >= 1 then\n          if k > max_idx then max_idx = k end\n        else is_array = false; break end\n      end\n      if is_array and max_idx == #v then\n        for i = 1, #v do\n          parts[i] = string.rep(indent, level + 1) .. encode(v[i], level + 1)\n        end\n        if #parts == 0 then return \"[]\" end\n        return \"[\\n\" .. table.concat(parts, \",\\n\") .. \"\\n\" .. string.rep(indent, level) .. \"]\"\n      else\n        local keys = {}\n        for k in pairs(v) do keys[#keys + 1] = k end\n        table.sort(keys, function(a, b) return tostring(a) \u003C tostring(b) end)\n        for _, k in ipairs(keys) do\n          local key_str = string.format(\"%q\", tostring(k))\n          local val_str = encode(v[k], level + 1)\n          parts[#parts + 1] = string.rep(indent, level + 1) .. key_str .. \": \" .. val_str\n        end\n        if #parts == 0 then return \"{}\" end\n        return \"{\\n\" .. table.concat(parts, \",\\n\") .. \"\\n\" .. string.rep(indent, level) .. \"}\"\n      end\n    end\n    error(\"cannot encode \" .. t, 2)\n  end\n  return encode(val, 0)\nend\n\n-- Test:\nlocal data = json.decode([[\n{\"name\": \"Alice\", \"age\": 30, \"tags\": [\"admin\", \"user\"], \"active\": true}\n]])\nprint(data.name, data.age, data.tags[1], data.active)  -- Alice 30 admin true\nprint(json.encode(data, true))\n","",[35,36,37,46,52,58,65,82,87,93,108,173,182,188,193,199,215,235,240,245,251,263,294,309,322,338,358,374,393,414,428,447,564,575,599,614,620,654,675,705,728,743,749,769,775,788,794,815,828,834,840,852,857,862,868,880,892,929,963,969,1040,1056,1061,1075,1080,1106,1121,1146,1154,1159,1164,1170,1182,1209,1227,1239,1274,1285,1296,1323,1337,1348,1375,1392,1400,1412,1422,1433,1450,1471,1499,1519,1524,1529,1534,1540,1552,1579,1596,1608,1642,1654,1663,1670,1681,1691,1705,1716,1733,1754,1781,1799,1804,1809,1814,1820,1833,1848,1863,1878,1897,1932,1946,1955,1974,2007,2022,2029,2048,2081,2098,2105,2110,2115,2121,2134,2145,2162,2175,2194,2223,2247,2252,2257,2263,2281,2301,2312,2340,2347,2352,2357,2363,2378,2402,2415,2431,2450,2479,2493,2522,2533,2538,2557,2571,2614,2628,2639,2645,2658,2671,2690,2726,2749,2769,2774,2794,2814,2847,2853,2877,2925,2930,2942,2977,3012,3030,3055,3076,3117,3122,3144,3189,3194,3199,3219,3224,3239,3244,3249,3255,3275,3281,3289,3328],"code",{"__ignoreMap":33},[38,39,42],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"sdCPZ","-- Minimal JSON parser using Lua patterns + recursive descent\n",[38,47,49],{"class":40,"line":48},2,[38,50,51],{"class":44},"-- Supports: objects, arrays, strings, numbers, booleans, null\n",[38,53,55],{"class":40,"line":54},3,[38,56,57],{"class":44},"-- Does NOT support: comments, trailing commas (strict JSON)\n",[38,59,61],{"class":40,"line":60},4,[38,62,64],{"emptyLinePlaceholder":63},true,"\n",[38,66,68,72,76,79],{"class":40,"line":67},5,[38,69,71],{"class":70},"svdQ7","local",[38,73,75],{"class":74},"ssxIu"," json ",[38,77,78],{"class":70},"=",[38,80,81],{"class":74}," {}\n",[38,83,85],{"class":40,"line":84},6,[38,86,64],{"emptyLinePlaceholder":63},[38,88,90],{"class":40,"line":89},7,[38,91,92],{"class":44},"-- Skip whitespace\n",[38,94,96,98,101,105],{"class":40,"line":95},8,[38,97,71],{"class":70},[38,99,100],{"class":70}," function",[38,102,104],{"class":103},"sIsaT"," skip_ws",[38,106,107],{"class":74},"(s, i)\n",[38,109,111,114,117,120,123,126,129,132,135,139,142,145,148,152,155,158,160,162,164,167,170],{"class":40,"line":110},9,[38,112,113],{"class":70},"  while",[38,115,116],{"class":74}," i ",[38,118,119],{"class":70},"\u003C=",[38,121,122],{"class":70}," #",[38,124,125],{"class":74},"s ",[38,127,128],{"class":70},"and",[38,130,131],{"class":103}," s",[38,133,134],{"class":74},":",[38,136,138],{"class":137},"snvgF","sub",[38,140,141],{"class":74},"(i, i):",[38,143,144],{"class":137},"match",[38,146,147],{"class":74},"(",[38,149,151],{"class":150},"sJ6F3","\"%s\"",[38,153,154],{"class":74},") ",[38,156,157],{"class":70},"do",[38,159,116],{"class":74},[38,161,78],{"class":70},[38,163,116],{"class":74},[38,165,166],{"class":70},"+",[38,168,169],{"class":137}," 1",[38,171,172],{"class":70}," end\n",[38,174,176,179],{"class":40,"line":175},10,[38,177,178],{"class":70},"  return",[38,180,181],{"class":74}," i\n",[38,183,185],{"class":40,"line":184},11,[38,186,187],{"class":70},"end\n",[38,189,191],{"class":40,"line":190},12,[38,192,64],{"emptyLinePlaceholder":63},[38,194,196],{"class":40,"line":195},13,[38,197,198],{"class":44},"-- Parse a value: dispatch by first character\n",[38,200,202,204,206,209,212],{"class":40,"line":201},14,[38,203,71],{"class":70},[38,205,100],{"class":70},[38,207,208],{"class":103}," parse_value",[38,210,211],{"class":74},"(s, i)  ",[38,213,214],{"class":44},"-- forward declared, filled below\n",[38,216,218,221,223,226,229,232],{"class":40,"line":217},15,[38,219,220],{"class":137},"  error",[38,222,147],{"class":74},[38,224,225],{"class":150},"\"parse_value not initialized\"",[38,227,228],{"class":74},", ",[38,230,231],{"class":137},"2",[38,233,234],{"class":74},")\n",[38,236,238],{"class":40,"line":237},16,[38,239,187],{"class":70},[38,241,243],{"class":40,"line":242},17,[38,244,64],{"emptyLinePlaceholder":63},[38,246,248],{"class":40,"line":247},18,[38,249,250],{"class":44},"-- Parse string: handle escape sequences\n",[38,252,254,256,258,261],{"class":40,"line":253},19,[38,255,71],{"class":70},[38,257,100],{"class":70},[38,259,260],{"class":103}," parse_string",[38,262,107],{"class":74},[38,264,266,269,271,274,276,278,281,284,287,289,292],{"class":40,"line":265},20,[38,267,268],{"class":137},"  assert",[38,270,147],{"class":74},[38,272,273],{"class":103},"s",[38,275,134],{"class":74},[38,277,138],{"class":137},[38,279,280],{"class":74},"(i, i) ",[38,282,283],{"class":70},"==",[38,285,286],{"class":150}," '\"'",[38,288,228],{"class":74},[38,290,291],{"class":150},"\"expected string\"",[38,293,234],{"class":74},[38,295,297,300,302,304,306],{"class":40,"line":296},21,[38,298,299],{"class":74},"  i ",[38,301,78],{"class":70},[38,303,116],{"class":74},[38,305,166],{"class":70},[38,307,308],{"class":137}," 1\n",[38,310,312,315,318,320],{"class":40,"line":311},22,[38,313,314],{"class":70},"  local",[38,316,317],{"class":74}," parts ",[38,319,78],{"class":70},[38,321,81],{"class":74},[38,323,325,327,329,331,333,335],{"class":40,"line":324},23,[38,326,113],{"class":70},[38,328,116],{"class":74},[38,330,119],{"class":70},[38,332,122],{"class":70},[38,334,125],{"class":74},[38,336,337],{"class":70},"do\n",[38,339,341,344,347,349,351,353,355],{"class":40,"line":340},24,[38,342,343],{"class":70},"    local",[38,345,346],{"class":74}," c ",[38,348,78],{"class":70},[38,350,131],{"class":103},[38,352,134],{"class":74},[38,354,138],{"class":137},[38,356,357],{"class":74},"(i, i)\n",[38,359,361,364,366,368,371],{"class":40,"line":360},25,[38,362,363],{"class":70},"    if",[38,365,346],{"class":74},[38,367,283],{"class":70},[38,369,370],{"class":150}," '\"' ",[38,372,373],{"class":70},"then\n",[38,375,377,380,383,386,388,390],{"class":40,"line":376},26,[38,378,379],{"class":70},"      return",[38,381,382],{"class":137}," table.concat",[38,384,385],{"class":74},"(parts), i ",[38,387,166],{"class":70},[38,389,169],{"class":137},[38,391,392],{"class":44},"  -- end of string, new position\n",[38,394,396,399,401,403,406,409,412],{"class":40,"line":395},27,[38,397,398],{"class":70},"    elseif",[38,400,346],{"class":74},[38,402,283],{"class":70},[38,404,405],{"class":150}," \"",[38,407,408],{"class":137},"\\\\",[38,410,411],{"class":150},"\" ",[38,413,373],{"class":70},[38,415,417,420,422,424,426],{"class":40,"line":416},28,[38,418,419],{"class":74},"      i ",[38,421,78],{"class":70},[38,423,116],{"class":74},[38,425,166],{"class":70},[38,427,308],{"class":137},[38,429,431,434,437,439,441,443,445],{"class":40,"line":430},29,[38,432,433],{"class":70},"      local",[38,435,436],{"class":74}," esc ",[38,438,78],{"class":70},[38,440,131],{"class":103},[38,442,134],{"class":74},[38,444,138],{"class":137},[38,446,357],{"class":74},[38,448,450,452,455,457,460,463,466,468,470,473,476,478,480,482,484,486,488,490,492,495,497,499,501,504,506,508,511,513,516,518,520,523,525,528,530,532,535,537,540,542,544,547,549,552,554,556,559,561],{"class":40,"line":449},30,[38,451,433],{"class":70},[38,453,454],{"class":74}," escapes ",[38,456,78],{"class":70},[38,458,459],{"class":74}," {[",[38,461,462],{"class":150},"'\"'",[38,464,465],{"class":74},"]",[38,467,78],{"class":70},[38,469,462],{"class":150},[38,471,472],{"class":74},", [",[38,474,475],{"class":150},"'",[38,477,408],{"class":137},[38,479,475],{"class":150},[38,481,465],{"class":74},[38,483,78],{"class":70},[38,485,475],{"class":150},[38,487,408],{"class":137},[38,489,475],{"class":150},[38,491,472],{"class":74},[38,493,494],{"class":150},"'\u002F'",[38,496,465],{"class":74},[38,498,78],{"class":70},[38,500,494],{"class":150},[38,502,503],{"class":74},", b",[38,505,78],{"class":70},[38,507,475],{"class":150},[38,509,510],{"class":137},"\\b",[38,512,475],{"class":150},[38,514,515],{"class":74},", f",[38,517,78],{"class":70},[38,519,475],{"class":150},[38,521,522],{"class":137},"\\f",[38,524,475],{"class":150},[38,526,527],{"class":74},", n",[38,529,78],{"class":70},[38,531,475],{"class":150},[38,533,534],{"class":137},"\\n",[38,536,475],{"class":150},[38,538,539],{"class":74},", r",[38,541,78],{"class":70},[38,543,475],{"class":150},[38,545,546],{"class":137},"\\r",[38,548,475],{"class":150},[38,550,551],{"class":74},", t",[38,553,78],{"class":70},[38,555,475],{"class":150},[38,557,558],{"class":137},"\\t",[38,560,475],{"class":150},[38,562,563],{"class":74},"}\n",[38,565,567,570,573],{"class":40,"line":566},31,[38,568,569],{"class":70},"      if",[38,571,572],{"class":74}," escapes[esc] ",[38,574,373],{"class":70},[38,576,578,581,584,587,589,591,594,596],{"class":40,"line":577},32,[38,579,580],{"class":74},"        parts[",[38,582,583],{"class":70},"#",[38,585,586],{"class":74},"parts ",[38,588,166],{"class":70},[38,590,169],{"class":137},[38,592,593],{"class":74},"] ",[38,595,78],{"class":70},[38,597,598],{"class":74}," escapes[esc]\n",[38,600,602,605,607,609,612],{"class":40,"line":601},33,[38,603,604],{"class":70},"      elseif",[38,606,436],{"class":74},[38,608,283],{"class":70},[38,610,611],{"class":150}," \"u\" ",[38,613,373],{"class":70},[38,615,617],{"class":40,"line":616},34,[38,618,619],{"class":44},"        -- Unicode escape: \\uXXXX → UTF-8 bytes\n",[38,621,623,626,629,631,633,635,637,640,642,644,647,649,652],{"class":40,"line":622},35,[38,624,625],{"class":70},"        local",[38,627,628],{"class":74}," hex ",[38,630,78],{"class":70},[38,632,131],{"class":103},[38,634,134],{"class":74},[38,636,138],{"class":137},[38,638,639],{"class":74},"(i ",[38,641,166],{"class":70},[38,643,169],{"class":137},[38,645,646],{"class":74},", i ",[38,648,166],{"class":70},[38,650,651],{"class":137}," 4",[38,653,234],{"class":74},[38,655,657,659,662,664,667,670,673],{"class":40,"line":656},36,[38,658,625],{"class":70},[38,660,661],{"class":74}," cp ",[38,663,78],{"class":70},[38,665,666],{"class":137}," tonumber",[38,668,669],{"class":74},"(hex, ",[38,671,672],{"class":137},"16",[38,674,234],{"class":74},[38,676,678,681,684,686,689,692,694,697,700,703],{"class":40,"line":677},37,[38,679,680],{"class":70},"        if",[38,682,683],{"class":70}," not",[38,685,661],{"class":74},[38,687,688],{"class":70},"then",[38,690,691],{"class":137}," error",[38,693,147],{"class":74},[38,695,696],{"class":150},"\"invalid unicode escape: \" ",[38,698,699],{"class":70},"..",[38,701,702],{"class":74}," hex) ",[38,704,187],{"class":70},[38,706,708,710,712,714,716,718,720,722,725],{"class":40,"line":707},38,[38,709,580],{"class":74},[38,711,583],{"class":70},[38,713,586],{"class":74},[38,715,166],{"class":70},[38,717,169],{"class":137},[38,719,593],{"class":74},[38,721,78],{"class":70},[38,723,724],{"class":137}," utf8.char",[38,726,727],{"class":74},"(cp)\n",[38,729,731,734,736,738,740],{"class":40,"line":730},39,[38,732,733],{"class":74},"        i ",[38,735,78],{"class":70},[38,737,116],{"class":74},[38,739,166],{"class":70},[38,741,742],{"class":137}," 4\n",[38,744,746],{"class":40,"line":745},40,[38,747,748],{"class":70},"      else\n",[38,750,752,755,757,760,762,764,766],{"class":40,"line":751},41,[38,753,754],{"class":137},"        error",[38,756,147],{"class":74},[38,758,759],{"class":150},"\"invalid escape: ",[38,761,408],{"class":137},[38,763,411],{"class":150},[38,765,699],{"class":70},[38,767,768],{"class":74}," esc)\n",[38,770,772],{"class":40,"line":771},42,[38,773,774],{"class":70},"      end\n",[38,776,778,780,782,784,786],{"class":40,"line":777},43,[38,779,419],{"class":74},[38,781,78],{"class":70},[38,783,116],{"class":74},[38,785,166],{"class":70},[38,787,308],{"class":137},[38,789,791],{"class":40,"line":790},44,[38,792,793],{"class":70},"    else\n",[38,795,797,800,802,804,806,808,810,812],{"class":40,"line":796},45,[38,798,799],{"class":74},"      parts[",[38,801,583],{"class":70},[38,803,586],{"class":74},[38,805,166],{"class":70},[38,807,169],{"class":137},[38,809,593],{"class":74},[38,811,78],{"class":70},[38,813,814],{"class":74}," c\n",[38,816,818,820,822,824,826],{"class":40,"line":817},46,[38,819,419],{"class":74},[38,821,78],{"class":70},[38,823,116],{"class":74},[38,825,166],{"class":70},[38,827,308],{"class":137},[38,829,831],{"class":40,"line":830},47,[38,832,833],{"class":70},"    end\n",[38,835,837],{"class":40,"line":836},48,[38,838,839],{"class":70},"  end\n",[38,841,843,845,847,850],{"class":40,"line":842},49,[38,844,220],{"class":137},[38,846,147],{"class":74},[38,848,849],{"class":150},"\"unterminated string\"",[38,851,234],{"class":74},[38,853,855],{"class":40,"line":854},50,[38,856,187],{"class":70},[38,858,860],{"class":40,"line":859},51,[38,861,64],{"emptyLinePlaceholder":63},[38,863,865],{"class":40,"line":864},52,[38,866,867],{"class":44},"-- Parse number: integer, float, negative, scientific\n",[38,869,871,873,875,878],{"class":40,"line":870},53,[38,872,71],{"class":70},[38,874,100],{"class":70},[38,876,877],{"class":103}," parse_number",[38,879,107],{"class":74},[38,881,883,885,888,890],{"class":40,"line":882},54,[38,884,314],{"class":70},[38,886,887],{"class":74}," start ",[38,889,78],{"class":70},[38,891,181],{"class":74},[38,893,895,898,900,902,904,906,908,911,913,915,917,919,921,923,926],{"class":40,"line":894},55,[38,896,897],{"class":70},"  if",[38,899,131],{"class":103},[38,901,134],{"class":74},[38,903,138],{"class":137},[38,905,280],{"class":74},[38,907,283],{"class":70},[38,909,910],{"class":150}," \"-\" ",[38,912,688],{"class":70},[38,914,116],{"class":74},[38,916,78],{"class":70},[38,918,116],{"class":74},[38,920,166],{"class":70},[38,922,169],{"class":137},[38,924,925],{"class":70}," end",[38,927,928],{"class":44},"  -- negative sign\n",[38,930,932,934,936,938,940,942,944,946,948,950,952,954,956,959,961],{"class":40,"line":931},56,[38,933,113],{"class":70},[38,935,116],{"class":74},[38,937,119],{"class":70},[38,939,122],{"class":70},[38,941,125],{"class":74},[38,943,128],{"class":70},[38,945,131],{"class":103},[38,947,134],{"class":74},[38,949,138],{"class":137},[38,951,141],{"class":74},[38,953,144],{"class":137},[38,955,147],{"class":74},[38,957,958],{"class":150},"\"[%d%.eE%+%-]\"",[38,960,154],{"class":74},[38,962,337],{"class":70},[38,964,966],{"class":40,"line":965},57,[38,967,968],{"class":44},"    -- careful: don't consume commas or brackets that look like signs\n",[38,970,972,974,976,978,980,982,984,986,989,991,993,995,997,999,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1029,1031,1033,1036,1038],{"class":40,"line":971},58,[38,973,363],{"class":70},[38,975,131],{"class":103},[38,977,134],{"class":74},[38,979,138],{"class":137},[38,981,141],{"class":74},[38,983,144],{"class":137},[38,985,147],{"class":74},[38,987,988],{"class":150},"\"[eE]\"",[38,990,154],{"class":74},[38,992,128],{"class":70},[38,994,116],{"class":74},[38,996,166],{"class":70},[38,998,169],{"class":137},[38,1000,1001],{"class":70}," \u003C=",[38,1003,122],{"class":70},[38,1005,125],{"class":74},[38,1007,128],{"class":70},[38,1009,131],{"class":103},[38,1011,134],{"class":74},[38,1013,138],{"class":137},[38,1015,639],{"class":74},[38,1017,166],{"class":70},[38,1019,169],{"class":137},[38,1021,646],{"class":74},[38,1023,166],{"class":70},[38,1025,169],{"class":137},[38,1027,1028],{"class":74},"):",[38,1030,144],{"class":137},[38,1032,147],{"class":74},[38,1034,1035],{"class":150},"\"[%d%+%-]\"",[38,1037,154],{"class":74},[38,1039,373],{"class":70},[38,1041,1043,1045,1047,1049,1051,1053],{"class":40,"line":1042},59,[38,1044,419],{"class":74},[38,1046,78],{"class":70},[38,1048,116],{"class":74},[38,1050,166],{"class":70},[38,1052,169],{"class":137},[38,1054,1055],{"class":44},"  -- skip exponent sign\n",[38,1057,1059],{"class":40,"line":1058},60,[38,1060,833],{"class":70},[38,1062,1064,1067,1069,1071,1073],{"class":40,"line":1063},61,[38,1065,1066],{"class":74},"    i ",[38,1068,78],{"class":70},[38,1070,116],{"class":74},[38,1072,166],{"class":70},[38,1074,308],{"class":137},[38,1076,1078],{"class":40,"line":1077},62,[38,1079,839],{"class":70},[38,1081,1083,1085,1088,1090,1092,1094,1096,1099,1102,1104],{"class":40,"line":1082},63,[38,1084,314],{"class":70},[38,1086,1087],{"class":74}," num_str ",[38,1089,78],{"class":70},[38,1091,131],{"class":103},[38,1093,134],{"class":74},[38,1095,138],{"class":137},[38,1097,1098],{"class":74},"(start, i ",[38,1100,1101],{"class":70},"-",[38,1103,169],{"class":137},[38,1105,234],{"class":74},[38,1107,1109,1111,1114,1116,1118],{"class":40,"line":1108},64,[38,1110,314],{"class":70},[38,1112,1113],{"class":74}," n ",[38,1115,78],{"class":70},[38,1117,666],{"class":137},[38,1119,1120],{"class":74},"(num_str)\n",[38,1122,1124,1126,1128,1130,1132,1134,1136,1139,1141,1144],{"class":40,"line":1123},65,[38,1125,897],{"class":70},[38,1127,683],{"class":70},[38,1129,1113],{"class":74},[38,1131,688],{"class":70},[38,1133,691],{"class":137},[38,1135,147],{"class":74},[38,1137,1138],{"class":150},"\"invalid number: \" ",[38,1140,699],{"class":70},[38,1142,1143],{"class":74}," num_str) ",[38,1145,187],{"class":70},[38,1147,1149,1151],{"class":40,"line":1148},66,[38,1150,178],{"class":70},[38,1152,1153],{"class":74}," n, i\n",[38,1155,1157],{"class":40,"line":1156},67,[38,1158,187],{"class":70},[38,1160,1162],{"class":40,"line":1161},68,[38,1163,64],{"emptyLinePlaceholder":63},[38,1165,1167],{"class":40,"line":1166},69,[38,1168,1169],{"class":44},"-- Parse object: { \"key\": value, ... }\n",[38,1171,1173,1175,1177,1180],{"class":40,"line":1172},70,[38,1174,71],{"class":70},[38,1176,100],{"class":70},[38,1178,1179],{"class":103}," parse_object",[38,1181,107],{"class":74},[38,1183,1185,1187,1189,1191,1193,1195,1197,1199,1202,1204,1207],{"class":40,"line":1184},71,[38,1186,268],{"class":137},[38,1188,147],{"class":74},[38,1190,273],{"class":103},[38,1192,134],{"class":74},[38,1194,138],{"class":137},[38,1196,280],{"class":74},[38,1198,283],{"class":70},[38,1200,1201],{"class":150}," \"{\"",[38,1203,228],{"class":74},[38,1205,1206],{"class":150},"\"expected object\"",[38,1208,234],{"class":74},[38,1210,1212,1214,1216,1218,1221,1223,1225],{"class":40,"line":1211},72,[38,1213,299],{"class":74},[38,1215,78],{"class":70},[38,1217,104],{"class":137},[38,1219,1220],{"class":74},"(s, i ",[38,1222,166],{"class":70},[38,1224,169],{"class":137},[38,1226,234],{"class":74},[38,1228,1230,1232,1235,1237],{"class":40,"line":1229},73,[38,1231,314],{"class":70},[38,1233,1234],{"class":74}," obj ",[38,1236,78],{"class":70},[38,1238,81],{"class":74},[38,1240,1242,1244,1246,1248,1250,1252,1254,1257,1259,1262,1265,1267,1269,1271],{"class":40,"line":1241},74,[38,1243,897],{"class":70},[38,1245,131],{"class":103},[38,1247,134],{"class":74},[38,1249,138],{"class":137},[38,1251,280],{"class":74},[38,1253,283],{"class":70},[38,1255,1256],{"class":150}," \"}\" ",[38,1258,688],{"class":70},[38,1260,1261],{"class":70}," return",[38,1263,1264],{"class":74}," obj, i ",[38,1266,166],{"class":70},[38,1268,169],{"class":137},[38,1270,925],{"class":70},[38,1272,1273],{"class":44},"  -- empty object\n",[38,1275,1277,1279,1282],{"class":40,"line":1276},75,[38,1278,113],{"class":70},[38,1280,1281],{"class":137}," true",[38,1283,1284],{"class":70}," do\n",[38,1286,1288,1290,1292,1294],{"class":40,"line":1287},76,[38,1289,1066],{"class":74},[38,1291,78],{"class":70},[38,1293,104],{"class":137},[38,1295,107],{"class":74},[38,1297,1299,1302,1304,1306,1308,1310,1312,1314,1316,1318,1321],{"class":40,"line":1298},77,[38,1300,1301],{"class":137},"    assert",[38,1303,147],{"class":74},[38,1305,273],{"class":103},[38,1307,134],{"class":74},[38,1309,138],{"class":137},[38,1311,280],{"class":74},[38,1313,283],{"class":70},[38,1315,286],{"class":150},[38,1317,228],{"class":74},[38,1319,1320],{"class":150},"\"expected key string\"",[38,1322,234],{"class":74},[38,1324,1326,1328,1331,1333,1335],{"class":40,"line":1325},78,[38,1327,343],{"class":70},[38,1329,1330],{"class":74}," key, i ",[38,1332,78],{"class":70},[38,1334,260],{"class":137},[38,1336,107],{"class":74},[38,1338,1340,1342,1344,1346],{"class":40,"line":1339},79,[38,1341,1066],{"class":74},[38,1343,78],{"class":70},[38,1345,104],{"class":137},[38,1347,107],{"class":74},[38,1349,1351,1353,1355,1357,1359,1361,1363,1365,1368,1370,1373],{"class":40,"line":1350},80,[38,1352,1301],{"class":137},[38,1354,147],{"class":74},[38,1356,273],{"class":103},[38,1358,134],{"class":74},[38,1360,138],{"class":137},[38,1362,280],{"class":74},[38,1364,283],{"class":70},[38,1366,1367],{"class":150}," \":\"",[38,1369,228],{"class":74},[38,1371,1372],{"class":150},"\"expected colon\"",[38,1374,234],{"class":74},[38,1376,1378,1380,1382,1384,1386,1388,1390],{"class":40,"line":1377},81,[38,1379,1066],{"class":74},[38,1381,78],{"class":70},[38,1383,104],{"class":137},[38,1385,1220],{"class":74},[38,1387,166],{"class":70},[38,1389,169],{"class":137},[38,1391,234],{"class":74},[38,1393,1395,1397],{"class":40,"line":1394},82,[38,1396,343],{"class":70},[38,1398,1399],{"class":74}," value\n",[38,1401,1403,1406,1408,1410],{"class":40,"line":1402},83,[38,1404,1405],{"class":74},"    value, i ",[38,1407,78],{"class":70},[38,1409,208],{"class":137},[38,1411,107],{"class":74},[38,1413,1415,1418,1420],{"class":40,"line":1414},84,[38,1416,1417],{"class":74},"    obj[key] ",[38,1419,78],{"class":70},[38,1421,1399],{"class":74},[38,1423,1425,1427,1429,1431],{"class":40,"line":1424},85,[38,1426,1066],{"class":74},[38,1428,78],{"class":70},[38,1430,104],{"class":137},[38,1432,107],{"class":74},[38,1434,1436,1438,1440,1442,1444,1446,1448],{"class":40,"line":1435},86,[38,1437,343],{"class":70},[38,1439,346],{"class":74},[38,1441,78],{"class":70},[38,1443,131],{"class":103},[38,1445,134],{"class":74},[38,1447,138],{"class":137},[38,1449,357],{"class":74},[38,1451,1453,1455,1457,1459,1461,1463,1465,1467,1469],{"class":40,"line":1452},87,[38,1454,363],{"class":70},[38,1456,346],{"class":74},[38,1458,283],{"class":70},[38,1460,1256],{"class":150},[38,1462,688],{"class":70},[38,1464,1261],{"class":70},[38,1466,1264],{"class":74},[38,1468,166],{"class":70},[38,1470,308],{"class":137},[38,1472,1474,1476,1478,1480,1483,1485,1487,1489,1491,1493,1495,1497],{"class":40,"line":1473},88,[38,1475,398],{"class":70},[38,1477,346],{"class":74},[38,1479,283],{"class":70},[38,1481,1482],{"class":150}," \",\" ",[38,1484,688],{"class":70},[38,1486,116],{"class":74},[38,1488,78],{"class":70},[38,1490,104],{"class":137},[38,1492,1220],{"class":74},[38,1494,166],{"class":70},[38,1496,169],{"class":137},[38,1498,234],{"class":74},[38,1500,1502,1505,1507,1509,1512,1514,1517],{"class":40,"line":1501},89,[38,1503,1504],{"class":70},"    else",[38,1506,691],{"class":137},[38,1508,147],{"class":74},[38,1510,1511],{"class":150},"\"expected ',' or '}' in object at \" ",[38,1513,699],{"class":70},[38,1515,1516],{"class":74}," i) ",[38,1518,187],{"class":70},[38,1520,1522],{"class":40,"line":1521},90,[38,1523,839],{"class":70},[38,1525,1527],{"class":40,"line":1526},91,[38,1528,187],{"class":70},[38,1530,1532],{"class":40,"line":1531},92,[38,1533,64],{"emptyLinePlaceholder":63},[38,1535,1537],{"class":40,"line":1536},93,[38,1538,1539],{"class":44},"-- Parse array: [ value, value, ... ]\n",[38,1541,1543,1545,1547,1550],{"class":40,"line":1542},94,[38,1544,71],{"class":70},[38,1546,100],{"class":70},[38,1548,1549],{"class":103}," parse_array",[38,1551,107],{"class":74},[38,1553,1555,1557,1559,1561,1563,1565,1567,1569,1572,1574,1577],{"class":40,"line":1554},95,[38,1556,268],{"class":137},[38,1558,147],{"class":74},[38,1560,273],{"class":103},[38,1562,134],{"class":74},[38,1564,138],{"class":137},[38,1566,280],{"class":74},[38,1568,283],{"class":70},[38,1570,1571],{"class":150}," \"[\"",[38,1573,228],{"class":74},[38,1575,1576],{"class":150},"\"expected array\"",[38,1578,234],{"class":74},[38,1580,1582,1584,1586,1588,1590,1592,1594],{"class":40,"line":1581},96,[38,1583,299],{"class":74},[38,1585,78],{"class":70},[38,1587,104],{"class":137},[38,1589,1220],{"class":74},[38,1591,166],{"class":70},[38,1593,169],{"class":137},[38,1595,234],{"class":74},[38,1597,1599,1601,1604,1606],{"class":40,"line":1598},97,[38,1600,314],{"class":70},[38,1602,1603],{"class":74}," arr ",[38,1605,78],{"class":70},[38,1607,81],{"class":74},[38,1609,1611,1613,1615,1617,1619,1621,1623,1626,1628,1630,1633,1635,1637,1639],{"class":40,"line":1610},98,[38,1612,897],{"class":70},[38,1614,131],{"class":103},[38,1616,134],{"class":74},[38,1618,138],{"class":137},[38,1620,280],{"class":74},[38,1622,283],{"class":70},[38,1624,1625],{"class":150}," \"]\" ",[38,1627,688],{"class":70},[38,1629,1261],{"class":70},[38,1631,1632],{"class":74}," arr, i ",[38,1634,166],{"class":70},[38,1636,169],{"class":137},[38,1638,925],{"class":70},[38,1640,1641],{"class":44},"  -- empty array\n",[38,1643,1645,1647,1650,1652],{"class":40,"line":1644},99,[38,1646,314],{"class":70},[38,1648,1649],{"class":74}," idx ",[38,1651,78],{"class":70},[38,1653,308],{"class":137},[38,1655,1657,1659,1661],{"class":40,"line":1656},100,[38,1658,113],{"class":70},[38,1660,1281],{"class":137},[38,1662,1284],{"class":70},[38,1664,1666,1668],{"class":40,"line":1665},101,[38,1667,343],{"class":70},[38,1669,1399],{"class":74},[38,1671,1673,1675,1677,1679],{"class":40,"line":1672},102,[38,1674,1405],{"class":74},[38,1676,78],{"class":70},[38,1678,208],{"class":137},[38,1680,107],{"class":74},[38,1682,1684,1687,1689],{"class":40,"line":1683},103,[38,1685,1686],{"class":74},"    arr[idx] ",[38,1688,78],{"class":70},[38,1690,1399],{"class":74},[38,1692,1694,1697,1699,1701,1703],{"class":40,"line":1693},104,[38,1695,1696],{"class":74},"    idx ",[38,1698,78],{"class":70},[38,1700,1649],{"class":74},[38,1702,166],{"class":70},[38,1704,308],{"class":137},[38,1706,1708,1710,1712,1714],{"class":40,"line":1707},105,[38,1709,1066],{"class":74},[38,1711,78],{"class":70},[38,1713,104],{"class":137},[38,1715,107],{"class":74},[38,1717,1719,1721,1723,1725,1727,1729,1731],{"class":40,"line":1718},106,[38,1720,343],{"class":70},[38,1722,346],{"class":74},[38,1724,78],{"class":70},[38,1726,131],{"class":103},[38,1728,134],{"class":74},[38,1730,138],{"class":137},[38,1732,357],{"class":74},[38,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752],{"class":40,"line":1735},107,[38,1737,363],{"class":70},[38,1739,346],{"class":74},[38,1741,283],{"class":70},[38,1743,1625],{"class":150},[38,1745,688],{"class":70},[38,1747,1261],{"class":70},[38,1749,1632],{"class":74},[38,1751,166],{"class":70},[38,1753,308],{"class":137},[38,1755,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775,1777,1779],{"class":40,"line":1756},108,[38,1758,398],{"class":70},[38,1760,346],{"class":74},[38,1762,283],{"class":70},[38,1764,1482],{"class":150},[38,1766,688],{"class":70},[38,1768,116],{"class":74},[38,1770,78],{"class":70},[38,1772,104],{"class":137},[38,1774,1220],{"class":74},[38,1776,166],{"class":70},[38,1778,169],{"class":137},[38,1780,234],{"class":74},[38,1782,1784,1786,1788,1790,1793,1795,1797],{"class":40,"line":1783},109,[38,1785,1504],{"class":70},[38,1787,691],{"class":137},[38,1789,147],{"class":74},[38,1791,1792],{"class":150},"\"expected ',' or ']' at \" ",[38,1794,699],{"class":70},[38,1796,1516],{"class":74},[38,1798,187],{"class":70},[38,1800,1802],{"class":40,"line":1801},110,[38,1803,839],{"class":70},[38,1805,1807],{"class":40,"line":1806},111,[38,1808,187],{"class":70},[38,1810,1812],{"class":40,"line":1811},112,[38,1813,64],{"emptyLinePlaceholder":63},[38,1815,1817],{"class":40,"line":1816},113,[38,1818,1819],{"class":44},"-- Dispatch\n",[38,1821,1823,1825,1828,1830],{"class":40,"line":1822},114,[38,1824,71],{"class":70},[38,1826,1827],{"class":74}," parsers ",[38,1829,78],{"class":70},[38,1831,1832],{"class":74}," {\n",[38,1834,1836,1839,1841,1843,1845],{"class":40,"line":1835},115,[38,1837,1838],{"class":74},"  [",[38,1840,462],{"class":150},[38,1842,593],{"class":74},[38,1844,78],{"class":70},[38,1846,1847],{"class":74}," parse_string,\n",[38,1849,1851,1853,1856,1858,1860],{"class":40,"line":1850},116,[38,1852,1838],{"class":74},[38,1854,1855],{"class":150},"'{'",[38,1857,593],{"class":74},[38,1859,78],{"class":70},[38,1861,1862],{"class":74}," parse_object,\n",[38,1864,1866,1868,1871,1873,1875],{"class":40,"line":1865},117,[38,1867,1838],{"class":74},[38,1869,1870],{"class":150},"'['",[38,1872,593],{"class":74},[38,1874,78],{"class":70},[38,1876,1877],{"class":74}," parse_array,\n",[38,1879,1881,1883,1886,1888,1890,1892,1894],{"class":40,"line":1880},118,[38,1882,1838],{"class":74},[38,1884,1885],{"class":150},"'t'",[38,1887,593],{"class":74},[38,1889,78],{"class":70},[38,1891,100],{"class":70},[38,1893,211],{"class":74},[38,1895,1896],{"class":44},"-- true\n",[38,1898,1900,1902,1904,1906,1908,1910,1913,1915,1918,1920,1922,1925,1927,1930],{"class":40,"line":1899},119,[38,1901,1301],{"class":137},[38,1903,147],{"class":74},[38,1905,273],{"class":103},[38,1907,134],{"class":74},[38,1909,138],{"class":137},[38,1911,1912],{"class":74},"(i, i ",[38,1914,166],{"class":70},[38,1916,1917],{"class":137}," 3",[38,1919,154],{"class":74},[38,1921,283],{"class":70},[38,1923,1924],{"class":150}," \"true\"",[38,1926,228],{"class":74},[38,1928,1929],{"class":150},"\"expected true\"",[38,1931,234],{"class":74},[38,1933,1935,1938,1940,1942,1944],{"class":40,"line":1934},120,[38,1936,1937],{"class":70},"    return",[38,1939,1281],{"class":137},[38,1941,646],{"class":74},[38,1943,166],{"class":70},[38,1945,742],{"class":137},[38,1947,1949,1952],{"class":40,"line":1948},121,[38,1950,1951],{"class":70},"  end",[38,1953,1954],{"class":74},",\n",[38,1956,1958,1960,1963,1965,1967,1969,1971],{"class":40,"line":1957},122,[38,1959,1838],{"class":74},[38,1961,1962],{"class":150},"'f'",[38,1964,593],{"class":74},[38,1966,78],{"class":70},[38,1968,100],{"class":70},[38,1970,211],{"class":74},[38,1972,1973],{"class":44},"-- false\n",[38,1975,1977,1979,1981,1983,1985,1987,1989,1991,1993,1995,1997,2000,2002,2005],{"class":40,"line":1976},123,[38,1978,1301],{"class":137},[38,1980,147],{"class":74},[38,1982,273],{"class":103},[38,1984,134],{"class":74},[38,1986,138],{"class":137},[38,1988,1912],{"class":74},[38,1990,166],{"class":70},[38,1992,651],{"class":137},[38,1994,154],{"class":74},[38,1996,283],{"class":70},[38,1998,1999],{"class":150}," \"false\"",[38,2001,228],{"class":74},[38,2003,2004],{"class":150},"\"expected false\"",[38,2006,234],{"class":74},[38,2008,2010,2012,2015,2017,2019],{"class":40,"line":2009},124,[38,2011,1937],{"class":70},[38,2013,2014],{"class":137}," false",[38,2016,646],{"class":74},[38,2018,166],{"class":70},[38,2020,2021],{"class":137}," 5\n",[38,2023,2025,2027],{"class":40,"line":2024},125,[38,2026,1951],{"class":70},[38,2028,1954],{"class":74},[38,2030,2032,2034,2037,2039,2041,2043,2045],{"class":40,"line":2031},126,[38,2033,1838],{"class":74},[38,2035,2036],{"class":150},"'n'",[38,2038,593],{"class":74},[38,2040,78],{"class":70},[38,2042,100],{"class":70},[38,2044,211],{"class":74},[38,2046,2047],{"class":44},"-- null → nil\n",[38,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2074,2076,2079],{"class":40,"line":2050},127,[38,2052,1301],{"class":137},[38,2054,147],{"class":74},[38,2056,273],{"class":103},[38,2058,134],{"class":74},[38,2060,138],{"class":137},[38,2062,1912],{"class":74},[38,2064,166],{"class":70},[38,2066,1917],{"class":137},[38,2068,154],{"class":74},[38,2070,283],{"class":70},[38,2072,2073],{"class":150}," \"null\"",[38,2075,228],{"class":74},[38,2077,2078],{"class":150},"\"expected null\"",[38,2080,234],{"class":74},[38,2082,2084,2086,2089,2091,2093,2095],{"class":40,"line":2083},128,[38,2085,1937],{"class":70},[38,2087,2088],{"class":137}," nil",[38,2090,646],{"class":74},[38,2092,166],{"class":70},[38,2094,651],{"class":137},[38,2096,2097],{"class":44},"  -- NOTE: JSON null → Lua nil (cannot distinguish from missing)\n",[38,2099,2101,2103],{"class":40,"line":2100},129,[38,2102,1951],{"class":70},[38,2104,1954],{"class":74},[38,2106,2108],{"class":40,"line":2107},130,[38,2109,563],{"class":74},[38,2111,2113],{"class":40,"line":2112},131,[38,2114,64],{"emptyLinePlaceholder":63},[38,2116,2118],{"class":40,"line":2117},132,[38,2119,2120],{"class":44},"-- parse_value: dispatch table for clean O(1) selection\n",[38,2122,2124,2127,2130,2132],{"class":40,"line":2123},133,[38,2125,2126],{"class":103},"parse_value",[38,2128,2129],{"class":70}," =",[38,2131,100],{"class":70},[38,2133,107],{"class":74},[38,2135,2137,2139,2141,2143],{"class":40,"line":2136},134,[38,2138,299],{"class":74},[38,2140,78],{"class":70},[38,2142,104],{"class":137},[38,2144,107],{"class":74},[38,2146,2148,2150,2152,2154,2156,2158,2160],{"class":40,"line":2147},135,[38,2149,314],{"class":70},[38,2151,346],{"class":74},[38,2153,78],{"class":70},[38,2155,131],{"class":103},[38,2157,134],{"class":74},[38,2159,138],{"class":137},[38,2161,357],{"class":74},[38,2163,2165,2167,2170,2172],{"class":40,"line":2164},136,[38,2166,314],{"class":70},[38,2168,2169],{"class":74}," parser ",[38,2171,78],{"class":70},[38,2173,2174],{"class":74}," parsers[c]\n",[38,2176,2178,2180,2182,2184,2186,2189,2192],{"class":40,"line":2177},137,[38,2179,897],{"class":70},[38,2181,2169],{"class":74},[38,2183,688],{"class":70},[38,2185,1261],{"class":70},[38,2187,2188],{"class":137}," parser",[38,2190,2191],{"class":74},"(s, i) ",[38,2193,187],{"class":70},[38,2195,2197,2199,2202,2204,2206,2208,2211,2213,2215,2217,2219,2221],{"class":40,"line":2196},138,[38,2198,897],{"class":70},[38,2200,2201],{"class":103}," c",[38,2203,134],{"class":74},[38,2205,144],{"class":137},[38,2207,147],{"class":74},[38,2209,2210],{"class":150},"\"[%-%d]\"",[38,2212,154],{"class":74},[38,2214,688],{"class":70},[38,2216,1261],{"class":70},[38,2218,877],{"class":137},[38,2220,2191],{"class":74},[38,2222,187],{"class":70},[38,2224,2226,2228,2230,2233,2235,2237,2239,2242,2244],{"class":40,"line":2225},139,[38,2227,220],{"class":137},[38,2229,147],{"class":74},[38,2231,2232],{"class":150},"\"unexpected character at \" ",[38,2234,699],{"class":70},[38,2236,116],{"class":74},[38,2238,699],{"class":70},[38,2240,2241],{"class":150}," \": \" ",[38,2243,699],{"class":70},[38,2245,2246],{"class":74}," c)\n",[38,2248,2250],{"class":40,"line":2249},140,[38,2251,187],{"class":70},[38,2253,2255],{"class":40,"line":2254},141,[38,2256,64],{"emptyLinePlaceholder":63},[38,2258,2260],{"class":40,"line":2259},142,[38,2261,2262],{"class":44},"-- Public API: decode JSON string to Lua table\n",[38,2264,2266,2269,2272,2275,2278],{"class":40,"line":2265},143,[38,2267,2268],{"class":70},"function",[38,2270,2271],{"class":103}," json",[38,2273,2274],{"class":74},".",[38,2276,2277],{"class":103},"decode",[38,2279,2280],{"class":74},"(s)\n",[38,2282,2284,2286,2289,2291,2293,2296,2299],{"class":40,"line":2283},144,[38,2285,314],{"class":70},[38,2287,2288],{"class":74}," value, i ",[38,2290,78],{"class":70},[38,2292,208],{"class":137},[38,2294,2295],{"class":74},"(s, ",[38,2297,2298],{"class":137},"1",[38,2300,234],{"class":74},[38,2302,2304,2306,2308,2310],{"class":40,"line":2303},145,[38,2305,299],{"class":74},[38,2307,78],{"class":70},[38,2309,104],{"class":137},[38,2311,107],{"class":74},[38,2313,2315,2317,2319,2321,2323,2325,2327,2329,2331,2334,2336,2338],{"class":40,"line":2314},146,[38,2316,897],{"class":70},[38,2318,116],{"class":74},[38,2320,119],{"class":70},[38,2322,122],{"class":70},[38,2324,125],{"class":74},[38,2326,688],{"class":70},[38,2328,691],{"class":137},[38,2330,147],{"class":74},[38,2332,2333],{"class":150},"\"trailing data at \" ",[38,2335,699],{"class":70},[38,2337,1516],{"class":74},[38,2339,187],{"class":70},[38,2341,2343,2345],{"class":40,"line":2342},147,[38,2344,178],{"class":70},[38,2346,1399],{"class":74},[38,2348,2350],{"class":40,"line":2349},148,[38,2351,187],{"class":70},[38,2353,2355],{"class":40,"line":2354},149,[38,2356,64],{"emptyLinePlaceholder":63},[38,2358,2360],{"class":40,"line":2359},150,[38,2361,2362],{"class":44},"-- Public API: encode Lua table to JSON string\n",[38,2364,2366,2368,2370,2372,2375],{"class":40,"line":2365},151,[38,2367,2268],{"class":70},[38,2369,2271],{"class":103},[38,2371,2274],{"class":74},[38,2373,2374],{"class":103},"encode",[38,2376,2377],{"class":74},"(val, pretty)\n",[38,2379,2381,2383,2386,2388,2391,2393,2396,2399],{"class":40,"line":2380},152,[38,2382,314],{"class":70},[38,2384,2385],{"class":74}," indent ",[38,2387,78],{"class":70},[38,2389,2390],{"class":74}," pretty ",[38,2392,128],{"class":70},[38,2394,2395],{"class":150}," \"  \" ",[38,2397,2398],{"class":70},"or",[38,2400,2401],{"class":150}," \"\"\n",[38,2403,2405,2407,2409,2412],{"class":40,"line":2404},153,[38,2406,314],{"class":70},[38,2408,100],{"class":70},[38,2410,2411],{"class":103}," encode",[38,2413,2414],{"class":74},"(v, level)\n",[38,2416,2418,2420,2423,2425,2428],{"class":40,"line":2417},154,[38,2419,343],{"class":70},[38,2421,2422],{"class":74}," t ",[38,2424,78],{"class":70},[38,2426,2427],{"class":137}," type",[38,2429,2430],{"class":74},"(v)\n",[38,2432,2434,2436,2438,2440,2443,2445,2447],{"class":40,"line":2433},155,[38,2435,363],{"class":70},[38,2437,2422],{"class":74},[38,2439,283],{"class":70},[38,2441,2442],{"class":150}," \"nil\" ",[38,2444,688],{"class":70},[38,2446,1261],{"class":70},[38,2448,2449],{"class":150}," \"null\"\n",[38,2451,2453,2455,2457,2459,2462,2464,2466,2469,2471,2474,2476],{"class":40,"line":2452},156,[38,2454,398],{"class":70},[38,2456,2422],{"class":74},[38,2458,283],{"class":70},[38,2460,2461],{"class":150}," \"boolean\" ",[38,2463,688],{"class":70},[38,2465,1261],{"class":70},[38,2467,2468],{"class":74}," v ",[38,2470,128],{"class":70},[38,2472,2473],{"class":150}," \"true\" ",[38,2475,2398],{"class":70},[38,2477,2478],{"class":150}," \"false\"\n",[38,2480,2482,2484,2486,2488,2491],{"class":40,"line":2481},157,[38,2483,398],{"class":70},[38,2485,2422],{"class":74},[38,2487,283],{"class":70},[38,2489,2490],{"class":150}," \"number\" ",[38,2492,373],{"class":70},[38,2494,2496,2498,2501,2504,2506,2509,2511,2513,2515,2518,2520],{"class":40,"line":2495},158,[38,2497,569],{"class":70},[38,2499,2500],{"class":137}," math.type",[38,2502,2503],{"class":74},"(v) ",[38,2505,283],{"class":70},[38,2507,2508],{"class":150}," \"integer\" ",[38,2510,2398],{"class":70},[38,2512,2468],{"class":74},[38,2514,283],{"class":70},[38,2516,2517],{"class":137}," math.floor",[38,2519,2503],{"class":74},[38,2521,373],{"class":70},[38,2523,2525,2528,2531],{"class":40,"line":2524},159,[38,2526,2527],{"class":70},"        return",[38,2529,2530],{"class":137}," tostring",[38,2532,2430],{"class":74},[38,2534,2536],{"class":40,"line":2535},160,[38,2537,774],{"class":70},[38,2539,2541,2543,2546,2548,2551,2554],{"class":40,"line":2540},161,[38,2542,379],{"class":70},[38,2544,2545],{"class":137}," string.format",[38,2547,147],{"class":74},[38,2549,2550],{"class":150},"\"%.17g\"",[38,2552,2553],{"class":74},", v)  ",[38,2555,2556],{"class":44},"-- full float precision\n",[38,2558,2560,2562,2564,2566,2569],{"class":40,"line":2559},162,[38,2561,398],{"class":70},[38,2563,2422],{"class":74},[38,2565,283],{"class":70},[38,2567,2568],{"class":150}," \"string\" ",[38,2570,373],{"class":70},[38,2572,2574,2576,2578,2580,2583,2586,2589,2591,2594,2597,2599,2601,2603,2605,2608,2611],{"class":40,"line":2573},163,[38,2575,379],{"class":70},[38,2577,2545],{"class":137},[38,2579,147],{"class":74},[38,2581,2582],{"class":150},"\"%q\"",[38,2584,2585],{"class":74},", v):",[38,2587,2588],{"class":137},"gsub",[38,2590,147],{"class":74},[38,2592,2593],{"class":150},"\"",[38,2595,2596],{"class":137},"\\\\\\n",[38,2598,2593],{"class":150},[38,2600,228],{"class":74},[38,2602,2593],{"class":150},[38,2604,408],{"class":137},[38,2606,2607],{"class":150},"n\"",[38,2609,2610],{"class":74},")  ",[38,2612,2613],{"class":44},"-- %q handles most escaping\n",[38,2615,2617,2619,2621,2623,2626],{"class":40,"line":2616},164,[38,2618,398],{"class":70},[38,2620,2422],{"class":74},[38,2622,283],{"class":70},[38,2624,2625],{"class":150}," \"table\" ",[38,2627,373],{"class":70},[38,2629,2631,2633,2635,2637],{"class":40,"line":2630},165,[38,2632,433],{"class":70},[38,2634,317],{"class":74},[38,2636,78],{"class":70},[38,2638,81],{"class":74},[38,2640,2642],{"class":40,"line":2641},166,[38,2643,2644],{"class":44},"      -- detect array vs object\n",[38,2646,2648,2650,2653,2655],{"class":40,"line":2647},167,[38,2649,433],{"class":70},[38,2651,2652],{"class":74}," is_array ",[38,2654,78],{"class":70},[38,2656,2657],{"class":137}," true\n",[38,2659,2661,2663,2666,2668],{"class":40,"line":2660},168,[38,2662,433],{"class":70},[38,2664,2665],{"class":74}," max_idx ",[38,2667,78],{"class":70},[38,2669,2670],{"class":137}," 0\n",[38,2672,2674,2677,2680,2683,2686,2688],{"class":40,"line":2673},169,[38,2675,2676],{"class":70},"      for",[38,2678,2679],{"class":74}," k ",[38,2681,2682],{"class":70},"in",[38,2684,2685],{"class":137}," pairs",[38,2687,2503],{"class":74},[38,2689,337],{"class":70},[38,2691,2693,2695,2697,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2721,2723],{"class":40,"line":2692},170,[38,2694,680],{"class":70},[38,2696,2427],{"class":137},[38,2698,2699],{"class":74},"(k) ",[38,2701,283],{"class":70},[38,2703,2490],{"class":150},[38,2705,128],{"class":70},[38,2707,2679],{"class":74},[38,2709,283],{"class":70},[38,2711,2517],{"class":137},[38,2713,2699],{"class":74},[38,2715,128],{"class":70},[38,2717,2679],{"class":74},[38,2719,2720],{"class":70},">=",[38,2722,169],{"class":137},[38,2724,2725],{"class":70}," then\n",[38,2727,2729,2732,2734,2737,2739,2741,2743,2745,2747],{"class":40,"line":2728},171,[38,2730,2731],{"class":70},"          if",[38,2733,2679],{"class":74},[38,2735,2736],{"class":70},">",[38,2738,2665],{"class":74},[38,2740,688],{"class":70},[38,2742,2665],{"class":74},[38,2744,78],{"class":70},[38,2746,2679],{"class":74},[38,2748,187],{"class":70},[38,2750,2752,2755,2757,2759,2761,2764,2767],{"class":40,"line":2751},172,[38,2753,2754],{"class":70},"        else",[38,2756,2652],{"class":74},[38,2758,78],{"class":70},[38,2760,2014],{"class":137},[38,2762,2763],{"class":74},"; ",[38,2765,2766],{"class":70},"break",[38,2768,172],{"class":70},[38,2770,2772],{"class":40,"line":2771},173,[38,2773,774],{"class":70},[38,2775,2777,2779,2781,2783,2785,2787,2789,2792],{"class":40,"line":2776},174,[38,2778,569],{"class":70},[38,2780,2652],{"class":74},[38,2782,128],{"class":70},[38,2784,2665],{"class":74},[38,2786,283],{"class":70},[38,2788,122],{"class":70},[38,2790,2791],{"class":74},"v ",[38,2793,373],{"class":70},[38,2795,2797,2800,2802,2804,2806,2808,2810,2812],{"class":40,"line":2796},175,[38,2798,2799],{"class":70},"        for",[38,2801,116],{"class":74},[38,2803,78],{"class":70},[38,2805,169],{"class":137},[38,2807,228],{"class":74},[38,2809,583],{"class":70},[38,2811,2791],{"class":74},[38,2813,337],{"class":70},[38,2815,2817,2820,2822,2825,2828,2830,2832,2834,2836,2838,2841,2843,2845],{"class":40,"line":2816},176,[38,2818,2819],{"class":74},"          parts[i] ",[38,2821,78],{"class":70},[38,2823,2824],{"class":137}," string.rep",[38,2826,2827],{"class":74},"(indent, level ",[38,2829,166],{"class":70},[38,2831,169],{"class":137},[38,2833,154],{"class":74},[38,2835,699],{"class":70},[38,2837,2411],{"class":137},[38,2839,2840],{"class":74},"(v[i], level ",[38,2842,166],{"class":70},[38,2844,169],{"class":137},[38,2846,234],{"class":74},[38,2848,2850],{"class":40,"line":2849},177,[38,2851,2852],{"class":70},"        end\n",[38,2854,2856,2858,2860,2862,2864,2867,2870,2872,2875],{"class":40,"line":2855},178,[38,2857,680],{"class":70},[38,2859,122],{"class":70},[38,2861,586],{"class":74},[38,2863,283],{"class":70},[38,2865,2866],{"class":137}," 0",[38,2868,2869],{"class":70}," then",[38,2871,1261],{"class":70},[38,2873,2874],{"class":150}," \"[]\" ",[38,2876,187],{"class":70},[38,2878,2880,2882,2885,2887,2889,2891,2893,2896,2899,2901,2903,2905,2907,2909,2911,2913,2915,2917,2920,2922],{"class":40,"line":2879},179,[38,2881,2527],{"class":70},[38,2883,2884],{"class":150}," \"[",[38,2886,534],{"class":137},[38,2888,411],{"class":150},[38,2890,699],{"class":70},[38,2892,382],{"class":137},[38,2894,2895],{"class":74},"(parts, ",[38,2897,2898],{"class":150},"\",",[38,2900,534],{"class":137},[38,2902,2593],{"class":150},[38,2904,154],{"class":74},[38,2906,699],{"class":70},[38,2908,405],{"class":150},[38,2910,534],{"class":137},[38,2912,411],{"class":150},[38,2914,699],{"class":70},[38,2916,2824],{"class":137},[38,2918,2919],{"class":74},"(indent, level) ",[38,2921,699],{"class":70},[38,2923,2924],{"class":150}," \"]\"\n",[38,2926,2928],{"class":40,"line":2927},180,[38,2929,748],{"class":70},[38,2931,2933,2935,2938,2940],{"class":40,"line":2932},181,[38,2934,625],{"class":70},[38,2936,2937],{"class":74}," keys ",[38,2939,78],{"class":70},[38,2941,81],{"class":74},[38,2943,2945,2947,2949,2951,2953,2955,2957,2960,2962,2965,2967,2969,2971,2973,2975],{"class":40,"line":2944},182,[38,2946,2799],{"class":70},[38,2948,2679],{"class":74},[38,2950,2682],{"class":70},[38,2952,2685],{"class":137},[38,2954,2503],{"class":74},[38,2956,157],{"class":70},[38,2958,2959],{"class":74}," keys[",[38,2961,583],{"class":70},[38,2963,2964],{"class":74},"keys ",[38,2966,166],{"class":70},[38,2968,169],{"class":137},[38,2970,593],{"class":74},[38,2972,78],{"class":70},[38,2974,2679],{"class":74},[38,2976,187],{"class":70},[38,2978,2980,2983,2986,2988,2991,2994,2996,2999,3002,3004,3007,3010],{"class":40,"line":2979},183,[38,2981,2982],{"class":137},"        table.sort",[38,2984,2985],{"class":74},"(keys, ",[38,2987,2268],{"class":70},[38,2989,2990],{"class":74},"(a, b) ",[38,2992,2993],{"class":70},"return",[38,2995,2530],{"class":137},[38,2997,2998],{"class":74},"(a) ",[38,3000,3001],{"class":70},"\u003C",[38,3003,2530],{"class":137},[38,3005,3006],{"class":74},"(b) ",[38,3008,3009],{"class":70},"end",[38,3011,234],{"class":74},[38,3013,3015,3017,3020,3022,3025,3028],{"class":40,"line":3014},184,[38,3016,2799],{"class":70},[38,3018,3019],{"class":74}," _, k ",[38,3021,2682],{"class":70},[38,3023,3024],{"class":137}," ipairs",[38,3026,3027],{"class":74},"(keys) ",[38,3029,337],{"class":70},[38,3031,3033,3036,3039,3041,3043,3045,3047,3049,3052],{"class":40,"line":3032},185,[38,3034,3035],{"class":70},"          local",[38,3037,3038],{"class":74}," key_str ",[38,3040,78],{"class":70},[38,3042,2545],{"class":137},[38,3044,147],{"class":74},[38,3046,2582],{"class":150},[38,3048,228],{"class":74},[38,3050,3051],{"class":137},"tostring",[38,3053,3054],{"class":74},"(k))\n",[38,3056,3058,3060,3063,3065,3067,3070,3072,3074],{"class":40,"line":3057},186,[38,3059,3035],{"class":70},[38,3061,3062],{"class":74}," val_str ",[38,3064,78],{"class":70},[38,3066,2411],{"class":137},[38,3068,3069],{"class":74},"(v[k], level ",[38,3071,166],{"class":70},[38,3073,169],{"class":137},[38,3075,234],{"class":74},[38,3077,3079,3082,3084,3086,3088,3090,3092,3094,3096,3098,3100,3102,3104,3106,3108,3110,3112,3114],{"class":40,"line":3078},187,[38,3080,3081],{"class":74},"          parts[",[38,3083,583],{"class":70},[38,3085,586],{"class":74},[38,3087,166],{"class":70},[38,3089,169],{"class":137},[38,3091,593],{"class":74},[38,3093,78],{"class":70},[38,3095,2824],{"class":137},[38,3097,2827],{"class":74},[38,3099,166],{"class":70},[38,3101,169],{"class":137},[38,3103,154],{"class":74},[38,3105,699],{"class":70},[38,3107,3038],{"class":74},[38,3109,699],{"class":70},[38,3111,2241],{"class":150},[38,3113,699],{"class":70},[38,3115,3116],{"class":74}," val_str\n",[38,3118,3120],{"class":40,"line":3119},188,[38,3121,2852],{"class":70},[38,3123,3125,3127,3129,3131,3133,3135,3137,3139,3142],{"class":40,"line":3124},189,[38,3126,680],{"class":70},[38,3128,122],{"class":70},[38,3130,586],{"class":74},[38,3132,283],{"class":70},[38,3134,2866],{"class":137},[38,3136,2869],{"class":70},[38,3138,1261],{"class":70},[38,3140,3141],{"class":150}," \"{}\" ",[38,3143,187],{"class":70},[38,3145,3147,3149,3152,3154,3156,3158,3160,3162,3164,3166,3168,3170,3172,3174,3176,3178,3180,3182,3184,3186],{"class":40,"line":3146},190,[38,3148,2527],{"class":70},[38,3150,3151],{"class":150}," \"{",[38,3153,534],{"class":137},[38,3155,411],{"class":150},[38,3157,699],{"class":70},[38,3159,382],{"class":137},[38,3161,2895],{"class":74},[38,3163,2898],{"class":150},[38,3165,534],{"class":137},[38,3167,2593],{"class":150},[38,3169,154],{"class":74},[38,3171,699],{"class":70},[38,3173,405],{"class":150},[38,3175,534],{"class":137},[38,3177,411],{"class":150},[38,3179,699],{"class":70},[38,3181,2824],{"class":137},[38,3183,2919],{"class":74},[38,3185,699],{"class":70},[38,3187,3188],{"class":150}," \"}\"\n",[38,3190,3192],{"class":40,"line":3191},191,[38,3193,774],{"class":70},[38,3195,3197],{"class":40,"line":3196},192,[38,3198,833],{"class":70},[38,3200,3202,3205,3207,3210,3212,3215,3217],{"class":40,"line":3201},193,[38,3203,3204],{"class":137},"    error",[38,3206,147],{"class":74},[38,3208,3209],{"class":150},"\"cannot encode \" ",[38,3211,699],{"class":70},[38,3213,3214],{"class":74}," t, ",[38,3216,231],{"class":137},[38,3218,234],{"class":74},[38,3220,3222],{"class":40,"line":3221},194,[38,3223,839],{"class":70},[38,3225,3227,3229,3231,3234,3237],{"class":40,"line":3226},195,[38,3228,178],{"class":70},[38,3230,2411],{"class":137},[38,3232,3233],{"class":74},"(val, ",[38,3235,3236],{"class":137},"0",[38,3238,234],{"class":74},[38,3240,3242],{"class":40,"line":3241},196,[38,3243,187],{"class":70},[38,3245,3247],{"class":40,"line":3246},197,[38,3248,64],{"emptyLinePlaceholder":63},[38,3250,3252],{"class":40,"line":3251},198,[38,3253,3254],{"class":44},"-- Test:\n",[38,3256,3258,3260,3263,3265,3268,3270,3272],{"class":40,"line":3257},199,[38,3259,71],{"class":70},[38,3261,3262],{"class":74}," data ",[38,3264,78],{"class":70},[38,3266,3267],{"class":74}," json.",[38,3269,2277],{"class":137},[38,3271,147],{"class":74},[38,3273,3274],{"class":150},"[[\n",[38,3276,3278],{"class":40,"line":3277},200,[38,3279,3280],{"class":150},"{\"name\": \"Alice\", \"age\": 30, \"tags\": [\"admin\", \"user\"], \"active\": true}\n",[38,3282,3284,3287],{"class":40,"line":3283},201,[38,3285,3286],{"class":150},"]]",[38,3288,234],{"class":74},[38,3290,3292,3295,3298,3301,3304,3307,3309,3312,3315,3317,3320,3323,3325],{"class":40,"line":3291},202,[38,3293,3294],{"class":137},"print",[38,3296,3297],{"class":74},"(data.",[38,3299,3300],{"class":103},"name",[38,3302,3303],{"class":74},", data.",[38,3305,3306],{"class":103},"age",[38,3308,3303],{"class":74},[38,3310,3311],{"class":103},"tags",[38,3313,3314],{"class":74},"[",[38,3316,2298],{"class":137},[38,3318,3319],{"class":74},"], data.",[38,3321,3322],{"class":103},"active",[38,3324,2610],{"class":74},[38,3326,3327],{"class":44},"-- Alice 30 admin true\n",[38,3329,3331,3333,3336,3338,3341,3344],{"class":40,"line":3330},203,[38,3332,3294],{"class":137},[38,3334,3335],{"class":74},"(json.",[38,3337,2374],{"class":137},[38,3339,3340],{"class":74},"(data, ",[38,3342,3343],{"class":137},"true",[38,3345,3346],{"class":74},"))\n",[19,3348,3350],{"id":3349},"project-2-coroutine-based-pipeline-processor","Project 2: Coroutine-Based Pipeline Processor",[24,3352,3354,3973,3977,6410,6414,7029,7033,7172,7176,7272,7276,7347,7351,7354,7447],{"language":26,"filename":3353},"projects\u002Fpipeline.lua",[29,3355,3357],{"className":31,"code":3356,"language":26,"meta":33,"style":33},"-- Data processing pipeline using coroutines (see ch11)\n-- Each stage is a coroutine that pulls from upstream and yields downstream\n-- Backpressure: consumer-driven (lazy, only processes when pulled)\n\nlocal function stage(name, input, transform)\n  return coroutine.wrap(function()\n    for item in input do  -- pull from upstream (may block until upstream yields)\n      local result = transform(item)\n      if result ~= nil then\n        coroutine.yield(result)  -- push downstream\n      end\n    end\n  end), name\nend\n\nlocal function source(items)\n  return coroutine.wrap(function()\n    for _, item in ipairs(items) do\n      coroutine.yield(item)\n    end\n  end)\nend\n\nlocal function sink(collect)\n  return function(input)\n    local results = {}\n    for item in input do\n      if collect then results[#results + 1] = item\n      else print(item) end\n    end\n    return results\n  end\nend\n\n-- Build a multi-stage pipeline\nlocal data = {\n  \"2024-01-15 INFO  server started\",\n  \"2024-01-15 ERROR connection timeout to db\",\n  \"2024-01-15 WARN  retry 1\u002F3\",\n  \"2024-01-15 ERROR auth failed for user admin\",\n  \"2024-01-15 INFO  request completed in 45ms\",\n}\n\nlocal pipe = source(data)\npipe = stage(\"parse\", pipe, function(line)\n  local date, level, msg = line:match(\"(%S+) (%S+) (.*)\")\n  return {date = date, level = level, msg = msg}\nend)\npipe = stage(\"filter_errors\", pipe, function(entry)\n  if entry.level == \"ERROR\" then return entry end  -- only errors pass through\n  return nil  -- filtered out (nil not yielded)\nend)\npipe = stage(\"format\", pipe, function(entry)\n  return string.format(\"[%s] %s: %s\", entry.date, entry.level, entry.msg)\nend)\n\nlocal results = sink(true)(pipe)\nfor _, r in ipairs(results) do print(r) end\n-- [2024-01-15] ERROR: connection timeout to db\n-- [2024-01-15] ERROR: auth failed for user admin\n",[35,3358,3359,3364,3369,3374,3378,3390,3404,3422,3437,3450,3461,3465,3469,3476,3480,3484,3496,3508,3524,3531,3535,3541,3545,3549,3561,3570,3581,3593,3621,3634,3638,3645,3649,3653,3657,3662,3672,3679,3686,3693,3700,3707,3711,3715,3729,3751,3774,3796,3802,3822,3850,3859,3865,3884,3912,3918,3922,3939,3963,3968],{"__ignoreMap":33},[38,3360,3361],{"class":40,"line":41},[38,3362,3363],{"class":44},"-- Data processing pipeline using coroutines (see ch11)\n",[38,3365,3366],{"class":40,"line":48},[38,3367,3368],{"class":44},"-- Each stage is a coroutine that pulls from upstream and yields downstream\n",[38,3370,3371],{"class":40,"line":54},[38,3372,3373],{"class":44},"-- Backpressure: consumer-driven (lazy, only processes when pulled)\n",[38,3375,3376],{"class":40,"line":60},[38,3377,64],{"emptyLinePlaceholder":63},[38,3379,3380,3382,3384,3387],{"class":40,"line":67},[38,3381,71],{"class":70},[38,3383,100],{"class":70},[38,3385,3386],{"class":103}," stage",[38,3388,3389],{"class":74},"(name, input, transform)\n",[38,3391,3392,3394,3397,3399,3401],{"class":40,"line":84},[38,3393,178],{"class":70},[38,3395,3396],{"class":137}," coroutine.wrap",[38,3398,147],{"class":74},[38,3400,2268],{"class":70},[38,3402,3403],{"class":74},"()\n",[38,3405,3406,3409,3412,3414,3417,3419],{"class":40,"line":89},[38,3407,3408],{"class":70},"    for",[38,3410,3411],{"class":74}," item ",[38,3413,2682],{"class":70},[38,3415,3416],{"class":74}," input ",[38,3418,157],{"class":70},[38,3420,3421],{"class":44},"  -- pull from upstream (may block until upstream yields)\n",[38,3423,3424,3426,3429,3431,3434],{"class":40,"line":95},[38,3425,433],{"class":70},[38,3427,3428],{"class":74}," result ",[38,3430,78],{"class":70},[38,3432,3433],{"class":137}," transform",[38,3435,3436],{"class":74},"(item)\n",[38,3438,3439,3441,3443,3446,3448],{"class":40,"line":110},[38,3440,569],{"class":70},[38,3442,3428],{"class":74},[38,3444,3445],{"class":70},"~=",[38,3447,2088],{"class":137},[38,3449,2725],{"class":70},[38,3451,3452,3455,3458],{"class":40,"line":175},[38,3453,3454],{"class":137},"        coroutine.yield",[38,3456,3457],{"class":74},"(result)  ",[38,3459,3460],{"class":44},"-- push downstream\n",[38,3462,3463],{"class":40,"line":184},[38,3464,774],{"class":70},[38,3466,3467],{"class":40,"line":190},[38,3468,833],{"class":70},[38,3470,3471,3473],{"class":40,"line":195},[38,3472,1951],{"class":70},[38,3474,3475],{"class":74},"), name\n",[38,3477,3478],{"class":40,"line":201},[38,3479,187],{"class":70},[38,3481,3482],{"class":40,"line":217},[38,3483,64],{"emptyLinePlaceholder":63},[38,3485,3486,3488,3490,3493],{"class":40,"line":237},[38,3487,71],{"class":70},[38,3489,100],{"class":70},[38,3491,3492],{"class":103}," source",[38,3494,3495],{"class":74},"(items)\n",[38,3497,3498,3500,3502,3504,3506],{"class":40,"line":242},[38,3499,178],{"class":70},[38,3501,3396],{"class":137},[38,3503,147],{"class":74},[38,3505,2268],{"class":70},[38,3507,3403],{"class":74},[38,3509,3510,3512,3515,3517,3519,3522],{"class":40,"line":247},[38,3511,3408],{"class":70},[38,3513,3514],{"class":74}," _, item ",[38,3516,2682],{"class":70},[38,3518,3024],{"class":137},[38,3520,3521],{"class":74},"(items) ",[38,3523,337],{"class":70},[38,3525,3526,3529],{"class":40,"line":253},[38,3527,3528],{"class":137},"      coroutine.yield",[38,3530,3436],{"class":74},[38,3532,3533],{"class":40,"line":265},[38,3534,833],{"class":70},[38,3536,3537,3539],{"class":40,"line":296},[38,3538,1951],{"class":70},[38,3540,234],{"class":74},[38,3542,3543],{"class":40,"line":311},[38,3544,187],{"class":70},[38,3546,3547],{"class":40,"line":324},[38,3548,64],{"emptyLinePlaceholder":63},[38,3550,3551,3553,3555,3558],{"class":40,"line":340},[38,3552,71],{"class":70},[38,3554,100],{"class":70},[38,3556,3557],{"class":103}," sink",[38,3559,3560],{"class":74},"(collect)\n",[38,3562,3563,3565,3567],{"class":40,"line":360},[38,3564,178],{"class":70},[38,3566,100],{"class":70},[38,3568,3569],{"class":74},"(input)\n",[38,3571,3572,3574,3577,3579],{"class":40,"line":376},[38,3573,343],{"class":70},[38,3575,3576],{"class":74}," results ",[38,3578,78],{"class":70},[38,3580,81],{"class":74},[38,3582,3583,3585,3587,3589,3591],{"class":40,"line":395},[38,3584,3408],{"class":70},[38,3586,3411],{"class":74},[38,3588,2682],{"class":70},[38,3590,3416],{"class":74},[38,3592,337],{"class":70},[38,3594,3595,3597,3600,3602,3605,3607,3610,3612,3614,3616,3618],{"class":40,"line":416},[38,3596,569],{"class":70},[38,3598,3599],{"class":74}," collect ",[38,3601,688],{"class":70},[38,3603,3604],{"class":74}," results[",[38,3606,583],{"class":70},[38,3608,3609],{"class":74},"results ",[38,3611,166],{"class":70},[38,3613,169],{"class":137},[38,3615,593],{"class":74},[38,3617,78],{"class":70},[38,3619,3620],{"class":74}," item\n",[38,3622,3623,3626,3629,3632],{"class":40,"line":430},[38,3624,3625],{"class":70},"      else",[38,3627,3628],{"class":137}," print",[38,3630,3631],{"class":74},"(item) ",[38,3633,187],{"class":70},[38,3635,3636],{"class":40,"line":449},[38,3637,833],{"class":70},[38,3639,3640,3642],{"class":40,"line":566},[38,3641,1937],{"class":70},[38,3643,3644],{"class":74}," results\n",[38,3646,3647],{"class":40,"line":577},[38,3648,839],{"class":70},[38,3650,3651],{"class":40,"line":601},[38,3652,187],{"class":70},[38,3654,3655],{"class":40,"line":616},[38,3656,64],{"emptyLinePlaceholder":63},[38,3658,3659],{"class":40,"line":622},[38,3660,3661],{"class":44},"-- Build a multi-stage pipeline\n",[38,3663,3664,3666,3668,3670],{"class":40,"line":656},[38,3665,71],{"class":70},[38,3667,3262],{"class":74},[38,3669,78],{"class":70},[38,3671,1832],{"class":74},[38,3673,3674,3677],{"class":40,"line":677},[38,3675,3676],{"class":150},"  \"2024-01-15 INFO  server started\"",[38,3678,1954],{"class":74},[38,3680,3681,3684],{"class":40,"line":707},[38,3682,3683],{"class":150},"  \"2024-01-15 ERROR connection timeout to db\"",[38,3685,1954],{"class":74},[38,3687,3688,3691],{"class":40,"line":730},[38,3689,3690],{"class":150},"  \"2024-01-15 WARN  retry 1\u002F3\"",[38,3692,1954],{"class":74},[38,3694,3695,3698],{"class":40,"line":745},[38,3696,3697],{"class":150},"  \"2024-01-15 ERROR auth failed for user admin\"",[38,3699,1954],{"class":74},[38,3701,3702,3705],{"class":40,"line":751},[38,3703,3704],{"class":150},"  \"2024-01-15 INFO  request completed in 45ms\"",[38,3706,1954],{"class":74},[38,3708,3709],{"class":40,"line":771},[38,3710,563],{"class":74},[38,3712,3713],{"class":40,"line":777},[38,3714,64],{"emptyLinePlaceholder":63},[38,3716,3717,3719,3722,3724,3726],{"class":40,"line":790},[38,3718,71],{"class":70},[38,3720,3721],{"class":74}," pipe ",[38,3723,78],{"class":70},[38,3725,3492],{"class":137},[38,3727,3728],{"class":74},"(data)\n",[38,3730,3731,3734,3736,3738,3740,3743,3746,3748],{"class":40,"line":796},[38,3732,3733],{"class":74},"pipe ",[38,3735,78],{"class":70},[38,3737,3386],{"class":137},[38,3739,147],{"class":74},[38,3741,3742],{"class":150},"\"parse\"",[38,3744,3745],{"class":74},", pipe, ",[38,3747,2268],{"class":70},[38,3749,3750],{"class":74},"(line)\n",[38,3752,3753,3755,3758,3760,3763,3765,3767,3769,3772],{"class":40,"line":817},[38,3754,314],{"class":70},[38,3756,3757],{"class":74}," date, level, msg ",[38,3759,78],{"class":70},[38,3761,3762],{"class":103}," line",[38,3764,134],{"class":74},[38,3766,144],{"class":137},[38,3768,147],{"class":74},[38,3770,3771],{"class":150},"\"(%S+) (%S+) (.*)\"",[38,3773,234],{"class":74},[38,3775,3776,3778,3781,3783,3786,3788,3791,3793],{"class":40,"line":830},[38,3777,178],{"class":70},[38,3779,3780],{"class":74}," {date ",[38,3782,78],{"class":70},[38,3784,3785],{"class":74}," date, level ",[38,3787,78],{"class":70},[38,3789,3790],{"class":74}," level, msg ",[38,3792,78],{"class":70},[38,3794,3795],{"class":74}," msg}\n",[38,3797,3798,3800],{"class":40,"line":836},[38,3799,3009],{"class":70},[38,3801,234],{"class":74},[38,3803,3804,3806,3808,3810,3812,3815,3817,3819],{"class":40,"line":842},[38,3805,3733],{"class":74},[38,3807,78],{"class":70},[38,3809,3386],{"class":137},[38,3811,147],{"class":74},[38,3813,3814],{"class":150},"\"filter_errors\"",[38,3816,3745],{"class":74},[38,3818,2268],{"class":70},[38,3820,3821],{"class":74},"(entry)\n",[38,3823,3824,3826,3829,3832,3835,3838,3840,3842,3845,3847],{"class":40,"line":854},[38,3825,897],{"class":70},[38,3827,3828],{"class":74}," entry.",[38,3830,3831],{"class":103},"level",[38,3833,3834],{"class":70}," ==",[38,3836,3837],{"class":150}," \"ERROR\" ",[38,3839,688],{"class":70},[38,3841,1261],{"class":70},[38,3843,3844],{"class":74}," entry ",[38,3846,3009],{"class":70},[38,3848,3849],{"class":44},"  -- only errors pass through\n",[38,3851,3852,3854,3856],{"class":40,"line":859},[38,3853,178],{"class":70},[38,3855,2088],{"class":137},[38,3857,3858],{"class":44},"  -- filtered out (nil not yielded)\n",[38,3860,3861,3863],{"class":40,"line":864},[38,3862,3009],{"class":70},[38,3864,234],{"class":74},[38,3866,3867,3869,3871,3873,3875,3878,3880,3882],{"class":40,"line":870},[38,3868,3733],{"class":74},[38,3870,78],{"class":70},[38,3872,3386],{"class":137},[38,3874,147],{"class":74},[38,3876,3877],{"class":150},"\"format\"",[38,3879,3745],{"class":74},[38,3881,2268],{"class":70},[38,3883,3821],{"class":74},[38,3885,3886,3888,3890,3892,3895,3898,3901,3903,3905,3907,3910],{"class":40,"line":882},[38,3887,178],{"class":70},[38,3889,2545],{"class":137},[38,3891,147],{"class":74},[38,3893,3894],{"class":150},"\"[%s] %s: %s\"",[38,3896,3897],{"class":74},", entry.",[38,3899,3900],{"class":103},"date",[38,3902,3897],{"class":74},[38,3904,3831],{"class":103},[38,3906,3897],{"class":74},[38,3908,3909],{"class":103},"msg",[38,3911,234],{"class":74},[38,3913,3914,3916],{"class":40,"line":894},[38,3915,3009],{"class":70},[38,3917,234],{"class":74},[38,3919,3920],{"class":40,"line":931},[38,3921,64],{"emptyLinePlaceholder":63},[38,3923,3924,3926,3928,3930,3932,3934,3936],{"class":40,"line":965},[38,3925,71],{"class":70},[38,3927,3576],{"class":74},[38,3929,78],{"class":70},[38,3931,3557],{"class":137},[38,3933,147],{"class":74},[38,3935,3343],{"class":137},[38,3937,3938],{"class":74},")(pipe)\n",[38,3940,3941,3944,3947,3949,3951,3954,3956,3958,3961],{"class":40,"line":971},[38,3942,3943],{"class":70},"for",[38,3945,3946],{"class":74}," _, r ",[38,3948,2682],{"class":70},[38,3950,3024],{"class":137},[38,3952,3953],{"class":74},"(results) ",[38,3955,157],{"class":70},[38,3957,3628],{"class":137},[38,3959,3960],{"class":74},"(r) ",[38,3962,187],{"class":70},[38,3964,3965],{"class":40,"line":1042},[38,3966,3967],{"class":44},"-- [2024-01-15] ERROR: connection timeout to db\n",[38,3969,3970],{"class":40,"line":1058},[38,3971,3972],{"class":44},"-- [2024-01-15] ERROR: auth failed for user admin\n",[19,3974,3976],{"id":3975},"project-3-orm-query-builder-metatables-chaining","Project 3: ORM Query Builder (Metatables + Chaining)",[24,3978,3980,5051,5055],{"language":26,"filename":3979},"projects\u002Fquery_builder.lua",[29,3981,3983],{"className":31,"code":3982,"language":26,"meta":33,"style":33},"-- Fluent query builder using metatables for method chaining\n-- Builds SQL strings with parameter binding (injection-safe)\n\nlocal QueryBuilder = {}\nQueryBuilder.__index = QueryBuilder\n\nfunction QueryBuilder.new(table_name)\n  return setmetatable({\n    table = table_name,\n    select = {\"*\"},\n    where_clauses = {},\n    params = {},  -- parameter values (for prepared statements)\n    order_by = nil,\n    limit_n = nil,\n    offset_n = nil,\n  }, QueryBuilder)\nend\n\n-- SELECT clause: list of columns\nfunction QueryBuilder:select_cols(...)\n  self.select = {...}\n  return self  -- fluent: return self for chaining\nend\n\n-- WHERE clause with parameter binding (prevents SQL injection)\nfunction QueryBuilder:where(condition, ...)\n  local args = {...}\n  -- Replace ? in condition with positional parameter placeholders\n  local clause = condition\n  local param_idx = #self.params\n  for _, arg in ipairs(args) do\n    param_idx = param_idx + 1\n    self.params[param_idx] = arg\n  end\n  self.where_clauses[#self.where_clauses + 1] = clause\n  return self\nend\n\n-- ORDER BY\nfunction QueryBuilder:order(column, direction)\n  -- Validate column name (prevent injection — only allow alphanumeric + underscore)\n  assert(column:match(\"^[%a_][%w_]*$\"), \"invalid column name: \" .. column)\n  self.order_by = column .. \" \" .. (direction or \"ASC\")\n  return self\nend\n\n-- LIMIT \u002F OFFSET\nfunction QueryBuilder:limit(n) self.limit_n = tonumber(n); return self end\nfunction QueryBuilder:offset(n) self.offset_n = tonumber(n); return self end\n\n-- Build the final SQL string + params array\nfunction QueryBuilder:build()\n  local sql = \"SELECT \" .. table.concat(self.select, \", \") .. \" FROM \" .. self.table\n  if #self.where_clauses > 0 then\n    sql = sql .. \" WHERE \" .. table.concat(self.where_clauses, \" AND \")\n  end\n  if self.order_by then\n    sql = sql .. \" ORDER BY \" .. self.order_by\n  end\n  if self.limit_n then\n    sql = sql .. \" LIMIT \" .. self.limit_n\n  end\n  if self.offset_n then\n    sql = sql .. \" OFFSET \" .. self.offset_n\n  end\n  return sql, self.params\nend\n\n-- __tostring for easy logging\nQueryBuilder.__tostring = function(self)\n  local sql, params = self:build()\n  if #params > 0 then\n    return sql .. \"  -- params: \" .. table.concat(params, \", \")\n  end\n  return sql\nend\n\n-- Usage:\nlocal q = QueryBuilder.new(\"users\")\n  :select_cols(\"id\", \"name\", \"email\")\n  :where(\"age > ?\", 18)\n  :where(\"status = ?\", \"active\")\n  :order(\"created_at\", \"DESC\")\n  :limit(10)\n\nprint(tostring(q))\n-- SELECT id, name, email FROM users WHERE age > ? AND status = ? ORDER BY created_at DESC LIMIT 10\n-- params: 18, active\n\n-- Execute (with a hypothetical db module):\n-- local sql, params = q:build()\n-- local results = db:execute(sql, unpack(params))\n",[35,3984,3985,3990,3995,3999,4010,4023,4027,4042,4052,4062,4078,4088,4101,4112,4123,4134,4139,4143,4147,4152,4166,4185,4195,4199,4203,4208,4222,4237,4242,4254,4273,4290,4303,4321,4325,4356,4363,4367,4371,4376,4390,4395,4424,4455,4461,4465,4469,4474,4508,4540,4544,4549,4562,4607,4626,4659,4663,4675,4697,4701,4713,4735,4739,4751,4773,4777,4790,4794,4798,4803,4817,4834,4849,4871,4875,4882,4886,4890,4895,4916,4940,4958,4976,4994,5007,5011,5022,5027,5032,5036,5041,5046],{"__ignoreMap":33},[38,3986,3987],{"class":40,"line":41},[38,3988,3989],{"class":44},"-- Fluent query builder using metatables for method chaining\n",[38,3991,3992],{"class":40,"line":48},[38,3993,3994],{"class":44},"-- Builds SQL strings with parameter binding (injection-safe)\n",[38,3996,3997],{"class":40,"line":54},[38,3998,64],{"emptyLinePlaceholder":63},[38,4000,4001,4003,4006,4008],{"class":40,"line":60},[38,4002,71],{"class":70},[38,4004,4005],{"class":74}," QueryBuilder ",[38,4007,78],{"class":70},[38,4009,81],{"class":74},[38,4011,4012,4015,4018,4020],{"class":40,"line":67},[38,4013,4014],{"class":74},"QueryBuilder.",[38,4016,4017],{"class":103},"__index",[38,4019,2129],{"class":70},[38,4021,4022],{"class":74}," QueryBuilder\n",[38,4024,4025],{"class":40,"line":84},[38,4026,64],{"emptyLinePlaceholder":63},[38,4028,4029,4031,4034,4036,4039],{"class":40,"line":89},[38,4030,2268],{"class":70},[38,4032,4033],{"class":103}," QueryBuilder",[38,4035,2274],{"class":74},[38,4037,4038],{"class":103},"new",[38,4040,4041],{"class":74},"(table_name)\n",[38,4043,4044,4046,4049],{"class":40,"line":95},[38,4045,178],{"class":70},[38,4047,4048],{"class":137}," setmetatable",[38,4050,4051],{"class":74},"({\n",[38,4053,4054,4057,4059],{"class":40,"line":110},[38,4055,4056],{"class":74},"    table ",[38,4058,78],{"class":70},[38,4060,4061],{"class":74}," table_name,\n",[38,4063,4064,4067,4069,4072,4075],{"class":40,"line":175},[38,4065,4066],{"class":74},"    select ",[38,4068,78],{"class":70},[38,4070,4071],{"class":74}," {",[38,4073,4074],{"class":150},"\"*\"",[38,4076,4077],{"class":74},"},\n",[38,4079,4080,4083,4085],{"class":40,"line":184},[38,4081,4082],{"class":74},"    where_clauses ",[38,4084,78],{"class":70},[38,4086,4087],{"class":74}," {},\n",[38,4089,4090,4093,4095,4098],{"class":40,"line":190},[38,4091,4092],{"class":74},"    params ",[38,4094,78],{"class":70},[38,4096,4097],{"class":74}," {},  ",[38,4099,4100],{"class":44},"-- parameter values (for prepared statements)\n",[38,4102,4103,4106,4108,4110],{"class":40,"line":195},[38,4104,4105],{"class":74},"    order_by ",[38,4107,78],{"class":70},[38,4109,2088],{"class":137},[38,4111,1954],{"class":74},[38,4113,4114,4117,4119,4121],{"class":40,"line":201},[38,4115,4116],{"class":74},"    limit_n ",[38,4118,78],{"class":70},[38,4120,2088],{"class":137},[38,4122,1954],{"class":74},[38,4124,4125,4128,4130,4132],{"class":40,"line":217},[38,4126,4127],{"class":74},"    offset_n ",[38,4129,78],{"class":70},[38,4131,2088],{"class":137},[38,4133,1954],{"class":74},[38,4135,4136],{"class":40,"line":237},[38,4137,4138],{"class":74},"  }, QueryBuilder)\n",[38,4140,4141],{"class":40,"line":242},[38,4142,187],{"class":70},[38,4144,4145],{"class":40,"line":247},[38,4146,64],{"emptyLinePlaceholder":63},[38,4148,4149],{"class":40,"line":253},[38,4150,4151],{"class":44},"-- SELECT clause: list of columns\n",[38,4153,4154,4156,4158,4160,4163],{"class":40,"line":265},[38,4155,2268],{"class":70},[38,4157,4033],{"class":103},[38,4159,134],{"class":74},[38,4161,4162],{"class":103},"select_cols",[38,4164,4165],{"class":74},"(...)\n",[38,4167,4168,4171,4173,4176,4178,4180,4183],{"class":40,"line":296},[38,4169,4170],{"class":137},"  self",[38,4172,2274],{"class":74},[38,4174,4175],{"class":103},"select",[38,4177,2129],{"class":70},[38,4179,4071],{"class":74},[38,4181,4182],{"class":137},"...",[38,4184,563],{"class":74},[38,4186,4187,4189,4192],{"class":40,"line":311},[38,4188,178],{"class":70},[38,4190,4191],{"class":137}," self",[38,4193,4194],{"class":44},"  -- fluent: return self for chaining\n",[38,4196,4197],{"class":40,"line":324},[38,4198,187],{"class":70},[38,4200,4201],{"class":40,"line":340},[38,4202,64],{"emptyLinePlaceholder":63},[38,4204,4205],{"class":40,"line":360},[38,4206,4207],{"class":44},"-- WHERE clause with parameter binding (prevents SQL injection)\n",[38,4209,4210,4212,4214,4216,4219],{"class":40,"line":376},[38,4211,2268],{"class":70},[38,4213,4033],{"class":103},[38,4215,134],{"class":74},[38,4217,4218],{"class":103},"where",[38,4220,4221],{"class":74},"(condition, ...)\n",[38,4223,4224,4226,4229,4231,4233,4235],{"class":40,"line":395},[38,4225,314],{"class":70},[38,4227,4228],{"class":74}," args ",[38,4230,78],{"class":70},[38,4232,4071],{"class":74},[38,4234,4182],{"class":137},[38,4236,563],{"class":74},[38,4238,4239],{"class":40,"line":416},[38,4240,4241],{"class":44},"  -- Replace ? in condition with positional parameter placeholders\n",[38,4243,4244,4246,4249,4251],{"class":40,"line":430},[38,4245,314],{"class":70},[38,4247,4248],{"class":74}," clause ",[38,4250,78],{"class":70},[38,4252,4253],{"class":74}," condition\n",[38,4255,4256,4258,4261,4263,4265,4268,4270],{"class":40,"line":449},[38,4257,314],{"class":70},[38,4259,4260],{"class":74}," param_idx ",[38,4262,78],{"class":70},[38,4264,122],{"class":70},[38,4266,4267],{"class":137},"self",[38,4269,2274],{"class":74},[38,4271,4272],{"class":103},"params\n",[38,4274,4275,4278,4281,4283,4285,4288],{"class":40,"line":566},[38,4276,4277],{"class":70},"  for",[38,4279,4280],{"class":74}," _, arg ",[38,4282,2682],{"class":70},[38,4284,3024],{"class":137},[38,4286,4287],{"class":74},"(args) ",[38,4289,337],{"class":70},[38,4291,4292,4295,4297,4299,4301],{"class":40,"line":577},[38,4293,4294],{"class":74},"    param_idx ",[38,4296,78],{"class":70},[38,4298,4260],{"class":74},[38,4300,166],{"class":70},[38,4302,308],{"class":137},[38,4304,4305,4308,4310,4313,4316,4318],{"class":40,"line":601},[38,4306,4307],{"class":137},"    self",[38,4309,2274],{"class":74},[38,4311,4312],{"class":103},"params",[38,4314,4315],{"class":74},"[param_idx] ",[38,4317,78],{"class":70},[38,4319,4320],{"class":74}," arg\n",[38,4322,4323],{"class":40,"line":616},[38,4324,839],{"class":70},[38,4326,4327,4329,4331,4334,4336,4338,4340,4342,4344,4347,4349,4351,4353],{"class":40,"line":622},[38,4328,4170],{"class":137},[38,4330,2274],{"class":74},[38,4332,4333],{"class":103},"where_clauses",[38,4335,3314],{"class":74},[38,4337,583],{"class":70},[38,4339,4267],{"class":137},[38,4341,2274],{"class":74},[38,4343,4333],{"class":103},[38,4345,4346],{"class":70}," +",[38,4348,169],{"class":137},[38,4350,593],{"class":74},[38,4352,78],{"class":70},[38,4354,4355],{"class":74}," clause\n",[38,4357,4358,4360],{"class":40,"line":656},[38,4359,178],{"class":70},[38,4361,4362],{"class":137}," self\n",[38,4364,4365],{"class":40,"line":677},[38,4366,187],{"class":70},[38,4368,4369],{"class":40,"line":707},[38,4370,64],{"emptyLinePlaceholder":63},[38,4372,4373],{"class":40,"line":730},[38,4374,4375],{"class":44},"-- ORDER BY\n",[38,4377,4378,4380,4382,4384,4387],{"class":40,"line":745},[38,4379,2268],{"class":70},[38,4381,4033],{"class":103},[38,4383,134],{"class":74},[38,4385,4386],{"class":103},"order",[38,4388,4389],{"class":74},"(column, direction)\n",[38,4391,4392],{"class":40,"line":751},[38,4393,4394],{"class":44},"  -- Validate column name (prevent injection — only allow alphanumeric + underscore)\n",[38,4396,4397,4399,4401,4404,4406,4408,4410,4413,4416,4419,4421],{"class":40,"line":771},[38,4398,268],{"class":137},[38,4400,147],{"class":74},[38,4402,4403],{"class":103},"column",[38,4405,134],{"class":74},[38,4407,144],{"class":137},[38,4409,147],{"class":74},[38,4411,4412],{"class":150},"\"^[%a_][%w_]*$\"",[38,4414,4415],{"class":74},"), ",[38,4417,4418],{"class":150},"\"invalid column name: \" ",[38,4420,699],{"class":70},[38,4422,4423],{"class":74}," column)\n",[38,4425,4426,4428,4430,4433,4435,4438,4440,4443,4445,4448,4450,4453],{"class":40,"line":777},[38,4427,4170],{"class":137},[38,4429,2274],{"class":74},[38,4431,4432],{"class":103},"order_by",[38,4434,2129],{"class":70},[38,4436,4437],{"class":74}," column ",[38,4439,699],{"class":70},[38,4441,4442],{"class":150}," \" \" ",[38,4444,699],{"class":70},[38,4446,4447],{"class":74}," (direction ",[38,4449,2398],{"class":70},[38,4451,4452],{"class":150}," \"ASC\"",[38,4454,234],{"class":74},[38,4456,4457,4459],{"class":40,"line":790},[38,4458,178],{"class":70},[38,4460,4362],{"class":137},[38,4462,4463],{"class":40,"line":796},[38,4464,187],{"class":70},[38,4466,4467],{"class":40,"line":817},[38,4468,64],{"emptyLinePlaceholder":63},[38,4470,4471],{"class":40,"line":830},[38,4472,4473],{"class":44},"-- LIMIT \u002F OFFSET\n",[38,4475,4476,4478,4480,4482,4485,4488,4490,4492,4495,4497,4499,4502,4504,4506],{"class":40,"line":836},[38,4477,2268],{"class":70},[38,4479,4033],{"class":103},[38,4481,134],{"class":74},[38,4483,4484],{"class":103},"limit",[38,4486,4487],{"class":74},"(n) ",[38,4489,4267],{"class":137},[38,4491,2274],{"class":74},[38,4493,4494],{"class":103},"limit_n",[38,4496,2129],{"class":70},[38,4498,666],{"class":137},[38,4500,4501],{"class":74},"(n); ",[38,4503,2993],{"class":70},[38,4505,4191],{"class":137},[38,4507,172],{"class":70},[38,4509,4510,4512,4514,4516,4519,4521,4523,4525,4528,4530,4532,4534,4536,4538],{"class":40,"line":842},[38,4511,2268],{"class":70},[38,4513,4033],{"class":103},[38,4515,134],{"class":74},[38,4517,4518],{"class":103},"offset",[38,4520,4487],{"class":74},[38,4522,4267],{"class":137},[38,4524,2274],{"class":74},[38,4526,4527],{"class":103},"offset_n",[38,4529,2129],{"class":70},[38,4531,666],{"class":137},[38,4533,4501],{"class":74},[38,4535,2993],{"class":70},[38,4537,4191],{"class":137},[38,4539,172],{"class":70},[38,4541,4542],{"class":40,"line":854},[38,4543,64],{"emptyLinePlaceholder":63},[38,4545,4546],{"class":40,"line":859},[38,4547,4548],{"class":44},"-- Build the final SQL string + params array\n",[38,4550,4551,4553,4555,4557,4560],{"class":40,"line":864},[38,4552,2268],{"class":70},[38,4554,4033],{"class":103},[38,4556,134],{"class":74},[38,4558,4559],{"class":103},"build",[38,4561,3403],{"class":74},[38,4563,4564,4566,4569,4571,4574,4576,4578,4580,4582,4584,4586,4588,4591,4593,4595,4598,4600,4602,4604],{"class":40,"line":870},[38,4565,314],{"class":70},[38,4567,4568],{"class":74}," sql ",[38,4570,78],{"class":70},[38,4572,4573],{"class":150}," \"SELECT \" ",[38,4575,699],{"class":70},[38,4577,382],{"class":137},[38,4579,147],{"class":74},[38,4581,4267],{"class":137},[38,4583,2274],{"class":74},[38,4585,4175],{"class":103},[38,4587,228],{"class":74},[38,4589,4590],{"class":150},"\", \"",[38,4592,154],{"class":74},[38,4594,699],{"class":70},[38,4596,4597],{"class":150}," \" FROM \" ",[38,4599,699],{"class":70},[38,4601,4191],{"class":137},[38,4603,2274],{"class":74},[38,4605,4606],{"class":103},"table\n",[38,4608,4609,4611,4613,4615,4617,4619,4622,4624],{"class":40,"line":882},[38,4610,897],{"class":70},[38,4612,122],{"class":70},[38,4614,4267],{"class":137},[38,4616,2274],{"class":74},[38,4618,4333],{"class":103},[38,4620,4621],{"class":70}," >",[38,4623,2866],{"class":137},[38,4625,2725],{"class":70},[38,4627,4628,4631,4633,4635,4637,4640,4642,4644,4646,4648,4650,4652,4654,4657],{"class":40,"line":894},[38,4629,4630],{"class":74},"    sql ",[38,4632,78],{"class":70},[38,4634,4568],{"class":74},[38,4636,699],{"class":70},[38,4638,4639],{"class":150}," \" WHERE \" ",[38,4641,699],{"class":70},[38,4643,382],{"class":137},[38,4645,147],{"class":74},[38,4647,4267],{"class":137},[38,4649,2274],{"class":74},[38,4651,4333],{"class":103},[38,4653,228],{"class":74},[38,4655,4656],{"class":150},"\" AND \"",[38,4658,234],{"class":74},[38,4660,4661],{"class":40,"line":931},[38,4662,839],{"class":70},[38,4664,4665,4667,4669,4671,4673],{"class":40,"line":965},[38,4666,897],{"class":70},[38,4668,4191],{"class":137},[38,4670,2274],{"class":74},[38,4672,4432],{"class":103},[38,4674,2725],{"class":70},[38,4676,4677,4679,4681,4683,4685,4688,4690,4692,4694],{"class":40,"line":971},[38,4678,4630],{"class":74},[38,4680,78],{"class":70},[38,4682,4568],{"class":74},[38,4684,699],{"class":70},[38,4686,4687],{"class":150}," \" ORDER BY \" ",[38,4689,699],{"class":70},[38,4691,4191],{"class":137},[38,4693,2274],{"class":74},[38,4695,4696],{"class":103},"order_by\n",[38,4698,4699],{"class":40,"line":1042},[38,4700,839],{"class":70},[38,4702,4703,4705,4707,4709,4711],{"class":40,"line":1058},[38,4704,897],{"class":70},[38,4706,4191],{"class":137},[38,4708,2274],{"class":74},[38,4710,4494],{"class":103},[38,4712,2725],{"class":70},[38,4714,4715,4717,4719,4721,4723,4726,4728,4730,4732],{"class":40,"line":1063},[38,4716,4630],{"class":74},[38,4718,78],{"class":70},[38,4720,4568],{"class":74},[38,4722,699],{"class":70},[38,4724,4725],{"class":150}," \" LIMIT \" ",[38,4727,699],{"class":70},[38,4729,4191],{"class":137},[38,4731,2274],{"class":74},[38,4733,4734],{"class":103},"limit_n\n",[38,4736,4737],{"class":40,"line":1077},[38,4738,839],{"class":70},[38,4740,4741,4743,4745,4747,4749],{"class":40,"line":1082},[38,4742,897],{"class":70},[38,4744,4191],{"class":137},[38,4746,2274],{"class":74},[38,4748,4527],{"class":103},[38,4750,2725],{"class":70},[38,4752,4753,4755,4757,4759,4761,4764,4766,4768,4770],{"class":40,"line":1108},[38,4754,4630],{"class":74},[38,4756,78],{"class":70},[38,4758,4568],{"class":74},[38,4760,699],{"class":70},[38,4762,4763],{"class":150}," \" OFFSET \" ",[38,4765,699],{"class":70},[38,4767,4191],{"class":137},[38,4769,2274],{"class":74},[38,4771,4772],{"class":103},"offset_n\n",[38,4774,4775],{"class":40,"line":1123},[38,4776,839],{"class":70},[38,4778,4779,4781,4784,4786,4788],{"class":40,"line":1148},[38,4780,178],{"class":70},[38,4782,4783],{"class":74}," sql, ",[38,4785,4267],{"class":137},[38,4787,2274],{"class":74},[38,4789,4272],{"class":103},[38,4791,4792],{"class":40,"line":1156},[38,4793,187],{"class":70},[38,4795,4796],{"class":40,"line":1161},[38,4797,64],{"emptyLinePlaceholder":63},[38,4799,4800],{"class":40,"line":1166},[38,4801,4802],{"class":44},"-- __tostring for easy logging\n",[38,4804,4805,4807,4810,4812,4814],{"class":40,"line":1172},[38,4806,4014],{"class":74},[38,4808,4809],{"class":103},"__tostring",[38,4811,2129],{"class":70},[38,4813,100],{"class":70},[38,4815,4816],{"class":74},"(self)\n",[38,4818,4819,4821,4824,4826,4828,4830,4832],{"class":40,"line":1184},[38,4820,314],{"class":70},[38,4822,4823],{"class":74}," sql, params ",[38,4825,78],{"class":70},[38,4827,4191],{"class":137},[38,4829,134],{"class":74},[38,4831,4559],{"class":137},[38,4833,3403],{"class":74},[38,4835,4836,4838,4840,4843,4845,4847],{"class":40,"line":1211},[38,4837,897],{"class":70},[38,4839,122],{"class":70},[38,4841,4842],{"class":74},"params ",[38,4844,2736],{"class":70},[38,4846,2866],{"class":137},[38,4848,2725],{"class":70},[38,4850,4851,4853,4855,4857,4860,4862,4864,4867,4869],{"class":40,"line":1229},[38,4852,1937],{"class":70},[38,4854,4568],{"class":74},[38,4856,699],{"class":70},[38,4858,4859],{"class":150}," \"  -- params: \" ",[38,4861,699],{"class":70},[38,4863,382],{"class":137},[38,4865,4866],{"class":74},"(params, ",[38,4868,4590],{"class":150},[38,4870,234],{"class":74},[38,4872,4873],{"class":40,"line":1241},[38,4874,839],{"class":70},[38,4876,4877,4879],{"class":40,"line":1276},[38,4878,178],{"class":70},[38,4880,4881],{"class":74}," sql\n",[38,4883,4884],{"class":40,"line":1287},[38,4885,187],{"class":70},[38,4887,4888],{"class":40,"line":1298},[38,4889,64],{"emptyLinePlaceholder":63},[38,4891,4892],{"class":40,"line":1325},[38,4893,4894],{"class":44},"-- Usage:\n",[38,4896,4897,4899,4902,4904,4907,4909,4911,4914],{"class":40,"line":1339},[38,4898,71],{"class":70},[38,4900,4901],{"class":74}," q ",[38,4903,78],{"class":70},[38,4905,4906],{"class":74}," QueryBuilder.",[38,4908,4038],{"class":137},[38,4910,147],{"class":74},[38,4912,4913],{"class":150},"\"users\"",[38,4915,234],{"class":74},[38,4917,4918,4921,4923,4925,4928,4930,4933,4935,4938],{"class":40,"line":1350},[38,4919,4920],{"class":74},"  :",[38,4922,4162],{"class":137},[38,4924,147],{"class":74},[38,4926,4927],{"class":150},"\"id\"",[38,4929,228],{"class":74},[38,4931,4932],{"class":150},"\"name\"",[38,4934,228],{"class":74},[38,4936,4937],{"class":150},"\"email\"",[38,4939,234],{"class":74},[38,4941,4942,4944,4946,4948,4951,4953,4956],{"class":40,"line":1377},[38,4943,4920],{"class":74},[38,4945,4218],{"class":137},[38,4947,147],{"class":74},[38,4949,4950],{"class":150},"\"age > ?\"",[38,4952,228],{"class":74},[38,4954,4955],{"class":137},"18",[38,4957,234],{"class":74},[38,4959,4960,4962,4964,4966,4969,4971,4974],{"class":40,"line":1394},[38,4961,4920],{"class":74},[38,4963,4218],{"class":137},[38,4965,147],{"class":74},[38,4967,4968],{"class":150},"\"status = ?\"",[38,4970,228],{"class":74},[38,4972,4973],{"class":150},"\"active\"",[38,4975,234],{"class":74},[38,4977,4978,4980,4982,4984,4987,4989,4992],{"class":40,"line":1402},[38,4979,4920],{"class":74},[38,4981,4386],{"class":137},[38,4983,147],{"class":74},[38,4985,4986],{"class":150},"\"created_at\"",[38,4988,228],{"class":74},[38,4990,4991],{"class":150},"\"DESC\"",[38,4993,234],{"class":74},[38,4995,4996,4998,5000,5002,5005],{"class":40,"line":1414},[38,4997,4920],{"class":74},[38,4999,4484],{"class":137},[38,5001,147],{"class":74},[38,5003,5004],{"class":137},"10",[38,5006,234],{"class":74},[38,5008,5009],{"class":40,"line":1424},[38,5010,64],{"emptyLinePlaceholder":63},[38,5012,5013,5015,5017,5019],{"class":40,"line":1435},[38,5014,3294],{"class":137},[38,5016,147],{"class":74},[38,5018,3051],{"class":137},[38,5020,5021],{"class":74},"(q))\n",[38,5023,5024],{"class":40,"line":1452},[38,5025,5026],{"class":44},"-- SELECT id, name, email FROM users WHERE age > ? AND status = ? ORDER BY created_at DESC LIMIT 10\n",[38,5028,5029],{"class":40,"line":1473},[38,5030,5031],{"class":44},"-- params: 18, active\n",[38,5033,5034],{"class":40,"line":1501},[38,5035,64],{"emptyLinePlaceholder":63},[38,5037,5038],{"class":40,"line":1521},[38,5039,5040],{"class":44},"-- Execute (with a hypothetical db module):\n",[38,5042,5043],{"class":40,"line":1526},[38,5044,5045],{"class":44},"-- local sql, params = q:build()\n",[38,5047,5048],{"class":40,"line":1531},[38,5049,5050],{"class":44},"-- local results = db:execute(sql, unpack(params))\n",[19,5052,5054],{"id":5053},"project-4-plugin-system-sandboxed-module-loading","Project 4: Plugin System (Sandboxed Module Loading)",[24,5056,5058,6063,6067,6072],{"language":26,"filename":5057},"projects\u002Fplugin_system.lua",[29,5059,5061],{"className":31,"code":5060,"language":26,"meta":33,"style":33},"-- Plugin system: load untrusted plugins in a sandbox, provide a controlled API\n-- Plugins can only access whitelisted functions (no io, os.execute, require)\n\nlocal PluginSystem = {}\nPluginSystem.__index = PluginSystem\n\n-- The sandbox API: only what plugins are allowed to use\nlocal function create_plugin_api(host)\n  return {\n    -- Logging (through host's logger, not io.write)\n    log = function(level, msg) host.logger:log(level, msg) end,\n\n    -- Register hooks (callback registration)\n    on_event = function(event, fn)\n      host.hooks[event] = host.hooks[event] or {}\n      table.insert(host.hooks[event], fn)\n    end,\n\n    -- Read-only config (copy, not reference)\n    get_config = function(key) return host.config[key] end,\n\n    -- Safe math\u002Fstring functions (no file\u002Fnetwork access)\n    math = math,  -- safe: pure functions\n    string = string,\n    table = table,\n    pairs = pairs, ipairs = ipairs, type = type,\n    tostring = tostring, tonumber = tonumber,\n    assert = assert, error = error,\n    select = select, setmetatable = setmetatable,\n    -- NO: io, os, require, load, loadfile, dofile, debug, _G\n  }\nend\n\nfunction PluginSystem.new(config)\n  return setmetatable({\n    config = config or {},\n    hooks = {},\n    logger = {log = function(lvl, msg) print(\"[\" .. lvl .. \"] \" .. msg) end},\n    loaded = {},\n  }, PluginSystem)\nend\n\n-- Load a plugin from source code (string) — sandboxed\nfunction PluginSystem:load(name, source)\n  local api = create_plugin_api(self)\n  -- Compile with the sandbox environment (4th arg to load)\n  local fn, err = load(source, name, \"t\", api)  -- \"t\" = text only (no bytecode injection)\n  if not fn then return false, \"compile error: \" .. err end\n\n  local ok, result = pcall(fn)  -- run the plugin's main chunk\n  if not ok then return false, \"runtime error: \" .. result end\n\n  self.loaded[name] = result or true\n  return true\nend\n\n-- Trigger an event: call all registered hooks for that event\nfunction PluginSystem:trigger(event, ...)\n  local hooks = self.hooks[event]\n  if not hooks then return end\n  for _, fn in ipairs(hooks) do\n    local ok, err = pcall(fn, ...)  -- protect: one bad plugin doesn't crash others\n    if not ok then\n      self.logger:log(\"error\", \"plugin hook '\" .. event .. \"' failed: \" .. tostring(err))\n    end\n  end\nend\n\n-- Usage:\nlocal ps = PluginSystem.new({server_name = \"MyApp\"})\n\n-- Load a plugin (from source string — in production, read from plugins\u002F dir)\nps:load(\"greeter\", [[\n  -- This code runs in a sandbox: cannot access io, os, _G, require\n  log(\"info\", \"greeter plugin loaded\")\n\n  on_event(\"user_join\", function(username)\n    log(\"info\", \"welcome \" .. username .. \" to \" .. get_config(\"server_name\"))\n  end)\n\n  on_event(\"user_leave\", function(username)\n    log(\"info\", \"goodbye \" .. username)\n  end)\n]])\n\n-- Trigger events (plugins respond)\nps:trigger(\"user_join\", \"Alice\")  -- [info] welcome Alice to MyApp\nps:trigger(\"user_leave\", \"Alice\")  -- [info] goodbye Alice\n\n-- Security test: a malicious plugin\nlocal ok, err = ps:load(\"malicious\", [[\n  os.execute(\"rm -rf \u002F\")  -- attempt to escape sandbox\n]])\nprint(ok, err)  -- false  runtime error: attempt to index nil value (global 'os')\n-- The sandbox holds: 'os' is not in the env table → nil → indexing nil → error\n",[35,5062,5063,5068,5073,5077,5088,5100,5104,5109,5121,5127,5132,5159,5163,5168,5180,5204,5217,5224,5228,5233,5259,5263,5268,5281,5291,5300,5325,5343,5362,5380,5385,5390,5394,5398,5412,5420,5434,5443,5487,5496,5501,5505,5509,5514,5528,5545,5550,5574,5601,5605,5623,5649,5653,5673,5679,5683,5687,5692,5706,5724,5738,5754,5775,5785,5825,5829,5833,5837,5841,5845,5870,5874,5879,5897,5902,5907,5911,5916,5921,5926,5930,5935,5940,5944,5950,5954,5959,5982,6004,6008,6013,6037,6042,6048,6058],{"__ignoreMap":33},[38,5064,5065],{"class":40,"line":41},[38,5066,5067],{"class":44},"-- Plugin system: load untrusted plugins in a sandbox, provide a controlled API\n",[38,5069,5070],{"class":40,"line":48},[38,5071,5072],{"class":44},"-- Plugins can only access whitelisted functions (no io, os.execute, require)\n",[38,5074,5075],{"class":40,"line":54},[38,5076,64],{"emptyLinePlaceholder":63},[38,5078,5079,5081,5084,5086],{"class":40,"line":60},[38,5080,71],{"class":70},[38,5082,5083],{"class":74}," PluginSystem ",[38,5085,78],{"class":70},[38,5087,81],{"class":74},[38,5089,5090,5093,5095,5097],{"class":40,"line":67},[38,5091,5092],{"class":74},"PluginSystem.",[38,5094,4017],{"class":103},[38,5096,2129],{"class":70},[38,5098,5099],{"class":74}," PluginSystem\n",[38,5101,5102],{"class":40,"line":84},[38,5103,64],{"emptyLinePlaceholder":63},[38,5105,5106],{"class":40,"line":89},[38,5107,5108],{"class":44},"-- The sandbox API: only what plugins are allowed to use\n",[38,5110,5111,5113,5115,5118],{"class":40,"line":95},[38,5112,71],{"class":70},[38,5114,100],{"class":70},[38,5116,5117],{"class":103}," create_plugin_api",[38,5119,5120],{"class":74},"(host)\n",[38,5122,5123,5125],{"class":40,"line":110},[38,5124,178],{"class":70},[38,5126,1832],{"class":74},[38,5128,5129],{"class":40,"line":175},[38,5130,5131],{"class":44},"    -- Logging (through host's logger, not io.write)\n",[38,5133,5134,5137,5139,5141,5144,5147,5149,5152,5155,5157],{"class":40,"line":184},[38,5135,5136],{"class":103},"    log",[38,5138,2129],{"class":70},[38,5140,100],{"class":70},[38,5142,5143],{"class":74},"(level, msg) host.",[38,5145,5146],{"class":103},"logger",[38,5148,134],{"class":74},[38,5150,5151],{"class":137},"log",[38,5153,5154],{"class":74},"(level, msg) ",[38,5156,3009],{"class":70},[38,5158,1954],{"class":74},[38,5160,5161],{"class":40,"line":190},[38,5162,64],{"emptyLinePlaceholder":63},[38,5164,5165],{"class":40,"line":195},[38,5166,5167],{"class":44},"    -- Register hooks (callback registration)\n",[38,5169,5170,5173,5175,5177],{"class":40,"line":201},[38,5171,5172],{"class":103},"    on_event",[38,5174,2129],{"class":70},[38,5176,100],{"class":70},[38,5178,5179],{"class":74},"(event, fn)\n",[38,5181,5182,5185,5188,5191,5193,5196,5198,5200,5202],{"class":40,"line":217},[38,5183,5184],{"class":74},"      host.",[38,5186,5187],{"class":103},"hooks",[38,5189,5190],{"class":74},"[event] ",[38,5192,78],{"class":70},[38,5194,5195],{"class":74}," host.",[38,5197,5187],{"class":103},[38,5199,5190],{"class":74},[38,5201,2398],{"class":70},[38,5203,81],{"class":74},[38,5205,5206,5209,5212,5214],{"class":40,"line":237},[38,5207,5208],{"class":137},"      table.insert",[38,5210,5211],{"class":74},"(host.",[38,5213,5187],{"class":103},[38,5215,5216],{"class":74},"[event], fn)\n",[38,5218,5219,5222],{"class":40,"line":242},[38,5220,5221],{"class":70},"    end",[38,5223,1954],{"class":74},[38,5225,5226],{"class":40,"line":247},[38,5227,64],{"emptyLinePlaceholder":63},[38,5229,5230],{"class":40,"line":253},[38,5231,5232],{"class":44},"    -- Read-only config (copy, not reference)\n",[38,5234,5235,5238,5240,5242,5245,5247,5249,5252,5255,5257],{"class":40,"line":265},[38,5236,5237],{"class":103},"    get_config",[38,5239,2129],{"class":70},[38,5241,100],{"class":70},[38,5243,5244],{"class":74},"(key) ",[38,5246,2993],{"class":70},[38,5248,5195],{"class":74},[38,5250,5251],{"class":103},"config",[38,5253,5254],{"class":74},"[key] ",[38,5256,3009],{"class":70},[38,5258,1954],{"class":74},[38,5260,5261],{"class":40,"line":296},[38,5262,64],{"emptyLinePlaceholder":63},[38,5264,5265],{"class":40,"line":311},[38,5266,5267],{"class":44},"    -- Safe math\u002Fstring functions (no file\u002Fnetwork access)\n",[38,5269,5270,5273,5275,5278],{"class":40,"line":324},[38,5271,5272],{"class":74},"    math ",[38,5274,78],{"class":70},[38,5276,5277],{"class":74}," math,  ",[38,5279,5280],{"class":44},"-- safe: pure functions\n",[38,5282,5283,5286,5288],{"class":40,"line":340},[38,5284,5285],{"class":74},"    string ",[38,5287,78],{"class":70},[38,5289,5290],{"class":74}," string,\n",[38,5292,5293,5295,5297],{"class":40,"line":360},[38,5294,4056],{"class":74},[38,5296,78],{"class":70},[38,5298,5299],{"class":74}," table,\n",[38,5301,5302,5305,5307,5309,5312,5314,5316,5319,5321,5323],{"class":40,"line":376},[38,5303,5304],{"class":74},"    pairs ",[38,5306,78],{"class":70},[38,5308,2685],{"class":137},[38,5310,5311],{"class":74},", ipairs ",[38,5313,78],{"class":70},[38,5315,3024],{"class":137},[38,5317,5318],{"class":74},", type ",[38,5320,78],{"class":70},[38,5322,2427],{"class":137},[38,5324,1954],{"class":74},[38,5326,5327,5330,5332,5334,5337,5339,5341],{"class":40,"line":395},[38,5328,5329],{"class":74},"    tostring ",[38,5331,78],{"class":70},[38,5333,2530],{"class":137},[38,5335,5336],{"class":74},", tonumber ",[38,5338,78],{"class":70},[38,5340,666],{"class":137},[38,5342,1954],{"class":74},[38,5344,5345,5348,5350,5353,5356,5358,5360],{"class":40,"line":416},[38,5346,5347],{"class":74},"    assert ",[38,5349,78],{"class":70},[38,5351,5352],{"class":137}," assert",[38,5354,5355],{"class":74},", error ",[38,5357,78],{"class":70},[38,5359,691],{"class":137},[38,5361,1954],{"class":74},[38,5363,5364,5366,5368,5371,5374,5376,5378],{"class":40,"line":430},[38,5365,4066],{"class":74},[38,5367,78],{"class":70},[38,5369,5370],{"class":137}," select",[38,5372,5373],{"class":74},", setmetatable ",[38,5375,78],{"class":70},[38,5377,4048],{"class":137},[38,5379,1954],{"class":74},[38,5381,5382],{"class":40,"line":449},[38,5383,5384],{"class":44},"    -- NO: io, os, require, load, loadfile, dofile, debug, _G\n",[38,5386,5387],{"class":40,"line":566},[38,5388,5389],{"class":74},"  }\n",[38,5391,5392],{"class":40,"line":577},[38,5393,187],{"class":70},[38,5395,5396],{"class":40,"line":601},[38,5397,64],{"emptyLinePlaceholder":63},[38,5399,5400,5402,5405,5407,5409],{"class":40,"line":616},[38,5401,2268],{"class":70},[38,5403,5404],{"class":103}," PluginSystem",[38,5406,2274],{"class":74},[38,5408,4038],{"class":103},[38,5410,5411],{"class":74},"(config)\n",[38,5413,5414,5416,5418],{"class":40,"line":622},[38,5415,178],{"class":70},[38,5417,4048],{"class":137},[38,5419,4051],{"class":74},[38,5421,5422,5425,5427,5430,5432],{"class":40,"line":656},[38,5423,5424],{"class":74},"    config ",[38,5426,78],{"class":70},[38,5428,5429],{"class":74}," config ",[38,5431,2398],{"class":70},[38,5433,4087],{"class":74},[38,5435,5436,5439,5441],{"class":40,"line":677},[38,5437,5438],{"class":74},"    hooks ",[38,5440,78],{"class":70},[38,5442,4087],{"class":74},[38,5444,5445,5448,5450,5452,5454,5456,5458,5461,5463,5465,5468,5470,5473,5475,5478,5480,5483,5485],{"class":40,"line":707},[38,5446,5447],{"class":74},"    logger ",[38,5449,78],{"class":70},[38,5451,4071],{"class":74},[38,5453,5151],{"class":103},[38,5455,2129],{"class":70},[38,5457,100],{"class":70},[38,5459,5460],{"class":74},"(lvl, msg) ",[38,5462,3294],{"class":137},[38,5464,147],{"class":74},[38,5466,5467],{"class":150},"\"[\" ",[38,5469,699],{"class":70},[38,5471,5472],{"class":74}," lvl ",[38,5474,699],{"class":70},[38,5476,5477],{"class":150}," \"] \" ",[38,5479,699],{"class":70},[38,5481,5482],{"class":74}," msg) ",[38,5484,3009],{"class":70},[38,5486,4077],{"class":74},[38,5488,5489,5492,5494],{"class":40,"line":730},[38,5490,5491],{"class":74},"    loaded ",[38,5493,78],{"class":70},[38,5495,4087],{"class":74},[38,5497,5498],{"class":40,"line":745},[38,5499,5500],{"class":74},"  }, PluginSystem)\n",[38,5502,5503],{"class":40,"line":751},[38,5504,187],{"class":70},[38,5506,5507],{"class":40,"line":771},[38,5508,64],{"emptyLinePlaceholder":63},[38,5510,5511],{"class":40,"line":777},[38,5512,5513],{"class":44},"-- Load a plugin from source code (string) — sandboxed\n",[38,5515,5516,5518,5520,5522,5525],{"class":40,"line":790},[38,5517,2268],{"class":70},[38,5519,5404],{"class":103},[38,5521,134],{"class":74},[38,5523,5524],{"class":103},"load",[38,5526,5527],{"class":74},"(name, source)\n",[38,5529,5530,5532,5535,5537,5539,5541,5543],{"class":40,"line":796},[38,5531,314],{"class":70},[38,5533,5534],{"class":74}," api ",[38,5536,78],{"class":70},[38,5538,5117],{"class":137},[38,5540,147],{"class":74},[38,5542,4267],{"class":137},[38,5544,234],{"class":74},[38,5546,5547],{"class":40,"line":817},[38,5548,5549],{"class":44},"  -- Compile with the sandbox environment (4th arg to load)\n",[38,5551,5552,5554,5557,5559,5562,5565,5568,5571],{"class":40,"line":830},[38,5553,314],{"class":70},[38,5555,5556],{"class":74}," fn, err ",[38,5558,78],{"class":70},[38,5560,5561],{"class":137}," load",[38,5563,5564],{"class":74},"(source, name, ",[38,5566,5567],{"class":150},"\"t\"",[38,5569,5570],{"class":74},", api)  ",[38,5572,5573],{"class":44},"-- \"t\" = text only (no bytecode injection)\n",[38,5575,5576,5578,5580,5583,5585,5587,5589,5591,5594,5596,5599],{"class":40,"line":836},[38,5577,897],{"class":70},[38,5579,683],{"class":70},[38,5581,5582],{"class":74}," fn ",[38,5584,688],{"class":70},[38,5586,1261],{"class":70},[38,5588,2014],{"class":137},[38,5590,228],{"class":74},[38,5592,5593],{"class":150},"\"compile error: \" ",[38,5595,699],{"class":70},[38,5597,5598],{"class":74}," err ",[38,5600,187],{"class":70},[38,5602,5603],{"class":40,"line":842},[38,5604,64],{"emptyLinePlaceholder":63},[38,5606,5607,5609,5612,5614,5617,5620],{"class":40,"line":854},[38,5608,314],{"class":70},[38,5610,5611],{"class":74}," ok, result ",[38,5613,78],{"class":70},[38,5615,5616],{"class":137}," pcall",[38,5618,5619],{"class":74},"(fn)  ",[38,5621,5622],{"class":44},"-- run the plugin's main chunk\n",[38,5624,5625,5627,5629,5632,5634,5636,5638,5640,5643,5645,5647],{"class":40,"line":859},[38,5626,897],{"class":70},[38,5628,683],{"class":70},[38,5630,5631],{"class":74}," ok ",[38,5633,688],{"class":70},[38,5635,1261],{"class":70},[38,5637,2014],{"class":137},[38,5639,228],{"class":74},[38,5641,5642],{"class":150},"\"runtime error: \" ",[38,5644,699],{"class":70},[38,5646,3428],{"class":74},[38,5648,187],{"class":70},[38,5650,5651],{"class":40,"line":864},[38,5652,64],{"emptyLinePlaceholder":63},[38,5654,5655,5657,5659,5662,5665,5667,5669,5671],{"class":40,"line":870},[38,5656,4170],{"class":137},[38,5658,2274],{"class":74},[38,5660,5661],{"class":103},"loaded",[38,5663,5664],{"class":74},"[name] ",[38,5666,78],{"class":70},[38,5668,3428],{"class":74},[38,5670,2398],{"class":70},[38,5672,2657],{"class":137},[38,5674,5675,5677],{"class":40,"line":882},[38,5676,178],{"class":70},[38,5678,2657],{"class":137},[38,5680,5681],{"class":40,"line":894},[38,5682,187],{"class":70},[38,5684,5685],{"class":40,"line":931},[38,5686,64],{"emptyLinePlaceholder":63},[38,5688,5689],{"class":40,"line":965},[38,5690,5691],{"class":44},"-- Trigger an event: call all registered hooks for that event\n",[38,5693,5694,5696,5698,5700,5703],{"class":40,"line":971},[38,5695,2268],{"class":70},[38,5697,5404],{"class":103},[38,5699,134],{"class":74},[38,5701,5702],{"class":103},"trigger",[38,5704,5705],{"class":74},"(event, ...)\n",[38,5707,5708,5710,5713,5715,5717,5719,5721],{"class":40,"line":1042},[38,5709,314],{"class":70},[38,5711,5712],{"class":74}," hooks ",[38,5714,78],{"class":70},[38,5716,4191],{"class":137},[38,5718,2274],{"class":74},[38,5720,5187],{"class":103},[38,5722,5723],{"class":74},"[event]\n",[38,5725,5726,5728,5730,5732,5734,5736],{"class":40,"line":1058},[38,5727,897],{"class":70},[38,5729,683],{"class":70},[38,5731,5712],{"class":74},[38,5733,688],{"class":70},[38,5735,1261],{"class":70},[38,5737,172],{"class":70},[38,5739,5740,5742,5745,5747,5749,5752],{"class":40,"line":1063},[38,5741,4277],{"class":70},[38,5743,5744],{"class":74}," _, fn ",[38,5746,2682],{"class":70},[38,5748,3024],{"class":137},[38,5750,5751],{"class":74},"(hooks) ",[38,5753,337],{"class":70},[38,5755,5756,5758,5761,5763,5765,5768,5770,5772],{"class":40,"line":1077},[38,5757,343],{"class":70},[38,5759,5760],{"class":74}," ok, err ",[38,5762,78],{"class":70},[38,5764,5616],{"class":137},[38,5766,5767],{"class":74},"(fn, ",[38,5769,4182],{"class":137},[38,5771,2610],{"class":74},[38,5773,5774],{"class":44},"-- protect: one bad plugin doesn't crash others\n",[38,5776,5777,5779,5781,5783],{"class":40,"line":1082},[38,5778,363],{"class":70},[38,5780,683],{"class":70},[38,5782,5631],{"class":74},[38,5784,373],{"class":70},[38,5786,5787,5790,5792,5794,5796,5798,5800,5803,5805,5808,5810,5813,5815,5818,5820,5822],{"class":40,"line":1108},[38,5788,5789],{"class":137},"      self",[38,5791,2274],{"class":74},[38,5793,5146],{"class":103},[38,5795,134],{"class":74},[38,5797,5151],{"class":137},[38,5799,147],{"class":74},[38,5801,5802],{"class":150},"\"error\"",[38,5804,228],{"class":74},[38,5806,5807],{"class":150},"\"plugin hook '\" ",[38,5809,699],{"class":70},[38,5811,5812],{"class":74}," event ",[38,5814,699],{"class":70},[38,5816,5817],{"class":150}," \"' failed: \" ",[38,5819,699],{"class":70},[38,5821,2530],{"class":137},[38,5823,5824],{"class":74},"(err))\n",[38,5826,5827],{"class":40,"line":1123},[38,5828,833],{"class":70},[38,5830,5831],{"class":40,"line":1148},[38,5832,839],{"class":70},[38,5834,5835],{"class":40,"line":1156},[38,5836,187],{"class":70},[38,5838,5839],{"class":40,"line":1161},[38,5840,64],{"emptyLinePlaceholder":63},[38,5842,5843],{"class":40,"line":1166},[38,5844,4894],{"class":44},[38,5846,5847,5849,5852,5854,5857,5859,5862,5864,5867],{"class":40,"line":1172},[38,5848,71],{"class":70},[38,5850,5851],{"class":74}," ps ",[38,5853,78],{"class":70},[38,5855,5856],{"class":74}," PluginSystem.",[38,5858,4038],{"class":137},[38,5860,5861],{"class":74},"({server_name ",[38,5863,78],{"class":70},[38,5865,5866],{"class":150}," \"MyApp\"",[38,5868,5869],{"class":74},"})\n",[38,5871,5872],{"class":40,"line":1184},[38,5873,64],{"emptyLinePlaceholder":63},[38,5875,5876],{"class":40,"line":1211},[38,5877,5878],{"class":44},"-- Load a plugin (from source string — in production, read from plugins\u002F dir)\n",[38,5880,5881,5884,5886,5888,5890,5893,5895],{"class":40,"line":1229},[38,5882,5883],{"class":103},"ps",[38,5885,134],{"class":74},[38,5887,5524],{"class":137},[38,5889,147],{"class":74},[38,5891,5892],{"class":150},"\"greeter\"",[38,5894,228],{"class":74},[38,5896,3274],{"class":150},[38,5898,5899],{"class":40,"line":1241},[38,5900,5901],{"class":150},"  -- This code runs in a sandbox: cannot access io, os, _G, require\n",[38,5903,5904],{"class":40,"line":1276},[38,5905,5906],{"class":150},"  log(\"info\", \"greeter plugin loaded\")\n",[38,5908,5909],{"class":40,"line":1287},[38,5910,64],{"emptyLinePlaceholder":63},[38,5912,5913],{"class":40,"line":1298},[38,5914,5915],{"class":150},"  on_event(\"user_join\", function(username)\n",[38,5917,5918],{"class":40,"line":1325},[38,5919,5920],{"class":150},"    log(\"info\", \"welcome \" .. username .. \" to \" .. get_config(\"server_name\"))\n",[38,5922,5923],{"class":40,"line":1339},[38,5924,5925],{"class":150},"  end)\n",[38,5927,5928],{"class":40,"line":1350},[38,5929,64],{"emptyLinePlaceholder":63},[38,5931,5932],{"class":40,"line":1377},[38,5933,5934],{"class":150},"  on_event(\"user_leave\", function(username)\n",[38,5936,5937],{"class":40,"line":1394},[38,5938,5939],{"class":150},"    log(\"info\", \"goodbye \" .. username)\n",[38,5941,5942],{"class":40,"line":1402},[38,5943,5925],{"class":150},[38,5945,5946,5948],{"class":40,"line":1414},[38,5947,3286],{"class":150},[38,5949,234],{"class":74},[38,5951,5952],{"class":40,"line":1424},[38,5953,64],{"emptyLinePlaceholder":63},[38,5955,5956],{"class":40,"line":1435},[38,5957,5958],{"class":44},"-- Trigger events (plugins respond)\n",[38,5960,5961,5963,5965,5967,5969,5972,5974,5977,5979],{"class":40,"line":1452},[38,5962,5883],{"class":103},[38,5964,134],{"class":74},[38,5966,5702],{"class":137},[38,5968,147],{"class":74},[38,5970,5971],{"class":150},"\"user_join\"",[38,5973,228],{"class":74},[38,5975,5976],{"class":150},"\"Alice\"",[38,5978,2610],{"class":74},[38,5980,5981],{"class":44},"-- [info] welcome Alice to MyApp\n",[38,5983,5984,5986,5988,5990,5992,5995,5997,5999,6001],{"class":40,"line":1473},[38,5985,5883],{"class":103},[38,5987,134],{"class":74},[38,5989,5702],{"class":137},[38,5991,147],{"class":74},[38,5993,5994],{"class":150},"\"user_leave\"",[38,5996,228],{"class":74},[38,5998,5976],{"class":150},[38,6000,2610],{"class":74},[38,6002,6003],{"class":44},"-- [info] goodbye Alice\n",[38,6005,6006],{"class":40,"line":1501},[38,6007,64],{"emptyLinePlaceholder":63},[38,6009,6010],{"class":40,"line":1521},[38,6011,6012],{"class":44},"-- Security test: a malicious plugin\n",[38,6014,6015,6017,6019,6021,6024,6026,6028,6030,6033,6035],{"class":40,"line":1526},[38,6016,71],{"class":70},[38,6018,5760],{"class":74},[38,6020,78],{"class":70},[38,6022,6023],{"class":103}," ps",[38,6025,134],{"class":74},[38,6027,5524],{"class":137},[38,6029,147],{"class":74},[38,6031,6032],{"class":150},"\"malicious\"",[38,6034,228],{"class":74},[38,6036,3274],{"class":150},[38,6038,6039],{"class":40,"line":1531},[38,6040,6041],{"class":150},"  os.execute(\"rm -rf \u002F\")  -- attempt to escape sandbox\n",[38,6043,6044,6046],{"class":40,"line":1536},[38,6045,3286],{"class":150},[38,6047,234],{"class":74},[38,6049,6050,6052,6055],{"class":40,"line":1542},[38,6051,3294],{"class":137},[38,6053,6054],{"class":74},"(ok, err)  ",[38,6056,6057],{"class":44},"-- false  runtime error: attempt to index nil value (global 'os')\n",[38,6059,6060],{"class":40,"line":1554},[38,6061,6062],{"class":44},"-- The sandbox holds: 'os' is not in the env table → nil → indexing nil → error\n",[19,6064,6066],{"id":6065},"exercise-set","Exercise Set",[6068,6069,6071],"h3",{"id":6070},"exercise-1-implement-a-binary-search-on-a-sorted-table","Exercise 1: Implement a binary search on a sorted table",[24,6073,6074],{"language":26},[29,6075,6077],{"className":31,"code":6076,"language":26,"meta":33,"style":33},"-- Write a function that performs binary search on a sorted array\n-- Return the index of the target, or nil if not found\n-- Handle: empty array, single element, target not found, duplicates (return any match)\n\nlocal function binary_search(arr, target)\n  -- Your code here\n  local lo, hi = 1, #arr\n  while lo \u003C= hi do\n    local mid = math.floor((lo + hi) \u002F 2)\n    if arr[mid] == target then return mid\n    elseif arr[mid] \u003C target then lo = mid + 1\n    else hi = mid - 1 end\n  end\n  return nil\nend\n\nassert(binary_search({1, 3, 5, 7, 9}, 5) == 3)\nassert(binary_search({1, 3, 5, 7, 9}, 4) == nil)\nassert(binary_search({}, 1) == nil)\nassert(binary_search({42}, 42) == 1)\nassert(binary_search({42}, 41) == nil)\n",[35,6078,6079,6084,6089,6094,6098,6110,6115,6133,6147,6174,6193,6215,6231,6235,6242,6246,6250,6298,6339,6360,6385],{"__ignoreMap":33},[38,6080,6081],{"class":40,"line":41},[38,6082,6083],{"class":44},"-- Write a function that performs binary search on a sorted array\n",[38,6085,6086],{"class":40,"line":48},[38,6087,6088],{"class":44},"-- Return the index of the target, or nil if not found\n",[38,6090,6091],{"class":40,"line":54},[38,6092,6093],{"class":44},"-- Handle: empty array, single element, target not found, duplicates (return any match)\n",[38,6095,6096],{"class":40,"line":60},[38,6097,64],{"emptyLinePlaceholder":63},[38,6099,6100,6102,6104,6107],{"class":40,"line":67},[38,6101,71],{"class":70},[38,6103,100],{"class":70},[38,6105,6106],{"class":103}," binary_search",[38,6108,6109],{"class":74},"(arr, target)\n",[38,6111,6112],{"class":40,"line":84},[38,6113,6114],{"class":44},"  -- Your code here\n",[38,6116,6117,6119,6122,6124,6126,6128,6130],{"class":40,"line":89},[38,6118,314],{"class":70},[38,6120,6121],{"class":74}," lo, hi ",[38,6123,78],{"class":70},[38,6125,169],{"class":137},[38,6127,228],{"class":74},[38,6129,583],{"class":70},[38,6131,6132],{"class":74},"arr\n",[38,6134,6135,6137,6140,6142,6145],{"class":40,"line":95},[38,6136,113],{"class":70},[38,6138,6139],{"class":74}," lo ",[38,6141,119],{"class":70},[38,6143,6144],{"class":74}," hi ",[38,6146,337],{"class":70},[38,6148,6149,6151,6154,6156,6158,6161,6163,6166,6169,6172],{"class":40,"line":110},[38,6150,343],{"class":70},[38,6152,6153],{"class":74}," mid ",[38,6155,78],{"class":70},[38,6157,2517],{"class":137},[38,6159,6160],{"class":74},"((lo ",[38,6162,166],{"class":70},[38,6164,6165],{"class":74}," hi) ",[38,6167,6168],{"class":70},"\u002F",[38,6170,6171],{"class":137}," 2",[38,6173,234],{"class":74},[38,6175,6176,6178,6181,6183,6186,6188,6190],{"class":40,"line":175},[38,6177,363],{"class":70},[38,6179,6180],{"class":74}," arr[mid] ",[38,6182,283],{"class":70},[38,6184,6185],{"class":74}," target ",[38,6187,688],{"class":70},[38,6189,1261],{"class":70},[38,6191,6192],{"class":74}," mid\n",[38,6194,6195,6197,6199,6201,6203,6205,6207,6209,6211,6213],{"class":40,"line":184},[38,6196,398],{"class":70},[38,6198,6180],{"class":74},[38,6200,3001],{"class":70},[38,6202,6185],{"class":74},[38,6204,688],{"class":70},[38,6206,6139],{"class":74},[38,6208,78],{"class":70},[38,6210,6153],{"class":74},[38,6212,166],{"class":70},[38,6214,308],{"class":137},[38,6216,6217,6219,6221,6223,6225,6227,6229],{"class":40,"line":190},[38,6218,1504],{"class":70},[38,6220,6144],{"class":74},[38,6222,78],{"class":70},[38,6224,6153],{"class":74},[38,6226,1101],{"class":70},[38,6228,169],{"class":137},[38,6230,172],{"class":70},[38,6232,6233],{"class":40,"line":195},[38,6234,839],{"class":70},[38,6236,6237,6239],{"class":40,"line":201},[38,6238,178],{"class":70},[38,6240,6241],{"class":137}," nil\n",[38,6243,6244],{"class":40,"line":217},[38,6245,187],{"class":70},[38,6247,6248],{"class":40,"line":237},[38,6249,64],{"emptyLinePlaceholder":63},[38,6251,6252,6255,6257,6260,6263,6265,6267,6270,6272,6275,6277,6280,6282,6285,6288,6290,6292,6294,6296],{"class":40,"line":242},[38,6253,6254],{"class":137},"assert",[38,6256,147],{"class":74},[38,6258,6259],{"class":137},"binary_search",[38,6261,6262],{"class":74},"({",[38,6264,2298],{"class":137},[38,6266,228],{"class":74},[38,6268,6269],{"class":137},"3",[38,6271,228],{"class":74},[38,6273,6274],{"class":137},"5",[38,6276,228],{"class":74},[38,6278,6279],{"class":137},"7",[38,6281,228],{"class":74},[38,6283,6284],{"class":137},"9",[38,6286,6287],{"class":74},"}, ",[38,6289,6274],{"class":137},[38,6291,154],{"class":74},[38,6293,283],{"class":70},[38,6295,1917],{"class":137},[38,6297,234],{"class":74},[38,6299,6300,6302,6304,6306,6308,6310,6312,6314,6316,6318,6320,6322,6324,6326,6328,6331,6333,6335,6337],{"class":40,"line":247},[38,6301,6254],{"class":137},[38,6303,147],{"class":74},[38,6305,6259],{"class":137},[38,6307,6262],{"class":74},[38,6309,2298],{"class":137},[38,6311,228],{"class":74},[38,6313,6269],{"class":137},[38,6315,228],{"class":74},[38,6317,6274],{"class":137},[38,6319,228],{"class":74},[38,6321,6279],{"class":137},[38,6323,228],{"class":74},[38,6325,6284],{"class":137},[38,6327,6287],{"class":74},[38,6329,6330],{"class":137},"4",[38,6332,154],{"class":74},[38,6334,283],{"class":70},[38,6336,2088],{"class":137},[38,6338,234],{"class":74},[38,6340,6341,6343,6345,6347,6350,6352,6354,6356,6358],{"class":40,"line":253},[38,6342,6254],{"class":137},[38,6344,147],{"class":74},[38,6346,6259],{"class":137},[38,6348,6349],{"class":74},"({}, ",[38,6351,2298],{"class":137},[38,6353,154],{"class":74},[38,6355,283],{"class":70},[38,6357,2088],{"class":137},[38,6359,234],{"class":74},[38,6361,6362,6364,6366,6368,6370,6373,6375,6377,6379,6381,6383],{"class":40,"line":265},[38,6363,6254],{"class":137},[38,6365,147],{"class":74},[38,6367,6259],{"class":137},[38,6369,6262],{"class":74},[38,6371,6372],{"class":137},"42",[38,6374,6287],{"class":74},[38,6376,6372],{"class":137},[38,6378,154],{"class":74},[38,6380,283],{"class":70},[38,6382,169],{"class":137},[38,6384,234],{"class":74},[38,6386,6387,6389,6391,6393,6395,6397,6399,6402,6404,6406,6408],{"class":40,"line":296},[38,6388,6254],{"class":137},[38,6390,147],{"class":74},[38,6392,6259],{"class":137},[38,6394,6262],{"class":74},[38,6396,6372],{"class":137},[38,6398,6287],{"class":74},[38,6400,6401],{"class":137},"41",[38,6403,154],{"class":74},[38,6405,283],{"class":70},[38,6407,2088],{"class":137},[38,6409,234],{"class":74},[6068,6411,6413],{"id":6412},"exercise-2-implement-an-lru-cache","Exercise 2: Implement an LRU cache",[24,6415,6416],{"language":26},[29,6417,6419],{"className":31,"code":6418,"language":26,"meta":33,"style":33},"-- LRU (Least Recently Used) cache: fixed capacity, evicts oldest on insert\n-- O(1) get and put using a hash table + doubly-linked list\n-- Hint: use two tables: a hash map (key → node) and manual prev\u002Fnext links\n\nlocal LRUCache = {}\nLRUCache.__index = LRUCache\n\nfunction LRUCache.new(capacity)\n  return setmetatable({\n    capacity = capacity,\n    cache = {},  -- key → {value = v, prev = key, next = key}\n    head = nil,  -- most recently used\n    tail = nil,  -- least recently used\n  }, LRUCache)\nend\n\nfunction LRUCache:get(key)\n  local node = self.cache[key]\n  if not node then return nil end\n  self:move_to_front(key, node)\n  return node.value\nend\n\nfunction LRUCache:put(key, value)\n  if self.cache[key] then\n    self.cache[key].value = value\n    self:move_to_front(key, self.cache[key])\n    return\n  end\n  if #self.cache >= self.capacity then\n    self:evict_tail()\n  end\n  self.cache[key] = {value = value}\n  self:insert_front(key, self.cache[key])\nend\n\n-- Implement: move_to_front, insert_front, evict_tail (linked list manipulation)\n-- (left as exercise for the reader — test with the assertions below)\n\nlocal cache = LRUCache.new(3)\ncache:put(\"a\", 1)\ncache:put(\"b\", 2)\ncache:put(\"c\", 3)\nassert(cache:get(\"a\") == 1)  -- a is now most recently used\ncache:put(\"d\", 4)  -- evicts \"b\" (least recently used after a was accessed)\nassert(cache:get(\"b\") == nil)\nassert(cache:get(\"a\") == 1)\nassert(cache:get(\"c\") == 3)\nassert(cache:get(\"d\") == 4)\n",[35,6420,6421,6426,6431,6436,6440,6451,6463,6467,6481,6489,6499,6511,6526,6540,6545,6549,6553,6567,6586,6602,6614,6624,6628,6632,6646,6660,6678,6698,6703,6707,6731,6742,6746,6766,6785,6789,6793,6798,6803,6807,6827,6846,6865,6884,6911,6933,6957,6981,7005],{"__ignoreMap":33},[38,6422,6423],{"class":40,"line":41},[38,6424,6425],{"class":44},"-- LRU (Least Recently Used) cache: fixed capacity, evicts oldest on insert\n",[38,6427,6428],{"class":40,"line":48},[38,6429,6430],{"class":44},"-- O(1) get and put using a hash table + doubly-linked list\n",[38,6432,6433],{"class":40,"line":54},[38,6434,6435],{"class":44},"-- Hint: use two tables: a hash map (key → node) and manual prev\u002Fnext links\n",[38,6437,6438],{"class":40,"line":60},[38,6439,64],{"emptyLinePlaceholder":63},[38,6441,6442,6444,6447,6449],{"class":40,"line":67},[38,6443,71],{"class":70},[38,6445,6446],{"class":74}," LRUCache ",[38,6448,78],{"class":70},[38,6450,81],{"class":74},[38,6452,6453,6456,6458,6460],{"class":40,"line":84},[38,6454,6455],{"class":74},"LRUCache.",[38,6457,4017],{"class":103},[38,6459,2129],{"class":70},[38,6461,6462],{"class":74}," LRUCache\n",[38,6464,6465],{"class":40,"line":89},[38,6466,64],{"emptyLinePlaceholder":63},[38,6468,6469,6471,6474,6476,6478],{"class":40,"line":95},[38,6470,2268],{"class":70},[38,6472,6473],{"class":103}," LRUCache",[38,6475,2274],{"class":74},[38,6477,4038],{"class":103},[38,6479,6480],{"class":74},"(capacity)\n",[38,6482,6483,6485,6487],{"class":40,"line":110},[38,6484,178],{"class":70},[38,6486,4048],{"class":137},[38,6488,4051],{"class":74},[38,6490,6491,6494,6496],{"class":40,"line":175},[38,6492,6493],{"class":74},"    capacity ",[38,6495,78],{"class":70},[38,6497,6498],{"class":74}," capacity,\n",[38,6500,6501,6504,6506,6508],{"class":40,"line":184},[38,6502,6503],{"class":74},"    cache ",[38,6505,78],{"class":70},[38,6507,4097],{"class":74},[38,6509,6510],{"class":44},"-- key → {value = v, prev = key, next = key}\n",[38,6512,6513,6516,6518,6520,6523],{"class":40,"line":190},[38,6514,6515],{"class":74},"    head ",[38,6517,78],{"class":70},[38,6519,2088],{"class":137},[38,6521,6522],{"class":74},",  ",[38,6524,6525],{"class":44},"-- most recently used\n",[38,6527,6528,6531,6533,6535,6537],{"class":40,"line":195},[38,6529,6530],{"class":74},"    tail ",[38,6532,78],{"class":70},[38,6534,2088],{"class":137},[38,6536,6522],{"class":74},[38,6538,6539],{"class":44},"-- least recently used\n",[38,6541,6542],{"class":40,"line":201},[38,6543,6544],{"class":74},"  }, LRUCache)\n",[38,6546,6547],{"class":40,"line":217},[38,6548,187],{"class":70},[38,6550,6551],{"class":40,"line":237},[38,6552,64],{"emptyLinePlaceholder":63},[38,6554,6555,6557,6559,6561,6564],{"class":40,"line":242},[38,6556,2268],{"class":70},[38,6558,6473],{"class":103},[38,6560,134],{"class":74},[38,6562,6563],{"class":103},"get",[38,6565,6566],{"class":74},"(key)\n",[38,6568,6569,6571,6574,6576,6578,6580,6583],{"class":40,"line":247},[38,6570,314],{"class":70},[38,6572,6573],{"class":74}," node ",[38,6575,78],{"class":70},[38,6577,4191],{"class":137},[38,6579,2274],{"class":74},[38,6581,6582],{"class":103},"cache",[38,6584,6585],{"class":74},"[key]\n",[38,6587,6588,6590,6592,6594,6596,6598,6600],{"class":40,"line":253},[38,6589,897],{"class":70},[38,6591,683],{"class":70},[38,6593,6573],{"class":74},[38,6595,688],{"class":70},[38,6597,1261],{"class":70},[38,6599,2088],{"class":137},[38,6601,172],{"class":70},[38,6603,6604,6606,6608,6611],{"class":40,"line":265},[38,6605,4170],{"class":137},[38,6607,134],{"class":74},[38,6609,6610],{"class":137},"move_to_front",[38,6612,6613],{"class":74},"(key, node)\n",[38,6615,6616,6618,6621],{"class":40,"line":296},[38,6617,178],{"class":70},[38,6619,6620],{"class":74}," node.",[38,6622,6623],{"class":103},"value\n",[38,6625,6626],{"class":40,"line":311},[38,6627,187],{"class":70},[38,6629,6630],{"class":40,"line":324},[38,6631,64],{"emptyLinePlaceholder":63},[38,6633,6634,6636,6638,6640,6643],{"class":40,"line":340},[38,6635,2268],{"class":70},[38,6637,6473],{"class":103},[38,6639,134],{"class":74},[38,6641,6642],{"class":103},"put",[38,6644,6645],{"class":74},"(key, value)\n",[38,6647,6648,6650,6652,6654,6656,6658],{"class":40,"line":360},[38,6649,897],{"class":70},[38,6651,4191],{"class":137},[38,6653,2274],{"class":74},[38,6655,6582],{"class":103},[38,6657,5254],{"class":74},[38,6659,373],{"class":70},[38,6661,6662,6664,6666,6668,6671,6674,6676],{"class":40,"line":376},[38,6663,4307],{"class":137},[38,6665,2274],{"class":74},[38,6667,6582],{"class":103},[38,6669,6670],{"class":74},"[key].",[38,6672,6673],{"class":103},"value",[38,6675,2129],{"class":70},[38,6677,1399],{"class":74},[38,6679,6680,6682,6684,6686,6689,6691,6693,6695],{"class":40,"line":395},[38,6681,4307],{"class":137},[38,6683,134],{"class":74},[38,6685,6610],{"class":137},[38,6687,6688],{"class":74},"(key, ",[38,6690,4267],{"class":137},[38,6692,2274],{"class":74},[38,6694,6582],{"class":103},[38,6696,6697],{"class":74},"[key])\n",[38,6699,6700],{"class":40,"line":416},[38,6701,6702],{"class":70},"    return\n",[38,6704,6705],{"class":40,"line":430},[38,6706,839],{"class":70},[38,6708,6709,6711,6713,6715,6717,6719,6722,6724,6726,6729],{"class":40,"line":449},[38,6710,897],{"class":70},[38,6712,122],{"class":70},[38,6714,4267],{"class":137},[38,6716,2274],{"class":74},[38,6718,6582],{"class":103},[38,6720,6721],{"class":70}," >=",[38,6723,4191],{"class":137},[38,6725,2274],{"class":74},[38,6727,6728],{"class":103},"capacity",[38,6730,2725],{"class":70},[38,6732,6733,6735,6737,6740],{"class":40,"line":566},[38,6734,4307],{"class":137},[38,6736,134],{"class":74},[38,6738,6739],{"class":137},"evict_tail",[38,6741,3403],{"class":74},[38,6743,6744],{"class":40,"line":577},[38,6745,839],{"class":70},[38,6747,6748,6750,6752,6754,6756,6758,6761,6763],{"class":40,"line":601},[38,6749,4170],{"class":137},[38,6751,2274],{"class":74},[38,6753,6582],{"class":103},[38,6755,5254],{"class":74},[38,6757,78],{"class":70},[38,6759,6760],{"class":74}," {value ",[38,6762,78],{"class":70},[38,6764,6765],{"class":74}," value}\n",[38,6767,6768,6770,6772,6775,6777,6779,6781,6783],{"class":40,"line":616},[38,6769,4170],{"class":137},[38,6771,134],{"class":74},[38,6773,6774],{"class":137},"insert_front",[38,6776,6688],{"class":74},[38,6778,4267],{"class":137},[38,6780,2274],{"class":74},[38,6782,6582],{"class":103},[38,6784,6697],{"class":74},[38,6786,6787],{"class":40,"line":622},[38,6788,187],{"class":70},[38,6790,6791],{"class":40,"line":656},[38,6792,64],{"emptyLinePlaceholder":63},[38,6794,6795],{"class":40,"line":677},[38,6796,6797],{"class":44},"-- Implement: move_to_front, insert_front, evict_tail (linked list manipulation)\n",[38,6799,6800],{"class":40,"line":707},[38,6801,6802],{"class":44},"-- (left as exercise for the reader — test with the assertions below)\n",[38,6804,6805],{"class":40,"line":730},[38,6806,64],{"emptyLinePlaceholder":63},[38,6808,6809,6811,6814,6816,6819,6821,6823,6825],{"class":40,"line":745},[38,6810,71],{"class":70},[38,6812,6813],{"class":74}," cache ",[38,6815,78],{"class":70},[38,6817,6818],{"class":74}," LRUCache.",[38,6820,4038],{"class":137},[38,6822,147],{"class":74},[38,6824,6269],{"class":137},[38,6826,234],{"class":74},[38,6828,6829,6831,6833,6835,6837,6840,6842,6844],{"class":40,"line":751},[38,6830,6582],{"class":103},[38,6832,134],{"class":74},[38,6834,6642],{"class":137},[38,6836,147],{"class":74},[38,6838,6839],{"class":150},"\"a\"",[38,6841,228],{"class":74},[38,6843,2298],{"class":137},[38,6845,234],{"class":74},[38,6847,6848,6850,6852,6854,6856,6859,6861,6863],{"class":40,"line":771},[38,6849,6582],{"class":103},[38,6851,134],{"class":74},[38,6853,6642],{"class":137},[38,6855,147],{"class":74},[38,6857,6858],{"class":150},"\"b\"",[38,6860,228],{"class":74},[38,6862,231],{"class":137},[38,6864,234],{"class":74},[38,6866,6867,6869,6871,6873,6875,6878,6880,6882],{"class":40,"line":777},[38,6868,6582],{"class":103},[38,6870,134],{"class":74},[38,6872,6642],{"class":137},[38,6874,147],{"class":74},[38,6876,6877],{"class":150},"\"c\"",[38,6879,228],{"class":74},[38,6881,6269],{"class":137},[38,6883,234],{"class":74},[38,6885,6886,6888,6890,6892,6894,6896,6898,6900,6902,6904,6906,6908],{"class":40,"line":790},[38,6887,6254],{"class":137},[38,6889,147],{"class":74},[38,6891,6582],{"class":103},[38,6893,134],{"class":74},[38,6895,6563],{"class":137},[38,6897,147],{"class":74},[38,6899,6839],{"class":150},[38,6901,154],{"class":74},[38,6903,283],{"class":70},[38,6905,169],{"class":137},[38,6907,2610],{"class":74},[38,6909,6910],{"class":44},"-- a is now most recently used\n",[38,6912,6913,6915,6917,6919,6921,6924,6926,6928,6930],{"class":40,"line":796},[38,6914,6582],{"class":103},[38,6916,134],{"class":74},[38,6918,6642],{"class":137},[38,6920,147],{"class":74},[38,6922,6923],{"class":150},"\"d\"",[38,6925,228],{"class":74},[38,6927,6330],{"class":137},[38,6929,2610],{"class":74},[38,6931,6932],{"class":44},"-- evicts \"b\" (least recently used after a was accessed)\n",[38,6934,6935,6937,6939,6941,6943,6945,6947,6949,6951,6953,6955],{"class":40,"line":817},[38,6936,6254],{"class":137},[38,6938,147],{"class":74},[38,6940,6582],{"class":103},[38,6942,134],{"class":74},[38,6944,6563],{"class":137},[38,6946,147],{"class":74},[38,6948,6858],{"class":150},[38,6950,154],{"class":74},[38,6952,283],{"class":70},[38,6954,2088],{"class":137},[38,6956,234],{"class":74},[38,6958,6959,6961,6963,6965,6967,6969,6971,6973,6975,6977,6979],{"class":40,"line":830},[38,6960,6254],{"class":137},[38,6962,147],{"class":74},[38,6964,6582],{"class":103},[38,6966,134],{"class":74},[38,6968,6563],{"class":137},[38,6970,147],{"class":74},[38,6972,6839],{"class":150},[38,6974,154],{"class":74},[38,6976,283],{"class":70},[38,6978,169],{"class":137},[38,6980,234],{"class":74},[38,6982,6983,6985,6987,6989,6991,6993,6995,6997,6999,7001,7003],{"class":40,"line":836},[38,6984,6254],{"class":137},[38,6986,147],{"class":74},[38,6988,6582],{"class":103},[38,6990,134],{"class":74},[38,6992,6563],{"class":137},[38,6994,147],{"class":74},[38,6996,6877],{"class":150},[38,6998,154],{"class":74},[38,7000,283],{"class":70},[38,7002,1917],{"class":137},[38,7004,234],{"class":74},[38,7006,7007,7009,7011,7013,7015,7017,7019,7021,7023,7025,7027],{"class":40,"line":842},[38,7008,6254],{"class":137},[38,7010,147],{"class":74},[38,7012,6582],{"class":103},[38,7014,134],{"class":74},[38,7016,6563],{"class":137},[38,7018,147],{"class":74},[38,7020,6923],{"class":150},[38,7022,154],{"class":74},[38,7024,283],{"class":70},[38,7026,651],{"class":137},[38,7028,234],{"class":74},[6068,7030,7032],{"id":7031},"exercise-3-write-a-coroutine-based-timer","Exercise 3: Write a coroutine-based timer",[24,7034,7035],{"language":26},[29,7036,7038],{"className":31,"code":7037,"language":26,"meta":33,"style":33},"-- Implement a \"sleep\" using coroutines + a scheduler\n-- The scheduler should track when each coroutine should resume\n-- Hint: use os.clock() for timing, a list of (coroutine, wake_time) pairs\n\n-- Your code: a simple event loop that:\n-- 1. Spawns coroutines that yield \"sleep N\" requests\n-- 2. The scheduler tracks wake times\n-- 3. Resumes coroutines when their sleep expires\n-- 4. Handles coroutine completion and errors\n\n-- Example expected behavior:\n-- local function task1()\n--   print(\"task1 start\")\n--   sleep(0.1)\n--   print(\"task1 end\")\n-- end\n-- local function task2()\n--   print(\"task2 start\")\n--   sleep(0.05)\n--   print(\"task2 end\")\n-- end\n-- run({task1, task2})\n-- Output (interleaved by time):\n-- task1 start\n-- task2 start\n-- task2 end (after 0.05s)\n-- task1 end (after 0.1s)\n",[35,7039,7040,7045,7050,7055,7059,7064,7069,7074,7079,7084,7088,7093,7098,7103,7108,7113,7118,7123,7128,7133,7138,7142,7147,7152,7157,7162,7167],{"__ignoreMap":33},[38,7041,7042],{"class":40,"line":41},[38,7043,7044],{"class":44},"-- Implement a \"sleep\" using coroutines + a scheduler\n",[38,7046,7047],{"class":40,"line":48},[38,7048,7049],{"class":44},"-- The scheduler should track when each coroutine should resume\n",[38,7051,7052],{"class":40,"line":54},[38,7053,7054],{"class":44},"-- Hint: use os.clock() for timing, a list of (coroutine, wake_time) pairs\n",[38,7056,7057],{"class":40,"line":60},[38,7058,64],{"emptyLinePlaceholder":63},[38,7060,7061],{"class":40,"line":67},[38,7062,7063],{"class":44},"-- Your code: a simple event loop that:\n",[38,7065,7066],{"class":40,"line":84},[38,7067,7068],{"class":44},"-- 1. Spawns coroutines that yield \"sleep N\" requests\n",[38,7070,7071],{"class":40,"line":89},[38,7072,7073],{"class":44},"-- 2. The scheduler tracks wake times\n",[38,7075,7076],{"class":40,"line":95},[38,7077,7078],{"class":44},"-- 3. Resumes coroutines when their sleep expires\n",[38,7080,7081],{"class":40,"line":110},[38,7082,7083],{"class":44},"-- 4. Handles coroutine completion and errors\n",[38,7085,7086],{"class":40,"line":175},[38,7087,64],{"emptyLinePlaceholder":63},[38,7089,7090],{"class":40,"line":184},[38,7091,7092],{"class":44},"-- Example expected behavior:\n",[38,7094,7095],{"class":40,"line":190},[38,7096,7097],{"class":44},"-- local function task1()\n",[38,7099,7100],{"class":40,"line":195},[38,7101,7102],{"class":44},"--   print(\"task1 start\")\n",[38,7104,7105],{"class":40,"line":201},[38,7106,7107],{"class":44},"--   sleep(0.1)\n",[38,7109,7110],{"class":40,"line":217},[38,7111,7112],{"class":44},"--   print(\"task1 end\")\n",[38,7114,7115],{"class":40,"line":237},[38,7116,7117],{"class":44},"-- end\n",[38,7119,7120],{"class":40,"line":242},[38,7121,7122],{"class":44},"-- local function task2()\n",[38,7124,7125],{"class":40,"line":247},[38,7126,7127],{"class":44},"--   print(\"task2 start\")\n",[38,7129,7130],{"class":40,"line":253},[38,7131,7132],{"class":44},"--   sleep(0.05)\n",[38,7134,7135],{"class":40,"line":265},[38,7136,7137],{"class":44},"--   print(\"task2 end\")\n",[38,7139,7140],{"class":40,"line":296},[38,7141,7117],{"class":44},[38,7143,7144],{"class":40,"line":311},[38,7145,7146],{"class":44},"-- run({task1, task2})\n",[38,7148,7149],{"class":40,"line":324},[38,7150,7151],{"class":44},"-- Output (interleaved by time):\n",[38,7153,7154],{"class":40,"line":340},[38,7155,7156],{"class":44},"-- task1 start\n",[38,7158,7159],{"class":40,"line":360},[38,7160,7161],{"class":44},"-- task2 start\n",[38,7163,7164],{"class":40,"line":376},[38,7165,7166],{"class":44},"-- task2 end (after 0.05s)\n",[38,7168,7169],{"class":40,"line":395},[38,7170,7171],{"class":44},"-- task1 end (after 0.1s)\n",[6068,7173,7175],{"id":7174},"exercise-4-metatable-based-type-system","Exercise 4: Metatable-based type system",[24,7177,7178],{"language":26},[29,7179,7181],{"className":31,"code":7180,"language":26,"meta":33,"style":33},"-- Implement a simple type checker using metatables\n-- Create a Type class that can:\n-- 1. Define a type with a name and validation function\n-- 2. Check if a value matches the type\n-- 3. Support union types (T1 | T2) and array types (Array\u003CT>)\n\n-- Example:\n-- local Int = Type.new(\"Int\", function(v) return math.type(v) == \"integer\" end)\n-- local Str = Type.new(\"Str\", function(v) return type(v) == \"string\" end)\n-- local StrOrInt = Type.union(Str, Int)\n-- local StrArray = Type.array(Str)\n-- \n-- assert(Int:check(42) == true)\n-- assert(Int:check(3.14) == false)\n-- assert(StrOrInt:check(\"hello\") == true)\n-- assert(StrOrInt:check(42) == true)\n-- assert(StrArray:check({\"a\", \"b\"}) == true)\n-- assert(StrArray:check({1, 2}) == false)\n",[35,7182,7183,7188,7193,7198,7203,7208,7212,7217,7222,7227,7232,7237,7242,7247,7252,7257,7262,7267],{"__ignoreMap":33},[38,7184,7185],{"class":40,"line":41},[38,7186,7187],{"class":44},"-- Implement a simple type checker using metatables\n",[38,7189,7190],{"class":40,"line":48},[38,7191,7192],{"class":44},"-- Create a Type class that can:\n",[38,7194,7195],{"class":40,"line":54},[38,7196,7197],{"class":44},"-- 1. Define a type with a name and validation function\n",[38,7199,7200],{"class":40,"line":60},[38,7201,7202],{"class":44},"-- 2. Check if a value matches the type\n",[38,7204,7205],{"class":40,"line":67},[38,7206,7207],{"class":44},"-- 3. Support union types (T1 | T2) and array types (Array\u003CT>)\n",[38,7209,7210],{"class":40,"line":84},[38,7211,64],{"emptyLinePlaceholder":63},[38,7213,7214],{"class":40,"line":89},[38,7215,7216],{"class":44},"-- Example:\n",[38,7218,7219],{"class":40,"line":95},[38,7220,7221],{"class":44},"-- local Int = Type.new(\"Int\", function(v) return math.type(v) == \"integer\" end)\n",[38,7223,7224],{"class":40,"line":110},[38,7225,7226],{"class":44},"-- local Str = Type.new(\"Str\", function(v) return type(v) == \"string\" end)\n",[38,7228,7229],{"class":40,"line":175},[38,7230,7231],{"class":44},"-- local StrOrInt = Type.union(Str, Int)\n",[38,7233,7234],{"class":40,"line":184},[38,7235,7236],{"class":44},"-- local StrArray = Type.array(Str)\n",[38,7238,7239],{"class":40,"line":190},[38,7240,7241],{"class":44},"-- \n",[38,7243,7244],{"class":40,"line":195},[38,7245,7246],{"class":44},"-- assert(Int:check(42) == true)\n",[38,7248,7249],{"class":40,"line":201},[38,7250,7251],{"class":44},"-- assert(Int:check(3.14) == false)\n",[38,7253,7254],{"class":40,"line":217},[38,7255,7256],{"class":44},"-- assert(StrOrInt:check(\"hello\") == true)\n",[38,7258,7259],{"class":40,"line":237},[38,7260,7261],{"class":44},"-- assert(StrOrInt:check(42) == true)\n",[38,7263,7264],{"class":40,"line":242},[38,7265,7266],{"class":44},"-- assert(StrArray:check({\"a\", \"b\"}) == true)\n",[38,7268,7269],{"class":40,"line":247},[38,7270,7271],{"class":44},"-- assert(StrArray:check({1, 2}) == false)\n",[19,7273,7275],{"id":7274},"final-challenge-build-a-mini-repl","🧠 Final Challenge: Build a Mini-REPL",[24,7277,7278],{"language":26},[29,7279,7281],{"className":31,"code":7280,"language":26,"meta":33,"style":33},"-- Build an interactive REPL that:\n-- 1. Reads expressions from stdin\n-- 2. Compiles them with load() (sandboxed)\n-- 3. Prints the result (handling multiple return values)\n-- 4. Maintains state between inputs (a persistent environment)\n-- 5. Handles errors gracefully (compile + runtime)\n-- 6. Supports special commands: :quit, :env, :help\n\n-- Hints:\n-- - Use load(line, \"repl\", \"t\", env) where env is a persistent table\n-- - For expressions (not statements), wrap in \"return ...\"\n-- - Use pcall to catch errors\n-- - The persistent env allows variables to survive between inputs\n",[35,7282,7283,7288,7293,7298,7303,7308,7313,7318,7322,7327,7332,7337,7342],{"__ignoreMap":33},[38,7284,7285],{"class":40,"line":41},[38,7286,7287],{"class":44},"-- Build an interactive REPL that:\n",[38,7289,7290],{"class":40,"line":48},[38,7291,7292],{"class":44},"-- 1. Reads expressions from stdin\n",[38,7294,7295],{"class":40,"line":54},[38,7296,7297],{"class":44},"-- 2. Compiles them with load() (sandboxed)\n",[38,7299,7300],{"class":40,"line":60},[38,7301,7302],{"class":44},"-- 3. Prints the result (handling multiple return values)\n",[38,7304,7305],{"class":40,"line":67},[38,7306,7307],{"class":44},"-- 4. Maintains state between inputs (a persistent environment)\n",[38,7309,7310],{"class":40,"line":84},[38,7311,7312],{"class":44},"-- 5. Handles errors gracefully (compile + runtime)\n",[38,7314,7315],{"class":40,"line":89},[38,7316,7317],{"class":44},"-- 6. Supports special commands: :quit, :env, :help\n",[38,7319,7320],{"class":40,"line":95},[38,7321,64],{"emptyLinePlaceholder":63},[38,7323,7324],{"class":40,"line":110},[38,7325,7326],{"class":44},"-- Hints:\n",[38,7328,7329],{"class":40,"line":175},[38,7330,7331],{"class":44},"-- - Use load(line, \"repl\", \"t\", env) where env is a persistent table\n",[38,7333,7334],{"class":40,"line":184},[38,7335,7336],{"class":44},"-- - For expressions (not statements), wrap in \"return ...\"\n",[38,7338,7339],{"class":40,"line":190},[38,7340,7341],{"class":44},"-- - Use pcall to catch errors\n",[38,7343,7344],{"class":40,"line":195},[38,7345,7346],{"class":44},"-- - The persistent env allows variables to survive between inputs\n",[19,7348,7350],{"id":7349},"summary","Summary",[15,7352,7353],{},"These projects exercise the full range of Lua's capabilities:",[7355,7356,7357,7370],"table",{},[7358,7359,7360],"thead",{},[7361,7362,7363,7367],"tr",{},[7364,7365,7366],"th",{},"Project",[7364,7368,7369],{},"Concepts Used",[7371,7372,7373,7382,7390,7401,7413,7421,7429,7437],"tbody",{},[7361,7374,7375,7379],{},[7376,7377,7378],"td",{},"JSON Parser",[7376,7380,7381],{},"Patterns, recursion, string manipulation, error handling",[7361,7383,7384,7387],{},[7376,7385,7386],{},"Pipeline",[7376,7388,7389],{},"Coroutines, generators, lazy evaluation, functional composition",[7361,7391,7392,7395],{},[7376,7393,7394],{},"Query Builder",[7376,7396,7397,7398,7400],{},"Metatables, fluent API, ",[35,7399,4809],{},", string building",[7361,7402,7403,7406],{},[7376,7404,7405],{},"Plugin System",[7376,7407,7408,7409,7412],{},"Sandboxing, ",[35,7410,7411],{},"load()"," with custom env, pcall, hook pattern",[7361,7414,7415,7418],{},[7376,7416,7417],{},"LRU Cache",[7376,7419,7420],{},"Tables as linked structures, metatables, O(1) operations",[7361,7422,7423,7426],{},[7376,7424,7425],{},"Timer",[7376,7427,7428],{},"Coroutines, scheduler, cooperative multitasking",[7361,7430,7431,7434],{},[7376,7432,7433],{},"Type system",[7376,7435,7436],{},"Metatables, function composition, recursive validation",[7361,7438,7439,7442],{},[7376,7440,7441],{},"REPL",[7376,7443,7444,7446],{},[35,7445,7411],{},", pcall, persistent environments, string eval",[15,7448,7449],{},"Each project can be extended: add error recovery, add more types, add persistence. The skills transfer directly to real Lua applications: game mods (Roblox, Garry's Mod), Neovim plugins, Redis scripting, and embedded Lua hosts.",[7451,7452,7453],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":33,"searchDepth":48,"depth":48,"links":7455},[7456,7457,7458,7463,7464],{"id":21,"depth":48,"text":22},{"id":3349,"depth":48,"text":3350},{"id":3975,"depth":48,"text":3976,"children":7459},[7460,7461,7462],{"id":6412,"depth":54,"text":6413},{"id":7031,"depth":54,"text":7032},{"id":7174,"depth":54,"text":7175},{"id":7274,"depth":48,"text":7275},{"id":7349,"depth":48,"text":7350},"md",{},"\u002Flua\u002F16-exercises-and-projects",{"title":7469,"description":17},{"Lua 16 — Capstone":7470,"description":7471},"Projects & Exercises",{"Production-grade capstone projects":7472},"a JSON parser using pattern matching, a coroutine-based async HTTP server skeleton, a metatable-based ORM query builder, and a Lua-hosted plugin system. Each project demonstrates multiple advanced concepts in a real-world context.","lua\u002F16-exercises-and-projects","vlKRwpHn75IJZgRi27rN3xAnZmAQoP0CybuH0NI_5tA",1789924650714]