diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..d701b72 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,9 @@ +( + (nil . + ( + (c-basic-offset . 2) + (tab-width . 8) + (indent-tabs-mode . nil) + ) + ) + ) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1aaa013 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +.* +*.ipynb +# Emacs backups # +################# +*~ +\#*\# +.\#* + +# Vim swap # +*.swp +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db + +# Emacs Clang autocompletion # +############################## +.clang-completion-error + +# Global tags # +############### +GPATH +GRTAGS +GSYMS +GTAGS diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..422f6a7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "external/lua-llthreads2"] + path = external/lua-llthreads2 + url = https://github.com/moteus/lua-llthreads2.git +[submodule "external/lzmq"] + path = external/lzmq + url = https://github.com/zeromq/lzmq.git + branch = master diff --git a/IPyLua/bokeh.lua b/IPyLua/bokeh.lua new file mode 100644 index 0000000..e0e4115 --- /dev/null +++ b/IPyLua/bokeh.lua @@ -0,0 +1,1606 @@ +local json = require "IPyLua.dkjson" +local uuid = require "IPyLua.uuid" +local html_template = require "IPyLua.html_template" +local null = json.null +local type = luatype or type + +local DEF_ALPHA = 0.8 +local DEF_BOX_WIDTH = 0.5 +local DEF_BREAKS = 20 +local DEF_HEIGTH = 6 +local DEF_LEVEL = "__nil__" +local DEF_LINE_WIDTH = 2 +local DEF_SIZE = 6 +local DEF_VIOLIN_WIDTH=0.90 +local DEF_WIDTH = 6 +local DEF_X = 0 +local DEF_XGRID = 100 +local DEF_Y = 0 +local DEF_YGRID = 100 +local EPSILON = 1e-06 + +local COLORS = { + -- "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#000000", + "#ff0000", "#00ff00", "#0000ff", "#ff00ff", "#00ffff", "#000000", + "#800000", "#008000", "#000080", "#808000", "#800080", "#008080", "#808080", + "#c00000", "#00c000", "#0000c0", "#c0c000", "#c000c0", "#00c0c0", "#c0c0c0", + "#400000", "#004000", "#000040", "#404000", "#400040", "#004040", "#404040", + "#200000", "#002000", "#000020", "#202000", "#200020", "#002020", "#202020", + "#600000", "#006000", "#000060", "#606000", "#600060", "#006060", "#606060", + "#a00000", "#00a000", "#0000a0", "#a0a000", "#a000a0", "#00a0a0", "#a0a0a0", + "#e00000", "#00e000", "#0000e0", "#e0e000", "#e000e0", "#00e0e0", "#e0e0e0", +} + +local math_abs = math.abs +local math_floor = math.floor +local math_min = math.min +local math_max = math.max + +local figure = {} +local figure_methods = {} + +-- forward declarations +local box_transformation +local hist2d_transformation +local violin_transformation +local linear_color_transformer +local linear_size_transformer +------------------------------ + +local pi4 = math.pi/4.0 +local function cor2angle(c) + if c < 0 then return pi4 else return -pi4 end +end + +local function cor2width(c) + return math_max(EPSILON, 1.0 - math.abs(c)) +end + +local function quantile(tbl, q) + local N = #tbl + local pos = (N+1)*q + local pos_floor,pos_ceil,result = math.floor(pos),math.ceil(pos) + local result + if pos_floor == 0 then + return tbl[1] + elseif pos_floor >= N then + return tbl[N] + else + local dec = pos - pos_floor + local a,b = tbl[pos_floor],tbl[pos_ceil] + return a + dec * (b - a) + end +end + +local function take_outliers(tbl, upper, lower) + local result = {} + for i=1,#tbl do + local x = tbl[i] + if x < lower or x > upper then result[#result+1] = x end + end + return result +end + +local function round(val) + if val > 0 then + return math.floor(val + 0.5) + end + return -math.floor(-val + 0.5) +end + +local function default_true(value) + if value == nil then return true else return value end +end + +local function reduce(t, func) + local out = t[1] + for i=2,#t do out = func(out, t[i]) end + return out +end + +local function min(t) + local t = t or 0.0 + local tt = type(t) + if tt == "table" or tt == "userdata" then + return reduce(t, math_min) + else + return t + end +end + +local function max(t) + local t = t or 0.0 + local tt = type(t) + if tt == "table" or tt == "userdata" then + return reduce(t, math_max) + else + return t + end +end + +local function minmax(t, size) + local t = t or 0.0 + local size = size or 0.0 + local tmin,tmax + local tt = type(t) + if tt == "table" or tt == "userdata" then + if type(size) == "table" or type(size) == "userdata" then + local s = size[1] * 0.5 + tmin = t[1] - s + tmax = t[1] + s + for i=2,#t do + local s = size[i] * 0.5 + tmin = math_min(tmin, t[i] - s) + tmax = math_max(tmax, t[i] + s) + end + else + assert(type(size) == "number", + "Needs a series or number as second parameter") + local s = size * 0.5 + tmin = t[1] - s + tmax = t[1] + s + for i=2,#t do + tmin = math_min(tmin, t[i] - s) + tmax = math_max(tmax, t[i] + s) + end + end + else + local s = max(size) * 0.5 + tmin = t - s + tmax = t + s + end + return tmin,tmax +end + +local function factors(t, out, dict) + local out,dict = out or {},dict or {} + if type(t) == "table" or type(t) == "userdata" then + for i=1,#t do + local s = tostring(t[i]) + if not dict[s] then + out[#out+1],dict[s] = s,true + end + end + else + local s = tostring(t) + if not dict[s] then + out[#out+1],dict[s] = s,true + end + end + return out,dict +end + +local function apply_gap(p, a, b) + local gap = p * (b-a) + return a-gap, b+gap +end + +local function tomatrix(m) + local tt = type(m) + if tt == "table" then return m end + if tt == "userdata" then + mt = getmetatable(m) + if mt.__index and mt.__len then return m end + end + error("Improper data type") +end + +local function toseries(s) + collectgarbage("collect") + if s==nil then return nil end + local tt = type(s) + if tt == "table" or tt == "userdata" then + local mt = getmetatable(s) + if mt and mt.ipylua_toseries then + return mt.ipylua_toseries(s) + elseif tt == "table" then + return s + else + error(("series data type %s cannot be handled: " ):format(tt)) + end + else + return s + end +end + +local function invert(t) + local r = {} + for k,v in pairs(t) do r[v] = k end + return r +end + +local function compute_optim(x, DEF) + local optim + local x = toseries(x) + if not x or type(x) == "number" then + optim = DEF + elseif type(x) == "string" then + optim = 1.0 + else + if type(x[1]) == "string" then + optim = 1.0 + else + local t = {} + for i=1,#x do t[i] = x[i] end table.sort(t) + optim = math.huge + for i=2,#t do optim = math_min( optim, math_abs( t[i-1] - t[i] ) ) end + end + end + return optim +end + +-- error checking + +local function check_equal_sizes(...) + local arg = table.pack(...) + local N = #arg[1] + for i=2,#arg do + assert(N == #arg[i], "all the given seires shoud be equal in size") + end +end + +local function check_table(t, ...) + local inv_list = invert({...}) + for i,v in pairs(t) do + assert(inv_list[i], ("unknown field %s"):format(i)) + end +end + +local function check_value(t, k, ...) + if t[k] then + local inv_list = invert({...}) + local v = t[k] + assert(inv_list[v], ("invalid value %s at field %s"):format(v,k)) + end +end + +local function check_type(t, k, ty) + local tt = type(t[k]) + assert(t[k] == nil or tt == ty, + ("type %s is not valid for field %s, expected %s"):format(tt, k, ty)) +end + +local function check_types(t, keys, types) + assert(#keys == #types) + for i=1,#keys do + local k,ty = keys[i],types[i] + check_type(t, k, ty) + end +end + +local function check_mandatory(t, key) + assert(t[key], ("field %s is mandatory"):format(key)) +end + +local function check_mandatories(t, ...) + for _,key in ipairs(table.pack(...)) do + check_mandatory(t, key) + end +end + +-- private functions + +local function create_data_columns(data, more_data) + local N + local s_data,columns = {},{} + + local function process(k, v) + local v = toseries(v) + local tt = type(v) + if tt == "table" or tt == "userdata" then + local M = #v + assert(not N or N==M, "Found different series sizes") + N = M + s_data[k] = v + table.insert(columns, k) + end + end + + for k,v in pairs(data) do process( k, v ) end + for k,v in pairs(more_data) do process( k, v ) end + + return s_data,columns,s_more_data +end + +local function create_simple_glyph_attributes(data, more_data, translate) + local attributes = { tags={}, doc=null } + for _,tbl in ipairs{ data, more_data } do + for k,v in pairs(tbl) do + local units + local tt = type(v) + if k == "height" or k == "width" then units = "data" end + if tt == "table" or tt == "userdata" then + attributes[ translate[k] or k ] = { units = units, field = k } + else + attributes[ translate[k] or k ] = { units = units, value = v } + end + end + end + return attributes +end + +local function next_color(self) + self._color_number = (self._color_number + 1) % #self._colors + local color = self._colors[ self._color_number + 1] + return color +end + +local function check_axis_range(self) + local glyphs = self._glyphs + + if not self._doc.attributes.x_range then + local axis = self._doc.attributes.below[1] or self._doc.attributes.above[1] + if axis.type == "LinearAxis" then + local x_min,x_max = math.huge,-math.huge + for _,source in ipairs(self._sources) do + local s_min,s_max = minmax(source.attributes.data.x or + (glyphs[source.id].attributes.x or {}).value, + source.attributes.data.width or + (glyphs[source.id].attributes.width or {}).value) + + x_min = math.min(x_min,s_min) + x_max = math.max(x_max,s_max) + end + self:x_range( apply_gap(0.05, x_min, x_max) ) + else + local list,dict = {},{} + for _,source in ipairs(self._sources) do + list,dict = factors(source.attributes.data.x or + (glyphs[source.id].attributes.x or {}).value, + list, dict) + end + self:x_range(list) + end + end + + if not self._doc.attributes.y_range then + local axis = self._doc.attributes.left[1] or self._doc.attributes.right[1] + if axis.type == "LinearAxis" then + local y_min,y_max = math.huge,-math.huge + for _,source in ipairs(self._sources) do + local s_min,s_max = minmax(source.attributes.data.y or + (glyphs[source.id].attributes.y or {}).value, + source.attributes.data.height or + (glyphs[source.id].attributes.height or {}).value) + + y_min = math.min(y_min, s_min) + y_max = math.max(y_max, s_max) + end + self:y_range( apply_gap(0.05, y_min, y_max) ) + else + local list,dict + for _,source in ipairs(self._sources) do + list,dict = factors(source.attributes.data.y or + (glyphs[source.id].attributes.y or {}).value, + list, dict) + end + self:y_range(list) + end + end +end + +local function add_source(self, source) + table.insert(self._sources, source) +end + +local function add_reference(self, id, tbl) + self._references[id] = self._references[id] or {} + table.insert(self._references[id], tbl) +end + +local function update_references(self, id, obj) + for _,ref in ipairs(self._references[id] or {}) do + for k,v in pairs(ref) do + ref[k] = assert( obj[k] ) + end + end +end + +local function compile_glyph(self, i) + i = i or #self._list + self._list[i] = json.encode( self._list[i] ) +end + +local function append_renderer(self, ref) + table.insert( self._doc.attributes.renderers, ref ) + return ref +end + +local function add_simple_glyph(self, name, attributes, subtype, source_ref) + local id = uuid.new() + local list = self._list + attributes.id = id + local glyph = { + attributes = attributes, + type = name, + id = id, + } + self._dict[id] = glyph + local ref = { type = name, subtype = subtype, id = id, } + add_reference(self, id, ref) + list[#list+1] = glyph + if source_ref then self._glyphs[source_ref.id] = glyph end + return ref,glyph +end + + +local function add_column_data_source(self, data, columns) + local attributes = { + tags = {}, + doc = null, + selected = { + ["2d"] = { indices = {} }, + ["1d"] = { indices = {} }, + ["0d"] = { indices = {}, flag=false }, + }, + callback = null, + data = data, + column_names = columns, + } + local source_ref,source = add_simple_glyph(self, "ColumnDataSource", + attributes) + add_source(self, source) + + return source_ref +end + +local function append_source_renderer(self, source_ref, glyph_ref) + local attributes = { + nonselection_glyph = null, + data_source = source_ref, + tags = {}, + doc = null, + selection_glyph = null, + glyph = glyph_ref, + } + return append_renderer(self, add_simple_glyph(self, "GlyphRenderer", + attributes) ) +end + +local function add_legend(self, legend, renderer_ref) + if not self._legend then + local attributes = { + tags = {}, + doc = null, + plot = self._docref, + legends = { { legend, { renderer_ref } } }, + } + local ref + ref,self._legend = add_simple_glyph(self, "Legend", attributes) + return append_renderer(self, ref) + else + table.insert(self._legend.attributes.legends, { legend, { renderer_ref } }) + end +end + +local function add_tool(self, name, attributes) + attributes.plot = self._docref + local tools = self._doc.attributes.tools + tools[#tools+1] = add_simple_glyph(self, name, attributes) +end + +local function add_axis(self, key, params) + check_mandatories(params, "pos", "type") + + local formatter_type = "BasicTickFormatter" + local ticker_type = "BasicTicker" + + if params.type == "CategoricalAxis" then + formatter_type = "CategoricalTickFormatter" + ticker_type = "CategoricalTicker" + end + + local doc_axis = self._doc.attributes[params.pos] + if not doc_axis[1] then + + local formatter_ref,formatter = add_simple_glyph(self, formatter_type, + { tags={}, doc=null }) + + local ticker_ref,ticker = + add_simple_glyph(self, ticker_type, + { tags={}, doc=null, mantissas={2, 5, 10}, + num_minor_ticks=params.num_minor_ticks }) + + local axis_ref,axis = add_simple_glyph(self, params.type, + { + tags={}, + doc=null, + axis_label=params.label, + plot = self._docref, + formatter = formatter_ref, + ticker = ticker_ref, + } + ) + + local dim = (key == "x") and 0 or 1 + append_renderer(self, add_simple_glyph(self, "Grid", + { tags={}, doc=null, dimension=dim, + ticker=ticker_ref, + plot = self._docref, })) + append_renderer(self, axis_ref) + + doc_axis[1] = axis_ref + else + local axis = self._dict[ doc_axis[1].id ] + if params.label then axis.attributes.axis_label = params.label end + if params.type then + axis.type = params.type + local formatter_id = axis.attributes.formatter.id + local ticker_id = axis.attributes.ticker.id + local formatter = self._dict[ formatter_id ] + local ticker = self._dict[ ticker_id ] + formatter.type = formatter_type + ticker.type = ticker_type + update_references(self, formatter_id, formatter) + update_references(self, ticker_id, ticker) + end + update_references(self, axis.id, axis) + end +end + +local function axis_range(self, a, b, key) + local range = key .. "_range" + local glyph + if not self._doc.attributes[range] then + local ref + ref,glyph = add_simple_glyph(self, "DUMMY", + { callback = null, doc = null, tags = {} }) + self._doc.attributes[range] = ref + else + glyph = self._dict[ self._doc.attributes[range].id ] + end + if type(a) == "table" then + assert(not b, "expected one table of factors or two numbers (min, max)") + glyph.type = "FactorRange" + glyph.attributes.factors = a + glyph.attributes["start"] = nil + glyph.attributes["end"] = nil + else + assert(type(a) == "number" and type(b) == "number", + "expected one table of factors or two numbers (min, max)") + glyph.type = "Range1d" + glyph.attributes.factors = nil + glyph.attributes["start"] = a + glyph.attributes["end"] = b + end + update_references(self, glyph.id, glyph) +end + +local function tool_events(self) + self._doc.attributes.tool_events = + add_simple_glyph( + self, "ToolEvents", + { + geometries = {}, + tags = {}, + doc = null, + } + ) +end + +-- figure class implementation + +local figure_methods = { + + -- axis + + x_axis = function(self, params) -- type, label, pos, log, grid, num_minor_ticks, visible, number_formatter + params = params or {} + check_table(params, "type", "label", "pos", "log", "grid", "num_minor_ticks", + "visible", "number_formatter") + check_value(params, "type", "LinearAxis", "CategoricalAxis") + check_value(params, "pos", "below", "above") + check_types(params, + {"log","grid","num_minor_ticks","visible"}, + {"boolean","boolean","number","boolean"}) + + add_axis(self, "x", params) + return self + end, + + y_axis = function(self, params) -- type, label, pos, log, grid, num_minor_ticks, visible, number_formatter + params = params or {} + check_table(params, "type", "label", "pos", "log", "grid", "num_minor_ticks", + "visible", "number_formatter") + check_value(params, "type", "LinearAxis", "CategoricalAxis") + check_value(params, "pos", "left", "right") + check_types(params, + {"log","grid","num_minor_ticks","visible"}, + {"boolean","boolean","number","boolean"}) + + add_axis(self, "y", params) + return self + end, + + x_range = function(self, a, b) + axis_range(self, a, b, "x") + return self + end, + + y_range = function(self, a, b) + axis_range(self, a, b, "y") + return self + end, + + crop_points = function(self, xmin, xmax, ymin, ymax) + for _,source in ipairs(self._sources) do + local data = source.attributes.data + local new_data = { + x = {}, + y = {}, + } + local other_than_xy = {} + for k,v in pairs(data) do + if k~="x" and k~="y" then + if type(v) == "table" then + new_data[k] = {} + table.insert(other_than_xy, k) + else + new_data[k] = v + end + end + end + local j=1 + for i=1,#data.x do + local x,y = data.x[i],data.y[i] + if xmin<=x and x<=xmax and ymin<=y and y<=ymax then + new_data.x[j] = x + new_data.y[j] = y + for _,k in ipairs(other_than_xy) do + new_data[k][j] = data[k][i] + end + j=j+1 + end + end + source.attributes.data = new_data + end + return self + end, + + -- tools + + tool_box_select = function(self, select_every_mousemove) + select_every_mousemove = default_true( select_every_mousemove ) + add_tool(self, "BoxSelectTool", { select_every_mousemove=select_every_mousemove, tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_box_zoom = function(self, dimensions) -- { "width", "height" } + dimensions = dimensions or { "width", "height" } + add_tool(self, "BoxZoomTool", { dimensions=dimensions, tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_crosshair = function(self) + add_tool(self, "CrossHair", { tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_hover = function(self, params) -- always_active, tooltips + params = params or {} + check_table(params, "always_active", "tooltips") + local always_active = default_true( params.always_active ) + local tooltips = params.tooltips or + { + {"Tag", "@hover"}, + {"(x,y)", "($x, $y)"} + } + add_tool(self, "HoverTool", { tags={}, + doc=null, + callback=null, + always_active=always_active, + name=null, + names={}, + plot=self._docref, + point_policy="follow_mouse", + renderers={}, + tooltips=tooltips }) + compile_glyph(self) + return self + end, + + tool_lasso_select = function(self, select_every_mousemove) + select_every_mousemove = default_true( select_every_mousemove ) + add_tool(self, "LassoSelectTool", { select_every_mousemove=select_every_mousemove, tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_pan = function(self, dimensions) -- { "width", "height" } + dimensions = dimensions or { "width", "height" } + add_tool(self, "PanTool", { dimensions=dimensions, tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_reset = function(self) + add_tool(self, "ResetTool", { tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_resize = function(self) + add_tool(self, "ResizeTool", { tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_save = function(self) + add_tool(self, "PreviewSaveTool", { tags={}, doc=null }) + compile_glyph(self) + return self + end, + + tool_wheel_zoom = function(self, dimensions) -- { "width", "height" } + dimensions = dimensions or { "width", "height" } + add_tool(self, "WheelZoomTool", { dimensions=dimensions, tags={}, doc=null }) + compile_glyph(self) + return self + end, + + -- color functions + + color_last = function(self) + return self._colors[ self._color_number + 1 ] + end, + + color_num = function(self, i) + assert(i>0 and i<=#self._colors, + ("color number out-of-bounds [1,%d]"):format(#self._colors)) + return self._colors[i] + end, + + -- layer functions + + boxes = function(self, params) -- min, max, q1, q2, q3, x, outliers, width, alpha, legend, color + check_table(params, "min", "max", "q1", "q2", "q3", "x", + "outliers", "width", "alpha", "legend", "color") + + check_mandatories(params, "min", "max", "q1", "q2", "q3", "x") + + local x = params.x + local min = params.min + local max = params.max + local q1 = params.q1 + local q2 = params.q2 + local q3 = params.q3 + local outliers = params.outliers + local width = params.width or DEF_BOX_WIDTH + local alpha = params.alpha or DEF_ALPHA + + local box_height = {} + local box_mid = {} + local line_height = {} + local line_mid = {} + local max_height = 0 + for i=1,#x do + box_mid[i] = (q1[i] + q3[i])/2.0 + box_height[i] = q3[i] - q1[i] + line_mid[i] = (max[i] + min[i])/2.0 + line_height[i] = max[i] - min[i] + if box_height[i] > max_height then max_height = box_height[i] end + end + + local color = params.color or next_color(self) + + self:bars{ x = x, + y = line_mid, + height = line_height, + width = width * 0.005, + alpha = alpha, + color = color, + line_color = "#000000", } + + self:bars{ x = x, + y = box_mid, + height = box_height, + width = width, + color = color, + alpha = alpha, + legend = params.legend, + line_color = "#000000", } + + self:bars{ x = x, + y = q2, + height = 0.005 * max_height, + width = width, + alpha = alpha, + color = "#000000", } + + self:bars{ x = x, + y = min, + height = 0.005 * max_height, + width = width * 0.3, + alpha = alpha, + color = color, + line_color = "#000000", } + + self:bars{ x = x, + y = max, + height = 0.005 * max_height, + width = width * 0.3, + alpha = alpha, + color = color, + line_color = "#000000", } + + -- FIXME: check sizes + if outliers then + for i=1,#x do + if outliers[i] and #outliers[i] > 0 then + local list = {} + for j=1,#outliers[i] do list[j] = x[i] end + self:points{ x = list, + y = outliers[i], + color = color, + alpha = alpha, } + --xgrid = 1, } + end + end + end + + self:x_axis{ type="CategoricalAxis", pos="below" } + + return self + end, + + boxplot = function(self, params) -- x, y, legend, alpha, color, factors, width, ignore_outliers + + local boxes = + box_transformation( params, + { + legend=params.legend, + alpha=params.alpha, + color=params.color, + } + ) + + return self:boxes( boxes ) + + end, + + lines = function(self, params) -- x, y, color, alpha, width, legend, more_data + params = params or {} + check_table(params, "x", "y", "color", "alpha", "width", "legend", "more_data") + check_mandatories(params, "x", "y") + local x = params.x + local y = params.y + local color = params.color or next_color(self) + local alpha = params.alpha or DEF_ALPHA + local width = params.width or DEF_LINE_WIDTH + + local data = { x=x, y=y, width=width, alpha=alpha, color=color } + local more_data = params.more_data or {} + + local s_data,s_columns,s_more_data = create_data_columns(data, more_data) + + local source_ref = add_column_data_source(self, s_data, s_columns) + + local attributes = + create_simple_glyph_attributes(data, more_data, + { color="line_color", + alpha="line_alpha", + width="line_width", }) + + local lines_ref = add_simple_glyph(self, "Line", attributes, nil, source_ref) + + local renderer_ref = append_source_renderer(self, source_ref, lines_ref) + + if params.legend then add_legend(self, params.legend, renderer_ref) end + + return self + end, + + bars = function(self, params) -- x, y, width, height, color, alpha, legend, hover, more_data + params = params or {} + check_table(params, "x", "height", "width", "y", "color", "alpha", + "legend", "hover", "more_data", "line_color") + check_mandatories(params, "x") + local x = params.x or DEF_X + local y = params.y or DEF_Y + local width = params.width or "auto" + local height = params.height or "auto" + local color = params.color or next_color(self) + local alpha = params.alpha or DEF_ALPHA + local hover = params.hover + local line_color = params.line_color + + if width == "auto" then width = compute_optim(x, DEF_WIDTH) end + if height == "auto" then height = compute_optim(y, DEF_HEIGTH) end + + local data = { + x=x, + y=y, + width=width, + height=height, + fill_alpha=alpha, + color=color, + hover=hover, + line_color=line_color, + } + local more_data = params.more_data or {} + + local s_data,s_columns,s_more_data = create_data_columns(data, more_data) + + local source_ref = add_column_data_source(self, s_data, s_columns) + + local attributes = + create_simple_glyph_attributes(data, more_data, + { color="fill_color" }) + + local lines_ref = add_simple_glyph(self, "Rect", attributes, nil, source_ref) + + local renderer_ref = append_source_renderer(self, source_ref, lines_ref) + + if params.legend then add_legend(self, params.legend, renderer_ref) end + + return self + end, + + corplot = function(self, params) -- xy, names, alpha, legend + params = params or {} + check_table(params, "xy", "names", "alpha", "legend") + check_mandatories(params, "xy") + local alpha = params.alpha or DEF_ALPHA + local hover = params.hover + + local names = params.names or {} + local xy = tomatrix( params.xy ) + + local bars_x = {} + local bars_y = {} + local bars_cor = {} + + local ovals_x = {} + local ovals_y = {} + local ovals_w = {} + local ovals_h = {} + local ovals_cor = {} + local ovals_angle = {} + local text = {} + local text_angle = {} + + local N = #xy + for i=N,1,-1 do + local row = toseries( xy[i] ) + assert(#row == N, "Needs a squared matrix as input") + for j=1,#row do + local c = row[j] + if j>i then + local a = cor2angle(c) + table.insert(ovals_x, names[j] or j) + table.insert(ovals_y, names[i] or i) + table.insert(ovals_angle, a) + table.insert(ovals_w, 0.9 * cor2width(c)) + table.insert(ovals_cor, c) + table.insert(text, ("%.3f"):format(c)) + table.insert(text_angle, -a) + else + table.insert(bars_x, names[j] or j) + table.insert(bars_y, names[i] or i) + table.insert(bars_cor, c) + end + end + end + + local bars_data = { + x=bars_x, + y=bars_y, + width=1.0, + height=1.0, + alpha=alpha, + color=linear_color_transformer(bars_cor, -1.0, 1.0), + hover=bars_cor, + legend=params.legend, + } + bars_data.line_color = bars_data.color + + self:bars( bars_data ) + + local ovals_data = { + x=ovals_x, + y=ovals_y, + alpha=alpha, + color=linear_color_transformer(ovals_cor, -1.0, 1.0), + legend=params.legend, + glyph="Oval", + more_data = { + angle=ovals_angle, + width=ovals_w, + height=0.9, + } + } + + self:points( ovals_data ) + + local text_data = { + x = ovals_x, + y = ovals_y, + color = "#000000", + glyph = "Text", + alpha = 1.0, + more_data = { + angle = text_angle, + text = text, + text_font_style="bold", + text_color = "#000000", + text_alpha = 1.0, + text_align = "center", + text_baseline = "middle", + } + } + + self:points( text_data ) + + if names then + self:x_axis{ type="CategoricalAxis", pos="below" } + self:y_axis{ type="CategoricalAxis", pos="left" } + end + + return self + end, + + points = function(self, params) -- x, y, glyph, color, alpha, size, legend, hover, more_data + params = params or {} + check_table(params, "x", "y", "glyph", "color", "alpha", "size", + "legend", "hover", "more_data") + check_value(params, "glyph", "Circle", "Triangle", "Oval", "Text") + check_mandatories(params, "x", "y") + local x = params.x + local y = params.y + local color = params.color or next_color(self) + local alpha = params.alpha or DEF_ALPHA + local size = params.size or DEF_SIZE + local hover = params.hover + + local data = { + x=x, + y=y, + color = color, + fill_alpha = alpha, + size = size, + hover = hover, + } + local more_data = params.more_data or {} + + local s_data,s_columns = create_data_columns(data, more_data) + + local source_ref = add_column_data_source(self, s_data, s_columns) + + local attributes = + create_simple_glyph_attributes(data, more_data, + { color="fill_color", }) + + local points_ref = add_simple_glyph(self, params.glyph or "Circle", + attributes, nil, source_ref) + + local renderer_ref = append_source_renderer(self, source_ref, points_ref) + + if params.legend then add_legend(self, params.legend, renderer_ref) end + + return self + end, + + hist2d = function(self, params) -- x, y, glyph, color, alpha, size, legend, xgrid, ygrid + params = params or {} + local hist2d = hist2d_transformation( + { x = params.x, + y = params.y, + minsize = params.minsize, + maxsize = params.maxsize, + xgrid = params.xgrid, + ygrid = params.ygrid, + }, + { + glyph = params.glyph, + color = params.color, + alpha = params.alpha, + legend = params.legend, + } + ) + return self:points( hist2d ) + end, + + vioplot = function(self, params) -- x, y, legend, alpha, color, factors, width + + local color = params.color or next_color(self) + + local violins = + violin_transformation(params, + { + alpha = params.alpha, + legend = params.legend, + color = color, + line_color = color, + } + ) + + for i=1,#violins.bars do + self:bars(violins.bars[i]) + end + + self:boxes(violins.boxes) + + self:x_axis{ type="CategoricalAxis", pos="below" } + + return self + end, + + -- conversion + + to_json = function(self) + check_axis_range(self) + local doc_json = json.encode(self._doc) + local tbl = { doc_json } + for i=2,#self._list do + local v = self._list[i] + if type(v) ~= "string" then v = json.encode(v) end + tbl[#tbl+1] = v + end + collectgarbage("collect") + return ("[%s]"):format(table.concat(tbl, ",")) + end, + +} + +local figure_mt = { + __index = figure_methods, + + ipylua_show = function(self) + local html = html_template:gsub("$([A-Z]+)", + { + SCRIPTID = uuid.new(), + MODELTYPE = self._doc.type, + MODELID = self._doc.id, + MODEL = self:to_json(), + } + ) + return { + ["text/html"] = html, + ["text/plain"] = "-- impossible to show ASCII art plots", + } + end, +} + +setmetatable( + figure, + { + __call = function(_,params) -- tools, height, title, xlab, ylab, xlim, ylim, padding_factor, xgrid, ygrid, xaxes, yaxes, tooltips + params = params or {} + + local function default(name, value) + params[name] = params[name] or value + end + + default("tools", { "pan", "wheel_zoom", "box_zoom", "resize", + "reset", "save", "hover" }) + default("width", 500) + default("height", 400) + default("title", nil) -- not necessary but useful to make it explicit + default("xlab", nil) + default("ylab", nil) + default("xlim", nil) + default("ylim", nil) + default("padding_factor", 0.07) + default("xgrid", true) + default("ygrid", true) + default("xaxes", {"below"}) + default("yaxes", {"left"}) + default("tooltips", nil) + -- ??? default("theme", "bokeh_theme") ??? + + local self = { _list = {}, _references={}, _dict = {}, + _color_number = #COLORS - 1, _colors = COLORS, + _sources = {}, _glyphs = {} } + setmetatable(self, figure_mt) + + self._docref,self._doc = + add_simple_glyph(self, "Plot", + { + plot_width = params.width, + plot_height = params.height, + title = params.title, + -- + x_range = nil, + y_range = nil, + extra_x_ranges = {}, + extra_y_ranges = {}, + id = plot_id, + tags = {}, + title_text_font_style = "bold", + title_text_font_size = { value = "12pt" }, + tools = {}, + renderers = {}, + below = {}, + above = {}, + left = {}, + right = {}, + responsive = false, + }, + "Chart") + + tool_events(self) + + for _,name in ipairs(params.tools) do + if name == "hover" then + self["tool_" .. name](self, { tooltips = params.tooltips }) + else + self["tool_" .. name](self) + end + end + + for _,pos in ipairs(params.xaxes) do + self:x_axis{ label=params.xlab, log=false, grid=params.xgrid, + num_minor_ticks=5, visible=true, number_formatter=tostring, + type="LinearAxis", pos=pos } + end + + for _,pos in ipairs(params.yaxes) do + self:y_axis{ label=params.ylab, log=false, grid=params.ygrid, + num_minor_ticks=5, visible=true, number_formatter=tostring, + type="LinearAxis", pos=pos } + end + + return self + end + } +) + +-- data transformers + +local function hist(x, breaks, output_type, scale) + local scale = scale or 1.0 + local min = x[1] + local max = x[#x] + local diff = max - min + if min == max then + return { y={min}, bins={#x}, width={scale}, height={1e-20} } + end + assert(diff > 0, "Unable to compute histogram for given data") + local inc = diff / breaks + local half = inc * 0.5 + local bins = {} + local width = {} + local y = {} + local max = 0.0 + for i=1,breaks do + bins[i] = 0.0 + y[i] = (i - 1.0) * inc + half + min + end + for i=1,#x do + local b = math_floor( (x[i] - min)/diff * breaks ) + 1.0 + b = math_max(0.0, math_min(breaks, b)) + bins[b] = bins[b] + 1.0 + max = math_max(max, bins[b]) + end + for i=1,#bins do width[i] = (bins[i]/max)*scale end + if output_type == "ratio" then + local scale = scale or 1.0 + local N = #x for i=1,#bins do bins[i] = (bins[i]/N) end + elseif scale then + for i=1,#bins do bins[i] = bins[i] end + end + return { y=y, width=width, height=inc, bins=bins, } +end + +-- +function box_transformation(params, more_params) -- x, y, factors, width, ignore_outliers + local x = toseries( params.x ) + local y = toseries( params.y ) + + assert(type(y) == "table" or type(y) == "userdata") + + local boxes = { + min = {}, + max = {}, + q1 = {}, + q2 = {}, + q3 = {}, + outliers = {}, + width = {}, + x = {}, + } + + local DEF_LEVEL = DEF_LEVEL + local levels + if not params.factors then + if type(x) == "table" or type(x) == "userdata" then + levels = factors(x) + else + DEF_LEVEL = x or DEF_LEVEL + levels = { DEF_LEVEL } + end + else + local aux = params.factors + levels = {} + for i=1,#aux do levels[i] = tostring(aux[i]) end + end + + local plt = {} + for i,factor in ipairs(levels) do plt[factor] = {} end + + for i=1,#y do + local key = x and x[i] and tostring(x[i]) or DEF_LEVEL + assert( plt[key], "found unknown factor level " .. key ) + table.insert( plt[key], y[i] ) + end + + for i,factor in ipairs(levels) do + + local cur = plt[factor] + table.sort(cur) + + local q1 = quantile(cur, 0.25) + local q2 = quantile(cur, 0.50) + local q3 = quantile(cur, 0.75) + local IQ = q3 - q1 + + local min = params.min or quantile(cur, 0.0) + local max = params.max or quantile(cur, 1.0) + + local upper = math.min(q3 + 1.5 * IQ, max) + local lower = math.max(q1 - 1.5 * IQ, min) + + local outliers + if not params.ignore_outliers then + outliers = take_outliers(cur, upper, lower) + end + + boxes.min[i] = upper + boxes.max[i] = lower + boxes.q1[i] = q1 + boxes.q2[i] = q2 + boxes.q3[i] = q3 + boxes.outliers[i] = outliers + boxes.width = params.width or DEF_BOX_WIDTH + boxes.x[i] = tostring( factor ) + + end + + for k,v in pairs(more_params or {}) do + assert(k ~= "more_data", "Unable to handle more_data argument") + assert(not boxes[k], "Unable to redefine parameter " .. k) + boxes[k]=v + end + + return boxes +end + +-- local hist2d_transformation is declared at the top of this file +function hist2d_transformation(params, more_params) -- x, y, minsize, maxsize, xgrid, ygrid + params = params or {} + check_table(params, "x", "y", "maxsize", "minsize", "xgrid", "ygrid") + check_mandatories(params, "x", "y") + check_types(params, + { "minsize", "maxsize" }, + { "number", "number" }) + local x = toseries(params.x) + local y = toseries(params.y) + local maxsize = params.maxsize or 2*DEF_SIZE + local minsize = params.minsize or 0.5*DEF_SIZE + local xgrid = params.xgrid or DEF_XGRID + local ygrid = params.ygrid or DEF_YGRID + assert(minsize <= maxsize, "failure of predicate minsize < maxsize") + check_equal_sizes(x, y) + local x_min,x_max = assert( minmax(x) ) + local y_min,y_max = assert( minmax(y) ) + local x_width,y_width,x_off,y_off + if xgrid == 1 or x_max == x_min then + x_width = math.max(1.0, x_max - x_min) + x_off = 0.0 + else + x_width = (x_max - x_min) / (xgrid-1) + x_off = x_width*0.5 + end + if ygrid == 1 or y_max == y_min then + y_width = math.max(1.0, y_max - y_min) + y_off = 0.0 + else + y_width = (y_max - y_min) / (ygrid-1) + y_off = y_width*0.5 + end + local grid = {} + for i=1,xgrid*ygrid do grid[i] = 0 end + local max_count = 0 + for i=1,#x do + local ix = math.floor((x[i]-x_min)/x_width) + local iy = math.floor((y[i]-y_min)/y_width) + local k = iy*xgrid + ix + 1 + grid[k] = grid[k] + 1 + if grid[k] > max_count then max_count= grid[k] end + end + local size_diff = maxsize - minsize + local new_x,new_y,new_sizes,counts,ratios = {},{},{},{},{} + local l=1 + for i=0,xgrid-1 do + for j=0,ygrid-2 do + local k = j*xgrid + i + 1 + if grid[k] > 0 then + local ratio = grid[k]/max_count + new_x[l] = i*x_width + x_min + x_off + new_y[l] = j*y_width + y_min + y_off + new_sizes[l] = minsize + size_diff * ratio + counts[l] = grid[k] + ratios[l] = ratio + l = l + 1 + end + end + end + + local result = { + x = new_x, + y = new_y, + size = new_sizes, + more_data = { + count = counts, + ratio = ratios, + }, + } + for k,v in pairs(more_params or {}) do + assert(k ~= "more_data", "Unable to handle more_data argument") + result[k]=v + end + + return result +end + +function violin_transformation(params, more_params) -- x, y, factors, width, breaks + local breaks = params.breaks or DEF_BREAKS + local width = params.width or DEF_VIOLIN_WIDTH + local more_params = more_params or {} + local x = toseries( params.x ) + local y = toseries( params.y ) + + assert(type(y) == "table" or type(y) == "userdata") + + local violins = { + bars = {}, + boxes = { + min = {}, + max = {}, + q1 = {}, + q2 = {}, + q3 = {}, + outliers = {}, + width = {}, + x = {}, + }, + } + + local DEF_LEVEL = DEF_LEVEL + local levels + if not params.factors then + if type(x) == "table" or type(x) == "userdata" then + levels = factors(x) + else + DEF_LEVEL = x or DEF_LEVEL + levels = { DEF_LEVEL } + end + else + local aux = params.factors + levels = {} + for i=1,#aux do levels[i] = tostring(aux[i]) end + end + + local plt = {} + for i,factor in ipairs(levels) do plt[factor] = {} end + + for i=1,#y do + local key = x and x[i] and tostring(x[i]) or DEF_LEVEL + assert( plt[key], "found unknown factor level " .. key ) + table.insert( plt[key], y[i] ) + end + + for i,factor in ipairs(levels) do + local cur = plt[factor] + table.sort(cur) + + local bars = violins.bars + local h = hist(cur, breaks, "ratio", width) + + bars[i] = { + x = tostring( factor ), + y = h.y, + width = h.width, + height = h.height, + hover = h.bins, + } + + local boxes = violins.boxes + local q1 = quantile(cur, 0.25) + local q2 = quantile(cur, 0.50) + local q3 = quantile(cur, 0.75) + local IQ = q3 - q1 + + local min = quantile(cur, 0.0) + local max = quantile(cur, 1.0) + + local upper = math.min(q3 + 1.5 * IQ, max) + local lower = math.max(q1 - 1.5 * IQ, min) + + boxes.x[i] = tostring( factor ) + boxes.min[i] = upper + boxes.max[i] = lower + boxes.q1[i] = q1 + boxes.q2[i] = q2 + boxes.q3[i] = q3 + boxes.width = width * 0.05 + boxes.alpha = 1.0 + boxes.color = more_params.color + + for k,v in pairs(more_params) do + assert(k ~= "more_data", "Unable to handle more_data argument") + assert(not bars[i][k], "Unable to redefine parameter " .. k) + bars[i][k]=v + end + end + + return violins +end + +-- color transformers + +-- http://www.andrewnoske.com/wiki/Code_-_heatmaps_and_color_gradients +function linear_color_transformer(x, xmin, xmax) + local x = toseries(x) + assert(type(x) == "table", "needs a series as input") + if not xmin and xmax then xmin = min(x) end + if not xmax and xmin then xmax = max(x) end + local min,max = xmin,xmax + if not min and not max then min,max = minmax(x) end + local diff = max-min + local color = {} + -- 4 colors: blue, green, yellow, red + local COLORS = { {0,0,255}, {0,255,0}, {255,255,0}, {255,0,0} } + for i=1,#x do + local idx1,idx2 -- our desired color will be between these two indexes in COLORS + local fract = 0 -- fraction between idx1 and idx2 + local v = (x[i] - min) / diff + if v <= 0 then + idx1,idx2 = 1,1 + elseif v >= 1 then + idx1,idx2 = #COLORS,#COLORS + else + v = v * (#COLORS-1) + idx1 = math.floor(v) + 1 + idx2 = idx1+1 + fract = v - (idx1 - 1) + end + local r = round( (COLORS[idx2][1] - COLORS[idx1][1])*fract + COLORS[idx1][1] ) + local g = round( (COLORS[idx2][2] - COLORS[idx1][2])*fract + COLORS[idx1][2] ) + local b = round( (COLORS[idx2][3] - COLORS[idx1][3])*fract + COLORS[idx1][3] ) + color[i] = ("#%02x%02x%02x"):format(r, g, b) + end + return color +end + +function linear_size_transformer(x, smin, smax) + local x = toseries(x) + assert(type(x) == "table", "needs a series as 1st argument") + local smin,smax = smin or DEF_SIZE*0.5, smax or DEF_SIZE*2.0 + local sdiff = smax - smin + local min,max = minmax(x) + local diff = max-min + local size = {} + for i=1,#x do + local v = (x[i] - min) / diff + size[i] = v*sdiff + smin + end + return size +end + +return { + figure = figure, + colors = { + linear = linear_color_transformer, + }, + sizes = { + linear = linear_size_transformer, + }, + transformations = { + box = box_transformation, + hist2d = hist2d_transformation, + }, +} diff --git a/IPyLua/dkjson.lua b/IPyLua/dkjson.lua new file mode 100644 index 0000000..2515b92 --- /dev/null +++ b/IPyLua/dkjson.lua @@ -0,0 +1,605 @@ +--[[ + + David Kolf's JSON module for Lua 5.1/5.2 + + Version 2.5 + + + For the documentation see the corresponding readme.txt or visit + . + + You can contact the author by sending an e-mail to 'david' at the + domain 'dkolf.de'. + + + Copyright (C) 2010-2013 David Heiko Kolf + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +--]] + +-- Use original module in case it has been loaded with a previous require or it +-- has been preload by the user +local json = package.loaded["dkjson"] or package.preload["dkjson"] +if json then return json end + +-- global dependencies: +local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = + pairs, luatype or type, tostring, tonumber, getmetatable, setmetatable, rawset +local error, require, pcall, select = error, require, pcall, select +local floor, huge = math.floor, math.huge +local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = + string.rep, string.gsub, string.sub, string.byte, string.char, +string.find, string.len, string.format +local strmatch = string.match +local concat = table.concat + +local json = { version = "dkjson 2.5" } + +local _ENV = nil -- blocking globals in Lua 5.2 + +pcall (function() + -- Enable access to blocked metatables. + -- Don't worry, this module doesn't change anything in them. + local debmeta = require "debug".getmetatable + if debmeta then getmetatable = debmeta end +end) + +json.null = setmetatable ({}, { + __tojson = function () return "null" end +}) + +local function is_array_userdata(v) + if type(v) == "userdata" then + local mt = getmetatable(v) + if mt.__len and type( mt.__index ) == "function" then return true end + end + return false +end + +local function isarray (tbl) + if type(tbl) == "userdata" then return true,#tbl end + local max, n, arraylen = 0, 0, 0 + for k,v in pairs (tbl) do + if k == 'n' and type(v) == 'number' then + arraylen = v + if v > max then + max = v + end + else + if type(k) ~= 'number' or k < 1 or floor(k) ~= k then + return false + end + if k > max then + max = k + end + n = n + 1 + end + end + if max > 10 and max > arraylen and max > n * 2 then + return false -- don't create an array with too many holes + end + return true, max +end + +local escapecodes = { + ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", + ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" +} + +local function escapeutf8 (uchar) + local value = escapecodes[uchar] + if value then + return value + end + local a, b, c, d = strbyte (uchar, 1, 4) + a, b, c, d = a or 0, b or 0, c or 0, d or 0 + if a <= 0x7f then + value = a + elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then + value = (a - 0xc0) * 0x40 + b - 0x80 + elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then + value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 + elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then + value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 + else + return "" + end + if value <= 0xffff then + return strformat ("\\u%.4x", value) + elseif value <= 0x10ffff then + -- encode as UTF-16 surrogate pair + value = value - 0x10000 + local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) + return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) + else + return "" + end +end + +local function fsub (str, pattern, repl) + -- gsub always builds a new string in a buffer, even when no match + -- exists. First using find should be more efficient when most strings + -- don't contain the pattern. + if strfind (str, pattern) then + return gsub (str, pattern, repl) + else + return str + end +end + +local function quotestring (value) + -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js + value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) + if strfind (value, "[\194\216\220\225\226\239]") then + value = fsub (value, "\194[\128-\159\173]", escapeutf8) + value = fsub (value, "\216[\128-\132]", escapeutf8) + value = fsub (value, "\220\143", escapeutf8) + value = fsub (value, "\225\158[\180\181]", escapeutf8) + value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) + value = fsub (value, "\226\129[\160-\175]", escapeutf8) + value = fsub (value, "\239\187\191", escapeutf8) + value = fsub (value, "\239\191[\176-\191]", escapeutf8) + end + return "\"" .. value .. "\"" +end +json.quotestring = quotestring + +local function replace(str, o, n) + local i, j = strfind (str, o, 1, true) + if i then + return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) + else + return str + end +end + +-- locale independent num2str and str2num functions +local decpoint, numfilter + +local function updatedecpoint () + decpoint = strmatch(tostring(0.5), "([^05+])") + -- build a filter that can be used to remove group separators + numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" +end + +updatedecpoint() + +local function num2str (num) + return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") +end + +local function str2num (str) + local num = tonumber(replace(str, ".", decpoint)) + if not num then + updatedecpoint() + num = tonumber(replace(str, ".", decpoint)) + end + return num +end + +local function addnewline2 (level, buffer, buflen) + buffer[buflen+1] = "\n" + buffer[buflen+2] = strrep (" ", level) + buflen = buflen + 2 + return buflen +end + +function json.addnewline (state) + if state.indent then + state.bufferlen = addnewline2 (state.level or 0, + state.buffer, state.bufferlen or #(state.buffer)) + end +end + +local encode2 -- forward declaration + +local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) + local kt = type (key) + if kt ~= 'string' and kt ~= 'number' then + return nil, "type '" .. kt .. "' is not supported as a key by JSON." + end + if prev then + buflen = buflen + 1 + buffer[buflen] = "," + end + if indent then + buflen = addnewline2 (level, buffer, buflen) + end + buffer[buflen+1] = quotestring (key) + buffer[buflen+2] = ":" + return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) +end + +local function appendcustom(res, buffer, state) + local buflen = state.bufferlen + if type (res) == 'string' then + buflen = buflen + 1 + buffer[buflen] = res + end + return buflen +end + +local function exception(reason, value, state, buffer, buflen, defaultmessage) + defaultmessage = defaultmessage or reason + local handler = state.exception + if not handler then + return nil, defaultmessage + else + state.bufferlen = buflen + local ret, msg = handler (reason, value, state, defaultmessage) + if not ret then return nil, msg or defaultmessage end + return appendcustom(ret, buffer, state) + end +end + +function json.encodeexception(reason, value, state, defaultmessage) + return quotestring("<" .. defaultmessage .. ">") +end + +encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) + local valtype = type (value) + local valmeta = getmetatable (value) + valmeta = type (valmeta) == 'table' and valmeta -- only tables + local valtojson = valmeta and valmeta.__tojson + if valtojson then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + state.bufferlen = buflen + local ret, msg = valtojson (value, state) + if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end + tables[value] = nil + buflen = appendcustom(ret, buffer, state) + elseif value == nil then + buflen = buflen + 1 + buffer[buflen] = "null" + elseif valtype == 'number' then + local s + if value ~= value or value >= huge or -value >= huge then + -- This is the behaviour of the original JSON implementation. + s = "null" + else + s = num2str (value) + end + buflen = buflen + 1 + buffer[buflen] = s + elseif valtype == 'boolean' then + buflen = buflen + 1 + buffer[buflen] = value and "true" or "false" + elseif valtype == 'string' then + buflen = buflen + 1 + buffer[buflen] = quotestring (value) + elseif valtype == 'table' or is_array_userdata(value) then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + level = level + 1 + local isa, n = isarray (value) + if n == 0 and valmeta and valmeta.__jsontype == 'object' then + isa = false + end + local msg + if isa then -- JSON array + buflen = buflen + 1 + buffer[buflen] = "[" + for i = 1, n do + buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + if i < n then + buflen = buflen + 1 + buffer[buflen] = "," + end + end + buflen = buflen + 1 + buffer[buflen] = "]" + else -- JSON object + local prev = false + buflen = buflen + 1 + buffer[buflen] = "{" + local order = valmeta and valmeta.__jsonorder or globalorder + if order then + local used = {} + n = #order + for i = 1, n do + local k = order[i] + local v = value[k] + if v then + used[k] = true + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + prev = true -- add a seperator before the next element + end + end + for k,v in pairs (value) do + if not used[k] then + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + else -- unordered + for k,v in pairs (value) do + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + if indent then + buflen = addnewline2 (level - 1, buffer, buflen) + end + buflen = buflen + 1 + buffer[buflen] = "}" + end + tables[value] = nil + else + return exception ('unsupported type', value, state, buffer, buflen, + "type '" .. valtype .. "' is not supported by JSON.") + end + return buflen +end + +function json.encode (value, state) + state = state or {} + local oldbuffer = state.buffer + local buffer = oldbuffer or {} + state.buffer = buffer + updatedecpoint() + local ret, msg = encode2 (value, state.indent, state.level or 0, + buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) + if not ret then + error (msg, 2) + elseif oldbuffer == buffer then + state.bufferlen = ret + return true + else + state.bufferlen = nil + state.buffer = nil + return concat (buffer) + end +end + +local function loc (str, where) + local line, pos, linepos = 1, 1, 0 + while true do + pos = strfind (str, "\n", pos, true) + if pos and pos < where then + line = line + 1 + linepos = pos + pos = pos + 1 + else + break + end + end + return "line " .. line .. ", column " .. (where - linepos) +end + +local function unterminated (str, what, where) + return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) +end + +local function scanwhite (str, pos) + while true do + pos = strfind (str, "%S", pos) + if not pos then return nil end + local sub2 = strsub (str, pos, pos + 1) + if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then + -- UTF-8 Byte Order Mark + pos = pos + 3 + elseif sub2 == "//" then + pos = strfind (str, "[\n\r]", pos + 2) + if not pos then return nil end + elseif sub2 == "/*" then + pos = strfind (str, "*/", pos + 2) + if not pos then return nil end + pos = pos + 2 + else + return pos + end + end +end + +local escapechars = { + ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", + ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" +} + +local function unichar (value) + if value < 0 then + return nil + elseif value <= 0x007f then + return strchar (value) + elseif value <= 0x07ff then + return strchar (0xc0 + floor(value/0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0xffff then + return strchar (0xe0 + floor(value/0x1000), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0x10ffff then + return strchar (0xf0 + floor(value/0x40000), + 0x80 + (floor(value/0x1000) % 0x40), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + else + return nil + end +end + +local function scanstring (str, pos) + local lastpos = pos + 1 + local buffer, n = {}, 0 + while true do + local nextpos = strfind (str, "[\"\\]", lastpos) + if not nextpos then + return unterminated (str, "string", pos) + end + if nextpos > lastpos then + n = n + 1 + buffer[n] = strsub (str, lastpos, nextpos - 1) + end + if strsub (str, nextpos, nextpos) == "\"" then + lastpos = nextpos + 1 + break + else + local escchar = strsub (str, nextpos + 1, nextpos + 1) + local value + if escchar == "u" then + value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) + if value then + local value2 + if 0xD800 <= value and value <= 0xDBff then + -- we have the high surrogate of UTF-16. Check if there is a + -- low surrogate escaped nearby to combine them. + if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then + value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) + if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then + value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 + else + value2 = nil -- in case it was out of range for a low surrogate + end + end + end + value = value and unichar (value) + if value then + if value2 then + lastpos = nextpos + 12 + else + lastpos = nextpos + 6 + end + end + end + end + if not value then + value = escapechars[escchar] or escchar + lastpos = nextpos + 2 + end + n = n + 1 + buffer[n] = value + end + end + if n == 1 then + return buffer[1], lastpos + elseif n > 1 then + return concat (buffer), lastpos + else + return "", lastpos + end +end + +local scanvalue -- forward declaration + +local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) + local len = strlen (str) + local tbl, n = {}, 0 + local pos = startpos + 1 + if what == 'object' then + setmetatable (tbl, objectmeta) + else + setmetatable (tbl, arraymeta) + end + while true do + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + local char = strsub (str, pos, pos) + if char == closechar then + return tbl, pos + 1 + end + local val1, err + val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + if char == ":" then + if val1 == nil then + return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" + end + pos = scanwhite (str, pos + 1) + if not pos then return unterminated (str, what, startpos) end + local val2 + val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + tbl[val1] = val2 + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + else + n = n + 1 + tbl[n] = val1 + end + if char == "," then + pos = pos + 1 + end + end +end + +scanvalue = function (str, pos, nullval, objectmeta, arraymeta) + pos = pos or 1 + pos = scanwhite (str, pos) + if not pos then + return nil, strlen (str) + 1, "no valid JSON value (reached the end)" + end + local char = strsub (str, pos, pos) + if char == "{" then + return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) + elseif char == "[" then + return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) + elseif char == "\"" then + return scanstring (str, pos) + else + local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) + if pstart then + local number = str2num (strsub (str, pstart, pend)) + if number then + return number, pend + 1 + end + end + pstart, pend = strfind (str, "^%a%w*", pos) + if pstart then + local name = strsub (str, pstart, pend) + if name == "true" then + return true, pend + 1 + elseif name == "false" then + return false, pend + 1 + elseif name == "null" then + return nullval, pend + 1 + end + end + return nil, pos, "no valid JSON value at " .. loc (str, pos) + end +end + +local function optionalmetatables(...) + if select("#", ...) > 0 then + return ... + else + return {__jsontype = 'object'}, {__jsontype = 'array'} + end +end + +function json.decode (str, pos, nullval, ...) + local objectmeta, arraymeta = optionalmetatables(...) + return scanvalue (str, pos, nullval, objectmeta, arraymeta) +end + +return json diff --git a/IPyLua/dom_builder.lua b/IPyLua/dom_builder.lua new file mode 100644 index 0000000..059097d --- /dev/null +++ b/IPyLua/dom_builder.lua @@ -0,0 +1,50 @@ +local pyget = pyget +local dom_builder = {} + +local element_mt = { + __tostring = function(self) return "<"..self.tag..">" end, + ipylua_show = function(self) + local tbl = { "<", self.tag } + for k,v in pairs(tbl) do + if type(k)~="number" and k~="tag" then + tbl[#tbl+1] = ('%s="%s"'):format(k,tostring(v)) + end + end + tbl[#tbl+1] = ">" + for i=1,#self do + local data + local tt = type(self[i]) + if tt == "string" or tt == "number" then + data = { ["text/html"] = self[i] } + else + data = pyget(self[i]) + end + if data["image/png"] then + tbl[#tbl+1] = (''):format(data["image/png"]) + elseif data["text/html"] then + tbl[#tbl+1] = data["text/html"] + else + tbl[#tbl+1] = assert( data["text/plain"] ) + end + end + tbl[#tbl+1] = "" + return { ["text/html"] = table.concat(tbl) } + end, +} +local function element(tag,params) + local t = {} for k,v in pairs(params or {}) do t[k] = v end + t.tag = tag + return setmetatable(t, element_mt) +end + +setmetatable(dom_builder,{ + __index = function(_,tag) + return function(params) + return element(tag,params) + end + end, +}) + +return dom_builder diff --git a/IPyLua/html_template.lua b/IPyLua/html_template.lua new file mode 100644 index 0000000..2ed903a --- /dev/null +++ b/IPyLua/html_template.lua @@ -0,0 +1,56 @@ +return [[ +]] diff --git a/IPyLua/rlcompleter.lua b/IPyLua/rlcompleter.lua new file mode 100644 index 0000000..5ae719b --- /dev/null +++ b/IPyLua/rlcompleter.lua @@ -0,0 +1,214 @@ +--[[ + IPyLua + + Copyright (c) 2015 Francisco Zamora-Martinez. Simplified, less deps and making + it work. + + https://github.com/pakozm/IPyLua + + Released under the MIT License, see the LICENSE file. + + usage: lua IPyLuaKernel.lua CONNECTION_FILENAME +--]] + +-- This file is based on the work of Patrick Rapin, adapted by Reuben Thomas: +-- https://github.com/rrthomas/lua-rlcompleter/blob/master/rlcompleter.lua + +local ok,lfs = pcall(require, "lfs") if not ok then lfs = nil end + +local type = luatype or type + +-- The list of Lua keywords +local keywords = { + 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', + 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while' +} + +local word_break_chars_pattern = " \t\n\"\\'><=%;%:%+%-%*%/%%%^%~%#%{%}%(%)%[%]%.%," +local last_word_pattern = ("[%s]?([^%s]*)$"):format(word_break_chars_pattern, + word_break_chars_pattern) +local endpos_pattern = ("(.*)[%s]?"):format(word_break_chars_pattern) + +-- Returns index_table field from a metatable, which is a copy of __index table +-- in APRIL-ANN +local function get_index(mt) + if type(mt.__index) == "table" then return mt.__index end + return mt.index_table -- only for APRIL-ANN +end + +-- This function needs to be called to complete current input line +local function do_completion(line, text, cursor_pos, env_G, env, _G) + local line = line:sub(1,cursor_pos):match("([^\n]*)[\n]?$") + -- Extract the last word. + local word=line:match( last_word_pattern ) or "" + local startpos,endpos=1,#(line:match(endpos_pattern) or "") + + -- Helper function registering possible completion words, verifying matches. + local matches_set = {} + local matches = {} + local function add(value) + value = tostring(value) + if not matches_set[value] then + if value:match("^" .. word) then + matches_set[value] = true + matches[#matches + 1] = value + end + end + end + + local filenames_path + -- This function does the same job as the default completion of readline, + -- completing paths and filenames. + local function filename_list(str) + if lfs then + local path, name = str:match("(.*)[\\/]+(.*)") + path = (path or ".") .. "/" + filenames_path = path + if str:sub(1,1) == "/" then path = "/" .. path end + name = name or str + if not lfs.attributes(path) then return end + for f in lfs.dir(path) do + if (lfs.attributes(path .. f) or {}).mode == 'directory' then + add(f .. "/") + else + add(f) + end + end + end + end + + -- This function is called in a context where a keyword or a global + -- variable can be inserted. Local variables cannot be listed! + local function add_globals() + for _, k in ipairs(keywords) do add(k) end + for i,tbl in ipairs{env, env_G, _G} do + for k in pairs(tbl) do add(k) end + end + end + + -- Main completion function. It evaluates the current sub-expression + -- to determine its type. Currently supports tables fields, global + -- variables and function prototype completion. + local function contextual_list(expr, sep, str) + if str then + return filename_list(str) + end + if expr and expr ~= "" then + local v = load("return " .. expr, nil, nil, env) + if v then + v = v() + local t = type(v) + if sep == '.' or sep == ':' then + if t == 'table' then + for k, v in pairs(v) do + if type(k) == 'string' and (sep ~= ':' or type(v) == "function") then + add(k) + end + end + end + if (t == 'string' or t == 'table' or t == 'userdata') and getmetatable(v) then + local aux = v + repeat + local mt = getmetatable(aux) + local idx = get_index(mt) + if idx and type(idx) == 'table' then + for k,v in pairs(idx) do + add(k) + end + end + if rawequal(aux,idx) then break end -- avoid infinite loops + aux = idx + until not aux or not getmetatable(aux) + end + elseif sep == '[' then + if t == 'table' then + for k in pairs(v) do + if type(k) == 'number' then + add(k .. "]") + end + end + if word ~= "" then add_globals() end + end + end + end + end + if #matches == 0 then + if not sep or sep~="." then add_globals() end + end + end + + -- This complex function tries to simplify the input line, by removing + -- literal strings, full table constructors and balanced groups of + -- parentheses. Returns the sub-expression preceding the word, the + -- separator item ( '.', ':', '[', '(' ) and the current string in case + -- of an unfinished string literal. + local function simplify_expression(expr) + -- Replace annoying sequences \' and \" inside literal strings + expr = expr:gsub("\\(['\"])", function (c) + return string.format("\\%03d", string.byte(c)) + end) + local curstring + -- Remove (finished and unfinished) literal strings + while true do + local idx1, _, equals = expr:find("%[(=*)%[") + local idx2, _, sign = expr:find("(['\"])") + if idx1 == nil and idx2 == nil then + break + end + local idx, startpat, endpat + if (idx1 or math.huge) < (idx2 or math.huge) then + idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]" + else + idx, startpat, endpat = idx2, sign, sign + end + if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then + expr = expr:gsub(startpat .. "(.-)" .. endpat, " STRING ") + else + expr = expr:gsub(startpat .. "(.*)", function (str) + curstring = str + return "(CURSTRING " + end) + end + end + expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses + expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors + -- Avoid two consecutive words without operator + expr = expr:gsub("(%w)%s+(%w)","%1|%2") + expr = expr:gsub("%s+", "") -- Remove now useless spaces + -- This main regular expression looks for table indexes and function calls. + return curstring, expr:match("([%.%w%[%]_]-)([:%.%[%(])" .. word .. "$") + end + + -- Now call the processing functions and return the list of results. + local str, expr, sep = simplify_expression(line:sub(1, endpos)) + contextual_list(expr, sep, str) + table.sort(matches) + -- rebuild the list of matches to prepend all required characters + if text ~= "" then + if filenames_path then + expr = filenames_path + sep = "" + elseif sep ~= "." then + expr = line:match("([^%(%)%[%]%,%:><=%+%-%*%^'\\\"])*"..word.."$") + sep = "" + end + if expr then + local prefix = expr .. (sep or "") + word = prefix .. word + for i,v in ipairs(matches) do matches[i] = prefix .. v end + end + end + return { + status = "ok", + matches = matches, + matched_text = word, + -- cursor_start = 1, + -- cusor_end = cursor_pos, + metadata = {}, + } +end + +return { + do_completion = do_completion, +} diff --git a/IPyLua/uuid.lua b/IPyLua/uuid.lua new file mode 100644 index 0000000..49951f1 --- /dev/null +++ b/IPyLua/uuid.lua @@ -0,0 +1,196 @@ +--------------------------------------------------------------------------------------- +-- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications) +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS-IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- see http://www.ietf.org/rfc/rfc4122.txt +-- +-- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard +-- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This +-- is solved by using the node field from a version 1 UUID. It represents the mac address. +-- +-- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module. +-- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket +-- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid). + +-- Use original module in case it has been loaded with a previous require or it +-- has been preload by the user +local M = package.loaded["uuid"] or package.preload["uuid"] +if M then return M end + +local M = {} +local math = require('math') +local os = require('os') +local string = require('string') + +local bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below. +local lua_version = tonumber(_VERSION:match("%d%.*%d*")) -- grab Lua version used + +local MATRIX_AND = {{0,0},{0,1} } +local MATRIX_OR = {{0,1},{1,1}} +local HEXES = '0123456789abcdef' + +-- performs the bitwise operation specified by truth matrix on two numbers. +local function BITWISE(x, y, matrix) + local z = 0 + local pow = 1 + while x > 0 or y > 0 do + z = z + (matrix[x%2+1][y%2+1] * pow) + pow = pow * 2 + x = math.floor(x/2) + y = math.floor(y/2) + end + return z +end + +local function INT2HEX(x) + local s,base = '',16 + local d + while x > 0 do + d = x % base + 1 + x = math.floor(x/base) + s = string.sub(HEXES, d, d)..s + end + while #s < 2 do s = "0" .. s end + return s +end + +---------------------------------------------------------------------------- +-- Creates a new uuid. Either provide a unique hex string, or make sure the +-- random seed is properly set. The module table itself is a shortcut to this +-- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`. +-- +-- For proper use there are 3 options; +-- +-- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no +-- parameter, eg. `my_uuid = uuid()` +-- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`, +-- and request a uuid using no parameter, eg. `my_uuid = uuid()` +-- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string, +-- eg. `my_uuid = uuid(my_networkcard_macaddress)` +-- +-- @return a properly formatted uuid string +-- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math.random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly! +-- @usage +-- local uuid = require("uuid") +-- print("here's a new uuid: ",uuid()) +function M.new(hwaddr) + -- bytes are treated as 8bit unsigned bytes. + local bytes = { + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + math.random(0, 255), + } + + if hwaddr then + assert(type(hwaddr)=="string", "Expected hex string, got "..type(hwaddr)) + -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters + local i,str, hwaddr = #hwaddr, hwaddr, "" + while i>0 and #hwaddr<12 do + local c = str:sub(i,i):lower() + if HEXES:find(c, 1, true) then + -- valid HEX character, so append it + hwaddr = c..hwaddr + end + i = i - 1 + end + assert(#hwaddr == 12, "Provided string did not contain at least 12 hex characters, retrieved '"..hwaddr.."' from '"..str.."'") + + -- no split() in lua. :( + bytes[11] = tonumber(hwaddr:sub(1, 2), 16) + bytes[12] = tonumber(hwaddr:sub(3, 4), 16) + bytes[13] = tonumber(hwaddr:sub(5, 6), 16) + bytes[14] = tonumber(hwaddr:sub(7, 8), 16) + bytes[15] = tonumber(hwaddr:sub(9, 10), 16) + bytes[16] = tonumber(hwaddr:sub(11, 12), 16) + end + + -- set the version + bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND) + bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR) + -- set the variant + bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND) + bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR) + return INT2HEX(bytes[1])..INT2HEX(bytes[2])..INT2HEX(bytes[3])..INT2HEX(bytes[4]).."-".. + INT2HEX(bytes[5])..INT2HEX(bytes[6]).."-".. + INT2HEX(bytes[7])..INT2HEX(bytes[8]).."-".. + INT2HEX(bytes[9])..INT2HEX(bytes[10]).."-".. + INT2HEX(bytes[11])..INT2HEX(bytes[12])..INT2HEX(bytes[13])..INT2HEX(bytes[14])..INT2HEX(bytes[15])..INT2HEX(bytes[16]) +end + +---------------------------------------------------------------------------- +-- Improved randomseed function. +-- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer +-- range. If this happens, the seed will be 0 or 1 and all randomness will +-- be gone (each application run will generate the same sequence of random +-- numbers in that case). This improved version drops the most significant +-- bits in those cases to get the seed within the proper range again. +-- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive) +-- @return the (potentially modified) seed used +-- @usage +-- local socket = require("socket") -- gettime() has higher precision than os.time() +-- local uuid = require("uuid") +-- -- see also example at uuid.seed() +-- uuid.randomseed(socket.gettime()*10000) +-- print("here's a new uuid: ",uuid()) +function M.randomseed(seed) + seed = math.floor(math.abs(seed)) + if seed >= (2^bitsize) then + -- integer overflow, so reduce to prevent a bad seed + seed = seed - math.floor(seed / 2^bitsize) * (2^bitsize) + end + if lua_version < 5.2 then + -- 5.1 uses (incorrect) signed int + math.randomseed(seed - 2^(bitsize-1)) + else + -- 5.2 uses (correct) unsigned int + math.randomseed(seed) + end + return seed +end + +---------------------------------------------------------------------------- +-- Seeds the random generator. +-- It does so in 2 possible ways; +-- +-- 1. use `os.time()`: this only offers resolution to one second (used when +-- LuaSocket hasn't been loaded yet +-- 2. use luasocket `gettime()` function, but it only does so when LuaSocket +-- has been required already. +-- @usage +-- local socket = require("socket") -- gettime() has higher precision than os.time() +-- -- LuaSocket loaded, so below line does the same as the example from randomseed() +-- uuid.seed() +-- print("here's a new uuid: ",uuid()) +function M.seed() + if package.loaded["socket"] and package.loaded["socket"].gettime then + return M.randomseed(package.loaded["socket"].gettime()*10000) + else + return M.randomseed(os.time()) + end +end + +return setmetatable( M, { __call = function(self, hwaddr) return self.new(hwaddr) end} ) diff --git a/IPyLuaKernel.lua b/IPyLuaKernel.lua new file mode 100644 index 0000000..d073a96 --- /dev/null +++ b/IPyLuaKernel.lua @@ -0,0 +1,978 @@ +--[[ + IPyLua + + Copyright (c) 2015 Francisco Zamora-Martinez. Simplified, less deps and making + it work. + + https://github.com/pakozm/IPyLua + + Original name and copyright: lua_ipython_kernel, + Copyright (c) 2013 Evan Wies. All rights reserved. + https://github.com/neomantra/lua_ipython_kernel + + Released under the MIT License, see the LICENSE file. + + usage: lua IPyLuaKernel.lua CONNECTION_FILENAME +--]] + +if #arg ~= 1 then + io.stderr:write(('usage: %s CONNECTION_FILENAME\n'):format(arg[0])) + os.exit(-1) +end + +local type = luatype or type + +local output_functions +local help_functions +local plot_functions +do + -- Setting IPyLua in the registry allow to extend this implementation with + -- specifications due to other Lua modules. For instance, APRIL-ANN uses this + -- variable to configure how to encode images, plots, matrices, ... As well + -- as introducing a sort of inline documentation. + -- + -- To extend IPyLua output you need to stack into registry + -- IPyLua.output_functions new functions which receive an object and return a + -- data table as expected by IPython, that is, a data table with pairs of { + -- [mime_type] = representation, ... } followed by the metadata table + -- (optional). + local reg = debug.getregistry() + reg.IPyLua = reg.IPyLua or {} + output_functions = reg.IPyLua.output_functions or {} + help_functions = reg.IPyLua.help_functions or {} + plot_functions = reg.IPyLua.plot_functions or {} + reg.IPyLua = { + output_functions = output_functions, + help_functions = help_functions, + plot_functions = plot_functions, + } +end + +local function lookup_function_for_object(obj, stack, ...) + for i=#stack,1,-1 do + local result = table.pack( pcall(stack[i], obj, ...) ) + if result[1] and result[2] then + return table.unpack(result, 2) + end + end +end + +local bokeh = require "IPyLua.bokeh" +local do_completion = require "IPyLua.rlcompleter".do_completion +local json = require "IPyLua.dkjson" +local zmq = require 'lzmq' +local zmq_poller = require 'lzmq.poller' +local zthreads = require "lzmq.threads" +local z_NOBLOCK, z_POLLIN = zmq.NOBLOCK, zmq.POLL_IN +local z_RCVMORE, z_SNDMORE = zmq.RCVMORE, zmq.SNDMORE +local zassert = zmq.assert + +local uuid = require 'IPyLua.uuid' -- TODO: randomseed or luasocket or something else + +local HMAC = '' +local MSG_DELIM = '' +local username = os.getenv('USER') or "unknown" + +-- our kernel's state +local kernel = { execution_count=0 } + +-- sockets description +local kernel_sockets + +local function next_execution_count() + kernel.execution_count = kernel.execution_count + 1 + return kernel.execution_count +end + +local function current_execution_count() + return kernel.execution_count +end + +-- load connection object info from JSON file +do + local connection_file = assert( io.open(arg[1]) ) + local connection_json = assert( connection_file:read('*a') ) + kernel.connection_obj = assert( json.decode(connection_json) ) + connection_file:close() +end + +------------------------------------------------------------------------------- +-- IPython Message ("ipmsg") functions + +local function ipmsg_to_table(parts) + local msg = { ids = {}, blobs = {} } + + local i = 1 + while i <= #parts and parts[i] ~= MSG_DELIM do + msg.ids[#msg.ids + 1] = parts[i] + i = i + 1 + end + i = i + 1 + msg.hmac = parts[i] ; i = i + 1 + msg.header = parts[i] ; i = i + 1 + msg.parent_header = parts[i] ; i = i + 1 + msg.metadata = parts[i] ; i = i + 1 + msg.content = parts[i] ; i = i + 1 + + while i <= #parts do + msg.blobs[#msg.blobs + 1] = parts[i] + i = i + 1 + end + + return msg +end + +local session_id = uuid.new() +local function ipmsg_header(msg_type) + return { + msg_id = uuid.new(), -- TODO: randomness warning: uuid.new() + msg_type = msg_type, + username = username, + session = session_id, + version = '5.0', + date = os.date("%Y-%m-%dT%H:%M:%S"), + } +end + + +local function ipmsg_send(sock, params) + local parent = params.parent or {header={}} + local ids = parent.ids or {} + local hdr = json.encode(assert( params.header )) + local meta = json.encode(params.meta or {}) + local content = json.encode(params.content or {}) + local blobs = params.blob + -- print("IPMSG_SEND") + -- print("\tHEADER", hdr) + -- print("\tCONTENT", content) + -- + local hmac = HMAC + local p_hdr = json.encode(parent.header) + -- + if type(ids) == 'table' then + for _, v in ipairs(ids) do + sock:send(v, z_SNDMORE) + end + else + sock:send(ids, z_SNDMORE) + end + sock:send(MSG_DELIM, z_SNDMORE) + sock:send(hmac, z_SNDMORE) + sock:send(hdr, z_SNDMORE) + sock:send(p_hdr, z_SNDMORE) + sock:send(meta, z_SNDMORE) + if blobs then + sock:send(content, z_SNDMORE) + if type(blobs) == 'table' then + for i, v in ipairs(blobs) do + if i == #blobs then + sock:send(v) + else + sock:send(v, z_SNDMORE) + end + end + else + sock:send(blobs) + end + else + sock:send(content) + end +end + + + +------------------------------------------------------------------------------- +local stringfy = function(v,use_quotes) + local v_str = tostring(v) + if type(v) == "string" and use_quotes then v_str = ("%q"):format(v) end + return not v_str:find("\n") and v_str or type(v) +end + +-- environment where all code is executed +local new_environment +local env_session +local env_parent +local env_source +do + local stdout_write = function(...) + local header = ipmsg_header( 'stream' ) + local content = { + name = 'stdout', + data = table.concat(table.pack(...)), + } + ipmsg_send(kernel.iopub_sock, { + session = env_session, + parent = env_parent, + header = header, + content = content + }) + end + + local stderr_write = function(...) + local header = ipmsg_header( 'stream' ) + local content = { + name = 'stderr', + data = table.concat(table.pack(...)), + } + ipmsg_send(kernel.iopub_sock, { + session = env_session, + parent = env_parent, + header = header, + content = content + }) + end + + local pyout = function(data, metadata) + local header = ipmsg_header( 'pyout' ) + local content = { + data = assert( data ), + execution_count = current_execution_count(), + metadata = metadata or {}, + } + ipmsg_send(kernel.iopub_sock, { + session = env_session, + parent = env_parent, + header = header, + content = content + }) + end + + local display_data = function(data, metadata) + local header = ipmsg_header( 'display_data' ) + local content = { + data = assert( data ), + source = 'stdin', + metadata = metadata or {}, + } + ipmsg_send(kernel.iopub_sock, { + session = env_session, + parent = env_parent, + header = header, + content = content + }) + end + + local MAX = 10 + local basic_output_function = function(obj, MAX) + local tt,footer = type(obj) + if (tt == "table" or tt == "userdata") then + local mt = getmetatable(obj) + if mt and mt.ipylua_show then + return mt.ipylua_show(obj) + + elseif mt and mt.__tostring then + local str = tostring(obj) + return { + ["text/plain"]=str.."\n", + ["text/html"]=('
%s
'):format(str), + } + + elseif tt == "table" then + local tbl = { "{" } + do + local max = false + for k,v in ipairs(obj) do + table.insert(tbl, ("\t[%d] = %s,"):format(k,stringfy(v,true))) + if k >= MAX then max=true break end + end + if max then table.insert(tbl, "\t...") end + end + do + local max = false + local keys = {} + for k,v in pairs(obj) do + if type(k) ~= "number" or k<1 or k>#obj then keys[#keys+1] = k end + end + table.sort(keys, function(a,b) return tostring(a) < tostring(b) end) + for i,k in ipairs(keys) do + table.insert(tbl, ("\t[%s] = %s,"):format(stringfy(k,true), + stringfy(obj[k],true))) + if i >= MAX then max=true break end + end + if max then table.insert(tbl, "\t...") end + footer = ("-- %s with %d array part, %d hash part"):format(tostring(obj), #obj, #keys) + end + table.insert(tbl, "}") + table.insert(tbl, footer) + local str = table.concat(tbl, "\n") + return { + ["text/plain"]=str.."\n", + ["text/html"]=('
%s
'):format(str), + } + end + + else + local str = tostring(obj) + return { + ["text/plain"]=str.."\n", + ["text/html"]=('
%s
'):format(str), + } + end + end + table.insert(output_functions, 1, basic_output_function) + + local basic_help_function = function(obj, verbosity) + local html = { + "

