Tinyboss

LUAU - Lua Upgraded v0.1

Jan 14th, 2013
213
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.20 KB | None | 0 0
  1. -- luau: Lua-upgraded
  2.  
  3. -- Since we're sharing our scope with the user, make a table to keep
  4. -- all our stuff in, so we pollute the namespace as little as possible:
  5. luau_={}
  6. luau_.n = 0         -- current line number
  7. luau_.opt = {}      -- options data
  8. luau_.mac = {}      -- macros
  9. -- A default macro:
  10. luau_.mac.la = {['def']='os.loadAPI("%1")',['pnum']=1}
  11.  
  12. -- Default option settings:
  13. luau_.opt.skip_lines={
  14.     ['type'] = 'number',
  15.     ['value'] = 0,
  16.     ['default'] = 0,
  17.     ['desc']='Number of blank lines to skip between input/output pairs.'
  18. }
  19. luau_.opt.use_color={
  20.     ['type'] = 'boolean',
  21.     ['value'] = true,
  22.     ['default'] = true,
  23.     ['desc']='Whether to use color, if available.'
  24. }
  25.  
  26. -- Some variables we do want to expose:
  27. in_ = {}      -- input history
  28. out_ = {}     -- output history
  29.  
  30.  
  31. -- Help topics:
  32. luau_.help = {}
  33.  
  34. luau_.help.summary = 'The following topics are available, and can be abbreviated--for instance, "/help sub" for "/help substitution".'
  35.  
  36. luau_.help.substitution = 'Typing "s/find-expr/replace-expr/" will repeat the previously entered line, but with all occurrences of find-expr replaced by replace-expr. Lua\'s string-matching patterns may be used in find-expr. This is often quicker than using up-arrow to recall the command and editing it manually.'
  37.  
  38. luau_.help.history = 'The tables in_ and out_ hold the input and output histories, and can be referenced, for example: "result=out_[9]".'
  39.  
  40. luau_.help['repeat-command'] = 'To repeat a previous input, use the "!<n>" command. For example, typing !23 will re-evaluate the 23rd command in the history.'
  41.  
  42. luau_.help['question-mark'] = 'The "?" character is a shortcut for print: "?foo" is short for "print(foo)". If foo is a table, then its keys and values will be displayed.'
  43.  
  44. luau_.help['macros'] = 'You can define macros to shorten frequently-typed strings. For example, if you type "/macro %la=os.loadAPI", then in the future any occurence of "%la" in commands you type will be changed to "os.loadAPI" before processing by Lua.\n\nMacros can also accept parameters. For example, "/macro %rs=rednet.send(%1,%2)" will tell luau to expand "%rs[23][\'hi!\']" into "rednet.send(23,\'hi!\')".\n\nParameters may appear more than once and out of order--a silly example is "/macro %palindrome=%3%2%1%2%3", which expands "%palindrome[A][B][C]" into "CBABC".'
  45.  
  46. luau_.help['commands'] = 'Luau has some built-in commands which are invoked by starting a line with the slash "/" character. Type "/commands" for a list.'
  47.  
  48. -- Build a sorted list of help topics:
  49. luau_.helpkeys = {}
  50. for k in pairs(luau_.help) do
  51.     if k~='summary' then
  52.         table.insert(luau_.helpkeys, k)
  53.     end
  54. end
  55. table.sort(luau_.helpkeys)
  56.  
  57. -- Define the appropriate color function:
  58. if term.isColor() then
  59.     term.setTextColor(colors.gray)
  60.     luau_.setColor = function(c)
  61.         if luau_.opt.use_color.value then
  62.             term.setTextColor(c)
  63.         else
  64.             term.setTextColor(gray)
  65.         end
  66.     end
  67. else
  68.     luau_.setColor = function(c) return end
  69. end
  70.  
  71. luau_.terminated = false
  72. -- End of initialization code.
  73.  
  74. function luau_.terminate()
  75.     -- Goodbye, cruel world:
  76.     luau_.terminated = true
  77. end
  78.  
  79. function luau_.separator()
  80.     -- A line of dashes, of the appropriate length:
  81.     return(string.rep('-', term.getSize()))
  82. end
  83.  
  84. function luau_.loadData()
  85.     -- Load config options and macros:
  86.     local h
  87.     if fs.exists('luau.conf') then
  88.         h = fs.open('luau.conf','r')
  89.         local opt, mac
  90.         opt = textutils.unserialize(h.readLine())
  91.         mac = textutils.unserialize(h.readLine())
  92.         h.close()
  93.         if type(opt)=='table' and type(mac)=='table' then
  94.             luau_.opt = opt
  95.             luau_.mac = mac
  96.             return true
  97.         else
  98.             luau_.showError('Unable to load configuration; /luau.conf is corrupt. Exiting. (Delete or rename /luau.conf to start fresh)')
  99.             return false
  100.         end
  101.     else
  102.         luau_.setColor(colors.blue)
  103.         print('Configuration file /luau.conf does not exist; creating with default values.')
  104.         luau_.saveData()
  105.         return true
  106.     end
  107. end
  108.  
  109. function luau_.saveData()
  110.     -- Save config options and macros:
  111.     h = fs.open('luau.conf','w')
  112.     h.writeLine(textutils.serialize(luau_.opt))
  113.     h.writeLine(textutils.serialize(luau_.mac))
  114.     h.close()
  115. end
  116.  
  117. function luau_.firstMatch(s, t)
  118.     for i, k in ipairs(t) do
  119.         if string.find(k, s) == 1 then
  120.             return k
  121.         end
  122.     end
  123.     return nil
  124. end
  125.  
  126. function luau_.showError(errorText)
  127.     -- Report errors to the user
  128.     luau_.setColor(colors.red)
  129.     print(errorText)
  130.     luau_.setColor(colors.gray)
  131. end
  132.  
  133. function luau_.trim(s)
  134.     -- Trim leading/trailing spaces from string s:
  135.     return (s:gsub("^%s*(.-)%s*$", "%1"))
  136. end
  137.  
  138. function luau_.macro(s)
  139.     -- !<n> to re-evaluate n-th input:
  140.     local a, b, n
  141.     a, b, n = s:find('!(%d+)')
  142.     n = tonumber(n)
  143.     if a == 1 then
  144.         if in_[n] then
  145.             s = in_[n]
  146.         else
  147.             luau_.showError("History entry "..tostring(n).." not found.")
  148.             s = ''
  149.         end
  150.         return s
  151.     end
  152.     -- s/foo/bar/ substitutions
  153.     a, b, find, replace, flags = s:find('s/(.+)/(.*)/(.*)')
  154.     if find and in_[luau_.n] then
  155.         s = in_[luau_.n-1]:gsub(find, replace)
  156.         return s
  157.     end
  158.     -- Macro expansion:
  159.     local lMark, rMark = '>tboss>', '<tboss<'
  160.     -- This lMark/rMark business is an ugly hack that wouldn't be
  161.     -- necessary if I knew how to directly capture the stuff INSIDE a
  162.     -- balanced expression captured with %b[], i.e. without the brackets.
  163.     for name, macro in pairs(luau_.mac) do
  164.         local pattern = '%%'..name..string.rep('(%b[])',macro.pnum)
  165.         -- This is a workaround for a bug in the string library:
  166.         local s_ = s..string.rep('[ ]',macro.pnum)
  167.         local a, b = s_:find(pattern)
  168.         if b and b<=s:len() then
  169.             if s_:find(pattern) then
  170.                 local def = macro.def:gsub('(%%%d)',lMark..'%1'..rMark)
  171.                 s_ = s_:gsub(pattern, def)
  172.                 s_ = s_:gsub(lMark..'%[', '')
  173.                 s_ = s_:gsub('%]'..rMark, '')
  174.                 s = s_:sub(1, s_:len()-3*macro.pnum)
  175.             end
  176.         end
  177.     end
  178.     return s
  179. end
  180.  
  181. function luau_.columnList(t)
  182.     -- Print the elements of t in columns:
  183.     -- Compute the max length of the names so we can decide
  184.     -- how many columns to print them in:
  185.     local maxLen = 0
  186.     for i,k in ipairs(t) do
  187.         if string.len(k) > maxLen then maxLen = string.len(k) end
  188.     end
  189.     local spacing = 2   -- Spacing between columns
  190.     local w, h = term.getSize()
  191.     local nCols = math.floor((w+spacing)/(maxLen+spacing))
  192.     -- Print all the names in columns:
  193.     for i, k in ipairs(t) do
  194.         if (i%nCols == 0) or (i == #t) then
  195.             print(k)
  196.         else
  197.             write(k..string.rep(' ', maxLen+spacing-string.len(k)))
  198.         end
  199.     end
  200. end
  201.  
  202. function luau_.showHelp(topic)
  203.     luau_.setColor(colors.blue)    
  204.     topic = tostring(topic)
  205.     if topic ~= '' then
  206.         -- Do partial matching:
  207.         local mtopic = luau_.firstMatch(topic, luau_.helpkeys)
  208.         if luau_.help[mtopic] then
  209.             local m, n = term.getSize()
  210.             textutils.pagedPrint('('..mtopic..') '..luau_.help[mtopic],n-3)
  211.         else
  212.             print('No help found for "'..topic..'". Type "help" for a list of topics.')
  213.         end
  214.     else
  215.         -- Print help summary and list of topics:
  216.         print(luau_.help.summary)
  217.         print(luau_.separator())
  218.         --luau_.setColor(colors.lightBlue)
  219.         luau_.columnList(luau_.helpkeys)
  220.     end
  221. end
  222.  
  223. function luau_.searchHistory(s)
  224.     -- Print a list of history entries matching s.
  225.     -- Should refactor this into a search and a print.
  226.     local nFound = 0
  227.     for i,v in ipairs(in_) do
  228.         if string.find(v,s) then
  229.             print(i..': '..v)
  230.             nFound = nFound+1
  231.         end
  232.     end
  233.     return nFound
  234. end
  235.  
  236. function luau_.keySort(a, b)
  237.     -- Order function for table keys, returns true if a<b:
  238.     if type(a)=='number' and type(b)=='number' then
  239.         return a<b
  240.     elseif type(a)=='number' then
  241.         return true
  242.     elseif type(b)=='number' then
  243.         return false
  244.     elseif type(a)=='string' and type(b)=='string' then
  245.         return a<b
  246.     elseif type(a)=='string' then
  247.         return true
  248.     elseif type(b)=='string' then
  249.         return false
  250.     else
  251.         return (type(a)<type(b))
  252.     end
  253. end
  254.  
  255. function luau_.serial(v)
  256.     -- Wrapper for textutils.serialize, to work with all types
  257.     if string.find("nil boolean number string table", type(v)) then
  258.         local err, str = pcall(function() textutils.serialize(v) end)
  259.         if err then str=tostring(v) end
  260.         return str
  261.     else
  262.         return tostring(v)
  263.     end
  264. end
  265.  
  266. function luau_.setMacro(p, overwrite)
  267.     -- Define or redefine a macro:
  268.     luau_.setColor(colors.yellow)
  269.     if p=='' then
  270.         -- Display currently defined macros:
  271.         print("The following macros are currently defined:")
  272.         print(luau_.separator())
  273.         for k in pairs(luau_.mac) do
  274.             print("%"..k.."="..luau_.mac[k].def)
  275.         end
  276.     else
  277.         -- Should be in the form "%name=def":
  278.         local a, b, name, def = p:find('%%([%w_]+)=(.*)')
  279.         if name then
  280.             -- See if it's already defined:
  281.             if luau_.mac[name] and not overwrite then
  282.                 print('Macro %'..name..' is already defined. Use /Macro to overwrite it.')
  283.                 return false
  284.             end
  285.             -- See how many params:
  286.             local n = 0
  287.             def:gsub('%%(%d+)', function(s) n=tonumber(s) end)
  288.             luau_.mac[name] = {['def']=def, ['pnum']=n}
  289.             -- Save it right away. We could wait until we exit, but
  290.             -- during development crashes are likely so let's not
  291.             -- lose people's work.
  292.             luau_.saveData()
  293.         else
  294.             luau_.showError('Invalid macro definition (see /help macro).')
  295.             return false
  296.         end
  297.     end
  298.     return true
  299. end
  300.  
  301. function luau_.setOpt(p)
  302.     luau_.setColor(colors.yellow)
  303.     if p=='' then
  304.         print('The following options are available. Type "/option <option_name>=<value>" to change, or "/option <option_name>" to get a description.')
  305.         local keys = {}
  306.         for k in pairs(luau_.opt) do keys[#keys+1]=k end
  307.         table.sort(keys, luau_.keySort)
  308.         print(luau_.separator())
  309.         for i, k in ipairs(keys) do
  310.             print(k..'='..tostring(luau_.opt[k].value))
  311.         end
  312.     elseif p:find('=') then
  313.         local a, b, opt, val = p:find("([%w_]+)%s*=%s*(.*)")
  314.         if luau_.opt[opt] then
  315.             -- Make sure the types are right:
  316.             local t = luau_.opt[opt].type
  317.             local ok = false
  318.             if t=='boolean' then
  319.                 local v = val:lower()
  320.                 if v=='true' or v=='1' or v=='yes' or v=='y' then
  321.                     luau_.opt[opt].value = true
  322.                     ok = true
  323.                 elseif v=='false' or v=='0' or v=='no' or v=='n' then
  324.                     luau_.opt[opt].value = false
  325.                     ok = true
  326.                 end
  327.             elseif t=='number' then
  328.                 local v = tonumber(val)
  329.                 if v then
  330.                     luau_.opt[opt].value = v
  331.                     ok = true
  332.                 end
  333.             elseif t=='string' then
  334.                 luau_.opt[opt].value = val
  335.                 ok = true
  336.             end
  337.             if ok then
  338.                 luau_.saveData()
  339.             else
  340.                 print('Value for '..opt..' must be of type '..t..'.')
  341.             end
  342.         else
  343.             print('Option "'..p..'" not found. Type "/option" for a list.')
  344.         end
  345.     else
  346.         if luau_.opt[p] then
  347.             print(luau_.opt[p].desc..' [type='..luau_.opt[p].type..', value='..tostring(luau_.opt[p].value)..', default='..tostring(luau_.opt[p].default)..']')
  348.         else
  349.             print('Option "'..p..'" not found. Type "/option" for a list.')
  350.         end
  351.     end
  352. end
  353.  
  354. function luau_.showCommands(p)
  355.     luau_.setColor(colors.blue)
  356.     if p=='' then
  357.         print('The following commands are available. Type "/command <command-name>" for details.')
  358.         print(luau_.separator())
  359.         luau_.columnList(luau_.cmdNames)
  360.     else
  361.         local cmd = luau_.firstMatch(p, luau_.cmdNames)
  362.         if cmd then
  363.             print('/'..cmd..': '..luau_.cmd[cmd].desc)
  364.         else
  365.             print('Command "'..p..'" not found. Type "/commands" for a list.')
  366.         end
  367.     end
  368. end
  369.  
  370. -- Now that we've defined all our handler functions, we can set up the command list:
  371. luau_.cmd = {}
  372. luau_.cmd.help = {
  373.     ['handler'] = luau_.showHelp,
  374.     ['desc'] = 'Online help. Type "/help <topic>", or "/help" for a list of topics.',
  375. }
  376. luau_.cmd.exit = {
  377.     ['handler'] = luau_.terminate,
  378.     ['desc'] = 'Exits Luau and returns you to the shell or wherever else you launched it from.',
  379. }
  380. luau_.cmd.history = {
  381.     ['handler'] = luau_.searchHistory,
  382.     ['desc'] = 'Typing "/history" will simply print the entire history buffer, while "/history <pattern>" will only show entries matching <pattern>.',
  383. }
  384. luau_.cmd.macro = {
  385.     ['handler'] = function(p) luau_.setMacro(p, false) end,
  386.     ['desc'] = 'Define a text-expansion macro with "/macro %<name>=<output>", or type "/macro" for a list of existing macros. Type "/help macros" for more details.',
  387. }
  388. luau_.cmd.Macro = {
  389.     ['handler'] = function(p) luau_.setMacro(p, true) end,
  390.     ['desc'] = 'Same as "/macro", but able to overwrite existing definitions.',
  391. }
  392. luau_.cmd.options = {
  393.     ['handler'] = luau_.setOpt,
  394.     ['desc'] = 'Type "/options" for a list of all options, "/option <name>" for an option\'s description, or "/option <name>=<value>" to change it.',
  395. }
  396. luau_.cmd.commands = {
  397.     ['handler'] = luau_.showCommands,
  398.     ['desc'] = 'Type "/commands" for a list of commands, or "/command <command-name>" to view a description of a particular one.',
  399. }
  400. luau_.cmdNames = {}
  401. for k in pairs(luau_.cmd) do table.insert(luau_.cmdNames, k) end
  402. table.sort(luau_.cmdNames)
  403.  
  404.  
  405. function luau_.go()
  406.     -- Load config options and macros:
  407.     if not luau_.loadData() then return false end
  408.     luau_.setColor(colors.blue)
  409.     print()
  410.     print('LUAU--LUA Upgraded v0.1 by Tinyboss\nType "/exit" to exit, "/help" for help.')
  411.     while not luau_.terminated do
  412.         luau_.setColor(colors.gray)
  413.         for i=1,luau_.opt.skip_lines.value do print() end
  414.         luau_.n = luau_.n+1
  415.         -- Get the next input, passing the input history for recall by up-arrow:
  416.         local input = ''
  417.         while luau_.trim(input)=='' do
  418.             luau_.setColor(colors.lime)
  419.             write(' in_['..luau_.n..']: ')
  420.             luau_.setColor(colors.lightGray)
  421.             input = luau_.trim(read(false, in_))
  422.         end
  423.         in_[luau_.n] = input
  424.         luau_.handled = false
  425.         -- ? is a shortcut for print, with benefits:
  426.         if in_[luau_.n]:sub(1,1) == "?" then
  427.             rest_ = luau_.trim(in_[luau_.n]:sub(2))
  428.             if rest_ == "" then
  429.                 -- If they just typed "?", they probably wanted help:
  430.                 luau_.setColor(colors.blue)
  431.                 print('"?foo" is a synonym for "print(foo)". Use "/help" for help.')
  432.                 luau_.handled = true
  433.             elseif type(getfenv(1)[rest_])=="table" then
  434.                 -- If it's a table, display it:
  435.                 local keys = {}
  436.                 for k in pairs(getfenv(1)[rest_]) do
  437.                     keys[#keys+1]=k
  438.                 end
  439.                 table.sort(keys, luau_.keySort)
  440.                 local kv = {}
  441.                 for i,k in ipairs(keys) do
  442.                     kv[i] = luau_.serial(k)..'='
  443.                         ..luau_.serial(getfenv(1)[rest_][k])
  444.                 end
  445.                 print('{',table.concat(kv, ', '),'}')
  446.                 luau_.handled = true
  447.             else
  448.                 in_[luau_.n] = 'print('..in_[luau_.n]:sub(2)..')'
  449.             end
  450.         end
  451.         if not luau_.handled then
  452.             -- See if it's a slash command:
  453.             local a_, b_, cmd_, rest_ = in_[luau_.n]:find('/(%a+)%s*(.*)')
  454.             if a_==1 then
  455.                 cmd_ = luau_.firstMatch(cmd_, luau_.cmdNames)
  456.                 if cmd_ then
  457.                     luau_.cmd[cmd_].handler(rest_)
  458.                 else
  459.                     luau_.setColor(colors.yellow)
  460.                     print('Invalid command. (Try "/help".)')
  461.                 end
  462.             else
  463.                 -- Do macro substitution before passing it to Lua:
  464.                 in_[luau_.n] = luau_.macro(in_[luau_.n])
  465.                 luau_.setColor(colors.gray)
  466.                 is_print_ = string.sub(in_[luau_.n], 1, 6)=='print('
  467.                 -- Try it as an expression first:
  468.                 func_, err_ = loadstring('out_[luau_.n]='..in_[luau_.n])
  469.                 if err_ then
  470.                     -- Try it as a statement (e.g. "pi=3"):
  471.                     func_, err_ = loadstring(in_[luau_.n])
  472.                     if err_ then
  473.                         luau_.showError('(loadstring)'..err_)
  474.                     else
  475.                         setfenv(func_, getfenv(1))
  476.                         success_, err_ = pcall(func_)
  477.                         if not success_ then
  478.                             luau_.showError(err_)
  479.                         end
  480.                     end
  481.                 else
  482.                     -- It worked as an expression, so store and show the result:
  483.                     setfenv(func_, getfenv(1))
  484.                     success_, err_ = pcall(func_)
  485.                     if success_ then
  486.                         if out_[luau_.n] and not is_print_ then
  487.                             luau_.setColor(colors.green)
  488.                             write('out_['..luau_.n..']: ')
  489.                             luau_.setColor(colors.gray)
  490.                             print(out_[luau_.n])
  491.                         end
  492.                     else
  493.                         luau_.showError(err_)
  494.                     end
  495.                 end
  496.             end
  497.         end
  498.     end
  499.     -- Save config and macro data:
  500.     luau_.saveData()
  501. end
  502.  
  503. luau_.go()
Advertisement
Add Comment
Please, Sign In to add comment