Lua Interpreter
Introduction to Lua
Although I heard of Lua several years ago, I decided to study this language until recently when Lua enters the top 10 languages of the TIOBE Programming Community Index. To my surprise Lua is almost an ideal dynamic typed functional language, because it is simple, efficient and powerful. As a learning practice I wrote a program to draw graph of any equation or inequation with two variables in Lua. If you are new to Lua I strongly recommend you read the book Programming in Lua and visit lua.org.
Lua is an extensible extension language. Extensible means libraries can be written in C and accessed in Lua in natural ways. Extension means it can be embedded into a host application so that users can program it. However due to the following two reasons I want to write library in C# and call the library in Lua:
1. I don't like to program in C/C++;
2. None of the Lua libraries can match the functionality provided by .net framework.
I don't find an existed solution so I implemented an Lua interpreter by myself. Since the interpreter is written in C#, .net library can be called in Lua code when it is appropriately wrapped in modules.
Implemention of the interpreter
The syntax of Lua is defined with parser expression grammar in Lua.Grammar.txt file. Then given the grammar file as input a homemade parser generator is used to generate the parser code. If the lua code is parsed successfully a syntax tree with Chunk as root node is returned. Then the interpreter executes the Chunk according to Lua semantics.
Most of the implementation is straightforward, a little difference with standard Lua is that strings are unicode and the library function string.format use the same formatter syntax as in C#'s string.Format.
The project code is compiled into two files: lua.exe and wlua.exe, one is command line version and the other is winform version. Here is the result after run the test.lua file:
Windows Forms Library
As a proof of the concept, I write a module to create UI using Windows Forms. After read the code in WinFormLib.cs you will understand why Lua is a miraculous language, the metatable mechanism is more powerful than firstly imagined. The module is named as "Gui", the same .net type, method, property name can be used to manipulate controls. Here is an example program in WinFormExample.wlua:
form = Gui.Form{ Text="Main Form", Height=200, StartPosition="CenterScreen", Gui.Label{ Text="Hello!", Name="lable", Width=80, Height=17, Top=9, Left=12 }, Gui.Button{ Text="Click", Width=80, Height=23, Top=30, Left=12, Click=function(sender,e) Gui.ShowMessage(lable.Text,"Clicked") end }, } Gui.Run(form)
Gui.ControlTypeName
returns a lua function to create an instance of the control, the function accepts a lua table as its parameter, key value pairs in the table are used to set values to control properties, array items in the table are added as child controls or sub items of the control. One special thing is that when set the Name
property, a global variable is created for the control. As you see, Lua as a data descriptive language is more compact than an equivalent XAML file.
The screen shot of this example is:
The Ledger.wlua file contains a more complete and practical example, it can add entries to a ledger sheet and save to a file, the saved file can opened later. Here is the code:
form = Gui.Form{ Text="Ledger Sheet", Width=700, Height=500, StartPosition="CenterScreen", Gui.SplitContainer { Dock="Fill", Width=700, SplitterDistance=200, Gui.TreeView{ Name="treeviewCategory", Dock="Fill", HideSelection=false }, Gui.Panel{ Dock="Fill", Gui.ListView{ Name="listviewEntries", Dock="Fill", View="Details", GridLines=true, FullRowSelect=true, ContextMenuStrip=Gui.ContextMenuStrip { Gui.ToolStripMenuItem { Text="Delete", Click=function(sender,e) DeleteEntry() end } } }, Gui.StatusStrip { Name="statusStrip", Dock="Bottom" } } }, Gui.ToolStrip{ Dock="Top", Top=0, Left=0, Width=700, Height=25, Gui.ToolStripButton { Name="btnOpen", Text="&Open", Width=88, Height=22, Image="icon\\open.png" }, Gui.ToolStripButton { Name="btnSave", Text="&Save", Width=88, Height=22, Image="icon\\save.png" }, Gui.ToolStripButton { Name="btnAdd", Text="&Add", Width=88, Height=22, Image="icon\\add.png" }, } } incomeNode = treeviewCategory.Nodes.Add("Income") outgoNode = treeviewCategory.Nodes.Add("Outgo") listviewEntries.Columns.Add(Gui.ColumnHeader{ Text="Date", Width=100 }) listviewEntries.Columns.Add(Gui.ColumnHeader{ Text="Detail", Width=260 }) listviewEntries.Columns.Add(Gui.ColumnHeader{ Text="Amount", Width=120 }) dialog = Gui.Form{ Text="Add Entry", Width=320, Height=220, StartPosition="CenterParent", FormBorderStyle="FixedDialog", ShowInTaskbar = false; Gui.Label{ Text="Subject:", Width=60, Height=17, Top=14, Left=12 }, Gui.RadioButton { Name="dialog_Income", Text="Income", Width=80, Height=20, Top=9, Left=80 }, Gui.RadioButton { Name="dialog_Outgo", Text="Outgo", Width=80, Height=20, Top=9, Left=160, Checked=true }, Gui.Label{ Text="Category:", Width=60, Height=17, Top=40, Left=12 }, Gui.ComboBox { Name="dialog_Category", Width=160, Height=20, Top=36, Left=80 }, Gui.Label{ Text="Detail:", Width=60, Height=17, Top=68, Left=12 }, Gui.TextBox { Name="dialog_Detail", Width=160, Height=20, Top=64, Left=80 }, Gui.Label{ Text="Amount:", Width=60, Height=17, Top=96, Left=12 }, Gui.TextBox { Name="dialog_Amount", Width=128, Height=20, Top=92, Left=80 }, Gui.Label{ Text="Date:", Width=60, Height=17, Top=128, Left=12 }, Gui.DateTimePicker { Name="dialog_Date", Width=128, Height=21, Top=124, Left=80, Format="Short" }, Gui.Button{ Text="OK", Name="dialog_btnOK", Width=80, Height=23, Top=156, Left=130, DialogResult="OK" }, Gui.Button{ Text="Cancel", Name="dialog_btnCancel", Width=80, Height=23, Top=156, Left=224, DialogResult="Cancel" }, AcceptButton=dialog_btnOK, CancelButton=dialog_btnCancel } Entries = {} btnAdd.Click = function (sender,e) dialog_Detail.Text = "" dialog_Amount.Text = "" if treeviewCategory.SelectedNode ~= nil and treeviewCategory.SelectedNode.Tag ~= nil then dialog_Category.Text = treeviewCategory.SelectedNode.Text end if dialog.ShowDialog(form) == "OK" then local subject = dialog_Income.Checked and "income" or "outgo" local category = dialog_Category.Text local detail = dialog_Detail.Text local amount = dialog_Amount.Text local date = dialog_Date.Value.ToShortDateString() local entry = {date, subject, category, detail, amount} table.insert(Entries, entry) local categoryNode = UpdateCategoryTree(entry) if treeviewCategory.SelectedNode == categoryNode then AddEntryToListView(entry) else treeviewCategory.SelectedNode = categoryNode end end end function FindCategoryNode(entry) local subject = entry[2] local category = entry[3] local subjectNode = subject == "outgo" and outgoNode or incomeNode local subNodes = subjectNode.Nodes.Find(category, false) if #subNodes == 0 then return nil, subjectNode else return subNodes[1], subjectNode end end function UpdateCategoryTree(entry) local categoryNode, subjectNode = FindCategoryNode(entry) if categoryNode == nil then local category = entry[3] categoryNode = subjectNode.Nodes.Add(category, category) categoryNode.Tag = {} dialog_Category.Items.Add(category) end table.insert(categoryNode.Tag, entry) return categoryNode end treeviewCategory.AfterSelect = function (sender, e) local entries = treeviewCategory.SelectedNode.Tag if entries ~= nil then listviewEntries.Items.Clear() for _,entry in ipairs(entries) do AddEntryToListView(entry) end end end function AddEntryToListView(entry) local item = Gui.ListViewItem{ Text=entry[1] } item.SubItems.Add(entry[4]) item.SubItems.Add(entry[5]) item.Tag = entry listviewEntries.Items.Add(item) end function DeleteEntry() local item = listviewEntries.SelectedItems[1] local entry = item.Tag if entry ~= nil then local categoryNode = FindCategoryNode(entry) table.removeitem(categoryNode.Tag, entry) table.removeitem(Entries, entry) listviewEntries.Items.Remove(item) end end btnSave.Click = function (sender, e) local sfd = Gui.SaveFileDialog{ Title="Save data", Filter="data file(*.dat)|*.dat" } if sfd.ShowDialog() == "OK" then local file = io.open(sfd.FileName, "w") file:write("Entries = {\r\n") for _,entry in ipairs(Entries) do file:write('{"', table.concat(entry, '", "'), '"},\r\n') end file:write('}') file:close() end end btnOpen.Click = function (sender, e) local ofd = Gui.OpenFileDialog{ Title="Open data file", Filter="data file(*.dat)|*.dat" } if ofd.ShowDialog() == "OK" then dofile(ofd.FileName) incomeNode.Nodes.Clear() outgoNode.Nodes.Clear() dialog_Category.Items.Clear() for _,entry in ipairs(Entries) do UpdateCategoryTree(entry) end treeviewCategory.ExpandAll() listviewEntries.Items.Clear() end end Gui.Run(form)
The Ledger Sheet form:
The Add Entry dialog:
Tips to run Lua code file
The first method is in Visual Studio project properties, set file name as command line parameter in Debug tab page.
In this way the lua code can be debugged indirectly through the execution of interpreter.
The second method is in Windows file explorer, drag .lua file to lua.exe, it will start lua.exe and pass .lua file as a parameter, the same works for .wlua file and wlua.exe.
The third method is when you ship the software, compile the interpreter will lua code file path hard coded.
Matters need attention
The possible usage of this project is use Lua as a data description language or a script language for a .net application. Currently the code is just enough for a demo, many features are incomplete and untested, you may need to do it by yourself if you want to include the Lua interpreter in a large project.
History
2011-07-19 Initial release.
发表评论
sLkCjL You made some decent points there. I looked on the internet for the topic and found most persons will agree with your blog.
aIKEHu This is one awesome blog.Thanks Again. Awesome.
7XSVvm Thanks a lot for the post. Fantastic.
nz3G5g Enjoyed every bit of your blog.Much thanks again. Really Great.
Olkdkf Really informative article.Much thanks again.
Hello. And Bye.
collecting fashionable things is not always that easy for everyone. A perfect appearance shoes matters much. But those are exclusive to catch your eyes and are also
good!
Hello. And Bye.