IPyLua help

", + "", + "", + (""):format(type(obj)), + } + + local plain = { + ("LuaType: %s\n"):format(type(obj)), + } + + if type(obj) == "function" then + local definition = {} + local info = debug.getinfo(obj) + if info.what == "Lua" and info.source and info.linedefined then + local source = info.source + local first = info.linedefined + local last = info.lastlinedefined + local iterator + if source:sub(1,1) == "@" then + iterator = table.pack( io.lines(source:sub(2)) ) + else + iterator = table.pack( source:gmatch("([^\r\n]+)") ) + end + local k=1 + for line in table.unpack( iterator ) do + if first == k then definition = line:match("^%s*(.*)%s*$") break end + k=k+1 + end + end + + if #definition == 0 then + definition = {"... = funcname","("} + if info.isvararg then + table.insert(definition, "...") + else + local args = {} + for i=1,info.nparams do table.insert(args, "arg"..i) end + table.insert(definition, table.concat(args,",")) + end + table.insert(definition, ")") + definition = table.concat(definition) + end + + table.insert(html, (""):format(definition)) + table.insert(html, (""):format(info.isvararg and "..." or info.nparams)) + table.insert(html, (""):format(info.what)) + table.insert(html, (""):format(info.nups)) + + table.insert(plain, ("Def.: %s\n"):format(definition)) + table.insert(plain, ("NumParams: %s\n"):format(info.isvararg and "..." or info.nparams)) + table.insert(plain, ("What: %s\n"):format(info.what)) + table.insert(plain, ("Nups: %s\n"):format(info.nups)) + + elseif type(obj) == "table" then + table.insert(html, (""):format(#obj)) + table.insert(plain, ("Length: %d\n"):format(#obj)) + + else + table.insert(html, (""):format(tostring(obj))) + table.insert(plain, ("ToString: %s\n"):format(tostring(obj))) + end + + table.insert(html, "
KeyValue
LuaType%s
Def.%s
NumParams%s
What%s
Nups%s
Length%d
ToString
%s
") + local data = { + ["text/html"] = table.concat(html), + ["text/plain"] = table.concat(plain), + } + return data + end + table.insert(help_functions, 1, basic_help_function) + + local function pcall_wrap(func,...) + local ok,msg = xpcall(func,debug.traceback,...) + if not ok then stderr_write(msg.."\n") return false end + return true + end + + local function show_obj(obj, MAX) + local data,metadata = lookup_function_for_object(obj, output_functions, MAX) + if data then pyout(data,metadata) return true end + return false + end + + local function help(obj, ...) + local data,metadata = lookup_function_for_object(obj, help_functions, ...) + if data then + pyout(data,metadata) + else + pyout({ ["text/plain"] = "No documentation found" }) + end + end + + function new_environment() + local env_G,env = {},{} + for k,v in pairs(_G) do env_G[k] = v end + + env_G.args = nil + env_G._G = env_G + + local env_G = setmetatable(env_G, { __index = _G }) + local env = setmetatable(env, { __index = env_G }) + + env_G.bokeh = bokeh + env_G.dom_builder = dom_builder + + env_G.pyout = function(data,metadata) + metadata = metadata or {} + assert(type(data) == "table", "Needs a table as first argument") + assert(type(metadata) == "table", "Needs nil or table as second argument") + pyout(data,metadata) + end + + env_G.pyget = function(obj) + return lookup_function_for_object(obj, output_functions, MAX) + end + + env_G.show = function(...) + if select('#',...) == 1 then + if show_obj(..., MAX) then return end + end + local args = table.pack(...) + local html = { "
" } + for i=1,args.n do + if args[i] == nil then + table.insert(html, '
nil
') + elseif args[i] == "\n" then + table.insert(html, '
') + else + local component + local data = env_G.pyget(args[i]) + -- TODO: add more mime-types + if data["text/html"] then + component = data["text/html"] + elseif data["image/png"] then + component = (''):format(data["image/png"]) + else + component = ("
%s
"):format(data["text/plain"]) + end + table.insert(html, ('
%s
'):format(component)) + end + -- for text/plain output + args[i]=stringfy(args[i]) + end + table.insert(html, "
") + local str = table.concat(args,"\t") + pyout({ + ["text/plain"] = str.."\n", + ["text/html"] = table.concat(html) + }) + end + + env_G.print = function(...) + local args = table.pack(...) + for i=1,args.n do args[i]=tostring(args[i]) end + local str = table.concat(args,"\t") + stdout_write(str.."\n") + end + + env_G.io.stdout = setmetatable({}, { __index={ write=function(_,...) return stdout_write(...) end } }) + env_G.io.stderr = setmetatable({}, { __index={ write=function(_,...) return stderr_write(...) end } }) + env_G.io.stdin = nil -- forbidden + + env_G.io.read = nil -- forbidden + + env_G.io.write = function(...) + env_G.io.stdout:write(...) + end + + env_G.vars = function() + show_obj(env, math.huge) + end + + env_G.help = help + + env_G["%quickref"] = function() + local tbl = { + "? -> Introduction and overview.", + "%quickref -> This guide.", + "%guiref -> Not implemented.", + "help(object) -> Help about a given object.", + "object? -> Help about a given object.", + "print(...) -> Standard Lua print function.", + "show(...) -> Show using a list of objects by columns (fancy output).", + "pyget(obj) -> Returns low-level data table for pyout() function.", + "pyout(data) -> Allow printing low-level data to IPython", + "vars() -> Shows all global variables declared by the user.", + } + show_obj(table.concat(tbl,"\n")) + end + + env_G["%guiref"] = function() + local tbl = { + "GUI reference not implemented.", + } + end + + local env_package = {} + for k,v in pairs(package) do + if type(v) == "table" then + env_package[k] = {} + for k2,v2 in pairs(v) do env_package[k][k2] = v2 end + else + env_package[k] = v + end + end + + local function lua_loader(name, modpath) + local fh = assert(io.open(modpath, 'rb')) + local source = fh:read'*a' + fh:close() + local ret = assert(load(source, modpath, 'bt', env_G))(name) + return ret + end + + --local function c_loader(name, modpath) + --local funcname = "luaopen_" .. name:gsub("^.*%-",""):gsub("%.","_") + --end + + env_package.searchers[2] = function(name) + local modpath, msg = env_package.searchpath(name, env_package.path) + if modpath then + return lua_loader, modpath + else + return nil, msg + end + end + + --env_package.searchers[3] = function(name) + --local modpath, msg = env_package.searchpath(name, env_package.cpath) + --if modpath then + --return c_loader, modpath + --else + --return nil, msg + --end + --end + + env_G.package = env_package + + setmetatable(package.loaded, { + __index=env_package.loaded, + __newindex=function(self,k,v) rawset(env_package.loaded,k,v) end, + }) + + local function env_require(...) + local old_package_loaded = package.loaded + local k,old = debug.getupvalue(require,1) + debug.setupvalue(require,1,env_package) + local result = require (...) + debug.setupvalue(require,1,old) + return result + end + + env_G.require = env_require + + return env,env_G + end +end +local env,env_G = new_environment() +_G.pyget = env_G.pyget +env_G.dom_builder = require "IPyLua.dom_builder" + +local reg = debug.getregistry() +local reg_G = reg[2] +if rawequal(reg_G,_G) then reg[2] = env_G else reg_G = nil end +------------------------------------------------------------------------------- + +local function send_execute_reply(sock, parent, count, status, err) + local session = parent.header.session + local header = ipmsg_header( 'execute_reply' ) + local content = { + status = status or 'ok', + execution_count = count, + payload = {}, + user_expresions = {}, + } + if status=="error" then + content.ename = err + content.evalue = '' + content.traceback = {err} + end + ipmsg_send(sock, { + session = session, + parent = parent, + header = header, + content = content, + }) +end + +local function send_busy_message(sock, parent) + local session = parent.header.session + local header = ipmsg_header( 'status' ) + local content = { execution_state='busy' } + ipmsg_send(sock, { + session = session, + parent = parent, + header = header, + content = content, + }) +end + +local function send_idle_message(sock, parent) + local session = parent.header.session + local header = ipmsg_header( 'status' ) + local content = { execution_state='idle' } + ipmsg_send(sock, { + session = session, + parent = parent, + header = header, + content = content, + }) +end + +local function execute_code(parent) + local session = parent.header.session + local code = parent.content.code + if not code or #code==0 then return end + env_parent = parent + env_session = session + env_source = code + if code:find("%?+\n?$") or code:find("^%?+") then + local x = load("return "..(code:match("^%?*([^?\n]*)%?*\n?$") or "nil"), nil, nil, env) + if x then x = x() end + env.help(x) + return true + else + if code:sub(1,1) == "%" then + code = ("_G[%q]()"):format(code:gsub("\n","")) + end + if code:sub(1,1) == "=" then code = "return " .. code:sub(2) end + local ok,err = true,nil + -- TODO: reimplement it to be Lua 5.1 compatible + local f,msg = load(code, nil, nil, env) + if f then + local out = table.pack(xpcall(f, debug.traceback)) + if not out[1] then + ok,err = nil,out[2] + elseif #out > 1 then + env.show(table.unpack(out, 2)) + end + else + ok,err = nil,msg + end + collectgarbage("collect") + return ok,err + end +end + +local function send_pyin_message(sock, parent, count) + local session = parent.header.session + local header = ipmsg_header( 'pyin' ) + local content = { code=parent.content.code, + execution_count = count, } + ipmsg_send(sock, { + session = session, + parent = parent, + header = header, + content = content, + }) +end + +local function send_pyerr_message(sock, parent, count, err) + local session = parent.header.session + local header = ipmsg_header( 'pyerr' ) + local content = { ename = err, evalue = '', traceback = {err}, + execution_count = count, } + ipmsg_send(sock, { + session = session, + parent = parent, + header = header, + content = content, + }) + -- env_G.pyout({ + -- ["text/plain"] = err.."\n", + -- ["text/html"] = ("
%s
"):format(err), + -- }) +end + +-- implemented routes +local shell_routes = { + kernel_info_request = function(sock, parent) + local session = parent.header.session + local header = ipmsg_header( 'kernel_info_reply' ) + local major,minor = _VERSION:match("(%d+)%.(%d+)") + local content = { + protocol_version = {4, 0}, + language_version = {tonumber(major), tonumber(minor)}, + language = 'lua', + } + ipmsg_send(sock, { + session=session, + parent=parent, + header=header, + content=content, + }) + end, + + execute_request = function(sock, parent) + parent.content = json.decode(parent.content) + local count + if parent.content.store_history then + count = next_execution_count() + else + count = current_execution_count() + end + -- + send_busy_message(kernel.iopub_sock, parent) + + local ok,err = execute_code(parent) + send_pyin_message(kernel.iopub_sock, parent, count) + if ok then + send_execute_reply(sock, parent, count) + else + err = err or "Unknown error" + send_pyerr_message(kernel.iopub_sock, parent, count, err) + send_execute_reply(sock, parent, count, "error", err) + end + send_idle_message(kernel.iopub_sock, parent) + end, + + shutdown_request = function(sock, parent) + print("IPyLua: Received shutdown signal") + parent.content = json.decode(parent.content) + -- + send_busy_message(sock, parent) + -- free sandbox environment resources + setmetatable(package.loaded, {}) + env = nil + env_G = nil + collectgarbage("collect") + -- + local session = parent.header.session + local header = ipmsg_header( 'shutdown_reply' ) + local content = parent.content + ipmsg_send(sock, { + session=session, + parent=parent, + header=header, + content=content, + }) + send_idle_message(kernel.iopub_sock, parent) + for _, v in ipairs(kernel_sockets) do + kernel[v.name]:close() + end + if reg_G then reg[2] = reg_G end + end, + + complete_request = function(sock, parent) + parent.content = json.decode(parent.content) + local ok,content = pcall(do_completion, + parent.content.code or parent.content.line, + parent.content.text, + parent.content.cursor_pos, + env_G, env, _ENV) + if not ok then + env_G.io.stderr:write("Error at do_completion\n") + else + local session = parent.header.session + local header = ipmsg_header( 'complete_reply' ) + ipmsg_send(sock, { + session=session, + parent=parent, + header=header, + content=content, + }) + end + end, + + history_request = function(sock, parent) + print("history_requested but not implemented") + end, + + comm_open = function(sock, parent) + print("comm_open but not implemented") + end, + + object_info_request = function(sock, parent) + parent.content = json.decode(parent.content) + local session = parent.header.session + local header = ipmsg_header( 'object_info_reply' ) + local oname = parent.content.oname + -- TODO: handle detail_level + local len + local x = load("return "..oname, nil, nil, env) + if x then + ok,x = pcall(x) + if not ok then x = nil end + ok,len = pcall(function() return #x end) + if not ok then len = nil end + end + local definition + local argspec + if type(x) == "function" then + local info = debug.getinfo(x) + if info.what == "Lua" and info.source and info.linedefined then + local source = info.source + local first = info.linedefined + local iterator + if source:sub(1,1) == "@" then + iterator = table.pack( io.lines(source:sub(2)) ) + else + iterator = table.pack( source:gmatch("([^\r\n]+)") ) + end + local k=1 + for line in table.unpack( iterator ) do + if first == k then definition = {line:match("^%s*(.*)%s*$")} break end + k=k+1 + end + else + definition = {"... = ",oname,"("} + + argspec = { args = {} } + if info.isvararg then + argspec.args[1] = "..." + table.insert(definition, "...") + else + local args = {} + for i=1,info.nparams do + args[i] = "arg"..i + end + table.insert(definition, table.concat(args,",")) + end + table.insert(definition, ")") + end + end + local content = { + oname = oname, + found = (x~=nil), + ismagic = false, + isalias = false, + namespace = 'global', + type_name = type(x), + string_form = stringfy(x), + length = len, + argspec = argspec, + definition = definition and table.concat(definition) or nil, + } + if x then + local data = lookup_function_for_object(x, help_functions) + if data then content.docstring = data["text/plain"] end + end + ipmsg_send(sock, { + session=session, + parent=parent, + header=header, + content=content, + }) + end, +} + +do + local function dummy_function() end + setmetatable(shell_routes, { + __index = function(self, key) + print(key) + return rawget(self,key) or dummy_function + end, + }) +end + +------------------------------------------------------------------------------- +-- ZMQ Read Handlers + +local function on_hb_read( sock ) + +end + +local function on_control_read( sock ) + local data = zassert( sock:recv(zmq.NOBLOCK) ) + -- TODO: handle 'timeout' error +end + +local function on_stdin_read( sock ) + local data = zassert( sock:recv(zmq.NOBLOCK) ) + -- TODO: handle 'timeout' error +end + +local function on_shell_read( sock ) + -- TODO: error handling + local ipmsg = zassert( sock:recv_all() ) + local msg = ipmsg_to_table(ipmsg) + -- for k, v in pairs(msg) do print(k,v) end + msg.header = json.decode(msg.header) + -- print("REQUEST FOR KEY", msg.header.msg_type) + shell_routes[msg.header.msg_type](sock, msg) +end + +------------------------------------------------------------------------------- +-- SETUP + +kernel_sockets = { + -- { name = 'heartbeat_sock', sock_type = zmq.REP, port = 'hb', handler = on_hb_read }, + { name = 'control_sock', sock_type = zmq.ROUTER, port = 'control', handler = on_control_read }, + { name = 'stdin_sock', sock_type = zmq.ROUTER, port = 'stdin', handler = on_stdin_read }, + { name = 'shell_sock', sock_type = zmq.ROUTER, port = 'shell', handler = on_shell_read }, + { name = 'iopub_sock', sock_type = zmq.PUB, port = 'iopub', handler = on_iopub_read }, +} + +local z_ctx = zmq.context() +local z_poller = zmq_poller(#kernel_sockets) +for _, v in ipairs(kernel_sockets) do + if v.name ~= "heartbeat_sock" then + -- TODO: error handling in here + local sock = zassert( z_ctx:socket(v.sock_type) ) + + local conn_obj = kernel.connection_obj + local addr = string.format('%s://%s:%s', + conn_obj.transport, + conn_obj.ip, + conn_obj[v.port..'_port']) + + zassert( sock:bind(addr) ) + + if v.name ~= 'iopub_sock' then -- avoid polling from iopub + z_poller:add(sock, zmq.POLLIN, v.handler) + end + + kernel[v.name] = sock + end +end + +-- heartbeat is controlled through an independent thread, allowing the main +-- thread to manage interactive commands given by the IPython +local thread = zthreads.run(z_ctx, + function(conn_obj) + local zmq = require "lzmq" + local z_ctx = require"lzmq.threads".get_parent_ctx() + local zassert = zmq.assert + local v = { + name = 'heartbeat_sock', + sock_type = zmq.REP, + port = 'hb', + } + local sock = zassert( z_ctx:socket(v.sock_type) ) + local addr = string.format('%s://%s:%s', + conn_obj.transport, + conn_obj.ip, + conn_obj[v.port..'_port']) + zassert( sock:bind(addr) ) + while true do + -- read the data and send a pong + local data,msg = sock:recv() + if msg ~= "timeout" then + if not data then break end + -- TODO: handle 'timeout' error + sock:send('pong') + end + end + end, + kernel.connection_obj) +thread:start(true,true) + +------------------------------------------------------------------------------- +-- POLL then SHUTDOWN + +--print("Starting poll") +z_poller:start() + +for _, v in ipairs(kernel_sockets) do + kernel[v.name]:close() +end +z_ctx:term() +thread:join() +print("IPyLua: Bye bye!") diff --git a/LICENSE b/LICENSE index b8a3c46..166c8d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,23 @@ -lua_ipython_kernel - +IPyLua +Copyright (c) 2015 Francisco Zamora-Martinez. Simplified, less deps and making +it work. + +Original name and copyright: lua_ipython_kernel Copyright (c) 2013 Evan Wies. All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 7c16eba..1f15dc2 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,44 @@ -# Lua IPython Kernel +# IPyLua: Lua IPython Kernel -This is a kernel to support Lua with [IPython](http://ipython.org). It is pure Lua and should work with both Lua and LuaJIT. +This is a kernel to support Lua with [IPython](http://ipython.org). It is pure +Lua and should work with both Lua and LuaJIT. ## Requirements The following Lua libraries are required: - * [zeromq](http://zeromq.org/bindings:lua) - * [dkjson](http://dkolf.de/src/dkjson-lua.fsl/home) - * [uuid](https://github.com/Tieske/uuid) - -Here's how to install via [LuaRocks](http://luarocks.org/): - -``` -# You need zeromq... on OSX I use Homebrew: -# brew install zeromq -# On Ubuntu: -# sudo apt-get install libzmq-dev - -# The LuaRocks -sudo luarocks install https://raw.github.com/Neopallium/lua-zmq/master/rockspecs/lua-zmq-scm-1.rockspec -sudo luarocks install dkjson -sudo luarocks install uuid -``` - -Of course you also need to [install IPython](http://ipython.org/install.html)... + * [lzmq](https://github.com/zeromq/lzmq) zeromq for Lua +The code for Lua JSON of [dkjson](http://dkolf.de/src/dkjson-lua.fsl/home) and +[uuid](https://github.com/Tieske/uuid/blob/master/src/uuid.lua) has been copied +into this module to avoid external dependencies. ## Installation The installation process is janky right now. - * Install the Requirements above + * Install the Requirements: [zeromq](http://zeromq.org/) and + [lzmq](https://github.com/zeromq/lzmq). - * Create a profile with IPython + * Create a profile with IPython: ``` -ipython profile create lua +$ ipython profile create IPyLua ``` - * Modify the profile's `ipython_config.py` to use lua_ipython_kernel. This will be at either - `~/.config/ipython/lua/ipython_config.py` or `~/.ipython/lua/ipython_config.py`: + * Modify the profile's `ipython_config.py` to use IPyLua. This + will be at either `~/.config/ipython/profile_IPyLua/ipython_config.py` or + `~/.ipython/profile_IPyLua/ipython_config.py`: -``` +```Python # Configuration file for ipython. c = get_config() c.KernelManager.kernel_cmd = [ - "luajit", # select your Lua interpreter here - "ipython_kernel.lua", # probably need full path + "luajit", # select your Lua interpreter here (lua5.2, lua5.1, luajit) + "IPyLua/IPyLuaKernel.lua", # probably need full path "{connection_file}" ] @@ -59,12 +47,15 @@ c.Session.key = b'' c.Session.keyfile = b'' ``` + * Copy the content of `config` folder into your + `~/.ipython/profile_IPyLua/static/custom/` folder. + * Invoke IPython with this Lua kernel: ``` -ipython console --profile lua +$ ipython console --profile IPyLua # or -ipython notebook --profile lua +$ ipython notebook --profile IPyLua ``` ## TODO @@ -78,23 +69,46 @@ ipython notebook --profile lua ## Acknowledgements -Thanks to Andrew Gibiansky for his [IHaskell article](http://andrew.gibiansky.com/blog/ipython/ipython-kernels/) that inspired this. +Thanks to Evan Wies who has written the original code +[lua_ipython_kernel](https://github.com/neomantra/lua_ipython_kernel). -Thanks to the makers of the dependencies of this library, who made this pretty easy to create: [Robert Jakabosky](https://github.com/Neopallium), [David Kolf](http://dkolf.de/src/dkjson-lua.fsl/home), and [Thijs Schreijer](https://github.com/Tieske). +Thanks to Andrew Gibiansky for his +[IHaskell article](http://andrew.gibiansky.com/blog/ipython/ipython-kernels/) +that inspired this. -And of course thanks to the [IPython folks ](http://ipython.org/citing.html). +Thanks to the makers of the dependencies of this library, who made this pretty +easy to create: [Robert Jakabosky](https://github.com/Neopallium), +[David Kolf](http://dkolf.de/src/dkjson-lua.fsl/home), and +[Thijs Schreijer](https://github.com/Tieske). +And of course thanks to the [IPython folks ](http://ipython.org/citing.html). ## LICENSE -**lua_ipython_kernel** is distributed under the [MIT License](http://opensource.org/licenses/mit-license.php). +**IPyLua** is distributed under the + [MIT License](http://opensource.org/licenses/mit-license.php). -> lua_ipython_kernel -> +> IPyLua +> Copyright (c) 2015 Francisco Zamora-Martinez. Simplified, less deps and making +> it work. +> +> Original name and copyright: lua_ipython_kernel > Copyright (c) 2013 Evan Wies. All rights reserved. > -> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: > -> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. > -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. diff --git a/custom/custom.css b/custom/custom.css new file mode 100644 index 0000000..f3b5812 --- /dev/null +++ b/custom/custom.css @@ -0,0 +1,103 @@ +/* disable smoothing on images (needed to visualize filters) */ +img { + image-rendering: optimizeSpeed; /* STOP SMOOTHING, GIVE ME SPEED */ + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: -o-crisp-edges; /* Opera */ + image-rendering: -webkit-optimize-contrast; /* Chrome (and eventually Safari) */ + image-rendering: pixelated; /* Chrome */ + image-rendering: optimize-contrast; /* CSS3 Proposed */ + -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ +} + +/* + + Name: Base16 Ocean Dark + Author: Chris Kempson (http://chriskempson.com) + + CodeMirror template adapted for IPython Notebook by Nikhil Sonnad (https://github.com/nsonnad/base16-ipython-notebook) + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +/* Uncomment to use a custom font */ +div#notebook, div.CodeMirror, div.output_area pre, div.output_wrapper, div.prompt { + font-family: 'Monaco', monospace !important; + font-size: 12pt; +} + +/* Change the width of the container */ +.container { width:95% !important; } + +/* GLOBALS */ +body {background-color: #2b303b;} +a {color: #8fa1b3;} + +/* INTRO PAGE */ +.toolbar_info, .list_container {color: #dfe1e8;} + +/* NOTEBOOK */ + +/* comment out this line to bring the toolbar back */ +div#maintoolbar, div#header {display: none !important;} + +div.navbar {color:white;} + +div#notebook {border-top: none;} + +div.input_prompt {color: #ab7967;} +div.output_prompt {color: #b48ead;} +div.input_area { + border-radius: 0px; + border: 1px solid #4f5b66; +} +div.output_area pre {font-weight: normal; color: #c0c5ce;} +div.output_subarea {font-weight: normal; color: #c0c5ce;} + +.rendered_html table, .rendered_html th, .rendered_html tr, .rendered_html td { + border: 1px #c0c5ce solid; + color: #c0c5ce; +} +div.output_html { font-family: sans-serif; } +table.dataframe tr {border: 1px #c0c5ce;} + +div.cell.selected {border-radius: 0px;} +div.cell.edit_mode {border-radius: 0px; border: thin solid #b48ead;} +div.text_cell_render, div.output_html {color: #c0c5ce;} + +span.ansiblack {color: #343d46;} +span.ansiblue {color: #96b5b4;} +span.ansigray {color: #a7adba;} +span.ansigreen {color: #a3be8c;} +span.ansipurple {color: #b48ead;} +span.ansired {color: #bf616a;} +span.ansiyellow {color: #ebcb8b;} + +div.output_stderr {background-color: #bf616a;} +div.output_stderr pre {color: #dfe1e8;} + +.cm-s-ipython.CodeMirror {background: #2b303b; color: #dfe1e8;} +.cm-s-ipython div.CodeMirror-selected {background: #343d46 !important;} +.cm-s-ipython .CodeMirror-gutters {background: #2b303b; border-right: 0px;} +.cm-s-ipython .CodeMirror-linenumber {color: #65737e;} +.cm-s-ipython .CodeMirror-cursor {border-left: 1px solid #a7adba !important;} + +.cm-s-ipython span.cm-comment {color: #ab7967;} +.cm-s-ipython span.cm-atom {color: #b48ead;} +.cm-s-ipython span.cm-number {color: #b48ead;} + +.cm-s-ipython span.cm-property, .cm-s-ipython span.cm-attribute {color: #a3be8c;} +.cm-s-ipython span.cm-keyword {color: #bf616a;} +.cm-s-ipython span.cm-string {color: #ebcb8b;} +.cm-s-ipython span.cm-operator {color: #ab7967;} +.cm-s-ipython span.cm-builtin {color: #b48ead;} + +.cm-s-ipython span.cm-variable {color: #a3be8c;} +.cm-s-ipython span.cm-variable-2 {color: #8fa1b3;} +.cm-s-ipython span.cm-def {color: #d08770;} +.cm-s-ipython span.cm-error {background: #bf616a; color: #a7adba;} +.cm-s-ipython span.cm-bracket {color: #c0c5ce;} +.cm-s-ipython span.cm-tag {color: #bf616a;} +.cm-s-ipython span.cm-link {color: #b48ead;} + +.cm-s-ipython .CodeMirror-matchingbracket { text-decoration: underline; color: #dfe1e8 !important;} diff --git a/custom/custom.js b/custom/custom.js new file mode 100644 index 0000000..44897ee --- /dev/null +++ b/custom/custom.js @@ -0,0 +1,48 @@ +// function checkDOMChange() { +// $("#ipylua_static_code").each(function() { +// var $this = $(this), +// $code = $this.html(); +// $this.empty(); +// var myCodeMirror = CodeMirror(this, { +// value: $code, +// mode: 'lua', +// lineNumbers: false, +// readOnly: true +// }); +// /*CodeMirror.fromTextArea*/ +// }) +// .attr("id", "highlighted_ipylua_static_code"); +// setTimeout( checkDOMChange, 500 ); +// } +// checkDOMChange(); + +$([IPython.events]).on('notebook_loaded.Notebook', function(){ + // add here logic that should be run once per **notebook load** + // (!= page load), like restarting a checkpoint + var md = IPython.notebook.metadata + if(md.language){ + console.log('language already defined and is :', md.language); + } else { + md.language = 'lua' ; + console.log('add metadata hint that language is lua'); + } +}); + +// logic per page-refresh +$([IPython.events]).on("app_initialized.NotebookApp", function () { + $('head').append(''); + + IPython.CodeCell.options_default['cm_config']['mode'] = 'lua'; + IPython.CodeCell.options_default['cm_config']['indentUnit'] = 2; + + CodeMirror.requireMode('lua', function() { + IPython.OutputArea.prototype._should_scroll = function(){return false} + cells = IPython.notebook.get_cells(); + for(var i in cells){ + c = cells[i]; + if (c.cell_type === 'code') { + c.auto_highlight() + } + } + }); +}); diff --git a/demo.ipynb b/demo.ipynb new file mode 100644 index 0000000..63be608 --- /dev/null +++ b/demo.ipynb @@ -0,0 +1,463 @@ +{ + "metadata": { + "language": "lua", + "name": "", + "signature": "sha256:f3ddbf4d6bccf8e356162a2248cf98b27c64b3105c5d37864c5f13c6e54fb769" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Demo for IPyLua" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can declare multi-line functions using the Notebook or the qtconsole with ctrl+intro." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "function fib(n)\n", + " if n <= 2 then return 1 end\n", + " return fib(n-1) + fib(n-2)\n", + "end" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 1 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for i=1,10 do print(\"fib(\"..i..\") =\",fib(i)) end" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
fib(1) =\t1
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(1) =\t1\n" + ] + }, + { + "html": [ + "
fib(2) =\t1
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(2) =\t1\n" + ] + }, + { + "html": [ + "
fib(3) =\t2
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(3) =\t2\n" + ] + }, + { + "html": [ + "
fib(4) =\t3
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(4) =\t3\n" + ] + }, + { + "html": [ + "
fib(5) =\t5
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(5) =\t5\n" + ] + }, + { + "html": [ + "
fib(6) =\t8
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(6) =\t8\n" + ] + }, + { + "html": [ + "
fib(7) =\t13
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(7) =\t13\n" + ] + }, + { + "html": [ + "
fib(8) =\t21
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(8) =\t21\n" + ] + }, + { + "html": [ + "
fib(9) =\t34
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(9) =\t34\n" + ] + }, + { + "html": [ + "
fib(10) =\t55
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 2, + "text": [ + "fib(10) =\t55\n" + ] + } + ], + "prompt_number": 2 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to declare tables as usual." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "tbl = {\n", + " a = 1,\n", + " b = 2,\n", + " \"Hello\", \"World\", \"!\"\n", + "}" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 3 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And the table can be shown as usual or using the fancy `show()` command." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print(tbl)\n", + "show(tbl)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
table: 0x1428010
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 4, + "text": [ + "table: 0x1428010\n" + ] + }, + { + "html": [ + "
{\n",
+        "\t[1] = \"Hello\",\n",
+        "\t[2] = \"World\",\n",
+        "\t[3] = \"!\",\n",
+        "\t[\"a\"] = 1,\n",
+        "\t[\"b\"] = 2,\n",
+        "}\n",
+        "-- table: 0x1428010 with 3 array part, 2 hash part
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 4, + "text": [ + "{\n", + "\t[1] = \"Hello\",\n", + "\t[2] = \"World\",\n", + "\t[3] = \"!\",\n", + "\t[\"a\"] = 1,\n", + "\t[\"b\"] = 2,\n", + "}\n", + "-- table: 0x1428010 with 3 array part, 2 hash part\n" + ] + } + ], + "prompt_number": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can even show multiple objects by columns because `show()` accepts a variable number of arguments." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "show(tbl, {1,2,3,4})" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
{\n",
+        "\t[1] = \"Hello\",\n",
+        "\t[2] = \"World\",\n",
+        "\t[3] = \"!\",\n",
+        "\t[\"a\"] = 1,\n",
+        "\t[\"b\"] = 2,\n",
+        "}\n",
+        "-- table: 0x1428010 with 3 array part, 2 hash part
{\n",
+        "\t[1] = 1,\n",
+        "\t[2] = 2,\n",
+        "\t[3] = 3,\n",
+        "\t[4] = 4,\n",
+        "}\n",
+        "-- table: 0x13a3f40 with 4 array part, 0 hash part
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 5, + "text": [ + "table: 0x1428010\ttable: 0x13a3f40\n" + ] + } + ], + "prompt_number": 5 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The function `vars()` allow shows all the variables declared by the user (global variables)." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "vars()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
{\n",
+        "\t[\"fib\"] = function: 0x14286d0,\n",
+        "\t[\"tbl\"] = table: 0x1428010,\n",
+        "}\n",
+        "-- table: 0x140d240 with 0 array part, 2 hash part
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 6, + "text": [ + "{\n", + "\t[\"fib\"] = function: 0x14286d0,\n", + "\t[\"tbl\"] = table: 0x1428010,\n", + "}\n", + "-- table: 0x140d240 with 0 array part, 2 hash part\n" + ] + } + ], + "prompt_number": 6 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "APRIL-ANN, a toolkit for pattern recognition tasks, has been connected with IPyLua and it is possible to show matrices, images and plots." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "require \"aprilann\"\n", + "x = matrix(300,300):linspace(0,0.5)\n", + "x = x + x:t()\n", + "x_img = Image(x)\n", + "show(x_img, x)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "
 0             0.00167224    0.00334448    0.00501672   ...  0.5         \n",
