Lua interpreter

Create a table of words that start a block. When one of those words is keyed, add 1 to a counter. Subtract 1 when an end is keyed. If the counter is 0, the block is complete and can be run.

This is a suggestion rather than a criticism, but why not do a project that uses animated graphics, rather than replicating the Lua interpreter? Graphics is where Codea’s unique features really shine.

Have you not seen the gorgeous green-on-black typography, the silky smooth typewriter scrolling, the blinking cursor beckoning the user? How dare you say my interpreter does not have good graphics! :stuck_out_tongue:

crawl back into your DOS cave, you neanderthal, you :-B

@dave1707 yeah that’s the approach I’ve gone with here. I put a space at the beginning and end of the line, and then look for the keyword preceded by a space and succeeded by a non-letter character, "%s"..keyword.."%A+" (in order to catch function( but not catch a variable name that includes a keyword like doGraphics or whatever. I guess that as the string currently stands do_graphics would return a false positive ). It’s these kinds of fiddly limit cases that made me wonder whether there was some way to use the Lua interpreter to parse the code, I dunno, using pcall and then examining the error message to work out whether there’s a missing end.

Oh well, I guess this works well enough.

@iam3to5am

I think this is close to what you asked. You don’t have to use do to initiate a block, you can use for if function while repeat.

Next up: query _G to handle autocomplete!


--# Main
-- Lua Interpreter
oldPrint = print

function setup()
    lines = {{level=1, str=""}} --indent level, string
    level = 1 --current indent level
    margin = WIDTH * 0.1
    typeWriter = HEIGHT*0.5
    screeny, targety =0,0
    textWrapWidth(WIDTH - margin * 2)
    font("Inconsolata")
    fill(6, 255, 0, 255)
    fontSize(30)
    textMode(CORNER)
    showKeyboard()
end

function interpret(command)
    local ok, result = pcall(function() return loadstring(command)() end)
    if not ok then --attempt to handle fragments like "5-2"
        ok, result = pcall(function() return loadstring("return "..command)() end)
    end
    if result then
        print(tostring(result))
    end
end

function print(...)
    local out = {...}
    lines[#lines+1]={level=0, str=table.concat(out, "  ")}
end

function draw()
    background(40, 40, 50)
    translate(0,screeny) 
    
        --cursor
    local cursorSpeed = 4
    local cursorLen = (ElapsedTime*cursorSpeed%2)//1 --a number that regularly alternates between 0 and 1 
    local cursor=string.rep("\\u{25ae}", cursorLen)..string.rep("\\u{25af}", 1-cursorLen) --cursor symbol..empty cursor symbol. The empty symbol is necessary otherwise the cursor blinking will push a word past the word wrap limit if you're at the end of a line. One of the disadvantages of a text-based cursor
    
    local y = HEIGHT
    for i,v in ipairs(lines) do
        local out = v.str
        if i==#lines then out = out..cursor end
        local indent = string.rep("> ", v.level)
        local w,h = textSize(indent..out)
        y = y - h
        
        text(indent..out, margin, y)
    end
    targety = math.max(0, typeWriter-y) --scroll screen
    screeny = screeny + (targety - screeny) * 0.1  
end

local block = {"function", "if", "do", "repeat"} --for, while statements end in do

function keyboard(key)
    if key == BACKSPACE then
        lines[#lines].str=string.sub(lines[#lines].str, 1, -2)
    elseif key == RETURN then
        lines[#lines].str = lines[#lines].str.." " --add space to end (and later to start of string) to check for discrete words
        --check for block start
        for _,v in ipairs(block) do
            if string.match(" "..lines[#lines].str, "%s"..v.."%A+") then 
                if level==1 then blockStart = #lines end --start of block
                level = level + 1
            end
        end
        --check for block end
        if string.match(" "..lines[#lines].str, "%send%A+") or string.match(" "..lines[#lines].str, "%suntil%A+") then
            level = math.max(level - 1, 1)
            lines[#lines].level = level
        end
        --interpret
        if level == 1 then
            if blockStart then
                local str = "" --concatenate block
                for i = blockStart, #lines do
                    str = str..lines[i].str
                end
                interpret(str)
                blockStart=nil
            else
                interpret(lines[#lines].str) --interpret just this line
            end
        end
        lines[#lines+1]={level=level, str=""}
    else
        lines[#lines].str = lines[#lines].str..key
    end
end


--# Sandbox
--Sandbox, prevent user from overwriting code

function null() end

local saveTab = saveProjectTab()

saveProjectTab = null

I think that [^_%w] should be a pretty good way of delimiting terms in Lua. Not an underscore or an alphanumeric.

Here’s a quick video of where I’m at:

http://youtu.be/SNQApQ4B1O0

Autocomplete, buttons for frequently used characters, cursor placement. It’s almost at feature-parity with the Codea editor!
:stuck_out_tongue:
(Exaggerating enormously…)

Still, it’s at the stage now where it’s actually reasonably pleasant to use. I’m not really sure how you would go about sandboxing it though. i.e. the user just has to reuse one of the function names as a variable to break everything. So it’s very far from idiot-proof. But for personal use it’s quite nice. And less than 300 lines!

https://github.com/Utsira/Codea/blob/master/Lua%20Interpreter.lua

(ps the GitHub repository is being maintained by Working Copy and the Codea client I wrote for it)

Has anyone made an idiot’s guide to using Working Copy and the Codea client?

Do you mean an idiot’s guide to Git?

No, an idiot’s guide to using your backup system to save Codea projects.

For someone like me.

There’s the installation and usage instructions here (at the top of the code block)

http://codea.io/talk/discussion/comment/61757/#Comment_61757

I plan eventually on blogging about it, if you’d like more detail.

It’s interesting that all guides to using Git stress using the command line, when it’s perfectly possible to just use GUIs/clients.

The Codea client is still in the “alpha” stage, feedback is welcome. I’ve just noticed I have a duplicate file in my repository, so there are some kinks to be ironed out. There’s an update to Working Copy arriving soon (I’m using the testflight beta) that allows multi-file write (ie write each tab to a separate file). Multi-file read is still a problem though. So for the moment the Codea client just reads and writes a single file, concatena-ing the project using the “paste into project” format.

very nice

Just to answer @Jmv38 and @Ignatz , why an interpreter/ console, I suppose it’s something that lots of languages come with (desktop Lua, Pythonista etc), so it’s a paradigm that lots of people are used to. Sometimes you just need to test something really quickly. Eg the other day I wanted to check whether an empty string "" returns nil. I guessed that it wouldn’t, but I still wanted to check before basing some logic on that. I used to have a project called tests with loads of these kinds of little tests. Or if it’s something related to a specific project you can have a test tab with a separate setup and draw function. Or you could create a “delete me” project and then delete it. But I think the most natural environment to do this kind of testing, usually where you’re just testing some behaviour you’re not sure of, and want feedback as soon as you hit return, is an interpreter/ console.

In any case it was a lot of fun to build, with lots of great feedback and tips from this thread. I’m constantly amazed at how much you can do with just a few hundred lines.

I haven’t yet found a way to fully sandbox it (other than manually testing for variables with the names draw, touched, keyboard, etc and not interpreting those lines). Although code being executed via loadstring can’t access upvalues, eg the local variables/ functions in the same tab as the loadstring call, which is handy. So you can make as many of the functions local as you can. Perhaps there’s some advanced technique (using environments?) for sealing off draw, touched etc from the user?

I just keep a tab on the right, called Test, with a minimal setup/draw function, and do my testing in there. One keystroke comments it out or makes it live.

The advantage is that when I write new functions, I can quickly test them - and it is a Lua interpreter as well, of course.

@yojimbo2000 I wrote a dynamic lua loader which uses function environments for sandboxing a while ago :slight_smile:

http://codea.io/talk/discussion/3114/include-a-lightweight-dynamic-tab-and-project-loader#latest

A version compatible with lua 5.3 is also in my entity component system framework :slight_smile:

https://github.com/XanDDemoX/Codea-Xile/blob/master/Xile.codea/Include.lua

Thanks, will have a look at those! I’ve not done anything with environments yet.