+        " 0.00167224    0.00334448    0.00501672    0.00668896   ...  0.501672    \n",
+        " 0.00334448    0.00501672    0.00668896    0.0083612    ...  0.503344    \n",
+        " 0.00501672    0.00668896    0.0083612     0.0100334    ...  0.505017    \n",
+        " 0.00668896    0.0083612     0.0100334     0.0117057    ...  0.506689    \n",
+        "...\n",
+        " 0.5           0.501672      0.503344      0.505017     ...  1           \n",
+        "# Matrix of size [300,300] stride [300,1] ref [0x1647560 data= 0x1705a00]\n",
+        "
" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 7, + "text": [ + "# Image 300x300+0+0 [0x14e5dd0 data= 0x176bc40]\tuserdata\n" + ] + } + ], + "prompt_number": 7 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, it is possible to perform basic plots (currently: points, bars, lines, hist2d). Plots receive several series, which can be plain Lua tables (with numeric indices or array tables) or APRIL-ANN matrices." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "local x = matrix(120,1):linspace(-10,10)\n", + "local y = stats.dist.normal(0,2):logpdf(x):exp()\n", + "local fig = bokeh.figure{ width=600, height=500 }\n", + "-- bars using Lua arrays\n", + "fig:bars{ x=0, y=0.1, height=0.2, width=2, alpha=0.3 }\n", + "-- bars with APRIL-ANN matrix\n", + "fig:bars{ x=x, height=y, y = y/2, width=0.01, color=fig:color_num(1), alpha=0.3 }\n", + "-- lines with APRIL-ANN matrix\n", + "fig:lines{ x=x, y=y, width=3, color=fig:color_num(1), alpha=0.3, legend=\"Normal\" }\n", + "-- points with APRIL-ANN matrix\n", + "fig:points{ x=x, y=y, color=fig:color_num(1) }\n", + "show(fig)" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "html": [ + "\n", + "(function(global) {\n", + " if (typeof (window._bokeh_onload_callbacks) === \"undefined\"){\n", + " window._bokeh_onload_callbacks = [];\n", + " }\n", + " function load_lib(url, callback){\n", + " window._bokeh_onload_callbacks.push(callback);\n", + " if (window._bokeh_is_loading){\n", + " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", new Date());\n", + " return null;\n", + " }\n", + " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", new Date());\n", + " window._bokeh_is_loading = true;\n", + " var s = document.createElement('script');\n", + " s.src = url;\n", + " s.async = true;\n", + " s.onreadystatechange = s.onload = function(){\n", + " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.10.0.min.css\");\n", + " window._bokeh_onload_callbacks.forEach(function(callback){callback()});\n", + " };\n", + " s.onerror = function(){\n", + " console.warn(\"failed to load library \" + url);\n", + " };\n", + " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", + " }\n", + "\n", + " bokehjs_url = \"https://cdn.pydata.org/bokeh/release/bokeh-0.10.0.min.js\"\n", + "\n", + " var elt = document.getElementById(\"9165a560-bf4e-4433-c48c-504cacda79f4\");\n", + " if(elt==null) {\n", + " console.log(\"Bokeh: ERROR: autoload.js configured with elementid '9165a560-bf4e-4433-c48c-504cacda79f4' but no matching script tag was found. \")\n", + " return false;\n", + " }\n", + "\n", + " // These will be set for the static case\n", + " var all_models = [{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"attributes\":{\"plot_width\":600,\"tool_events\":{\"id\":\"5e66c576-6502-4ff6-cf0c-ef6d971172a7\",\"type\":\"ToolEvents\"},\"y_range\":{\"id\":\"b6f10976-3f8d-499b-c9fa-e7c7d460bb5e\",\"type\":\"Range1d\"},\"extra_y_ranges\":[],\"x_range\":{\"id\":\"8a353caa-5def-4bf2-cb6d-6ca017514ca8\",\"type\":\"Range1d\"},\"below\":[{\"id\":\"51e271a7-53be-45eb-c5f7-7e484c1a4959\",\"type\":\"LinearAxis\"}],\"above\":[],\"plot_height\":500,\"title_text_font_size\":{\"value\":\"12pt\"},\"responsive\":false,\"left\":[{\"id\":\"23d12fe9-b07e-4887-c830-ab334229a223\",\"type\":\"LinearAxis\"}],\"title_text_font_style\":\"bold\",\"extra_x_ranges\":[],\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"tags\":[],\"right\":[],\"tools\":[{\"id\":\"e19ed225-c881-43ad-c30c-3ed9a8324b06\",\"type\":\"PanTool\"},{\"id\":\"98107dfe-121c-4540-c5e4-aebff62066d7\",\"type\":\"WheelZoomTool\"},{\"id\":\"be39fd87-bb30-44f7-c473-d0e5a51beb3d\",\"type\":\"BoxZoomTool\"},{\"id\":\"2c683c3f-8531-40ad-c02e-6d0c4ed3e30d\",\"type\":\"ResizeTool\"},{\"id\":\"0de094c8-10c9-4f4d-cf90-32e2ac1e1fd8\",\"type\":\"ResetTool\"},{\"id\":\"875b180c-8d98-49a3-c927-af15fa922208\",\"type\":\"PreviewSaveTool\"},{\"id\":\"73b7d084-8190-41be-c104-a0cd23c0a6aa\",\"type\":\"HoverTool\"}],\"renderers\":[{\"id\":\"86cfe783-2733-4283-c28e-03cc212d9572\",\"type\":\"Grid\"},{\"id\":\"51e271a7-53be-45eb-c5f7-7e484c1a4959\",\"type\":\"LinearAxis\"},{\"id\":\"8e942c28-29b8-414c-c168-91038447d0a8\",\"type\":\"Grid\"},{\"id\":\"23d12fe9-b07e-4887-c830-ab334229a223\",\"type\":\"LinearAxis\"},{\"id\":\"91e46610-deb8-4b08-cbda-75da7b058fb1\",\"type\":\"GlyphRenderer\"},{\"id\":\"c5d790f0-9383-428b-c240-087a91224fc7\",\"type\":\"GlyphRenderer\"},{\"id\":\"c3590fb9-52d8-49dd-c9f0-ff565f7a06a6\",\"type\":\"GlyphRenderer\"},{\"id\":\"85a78014-8ae6-4aae-ca19-64e4a9e2186c\",\"type\":\"Legend\"},{\"id\":\"47b0bc47-2028-4381-c374-41793ca9e5ab\",\"type\":\"GlyphRenderer\"}]},\"type\":\"Plot\"},{\"id\":\"5e66c576-6502-4ff6-cf0c-ef6d971172a7\",\"attributes\":{\"geometries\":[],\"id\":\"5e66c576-6502-4ff6-cf0c-ef6d971172a7\",\"doc\":null,\"tags\":[]},\"type\":\"ToolEvents\"},{\"id\":\"e19ed225-c881-43ad-c30c-3ed9a8324b06\",\"attributes\":{\"id\":\"e19ed225-c881-43ad-c30c-3ed9a8324b06\",\"dimensions\":[\"width\",\"height\"],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"PanTool\"},{\"id\":\"98107dfe-121c-4540-c5e4-aebff62066d7\",\"attributes\":{\"id\":\"98107dfe-121c-4540-c5e4-aebff62066d7\",\"dimensions\":[\"width\",\"height\"],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"WheelZoomTool\"},{\"id\":\"be39fd87-bb30-44f7-c473-d0e5a51beb3d\",\"attributes\":{\"id\":\"be39fd87-bb30-44f7-c473-d0e5a51beb3d\",\"dimensions\":[\"width\",\"height\"],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"BoxZoomTool\"},{\"id\":\"2c683c3f-8531-40ad-c02e-6d0c4ed3e30d\",\"attributes\":{\"doc\":null,\"id\":\"2c683c3f-8531-40ad-c02e-6d0c4ed3e30d\",\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"ResizeTool\"},{\"id\":\"0de094c8-10c9-4f4d-cf90-32e2ac1e1fd8\",\"attributes\":{\"doc\":null,\"id\":\"0de094c8-10c9-4f4d-cf90-32e2ac1e1fd8\",\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"ResetTool\"},{\"id\":\"875b180c-8d98-49a3-c927-af15fa922208\",\"attributes\":{\"doc\":null,\"id\":\"875b180c-8d98-49a3-c927-af15fa922208\",\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"tags\":[]},\"type\":\"PreviewSaveTool\"},{\"id\":\"73b7d084-8190-41be-c104-a0cd23c0a6aa\",\"attributes\":{\"tooltips\":[[\"Tag\",\"@hover\"],[\"(x,y)\",\"($x, $y)\"]],\"always_active\":true,\"doc\":null,\"point_policy\":\"follow_mouse\",\"id\":\"73b7d084-8190-41be-c104-a0cd23c0a6aa\",\"renderers\":[],\"tags\":[],\"name\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"names\":[],\"callback\":null},\"type\":\"HoverTool\"},{\"id\":\"1bbeb7a9-5671-4c1d-ccfb-32938e559b01\",\"attributes\":{\"doc\":null,\"id\":\"1bbeb7a9-5671-4c1d-ccfb-32938e559b01\",\"tags\":[]},\"type\":\"BasicTickFormatter\"},{\"id\":\"0c6c858e-fd57-4c1e-ccec-ec7fac922ac8\",\"attributes\":{\"mantissas\":[2,5,10],\"id\":\"0c6c858e-fd57-4c1e-ccec-ec7fac922ac8\",\"tags\":[],\"doc\":null,\"num_minor_ticks\":5},\"type\":\"BasicTicker\"},{\"id\":\"51e271a7-53be-45eb-c5f7-7e484c1a4959\",\"attributes\":{\"id\":\"51e271a7-53be-45eb-c5f7-7e484c1a4959\",\"ticker\":{\"id\":\"0c6c858e-fd57-4c1e-ccec-ec7fac922ac8\",\"type\":\"BasicTicker\"},\"tags\":[],\"doc\":null,\"formatter\":{\"id\":\"1bbeb7a9-5671-4c1d-ccfb-32938e559b01\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"}},\"type\":\"LinearAxis\"},{\"id\":\"86cfe783-2733-4283-c28e-03cc212d9572\",\"attributes\":{\"id\":\"86cfe783-2733-4283-c28e-03cc212d9572\",\"tags\":[],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"ticker\":{\"id\":\"0c6c858e-fd57-4c1e-ccec-ec7fac922ac8\",\"type\":\"BasicTicker\"},\"dimension\":0},\"type\":\"Grid\"},{\"id\":\"0f071a63-c5df-4e7f-cecd-c724e7117d6e\",\"attributes\":{\"doc\":null,\"id\":\"0f071a63-c5df-4e7f-cecd-c724e7117d6e\",\"tags\":[]},\"type\":\"BasicTickFormatter\"},{\"id\":\"e165f208-9994-4bb9-cb8e-8644bc1cb6cc\",\"attributes\":{\"mantissas\":[2,5,10],\"id\":\"e165f208-9994-4bb9-cb8e-8644bc1cb6cc\",\"tags\":[],\"doc\":null,\"num_minor_ticks\":5},\"type\":\"BasicTicker\"},{\"id\":\"23d12fe9-b07e-4887-c830-ab334229a223\",\"attributes\":{\"id\":\"23d12fe9-b07e-4887-c830-ab334229a223\",\"ticker\":{\"id\":\"e165f208-9994-4bb9-cb8e-8644bc1cb6cc\",\"type\":\"BasicTicker\"},\"tags\":[],\"doc\":null,\"formatter\":{\"id\":\"0f071a63-c5df-4e7f-cecd-c724e7117d6e\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"}},\"type\":\"LinearAxis\"},{\"id\":\"8e942c28-29b8-414c-c168-91038447d0a8\",\"attributes\":{\"id\":\"8e942c28-29b8-414c-c168-91038447d0a8\",\"tags\":[],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"},\"ticker\":{\"id\":\"e165f208-9994-4bb9-cb8e-8644bc1cb6cc\",\"type\":\"BasicTicker\"},\"dimension\":1},\"type\":\"Grid\"},{\"id\":\"180091c9-7ff9-41cb-c1fc-fe6c26a190b4\",\"attributes\":{\"column_names\":[],\"id\":\"180091c9-7ff9-41cb-c1fc-fe6c26a190b4\",\"tags\":[],\"doc\":null,\"data\":[],\"callback\":null,\"selected\":{\"2d\":{\"indices\":[]},\"1d\":{\"indices\":[]},\"0d\":{\"indices\":[],\"flag\":false}}},\"type\":\"ColumnDataSource\"},{\"id\":\"35bcdc5e-74be-4bbc-cb3c-c0ab8490539d\",\"attributes\":{\"fill_color\":{\"value\":\"#ff0000\"},\"doc\":null,\"y\":{\"value\":0.1},\"height\":{\"units\":\"data\",\"value\":0.2},\"x\":{\"value\":0},\"tags\":[],\"fill_alpha\":{\"value\":0.3},\"id\":\"35bcdc5e-74be-4bbc-cb3c-c0ab8490539d\",\"width\":{\"units\":\"data\",\"value\":2}},\"type\":\"Rect\"},{\"id\":\"91e46610-deb8-4b08-cbda-75da7b058fb1\",\"attributes\":{\"data_source\":{\"id\":\"180091c9-7ff9-41cb-c1fc-fe6c26a190b4\",\"type\":\"ColumnDataSource\"},\"id\":\"91e46610-deb8-4b08-cbda-75da7b058fb1\",\"tags\":[],\"nonselection_glyph\":null,\"glyph\":{\"id\":\"35bcdc5e-74be-4bbc-cb3c-c0ab8490539d\",\"type\":\"Rect\"},\"doc\":null,\"selection_glyph\":null},\"type\":\"GlyphRenderer\"},{\"id\":\"c26c1037-2bbb-4352-c3b3-fd7c435119d5\",\"attributes\":{\"column_names\":[\"height\",\"x\",\"y\"],\"id\":\"c26c1037-2bbb-4352-c3b3-fd7c435119d5\",\"tags\":[],\"doc\":null,\"data\":{\"height\":[3.9177151106573e-12,9.0140239022984e-12,2.0449048704352e-11,4.5739547566148e-11,1.0087341378862e-10,2.1934536131862e-10,4.7027071126138e-10,9.9410391030119e-10,2.0719614912679e-09,4.2579300085777e-09,8.6274782873375e-09,1.7235853633224e-08,3.3950691147311e-08,6.5937328486143e-08,1.26264282585e-07,2.3839400853376e-07,4.4378870711625e-07,8.1456255429657e-07,1.4741412996955e-06,2.6303830509278e-06,4.6277018554974e-06,8.0274767242372e-06,1.3729629245063e-05,2.3152837457019e-05,3.8496076740557e-05,6.3109509937931e-05,0.00010200936958427,0.0001625737932045,0.00025546317920089,0.00039579646545462,0.00060461956309155,0.00091066444292665,0.001352388295345,0.001980205764994,0.0028588138520718,0.0040693716146052,0.005711309146136,0.0079033244401217,0.010783275589347,0.014506331644952,0.019241157919168,0.025163479149342,0.032447185367346,0.04125240072608,0.051711603999138,0.063913553953171,0.077886909246445,0.093584075570107,0.1108680292964,0.12950205802917,0.14914655685425,0.16936209797859,0.18962070345879,0.20932511985302,0.22783651947975,0.24450719356537,0.25871786475182,0.26991522312164,0.27764809131622,0.28159722685814,0.28159722685814,0.27764809131622,0.26991522312164,0.25871786475182,0.24450719356537,0.22783651947975,0.20932511985302,0.18962070345879,0.16936209797859,0.14914655685425,0.12950205802917,0.1108680292964,0.093584075570107,0.077886909246445,0.063913598656654,0.051711566746235,0.04125240072608,0.032447185367346,0.025163503363729,0.019241139292717,0.014506331644952,0.010783275589347,0.0079033328220248,0.0057113035582006,0.0040693716146052,0.0028588138520718,0.0019802076276392,0.0013523870147765,0.00091066444292665,0.00060461956309155,0.0003957970184274,0.00025546291726641,0.0001625737932045,0.00010200936958427,6.3109575421549e-05,3.8496040360769e-05,2.3152770154411e-05,1.3729629245063e-05,8.027462172322e-06,4.6277195906441e-06,2.6303830509278e-06,1.4741356153536e-06,8.1456408906888e-07,4.4378870711625e-07,2.3839309903906e-07,1.26264282585e-07,6.5937200588451e-08,3.3950819045003e-08,1.7235853633224e-08,8.6274454247359e-09,4.2579459957892e-09,2.0719614912679e-09,9.9410013554291e-10,4.7027071126138e-10,2.1934536131862e-10,1.0087380236667e-10,4.5739547566148e-11,2.0448970641795e-11,9.0140759440027e-12,3.9177151106573e-12],\"x\":[-10,-9.8319330215454,-9.6638650894165,-9.4957981109619,-9.3277311325073,-9.1596641540527,-8.9915962219238,-8.8235292434692,-8.6554622650146,-8.4873952865601,-8.3193273544312,-8.1512603759766,-7.983193397522,-7.8151259422302,-7.6470584869385,-7.4789915084839,-7.3109245300293,-7.1428570747375,-6.9747896194458,-6.8067226409912,-6.6386556625366,-6.4705882072449,-6.3025207519531,-6.1344537734985,-5.9663863182068,-5.7983193397522,-5.6302518844604,-5.4621849060059,-5.2941174507141,-5.1260504722595,-4.9579830169678,-4.7899160385132,-4.6218485832214,-4.4537816047668,-4.2857141494751,-4.1176471710205,-3.9495797157288,-3.7815127372742,-3.6134452819824,-3.4453783035278,-3.2773108482361,-3.1092438697815,-2.9411764144897,-2.7731094360352,-2.6050419807434,-2.4369750022888,-2.2689075469971,-2.1008405685425,-1.9327726364136,-1.764705657959,-1.5966386795044,-1.4285717010498,-1.2605037689209,-1.0924367904663,-0.92436981201172,-0.75630283355713,-0.58823490142822,-0.42016792297363,-0.25210094451904,-0.084033966064453,0.084033966064453,0.25210094451904,0.42016792297363,0.58823490142822,0.75630283355713,0.92436981201172,1.0924367904663,1.2605037689209,1.4285717010498,1.5966386795044,1.764705657959,1.9327726364136,2.1008405685425,2.2689075469971,2.4369745254517,2.6050424575806,2.7731094360352,2.9411764144897,3.1092433929443,3.2773113250732,3.4453783035278,3.6134452819824,3.781512260437,3.9495801925659,4.1176471710205,4.2857141494751,4.4537811279297,4.6218490600586,4.7899160385132,4.9579830169678,5.1260499954224,5.2941179275513,5.4621849060059,5.6302518844604,5.798318862915,5.9663867950439,6.1344547271729,6.3025207519531,6.470588684082,6.6386547088623,6.8067226409912,6.9747905731201,7.1428565979004,7.3109245300293,7.4789924621582,7.6470584869385,7.8151264190674,7.9831924438477,8.1512603759766,8.3193283081055,8.4873943328857,8.6554622650146,8.8235301971436,8.9915962219238,9.1596641540527,9.327730178833,9.4957981109619,9.6638660430908,9.8319320678711,10],\"y\":[1.9588575553287e-12,4.5070119511492e-12,1.0224524352176e-11,2.2869773783074e-11,5.0436706894308e-11,1.0967268065931e-10,2.3513535563069e-10,4.970519551506e-10,1.0359807456339e-09,2.1289650042888e-09,4.3137391436687e-09,8.617926816612e-09,1.6975345573655e-08,3.2968664243072e-08,6.3132141292499e-08,1.1919700426688e-07,2.2189435355813e-07,4.0728127714829e-07,7.3707064984774e-07,1.3151915254639e-06,2.3138509277487e-06,4.0137383621186e-06,6.8648146225314e-06,1.1576418728509e-05,1.9248038370279e-05,3.1554754968965e-05,5.1004684792133e-05,8.1286896602251e-05,0.00012773158960044,0.00019789823272731,0.00030230978154577,0.00045533222146332,0.0006761941476725,0.00099010288249701,0.0014294069260359,0.0020346858073026,0.002855654573068,0.0039516622200608,0.0053916377946734,0.0072531658224761,0.0096205789595842,0.012581739574671,0.016223592683673,0.02062620036304,0.025855801999569,0.031956776976585,0.038943454623222,0.046792037785053,0.055434014648199,0.064751029014587,0.074573278427124,0.084681048989296,0.094810351729393,0.10466255992651,0.11391825973988,0.12225359678268,0.12935893237591,0.13495761156082,0.13882404565811,0.14079861342907,0.14079861342907,0.13882404565811,0.13495761156082,0.12935893237591,0.12225359678268,0.11391825973988,0.10466255992651,0.094810351729393,0.084681048989296,0.074573278427124,0.064751029014587,0.055434014648199,0.046792037785053,0.038943454623222,0.031956799328327,0.025855783373117,0.02062620036304,0.016223592683673,0.012581751681864,0.0096205696463585,0.0072531658224761,0.0053916377946734,0.0039516664110124,0.0028556517791003,0.0020346858073026,0.0014294069260359,0.00099010381381959,0.00067619350738823,0.00045533222146332,0.00030230978154577,0.0001978985092137,0.00012773145863321,8.1286896602251e-05,5.1004684792133e-05,3.1554787710775e-05,1.9248020180385e-05,1.1576385077205e-05,6.8648146225314e-06,4.013731086161e-06,2.3138597953221e-06,1.3151915254639e-06,7.3706780767679e-07,4.0728204453444e-07,2.2189435355813e-07,1.1919654951953e-07,6.3132141292499e-08,3.2968600294225e-08,1.6975409522502e-08,8.617926816612e-09,4.313722712368e-09,2.1289729978946e-09,1.0359807456339e-09,4.9705006777145e-10,2.3513535563069e-10,1.0967268065931e-10,5.0436901183337e-11,2.2869773783074e-11,1.0224485320898e-11,4.5070379720014e-12,1.9588575553287e-12]},\"callback\":null,\"selected\":{\"2d\":{\"indices\":[]},\"1d\":{\"indices\":[]},\"0d\":{\"indices\":[],\"flag\":false}}},\"type\":\"ColumnDataSource\"},{\"id\":\"3580e514-38c0-4ced-cc91-c8169758c759\",\"attributes\":{\"fill_color\":{\"value\":\"#ff0000\"},\"doc\":null,\"y\":{\"field\":\"y\"},\"height\":{\"units\":\"data\",\"field\":\"height\"},\"x\":{\"field\":\"x\"},\"tags\":[],\"fill_alpha\":{\"value\":0.3},\"id\":\"3580e514-38c0-4ced-cc91-c8169758c759\",\"width\":{\"units\":\"data\",\"value\":0.01}},\"type\":\"Rect\"},{\"id\":\"c5d790f0-9383-428b-c240-087a91224fc7\",\"attributes\":{\"data_source\":{\"id\":\"c26c1037-2bbb-4352-c3b3-fd7c435119d5\",\"type\":\"ColumnDataSource\"},\"id\":\"c5d790f0-9383-428b-c240-087a91224fc7\",\"tags\":[],\"nonselection_glyph\":null,\"glyph\":{\"id\":\"3580e514-38c0-4ced-cc91-c8169758c759\",\"type\":\"Rect\"},\"doc\":null,\"selection_glyph\":null},\"type\":\"GlyphRenderer\"},{\"id\":\"a334dcdb-f5f8-4990-c992-a721ea6e7bb0\",\"attributes\":{\"column_names\":[\"x\",\"y\"],\"id\":\"a334dcdb-f5f8-4990-c992-a721ea6e7bb0\",\"tags\":[],\"doc\":null,\"data\":{\"x\":[-10,-9.8319330215454,-9.6638650894165,-9.4957981109619,-9.3277311325073,-9.1596641540527,-8.9915962219238,-8.8235292434692,-8.6554622650146,-8.4873952865601,-8.3193273544312,-8.1512603759766,-7.983193397522,-7.8151259422302,-7.6470584869385,-7.4789915084839,-7.3109245300293,-7.1428570747375,-6.9747896194458,-6.8067226409912,-6.6386556625366,-6.4705882072449,-6.3025207519531,-6.1344537734985,-5.9663863182068,-5.7983193397522,-5.6302518844604,-5.4621849060059,-5.2941174507141,-5.1260504722595,-4.9579830169678,-4.7899160385132,-4.6218485832214,-4.4537816047668,-4.2857141494751,-4.1176471710205,-3.9495797157288,-3.7815127372742,-3.6134452819824,-3.4453783035278,-3.2773108482361,-3.1092438697815,-2.9411764144897,-2.7731094360352,-2.6050419807434,-2.4369750022888,-2.2689075469971,-2.1008405685425,-1.9327726364136,-1.764705657959,-1.5966386795044,-1.4285717010498,-1.2605037689209,-1.0924367904663,-0.92436981201172,-0.75630283355713,-0.58823490142822,-0.42016792297363,-0.25210094451904,-0.084033966064453,0.084033966064453,0.25210094451904,0.42016792297363,0.58823490142822,0.75630283355713,0.92436981201172,1.0924367904663,1.2605037689209,1.4285717010498,1.5966386795044,1.764705657959,1.9327726364136,2.1008405685425,2.2689075469971,2.4369745254517,2.6050424575806,2.7731094360352,2.9411764144897,3.1092433929443,3.2773113250732,3.4453783035278,3.6134452819824,3.781512260437,3.9495801925659,4.1176471710205,4.2857141494751,4.4537811279297,4.6218490600586,4.7899160385132,4.9579830169678,5.1260499954224,5.2941179275513,5.4621849060059,5.6302518844604,5.798318862915,5.9663867950439,6.1344547271729,6.3025207519531,6.470588684082,6.6386547088623,6.8067226409912,6.9747905731201,7.1428565979004,7.3109245300293,7.4789924621582,7.6470584869385,7.8151264190674,7.9831924438477,8.1512603759766,8.3193283081055,8.4873943328857,8.6554622650146,8.8235301971436,8.9915962219238,9.1596641540527,9.327730178833,9.4957981109619,9.6638660430908,9.8319320678711,10],\"y\":[3.9177151106573e-12,9.0140239022984e-12,2.0449048704352e-11,4.5739547566148e-11,1.0087341378862e-10,2.1934536131862e-10,4.7027071126138e-10,9.9410391030119e-10,2.0719614912679e-09,4.2579300085777e-09,8.6274782873375e-09,1.7235853633224e-08,3.3950691147311e-08,6.5937328486143e-08,1.26264282585e-07,2.3839400853376e-07,4.4378870711625e-07,8.1456255429657e-07,1.4741412996955e-06,2.6303830509278e-06,4.6277018554974e-06,8.0274767242372e-06,1.3729629245063e-05,2.3152837457019e-05,3.8496076740557e-05,6.3109509937931e-05,0.00010200936958427,0.0001625737932045,0.00025546317920089,0.00039579646545462,0.00060461956309155,0.00091066444292665,0.001352388295345,0.001980205764994,0.0028588138520718,0.0040693716146052,0.005711309146136,0.0079033244401217,0.010783275589347,0.014506331644952,0.019241157919168,0.025163479149342,0.032447185367346,0.04125240072608,0.051711603999138,0.063913553953171,0.077886909246445,0.093584075570107,0.1108680292964,0.12950205802917,0.14914655685425,0.16936209797859,0.18962070345879,0.20932511985302,0.22783651947975,0.24450719356537,0.25871786475182,0.26991522312164,0.27764809131622,0.28159722685814,0.28159722685814,0.27764809131622,0.26991522312164,0.25871786475182,0.24450719356537,0.22783651947975,0.20932511985302,0.18962070345879,0.16936209797859,0.14914655685425,0.12950205802917,0.1108680292964,0.093584075570107,0.077886909246445,0.063913598656654,0.051711566746235,0.04125240072608,0.032447185367346,0.025163503363729,0.019241139292717,0.014506331644952,0.010783275589347,0.0079033328220248,0.0057113035582006,0.0040693716146052,0.0028588138520718,0.0019802076276392,0.0013523870147765,0.00091066444292665,0.00060461956309155,0.0003957970184274,0.00025546291726641,0.0001625737932045,0.00010200936958427,6.3109575421549e-05,3.8496040360769e-05,2.3152770154411e-05,1.3729629245063e-05,8.027462172322e-06,4.6277195906441e-06,2.6303830509278e-06,1.4741356153536e-06,8.1456408906888e-07,4.4378870711625e-07,2.3839309903906e-07,1.26264282585e-07,6.5937200588451e-08,3.3950819045003e-08,1.7235853633224e-08,8.6274454247359e-09,4.2579459957892e-09,2.0719614912679e-09,9.9410013554291e-10,4.7027071126138e-10,2.1934536131862e-10,1.0087380236667e-10,4.5739547566148e-11,2.0448970641795e-11,9.0140759440027e-12,3.9177151106573e-12]},\"callback\":null,\"selected\":{\"2d\":{\"indices\":[]},\"1d\":{\"indices\":[]},\"0d\":{\"indices\":[],\"flag\":false}}},\"type\":\"ColumnDataSource\"},{\"id\":\"460ba0da-8ee3-45c5-c56e-3fb5908f7d33\",\"attributes\":{\"id\":\"460ba0da-8ee3-45c5-c56e-3fb5908f7d33\",\"line_alpha\":{\"value\":0.3},\"x\":{\"field\":\"x\"},\"tags\":[],\"doc\":null,\"y\":{\"field\":\"y\"},\"line_color\":{\"value\":\"#ff0000\"},\"line_width\":{\"units\":\"data\",\"value\":3}},\"type\":\"Line\"},{\"id\":\"c3590fb9-52d8-49dd-c9f0-ff565f7a06a6\",\"attributes\":{\"data_source\":{\"id\":\"a334dcdb-f5f8-4990-c992-a721ea6e7bb0\",\"type\":\"ColumnDataSource\"},\"id\":\"c3590fb9-52d8-49dd-c9f0-ff565f7a06a6\",\"tags\":[],\"nonselection_glyph\":null,\"glyph\":{\"id\":\"460ba0da-8ee3-45c5-c56e-3fb5908f7d33\",\"type\":\"Line\"},\"doc\":null,\"selection_glyph\":null},\"type\":\"GlyphRenderer\"},{\"id\":\"85a78014-8ae6-4aae-ca19-64e4a9e2186c\",\"attributes\":{\"id\":\"85a78014-8ae6-4aae-ca19-64e4a9e2186c\",\"legends\":[[\"Normal\",[{\"id\":\"c3590fb9-52d8-49dd-c9f0-ff565f7a06a6\",\"type\":\"GlyphRenderer\"}]]],\"tags\":[],\"doc\":null,\"plot\":{\"id\":\"0fcf2c2b-4429-4f0d-cfb9-d9919d9bf371\",\"type\":\"Plot\",\"subtype\":\"Chart\"}},\"type\":\"Legend\"},{\"id\":\"3c27268f-0070-4c6b-cc6b-c1c0e5c7676b\",\"attributes\":{\"column_names\":[\"x\",\"y\"],\"id\":\"3c27268f-0070-4c6b-cc6b-c1c0e5c7676b\",\"tags\":[],\"doc\":null,\"data\":{\"x\":[-10,-9.8319330215454,-9.6638650894165,-9.4957981109619,-9.3277311325073,-9.1596641540527,-8.9915962219238,-8.8235292434692,-8.6554622650146,-8.4873952865601,-8.3193273544312,-8.1512603759766,-7.983193397522,-7.8151259422302,-7.6470584869385,-7.4789915084839,-7.3109245300293,-7.1428570747375,-6.9747896194458,-6.8067226409912,-6.6386556625366,-6.4705882072449,-6.3025207519531,-6.1344537734985,-5.9663863182068,-5.7983193397522,-5.6302518844604,-5.4621849060059,-5.2941174507141,-5.1260504722595,-4.9579830169678,-4.7899160385132,-4.6218485832214,-4.4537816047668,-4.2857141494751,-4.1176471710205,-3.9495797157288,-3.7815127372742,-3.6134452819824,-3.4453783035278,-3.2773108482361,-3.1092438697815,-2.9411764144897,-2.7731094360352,-2.6050419807434,-2.4369750022888,-2.2689075469971,-2.1008405685425,-1.9327726364136,-1.764705657959,-1.5966386795044,-1.4285717010498,-1.2605037689209,-1.0924367904663,-0.92436981201172,-0.75630283355713,-0.58823490142822,-0.42016792297363,-0.25210094451904,-0.084033966064453,0.084033966064453,0.25210094451904,0.42016792297363,0.58823490142822,0.75630283355713,0.92436981201172,1.0924367904663,1.2605037689209,1.4285717010498,1.5966386795044,1.764705657959,1.9327726364136,2.1008405685425,2.2689075469971,2.4369745254517,2.6050424575806,2.7731094360352,2.9411764144897,3.1092433929443,3.2773113250732,3.4453783035278,3.6134452819824,3.781512260437,3.9495801925659,4.1176471710205,4.2857141494751,4.4537811279297,4.6218490600586,4.7899160385132,4.9579830169678,5.1260499954224,5.2941179275513,5.4621849060059,5.6302518844604,5.798318862915,5.9663867950439,6.1344547271729,6.3025207519531,6.470588684082,6.6386547088623,6.8067226409912,6.9747905731201,7.1428565979004,7.3109245300293,7.4789924621582,7.6470584869385,7.8151264190674,7.9831924438477,8.1512603759766,8.3193283081055,8.4873943328857,8.6554622650146,8.8235301971436,8.9915962219238,9.1596641540527,9.327730178833,9.4957981109619,9.6638660430908,9.8319320678711,10],\"y\":[3.9177151106573e-12,9.0140239022984e-12,2.0449048704352e-11,4.5739547566148e-11,1.0087341378862e-10,2.1934536131862e-10,4.7027071126138e-10,9.9410391030119e-10,2.0719614912679e-09,4.2579300085777e-09,8.6274782873375e-09,1.7235853633224e-08,3.3950691147311e-08,6.5937328486143e-08,1.26264282585e-07,2.3839400853376e-07,4.4378870711625e-07,8.1456255429657e-07,1.4741412996955e-06,2.6303830509278e-06,4.6277018554974e-06,8.0274767242372e-06,1.3729629245063e-05,2.3152837457019e-05,3.8496076740557e-05,6.3109509937931e-05,0.00010200936958427,0.0001625737932045,0.00025546317920089,0.00039579646545462,0.00060461956309155,0.00091066444292665,0.001352388295345,0.001980205764994,0.0028588138520718,0.0040693716146052,0.005711309146136,0.0079033244401217,0.010783275589347,0.014506331644952,0.019241157919168,0.025163479149342,0.032447185367346,0.04125240072608,0.051711603999138,0.063913553953171,0.077886909246445,0.093584075570107,0.1108680292964,0.12950205802917,0.14914655685425,0.16936209797859,0.18962070345879,0.20932511985302,0.22783651947975,0.24450719356537,0.25871786475182,0.26991522312164,0.27764809131622,0.28159722685814,0.28159722685814,0.27764809131622,0.26991522312164,0.25871786475182,0.24450719356537,0.22783651947975,0.20932511985302,0.18962070345879,0.16936209797859,0.14914655685425,0.12950205802917,0.1108680292964,0.093584075570107,0.077886909246445,0.063913598656654,0.051711566746235,0.04125240072608,0.032447185367346,0.025163503363729,0.019241139292717,0.014506331644952,0.010783275589347,0.0079033328220248,0.0057113035582006,0.0040693716146052,0.0028588138520718,0.0019802076276392,0.0013523870147765,0.00091066444292665,0.00060461956309155,0.0003957970184274,0.00025546291726641,0.0001625737932045,0.00010200936958427,6.3109575421549e-05,3.8496040360769e-05,2.3152770154411e-05,1.3729629245063e-05,8.027462172322e-06,4.6277195906441e-06,2.6303830509278e-06,1.4741356153536e-06,8.1456408906888e-07,4.4378870711625e-07,2.3839309903906e-07,1.26264282585e-07,6.5937200588451e-08,3.3950819045003e-08,1.7235853633224e-08,8.6274454247359e-09,4.2579459957892e-09,2.0719614912679e-09,9.9410013554291e-10,4.7027071126138e-10,2.1934536131862e-10,1.0087380236667e-10,4.5739547566148e-11,2.0448970641795e-11,9.0140759440027e-12,3.9177151106573e-12]},\"callback\":null,\"selected\":{\"2d\":{\"indices\":[]},\"1d\":{\"indices\":[]},\"0d\":{\"indices\":[],\"flag\":false}}},\"type\":\"ColumnDataSource\"},{\"id\":\"6fe880f9-ce5a-4823-c80d-081cf020892c\",\"attributes\":{\"id\":\"6fe880f9-ce5a-4823-c80d-081cf020892c\",\"fill_color\":{\"value\":\"#ff0000\"},\"x\":{\"field\":\"x\"},\"tags\":[],\"doc\":null,\"y\":{\"field\":\"y\"},\"fill_alpha\":{\"value\":0.8},\"size\":{\"value\":6}},\"type\":\"Circle\"},{\"id\":\"47b0bc47-2028-4381-c374-41793ca9e5ab\",\"attributes\":{\"data_source\":{\"id\":\"3c27268f-0070-4c6b-cc6b-c1c0e5c7676b\",\"type\":\"ColumnDataSource\"},\"id\":\"47b0bc47-2028-4381-c374-41793ca9e5ab\",\"tags\":[],\"nonselection_glyph\":null,\"glyph\":{\"id\":\"6fe880f9-ce5a-4823-c80d-081cf020892c\",\"type\":\"Circle\"},\"doc\":null,\"selection_glyph\":null},\"type\":\"GlyphRenderer\"},{\"id\":\"8a353caa-5def-4bf2-cb6d-6ca017514ca8\",\"attributes\":{\"id\":\"8a353caa-5def-4bf2-cb6d-6ca017514ca8\",\"end\":11.011,\"tags\":[],\"doc\":null,\"start\":-11.011,\"callback\":null},\"type\":\"Range1d\"},{\"id\":\"b6f10976-3f8d-499b-c9fa-e7c7d460bb5e\",\"attributes\":{\"id\":\"b6f10976-3f8d-499b-c9fa-e7c7d460bb5e\",\"end\":0.32,\"tags\":[],\"doc\":null,\"start\":-0.12,\"callback\":null},\"type\":\"Range1d\"}];\n", + "\n", + " if(typeof(Bokeh) !== \"undefined\") {\n", + " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", + " Bokeh.embed.inject_plot(\"9165a560-bf4e-4433-c48c-504cacda79f4\", all_models);\n", + " } else {\n", + " load_lib(bokehjs_url, function() {\n", + " console.log(\"Bokeh: BokehJS plotting callback run at\", new Date())\n", + " Bokeh.embed.inject_plot(\"9165a560-bf4e-4433-c48c-504cacda79f4\", all_models);\n", + " });\n", + " }\n", + "\n", + "}(this));\n", + "\n" + ], + "metadata": {}, + "output_type": "pyout", + "prompt_number": 10, + "text": [ + "-- impossible to show ASCII art plots" + ] + } + ], + "prompt_number": 10 + } + ], + "metadata": {} + } + ] +} \ No newline at end of file diff --git a/external/lua-llthreads2 b/external/lua-llthreads2 new file mode 160000 index 0000000..07cda72 --- /dev/null +++ b/external/lua-llthreads2 @@ -0,0 +1 @@ +Subproject commit 07cda72f53d1c1bb176417797e1ea3d2c6272931 diff --git a/external/lzmq b/external/lzmq new file mode 160000 index 0000000..72dbca0 --- /dev/null +++ b/external/lzmq @@ -0,0 +1 @@ +Subproject commit 72dbca09652bcc8f27a6ae2662f05d8220e25a2f diff --git a/ipylua.sh b/ipylua.sh new file mode 100755 index 0000000..db9f595 --- /dev/null +++ b/ipylua.sh @@ -0,0 +1,6 @@ +#!/bin/bash +how=$1 +if [[ -z $how ]]; then how="console"; fi +ipython $how --profile=IPyLua --colors=Linux --ConsoleWidget.font_size=12 +pid=$(pgrep -f "lua5.2.*profile_IPyLua.*") +if [[ ! -z $pid ]]; then kill -9 $pid; fi diff --git a/ipython_kernel.lua b/ipython_kernel.lua deleted file mode 100644 index 9ce1c44..0000000 --- a/ipython_kernel.lua +++ /dev/null @@ -1,268 +0,0 @@ ---[[ -lua_ipython_kernel - -Copyright (c) 2013 Evan Wies. All rights reserved. - -Released under the MIT License, see the LICENSE file. - -https://github.com/neomantra/lua_ipython_kernel - -usage: lua ipython_kernel.lua `connection_file` -]] - - -if #arg ~= 1 then - io.stderr:write('usage: ipython_kernel.lua `connection_filename`\n') - os.exit(-1) -end - -local json = require 'dkjson' - --- our kernel's state -local kernel = {} - --- load connection object info from JSON file -do - local connection_file = io.open(arg[1]) - if not connection_file then - io.stderr:write('couldn not open connection file "', arg[1], '")": ', err, '\n') - os.exit(-1) - end - - local connection_json, err = connection_file:read('*a') - if not connection_json then - io.stderr:write('couldn not read connection file "', arg[1], '")": ', err, '\n') - os.exit(-1) - end - - kernel.connection_obj = json.decode(connection_json) - if not kernel.connection_obj then - io.stderr:write('connection file is missing connection object\n') - os.exit(-1) - end - connection_file:close() -end - - -local zmq = require 'zmq' -local zmq_poller = require 'zmq/poller' -local z_NOBLOCK, z_POLLIN = zmq.NOBLOCK, zmq.POLL_IN -local z_RCVMORE, z_SNDMORE = zmq.RCVMORE, zmq.SNDMORE - -local uuid = require 'uuid' --- TODO: randomseed or luasocket or something else - -local username = os.getenv('USER') - - - -------------------------------------------------------------------------------- --- IPython Message ("ipmsg") functions - -local function ipmsg_to_table(parts) - local ipmsg = { ids = {}, blobs = {} } - - local i = 1 - while i <= #parts and parts[i] ~= '' do - ipmsg.ids[#ipmsg.ids + 1] = parts[i] - i = i + 1 - end - i = i + 1 - ipmsg.hmac = parts[i] ; i = i + 1 - ipmsg.header = parts[i] ; i = i + 1 - ipmsg.parent_header = parts[i] ; i = i + 1 - ipmsg.metadata = parts[i] ; i = i + 1 - ipmsg.content = parts[i] ; i = i + 1 - - while i <= #parts do - ipmsg.blobs[#ipmsg.blobs + 1] = parts[i] - i = i + 1 - end - - return ipmsg -end - - -local function ipmsg_header(session, msg_type) - return json.encode({ - msg_id = uuid.new(), -- TODO: randomness warning - username = username, - date = os.date('%Y-%m-%dT%h:%M:%s.000000'), -- TODO milliseconds - session = session, - msg_type = msg_type, - }) -end - - -local function ipmsg_recv(sock) - -- TODO: error handling - local parts, err = {} - parts[1], err = sock:recv() - while sock:getopt(z_RCVMORE) == 1 do - parts[#parts + 1], err = sock:recv() - end - return parts, err -end - - -local function ipmsg_send(sock, ids, hmac, hdr, p_hdr, meta, content, blobs) - if type(ids) == 'table' then - for _, v in ipairs(ids) do - sock:send(v, z_SNDMORE) - end - else - sock:send(ids, z_SNDMORE) - end - sock:send('', z_SNDMORE) - sock:send(hmac, z_SNDMORE) - sock:send(hdr, z_SNDMORE) - sock:send(p_hdr, z_SNDMORE) - sock:send(meta, z_SNDMORE) - if blobs then - sock:send(content, z_SNDMORE) - if type(blobs) == 'table' then - for i, v in ipairs(blobs) do - if i == #blobs then - sock:send(v) - else - sock:send(v, z_SNDMORE) - end - end - else - sock:send(blobs) - end - else - sock:send(content) - end -end - - - -------------------------------------------------------------------------------- --- ZMQ Read Handlers - -local function on_hb_read( sock ) - -- read the data and send a pong - local data, err = sock:recv(zmq.NOBLOCK) - if not data then - assert(err == 'timeout', 'Bad error on zmq socket.') - -- TODO return sock_blocked(on_sock_recv, z_POLLIN) - assert(false, "bad hb_read_data") - end - sock:send('pong') -end - -local function on_control_read( sock ) - -- read the data and send a pong - local data, err = sock:recv(zmq.NOBLOCK) - if not data then - assert(err == 'timeout', 'Bad error on zmq socket.') - -- TODO return sock_blocked(on_sock_recv, z_POLLIN) - assert(false, "bad hb_read_data") - end --- print('control', data) -end - -local function on_stdin_read( sock ) - -- read the data and send a pong - local data, err = sock:recv(zmq.NOBLOCK) - if not data then - assert(err == 'timeout', 'Bad error on zmq socket.') - -- TODO return sock_blocked(on_sock_recv, z_POLLIN) - assert(false, "bad hb_read_data") - end --- print('stdin', data) -end - - - -local function on_shell_read( sock ) - - local ipmsg, err = ipmsg_recv(sock) - local msg = ipmsg_to_table(ipmsg) - - for k, v in pairs(msg) do print(k,v) end - - local header_obj = json.decode(msg.header) - if header_obj.msg_type == 'kernel_info_request' then - local header = ipmsg_header( header_obj.session, 'kernel_info_reply' ) - local content = json.encode({ - protocol_version = {4, 0}, - language_version = {5, 1}, - language = 'lua', - }) - - ipmsg_send(sock, header_obj.session, '', header, msg.header, '{}', content) - - elseif header_obj.msg_type == 'execute_request' then - - local header = ipmsg_header( header_obj.session, 'execute_reply' ) - kernel.execution_count = kernel.execution_count + 1 - local content = json.encode({ - status = 'ok', - execution_count = kernel.execution_count, - }) - - ipmsg_send(sock, header_obj.session, '', header, msg.header, '{}', content) - - end - -end - -local function on_iopub_read( sock ) - -- read the data and send a pong - local data, err = sock:recv(zmq.NOBLOCK) - if not data then - assert(err == 'timeout', 'Bad error on zmq socket.') - -- TODO return sock_blocked(on_sock_recv, z_POLLIN) - assert(false, "bad hb_read_data") - end --- print('iopub', data) - -end - - -------------------------------------------------------------------------------- --- SETUP - -local kernel_sockets = { - { name = 'heartbeat_sock', sock_type = zmq.REP, port = 'hb', handler = on_hb_read }, - { name = 'control_sock', sock_type = zmq.ROUTER, port = 'control', handler = on_control_read }, - { name = 'stdin_sock', sock_type = zmq.ROUTER, port = 'stdin', handler = on_stdin_read }, - { name = 'shell_sock', sock_type = zmq.ROUTER, port = 'shell', handler = on_shell_read }, - { name = 'iopub_sock', sock_type = zmq.PUB, port = 'iopub', handler = on_iopub_read }, -} - -local z_ctx = zmq.init() -local z_poller = zmq_poller(#kernel_sockets) -for _, v in ipairs(kernel_sockets) do - -- TODO: error handling in here - local sock = z_ctx:socket(v.sock_type) - - local conn_obj = kernel.connection_obj - local addr = string.format('%s://%s:%s', - conn_obj.transport, - conn_obj.ip, - conn_obj[v.port..'_port']) - --- io.stderr:write(string.format('binding %s to %s\n', v.name, addr)) - sock:bind(addr) - - z_poller:add(sock, zmq.POLLIN, v.handler) - - kernel[v.name] = sock -end - -kernel.execution_count = 0 - - -------------------------------------------------------------------------------- --- POLL then SHUTDOWN - ---print("Starting poll") -z_poller:start() - -for _, v in ipairs(kernel_sockets) do - kernel[v.name]:close() -end -z_ctx:term()