mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Reduction of moose.lua sizing working now!
This commit is contained in:
270
Utils/luarocks/share/lua/5.1/defaultcss.lua
Normal file
270
Utils/luarocks/share/lua/5.1/defaultcss.lua
Normal file
@@ -0,0 +1,270 @@
|
||||
return [[html {
|
||||
color: #000;
|
||||
background: #FFF;
|
||||
}
|
||||
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
fieldset,img {
|
||||
border: 0;
|
||||
}
|
||||
address,caption,cite,code,dfn,em,strong,th,var,optgroup {
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
del,ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
li {
|
||||
list-style: bullet;
|
||||
margin-left: 20px;
|
||||
}
|
||||
caption,th {
|
||||
text-align: left;
|
||||
}
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
q:before,q:after {
|
||||
content: '';
|
||||
}
|
||||
abbr,acronym {
|
||||
border: 0;
|
||||
font-variant: normal;
|
||||
}
|
||||
sup {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
sub {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
legend {
|
||||
color: #000;
|
||||
}
|
||||
input,button,textarea,select,optgroup,option {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
input,button,textarea,select {*font-size:100%;
|
||||
}
|
||||
/* END RESET */
|
||||
|
||||
body {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
font-family: arial, helvetica, geneva, sans-serif;
|
||||
background-color: #ffffff; margin: 0px;
|
||||
}
|
||||
|
||||
code, tt { font-family: monospace; }
|
||||
|
||||
body, p, td, th { font-size: .95em; line-height: 1.2em;}
|
||||
|
||||
p, ul { margin: 10px 0 0 10px;}
|
||||
|
||||
strong { font-weight: bold;}
|
||||
|
||||
em { font-style: italic;}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 25px 0 20px 0;
|
||||
}
|
||||
h2, h3, h4 { margin: 15px 0 10px 0; }
|
||||
h2 { font-size: 1.25em; }
|
||||
h3 { font-size: 1.15em; }
|
||||
h4 { font-size: 1.06em; }
|
||||
|
||||
a:link { font-weight: bold; color: #004080; text-decoration: none; }
|
||||
a:visited { font-weight: bold; color: #006699; text-decoration: none; }
|
||||
a:link:hover { text-decoration: underline; }
|
||||
|
||||
hr {
|
||||
color:#cccccc;
|
||||
background: #00007f;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
blockquote { margin-left: 3em; }
|
||||
|
||||
ul { list-style-type: disc; }
|
||||
|
||||
p.name {
|
||||
font-family: "Andale Mono", monospace;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
pre.example {
|
||||
background-color: rgb(245, 245, 245);
|
||||
border: 1px solid silver;
|
||||
padding: 10px;
|
||||
margin: 10px 0 10px 0;
|
||||
font-family: "Andale Mono", monospace;
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: rgb(245, 245, 245);
|
||||
border: 1px solid silver;
|
||||
padding: 10px;
|
||||
margin: 10px 0 10px 0;
|
||||
font-family: "Andale Mono", monospace;
|
||||
}
|
||||
|
||||
|
||||
table.index { border: 1px #00007f; }
|
||||
table.index td { text-align: left; vertical-align: top; }
|
||||
|
||||
#container {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#product {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#product big {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
#main {
|
||||
background-color: #f0f0f0;
|
||||
border-left: 2px solid #cccccc;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
float: left;
|
||||
width: 18em;
|
||||
vertical-align: top;
|
||||
background-color: #f0f0f0;
|
||||
overflow: scroll;
|
||||
position: fixed;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#navigation h2 {
|
||||
background-color:#e7e7e7;
|
||||
font-size:1.1em;
|
||||
color:#000000;
|
||||
text-align: left;
|
||||
padding:0.2em;
|
||||
border-top:1px solid #dddddd;
|
||||
border-bottom:1px solid #dddddd;
|
||||
}
|
||||
|
||||
#navigation ul
|
||||
{
|
||||
font-size:1em;
|
||||
list-style-type: none;
|
||||
margin: 1px 1px 10px 1px;
|
||||
}
|
||||
|
||||
#navigation li {
|
||||
text-indent: -1em;
|
||||
display: block;
|
||||
margin: 3px 0px 0px 22px;
|
||||
}
|
||||
|
||||
#navigation li li a {
|
||||
margin: 0px 3px 0px -1em;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-left: 18em;
|
||||
padding: 1em;
|
||||
border-left: 2px solid #cccccc;
|
||||
border-right: 2px solid #cccccc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#about {
|
||||
clear: both;
|
||||
padding: 5px;
|
||||
border-top: 2px solid #cccccc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
font: 12pt "Times New Roman", "TimeNR", Times, serif;
|
||||
}
|
||||
a { font-weight: bold; color: #004080; text-decoration: underline; }
|
||||
|
||||
#main {
|
||||
background-color: #ffffff;
|
||||
border-left: 0px;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-left: 2%;
|
||||
margin-right: 2%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 1em;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
display: none;
|
||||
}
|
||||
pre.example {
|
||||
font-family: "Andale Mono", monospace;
|
||||
font-size: 10pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
table.module_list {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #cccccc;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.module_list td {
|
||||
border-width: 1px;
|
||||
padding: 3px;
|
||||
border-style: solid;
|
||||
border-color: #cccccc;
|
||||
}
|
||||
table.module_list td.name { background-color: #f0f0f0; }
|
||||
table.module_list td.summary { width: 100%; }
|
||||
|
||||
|
||||
table.function_list {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #cccccc;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.function_list td {
|
||||
border-width: 1px;
|
||||
padding: 3px;
|
||||
border-style: solid;
|
||||
border-color: #cccccc;
|
||||
}
|
||||
table.function_list td.name { background-color: #f0f0f0; }
|
||||
table.function_list td.summary { width: 100%; }
|
||||
|
||||
dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
|
||||
dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
|
||||
dl.table h3, dl.function h3 {font-size: .95em;}
|
||||
|
||||
]]
|
||||
87
Utils/luarocks/share/lua/5.1/docgenerator.lua
Normal file
87
Utils/luarocks/share/lua/5.1/docgenerator.lua
Normal file
@@ -0,0 +1,87 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Load documentation generator and update its path
|
||||
--
|
||||
local templateengine = require 'templateengine'
|
||||
for name, def in pairs( require 'template.utils' ) do
|
||||
templateengine.env [ name ] = def
|
||||
end
|
||||
|
||||
-- Load documentation extractor and set handled languages
|
||||
local lddextractor = require 'lddextractor'
|
||||
|
||||
local M = {}
|
||||
M.defaultsitemainpagename = 'index'
|
||||
|
||||
function M.generatedocforfiles(filenames, cssname,noheuristic)
|
||||
if not filenames then return nil, 'No files provided.' end
|
||||
--
|
||||
-- Generate API model elements for all files
|
||||
--
|
||||
local generatedfiles = {}
|
||||
local wrongfiles = {}
|
||||
for _, filename in pairs( filenames ) do
|
||||
-- Load file content
|
||||
local file, error = io.open(filename, 'r')
|
||||
if not file then return nil, 'Unable to read "'..filename..'"\n'..err end
|
||||
local code = file:read('*all')
|
||||
file:close()
|
||||
-- Get module for current file
|
||||
local apimodule, err = lddextractor.generateapimodule(filename, code,noheuristic)
|
||||
|
||||
-- Handle modules with module name
|
||||
if apimodule and apimodule.name then
|
||||
generatedfiles[ apimodule.name ] = apimodule
|
||||
elseif not apimodule then
|
||||
-- Track faulty files
|
||||
table.insert(wrongfiles, 'Unable to extract comments from "'..filename..'".\n'..err)
|
||||
elseif not apimodule.name then
|
||||
-- Do not generate documentation for unnamed modules
|
||||
table.insert(wrongfiles, 'Unable to create documentation for "'..filename..'", no module name provided.')
|
||||
end
|
||||
end
|
||||
--
|
||||
-- Defining index, which will summarize all modules
|
||||
--
|
||||
local index = {
|
||||
modules = generatedfiles,
|
||||
name = M.defaultsitemainpagename,
|
||||
tag='index'
|
||||
}
|
||||
generatedfiles[ M.defaultsitemainpagename ] = index
|
||||
|
||||
--
|
||||
-- Define page cursor
|
||||
--
|
||||
local page = {
|
||||
currentmodule = nil,
|
||||
headers = { [[<link rel="stylesheet" href="]].. cssname ..[[" type="text/css"/>]] },
|
||||
modules = generatedfiles,
|
||||
tag = 'page'
|
||||
}
|
||||
|
||||
--
|
||||
-- Iterate over modules, generating complete doc pages
|
||||
--
|
||||
for _, module in pairs( generatedfiles ) do
|
||||
-- Update current cursor page
|
||||
page.currentmodule = module
|
||||
-- Generate page
|
||||
local content, error = templateengine.applytemplate(page)
|
||||
if not content then return nil, error end
|
||||
module.body = content
|
||||
end
|
||||
return generatedfiles, wrongfiles
|
||||
end
|
||||
return M
|
||||
102
Utils/luarocks/share/lua/5.1/extractors.lua
Normal file
102
Utils/luarocks/share/lua/5.1/extractors.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local M = {}
|
||||
require 'metalua.loader'
|
||||
local compiler = require 'metalua.compiler'
|
||||
local mlc = compiler.new()
|
||||
local Q = require 'metalua.treequery'
|
||||
|
||||
-- Enable to retrieve all Javadoc-like comments from C code
|
||||
function M.c(code)
|
||||
if not code then return nil, 'No code provided' end
|
||||
local comments = {}
|
||||
-- Loop over comments stripping cosmetic '*'
|
||||
for comment in code:gmatch('%s*/%*%*+(.-)%*+/') do
|
||||
-- All Lua special comment are prefixed with an '-',
|
||||
-- so we also comment C comment to make them compliant
|
||||
table.insert(comments, '-'..comment)
|
||||
end
|
||||
return comments
|
||||
end
|
||||
|
||||
-- Enable to retrieve "---" comments from Lua code
|
||||
function M.lua( code )
|
||||
if not code then return nil, 'No code provided' end
|
||||
|
||||
-- manage shebang
|
||||
if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end
|
||||
|
||||
-- check for errors
|
||||
local f, err = loadstring(code,'source_to_check')
|
||||
if not f then
|
||||
return nil, 'Syntax error.\n' .. err
|
||||
end
|
||||
|
||||
-- Get ast from file
|
||||
local status, ast = pcall(mlc.src_to_ast, mlc, code)
|
||||
--
|
||||
-- Detect parsing errors
|
||||
--
|
||||
if not status then
|
||||
return nil, 'There might be a syntax error.\n' .. ast
|
||||
end
|
||||
|
||||
--
|
||||
-- Extract commented nodes from AST
|
||||
--
|
||||
|
||||
-- Function enabling commented node selection
|
||||
local function acceptcommentednode(node)
|
||||
return node.lineinfo and ( node.lineinfo.last.comments or node.lineinfo.first.comments )
|
||||
end
|
||||
|
||||
-- Fetch commented node from AST
|
||||
local commentednodes = Q(ast):filter( acceptcommentednode ):list()
|
||||
|
||||
-- Comment cache to avoid selecting same comment twice
|
||||
local commentcache = {}
|
||||
-- Will contain selected comments
|
||||
local comments = {}
|
||||
|
||||
-- Loop over commented nodes
|
||||
for _, node in ipairs( commentednodes ) do
|
||||
|
||||
-- A node can is relateds to comment before and after itself,
|
||||
-- the following gathers them.
|
||||
local commentlists = {}
|
||||
if node.lineinfo and node.lineinfo.first.comments then
|
||||
table.insert(commentlists, node.lineinfo.first.comments)
|
||||
end
|
||||
if node.lineinfo and node.lineinfo.last.comments then
|
||||
table.insert(commentlists, node.lineinfo.last.comments)
|
||||
end
|
||||
-- Now that we have comments before and fater the node,
|
||||
-- collect them in a single table
|
||||
for _, list in ipairs( commentlists ) do
|
||||
for _, commenttable in ipairs(list) do
|
||||
-- Only select special comments
|
||||
local firstcomment = #commenttable > 0 and #commenttable[1] > 0 and commenttable[1]
|
||||
if firstcomment:sub(1, 1) == '-' then
|
||||
for _, comment in ipairs( commenttable ) do
|
||||
-- Only comments which were not already collected
|
||||
if not commentcache[comment] then
|
||||
commentcache[comment] = true
|
||||
table.insert(comments, comment)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return comments
|
||||
end
|
||||
return M
|
||||
130
Utils/luarocks/share/lua/5.1/fs/lfs.lua
Normal file
130
Utils/luarocks/share/lua/5.1/fs/lfs.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local lfs = require 'lfs'
|
||||
local M = {}
|
||||
local function iswindows()
|
||||
local p = io.popen("echo %os%")
|
||||
if not p then
|
||||
return false
|
||||
end
|
||||
local result =p:read("*l")
|
||||
p:close()
|
||||
return result == "Windows_NT"
|
||||
end
|
||||
M.separator = iswindows() and [[\]] or [[/]]
|
||||
---
|
||||
-- Will recursively browse given directories and list files encountered
|
||||
-- @param tab Table, list where files will be added
|
||||
-- @param dirorfiles list of path to browse in order to build list.
|
||||
-- Files from this list will be added to <code>tab</code> list.
|
||||
-- @return <code>tab</code> list, table containing all files from directories
|
||||
-- and files contained in <code>dirorfile</code>
|
||||
local function appendfiles(tab, dirorfile)
|
||||
|
||||
-- Nothing to process
|
||||
if #dirorfile < 1 then return tab end
|
||||
|
||||
-- Append all files to list
|
||||
local dirs = {}
|
||||
for _, path in ipairs( dirorfile ) do
|
||||
-- Determine element nature
|
||||
local elementnature = lfs.attributes (path, "mode")
|
||||
|
||||
-- Handle files
|
||||
if elementnature == 'file' then
|
||||
table.insert(tab, path)
|
||||
else if elementnature == 'directory' then
|
||||
|
||||
-- Check if folder is accessible
|
||||
local status, error = pcall(lfs.dir, path)
|
||||
if not status then return nil, error end
|
||||
|
||||
--
|
||||
-- Handle folders
|
||||
--
|
||||
for diskelement in lfs.dir(path) do
|
||||
|
||||
-- Format current file name
|
||||
local currentfilename
|
||||
if path:sub(#path) == M.separator then
|
||||
currentfilename = path .. diskelement
|
||||
else
|
||||
currentfilename = path .. M.separator .. diskelement
|
||||
end
|
||||
|
||||
-- Handle folder elements
|
||||
local nature, err = lfs.attributes (currentfilename, "mode")
|
||||
-- Append file to current list
|
||||
if nature == 'file' then
|
||||
table.insert(tab, currentfilename)
|
||||
elseif nature == 'directory' then
|
||||
-- Avoid current and parent directory in order to avoid
|
||||
-- endless recursion
|
||||
if diskelement ~= '.' and diskelement ~= '..' then
|
||||
-- Handle subfolders
|
||||
table.insert(dirs, currentfilename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- If we only encountered files, going deeper is useless
|
||||
if #dirs == 0 then return tab end
|
||||
-- Append files from encountered directories
|
||||
return appendfiles(tab, dirs)
|
||||
end
|
||||
---
|
||||
-- Provide a list of files from a directory
|
||||
-- @param list Table of directories to browse
|
||||
-- @return table of string, path to files contained in given directories
|
||||
function M.filelist(list)
|
||||
if not list then return nil, 'No directory list provided' end
|
||||
return appendfiles({}, list)
|
||||
end
|
||||
function M.checkdirectory( dirlist )
|
||||
if not dirlist then return false end
|
||||
local missingdirs = {}
|
||||
for _, filename in ipairs( dirlist ) do
|
||||
if not lfs.attributes(filename, 'mode') then
|
||||
table.insert(missingdirs, filename)
|
||||
end
|
||||
end
|
||||
if #missingdirs > 0 then
|
||||
return false, missingdirs
|
||||
end
|
||||
return true
|
||||
end
|
||||
function M.fill(filename, content)
|
||||
--
|
||||
-- Ensure parent directory exists
|
||||
--
|
||||
local parent = filename:gmatch([[(.*)]] .. M.separator ..[[(.+)]])()
|
||||
local parentnature = lfs.attributes(parent, 'mode')
|
||||
-- Create parent directory while absent
|
||||
if not parentnature then
|
||||
lfs.mkdir( parent )
|
||||
elseif parentnature ~= 'directory' then
|
||||
-- Notify that disk element already exists
|
||||
return nil, parent..' is a '..parentnature..'.'
|
||||
end
|
||||
|
||||
-- Create actual file
|
||||
local file, error = io.open(filename, 'w')
|
||||
if not file then
|
||||
return nil, error
|
||||
end
|
||||
file:write( content )
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
return M
|
||||
113
Utils/luarocks/share/lua/5.1/lddextractor.lua
Normal file
113
Utils/luarocks/share/lua/5.1/lddextractor.lua
Normal file
@@ -0,0 +1,113 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
require 'metalua.loader'
|
||||
local compiler = require 'metalua.compiler'
|
||||
local mlc = compiler.new()
|
||||
local M = {}
|
||||
|
||||
--
|
||||
-- Define default supported languages
|
||||
--
|
||||
M.supportedlanguages = {}
|
||||
local extractors = require 'extractors'
|
||||
|
||||
-- Support Lua comment extracting
|
||||
M.supportedlanguages['lua'] = extractors.lua
|
||||
|
||||
-- Support C comment extracting
|
||||
for _,c in ipairs({'c', 'cpp', 'c++'}) do
|
||||
M.supportedlanguages[c] = extractors.c
|
||||
end
|
||||
|
||||
-- Extract comment from code,
|
||||
-- type of code is deduced from filename extension
|
||||
function M.extract(filename, code)
|
||||
-- Check parameters
|
||||
if not code then return nil, 'No code provided' end
|
||||
if type(filename) ~= "string" then
|
||||
return nil, 'No string for file name provided'
|
||||
end
|
||||
|
||||
-- Extract file extension
|
||||
local fileextension = filename:gmatch('.*%.(.*)')()
|
||||
if not fileextension then
|
||||
return nil, 'File '..filename..' has no extension, could not determine how to extract documentation.'
|
||||
end
|
||||
|
||||
-- Check if it is possible to extract documentation from these files
|
||||
local extractor = M.supportedlanguages[ fileextension ]
|
||||
if not extractor then
|
||||
return nil, 'Unable to extract documentation from '.. fileextension .. ' file.'
|
||||
end
|
||||
return extractor( code )
|
||||
end
|
||||
-- Generate a file gathering only comments from given code
|
||||
function M.generatecommentfile(filename, code)
|
||||
local comments, error = M.extract(filename, code)
|
||||
if not comments then
|
||||
return nil, 'Unable to generate comment file.\n'..error
|
||||
end
|
||||
local filecontent = {}
|
||||
for _, comment in ipairs( comments ) do
|
||||
table.insert(filecontent, "--[[")
|
||||
table.insert(filecontent, comment)
|
||||
table.insert(filecontent, "\n]]\n\n")
|
||||
end
|
||||
return table.concat(filecontent)..'return nil\n'
|
||||
end
|
||||
-- Create API Model module from a 'comment only' lua file
|
||||
function M.generateapimodule(filename, code,noheuristic)
|
||||
if not filename then return nil, 'No file name given.' end
|
||||
if not code then return nil, 'No code provided.' end
|
||||
if type(filename) ~= "string" then return nil, 'No string for file name provided' end
|
||||
|
||||
-- for non lua file get comment file
|
||||
if filename:gmatch('.*%.(.*)')() ~= 'lua' then
|
||||
local err
|
||||
code, err = M.generatecommentfile(filename, code)
|
||||
if not code then
|
||||
return nil, 'Unable to create api module for "'..filename..'".\n'..err
|
||||
end
|
||||
else
|
||||
|
||||
-- manage shebang
|
||||
if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end
|
||||
|
||||
-- check for errors
|
||||
local f, err = loadstring(code,'source_to_check')
|
||||
if not f then
|
||||
return nil, 'File'..filename..'contains syntax error.\n' .. err
|
||||
end
|
||||
end
|
||||
|
||||
local status, ast = pcall(mlc.src_to_ast, mlc, code)
|
||||
if not status then
|
||||
return nil, 'Unable to compute ast for "'..filename..'".\n'..ast
|
||||
end
|
||||
|
||||
-- Extract module name as the filename without extension
|
||||
local modulename
|
||||
local matcher = string.gmatch(filename,'.*/(.*)%..*$')
|
||||
if matcher then modulename = matcher() end
|
||||
|
||||
-- Create api model
|
||||
local apimodelbuilder = require 'models.apimodelbuilder'
|
||||
local _file, comment2apiobj = apimodelbuilder.createmoduleapi(ast, modulename)
|
||||
|
||||
-- Create internal model
|
||||
if not noheuristic then
|
||||
local internalmodelbuilder = require "models.internalmodelbuilder"
|
||||
local _internalcontent = internalmodelbuilder.createinternalcontent(ast,_file,comment2apiobj, modulename)
|
||||
end
|
||||
return _file
|
||||
end
|
||||
return M
|
||||
465
Utils/luarocks/share/lua/5.1/luasrcdiet/equiv.lua
Normal file
465
Utils/luarocks/share/lua/5.1/luasrcdiet/equiv.lua
Normal file
@@ -0,0 +1,465 @@
|
||||
---------
|
||||
-- Source and binary equivalency comparisons
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * Intended as an extra safety check for mission-critical code,
|
||||
-- should give affirmative results if everything works.
|
||||
-- * Heavy on load() and string.dump(), which may be slowish,
|
||||
-- and may cause problems for cross-compiled applications.
|
||||
-- * Optional detailed information dump is mainly for debugging,
|
||||
-- reason being, if the two are not equivalent when they should be,
|
||||
-- then some form of optimization has failed.
|
||||
-- * source: IMPORTANT: TK_NAME not compared if opt-locals enabled.
|
||||
-- * binary: IMPORTANT: Some shortcuts are taken with int and size_t
|
||||
-- value reading -- if the functions break, then the binary chunk
|
||||
-- is very large indeed.
|
||||
-- * binary: There is a lack of diagnostic information when a compare
|
||||
-- fails; you can use ChunkSpy and compare using visual diff.
|
||||
----
|
||||
local byte = string.byte
|
||||
local dump = string.dump
|
||||
local load = loadstring or load --luacheck: ignore 113
|
||||
local sub = string.sub
|
||||
|
||||
local M = {}
|
||||
|
||||
local is_realtoken = { -- significant (grammar) tokens
|
||||
TK_KEYWORD = true,
|
||||
TK_NAME = true,
|
||||
TK_NUMBER = true,
|
||||
TK_STRING = true,
|
||||
TK_LSTRING = true,
|
||||
TK_OP = true,
|
||||
TK_EOS = true,
|
||||
}
|
||||
|
||||
local option, llex, warn
|
||||
|
||||
|
||||
--- The initialization function.
|
||||
--
|
||||
-- @tparam {[string]=bool,...} _option
|
||||
-- @tparam luasrcdiet.llex _llex
|
||||
-- @tparam table _warn
|
||||
function M.init(_option, _llex, _warn)
|
||||
option = _option
|
||||
llex = _llex
|
||||
warn = _warn
|
||||
end
|
||||
|
||||
--- Builds lists containing a 'normal' lexer stream.
|
||||
--
|
||||
-- @tparam string s The source code.
|
||||
-- @treturn table
|
||||
-- @treturn table
|
||||
local function build_stream(s)
|
||||
local stok, sseminfo = llex.lex(s) -- source list (with whitespace elements)
|
||||
local tok, seminfo -- processed list (real elements only)
|
||||
= {}, {}
|
||||
for i = 1, #stok do
|
||||
local t = stok[i]
|
||||
if is_realtoken[t] then
|
||||
tok[#tok + 1] = t
|
||||
seminfo[#seminfo + 1] = sseminfo[i]
|
||||
end
|
||||
end--for
|
||||
return tok, seminfo
|
||||
end
|
||||
|
||||
-- Tests source (lexer stream) equivalence.
|
||||
--
|
||||
-- @tparam string z
|
||||
-- @tparam string dat
|
||||
function M.source(z, dat)
|
||||
|
||||
-- Returns a dumped string for seminfo compares.
|
||||
local function dumpsem(s)
|
||||
local sf = load("return "..s, "z")
|
||||
if sf then
|
||||
return dump(sf)
|
||||
end
|
||||
end
|
||||
|
||||
-- Marks and optionally reports non-equivalence.
|
||||
local function bork(msg)
|
||||
if option.DETAILS then print("SRCEQUIV: "..msg) end
|
||||
warn.SRC_EQUIV = true
|
||||
end
|
||||
|
||||
-- Get lexer streams for both source strings, compare.
|
||||
local tok1, seminfo1 = build_stream(z) -- original
|
||||
local tok2, seminfo2 = build_stream(dat) -- compressed
|
||||
|
||||
-- Compare shbang lines ignoring EOL.
|
||||
local sh1 = z:match("^(#[^\r\n]*)")
|
||||
local sh2 = dat:match("^(#[^\r\n]*)")
|
||||
if sh1 or sh2 then
|
||||
if not sh1 or not sh2 or sh1 ~= sh2 then
|
||||
bork("shbang lines different")
|
||||
end
|
||||
end
|
||||
|
||||
-- Compare by simple count.
|
||||
if #tok1 ~= #tok2 then
|
||||
bork("count "..#tok1.." "..#tok2)
|
||||
return
|
||||
end
|
||||
|
||||
-- Compare each element the best we can.
|
||||
for i = 1, #tok1 do
|
||||
local t1, t2 = tok1[i], tok2[i]
|
||||
local s1, s2 = seminfo1[i], seminfo2[i]
|
||||
if t1 ~= t2 then -- by type
|
||||
bork("type ["..i.."] "..t1.." "..t2)
|
||||
break
|
||||
end
|
||||
if t1 == "TK_KEYWORD" or t1 == "TK_NAME" or t1 == "TK_OP" then
|
||||
if t1 == "TK_NAME" and option["opt-locals"] then
|
||||
-- can't compare identifiers of locals that are optimized
|
||||
elseif s1 ~= s2 then -- by semantic info (simple)
|
||||
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
|
||||
break
|
||||
end
|
||||
elseif t1 == "TK_EOS" then
|
||||
-- no seminfo to compare
|
||||
else-- "TK_NUMBER" or "TK_STRING" or "TK_LSTRING"
|
||||
-- compare 'binary' form, so dump a function
|
||||
local s1b,s2b = dumpsem(s1), dumpsem(s2)
|
||||
if not s1b or not s2b or s1b ~= s2b then
|
||||
bork("seminfo ["..i.."] "..t1.." "..s1.." "..s2)
|
||||
break
|
||||
end
|
||||
end
|
||||
end--for
|
||||
|
||||
-- Successful comparison if end is reached with no borks.
|
||||
end
|
||||
|
||||
--- Tests binary chunk equivalence (only for PUC Lua 5.1).
|
||||
--
|
||||
-- @tparam string z
|
||||
-- @tparam string dat
|
||||
function M.binary(z, dat)
|
||||
local TNIL = 0 --luacheck: ignore 211
|
||||
local TBOOLEAN = 1
|
||||
local TNUMBER = 3
|
||||
local TSTRING = 4
|
||||
|
||||
-- sizes of data types
|
||||
local endian
|
||||
local sz_int
|
||||
local sz_sizet
|
||||
local sz_inst
|
||||
local sz_number
|
||||
local getint
|
||||
local getsizet
|
||||
|
||||
-- Marks and optionally reports non-equivalence.
|
||||
local function bork(msg)
|
||||
if option.DETAILS then print("BINEQUIV: "..msg) end
|
||||
warn.BIN_EQUIV = true
|
||||
end
|
||||
|
||||
-- Checks if bytes exist.
|
||||
local function ensure(c, sz)
|
||||
if c.i + sz - 1 > c.len then return end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Skips some bytes.
|
||||
local function skip(c, sz)
|
||||
if not sz then sz = 1 end
|
||||
c.i = c.i + sz
|
||||
end
|
||||
|
||||
-- Returns a byte value.
|
||||
local function getbyte(c)
|
||||
local i = c.i
|
||||
if i > c.len then return end
|
||||
local d = sub(c.dat, i, i)
|
||||
c.i = i + 1
|
||||
return byte(d)
|
||||
end
|
||||
|
||||
-- Return an int value (little-endian).
|
||||
local function getint_l(c)
|
||||
local n, scale = 0, 1
|
||||
if not ensure(c, sz_int) then return end
|
||||
for _ = 1, sz_int do
|
||||
n = n + scale * getbyte(c)
|
||||
scale = scale * 256
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Returns an int value (big-endian).
|
||||
local function getint_b(c)
|
||||
local n = 0
|
||||
if not ensure(c, sz_int) then return end
|
||||
for _ = 1, sz_int do
|
||||
n = n * 256 + getbyte(c)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Returns a size_t value (little-endian).
|
||||
local function getsizet_l(c)
|
||||
local n, scale = 0, 1
|
||||
if not ensure(c, sz_sizet) then return end
|
||||
for _ = 1, sz_sizet do
|
||||
n = n + scale * getbyte(c)
|
||||
scale = scale * 256
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Returns a size_t value (big-endian).
|
||||
local function getsizet_b(c)
|
||||
local n = 0
|
||||
if not ensure(c, sz_sizet) then return end
|
||||
for _ = 1, sz_sizet do
|
||||
n = n * 256 + getbyte(c)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
-- Returns a block (as a string).
|
||||
local function getblock(c, sz)
|
||||
local i = c.i
|
||||
local j = i + sz - 1
|
||||
if j > c.len then return end
|
||||
local d = sub(c.dat, i, j)
|
||||
c.i = i + sz
|
||||
return d
|
||||
end
|
||||
|
||||
-- Returns a string.
|
||||
local function getstring(c)
|
||||
local n = getsizet(c)
|
||||
if not n then return end
|
||||
if n == 0 then return "" end
|
||||
return getblock(c, n)
|
||||
end
|
||||
|
||||
-- Compares byte value.
|
||||
local function goodbyte(c1, c2)
|
||||
local b1, b2 = getbyte(c1), getbyte(c2)
|
||||
if not b1 or not b2 or b1 ~= b2 then
|
||||
return
|
||||
end
|
||||
return b1
|
||||
end
|
||||
|
||||
-- Compares byte value.
|
||||
local function badbyte(c1, c2)
|
||||
local b = goodbyte(c1, c2)
|
||||
if not b then return true end
|
||||
end
|
||||
|
||||
-- Compares int value.
|
||||
local function goodint(c1, c2)
|
||||
local i1, i2 = getint(c1), getint(c2)
|
||||
if not i1 or not i2 or i1 ~= i2 then
|
||||
return
|
||||
end
|
||||
return i1
|
||||
end
|
||||
|
||||
-- Recursively-called function to compare function prototypes.
|
||||
local function getfunc(c1, c2)
|
||||
-- source name (ignored)
|
||||
if not getstring(c1) or not getstring(c2) then
|
||||
bork("bad source name"); return
|
||||
end
|
||||
-- linedefined (ignored)
|
||||
if not getint(c1) or not getint(c2) then
|
||||
bork("bad linedefined"); return
|
||||
end
|
||||
-- lastlinedefined (ignored)
|
||||
if not getint(c1) or not getint(c2) then
|
||||
bork("bad lastlinedefined"); return
|
||||
end
|
||||
if not (ensure(c1, 4) and ensure(c2, 4)) then
|
||||
bork("prototype header broken")
|
||||
end
|
||||
-- nups (compared)
|
||||
if badbyte(c1, c2) then
|
||||
bork("bad nups"); return
|
||||
end
|
||||
-- numparams (compared)
|
||||
if badbyte(c1, c2) then
|
||||
bork("bad numparams"); return
|
||||
end
|
||||
-- is_vararg (compared)
|
||||
if badbyte(c1, c2) then
|
||||
bork("bad is_vararg"); return
|
||||
end
|
||||
-- maxstacksize (compared)
|
||||
if badbyte(c1, c2) then
|
||||
bork("bad maxstacksize"); return
|
||||
end
|
||||
-- code (compared)
|
||||
local ncode = goodint(c1, c2)
|
||||
if not ncode then
|
||||
bork("bad ncode"); return
|
||||
end
|
||||
local code1 = getblock(c1, ncode * sz_inst)
|
||||
local code2 = getblock(c2, ncode * sz_inst)
|
||||
if not code1 or not code2 or code1 ~= code2 then
|
||||
bork("bad code block"); return
|
||||
end
|
||||
-- constants (compared)
|
||||
local nconst = goodint(c1, c2)
|
||||
if not nconst then
|
||||
bork("bad nconst"); return
|
||||
end
|
||||
for _ = 1, nconst do
|
||||
local ctype = goodbyte(c1, c2)
|
||||
if not ctype then
|
||||
bork("bad const type"); return
|
||||
end
|
||||
if ctype == TBOOLEAN then
|
||||
if badbyte(c1, c2) then
|
||||
bork("bad boolean value"); return
|
||||
end
|
||||
elseif ctype == TNUMBER then
|
||||
local num1 = getblock(c1, sz_number)
|
||||
local num2 = getblock(c2, sz_number)
|
||||
if not num1 or not num2 or num1 ~= num2 then
|
||||
bork("bad number value"); return
|
||||
end
|
||||
elseif ctype == TSTRING then
|
||||
local str1 = getstring(c1)
|
||||
local str2 = getstring(c2)
|
||||
if not str1 or not str2 or str1 ~= str2 then
|
||||
bork("bad string value"); return
|
||||
end
|
||||
end
|
||||
end
|
||||
-- prototypes (compared recursively)
|
||||
local nproto = goodint(c1, c2)
|
||||
if not nproto then
|
||||
bork("bad nproto"); return
|
||||
end
|
||||
for _ = 1, nproto do
|
||||
if not getfunc(c1, c2) then
|
||||
bork("bad function prototype"); return
|
||||
end
|
||||
end
|
||||
-- debug information (ignored)
|
||||
-- lineinfo (ignored)
|
||||
local sizelineinfo1 = getint(c1)
|
||||
if not sizelineinfo1 then
|
||||
bork("bad sizelineinfo1"); return
|
||||
end
|
||||
local sizelineinfo2 = getint(c2)
|
||||
if not sizelineinfo2 then
|
||||
bork("bad sizelineinfo2"); return
|
||||
end
|
||||
if not getblock(c1, sizelineinfo1 * sz_int) then
|
||||
bork("bad lineinfo1"); return
|
||||
end
|
||||
if not getblock(c2, sizelineinfo2 * sz_int) then
|
||||
bork("bad lineinfo2"); return
|
||||
end
|
||||
-- locvars (ignored)
|
||||
local sizelocvars1 = getint(c1)
|
||||
if not sizelocvars1 then
|
||||
bork("bad sizelocvars1"); return
|
||||
end
|
||||
local sizelocvars2 = getint(c2)
|
||||
if not sizelocvars2 then
|
||||
bork("bad sizelocvars2"); return
|
||||
end
|
||||
for _ = 1, sizelocvars1 do
|
||||
if not getstring(c1) or not getint(c1) or not getint(c1) then
|
||||
bork("bad locvars1"); return
|
||||
end
|
||||
end
|
||||
for _ = 1, sizelocvars2 do
|
||||
if not getstring(c2) or not getint(c2) or not getint(c2) then
|
||||
bork("bad locvars2"); return
|
||||
end
|
||||
end
|
||||
-- upvalues (ignored)
|
||||
local sizeupvalues1 = getint(c1)
|
||||
if not sizeupvalues1 then
|
||||
bork("bad sizeupvalues1"); return
|
||||
end
|
||||
local sizeupvalues2 = getint(c2)
|
||||
if not sizeupvalues2 then
|
||||
bork("bad sizeupvalues2"); return
|
||||
end
|
||||
for _ = 1, sizeupvalues1 do
|
||||
if not getstring(c1) then bork("bad upvalues1"); return end
|
||||
end
|
||||
for _ = 1, sizeupvalues2 do
|
||||
if not getstring(c2) then bork("bad upvalues2"); return end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Removes shbang line so that load runs.
|
||||
local function zap_shbang(s)
|
||||
local shbang = s:match("^(#[^\r\n]*\r?\n?)")
|
||||
if shbang then -- cut out shbang
|
||||
s = sub(s, #shbang + 1)
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
-- Attempt to compile, then dump to get binary chunk string.
|
||||
local cz = load(zap_shbang(z), "z")
|
||||
if not cz then
|
||||
bork("failed to compile original sources for binary chunk comparison")
|
||||
return
|
||||
end
|
||||
|
||||
local cdat = load(zap_shbang(dat), "z")
|
||||
if not cdat then
|
||||
bork("failed to compile compressed result for binary chunk comparison")
|
||||
end
|
||||
|
||||
-- if load() works, dump assuming string.dump() is error-free
|
||||
local c1 = { i = 1, dat = dump(cz) }
|
||||
c1.len = #c1.dat
|
||||
|
||||
local c2 = { i = 1, dat = dump(cdat) }
|
||||
c2.len = #c2.dat
|
||||
|
||||
-- Parse binary chunks to verify equivalence.
|
||||
-- * For headers, handle sizes to allow a degree of flexibility.
|
||||
-- * Assume a valid binary chunk is generated, since it was not
|
||||
-- generated via external means.
|
||||
if not (ensure(c1, 12) and ensure(c2, 12)) then
|
||||
bork("header broken")
|
||||
end
|
||||
skip(c1, 6) -- skip signature(4), version, format
|
||||
endian = getbyte(c1) -- 1 = little endian
|
||||
sz_int = getbyte(c1) -- get data type sizes
|
||||
sz_sizet = getbyte(c1)
|
||||
sz_inst = getbyte(c1)
|
||||
sz_number = getbyte(c1)
|
||||
skip(c1) -- skip integral flag
|
||||
skip(c2, 12) -- skip other header (assume similar)
|
||||
|
||||
if endian == 1 then -- set for endian sensitive data we need
|
||||
getint = getint_l
|
||||
getsizet = getsizet_l
|
||||
else
|
||||
getint = getint_b
|
||||
getsizet = getsizet_b
|
||||
end
|
||||
getfunc(c1, c2) -- get prototype at root
|
||||
|
||||
if c1.i ~= c1.len + 1 then
|
||||
bork("inconsistent binary chunk1"); return
|
||||
elseif c2.i ~= c2.len + 1 then
|
||||
bork("inconsistent binary chunk2"); return
|
||||
end
|
||||
|
||||
-- Successful comparison if end is reached with no borks.
|
||||
end
|
||||
|
||||
return M
|
||||
74
Utils/luarocks/share/lua/5.1/luasrcdiet/fs.lua
Normal file
74
Utils/luarocks/share/lua/5.1/luasrcdiet/fs.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
---------
|
||||
-- Utility functions for operations on a file system.
|
||||
--
|
||||
-- **Note: This module is not part of public API!**
|
||||
----
|
||||
local fmt = string.format
|
||||
local open = io.open
|
||||
|
||||
local UTF8_BOM = '\239\187\191'
|
||||
|
||||
local function normalize_io_error (name, err)
|
||||
if err:sub(1, #name + 2) == name..': ' then
|
||||
err = err:sub(#name + 3)
|
||||
end
|
||||
return err
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Reads the specified file and returns its content as string.
|
||||
--
|
||||
-- @tparam string filename Path of the file to read.
|
||||
-- @tparam string mode The mode in which to open the file, see @{io.open} (default: "r").
|
||||
-- @treturn[1] string A content of the file.
|
||||
-- @treturn[2] nil
|
||||
-- @treturn[2] string An error message.
|
||||
function M.read_file (filename, mode)
|
||||
local handler, err = open(filename, mode or 'r')
|
||||
if not handler then
|
||||
return nil, fmt('Could not open %s for reading: %s',
|
||||
filename, normalize_io_error(filename, err))
|
||||
end
|
||||
|
||||
local content, err = handler:read('*a') --luacheck: ignore 411
|
||||
if not content then
|
||||
return nil, fmt('Could not read %s: %s', filename, normalize_io_error(filename, err))
|
||||
end
|
||||
|
||||
handler:close()
|
||||
|
||||
if content:sub(1, #UTF8_BOM) == UTF8_BOM then
|
||||
content = content:sub(#UTF8_BOM + 1)
|
||||
end
|
||||
|
||||
return content
|
||||
end
|
||||
|
||||
--- Writes the given data to the specified file.
|
||||
--
|
||||
-- @tparam string filename Path of the file to write.
|
||||
-- @tparam string data The data to write.
|
||||
-- @tparam ?string mode The mode in which to open the file, see @{io.open} (default: "w").
|
||||
-- @treturn[1] true
|
||||
-- @treturn[2] nil
|
||||
-- @treturn[2] string An error message.
|
||||
function M.write_file (filename, data, mode)
|
||||
local handler, err = open(filename, mode or 'w')
|
||||
if not handler then
|
||||
return nil, fmt('Could not open %s for writing: %s',
|
||||
filename, normalize_io_error(filename, err))
|
||||
end
|
||||
|
||||
local _, err = handler:write(data) --luacheck: ignore 411
|
||||
if err then
|
||||
return nil, fmt('Could not write %s: %s', filename, normalize_io_error(filename, err))
|
||||
end
|
||||
|
||||
handler:flush()
|
||||
handler:close()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
||||
117
Utils/luarocks/share/lua/5.1/luasrcdiet/init.lua
Normal file
117
Utils/luarocks/share/lua/5.1/luasrcdiet/init.lua
Normal file
@@ -0,0 +1,117 @@
|
||||
---------
|
||||
-- LuaSrcDiet API
|
||||
----
|
||||
local equiv = require 'luasrcdiet.equiv'
|
||||
local llex = require 'luasrcdiet.llex'
|
||||
local lparser = require 'luasrcdiet.lparser'
|
||||
local optlex = require 'luasrcdiet.optlex'
|
||||
local optparser = require 'luasrcdiet.optparser'
|
||||
local utils = require 'luasrcdiet.utils'
|
||||
|
||||
local concat = table.concat
|
||||
local merge = utils.merge
|
||||
|
||||
local _ -- placeholder
|
||||
|
||||
|
||||
local function noop ()
|
||||
return
|
||||
end
|
||||
|
||||
local function opts_to_legacy (opts)
|
||||
local res = {}
|
||||
for key, val in pairs(opts) do
|
||||
res['opt-'..key] = val
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local M = {}
|
||||
|
||||
--- The module's name.
|
||||
M._NAME = 'luasrcdiet'
|
||||
|
||||
--- The module's version number.
|
||||
M._VERSION = '0.3.0'
|
||||
|
||||
--- The module's homepage.
|
||||
M._HOMEPAGE = 'https://github.com/jirutka/luasrcdiet'
|
||||
|
||||
--- All optimizations disabled.
|
||||
M.NONE_OPTS = {
|
||||
binequiv = false,
|
||||
comments = false,
|
||||
emptylines = false,
|
||||
entropy = false,
|
||||
eols = false,
|
||||
experimental = false,
|
||||
locals = false,
|
||||
numbers = false,
|
||||
srcequiv = false,
|
||||
strings = false,
|
||||
whitespace = false,
|
||||
}
|
||||
|
||||
--- Basic optimizations enabled.
|
||||
-- @table BASIC_OPTS
|
||||
M.BASIC_OPTS = merge(M.NONE_OPTS, {
|
||||
comments = true,
|
||||
emptylines = true,
|
||||
srcequiv = true,
|
||||
whitespace = true,
|
||||
})
|
||||
|
||||
--- Defaults.
|
||||
-- @table DEFAULT_OPTS
|
||||
M.DEFAULT_OPTS = merge(M.BASIC_OPTS, {
|
||||
locals = true,
|
||||
numbers = true,
|
||||
})
|
||||
|
||||
--- Maximum optimizations enabled (all except experimental).
|
||||
-- @table MAXIMUM_OPTS
|
||||
M.MAXIMUM_OPTS = merge(M.DEFAULT_OPTS, {
|
||||
entropy = true,
|
||||
eols = true,
|
||||
strings = true,
|
||||
})
|
||||
|
||||
--- Optimizes the given Lua source code.
|
||||
--
|
||||
-- @tparam ?{[string]=bool,...} opts Optimizations to do (default is @{DEFAULT_OPTS}).
|
||||
-- @tparam string source The Lua source code to optimize.
|
||||
-- @treturn string Optimized source.
|
||||
-- @raise if the source is malformed, source equivalence test failed, or some
|
||||
-- other error occured.
|
||||
function M.optimize (opts, source)
|
||||
assert(source and type(source) == 'string',
|
||||
'bad argument #2: expected string, got a '..type(source))
|
||||
|
||||
opts = opts and merge(M.NONE_OPTS, opts) or M.DEFAULT_OPTS
|
||||
local legacy_opts = opts_to_legacy(opts)
|
||||
|
||||
local toklist, seminfolist, toklnlist = llex.lex(source)
|
||||
local xinfo = lparser.parse(toklist, seminfolist, toklnlist)
|
||||
|
||||
optparser.print = noop
|
||||
optparser.optimize(legacy_opts, toklist, seminfolist, xinfo)
|
||||
|
||||
local warn = optlex.warn -- use this as a general warning lookup
|
||||
optlex.print = noop
|
||||
_, seminfolist = optlex.optimize(legacy_opts, toklist, seminfolist, toklnlist)
|
||||
local optim_source = concat(seminfolist)
|
||||
|
||||
if opts.srcequiv and not opts.experimental then
|
||||
equiv.init(legacy_opts, llex, warn)
|
||||
equiv.source(source, optim_source)
|
||||
|
||||
if warn.SRC_EQUIV then
|
||||
error('Source equivalence test failed!')
|
||||
end
|
||||
end
|
||||
|
||||
return optim_source
|
||||
end
|
||||
|
||||
return M
|
||||
350
Utils/luarocks/share/lua/5.1/luasrcdiet/llex.lua
Normal file
350
Utils/luarocks/share/lua/5.1/luasrcdiet/llex.lua
Normal file
@@ -0,0 +1,350 @@
|
||||
---------
|
||||
-- Lua 5.1+ lexical analyzer written in Lua.
|
||||
--
|
||||
-- This file is part of LuaSrcDiet, based on Yueliang material.
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * This is a version of the native 5.1.x lexer from Yueliang 0.4.0,
|
||||
-- with significant modifications to handle LuaSrcDiet's needs:
|
||||
-- (1) llex.error is an optional error function handler,
|
||||
-- (2) seminfo for strings include their delimiters and no
|
||||
-- translation operations are performed on them.
|
||||
-- * ADDED shbang handling has been added to support executable scripts.
|
||||
-- * NO localized decimal point replacement magic.
|
||||
-- * NO limit to number of lines.
|
||||
-- * NO support for compatible long strings (LUA\_COMPAT_LSTR).
|
||||
-- * Added goto keyword and double-colon operator (Lua 5.2+).
|
||||
----
|
||||
local find = string.find
|
||||
local fmt = string.format
|
||||
local match = string.match
|
||||
local sub = string.sub
|
||||
local tonumber = tonumber
|
||||
|
||||
local M = {}
|
||||
|
||||
local kw = {}
|
||||
for v in ([[
|
||||
and break do else elseif end false for function goto if in
|
||||
local nil not or repeat return then true until while]]):gmatch("%S+") do
|
||||
kw[v] = true
|
||||
end
|
||||
|
||||
local z, -- source stream
|
||||
sourceid, -- name of source
|
||||
I, -- position of lexer
|
||||
buff, -- buffer for strings
|
||||
ln, -- line number
|
||||
tok, -- lexed token list
|
||||
seminfo, -- lexed semantic information list
|
||||
tokln -- line numbers for messages
|
||||
|
||||
|
||||
--- Adds information to token listing.
|
||||
--
|
||||
-- @tparam string token
|
||||
-- @tparam string info
|
||||
local function addtoken(token, info)
|
||||
local i = #tok + 1
|
||||
tok[i] = token
|
||||
seminfo[i] = info
|
||||
tokln[i] = ln
|
||||
end
|
||||
|
||||
--- Handles line number incrementation and end-of-line characters.
|
||||
--
|
||||
-- @tparam int i Position of lexer in the source stream.
|
||||
-- @tparam bool is_tok
|
||||
-- @treturn int
|
||||
local function inclinenumber(i, is_tok)
|
||||
local old = sub(z, i, i)
|
||||
i = i + 1 -- skip '\n' or '\r'
|
||||
local c = sub(z, i, i)
|
||||
if (c == "\n" or c == "\r") and (c ~= old) then
|
||||
i = i + 1 -- skip '\n\r' or '\r\n'
|
||||
old = old..c
|
||||
end
|
||||
if is_tok then addtoken("TK_EOL", old) end
|
||||
ln = ln + 1
|
||||
I = i
|
||||
return i
|
||||
end
|
||||
|
||||
--- Returns a chunk name or id, no truncation for long names.
|
||||
--
|
||||
-- @treturn string
|
||||
local function chunkid()
|
||||
if sourceid and match(sourceid, "^[=@]") then
|
||||
return sub(sourceid, 2) -- remove first char
|
||||
end
|
||||
return "[string]"
|
||||
end
|
||||
|
||||
--- Formats error message and throws error.
|
||||
--
|
||||
-- A simplified version, does not report what token was responsible.
|
||||
--
|
||||
-- @tparam string s
|
||||
-- @tparam int line The line number.
|
||||
-- @raise
|
||||
local function errorline(s, line)
|
||||
local e = M.error or error
|
||||
e(fmt("%s:%d: %s", chunkid(), line or ln, s))
|
||||
end
|
||||
|
||||
--- Counts separators (`="` in a long string delimiter.
|
||||
--
|
||||
-- @tparam int i Position of lexer in the source stream.
|
||||
-- @treturn int
|
||||
local function skip_sep(i)
|
||||
local s = sub(z, i, i)
|
||||
i = i + 1
|
||||
local count = #match(z, "=*", i)
|
||||
i = i + count
|
||||
I = i
|
||||
return (sub(z, i, i) == s) and count or (-count) - 1
|
||||
end
|
||||
|
||||
--- Reads a long string or long comment.
|
||||
--
|
||||
-- @tparam bool is_str
|
||||
-- @tparam string sep
|
||||
-- @treturn string
|
||||
-- @raise if unfinished long string or comment.
|
||||
local function read_long_string(is_str, sep)
|
||||
local i = I + 1 -- skip 2nd '['
|
||||
local c = sub(z, i, i)
|
||||
if c == "\r" or c == "\n" then -- string starts with a newline?
|
||||
i = inclinenumber(i) -- skip it
|
||||
end
|
||||
while true do
|
||||
local p, _, r = find(z, "([\r\n%]])", i) -- (long range match)
|
||||
if not p then
|
||||
errorline(is_str and "unfinished long string" or
|
||||
"unfinished long comment")
|
||||
end
|
||||
i = p
|
||||
if r == "]" then -- delimiter test
|
||||
if skip_sep(i) == sep then
|
||||
buff = sub(z, buff, I)
|
||||
I = I + 1 -- skip 2nd ']'
|
||||
return buff
|
||||
end
|
||||
i = I
|
||||
else -- newline
|
||||
buff = buff.."\n"
|
||||
i = inclinenumber(i)
|
||||
end
|
||||
end--while
|
||||
end
|
||||
|
||||
--- Reads a string.
|
||||
--
|
||||
-- @tparam string del The delimiter.
|
||||
-- @treturn string
|
||||
-- @raise if unfinished string or too large escape sequence.
|
||||
local function read_string(del)
|
||||
local i = I
|
||||
while true do
|
||||
local p, _, r = find(z, "([\n\r\\\"\'])", i) -- (long range match)
|
||||
if p then
|
||||
if r == "\n" or r == "\r" then
|
||||
errorline("unfinished string")
|
||||
end
|
||||
i = p
|
||||
if r == "\\" then -- handle escapes
|
||||
i = i + 1
|
||||
r = sub(z, i, i)
|
||||
if r == "" then break end -- (EOZ error)
|
||||
p = find("abfnrtv\n\r", r, 1, true)
|
||||
|
||||
if p then -- special escapes
|
||||
if p > 7 then
|
||||
i = inclinenumber(i)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
elseif find(r, "%D") then -- other non-digits
|
||||
i = i + 1
|
||||
|
||||
else -- \xxx sequence
|
||||
local _, q, s = find(z, "^(%d%d?%d?)", i)
|
||||
i = q + 1
|
||||
if s + 1 > 256 then -- UCHAR_MAX
|
||||
errorline("escape sequence too large")
|
||||
end
|
||||
|
||||
end--if p
|
||||
else
|
||||
i = i + 1
|
||||
if r == del then -- ending delimiter
|
||||
I = i
|
||||
return sub(z, buff, i - 1) -- return string
|
||||
end
|
||||
end--if r
|
||||
else
|
||||
break -- (error)
|
||||
end--if p
|
||||
end--while
|
||||
errorline("unfinished string")
|
||||
end
|
||||
|
||||
|
||||
--- Initializes lexer for given source _z and source name _sourceid.
|
||||
--
|
||||
-- @tparam string _z The source code.
|
||||
-- @tparam string _sourceid Name of the source.
|
||||
local function init(_z, _sourceid)
|
||||
z = _z -- source
|
||||
sourceid = _sourceid -- name of source
|
||||
I = 1 -- lexer's position in source
|
||||
ln = 1 -- line number
|
||||
tok = {} -- lexed token list*
|
||||
seminfo = {} -- lexed semantic information list*
|
||||
tokln = {} -- line numbers for messages*
|
||||
|
||||
-- Initial processing (shbang handling).
|
||||
local p, _, q, r = find(z, "^(#[^\r\n]*)(\r?\n?)")
|
||||
if p then -- skip first line
|
||||
I = I + #q
|
||||
addtoken("TK_COMMENT", q)
|
||||
if #r > 0 then inclinenumber(I, true) end
|
||||
end
|
||||
end
|
||||
|
||||
--- Runs lexer on the given source code.
|
||||
--
|
||||
-- @tparam string source The Lua source to scan.
|
||||
-- @tparam ?string source_name Name of the source (optional).
|
||||
-- @treturn {string,...} A list of lexed tokens.
|
||||
-- @treturn {string,...} A list of semantic information (lexed strings).
|
||||
-- @treturn {int,...} A list of line numbers.
|
||||
function M.lex(source, source_name)
|
||||
init(source, source_name)
|
||||
|
||||
while true do--outer
|
||||
local i = I
|
||||
-- inner loop allows break to be used to nicely section tests
|
||||
while true do --luacheck: ignore 512
|
||||
|
||||
local p, _, r = find(z, "^([_%a][_%w]*)", i)
|
||||
if p then
|
||||
I = i + #r
|
||||
if kw[r] then
|
||||
addtoken("TK_KEYWORD", r) -- reserved word (keyword)
|
||||
else
|
||||
addtoken("TK_NAME", r) -- identifier
|
||||
end
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
local p, _, r = find(z, "^(%.?)%d", i)
|
||||
if p then -- numeral
|
||||
if r == "." then i = i + 1 end
|
||||
local _, q, r = find(z, "^%d*[%.%d]*([eE]?)", i) --luacheck: ignore 421
|
||||
i = q + 1
|
||||
if #r == 1 then -- optional exponent
|
||||
if match(z, "^[%+%-]", i) then -- optional sign
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
local _, q = find(z, "^[_%w]*", i)
|
||||
I = q + 1
|
||||
local v = sub(z, p, q) -- string equivalent
|
||||
if not tonumber(v) then -- handles hex test also
|
||||
errorline("malformed number")
|
||||
end
|
||||
addtoken("TK_NUMBER", v)
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
local p, q, r, t = find(z, "^((%s)[ \t\v\f]*)", i)
|
||||
if p then
|
||||
if t == "\n" or t == "\r" then -- newline
|
||||
inclinenumber(i, true)
|
||||
else
|
||||
I = q + 1 -- whitespace
|
||||
addtoken("TK_SPACE", r)
|
||||
end
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
local _, q = find(z, "^::", i)
|
||||
if q then
|
||||
I = q + 1
|
||||
addtoken("TK_OP", "::")
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
local r = match(z, "^%p", i)
|
||||
if r then
|
||||
buff = i
|
||||
local p = find("-[\"\'.=<>~", r, 1, true) --luacheck: ignore 421
|
||||
if p then
|
||||
|
||||
-- two-level if block for punctuation/symbols
|
||||
if p <= 2 then
|
||||
if p == 1 then -- minus
|
||||
local c = match(z, "^%-%-(%[?)", i)
|
||||
if c then
|
||||
i = i + 2
|
||||
local sep = -1
|
||||
if c == "[" then
|
||||
sep = skip_sep(i)
|
||||
end
|
||||
if sep >= 0 then -- long comment
|
||||
addtoken("TK_LCOMMENT", read_long_string(false, sep))
|
||||
else -- short comment
|
||||
I = find(z, "[\n\r]", i) or (#z + 1)
|
||||
addtoken("TK_COMMENT", sub(z, buff, I - 1))
|
||||
end
|
||||
break -- (continue)
|
||||
end
|
||||
-- (fall through for "-")
|
||||
else -- [ or long string
|
||||
local sep = skip_sep(i)
|
||||
if sep >= 0 then
|
||||
addtoken("TK_LSTRING", read_long_string(true, sep))
|
||||
elseif sep == -1 then
|
||||
addtoken("TK_OP", "[")
|
||||
else
|
||||
errorline("invalid long string delimiter")
|
||||
end
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
elseif p <= 5 then
|
||||
if p < 5 then -- strings
|
||||
I = i + 1
|
||||
addtoken("TK_STRING", read_string(r))
|
||||
break -- (continue)
|
||||
end
|
||||
r = match(z, "^%.%.?%.?", i) -- .|..|... dots
|
||||
-- (fall through)
|
||||
|
||||
else -- relational
|
||||
r = match(z, "^%p=?", i)
|
||||
-- (fall through)
|
||||
end
|
||||
end
|
||||
I = i + #r
|
||||
addtoken("TK_OP", r) -- for other symbols, fall through
|
||||
break -- (continue)
|
||||
end
|
||||
|
||||
local r = sub(z, i, i)
|
||||
if r ~= "" then
|
||||
I = i + 1
|
||||
addtoken("TK_OP", r) -- other single-char tokens
|
||||
break
|
||||
end
|
||||
addtoken("TK_EOS", "") -- end of stream,
|
||||
return tok, seminfo, tokln -- exit here
|
||||
|
||||
end--while inner
|
||||
end--while outer
|
||||
end
|
||||
|
||||
return M
|
||||
1286
Utils/luarocks/share/lua/5.1/luasrcdiet/lparser.lua
Normal file
1286
Utils/luarocks/share/lua/5.1/luasrcdiet/lparser.lua
Normal file
File diff suppressed because it is too large
Load Diff
852
Utils/luarocks/share/lua/5.1/luasrcdiet/optlex.lua
Normal file
852
Utils/luarocks/share/lua/5.1/luasrcdiet/optlex.lua
Normal file
@@ -0,0 +1,852 @@
|
||||
---------
|
||||
-- This module does lexer-based optimizations.
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * TODO: General string delimiter conversion optimizer.
|
||||
-- * TODO: (numbers) warn if overly significant digit.
|
||||
----
|
||||
local char = string.char
|
||||
local find = string.find
|
||||
local match = string.match
|
||||
local rep = string.rep
|
||||
local sub = string.sub
|
||||
local tonumber = tonumber
|
||||
local tostring = tostring
|
||||
|
||||
local print -- set in optimize()
|
||||
|
||||
local M = {}
|
||||
|
||||
-- error function, can override by setting own function into module
|
||||
M.error = error
|
||||
|
||||
M.warn = {} -- table for warning flags
|
||||
|
||||
local stoks, sinfos, stoklns -- source lists
|
||||
|
||||
local is_realtoken = { -- significant (grammar) tokens
|
||||
TK_KEYWORD = true,
|
||||
TK_NAME = true,
|
||||
TK_NUMBER = true,
|
||||
TK_STRING = true,
|
||||
TK_LSTRING = true,
|
||||
TK_OP = true,
|
||||
TK_EOS = true,
|
||||
}
|
||||
local is_faketoken = { -- whitespace (non-grammar) tokens
|
||||
TK_COMMENT = true,
|
||||
TK_LCOMMENT = true,
|
||||
TK_EOL = true,
|
||||
TK_SPACE = true,
|
||||
}
|
||||
|
||||
local opt_details -- for extra information
|
||||
|
||||
--- Returns true if current token is at the start of a line.
|
||||
--
|
||||
-- It skips over deleted tokens via recursion.
|
||||
--
|
||||
-- @tparam int i
|
||||
-- @treturn bool
|
||||
local function atlinestart(i)
|
||||
local tok = stoks[i - 1]
|
||||
if i <= 1 or tok == "TK_EOL" then
|
||||
return true
|
||||
elseif tok == "" then
|
||||
return atlinestart(i - 1)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns true if current token is at the end of a line.
|
||||
--
|
||||
-- It skips over deleted tokens via recursion.
|
||||
--
|
||||
-- @tparam int i
|
||||
-- @treturn bool
|
||||
local function atlineend(i)
|
||||
local tok = stoks[i + 1]
|
||||
if i >= #stoks or tok == "TK_EOL" or tok == "TK_EOS" then
|
||||
return true
|
||||
elseif tok == "" then
|
||||
return atlineend(i + 1)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Counts comment EOLs inside a long comment.
|
||||
--
|
||||
-- In order to keep line numbering, EOLs need to be reinserted.
|
||||
--
|
||||
-- @tparam string lcomment
|
||||
-- @treturn int
|
||||
local function commenteols(lcomment)
|
||||
local sep = #match(lcomment, "^%-%-%[=*%[")
|
||||
local z = sub(lcomment, sep + 1, -(sep - 1)) -- remove delims
|
||||
local i, c = 1, 0
|
||||
while true do
|
||||
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||
if not p then break end -- if no matches, done
|
||||
i = p + 1
|
||||
c = c + 1
|
||||
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
--- Compares two tokens (i, j) and returns the whitespace required.
|
||||
--
|
||||
-- See documentation for a reference table of interactions.
|
||||
--
|
||||
-- Only two grammar/real tokens are being considered:
|
||||
--
|
||||
-- * if `""`, no separation is needed,
|
||||
-- * if `" "`, then at least one whitespace (or EOL) is required.
|
||||
--
|
||||
-- Note: This doesn't work at the start or the end or for EOS!
|
||||
--
|
||||
-- @tparam int i
|
||||
-- @tparam int j
|
||||
-- @treturn string
|
||||
local function checkpair(i, j)
|
||||
local t1, t2 = stoks[i], stoks[j]
|
||||
|
||||
if t1 == "TK_STRING" or t1 == "TK_LSTRING" or
|
||||
t2 == "TK_STRING" or t2 == "TK_LSTRING" then
|
||||
return ""
|
||||
|
||||
elseif t1 == "TK_OP" or t2 == "TK_OP" then
|
||||
if (t1 == "TK_OP" and (t2 == "TK_KEYWORD" or t2 == "TK_NAME")) or
|
||||
(t2 == "TK_OP" and (t1 == "TK_KEYWORD" or t1 == "TK_NAME")) then
|
||||
return ""
|
||||
end
|
||||
if t1 == "TK_OP" and t2 == "TK_OP" then
|
||||
-- for TK_OP/TK_OP pairs, see notes in technotes.txt
|
||||
local op, op2 = sinfos[i], sinfos[j]
|
||||
if (match(op, "^%.%.?$") and match(op2, "^%.")) or
|
||||
(match(op, "^[~=<>]$") and op2 == "=") or
|
||||
(op == "[" and (op2 == "[" or op2 == "=")) then
|
||||
return " "
|
||||
end
|
||||
return ""
|
||||
end
|
||||
-- "TK_OP" + "TK_NUMBER" case
|
||||
local op = sinfos[i]
|
||||
if t2 == "TK_OP" then op = sinfos[j] end
|
||||
if match(op, "^%.%.?%.?$") then
|
||||
return " "
|
||||
end
|
||||
return ""
|
||||
|
||||
else-- "TK_KEYWORD" | "TK_NAME" | "TK_NUMBER" then
|
||||
return " "
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Repack tokens, removing deletions caused by optimization process.
|
||||
local function repack_tokens()
|
||||
local dtoks, dinfos, dtoklns = {}, {}, {}
|
||||
local j = 1
|
||||
for i = 1, #stoks do
|
||||
local tok = stoks[i]
|
||||
if tok ~= "" then
|
||||
dtoks[j], dinfos[j], dtoklns[j] = tok, sinfos[i], stoklns[i]
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
stoks, sinfos, stoklns = dtoks, dinfos, dtoklns
|
||||
end
|
||||
|
||||
--- Does number optimization.
|
||||
--
|
||||
-- Optimization using string formatting functions is one way of doing this,
|
||||
-- but here, we consider all cases and handle them separately (possibly an
|
||||
-- idiotic approach...).
|
||||
--
|
||||
-- Scientific notation being generated is not in canonical form, this may or
|
||||
-- may not be a bad thing.
|
||||
--
|
||||
-- Note: Intermediate portions need to fit into a normal number range.
|
||||
--
|
||||
-- Optimizations can be divided based on number patterns:
|
||||
--
|
||||
-- * hexadecimal:
|
||||
-- (1) no need to remove leading zeros, just skip to (2)
|
||||
-- (2) convert to integer if size equal or smaller
|
||||
-- * change if equal size -> lose the 'x' to reduce entropy
|
||||
-- (3) number is then processed as an integer
|
||||
-- (4) note: does not make 0[xX] consistent
|
||||
-- * integer:
|
||||
-- (1) reduce useless fractional part, if present, e.g. 123.000 -> 123.
|
||||
-- (2) remove leading zeros, e.g. 000123
|
||||
-- * float:
|
||||
-- (1) split into digits dot digits
|
||||
-- (2) if no integer portion, take as zero (can omit later)
|
||||
-- (3) handle degenerate .000 case, after which the fractional part
|
||||
-- must be non-zero (if zero, it's matched as float .0)
|
||||
-- (4) remove trailing zeros for fractional portion
|
||||
-- (5) p.q where p > 0 and q > 0 cannot be shortened any more
|
||||
-- (6) otherwise p == 0 and the form is .q, e.g. .000123
|
||||
-- (7) if scientific shorter, convert, e.g. .000123 -> 123e-6
|
||||
-- * scientific:
|
||||
-- (1) split into (digits dot digits) [eE] ([+-] digits)
|
||||
-- (2) if significand is zero, just use .0
|
||||
-- (3) remove leading zeros for significand
|
||||
-- (4) shift out trailing zeros for significand
|
||||
-- (5) examine exponent and determine which format is best:
|
||||
-- number with fraction, or scientific
|
||||
--
|
||||
-- Note: Number with fraction and scientific number is never converted
|
||||
-- to integer, because Lua 5.3 distinguishes between integers and floats.
|
||||
--
|
||||
--
|
||||
-- @tparam int i
|
||||
local function do_number(i)
|
||||
local before = sinfos[i] -- 'before'
|
||||
local z = before -- working representation
|
||||
local y -- 'after', if better
|
||||
--------------------------------------------------------------------
|
||||
if match(z, "^0[xX]") then -- hexadecimal number
|
||||
local v = tostring(tonumber(z))
|
||||
if #v <= #z then
|
||||
z = v -- change to integer, AND continue
|
||||
else
|
||||
return -- no change; stick to hex
|
||||
end
|
||||
end
|
||||
|
||||
if match(z, "^%d+$") then -- integer
|
||||
if tonumber(z) > 0 then
|
||||
y = match(z, "^0*([1-9]%d*)$") -- remove leading zeros
|
||||
else
|
||||
y = "0" -- basic zero
|
||||
end
|
||||
|
||||
elseif not match(z, "[eE]") then -- float
|
||||
local p, q = match(z, "^(%d*)%.(%d*)$") -- split
|
||||
if p == "" then p = 0 end -- int part zero
|
||||
if q == "" then q = "0" end -- fraction part zero
|
||||
if tonumber(q) == 0 and p == 0 then
|
||||
y = ".0" -- degenerate .000 to .0
|
||||
else
|
||||
-- now, q > 0 holds and p is a number
|
||||
local zeros_cnt = #match(q, "0*$") -- remove trailing zeros
|
||||
if zeros_cnt > 0 then
|
||||
q = sub(q, 1, #q - zeros_cnt)
|
||||
end
|
||||
-- if p > 0, nothing else we can do to simplify p.q case
|
||||
if tonumber(p) > 0 then
|
||||
y = p.."."..q
|
||||
else
|
||||
y = "."..q -- tentative, e.g. .000123
|
||||
local v = #match(q, "^0*") -- # leading spaces
|
||||
local w = #q - v -- # significant digits
|
||||
local nv = tostring(#q)
|
||||
-- e.g. compare 123e-6 versus .000123
|
||||
if w + 2 + #nv < 1 + #q then
|
||||
y = sub(q, -w).."e-"..nv
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else -- scientific number
|
||||
local sig, ex = match(z, "^([^eE]+)[eE]([%+%-]?%d+)$")
|
||||
ex = tonumber(ex)
|
||||
-- if got ".", shift out fractional portion of significand
|
||||
local p, q = match(sig, "^(%d*)%.(%d*)$")
|
||||
if p then
|
||||
ex = ex - #q
|
||||
sig = p..q
|
||||
end
|
||||
if tonumber(sig) == 0 then
|
||||
y = ".0" -- basic float zero
|
||||
else
|
||||
local v = #match(sig, "^0*") -- remove leading zeros
|
||||
sig = sub(sig, v + 1)
|
||||
v = #match(sig, "0*$") -- shift out trailing zeros
|
||||
if v > 0 then
|
||||
sig = sub(sig, 1, #sig - v)
|
||||
ex = ex + v
|
||||
end
|
||||
-- examine exponent and determine which format is best
|
||||
local nex = tostring(ex)
|
||||
if ex >= 0 and (ex <= 1 + #nex) then -- a float
|
||||
y = sig..rep("0", ex).."."
|
||||
elseif ex < 0 and (ex >= -#sig) then -- fraction, e.g. .123
|
||||
v = #sig + ex
|
||||
y = sub(sig, 1, v).."."..sub(sig, v + 1)
|
||||
elseif ex < 0 and (#nex >= -ex - #sig) then
|
||||
-- e.g. compare 1234e-5 versus .01234
|
||||
-- gives: #sig + 1 + #nex >= 1 + (-ex - #sig) + #sig
|
||||
-- -> #nex >= -ex - #sig
|
||||
v = -ex - #sig
|
||||
y = "."..rep("0", v)..sig
|
||||
else -- non-canonical scientific representation
|
||||
y = sig.."e"..ex
|
||||
end
|
||||
end--if sig
|
||||
end
|
||||
|
||||
if y and y ~= sinfos[i] then
|
||||
if opt_details then
|
||||
print("<number> (line "..stoklns[i]..") "..sinfos[i].." -> "..y)
|
||||
opt_details = opt_details + 1
|
||||
end
|
||||
sinfos[i] = y
|
||||
end
|
||||
end
|
||||
|
||||
--- Does string optimization.
|
||||
--
|
||||
-- Note: It works on well-formed strings only!
|
||||
--
|
||||
-- Optimizations on characters can be summarized as follows:
|
||||
--
|
||||
-- \a\b\f\n\r\t\v -- no change
|
||||
-- \\ -- no change
|
||||
-- \"\' -- depends on delim, other can remove \
|
||||
-- \[\] -- remove \
|
||||
-- \<char> -- general escape, remove \ (Lua 5.1 only)
|
||||
-- \<eol> -- normalize the EOL only
|
||||
-- \ddd -- if \a\b\f\n\r\t\v, change to latter
|
||||
-- if other < ascii 32, keep ddd but zap leading zeros
|
||||
-- but cannot have following digits
|
||||
-- if >= ascii 32, translate it into the literal, then also
|
||||
-- do escapes for \\,\",\' cases
|
||||
-- <other> -- no change
|
||||
--
|
||||
-- Switch delimiters if string becomes shorter.
|
||||
--
|
||||
-- @tparam int I
|
||||
local function do_string(I)
|
||||
local info = sinfos[I]
|
||||
local delim = sub(info, 1, 1) -- delimiter used
|
||||
local ndelim = (delim == "'") and '"' or "'" -- opposite " <-> '
|
||||
local z = sub(info, 2, -2) -- actual string
|
||||
local i = 1
|
||||
local c_delim, c_ndelim = 0, 0 -- "/' counts
|
||||
|
||||
while i <= #z do
|
||||
local c = sub(z, i, i)
|
||||
|
||||
if c == "\\" then -- escaped stuff
|
||||
local j = i + 1
|
||||
local d = sub(z, j, j)
|
||||
local p = find("abfnrtv\\\n\r\"\'0123456789", d, 1, true)
|
||||
|
||||
if not p then -- \<char> -- remove \ (Lua 5.1 only)
|
||||
z = sub(z, 1, i - 1)..sub(z, j)
|
||||
i = i + 1
|
||||
|
||||
elseif p <= 8 then -- \a\b\f\n\r\t\v\\
|
||||
i = i + 2 -- no change
|
||||
|
||||
elseif p <= 10 then -- \<eol> -- normalize EOL
|
||||
local eol = sub(z, j, j + 1)
|
||||
if eol == "\r\n" or eol == "\n\r" then
|
||||
z = sub(z, 1, i).."\n"..sub(z, j + 2)
|
||||
elseif p == 10 then -- \r case
|
||||
z = sub(z, 1, i).."\n"..sub(z, j + 1)
|
||||
end
|
||||
i = i + 2
|
||||
|
||||
elseif p <= 12 then -- \"\' -- remove \ for ndelim
|
||||
if d == delim then
|
||||
c_delim = c_delim + 1
|
||||
i = i + 2
|
||||
else
|
||||
c_ndelim = c_ndelim + 1
|
||||
z = sub(z, 1, i - 1)..sub(z, j)
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
else -- \ddd -- various steps
|
||||
local s = match(z, "^(%d%d?%d?)", j)
|
||||
j = i + 1 + #s -- skip to location
|
||||
local cv = tonumber(s)
|
||||
local cc = char(cv)
|
||||
p = find("\a\b\f\n\r\t\v", cc, 1, true)
|
||||
if p then -- special escapes
|
||||
s = "\\"..sub("abfnrtv", p, p)
|
||||
elseif cv < 32 then -- normalized \ddd
|
||||
if match(sub(z, j, j), "%d") then
|
||||
-- if a digit follows, \ddd cannot be shortened
|
||||
s = "\\"..s
|
||||
else
|
||||
s = "\\"..cv
|
||||
end
|
||||
elseif cc == delim then -- \<delim>
|
||||
s = "\\"..cc
|
||||
c_delim = c_delim + 1
|
||||
elseif cc == "\\" then -- \\
|
||||
s = "\\\\"
|
||||
else -- literal character
|
||||
s = cc
|
||||
if cc == ndelim then
|
||||
c_ndelim = c_ndelim + 1
|
||||
end
|
||||
end
|
||||
z = sub(z, 1, i - 1)..s..sub(z, j)
|
||||
i = i + #s
|
||||
|
||||
end--if p
|
||||
|
||||
else-- c ~= "\\" -- <other> -- no change
|
||||
i = i + 1
|
||||
if c == ndelim then -- count ndelim, for switching delimiters
|
||||
c_ndelim = c_ndelim + 1
|
||||
end
|
||||
|
||||
end--if c
|
||||
end--while
|
||||
|
||||
-- Switching delimiters, a long-winded derivation:
|
||||
-- (1) delim takes 2+2*c_delim bytes, ndelim takes c_ndelim bytes
|
||||
-- (2) delim becomes c_delim bytes, ndelim becomes 2+2*c_ndelim bytes
|
||||
-- simplifying the condition (1)>(2) --> c_delim > c_ndelim
|
||||
if c_delim > c_ndelim then
|
||||
i = 1
|
||||
while i <= #z do
|
||||
local p, _, r = find(z, "([\'\"])", i)
|
||||
if not p then break end
|
||||
if r == delim then -- \<delim> -> <delim>
|
||||
z = sub(z, 1, p - 2)..sub(z, p)
|
||||
i = p
|
||||
else-- r == ndelim -- <ndelim> -> \<ndelim>
|
||||
z = sub(z, 1, p - 1).."\\"..sub(z, p)
|
||||
i = p + 2
|
||||
end
|
||||
end--while
|
||||
delim = ndelim -- actually change delimiters
|
||||
end
|
||||
|
||||
z = delim..z..delim
|
||||
if z ~= sinfos[I] then
|
||||
if opt_details then
|
||||
print("<string> (line "..stoklns[I]..") "..sinfos[I].." -> "..z)
|
||||
opt_details = opt_details + 1
|
||||
end
|
||||
sinfos[I] = z
|
||||
end
|
||||
end
|
||||
|
||||
--- Does long string optimization.
|
||||
--
|
||||
-- * remove first optional newline
|
||||
-- * normalize embedded newlines
|
||||
-- * reduce '=' separators in delimiters if possible
|
||||
--
|
||||
-- Note: warning flagged if trailing whitespace found, not trimmed.
|
||||
--
|
||||
-- @tparam int I
|
||||
local function do_lstring(I)
|
||||
local info = sinfos[I]
|
||||
local delim1 = match(info, "^%[=*%[") -- cut out delimiters
|
||||
local sep = #delim1
|
||||
local delim2 = sub(info, -sep, -1)
|
||||
local z = sub(info, sep + 1, -(sep + 1)) -- lstring without delims
|
||||
local y = ""
|
||||
local i = 1
|
||||
|
||||
while true do
|
||||
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||
-- deal with a single line
|
||||
local ln
|
||||
if not p then
|
||||
ln = sub(z, i)
|
||||
elseif p >= i then
|
||||
ln = sub(z, i, p - 1)
|
||||
end
|
||||
if ln ~= "" then
|
||||
-- flag a warning if there are trailing spaces, won't optimize!
|
||||
if match(ln, "%s+$") then
|
||||
M.warn.LSTRING = "trailing whitespace in long string near line "..stoklns[I]
|
||||
end
|
||||
y = y..ln
|
||||
end
|
||||
if not p then -- done if no more EOLs
|
||||
break
|
||||
end
|
||||
-- deal with line endings, normalize them
|
||||
i = p + 1
|
||||
if p then
|
||||
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||
i = i + 1
|
||||
end
|
||||
-- skip first newline, which can be safely deleted
|
||||
if not(i == 1 and i == p) then
|
||||
y = y.."\n"
|
||||
end
|
||||
end
|
||||
end--while
|
||||
|
||||
-- handle possible deletion of one or more '=' separators
|
||||
if sep >= 3 then
|
||||
local chk, okay = sep - 1
|
||||
-- loop to test ending delimiter with less of '=' down to zero
|
||||
while chk >= 2 do
|
||||
local delim = "%]"..rep("=", chk - 2).."%]"
|
||||
if not match(y, delim) then okay = chk end
|
||||
chk = chk - 1
|
||||
end
|
||||
if okay then -- change delimiters
|
||||
sep = rep("=", okay - 2)
|
||||
delim1, delim2 = "["..sep.."[", "]"..sep.."]"
|
||||
end
|
||||
end
|
||||
|
||||
sinfos[I] = delim1..y..delim2
|
||||
end
|
||||
|
||||
--- Does long comment optimization.
|
||||
--
|
||||
-- * trim trailing whitespace
|
||||
-- * normalize embedded newlines
|
||||
-- * reduce '=' separators in delimiters if possible
|
||||
--
|
||||
-- Note: It does not remove first optional newline.
|
||||
--
|
||||
-- @tparam int I
|
||||
local function do_lcomment(I)
|
||||
local info = sinfos[I]
|
||||
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
|
||||
local sep = #delim1
|
||||
local delim2 = sub(info, -(sep - 2), -1)
|
||||
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
|
||||
local y = ""
|
||||
local i = 1
|
||||
|
||||
while true do
|
||||
local p, _, r, s = find(z, "([\r\n])([\r\n]?)", i)
|
||||
-- deal with a single line, extract and check trailing whitespace
|
||||
local ln
|
||||
if not p then
|
||||
ln = sub(z, i)
|
||||
elseif p >= i then
|
||||
ln = sub(z, i, p - 1)
|
||||
end
|
||||
if ln ~= "" then
|
||||
-- trim trailing whitespace if non-empty line
|
||||
local ws = match(ln, "%s*$")
|
||||
if #ws > 0 then ln = sub(ln, 1, -(ws + 1)) end
|
||||
y = y..ln
|
||||
end
|
||||
if not p then -- done if no more EOLs
|
||||
break
|
||||
end
|
||||
-- deal with line endings, normalize them
|
||||
i = p + 1
|
||||
if p then
|
||||
if #s > 0 and r ~= s then -- skip CRLF or LFCR
|
||||
i = i + 1
|
||||
end
|
||||
y = y.."\n"
|
||||
end
|
||||
end--while
|
||||
|
||||
-- handle possible deletion of one or more '=' separators
|
||||
sep = sep - 2
|
||||
if sep >= 3 then
|
||||
local chk, okay = sep - 1
|
||||
-- loop to test ending delimiter with less of '=' down to zero
|
||||
while chk >= 2 do
|
||||
local delim = "%]"..rep("=", chk - 2).."%]"
|
||||
if not match(y, delim) then okay = chk end
|
||||
chk = chk - 1
|
||||
end
|
||||
if okay then -- change delimiters
|
||||
sep = rep("=", okay - 2)
|
||||
delim1, delim2 = "--["..sep.."[", "]"..sep.."]"
|
||||
end
|
||||
end
|
||||
|
||||
sinfos[I] = delim1..y..delim2
|
||||
end
|
||||
|
||||
--- Does short comment optimization.
|
||||
--
|
||||
-- * trim trailing whitespace
|
||||
--
|
||||
-- @tparam int i
|
||||
local function do_comment(i)
|
||||
local info = sinfos[i]
|
||||
local ws = match(info, "%s*$") -- just look from end of string
|
||||
if #ws > 0 then
|
||||
info = sub(info, 1, -(ws + 1)) -- trim trailing whitespace
|
||||
end
|
||||
sinfos[i] = info
|
||||
end
|
||||
|
||||
--- Returns true if string found in long comment.
|
||||
--
|
||||
-- This is a feature to keep copyright or license texts.
|
||||
--
|
||||
-- @tparam bool opt_keep
|
||||
-- @tparam string info
|
||||
-- @treturn bool
|
||||
local function keep_lcomment(opt_keep, info)
|
||||
if not opt_keep then return false end -- option not set
|
||||
local delim1 = match(info, "^%-%-%[=*%[") -- cut out delimiters
|
||||
local sep = #delim1
|
||||
local z = sub(info, sep + 1, -(sep - 1)) -- comment without delims
|
||||
if find(z, opt_keep, 1, true) then -- try to match
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- The main entry point.
|
||||
--
|
||||
-- * currently, lexer processing has 2 passes
|
||||
-- * processing is done on a line-oriented basis, which is easier to
|
||||
-- grok due to the next point...
|
||||
-- * since there are various options that can be enabled or disabled,
|
||||
-- processing is a little messy or convoluted
|
||||
--
|
||||
-- @tparam {[string]=bool,...} option
|
||||
-- @tparam {string,...} toklist
|
||||
-- @tparam {string,...} semlist
|
||||
-- @tparam {int,...} toklnlist
|
||||
-- @treturn {string,...} toklist
|
||||
-- @treturn {string,...} semlist
|
||||
-- @treturn {int,...} toklnlist
|
||||
function M.optimize(option, toklist, semlist, toklnlist)
|
||||
-- Set option flags.
|
||||
local opt_comments = option["opt-comments"]
|
||||
local opt_whitespace = option["opt-whitespace"]
|
||||
local opt_emptylines = option["opt-emptylines"]
|
||||
local opt_eols = option["opt-eols"]
|
||||
local opt_strings = option["opt-strings"]
|
||||
local opt_numbers = option["opt-numbers"]
|
||||
local opt_x = option["opt-experimental"]
|
||||
local opt_keep = option.KEEP
|
||||
opt_details = option.DETAILS and 0 -- upvalues for details display
|
||||
print = M.print or _G.print
|
||||
if opt_eols then -- forced settings, otherwise won't work properly
|
||||
opt_comments = true
|
||||
opt_whitespace = true
|
||||
opt_emptylines = true
|
||||
elseif opt_x then
|
||||
opt_whitespace = true
|
||||
end
|
||||
|
||||
-- Variable initialization.
|
||||
stoks, sinfos, stoklns -- set source lists
|
||||
= toklist, semlist, toklnlist
|
||||
local i = 1 -- token position
|
||||
local tok, info -- current token
|
||||
local prev -- position of last grammar token
|
||||
-- on same line (for TK_SPACE stuff)
|
||||
|
||||
-- Changes a token, info pair.
|
||||
local function settoken(tok, info, I) --luacheck: ignore 431
|
||||
I = I or i
|
||||
stoks[I] = tok or ""
|
||||
sinfos[I] = info or ""
|
||||
end
|
||||
|
||||
-- Experimental optimization for ';' operator.
|
||||
if opt_x then
|
||||
while true do
|
||||
tok, info = stoks[i], sinfos[i]
|
||||
if tok == "TK_EOS" then -- end of stream/pass
|
||||
break
|
||||
elseif tok == "TK_OP" and info == ";" then
|
||||
-- ';' operator found, since it is entirely optional, set it
|
||||
-- as a space to let whitespace optimization do the rest
|
||||
settoken("TK_SPACE", " ")
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
repack_tokens()
|
||||
end
|
||||
|
||||
-- Processing loop (PASS 1)
|
||||
i = 1
|
||||
while true do
|
||||
tok, info = stoks[i], sinfos[i]
|
||||
|
||||
local atstart = atlinestart(i) -- set line begin flag
|
||||
if atstart then prev = nil end
|
||||
|
||||
if tok == "TK_EOS" then -- end of stream/pass
|
||||
break
|
||||
|
||||
elseif tok == "TK_KEYWORD" or -- keywords, identifiers,
|
||||
tok == "TK_NAME" or -- operators
|
||||
tok == "TK_OP" then
|
||||
-- TK_KEYWORD and TK_OP can't be optimized without a big
|
||||
-- optimization framework; it would be more of an optimizing
|
||||
-- compiler, not a source code compressor
|
||||
-- TK_NAME that are locals needs parser to analyze/optimize
|
||||
prev = i
|
||||
|
||||
elseif tok == "TK_NUMBER" then -- numbers
|
||||
if opt_numbers then
|
||||
do_number(i) -- optimize
|
||||
end
|
||||
prev = i
|
||||
|
||||
elseif tok == "TK_STRING" or -- strings, long strings
|
||||
tok == "TK_LSTRING" then
|
||||
if opt_strings then
|
||||
if tok == "TK_STRING" then
|
||||
do_string(i) -- optimize
|
||||
else
|
||||
do_lstring(i) -- optimize
|
||||
end
|
||||
end
|
||||
prev = i
|
||||
|
||||
elseif tok == "TK_COMMENT" then -- short comments
|
||||
if opt_comments then
|
||||
if i == 1 and sub(info, 1, 1) == "#" then
|
||||
-- keep shbang comment, trim whitespace
|
||||
do_comment(i)
|
||||
else
|
||||
-- safe to delete, as a TK_EOL (or TK_EOS) always follows
|
||||
settoken() -- remove entirely
|
||||
end
|
||||
elseif opt_whitespace then -- trim whitespace only
|
||||
do_comment(i)
|
||||
end
|
||||
|
||||
elseif tok == "TK_LCOMMENT" then -- long comments
|
||||
if keep_lcomment(opt_keep, info) then
|
||||
-- if --keep, we keep a long comment if <msg> is found;
|
||||
-- this is a feature to keep copyright or license texts
|
||||
if opt_whitespace then -- trim whitespace only
|
||||
do_lcomment(i)
|
||||
end
|
||||
prev = i
|
||||
elseif opt_comments then
|
||||
local eols = commenteols(info)
|
||||
|
||||
-- prepare opt_emptylines case first, if a disposable token
|
||||
-- follows, current one is safe to dump, else keep a space;
|
||||
-- it is implied that the operation is safe for '-', because
|
||||
-- current is a TK_LCOMMENT, and must be separate from a '-'
|
||||
if is_faketoken[stoks[i + 1]] then
|
||||
settoken() -- remove entirely
|
||||
tok = ""
|
||||
else
|
||||
settoken("TK_SPACE", " ")
|
||||
end
|
||||
|
||||
-- if there are embedded EOLs to keep and opt_emptylines is
|
||||
-- disabled, then switch the token into one or more EOLs
|
||||
if not opt_emptylines and eols > 0 then
|
||||
settoken("TK_EOL", rep("\n", eols))
|
||||
end
|
||||
|
||||
-- if optimizing whitespaces, force reinterpretation of the
|
||||
-- token to give a chance for the space to be optimized away
|
||||
if opt_whitespace and tok ~= "" then
|
||||
i = i - 1 -- to reinterpret
|
||||
end
|
||||
else -- disabled case
|
||||
if opt_whitespace then -- trim whitespace only
|
||||
do_lcomment(i)
|
||||
end
|
||||
prev = i
|
||||
end
|
||||
|
||||
elseif tok == "TK_EOL" then -- line endings
|
||||
if atstart and opt_emptylines then
|
||||
settoken() -- remove entirely
|
||||
elseif info == "\r\n" or info == "\n\r" then
|
||||
-- normalize the rest of the EOLs for CRLF/LFCR only
|
||||
-- (note that TK_LCOMMENT can change into several EOLs)
|
||||
settoken("TK_EOL", "\n")
|
||||
end
|
||||
|
||||
elseif tok == "TK_SPACE" then -- whitespace
|
||||
if opt_whitespace then
|
||||
if atstart or atlineend(i) then
|
||||
-- delete leading and trailing whitespace
|
||||
settoken() -- remove entirely
|
||||
else
|
||||
|
||||
-- at this point, since leading whitespace have been removed,
|
||||
-- there should be a either a real token or a TK_LCOMMENT
|
||||
-- prior to hitting this whitespace; the TK_LCOMMENT case
|
||||
-- only happens if opt_comments is disabled; so prev ~= nil
|
||||
local ptok = stoks[prev]
|
||||
if ptok == "TK_LCOMMENT" then
|
||||
-- previous TK_LCOMMENT can abut with anything
|
||||
settoken() -- remove entirely
|
||||
else
|
||||
-- prev must be a grammar token; consecutive TK_SPACE
|
||||
-- tokens is impossible when optimizing whitespace
|
||||
local ntok = stoks[i + 1]
|
||||
if is_faketoken[ntok] then
|
||||
-- handle special case where a '-' cannot abut with
|
||||
-- either a short comment or a long comment
|
||||
if (ntok == "TK_COMMENT" or ntok == "TK_LCOMMENT") and
|
||||
ptok == "TK_OP" and sinfos[prev] == "-" then
|
||||
-- keep token
|
||||
else
|
||||
settoken() -- remove entirely
|
||||
end
|
||||
else--is_realtoken
|
||||
-- check a pair of grammar tokens, if can abut, then
|
||||
-- delete space token entirely, otherwise keep one space
|
||||
local s = checkpair(prev, i + 1)
|
||||
if s == "" then
|
||||
settoken() -- remove entirely
|
||||
else
|
||||
settoken("TK_SPACE", " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
error("unidentified token encountered")
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end--while
|
||||
repack_tokens()
|
||||
|
||||
-- Processing loop (PASS 2)
|
||||
if opt_eols then
|
||||
i = 1
|
||||
-- Aggressive EOL removal only works with most non-grammar tokens
|
||||
-- optimized away because it is a rather simple scheme -- basically
|
||||
-- it just checks 'real' token pairs around EOLs.
|
||||
if stoks[1] == "TK_COMMENT" then
|
||||
-- first comment still existing must be shbang, skip whole line
|
||||
i = 3
|
||||
end
|
||||
while true do
|
||||
tok = stoks[i]
|
||||
|
||||
if tok == "TK_EOS" then -- end of stream/pass
|
||||
break
|
||||
|
||||
elseif tok == "TK_EOL" then -- consider each TK_EOL
|
||||
local t1, t2 = stoks[i - 1], stoks[i + 1]
|
||||
if is_realtoken[t1] and is_realtoken[t2] then -- sanity check
|
||||
local s = checkpair(i - 1, i + 1)
|
||||
if s == "" or t2 == "TK_EOS" then
|
||||
settoken() -- remove entirely
|
||||
end
|
||||
end
|
||||
end--if tok
|
||||
|
||||
i = i + 1
|
||||
end--while
|
||||
repack_tokens()
|
||||
end
|
||||
|
||||
if opt_details and opt_details > 0 then print() end -- spacing
|
||||
return stoks, sinfos, stoklns
|
||||
end
|
||||
|
||||
return M
|
||||
644
Utils/luarocks/share/lua/5.1/luasrcdiet/optparser.lua
Normal file
644
Utils/luarocks/share/lua/5.1/luasrcdiet/optparser.lua
Normal file
@@ -0,0 +1,644 @@
|
||||
---------
|
||||
-- This module does parser-based optimizations.
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * The processing load is quite significant, but since this is an
|
||||
-- off-line text processor, I believe we can wait a few seconds.
|
||||
-- * TODO: Might process "local a,a,a" wrongly... need tests!
|
||||
-- * TODO: Remove position handling if overlapped locals (rem < 0)
|
||||
-- needs more study, to check behaviour.
|
||||
-- * TODO: There are probably better ways to do allocation, e.g. by
|
||||
-- choosing better methods to sort and pick locals...
|
||||
-- * TODO: We don't need 53*63 two-letter identifiers; we can make
|
||||
-- do with significantly less depending on how many that are really
|
||||
-- needed and improve entropy; e.g. 13 needed -> choose 4*4 instead.
|
||||
----
|
||||
local byte = string.byte
|
||||
local char = string.char
|
||||
local concat = table.concat
|
||||
local fmt = string.format
|
||||
local pairs = pairs
|
||||
local rep = string.rep
|
||||
local sort = table.sort
|
||||
local sub = string.sub
|
||||
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Letter frequencies for reducing symbol entropy (fixed version)
|
||||
-- * Might help a wee bit when the output file is compressed
|
||||
-- * See Wikipedia: http://en.wikipedia.org/wiki/Letter_frequencies
|
||||
-- * We use letter frequencies according to a Linotype keyboard, plus
|
||||
-- the underscore, and both lower case and upper case letters.
|
||||
-- * The arrangement below (LC, underscore, %d, UC) is arbitrary.
|
||||
-- * This is certainly not optimal, but is quick-and-dirty and the
|
||||
-- process has no significant overhead
|
||||
local LETTERS = "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ"
|
||||
local ALPHANUM = "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ"
|
||||
|
||||
-- Names or identifiers that must be skipped.
|
||||
-- (The first two lines are for keywords.)
|
||||
local SKIP_NAME = {}
|
||||
for v in ([[
|
||||
and break do else elseif end false for function if in
|
||||
local nil not or repeat return then true until while
|
||||
self _ENV]]):gmatch("%S+") do
|
||||
SKIP_NAME[v] = true
|
||||
end
|
||||
|
||||
|
||||
local toklist, seminfolist, -- token lists (lexer output)
|
||||
tokpar, seminfopar, xrefpar, -- token lists (parser output)
|
||||
globalinfo, localinfo, -- variable information tables
|
||||
statinfo, -- statment type table
|
||||
globaluniq, localuniq, -- unique name tables
|
||||
var_new, -- index of new variable names
|
||||
varlist -- list of output variables
|
||||
|
||||
--- Preprocesses information table to get lists of unique names.
|
||||
--
|
||||
-- @tparam {table,...} infotable
|
||||
-- @treturn table
|
||||
local function preprocess(infotable)
|
||||
local uniqtable = {}
|
||||
for i = 1, #infotable do -- enumerate info table
|
||||
local obj = infotable[i]
|
||||
local name = obj.name
|
||||
|
||||
if not uniqtable[name] then -- not found, start an entry
|
||||
uniqtable[name] = {
|
||||
decl = 0, token = 0, size = 0,
|
||||
}
|
||||
end
|
||||
|
||||
local uniq = uniqtable[name] -- count declarations, tokens, size
|
||||
uniq.decl = uniq.decl + 1
|
||||
local xref = obj.xref
|
||||
local xcount = #xref
|
||||
uniq.token = uniq.token + xcount
|
||||
uniq.size = uniq.size + xcount * #name
|
||||
|
||||
if obj.decl then -- if local table, create first,last pairs
|
||||
obj.id = i
|
||||
obj.xcount = xcount
|
||||
if xcount > 1 then -- if ==1, means local never accessed
|
||||
obj.first = xref[2]
|
||||
obj.last = xref[xcount]
|
||||
end
|
||||
|
||||
else -- if global table, add a back ref
|
||||
uniq.id = i
|
||||
end
|
||||
|
||||
end--for
|
||||
return uniqtable
|
||||
end
|
||||
|
||||
--- Calculates actual symbol frequencies, in order to reduce entropy.
|
||||
--
|
||||
-- * This may help further reduce the size of compressed sources.
|
||||
-- * Note that since parsing optimizations is put before lexing
|
||||
-- optimizations, the frequency table is not exact!
|
||||
-- * Yes, this will miss --keep block comments too...
|
||||
--
|
||||
-- @tparam table option
|
||||
local function recalc_for_entropy(option)
|
||||
-- table of token classes to accept in calculating symbol frequency
|
||||
local ACCEPT = {
|
||||
TK_KEYWORD = true, TK_NAME = true, TK_NUMBER = true,
|
||||
TK_STRING = true, TK_LSTRING = true,
|
||||
}
|
||||
if not option["opt-comments"] then
|
||||
ACCEPT.TK_COMMENT = true
|
||||
ACCEPT.TK_LCOMMENT = true
|
||||
end
|
||||
|
||||
-- Create a new table and remove any original locals by filtering.
|
||||
local filtered = {}
|
||||
for i = 1, #toklist do
|
||||
filtered[i] = seminfolist[i]
|
||||
end
|
||||
for i = 1, #localinfo do -- enumerate local info table
|
||||
local obj = localinfo[i]
|
||||
local xref = obj.xref
|
||||
for j = 1, obj.xcount do
|
||||
local p = xref[j]
|
||||
filtered[p] = "" -- remove locals
|
||||
end
|
||||
end
|
||||
|
||||
local freq = {} -- reset symbol frequency table
|
||||
for i = 0, 255 do freq[i] = 0 end
|
||||
for i = 1, #toklist do -- gather symbol frequency
|
||||
local tok, info = toklist[i], filtered[i]
|
||||
if ACCEPT[tok] then
|
||||
for j = 1, #info do
|
||||
local c = byte(info, j)
|
||||
freq[c] = freq[c] + 1
|
||||
end
|
||||
end--if
|
||||
end--for
|
||||
|
||||
-- Re-sorts symbols according to actual frequencies.
|
||||
--
|
||||
-- @tparam string symbols
|
||||
-- @treturn string
|
||||
local function resort(symbols)
|
||||
local symlist = {}
|
||||
for i = 1, #symbols do -- prepare table to sort
|
||||
local c = byte(symbols, i)
|
||||
symlist[i] = { c = c, freq = freq[c], }
|
||||
end
|
||||
sort(symlist, function(v1, v2) -- sort selected symbols
|
||||
return v1.freq > v2.freq
|
||||
end)
|
||||
local charlist = {} -- reconstitute the string
|
||||
for i = 1, #symlist do
|
||||
charlist[i] = char(symlist[i].c)
|
||||
end
|
||||
return concat(charlist)
|
||||
end
|
||||
|
||||
LETTERS = resort(LETTERS) -- change letter arrangement
|
||||
ALPHANUM = resort(ALPHANUM)
|
||||
end
|
||||
|
||||
--- Returns a string containing a new local variable name to use, and
|
||||
-- a flag indicating whether it collides with a global variable.
|
||||
--
|
||||
-- Trapping keywords and other names like 'self' is done elsewhere.
|
||||
--
|
||||
-- @treturn string A new local variable name.
|
||||
-- @treturn bool Whether the name collides with a global variable.
|
||||
local function new_var_name()
|
||||
local var
|
||||
local cletters, calphanum = #LETTERS, #ALPHANUM
|
||||
local v = var_new
|
||||
if v < cletters then -- single char
|
||||
v = v + 1
|
||||
var = sub(LETTERS, v, v)
|
||||
else -- longer names
|
||||
local range, sz = cletters, 1 -- calculate # chars fit
|
||||
repeat
|
||||
v = v - range
|
||||
range = range * calphanum
|
||||
sz = sz + 1
|
||||
until range > v
|
||||
local n = v % cletters -- left side cycles faster
|
||||
v = (v - n) / cletters -- do first char first
|
||||
n = n + 1
|
||||
var = sub(LETTERS, n, n)
|
||||
while sz > 1 do
|
||||
local m = v % calphanum
|
||||
v = (v - m) / calphanum
|
||||
m = m + 1
|
||||
var = var..sub(ALPHANUM, m, m)
|
||||
sz = sz - 1
|
||||
end
|
||||
end
|
||||
var_new = var_new + 1
|
||||
return var, globaluniq[var] ~= nil
|
||||
end
|
||||
|
||||
--- Calculates and prints some statistics.
|
||||
--
|
||||
-- Note: probably better in main source, put here for now.
|
||||
--
|
||||
-- @tparam table globaluniq
|
||||
-- @tparam table localuniq
|
||||
-- @tparam table afteruniq
|
||||
-- @tparam table option
|
||||
local function stats_summary(globaluniq, localuniq, afteruniq, option) --luacheck: ignore 431
|
||||
local print = M.print or print
|
||||
local opt_details = option.DETAILS
|
||||
if option.QUIET then return end
|
||||
|
||||
local uniq_g , uniq_li, uniq_lo = 0, 0, 0
|
||||
local decl_g, decl_li, decl_lo = 0, 0, 0
|
||||
local token_g, token_li, token_lo = 0, 0, 0
|
||||
local size_g, size_li, size_lo = 0, 0, 0
|
||||
|
||||
local function avg(c, l) -- safe average function
|
||||
if c == 0 then return 0 end
|
||||
return l / c
|
||||
end
|
||||
|
||||
-- Collect statistics (Note: globals do not have declarations!)
|
||||
for _, uniq in pairs(globaluniq) do
|
||||
uniq_g = uniq_g + 1
|
||||
token_g = token_g + uniq.token
|
||||
size_g = size_g + uniq.size
|
||||
end
|
||||
for _, uniq in pairs(localuniq) do
|
||||
uniq_li = uniq_li + 1
|
||||
decl_li = decl_li + uniq.decl
|
||||
token_li = token_li + uniq.token
|
||||
size_li = size_li + uniq.size
|
||||
end
|
||||
for _, uniq in pairs(afteruniq) do
|
||||
uniq_lo = uniq_lo + 1
|
||||
decl_lo = decl_lo + uniq.decl
|
||||
token_lo = token_lo + uniq.token
|
||||
size_lo = size_lo + uniq.size
|
||||
end
|
||||
local uniq_ti = uniq_g + uniq_li
|
||||
local decl_ti = decl_g + decl_li
|
||||
local token_ti = token_g + token_li
|
||||
local size_ti = size_g + size_li
|
||||
local uniq_to = uniq_g + uniq_lo
|
||||
local decl_to = decl_g + decl_lo
|
||||
local token_to = token_g + token_lo
|
||||
local size_to = size_g + size_lo
|
||||
|
||||
-- Detailed stats: global list
|
||||
if opt_details then
|
||||
local sorted = {} -- sort table of unique global names by size
|
||||
for name, uniq in pairs(globaluniq) do
|
||||
uniq.name = name
|
||||
sorted[#sorted + 1] = uniq
|
||||
end
|
||||
sort(sorted, function(v1, v2)
|
||||
return v1.size > v2.size
|
||||
end)
|
||||
|
||||
do
|
||||
local tabf1, tabf2 = "%8s%8s%10s %s", "%8d%8d%10.2f %s"
|
||||
local hl = rep("-", 44)
|
||||
print("*** global variable list (sorted by size) ***\n"..hl)
|
||||
print(fmt(tabf1, "Token", "Input", "Input", "Global"))
|
||||
print(fmt(tabf1, "Count", "Bytes", "Average", "Name"))
|
||||
print(hl)
|
||||
for i = 1, #sorted do
|
||||
local uniq = sorted[i]
|
||||
print(fmt(tabf2, uniq.token, uniq.size, avg(uniq.token, uniq.size), uniq.name))
|
||||
end
|
||||
print(hl)
|
||||
print(fmt(tabf2, token_g, size_g, avg(token_g, size_g), "TOTAL"))
|
||||
print(hl.."\n")
|
||||
end
|
||||
|
||||
-- Detailed stats: local list
|
||||
do
|
||||
local tabf1, tabf2 = "%8s%8s%8s%10s%8s%10s %s", "%8d%8d%8d%10.2f%8d%10.2f %s"
|
||||
local hl = rep("-", 70)
|
||||
print("*** local variable list (sorted by allocation order) ***\n"..hl)
|
||||
print(fmt(tabf1, "Decl.", "Token", "Input", "Input", "Output", "Output", "Global"))
|
||||
print(fmt(tabf1, "Count", "Count", "Bytes", "Average", "Bytes", "Average", "Name"))
|
||||
print(hl)
|
||||
for i = 1, #varlist do -- iterate according to order assigned
|
||||
local name = varlist[i]
|
||||
local uniq = afteruniq[name]
|
||||
local old_t, old_s = 0, 0
|
||||
for j = 1, #localinfo do -- find corresponding old names and calculate
|
||||
local obj = localinfo[j]
|
||||
if obj.name == name then
|
||||
old_t = old_t + obj.xcount
|
||||
old_s = old_s + obj.xcount * #obj.oldname
|
||||
end
|
||||
end
|
||||
print(fmt(tabf2, uniq.decl, uniq.token, old_s, avg(old_t, old_s),
|
||||
uniq.size, avg(uniq.token, uniq.size), name))
|
||||
end
|
||||
print(hl)
|
||||
print(fmt(tabf2, decl_lo, token_lo, size_li, avg(token_li, size_li),
|
||||
size_lo, avg(token_lo, size_lo), "TOTAL"))
|
||||
print(hl.."\n")
|
||||
end
|
||||
end--if opt_details
|
||||
|
||||
-- Display output
|
||||
do
|
||||
local tabf1, tabf2 = "%-16s%8s%8s%8s%8s%10s", "%-16s%8d%8d%8d%8d%10.2f"
|
||||
local hl = rep("-", 58)
|
||||
print("*** local variable optimization summary ***\n"..hl)
|
||||
print(fmt(tabf1, "Variable", "Unique", "Decl.", "Token", "Size", "Average"))
|
||||
print(fmt(tabf1, "Types", "Names", "Count", "Count", "Bytes", "Bytes"))
|
||||
print(hl)
|
||||
print(fmt(tabf2, "Global", uniq_g, decl_g, token_g, size_g, avg(token_g, size_g)))
|
||||
print(hl)
|
||||
print(fmt(tabf2, "Local (in)", uniq_li, decl_li, token_li, size_li, avg(token_li, size_li)))
|
||||
print(fmt(tabf2, "TOTAL (in)", uniq_ti, decl_ti, token_ti, size_ti, avg(token_ti, size_ti)))
|
||||
print(hl)
|
||||
print(fmt(tabf2, "Local (out)", uniq_lo, decl_lo, token_lo, size_lo, avg(token_lo, size_lo)))
|
||||
print(fmt(tabf2, "TOTAL (out)", uniq_to, decl_to, token_to, size_to, avg(token_to, size_to)))
|
||||
print(hl.."\n")
|
||||
end
|
||||
end
|
||||
|
||||
--- Does experimental optimization for f("string") statements.
|
||||
--
|
||||
-- It's safe to delete parentheses without adding whitespace, as both
|
||||
-- kinds of strings can abut with anything else.
|
||||
local function optimize_func1()
|
||||
|
||||
local function is_strcall(j) -- find f("string") pattern
|
||||
local t1 = tokpar[j + 1] or ""
|
||||
local t2 = tokpar[j + 2] or ""
|
||||
local t3 = tokpar[j + 3] or ""
|
||||
if t1 == "(" and t2 == "<string>" and t3 == ")" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local del_list = {} -- scan for function pattern,
|
||||
local i = 1 -- tokens to be deleted are marked
|
||||
while i <= #tokpar do
|
||||
local id = statinfo[i]
|
||||
if id == "call" and is_strcall(i) then -- found & mark ()
|
||||
del_list[i + 1] = true -- '('
|
||||
del_list[i + 3] = true -- ')'
|
||||
i = i + 3
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- Delete a token and adjust all relevant tables.
|
||||
-- * Currently invalidates globalinfo and localinfo (not updated),
|
||||
-- so any other optimization is done after processing locals
|
||||
-- (of course, we can also lex the source data again...).
|
||||
-- * Faster one-pass token deletion.
|
||||
local del_list2 = {}
|
||||
do
|
||||
local i, dst, idend = 1, 1, #tokpar
|
||||
while dst <= idend do -- process parser tables
|
||||
if del_list[i] then -- found a token to delete?
|
||||
del_list2[xrefpar[i]] = true
|
||||
i = i + 1
|
||||
end
|
||||
if i > dst then
|
||||
if i <= idend then -- shift table items lower
|
||||
tokpar[dst] = tokpar[i]
|
||||
seminfopar[dst] = seminfopar[i]
|
||||
xrefpar[dst] = xrefpar[i] - (i - dst)
|
||||
statinfo[dst] = statinfo[i]
|
||||
else -- nil out excess entries
|
||||
tokpar[dst] = nil
|
||||
seminfopar[dst] = nil
|
||||
xrefpar[dst] = nil
|
||||
statinfo[dst] = nil
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
dst = dst + 1
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local i, dst, idend = 1, 1, #toklist
|
||||
while dst <= idend do -- process lexer tables
|
||||
if del_list2[i] then -- found a token to delete?
|
||||
i = i + 1
|
||||
end
|
||||
if i > dst then
|
||||
if i <= idend then -- shift table items lower
|
||||
toklist[dst] = toklist[i]
|
||||
seminfolist[dst] = seminfolist[i]
|
||||
else -- nil out excess entries
|
||||
toklist[dst] = nil
|
||||
seminfolist[dst] = nil
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
dst = dst + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Does local variable optimization.
|
||||
--
|
||||
-- @tparam {[string]=bool,...} option
|
||||
local function optimize_locals(option)
|
||||
var_new = 0 -- reset variable name allocator
|
||||
varlist = {}
|
||||
|
||||
-- Preprocess global/local tables, handle entropy reduction.
|
||||
globaluniq = preprocess(globalinfo)
|
||||
localuniq = preprocess(localinfo)
|
||||
if option["opt-entropy"] then -- for entropy improvement
|
||||
recalc_for_entropy(option)
|
||||
end
|
||||
|
||||
-- Build initial declared object table, then sort according to
|
||||
-- token count, this might help assign more tokens to more common
|
||||
-- variable names such as 'e' thus possibly reducing entropy.
|
||||
-- * An object knows its localinfo index via its 'id' field.
|
||||
-- * Special handling for "self" and "_ENV" special local (parameter) here.
|
||||
local object = {}
|
||||
for i = 1, #localinfo do
|
||||
object[i] = localinfo[i]
|
||||
end
|
||||
sort(object, function(v1, v2) -- sort largest first
|
||||
return v1.xcount > v2.xcount
|
||||
end)
|
||||
|
||||
-- The special "self" and "_ENV" function parameters must be preserved.
|
||||
-- * The allocator below will never use "self", so it is safe to
|
||||
-- keep those implicit declarations as-is.
|
||||
local temp, j, used_specials = {}, 1, {}
|
||||
for i = 1, #object do
|
||||
local obj = object[i]
|
||||
if not obj.is_special then
|
||||
temp[j] = obj
|
||||
j = j + 1
|
||||
else
|
||||
used_specials[#used_specials + 1] = obj.name
|
||||
end
|
||||
end
|
||||
object = temp
|
||||
|
||||
-- A simple first-come first-served heuristic name allocator,
|
||||
-- note that this is in no way optimal...
|
||||
-- * Each object is a local variable declaration plus existence.
|
||||
-- * The aim is to assign short names to as many tokens as possible,
|
||||
-- so the following tries to maximize name reuse.
|
||||
-- * Note that we preserve sort order.
|
||||
local nobject = #object
|
||||
while nobject > 0 do
|
||||
local varname, gcollide
|
||||
repeat
|
||||
varname, gcollide = new_var_name() -- collect a variable name
|
||||
until not SKIP_NAME[varname] -- skip all special names
|
||||
varlist[#varlist + 1] = varname -- keep a list
|
||||
local oleft = nobject
|
||||
|
||||
-- If variable name collides with an existing global, the name
|
||||
-- cannot be used by a local when the name is accessed as a global
|
||||
-- during which the local is alive (between 'act' to 'rem'), so
|
||||
-- we drop objects that collides with the corresponding global.
|
||||
if gcollide then
|
||||
-- find the xref table of the global
|
||||
local gref = globalinfo[globaluniq[varname].id].xref
|
||||
local ngref = #gref
|
||||
-- enumerate for all current objects; all are valid at this point
|
||||
for i = 1, nobject do
|
||||
local obj = object[i]
|
||||
local act, rem = obj.act, obj.rem -- 'live' range of local
|
||||
-- if rem < 0, it is a -id to a local that had the same name
|
||||
-- so follow rem to extend it; does this make sense?
|
||||
while rem < 0 do
|
||||
rem = localinfo[-rem].rem
|
||||
end
|
||||
local drop
|
||||
for j = 1, ngref do
|
||||
local p = gref[j]
|
||||
if p >= act and p <= rem then drop = true end -- in range?
|
||||
end
|
||||
if drop then
|
||||
obj.skip = true
|
||||
oleft = oleft - 1
|
||||
end
|
||||
end--for
|
||||
end--if gcollide
|
||||
|
||||
-- Now the first unassigned local (since it's sorted) will be the
|
||||
-- one with the most tokens to rename, so we set this one and then
|
||||
-- eliminate all others that collides, then any locals that left
|
||||
-- can then reuse the same variable name; this is repeated until
|
||||
-- all local declaration that can use this name is assigned.
|
||||
--
|
||||
-- The criteria for local-local reuse/collision is:
|
||||
-- A is the local with a name already assigned
|
||||
-- B is the unassigned local under consideration
|
||||
-- => anytime A is accessed, it cannot be when B is 'live'
|
||||
-- => to speed up things, we have first/last accesses noted
|
||||
while oleft > 0 do
|
||||
local i = 1
|
||||
while object[i].skip do -- scan for first object
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- First object is free for assignment of the variable name
|
||||
-- [first,last] gives the access range for collision checking.
|
||||
oleft = oleft - 1
|
||||
local obja = object[i]
|
||||
i = i + 1
|
||||
obja.newname = varname
|
||||
obja.skip = true
|
||||
obja.done = true
|
||||
local first, last = obja.first, obja.last
|
||||
local xref = obja.xref
|
||||
|
||||
-- Then, scan all the rest and drop those colliding.
|
||||
-- If A was never accessed then it'll never collide with anything
|
||||
-- otherwise trivial skip if:
|
||||
-- * B was activated after A's last access (last < act),
|
||||
-- * B was removed before A's first access (first > rem),
|
||||
-- if not, see detailed skip below...
|
||||
if first and oleft > 0 then -- must have at least 1 access
|
||||
local scanleft = oleft
|
||||
while scanleft > 0 do
|
||||
while object[i].skip do -- next valid object
|
||||
i = i + 1
|
||||
end
|
||||
scanleft = scanleft - 1
|
||||
local objb = object[i]
|
||||
i = i + 1
|
||||
local act, rem = objb.act, objb.rem -- live range of B
|
||||
-- if rem < 0, extend range of rem thru' following local
|
||||
while rem < 0 do
|
||||
rem = localinfo[-rem].rem
|
||||
end
|
||||
|
||||
if not(last < act or first > rem) then -- possible collision
|
||||
|
||||
-- B is activated later than A or at the same statement,
|
||||
-- this means for no collision, A cannot be accessed when B
|
||||
-- is alive, since B overrides A (or is a peer).
|
||||
if act >= obja.act then
|
||||
for j = 1, obja.xcount do -- ... then check every access
|
||||
local p = xref[j]
|
||||
if p >= act and p <= rem then -- A accessed when B live!
|
||||
oleft = oleft - 1
|
||||
objb.skip = true
|
||||
break
|
||||
end
|
||||
end--for
|
||||
|
||||
-- A is activated later than B, this means for no collision,
|
||||
-- A's access is okay since it overrides B, but B's last
|
||||
-- access need to be earlier than A's activation time.
|
||||
else
|
||||
if objb.last and objb.last >= obja.act then
|
||||
oleft = oleft - 1
|
||||
objb.skip = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if oleft == 0 then break end
|
||||
end
|
||||
end--if first
|
||||
|
||||
end--while
|
||||
|
||||
-- After assigning all possible locals to one variable name, the
|
||||
-- unassigned locals/objects have the skip field reset and the table
|
||||
-- is compacted, to hopefully reduce iteration time.
|
||||
local temp, j = {}, 1
|
||||
for i = 1, nobject do
|
||||
local obj = object[i]
|
||||
if not obj.done then
|
||||
obj.skip = false
|
||||
temp[j] = obj
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
object = temp -- new compacted object table
|
||||
nobject = #object -- objects left to process
|
||||
|
||||
end--while
|
||||
|
||||
-- After assigning all locals with new variable names, we can
|
||||
-- patch in the new names, and reprocess to get 'after' stats.
|
||||
for i = 1, #localinfo do -- enumerate all locals
|
||||
local obj = localinfo[i]
|
||||
local xref = obj.xref
|
||||
if obj.newname then -- if got new name, patch it in
|
||||
for j = 1, obj.xcount do
|
||||
local p = xref[j] -- xrefs indexes the token list
|
||||
seminfolist[p] = obj.newname
|
||||
end
|
||||
obj.name, obj.oldname -- adjust names
|
||||
= obj.newname, obj.name
|
||||
else
|
||||
obj.oldname = obj.name -- for cases like 'self'
|
||||
end
|
||||
end
|
||||
|
||||
-- Deal with statistics output.
|
||||
for _, name in ipairs(used_specials) do
|
||||
varlist[#varlist + 1] = name
|
||||
end
|
||||
local afteruniq = preprocess(localinfo)
|
||||
stats_summary(globaluniq, localuniq, afteruniq, option)
|
||||
end
|
||||
|
||||
--- The main entry point.
|
||||
--
|
||||
-- @tparam table option
|
||||
-- @tparam {string,...} _toklist
|
||||
-- @tparam {string,...} _seminfolist
|
||||
-- @tparam table xinfo
|
||||
function M.optimize(option, _toklist, _seminfolist, xinfo)
|
||||
-- set tables
|
||||
toklist, seminfolist -- from lexer
|
||||
= _toklist, _seminfolist
|
||||
tokpar, seminfopar, xrefpar -- from parser
|
||||
= xinfo.toklist, xinfo.seminfolist, xinfo.xreflist
|
||||
globalinfo, localinfo, statinfo -- from parser
|
||||
= xinfo.globalinfo, xinfo.localinfo, xinfo.statinfo
|
||||
|
||||
-- Optimize locals.
|
||||
if option["opt-locals"] then
|
||||
optimize_locals(option)
|
||||
end
|
||||
|
||||
-- Other optimizations.
|
||||
if option["opt-experimental"] then -- experimental
|
||||
optimize_func1()
|
||||
-- WARNING globalinfo and localinfo now invalidated!
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
90
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/example.lua
Normal file
90
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/example.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
---------
|
||||
-- Example of a plugin for LuaSrcDiet.
|
||||
--
|
||||
-- WARNING: highly experimental! interface liable to change
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * Any function can be omitted and LuaSrcDiet won't call it.
|
||||
-- * The functions are:
|
||||
-- (1) init(_option, _srcfl, _destfl)
|
||||
-- (2) post_load(z) can return z
|
||||
-- (3) post_lex(toklist, seminfolist, toklnlist)
|
||||
-- (4) post_parse(globalinfo, localinfo)
|
||||
-- (5) post_optparse()
|
||||
-- (6) post_optlex(toklist, seminfolist, toklnlist)
|
||||
-- * Older tables can be copied and kept in the plugin and used later.
|
||||
-- * If you modify 'option', remember that LuaSrcDiet might be
|
||||
-- processing more than one file.
|
||||
-- * Arrangement of the functions is not final!
|
||||
-- * TODO: can't process additional options from command line yet
|
||||
----
|
||||
|
||||
local M = {}
|
||||
|
||||
local option -- local reference to list of options
|
||||
local srcfl, destfl -- filenames
|
||||
local old_quiet
|
||||
|
||||
local function print(...) -- handle quiet option
|
||||
if option.QUIET then return end
|
||||
_G.print(...)
|
||||
end
|
||||
|
||||
--- Initialization.
|
||||
--
|
||||
-- @tparam {[string]=bool,...} _option
|
||||
-- @tparam string _srcfl Path of the source file.
|
||||
-- @tparam string _destfl Path of the destination file.
|
||||
function M.init(_option, _srcfl, _destfl)
|
||||
option = _option
|
||||
srcfl, destfl = _srcfl, _destfl
|
||||
-- plugin can impose its own option starting from here
|
||||
end
|
||||
|
||||
--- Message display, post-load processing, can return z.
|
||||
function M.post_load(z)
|
||||
-- this message will print after the LuaSrcDiet title message
|
||||
print([[
|
||||
Example plugin module for LuaSrcDiet
|
||||
]])
|
||||
print("Example: source file name is '"..srcfl.."'")
|
||||
print("Example: destination file name is '"..destfl.."'")
|
||||
print("Example: the size of the source file is "..#z.." bytes")
|
||||
-- returning z is optional; this allows optional replacement of
|
||||
-- the source data prior to lexing
|
||||
return z
|
||||
end
|
||||
|
||||
--- Post-lexing processing, can work on lexer table output.
|
||||
function M.post_lex(toklist, seminfolist, toklnlist) --luacheck: ignore
|
||||
print("Example: the number of lexed elements is "..#toklist)
|
||||
end
|
||||
|
||||
--- Post-parsing processing, gives globalinfo, localinfo.
|
||||
function M.post_parse(globalinfo, localinfo)
|
||||
print("Example: size of globalinfo is "..#globalinfo)
|
||||
print("Example: size of localinfo is "..#localinfo)
|
||||
old_quiet = option.QUIET
|
||||
option.QUIET = true
|
||||
end
|
||||
|
||||
--- Post-parser optimization processing, can get tables from elsewhere.
|
||||
function M.post_optparse()
|
||||
option.QUIET = old_quiet
|
||||
print("Example: pretend to do post-optparse")
|
||||
end
|
||||
|
||||
--- Post-lexer optimization processing, can get tables from elsewhere.
|
||||
function M.post_optlex(toklist, seminfolist, toklnlist) --luacheck: ignore
|
||||
print("Example: pretend to do post-optlex")
|
||||
-- restore old settings, other file might need original settings
|
||||
option.QUIET = old_quiet
|
||||
-- option.EXIT can be set at the end of any post_* function to stop
|
||||
-- further processing and exit for the current file being worked on
|
||||
-- in this case, final stats printout is disabled and the output will
|
||||
-- not be written to the destination file
|
||||
option.EXIT = true
|
||||
end
|
||||
|
||||
return M
|
||||
177
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/html.lua
Normal file
177
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/html.lua
Normal file
@@ -0,0 +1,177 @@
|
||||
---------
|
||||
-- Turns Lua 5.1 source code into HTML files.
|
||||
--
|
||||
-- WARNING: highly experimental! interface liable to change
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * This HTML highlighter marks globals brightly so that their usage
|
||||
-- can be manually optimized.
|
||||
-- * Either uses a .html extension for output files or it follows the
|
||||
-- -o <filespec> option.
|
||||
-- * The HTML style tries to follow that of the Lua wiki.
|
||||
----
|
||||
local fs = require "luasrcdiet.fs"
|
||||
|
||||
local concat = table.concat
|
||||
local find = string.find
|
||||
local fmt = string.format
|
||||
local sub = string.sub
|
||||
|
||||
local M = {}
|
||||
|
||||
local HTML_EXT = ".html"
|
||||
local ENTITIES = {
|
||||
["&"] = "&", ["<"] = "<", [">"] = ">",
|
||||
["'"] = "'", ["\""] = """,
|
||||
}
|
||||
|
||||
-- simple headers and footers
|
||||
local HEADER = [[
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<meta name="Generator" content="LuaSrcDiet">
|
||||
<style type="text/css">
|
||||
%s</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="code">
|
||||
]]
|
||||
local FOOTER = [[
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
]]
|
||||
-- for more, please see wikimain.css from the Lua wiki site
|
||||
local STYLESHEET = [[
|
||||
BODY {
|
||||
background: white;
|
||||
color: navy;
|
||||
}
|
||||
pre.code { color: black; }
|
||||
span.comment { color: #00a000; }
|
||||
span.string { color: #009090; }
|
||||
span.keyword { color: black; font-weight: bold; }
|
||||
span.number { color: #993399; }
|
||||
span.operator { }
|
||||
span.name { }
|
||||
span.global { color: #ff0000; font-weight: bold; }
|
||||
span.local { color: #0000ff; font-weight: bold; }
|
||||
]]
|
||||
|
||||
local option -- local reference to list of options
|
||||
local srcfl, destfl -- filenames
|
||||
local toklist, seminfolist -- token data
|
||||
|
||||
local function print(...) -- handle quiet option
|
||||
if option.QUIET then return end
|
||||
_G.print(...)
|
||||
end
|
||||
|
||||
--- Initialization.
|
||||
function M.init(_option, _srcfl)
|
||||
option = _option
|
||||
srcfl = _srcfl
|
||||
local extb, _ = find(srcfl, "%.[^%.%\\%/]*$")
|
||||
local basename = srcfl
|
||||
if extb and extb > 1 then
|
||||
basename = sub(srcfl, 1, extb - 1)
|
||||
end
|
||||
destfl = basename..HTML_EXT
|
||||
if option.OUTPUT_FILE then
|
||||
destfl = option.OUTPUT_FILE
|
||||
end
|
||||
if srcfl == destfl then
|
||||
error("output filename identical to input filename")
|
||||
end
|
||||
end
|
||||
|
||||
--- Message display, post-load processing.
|
||||
function M.post_load()
|
||||
print([[
|
||||
HTML plugin module for LuaSrcDiet
|
||||
]])
|
||||
print("Exporting: "..srcfl.." -> "..destfl.."\n")
|
||||
end
|
||||
|
||||
--- Post-lexing processing, can work on lexer table output.
|
||||
function M.post_lex(_toklist, _seminfolist)
|
||||
toklist, seminfolist = _toklist, _seminfolist
|
||||
end
|
||||
|
||||
--- Escapes the usual suspects for HTML/XML.
|
||||
local function do_entities(z)
|
||||
local i = 1
|
||||
while i <= #z do
|
||||
local c = sub(z, i, i)
|
||||
local d = ENTITIES[c]
|
||||
if d then
|
||||
c = d
|
||||
z = sub(z, 1, i - 1)..c..sub(z, i + 1)
|
||||
end
|
||||
i = i + #c
|
||||
end--while
|
||||
return z
|
||||
end
|
||||
|
||||
--- Post-parsing processing, gives globalinfo, localinfo.
|
||||
function M.post_parse(globalinfo, localinfo)
|
||||
local html = {}
|
||||
local function add(s) -- html helpers
|
||||
html[#html + 1] = s
|
||||
end
|
||||
local function span(class, s)
|
||||
add('<span class="'..class..'">'..s..'</span>')
|
||||
end
|
||||
|
||||
for i = 1, #globalinfo do -- mark global identifiers as TK_GLOBAL
|
||||
local obj = globalinfo[i]
|
||||
local xref = obj.xref
|
||||
for j = 1, #xref do
|
||||
local p = xref[j]
|
||||
toklist[p] = "TK_GLOBAL"
|
||||
end
|
||||
end--for
|
||||
|
||||
for i = 1, #localinfo do -- mark local identifiers as TK_LOCAL
|
||||
local obj = localinfo[i]
|
||||
local xref = obj.xref
|
||||
for j = 1, #xref do
|
||||
local p = xref[j]
|
||||
toklist[p] = "TK_LOCAL"
|
||||
end
|
||||
end--for
|
||||
|
||||
add(fmt(HEADER, -- header and leading stuff
|
||||
do_entities(srcfl),
|
||||
STYLESHEET))
|
||||
for i = 1, #toklist do -- enumerate token list
|
||||
local tok, info = toklist[i], seminfolist[i]
|
||||
if tok == "TK_KEYWORD" then
|
||||
span("keyword", info)
|
||||
elseif tok == "TK_STRING" or tok == "TK_LSTRING" then
|
||||
span("string", do_entities(info))
|
||||
elseif tok == "TK_COMMENT" or tok == "TK_LCOMMENT" then
|
||||
span("comment", do_entities(info))
|
||||
elseif tok == "TK_GLOBAL" then
|
||||
span("global", info)
|
||||
elseif tok == "TK_LOCAL" then
|
||||
span("local", info)
|
||||
elseif tok == "TK_NAME" then
|
||||
span("name", info)
|
||||
elseif tok == "TK_NUMBER" then
|
||||
span("number", info)
|
||||
elseif tok == "TK_OP" then
|
||||
span("operator", do_entities(info))
|
||||
elseif tok ~= "TK_EOS" then -- TK_EOL, TK_SPACE
|
||||
add(info)
|
||||
end
|
||||
end--for
|
||||
add(FOOTER)
|
||||
assert(fs.write_file(destfl, concat(html), "wb"))
|
||||
option.EXIT = true
|
||||
end
|
||||
|
||||
return M
|
||||
89
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/sloc.lua
Normal file
89
Utils/luarocks/share/lua/5.1/luasrcdiet/plugin/sloc.lua
Normal file
@@ -0,0 +1,89 @@
|
||||
---------
|
||||
-- Calculates SLOC for Lua 5.1 scripts
|
||||
--
|
||||
-- WARNING: highly experimental! interface liable to change
|
||||
--
|
||||
-- **Notes:**
|
||||
--
|
||||
-- * SLOC's behaviour is based on David Wheeler's SLOCCount.
|
||||
-- * Empty lines and comment don't count as significant.
|
||||
-- * Empty lines in long strings are also insignificant. This is
|
||||
-- debatable. In SLOCCount, this allows counting of invalid multi-
|
||||
-- line strings for C. But an empty line is still an empty line.
|
||||
-- * Ignores the --quiet option, print own result line.
|
||||
----
|
||||
|
||||
local M = {}
|
||||
|
||||
local option -- local reference to list of options
|
||||
local srcfl -- source file name
|
||||
|
||||
function M.init(_option, _srcfl)
|
||||
option = _option
|
||||
option.QUIET = true
|
||||
srcfl = _srcfl
|
||||
end
|
||||
|
||||
--- Splits a block into a table of lines (minus EOLs).
|
||||
--
|
||||
-- @tparam string blk
|
||||
-- @treturn {string,...} lines
|
||||
local function split(blk)
|
||||
local lines = {}
|
||||
local i, nblk = 1, #blk
|
||||
while i <= nblk do
|
||||
local p, q, r, s = blk:find("([\r\n])([\r\n]?)", i)
|
||||
if not p then
|
||||
p = nblk + 1
|
||||
end
|
||||
lines[#lines + 1] = blk:sub(i, p - 1)
|
||||
i = p + 1
|
||||
if p < nblk and q > p and r ~= s then -- handle Lua-style CRLF, LFCR
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
--- Post-lexing processing, can work on lexer table output.
|
||||
function M.post_lex(toklist, seminfolist, toklnlist)
|
||||
local lnow, sloc = 0, 0
|
||||
local function chk(ln) -- if a new line, count it as an SLOC
|
||||
if ln > lnow then -- new line # must be > old line #
|
||||
sloc = sloc + 1; lnow = ln
|
||||
end
|
||||
end
|
||||
for i = 1, #toklist do -- enumerate over all tokens
|
||||
local tok, info, ln
|
||||
= toklist[i], seminfolist[i], toklnlist[i]
|
||||
|
||||
if tok == "TK_KEYWORD" or tok == "TK_NAME" or -- significant
|
||||
tok == "TK_NUMBER" or tok == "TK_OP" then
|
||||
chk(ln)
|
||||
|
||||
-- Both TK_STRING and TK_LSTRING may be multi-line, hence, a loop
|
||||
-- is needed in order to mark off lines one-by-one. Since llex.lua
|
||||
-- currently returns the line number of the last part of the string,
|
||||
-- we must subtract in order to get the starting line number.
|
||||
elseif tok == "TK_STRING" then -- possible multi-line
|
||||
local t = split(info)
|
||||
ln = ln - #t + 1
|
||||
for _ = 1, #t do
|
||||
chk(ln); ln = ln + 1
|
||||
end
|
||||
|
||||
elseif tok == "TK_LSTRING" then -- possible multi-line
|
||||
local t = split(info)
|
||||
ln = ln - #t + 1
|
||||
for j = 1, #t do
|
||||
if t[j] ~= "" then chk(ln) end
|
||||
ln = ln + 1
|
||||
end
|
||||
-- Other tokens are comments or whitespace and are ignored.
|
||||
end
|
||||
end--for
|
||||
print(srcfl..": "..sloc) -- display result
|
||||
option.EXIT = true
|
||||
end
|
||||
|
||||
return M
|
||||
30
Utils/luarocks/share/lua/5.1/luasrcdiet/utils.lua
Normal file
30
Utils/luarocks/share/lua/5.1/luasrcdiet/utils.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
---------
|
||||
-- General utility functions.
|
||||
--
|
||||
-- **Note: This module is not part of public API!**
|
||||
----
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Returns a new table containing the contents of all the given tables.
|
||||
-- Tables are iterated using @{pairs}, so this function is intended for tables
|
||||
-- that represent *associative arrays*. Entries with duplicate keys are
|
||||
-- overwritten with the values from a later table.
|
||||
--
|
||||
-- @tparam {table,...} ... The tables to merge.
|
||||
-- @treturn table A new table.
|
||||
function M.merge (...)
|
||||
local result = {}
|
||||
|
||||
for _, tab in ipairs{...} do
|
||||
for key, val in pairs(tab) do
|
||||
result[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return M
|
||||
1359
Utils/luarocks/share/lua/5.1/markdown.lua
Normal file
1359
Utils/luarocks/share/lua/5.1/markdown.lua
Normal file
File diff suppressed because it is too large
Load Diff
181
Utils/luarocks/share/lua/5.1/metalua/compiler.lua
Normal file
181
Utils/luarocks/share/lua/5.1/metalua/compiler.lua
Normal file
@@ -0,0 +1,181 @@
|
||||
---------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Convert between various code representation formats. Atomic
|
||||
-- converters are written in extenso, others are composed automatically
|
||||
-- by chaining the atomic ones together in a closure.
|
||||
--
|
||||
-- Supported formats are:
|
||||
--
|
||||
-- * srcfile: the name of a file containing sources.
|
||||
-- * src: these sources as a single string.
|
||||
-- * lexstream: a stream of lexemes.
|
||||
-- * ast: an abstract syntax tree.
|
||||
-- * proto: a (Yueliang) struture containing a high level
|
||||
-- representation of bytecode. Largely based on the
|
||||
-- Proto structure in Lua's VM
|
||||
-- * bytecode: a string dump of the function, as taken by
|
||||
-- loadstring() and produced by string.dump().
|
||||
-- * function: an executable lua function in RAM.
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local checks = require 'checks'
|
||||
|
||||
local M = { }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can
|
||||
-- be transformed into a 'b' (but not the other way around).
|
||||
-- M.sequence goes for numbers to format names, M.order goes from format
|
||||
-- names to numbers.
|
||||
--------------------------------------------------------------------------------
|
||||
M.sequence = {
|
||||
'srcfile', 'src', 'lexstream', 'ast', 'proto', 'bytecode', 'function' }
|
||||
|
||||
local arg_types = {
|
||||
srcfile = { 'string', '?string' },
|
||||
src = { 'string', '?string' },
|
||||
lexstream = { 'lexer.stream', '?string' },
|
||||
ast = { 'table', '?string' },
|
||||
proto = { 'table', '?string' },
|
||||
bytecode = { 'string', '?string' },
|
||||
}
|
||||
|
||||
if false then
|
||||
-- if defined, runs on every newly-generated AST
|
||||
function M.check_ast(ast)
|
||||
local function rec(x, n, parent)
|
||||
if not x.lineinfo and parent.lineinfo then
|
||||
local pp = require 'metalua.pprint'
|
||||
pp.printf("WARNING: Missing lineinfo in child #%s `%s{...} of node at %s",
|
||||
n, x.tag or '', tostring(parent.lineinfo))
|
||||
end
|
||||
for i, child in ipairs(x) do
|
||||
if type(child)=='table' then rec(child, i, x) end
|
||||
end
|
||||
end
|
||||
rec(ast, -1, { })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
M.order= { }; for a,b in pairs(M.sequence) do M.order[b]=a end
|
||||
|
||||
local CONV = { } -- conversion metatable __index
|
||||
|
||||
function CONV :srcfile_to_src(x, name)
|
||||
checks('metalua.compiler', 'string', '?string')
|
||||
name = name or '@'..x
|
||||
local f, msg = io.open (x, 'rb')
|
||||
if not f then error(msg) end
|
||||
local r, msg = f :read '*a'
|
||||
if not r then error("Cannot read file '"..x.."': "..msg) end
|
||||
f :close()
|
||||
return r, name
|
||||
end
|
||||
|
||||
function CONV :src_to_lexstream(src, name)
|
||||
checks('metalua.compiler', 'string', '?string')
|
||||
local r = self.parser.lexer :newstream (src, name)
|
||||
return r, name
|
||||
end
|
||||
|
||||
function CONV :lexstream_to_ast(lx, name)
|
||||
checks('metalua.compiler', 'lexer.stream', '?string')
|
||||
local r = self.parser.chunk(lx)
|
||||
r.source = name
|
||||
if M.check_ast then M.check_ast (r) end
|
||||
return r, name
|
||||
end
|
||||
|
||||
local bytecode_compiler = nil -- cache to avoid repeated `pcall(require(...))`
|
||||
local function get_bytecode_compiler()
|
||||
if bytecode_compiler then return bytecode_compiler else
|
||||
local status, result = pcall(require, 'metalua.compiler.bytecode')
|
||||
if status then
|
||||
bytecode_compiler = result
|
||||
return result
|
||||
elseif string.match(result, "not found") then
|
||||
error "Compilation only available with full Metalua"
|
||||
else error (result) end
|
||||
end
|
||||
end
|
||||
|
||||
function CONV :ast_to_proto(ast, name)
|
||||
checks('metalua.compiler', 'table', '?string')
|
||||
return get_bytecode_compiler().ast_to_proto(ast, name), name
|
||||
end
|
||||
|
||||
function CONV :proto_to_bytecode(proto, name)
|
||||
return get_bytecode_compiler().proto_to_bytecode(proto), name
|
||||
end
|
||||
|
||||
function CONV :bytecode_to_function(bc, name)
|
||||
checks('metalua.compiler', 'string', '?string')
|
||||
return loadstring(bc, name)
|
||||
end
|
||||
|
||||
-- Create all sensible combinations
|
||||
for i=1,#M.sequence do
|
||||
local src = M.sequence[i]
|
||||
for j=i+2, #M.sequence do
|
||||
local dst = M.sequence[j]
|
||||
local dst_name = src.."_to_"..dst
|
||||
local my_arg_types = arg_types[src]
|
||||
local functions = { }
|
||||
for k=i, j-1 do
|
||||
local name = M.sequence[k].."_to_"..M.sequence[k+1]
|
||||
local f = assert(CONV[name], name)
|
||||
table.insert (functions, f)
|
||||
end
|
||||
CONV[dst_name] = function(self, a, b)
|
||||
checks('metalua.compiler', unpack(my_arg_types))
|
||||
for _, f in ipairs(functions) do
|
||||
a, b = f(self, a, b)
|
||||
end
|
||||
return a, b
|
||||
end
|
||||
--printf("Created M.%s out of %s", dst_name, table.concat(n, ', '))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- This one goes in the "wrong" direction, cannot be composed.
|
||||
--------------------------------------------------------------------------------
|
||||
function CONV :function_to_bytecode(...) return string.dump(...) end
|
||||
|
||||
function CONV :ast_to_src(...)
|
||||
require 'metalua.loader' -- ast_to_string isn't written in plain lua
|
||||
return require 'metalua.compiler.ast_to_src' (...)
|
||||
end
|
||||
|
||||
local MT = { __index=CONV, __type='metalua.compiler' }
|
||||
|
||||
function M.new()
|
||||
local parser = require 'metalua.compiler.parser' .new()
|
||||
local self = { parser = parser }
|
||||
setmetatable(self, MT)
|
||||
return self
|
||||
end
|
||||
|
||||
return M
|
||||
682
Utils/luarocks/share/lua/5.1/metalua/compiler/ast_to_src.mlua
Normal file
682
Utils/luarocks/share/lua/5.1/metalua/compiler/ast_to_src.mlua
Normal file
@@ -0,0 +1,682 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-{ extension ('match', ...) }
|
||||
|
||||
local M = { }
|
||||
M.__index = M
|
||||
M.__call = |self, ...| self:run(...)
|
||||
|
||||
local pp=require 'metalua.pprint'
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Instanciate a new AST->source synthetizer
|
||||
--------------------------------------------------------------------------------
|
||||
function M.new ()
|
||||
local self = {
|
||||
_acc = { }, -- Accumulates pieces of source as strings
|
||||
current_indent = 0, -- Current level of line indentation
|
||||
indent_step = " " -- Indentation symbol, normally spaces or '\t'
|
||||
}
|
||||
return setmetatable (self, M)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Run a synthetizer on the `ast' arg and return the source as a string.
|
||||
-- Can also be used as a static method `M.run (ast)'; in this case,
|
||||
-- a temporary Metizer is instanciated on the fly.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:run (ast)
|
||||
if not ast then
|
||||
self, ast = M.new(), self
|
||||
end
|
||||
self._acc = { }
|
||||
self:node (ast)
|
||||
return table.concat (self._acc)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Accumulate a piece of source file in the synthetizer.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:acc (x)
|
||||
if x then table.insert (self._acc, x) end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Accumulate an indented newline.
|
||||
-- Jumps an extra line if indentation is 0, so that
|
||||
-- toplevel definitions are separated by an extra empty line.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:nl ()
|
||||
if self.current_indent == 0 then self:acc "\n" end
|
||||
self:acc ("\n" .. self.indent_step:rep (self.current_indent))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Increase indentation and accumulate a new line.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:nlindent ()
|
||||
self.current_indent = self.current_indent + 1
|
||||
self:nl ()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Decrease indentation and accumulate a new line.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:nldedent ()
|
||||
self.current_indent = self.current_indent - 1
|
||||
self:acc ("\n" .. self.indent_step:rep (self.current_indent))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Keywords, which are illegal as identifiers.
|
||||
--------------------------------------------------------------------------------
|
||||
local keywords_list = {
|
||||
"and", "break", "do", "else", "elseif",
|
||||
"end", "false", "for", "function", "if",
|
||||
"in", "local", "nil", "not", "or",
|
||||
"repeat", "return", "then", "true", "until",
|
||||
"while" }
|
||||
local keywords = { }
|
||||
for _, kw in pairs(keywords_list) do keywords[kw]=true end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Return true iff string `id' is a legal identifier name.
|
||||
--------------------------------------------------------------------------------
|
||||
local function is_ident (id)
|
||||
return string['match'](id, "^[%a_][%w_]*$") and not keywords[id]
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Return true iff ast represents a legal function name for
|
||||
-- syntax sugar ``function foo.bar.gnat() ... end'':
|
||||
-- a series of nested string indexes, with an identifier as
|
||||
-- the innermost node.
|
||||
--------------------------------------------------------------------------------
|
||||
local function is_idx_stack (ast)
|
||||
match ast with
|
||||
| `Id{ _ } -> return true
|
||||
| `Index{ left, `String{ _ } } -> return is_idx_stack (left)
|
||||
| _ -> return false
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Operator precedences, in increasing order.
|
||||
-- This is not directly used, it's used to generate op_prec below.
|
||||
--------------------------------------------------------------------------------
|
||||
local op_preprec = {
|
||||
{ "or", "and" },
|
||||
{ "lt", "le", "eq", "ne" },
|
||||
{ "concat" },
|
||||
{ "add", "sub" },
|
||||
{ "mul", "div", "mod" },
|
||||
{ "unary", "not", "len" },
|
||||
{ "pow" },
|
||||
{ "index" } }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- operator --> precedence table, generated from op_preprec.
|
||||
--------------------------------------------------------------------------------
|
||||
local op_prec = { }
|
||||
|
||||
for prec, ops in ipairs (op_preprec) do
|
||||
for _, op in ipairs (ops) do
|
||||
op_prec[op] = prec
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- operator --> source representation.
|
||||
--------------------------------------------------------------------------------
|
||||
local op_symbol = {
|
||||
add = " + ", sub = " - ", mul = " * ",
|
||||
div = " / ", mod = " % ", pow = " ^ ",
|
||||
concat = " .. ", eq = " == ", ne = " ~= ",
|
||||
lt = " < ", le = " <= ", ["and"] = " and ",
|
||||
["or"] = " or ", ["not"] = "not ", len = "# " }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Accumulate the source representation of AST `node' in
|
||||
-- the synthetizer. Most of the work is done by delegating to
|
||||
-- the method having the name of the AST tag.
|
||||
-- If something can't be converted to normal sources, it's
|
||||
-- instead dumped as a `-{ ... }' splice in the source accumulator.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:node (node)
|
||||
assert (self~=M and self._acc)
|
||||
if node==nil then self:acc'<<error>>'
|
||||
elseif not self.custom_printer or not self.custom_printer (self, node) then
|
||||
if not node.tag then -- tagless (henceunindented) block.
|
||||
self:list (node, self.nl)
|
||||
else
|
||||
local f = M[node.tag]
|
||||
if type (f) == "function" then -- Delegate to tag method.
|
||||
f (self, node, unpack (node))
|
||||
elseif type (f) == "string" then -- tag string.
|
||||
self:acc (f)
|
||||
else -- No appropriate method, fall back to splice dumping.
|
||||
-- This cannot happen in a plain Lua AST.
|
||||
self:acc " -{ "
|
||||
self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1}), 80)
|
||||
self:acc " }"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:block(body)
|
||||
if not self.custom_printer or not self.custom_printer (self, body) then
|
||||
self:nlindent ()
|
||||
self:list (body, self.nl)
|
||||
self:nldedent ()
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Convert every node in the AST list `list' passed as 1st arg.
|
||||
-- `sep' is an optional separator to be accumulated between each list element,
|
||||
-- it can be a string or a synth method.
|
||||
-- `start' is an optional number (default == 1), indicating which is the
|
||||
-- first element of list to be converted, so that we can skip the begining
|
||||
-- of a list.
|
||||
--------------------------------------------------------------------------------
|
||||
function M:list (list, sep, start)
|
||||
for i = start or 1, # list do
|
||||
self:node (list[i])
|
||||
if list[i + 1] then
|
||||
if not sep then
|
||||
elseif type (sep) == "function" then sep (self)
|
||||
elseif type (sep) == "string" then self:acc (sep)
|
||||
else error "Invalid list separator" end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Tag methods.
|
||||
-- ------------
|
||||
--
|
||||
-- Specific AST node dumping methods, associated to their node kinds
|
||||
-- by their name, which is the corresponding AST tag.
|
||||
-- synth:node() is in charge of delegating a node's treatment to the
|
||||
-- appropriate tag method.
|
||||
--
|
||||
-- Such tag methods are called with the AST node as 1st arg.
|
||||
-- As a convenience, the n node's children are passed as args #2 ... n+1.
|
||||
--
|
||||
-- There are several things that could be refactored into common subroutines
|
||||
-- here: statement blocks dumping, function dumping...
|
||||
-- However, given their small size and linear execution
|
||||
-- (they basically perform series of :acc(), :node(), :list(),
|
||||
-- :nl(), :nlindent() and :nldedent() calls), it seems more readable
|
||||
-- to avoid multiplication of such tiny functions.
|
||||
--
|
||||
-- To make sense out of these, you need to know metalua's AST syntax, as
|
||||
-- found in the reference manual or in metalua/doc/ast.txt.
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function M:Do (node)
|
||||
self:acc "do"
|
||||
self:block (node)
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Set (node)
|
||||
match node with
|
||||
| `Set{ { `Index{ lhs, `String{ method } } },
|
||||
{ `Function{ { `Id "self", ... } == params, body } } }
|
||||
if is_idx_stack (lhs) and is_ident (method) ->
|
||||
-- ``function foo:bar(...) ... end'' --
|
||||
self:acc "function "
|
||||
self:node (lhs)
|
||||
self:acc ":"
|
||||
self:acc (method)
|
||||
self:acc " ("
|
||||
self:list (params, ", ", 2)
|
||||
self:acc ")"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
|
||||
| `Set{ { lhs }, { `Function{ params, body } } } if is_idx_stack (lhs) ->
|
||||
-- ``function foo(...) ... end'' --
|
||||
self:acc "function "
|
||||
self:node (lhs)
|
||||
self:acc " ("
|
||||
self:list (params, ", ")
|
||||
self:acc ")"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
|
||||
| `Set{ { `Id{ lhs1name } == lhs1, ... } == lhs, rhs }
|
||||
if not is_ident (lhs1name) ->
|
||||
-- ``foo, ... = ...'' when foo is *not* a valid identifier.
|
||||
-- In that case, the spliced 1st variable must get parentheses,
|
||||
-- to be distinguished from a statement splice.
|
||||
-- This cannot happen in a plain Lua AST.
|
||||
self:acc "("
|
||||
self:node (lhs1)
|
||||
self:acc ")"
|
||||
if lhs[2] then -- more than one lhs variable
|
||||
self:acc ", "
|
||||
self:list (lhs, ", ", 2)
|
||||
end
|
||||
self:acc " = "
|
||||
self:list (rhs, ", ")
|
||||
|
||||
| `Set{ lhs, rhs } ->
|
||||
-- ``... = ...'', no syntax sugar --
|
||||
self:list (lhs, ", ")
|
||||
self:acc " = "
|
||||
self:list (rhs, ", ")
|
||||
| `Set{ lhs, rhs, annot } ->
|
||||
-- ``... = ...'', no syntax sugar, annotation --
|
||||
local n = #lhs
|
||||
for i=1,n do
|
||||
local ell, a = lhs[i], annot[i]
|
||||
self:node (ell)
|
||||
if a then
|
||||
self:acc ' #'
|
||||
self:node(a)
|
||||
end
|
||||
if i~=n then self:acc ', ' end
|
||||
end
|
||||
self:acc " = "
|
||||
self:list (rhs, ", ")
|
||||
end
|
||||
end
|
||||
|
||||
function M:While (node, cond, body)
|
||||
self:acc "while "
|
||||
self:node (cond)
|
||||
self:acc " do"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Repeat (node, body, cond)
|
||||
self:acc "repeat"
|
||||
self:block (body)
|
||||
self:acc "until "
|
||||
self:node (cond)
|
||||
end
|
||||
|
||||
function M:If (node)
|
||||
for i = 1, #node-1, 2 do
|
||||
-- for each ``if/then'' and ``elseif/then'' pair --
|
||||
local cond, body = node[i], node[i+1]
|
||||
self:acc (i==1 and "if " or "elseif ")
|
||||
self:node (cond)
|
||||
self:acc " then"
|
||||
self:block (body)
|
||||
end
|
||||
-- odd number of children --> last one is an `else' clause --
|
||||
if #node%2 == 1 then
|
||||
self:acc "else"
|
||||
self:block (node[#node])
|
||||
end
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Fornum (node, var, first, last)
|
||||
local body = node[#node]
|
||||
self:acc "for "
|
||||
self:node (var)
|
||||
self:acc " = "
|
||||
self:node (first)
|
||||
self:acc ", "
|
||||
self:node (last)
|
||||
if #node==5 then -- 5 children --> child #4 is a step increment.
|
||||
self:acc ", "
|
||||
self:node (node[4])
|
||||
end
|
||||
self:acc " do"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Forin (node, vars, generators, body)
|
||||
self:acc "for "
|
||||
self:list (vars, ", ")
|
||||
self:acc " in "
|
||||
self:list (generators, ", ")
|
||||
self:acc " do"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Local (node, lhs, rhs, annots)
|
||||
if next (lhs) then
|
||||
self:acc "local "
|
||||
if annots then
|
||||
local n = #lhs
|
||||
for i=1, n do
|
||||
self:node (lhs)
|
||||
local a = annots[i]
|
||||
if a then
|
||||
self:acc ' #'
|
||||
self:node (a)
|
||||
end
|
||||
if i~=n then self:acc ', ' end
|
||||
end
|
||||
else
|
||||
self:list (lhs, ", ")
|
||||
end
|
||||
if rhs[1] then
|
||||
self:acc " = "
|
||||
self:list (rhs, ", ")
|
||||
end
|
||||
else -- Can't create a local statement with 0 variables in plain Lua
|
||||
self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1, fix_indent=2}))
|
||||
end
|
||||
end
|
||||
|
||||
function M:Localrec (node, lhs, rhs)
|
||||
match node with
|
||||
| `Localrec{ { `Id{name} }, { `Function{ params, body } } }
|
||||
if is_ident (name) ->
|
||||
-- ``local function name() ... end'' --
|
||||
self:acc "local function "
|
||||
self:acc (name)
|
||||
self:acc " ("
|
||||
self:list (params, ", ")
|
||||
self:acc ")"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
|
||||
| _ ->
|
||||
-- Other localrec are unprintable ==> splice them --
|
||||
-- This cannot happen in a plain Lua AST. --
|
||||
self:acc "-{ "
|
||||
self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1, fix_indent=2}))
|
||||
self:acc " }"
|
||||
end
|
||||
end
|
||||
|
||||
function M:Call (node, f)
|
||||
-- single string or table literal arg ==> no need for parentheses. --
|
||||
local parens
|
||||
match node with
|
||||
| `Call{ _, `String{_} }
|
||||
| `Call{ _, `Table{...}} -> parens = false
|
||||
| _ -> parens = true
|
||||
end
|
||||
self:node (f)
|
||||
self:acc (parens and " (" or " ")
|
||||
self:list (node, ", ", 2) -- skip `f'.
|
||||
self:acc (parens and ")")
|
||||
end
|
||||
|
||||
function M:Invoke (node, f, method)
|
||||
-- single string or table literal arg ==> no need for parentheses. --
|
||||
local parens
|
||||
match node with
|
||||
| `Invoke{ _, _, `String{_} }
|
||||
| `Invoke{ _, _, `Table{...}} -> parens = false
|
||||
| _ -> parens = true
|
||||
end
|
||||
self:node (f)
|
||||
self:acc ":"
|
||||
self:acc (method[1])
|
||||
self:acc (parens and " (" or " ")
|
||||
self:list (node, ", ", 3) -- Skip args #1 and #2, object and method name.
|
||||
self:acc (parens and ")")
|
||||
end
|
||||
|
||||
function M:Return (node)
|
||||
self:acc "return "
|
||||
self:list (node, ", ")
|
||||
end
|
||||
|
||||
M.Break = "break"
|
||||
M.Nil = "nil"
|
||||
M.False = "false"
|
||||
M.True = "true"
|
||||
M.Dots = "..."
|
||||
|
||||
function M:Number (node, n)
|
||||
self:acc (tostring (n))
|
||||
end
|
||||
|
||||
function M:String (node, str)
|
||||
-- format "%q" prints '\n' in an umpractical way IMO,
|
||||
-- so this is fixed with the :gsub( ) call.
|
||||
self:acc (string.format ("%q", str):gsub ("\\\n", "\\n"))
|
||||
end
|
||||
|
||||
function M:Function (node, params, body, annots)
|
||||
self:acc "function ("
|
||||
if annots then
|
||||
local n = #params
|
||||
for i=1,n do
|
||||
local p, a = params[i], annots[i]
|
||||
self:node(p)
|
||||
if annots then
|
||||
self:acc " #"
|
||||
self:node(a)
|
||||
end
|
||||
if i~=n then self:acc ', ' end
|
||||
end
|
||||
else
|
||||
self:list (params, ", ")
|
||||
end
|
||||
self:acc ")"
|
||||
self:block (body)
|
||||
self:acc "end"
|
||||
end
|
||||
|
||||
function M:Table (node)
|
||||
if not node[1] then self:acc "{ }" else
|
||||
self:acc "{"
|
||||
if #node > 1 then self:nlindent () else self:acc " " end
|
||||
for i, elem in ipairs (node) do
|
||||
match elem with
|
||||
| `Pair{ `String{ key }, value } if is_ident (key) ->
|
||||
-- ``key = value''. --
|
||||
self:acc (key)
|
||||
self:acc " = "
|
||||
self:node (value)
|
||||
|
||||
| `Pair{ key, value } ->
|
||||
-- ``[key] = value''. --
|
||||
self:acc "["
|
||||
self:node (key)
|
||||
self:acc "] = "
|
||||
self:node (value)
|
||||
|
||||
| _ ->
|
||||
-- ``value''. --
|
||||
self:node (elem)
|
||||
end
|
||||
if node [i+1] then
|
||||
self:acc ","
|
||||
self:nl ()
|
||||
end
|
||||
end
|
||||
if #node > 1 then self:nldedent () else self:acc " " end
|
||||
self:acc "}"
|
||||
end
|
||||
end
|
||||
|
||||
function M:Op (node, op, a, b)
|
||||
-- Transform ``not (a == b)'' into ``a ~= b''. --
|
||||
match node with
|
||||
| `Op{ "not", `Op{ "eq", _a, _b } }
|
||||
| `Op{ "not", `Paren{ `Op{ "eq", _a, _b } } } ->
|
||||
op, a, b = "ne", _a, _b
|
||||
| _ ->
|
||||
end
|
||||
|
||||
if b then -- binary operator.
|
||||
local left_paren, right_paren
|
||||
match a with
|
||||
| `Op{ op_a, ...} if op_prec[op] >= op_prec[op_a] -> left_paren = true
|
||||
| _ -> left_paren = false
|
||||
end
|
||||
|
||||
match b with -- FIXME: might not work with right assoc operators ^ and ..
|
||||
| `Op{ op_b, ...} if op_prec[op] >= op_prec[op_b] -> right_paren = true
|
||||
| _ -> right_paren = false
|
||||
end
|
||||
|
||||
self:acc (left_paren and "(")
|
||||
self:node (a)
|
||||
self:acc (left_paren and ")")
|
||||
|
||||
self:acc (op_symbol [op])
|
||||
|
||||
self:acc (right_paren and "(")
|
||||
self:node (b)
|
||||
self:acc (right_paren and ")")
|
||||
|
||||
else -- unary operator.
|
||||
local paren
|
||||
match a with
|
||||
| `Op{ op_a, ... } if op_prec[op] >= op_prec[op_a] -> paren = true
|
||||
| _ -> paren = false
|
||||
end
|
||||
self:acc (op_symbol[op])
|
||||
self:acc (paren and "(")
|
||||
self:node (a)
|
||||
self:acc (paren and ")")
|
||||
end
|
||||
end
|
||||
|
||||
function M:Paren (node, content)
|
||||
self:acc "("
|
||||
self:node (content)
|
||||
self:acc ")"
|
||||
end
|
||||
|
||||
function M:Index (node, table, key)
|
||||
local paren_table
|
||||
-- Check precedence, see if parens are needed around the table --
|
||||
match table with
|
||||
| `Op{ op, ... } if op_prec[op] < op_prec.index -> paren_table = true
|
||||
| _ -> paren_table = false
|
||||
end
|
||||
|
||||
self:acc (paren_table and "(")
|
||||
self:node (table)
|
||||
self:acc (paren_table and ")")
|
||||
|
||||
match key with
|
||||
| `String{ field } if is_ident (field) ->
|
||||
-- ``table.key''. --
|
||||
self:acc "."
|
||||
self:acc (field)
|
||||
| _ ->
|
||||
-- ``table [key]''. --
|
||||
self:acc "["
|
||||
self:node (key)
|
||||
self:acc "]"
|
||||
end
|
||||
end
|
||||
|
||||
function M:Id (node, name)
|
||||
if is_ident (name) then
|
||||
self:acc (name)
|
||||
else -- Unprintable identifier, fall back to splice representation.
|
||||
-- This cannot happen in a plain Lua AST.
|
||||
self:acc "-{`Id "
|
||||
self:String (node, name)
|
||||
self:acc "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
M.TDyn = '*'
|
||||
M.TDynbar = '**'
|
||||
M.TPass = 'pass'
|
||||
M.TField = 'field'
|
||||
M.TIdbar = M.TId
|
||||
M.TReturn = M.Return
|
||||
|
||||
|
||||
function M:TId (node, name) self:acc(name) end
|
||||
|
||||
|
||||
function M:TCatbar(node, te, tebar)
|
||||
self:acc'('
|
||||
self:node(te)
|
||||
self:acc'|'
|
||||
self:tebar(tebar)
|
||||
self:acc')'
|
||||
end
|
||||
|
||||
function M:TFunction(node, p, r)
|
||||
self:tebar(p)
|
||||
self:acc '->'
|
||||
self:tebar(r)
|
||||
end
|
||||
|
||||
function M:TTable (node, default, pairs)
|
||||
self:acc '['
|
||||
self:list (pairs, ', ')
|
||||
if default.tag~='TField' then
|
||||
self:acc '|'
|
||||
self:node (default)
|
||||
end
|
||||
self:acc ']'
|
||||
end
|
||||
|
||||
function M:TPair (node, k, v)
|
||||
self:node (k)
|
||||
self:acc '='
|
||||
self:node (v)
|
||||
end
|
||||
|
||||
function M:TIdbar (node, name)
|
||||
self :acc (name)
|
||||
end
|
||||
|
||||
function M:TCatbar (node, a, b)
|
||||
self:node(a)
|
||||
self:acc ' ++ '
|
||||
self:node(b)
|
||||
end
|
||||
|
||||
function M:tebar(node)
|
||||
if node.tag then self:node(node) else
|
||||
self:acc '('
|
||||
self:list(node, ', ')
|
||||
self:acc ')'
|
||||
end
|
||||
end
|
||||
|
||||
function M:TUnkbar(node, name)
|
||||
self:acc '~~'
|
||||
self:acc (name)
|
||||
end
|
||||
|
||||
function M:TUnk(node, name)
|
||||
self:acc '~'
|
||||
self:acc (name)
|
||||
end
|
||||
|
||||
for name, tag in pairs{ const='TConst', var='TVar', currently='TCurrently', just='TJust' } do
|
||||
M[tag] = function(self, node, te)
|
||||
self:acc (name..' ')
|
||||
self:node(te)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
29
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode.lua
Normal file
29
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local compile = require 'metalua.compiler.bytecode.compile'
|
||||
local ldump = require 'metalua.compiler.bytecode.ldump'
|
||||
|
||||
local M = { }
|
||||
|
||||
M.ast_to_proto = compile.ast_to_proto
|
||||
M.proto_to_bytecode = ldump.dump_string
|
||||
M.proto_to_file = ldump.dump_file
|
||||
|
||||
return M
|
||||
1263
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/compile.lua
Normal file
1263
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/compile.lua
Normal file
File diff suppressed because it is too large
Load Diff
1038
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lcode.lua
Normal file
1038
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/lcode.lua
Normal file
File diff suppressed because it is too large
Load Diff
448
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/ldump.lua
Normal file
448
Utils/luarocks/share/lua/5.1/metalua/compiler/bytecode/ldump.lua
Normal file
@@ -0,0 +1,448 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2005-2013 Kein-Hong Man, Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang
|
||||
-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
|
||||
ldump.lua
|
||||
Save bytecodes in Lua
|
||||
This file is part of Yueliang.
|
||||
|
||||
Copyright (c) 2005 Kein-Hong Man <khman@users.sf.net>
|
||||
The COPYRIGHT file describes the conditions
|
||||
under which this software may be distributed.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
[FF] Slightly modified, mainly to produce Lua 5.1 bytecode.
|
||||
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
-- Notes:
|
||||
-- * LUA_NUMBER (double), byte order (little endian) and some other
|
||||
-- header values hard-coded; see other notes below...
|
||||
-- * One significant difference is that instructions are still in table
|
||||
-- form (with OP/A/B/C/Bx fields) and luaP:Instruction() is needed to
|
||||
-- convert them into 4-char strings
|
||||
-- * Deleted:
|
||||
-- luaU:DumpVector: folded into DumpLines, DumpCode
|
||||
-- * Added:
|
||||
-- luaU:endianness() (from lundump.c)
|
||||
-- luaU:make_setS: create a chunk writer that writes to a string
|
||||
-- luaU:make_setF: create a chunk writer that writes to a file
|
||||
-- (lua.h contains a typedef for a Chunkwriter pointer, and
|
||||
-- a Lua-based implementation exists, writer() in lstrlib.c)
|
||||
-- luaU:from_double(x): encode double value for writing
|
||||
-- luaU:from_int(x): encode integer value for writing
|
||||
-- (error checking is limited for these conversion functions)
|
||||
-- (double conversion does not support denormals or NaNs)
|
||||
-- luaU:ttype(o) (from lobject.h)
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
local luaP = require 'metalua.compiler.bytecode.lopcodes'
|
||||
|
||||
local M = { }
|
||||
|
||||
local format = { }
|
||||
format.header = string.dump(function()end):sub(1, 12)
|
||||
format.little_endian, format.int_size,
|
||||
format.size_t_size, format.instr_size,
|
||||
format.number_size, format.integral = format.header:byte(7, 12)
|
||||
format.little_endian = format.little_endian~=0
|
||||
format.integral = format.integral ~=0
|
||||
|
||||
assert(format.integral or format.number_size==8, "Number format not supported by dumper")
|
||||
assert(format.little_endian, "Big endian architectures not supported by dumper")
|
||||
|
||||
--requires luaP
|
||||
local luaU = { }
|
||||
M.luaU = luaU
|
||||
|
||||
luaU.format = format
|
||||
|
||||
-- constants used by dumper
|
||||
luaU.LUA_TNIL = 0
|
||||
luaU.LUA_TBOOLEAN = 1
|
||||
luaU.LUA_TNUMBER = 3 -- (all in lua.h)
|
||||
luaU.LUA_TSTRING = 4
|
||||
luaU.LUA_TNONE = -1
|
||||
|
||||
-- definitions for headers of binary files
|
||||
--luaU.LUA_SIGNATURE = "\27Lua" -- binary files start with "<esc>Lua"
|
||||
--luaU.VERSION = 81 -- 0x50; last format change was in 5.0
|
||||
--luaU.FORMAT_VERSION = 0 -- 0 is official version. yeah I know I'm a liar.
|
||||
|
||||
-- a multiple of PI for testing native format
|
||||
-- multiplying by 1E7 gives non-trivial integer values
|
||||
--luaU.TEST_NUMBER = 3.14159265358979323846E7
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
-- Additional functions to handle chunk writing
|
||||
-- * to use make_setS and make_setF, see test_ldump.lua elsewhere
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- works like the lobject.h version except that TObject used in these
|
||||
-- scripts only has a 'value' field, no 'tt' field (native types used)
|
||||
------------------------------------------------------------------------
|
||||
function luaU:ttype(o)
|
||||
local tt = type(o.value)
|
||||
if tt == "number" then return self.LUA_TNUMBER
|
||||
elseif tt == "string" then return self.LUA_TSTRING
|
||||
elseif tt == "nil" then return self.LUA_TNIL
|
||||
elseif tt == "boolean" then return self.LUA_TBOOLEAN
|
||||
else
|
||||
return self.LUA_TNONE -- the rest should not appear
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- create a chunk writer that writes to a string
|
||||
-- * returns the writer function and a table containing the string
|
||||
-- * to get the final result, look in buff.data
|
||||
------------------------------------------------------------------------
|
||||
function luaU:make_setS()
|
||||
local buff = {}
|
||||
buff.data = ""
|
||||
local writer =
|
||||
function(s, buff) -- chunk writer
|
||||
if not s then return end
|
||||
buff.data = buff.data..s
|
||||
end
|
||||
return writer, buff
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- create a chunk writer that writes to a file
|
||||
-- * returns the writer function and a table containing the file handle
|
||||
-- * if a nil is passed, then writer should close the open file
|
||||
------------------------------------------------------------------------
|
||||
function luaU:make_setF(filename)
|
||||
local buff = {}
|
||||
buff.h = io.open(filename, "wb")
|
||||
if not buff.h then return nil end
|
||||
local writer =
|
||||
function(s, buff) -- chunk writer
|
||||
if not buff.h then return end
|
||||
if not s then buff.h:close(); return end
|
||||
buff.h:write(s)
|
||||
end
|
||||
return writer, buff
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- converts a IEEE754 double number to an 8-byte little-endian string
|
||||
-- * luaU:from_double() and luaU:from_int() are from ChunkBake project
|
||||
-- * supports +/- Infinity, but not denormals or NaNs
|
||||
-----------------------------------------------------------------------
|
||||
function luaU:from_double(x)
|
||||
local function grab_byte(v)
|
||||
return math.floor(v / 256),
|
||||
string.char(math.mod(math.floor(v), 256))
|
||||
end
|
||||
local sign = 0
|
||||
if x < 0 then sign = 1; x = -x end
|
||||
local mantissa, exponent = math.frexp(x)
|
||||
if x == 0 then -- zero
|
||||
mantissa, exponent = 0, 0
|
||||
elseif x == 1/0 then
|
||||
mantissa, exponent = 0, 2047
|
||||
else
|
||||
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
|
||||
exponent = exponent + 1022
|
||||
end
|
||||
local v, byte = "" -- convert to bytes
|
||||
x = mantissa
|
||||
for i = 1,6 do
|
||||
x, byte = grab_byte(x); v = v..byte -- 47:0
|
||||
end
|
||||
x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48
|
||||
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56
|
||||
return v
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- converts a number to a little-endian 32-bit integer string
|
||||
-- * input value assumed to not overflow, can be signed/unsigned
|
||||
-----------------------------------------------------------------------
|
||||
function luaU:from_int(x, size)
|
||||
local v = ""
|
||||
x = math.floor(x)
|
||||
if x >= 0 then
|
||||
for i = 1, size do
|
||||
v = v..string.char(math.mod(x, 256)); x = math.floor(x / 256)
|
||||
end
|
||||
else -- x < 0
|
||||
x = -x
|
||||
local carry = 1
|
||||
for i = 1, size do
|
||||
local c = 255 - math.mod(x, 256) + carry
|
||||
if c == 256 then c = 0; carry = 1 else carry = 0 end
|
||||
v = v..string.char(c); x = math.floor(x / 256)
|
||||
end
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
-- Functions to make a binary chunk
|
||||
-- * many functions have the size parameter removed, since output is
|
||||
-- in the form of a string and some sizes are implicit or hard-coded
|
||||
-- * luaU:DumpVector has been deleted (used in DumpCode & DumpLines)
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump a block of literal bytes
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpLiteral(s, D) self:DumpBlock(s, D) end
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
-- struct DumpState:
|
||||
-- L -- lua_State (not used in this script)
|
||||
-- write -- lua_Chunkwriter (chunk writer function)
|
||||
-- data -- void* (chunk writer context or data already written)
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps a block of bytes
|
||||
-- * lua_unlock(D.L), lua_lock(D.L) deleted
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpBlock(b, D) D.write(b, D.data) end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps a single byte
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpByte(y, D)
|
||||
self:DumpBlock(string.char(y), D)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps a signed integer of size `format.int_size` (for int)
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpInt(x, D)
|
||||
self:DumpBlock(self:from_int(x, format.int_size), D)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps an unsigned integer of size `format.size_t_size` (for size_t)
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpSize(x, D)
|
||||
self:DumpBlock(self:from_int(x, format.size_t_size), D)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps a LUA_NUMBER; can be an int or double depending on the VM.
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpNumber(x, D)
|
||||
if format.integral then
|
||||
self:DumpBlock(self:from_int(x, format.number_size), D)
|
||||
else
|
||||
self:DumpBlock(self:from_double(x), D)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps a Lua string
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpString(s, D)
|
||||
if s == nil then
|
||||
self:DumpSize(0, D)
|
||||
else
|
||||
s = s.."\0" -- include trailing '\0'
|
||||
self:DumpSize(string.len(s), D)
|
||||
self:DumpBlock(s, D)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps instruction block from function prototype
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpCode(f, D)
|
||||
local n = f.sizecode
|
||||
self:DumpInt(n, D)
|
||||
--was DumpVector
|
||||
for i = 0, n - 1 do
|
||||
self:DumpBlock(luaP:Instruction(f.code[i]), D)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps local variable names from function prototype
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpLocals(f, D)
|
||||
local n = f.sizelocvars
|
||||
self:DumpInt(n, D)
|
||||
for i = 0, n - 1 do
|
||||
-- Dirty temporary fix:
|
||||
-- `Stat{ } keeps properly count of the number of local vars,
|
||||
-- but fails to keep score of their debug info (names).
|
||||
-- It therefore might happen that #f.localvars < f.sizelocvars, or
|
||||
-- that a variable's startpc and endpc fields are left unset.
|
||||
-- FIXME: This might not be needed anymore, check the bug report
|
||||
-- by J. Belmonte.
|
||||
local var = f.locvars[i]
|
||||
if not var then break end
|
||||
-- printf("[DUMPLOCALS] dumping local var #%i = %s", i, table.tostring(var))
|
||||
self:DumpString(var.varname, D)
|
||||
self:DumpInt(var.startpc or 0, D)
|
||||
self:DumpInt(var.endpc or 0, D)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dumps line information from function prototype
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpLines(f, D)
|
||||
local n = f.sizelineinfo
|
||||
self:DumpInt(n, D)
|
||||
--was DumpVector
|
||||
for i = 0, n - 1 do
|
||||
self:DumpInt(f.lineinfo[i], D) -- was DumpBlock
|
||||
--print(i, f.lineinfo[i])
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump upvalue names from function prototype
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpUpvalues(f, D)
|
||||
local n = f.sizeupvalues
|
||||
self:DumpInt(n, D)
|
||||
for i = 0, n - 1 do
|
||||
self:DumpString(f.upvalues[i], D)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump constant pool from function prototype
|
||||
-- * nvalue(o) and tsvalue(o) macros removed
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpConstants(f, D)
|
||||
local n = f.sizek
|
||||
self:DumpInt(n, D)
|
||||
for i = 0, n - 1 do
|
||||
local o = f.k[i] -- TObject
|
||||
local tt = self:ttype(o)
|
||||
assert (tt >= 0)
|
||||
self:DumpByte(tt, D)
|
||||
if tt == self.LUA_TNUMBER then
|
||||
self:DumpNumber(o.value, D)
|
||||
elseif tt == self.LUA_TSTRING then
|
||||
self:DumpString(o.value, D)
|
||||
elseif tt == self.LUA_TBOOLEAN then
|
||||
self:DumpByte (o.value and 1 or 0, D)
|
||||
elseif tt == self.LUA_TNIL then
|
||||
else
|
||||
assert(false) -- cannot happen
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function luaU:DumpProtos (f, D)
|
||||
local n = f.sizep
|
||||
assert (n)
|
||||
self:DumpInt(n, D)
|
||||
for i = 0, n - 1 do
|
||||
self:DumpFunction(f.p[i], f.source, D)
|
||||
end
|
||||
end
|
||||
|
||||
function luaU:DumpDebug(f, D)
|
||||
self:DumpLines(f, D)
|
||||
self:DumpLocals(f, D)
|
||||
self:DumpUpvalues(f, D)
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump child function prototypes from function prototype
|
||||
--FF completely reworked for 5.1 format
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpFunction(f, p, D)
|
||||
-- print "Dumping function:"
|
||||
-- table.print(f, 60)
|
||||
|
||||
local source = f.source
|
||||
if source == p then source = nil end
|
||||
self:DumpString(source, D)
|
||||
self:DumpInt(f.lineDefined, D)
|
||||
self:DumpInt(f.lastLineDefined or 42, D)
|
||||
self:DumpByte(f.nups, D)
|
||||
self:DumpByte(f.numparams, D)
|
||||
self:DumpByte(f.is_vararg, D)
|
||||
self:DumpByte(f.maxstacksize, D)
|
||||
self:DumpCode(f, D)
|
||||
self:DumpConstants(f, D)
|
||||
self:DumpProtos( f, D)
|
||||
self:DumpDebug(f, D)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump Lua header section (some sizes hard-coded)
|
||||
--FF: updated for version 5.1
|
||||
------------------------------------------------------------------------
|
||||
function luaU:DumpHeader(D)
|
||||
self:DumpLiteral(format.header, D)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- dump function as precompiled chunk
|
||||
-- * w, data are created from make_setS, make_setF
|
||||
--FF: suppressed extraneous [L] param
|
||||
------------------------------------------------------------------------
|
||||
function luaU:dump (Main, w, data)
|
||||
local D = {} -- DumpState
|
||||
D.write = w
|
||||
D.data = data
|
||||
self:DumpHeader(D)
|
||||
self:DumpFunction(Main, nil, D)
|
||||
-- added: for a chunk writer writing to a file, this final call with
|
||||
-- nil data is to indicate to the writer to close the file
|
||||
D.write(nil, D.data)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- find byte order (from lundump.c)
|
||||
-- * hard-coded to little-endian
|
||||
------------------------------------------------------------------------
|
||||
function luaU:endianness()
|
||||
return 1
|
||||
end
|
||||
|
||||
-- FIXME: ugly concat-base generation in [make_setS], bufferize properly!
|
||||
function M.dump_string (proto)
|
||||
local writer, buff = luaU:make_setS()
|
||||
luaU:dump (proto, writer, buff)
|
||||
return buff.data
|
||||
end
|
||||
|
||||
-- FIXME: [make_setS] sucks, perform synchronous file writing
|
||||
-- Now unused
|
||||
function M.dump_file (proto, filename)
|
||||
local writer, buff = luaU:make_setS()
|
||||
luaU:dump (proto, writer, buff)
|
||||
local file = io.open (filename, "wb")
|
||||
file:write (buff.data)
|
||||
io.close(file)
|
||||
--if UNIX_SHARPBANG then os.execute ("chmod a+x "..filename) end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,442 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2005-2013 Kein-Hong Man, Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kein-Hong Man - Initial implementation for Lua 5.0, part of Yueliang
|
||||
-- Fabien Fleutot - Port to Lua 5.1, integration with Metalua
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
|
||||
$Id$
|
||||
|
||||
lopcodes.lua
|
||||
Lua 5 virtual machine opcodes in Lua
|
||||
This file is part of Yueliang.
|
||||
|
||||
Copyright (c) 2005 Kein-Hong Man <khman@users.sf.net>
|
||||
The COPYRIGHT file describes the conditions
|
||||
under which this software may be distributed.
|
||||
|
||||
See the ChangeLog for more information.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
[FF] Slightly modified, mainly to produce Lua 5.1 bytecode.
|
||||
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
-- Notes:
|
||||
-- * an Instruction is a table with OP, A, B, C, Bx elements; this
|
||||
-- should allow instruction handling to work with doubles and ints
|
||||
-- * Added:
|
||||
-- luaP:Instruction(i): convert field elements to a 4-char string
|
||||
-- luaP:DecodeInst(x): convert 4-char string into field elements
|
||||
-- * WARNING luaP:Instruction outputs instructions encoded in little-
|
||||
-- endian form and field size and positions are hard-coded
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
local function debugf() end
|
||||
|
||||
local luaP = { }
|
||||
|
||||
--[[
|
||||
===========================================================================
|
||||
We assume that instructions are unsigned numbers.
|
||||
All instructions have an opcode in the first 6 bits.
|
||||
Instructions can have the following fields:
|
||||
'A' : 8 bits
|
||||
'B' : 9 bits
|
||||
'C' : 9 bits
|
||||
'Bx' : 18 bits ('B' and 'C' together)
|
||||
'sBx' : signed Bx
|
||||
|
||||
A signed argument is represented in excess K; that is, the number
|
||||
value is the unsigned value minus K. K is exactly the maximum value
|
||||
for that argument (so that -max is represented by 0, and +max is
|
||||
represented by 2*max), which is half the maximum for the corresponding
|
||||
unsigned argument.
|
||||
===========================================================================
|
||||
--]]
|
||||
|
||||
luaP.OpMode = {"iABC", "iABx", "iAsBx"} -- basic instruction format
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- size and position of opcode arguments.
|
||||
-- * WARNING size and position is hard-coded elsewhere in this script
|
||||
------------------------------------------------------------------------
|
||||
luaP.SIZE_C = 9
|
||||
luaP.SIZE_B = 9
|
||||
luaP.SIZE_Bx = luaP.SIZE_C + luaP.SIZE_B
|
||||
luaP.SIZE_A = 8
|
||||
|
||||
luaP.SIZE_OP = 6
|
||||
|
||||
luaP.POS_C = luaP.SIZE_OP
|
||||
luaP.POS_B = luaP.POS_C + luaP.SIZE_C
|
||||
luaP.POS_Bx = luaP.POS_C
|
||||
luaP.POS_A = luaP.POS_B + luaP.SIZE_B
|
||||
|
||||
--FF from 5.1
|
||||
luaP.BITRK = 2^(luaP.SIZE_B - 1)
|
||||
function luaP:ISK(x) return x >= self.BITRK end
|
||||
luaP.MAXINDEXRK = luaP.BITRK - 1
|
||||
function luaP:RKASK(x)
|
||||
if x < self.BITRK then return x+self.BITRK else return x end
|
||||
end
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- limits for opcode arguments.
|
||||
-- we use (signed) int to manipulate most arguments,
|
||||
-- so they must fit in BITS_INT-1 bits (-1 for sign)
|
||||
------------------------------------------------------------------------
|
||||
-- removed "#if SIZE_Bx < BITS_INT-1" test, assume this script is
|
||||
-- running on a Lua VM with double or int as LUA_NUMBER
|
||||
|
||||
luaP.MAXARG_Bx = math.ldexp(1, luaP.SIZE_Bx) - 1
|
||||
luaP.MAXARG_sBx = math.floor(luaP.MAXARG_Bx / 2) -- 'sBx' is signed
|
||||
|
||||
luaP.MAXARG_A = math.ldexp(1, luaP.SIZE_A) - 1
|
||||
luaP.MAXARG_B = math.ldexp(1, luaP.SIZE_B) - 1
|
||||
luaP.MAXARG_C = math.ldexp(1, luaP.SIZE_C) - 1
|
||||
|
||||
-- creates a mask with 'n' 1 bits at position 'p'
|
||||
-- MASK1(n,p) deleted
|
||||
-- creates a mask with 'n' 0 bits at position 'p'
|
||||
-- MASK0(n,p) deleted
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
Visual representation for reference:
|
||||
|
||||
31 | | | 0 bit position
|
||||
+-----+-----+-----+----------+
|
||||
| B | C | A | Opcode | iABC format
|
||||
+-----+-----+-----+----------+
|
||||
- 9 - 9 - 8 - 6 - field sizes
|
||||
+-----+-----+-----+----------+
|
||||
| [s]Bx | A | Opcode | iABx | iAsBx format
|
||||
+-----+-----+-----+----------+
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- the following macros help to manipulate instructions
|
||||
-- * changed to a table object representation, very clean compared to
|
||||
-- the [nightmare] alternatives of using a number or a string
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- these accept or return opcodes in the form of string names
|
||||
function luaP:GET_OPCODE(i) return self.ROpCode[i.OP] end
|
||||
function luaP:SET_OPCODE(i, o) i.OP = self.OpCode[o] end
|
||||
|
||||
function luaP:GETARG_A(i) return i.A end
|
||||
function luaP:SETARG_A(i, u) i.A = u end
|
||||
|
||||
function luaP:GETARG_B(i) return i.B end
|
||||
function luaP:SETARG_B(i, b) i.B = b end
|
||||
|
||||
function luaP:GETARG_C(i) return i.C end
|
||||
function luaP:SETARG_C(i, b) i.C = b end
|
||||
|
||||
function luaP:GETARG_Bx(i) return i.Bx end
|
||||
function luaP:SETARG_Bx(i, b) i.Bx = b end
|
||||
|
||||
function luaP:GETARG_sBx(i) return i.Bx - self.MAXARG_sBx end
|
||||
function luaP:SETARG_sBx(i, b) i.Bx = b + self.MAXARG_sBx end
|
||||
|
||||
function luaP:CREATE_ABC(o,a,b,c)
|
||||
return {OP = self.OpCode[o], A = a, B = b, C = c}
|
||||
end
|
||||
|
||||
function luaP:CREATE_ABx(o,a,bc)
|
||||
return {OP = self.OpCode[o], A = a, Bx = bc}
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Bit shuffling stuffs
|
||||
------------------------------------------------------------------------
|
||||
|
||||
if false and pcall (require, 'bit') then
|
||||
------------------------------------------------------------------------
|
||||
-- Return a 4-char string little-endian encoded form of an instruction
|
||||
------------------------------------------------------------------------
|
||||
function luaP:Instruction(i)
|
||||
--FIXME
|
||||
end
|
||||
else
|
||||
------------------------------------------------------------------------
|
||||
-- Version without bit manipulation library.
|
||||
------------------------------------------------------------------------
|
||||
local p2 = {1,2,4,8,16,32,64,128,256, 512, 1024, 2048, 4096}
|
||||
-- keeps [n] bits from [x]
|
||||
local function keep (x, n) return x % p2[n+1] end
|
||||
-- shifts bits of [x] [n] places to the right
|
||||
local function srb (x,n) return math.floor (x / p2[n+1]) end
|
||||
-- shifts bits of [x] [n] places to the left
|
||||
local function slb (x,n) return x * p2[n+1] end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Return a 4-char string little-endian encoded form of an instruction
|
||||
------------------------------------------------------------------------
|
||||
function luaP:Instruction(i)
|
||||
-- printf("Instr->string: %s %s", self.opnames[i.OP], table.tostring(i))
|
||||
local c0, c1, c2, c3
|
||||
-- change to OP/A/B/C format if needed
|
||||
if i.Bx then i.C = keep (i.Bx, 9); i.B = srb (i.Bx, 9) end
|
||||
-- c0 = 6B from opcode + 2LSB from A (flushed to MSB)
|
||||
c0 = i.OP + slb (keep (i.A, 2), 6)
|
||||
-- c1 = 6MSB from A + 2LSB from C (flushed to MSB)
|
||||
c1 = srb (i.A, 2) + slb (keep (i.C, 2), 6)
|
||||
-- c2 = 7MSB from C + 1LSB from B (flushed to MSB)
|
||||
c2 = srb (i.C, 2) + slb (keep (i.B, 1), 7)
|
||||
-- c3 = 8MSB from B
|
||||
c3 = srb (i.B, 1)
|
||||
--printf ("Instruction: %s %s", self.opnames[i.OP], tostringv (i))
|
||||
--printf ("Bin encoding: %x %x %x %x", c0, c1, c2, c3)
|
||||
return string.char(c0, c1, c2, c3)
|
||||
end
|
||||
end
|
||||
------------------------------------------------------------------------
|
||||
-- decodes a 4-char little-endian string into an instruction struct
|
||||
------------------------------------------------------------------------
|
||||
function luaP:DecodeInst(x)
|
||||
error "Not implemented"
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- invalid register that fits in 8 bits
|
||||
------------------------------------------------------------------------
|
||||
luaP.NO_REG = luaP.MAXARG_A
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- R(x) - register
|
||||
-- Kst(x) - constant (in constant table)
|
||||
-- RK(x) == if x < MAXSTACK then R(x) else Kst(x-MAXSTACK)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- grep "ORDER OP" if you change these enums
|
||||
------------------------------------------------------------------------
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
Lua virtual machine opcodes (enum OpCode):
|
||||
------------------------------------------------------------------------
|
||||
name args description
|
||||
------------------------------------------------------------------------
|
||||
OP_MOVE A B R(A) := R(B)
|
||||
OP_LOADK A Bx R(A) := Kst(Bx)
|
||||
OP_LOADBOOL A B C R(A) := (Bool)B; if (C) PC++
|
||||
OP_LOADNIL A B R(A) := ... := R(B) := nil
|
||||
OP_GETUPVAL A B R(A) := UpValue[B]
|
||||
OP_GETGLOBAL A Bx R(A) := Gbl[Kst(Bx)]
|
||||
OP_GETTABLE A B C R(A) := R(B)[RK(C)]
|
||||
OP_SETGLOBAL A Bx Gbl[Kst(Bx)] := R(A)
|
||||
OP_SETUPVAL A B UpValue[B] := R(A)
|
||||
OP_SETTABLE A B C R(A)[RK(B)] := RK(C)
|
||||
OP_NEWTABLE A B C R(A) := {} (size = B,C)
|
||||
OP_SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)]
|
||||
OP_ADD A B C R(A) := RK(B) + RK(C)
|
||||
OP_SUB A B C R(A) := RK(B) - RK(C)
|
||||
OP_MUL A B C R(A) := RK(B) * RK(C)
|
||||
OP_DIV A B C R(A) := RK(B) / RK(C)
|
||||
OP_POW A B C R(A) := RK(B) ^ RK(C)
|
||||
OP_UNM A B R(A) := -R(B)
|
||||
OP_NOT A B R(A) := not R(B)
|
||||
OP_CONCAT A B C R(A) := R(B).. ... ..R(C)
|
||||
OP_JMP sBx PC += sBx
|
||||
OP_EQ A B C if ((RK(B) == RK(C)) ~= A) then pc++
|
||||
OP_LT A B C if ((RK(B) < RK(C)) ~= A) then pc++
|
||||
OP_LE A B C if ((RK(B) <= RK(C)) ~= A) then pc++
|
||||
OP_TEST A B C if (R(B) <=> C) then R(A) := R(B) else pc++
|
||||
OP_CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
|
||||
OP_TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1))
|
||||
OP_RETURN A B return R(A), ... ,R(A+B-2) (see note)
|
||||
OP_FORLOOP A sBx R(A)+=R(A+2); if R(A) <?= R(A+1) then PC+= sBx
|
||||
OP_TFORLOOP A C R(A+2), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
|
||||
if R(A+2) ~= nil then pc++
|
||||
OP_TFORPREP A sBx if type(R(A)) == table then R(A+1):=R(A), R(A):=next;
|
||||
PC += sBx
|
||||
OP_SETLIST A Bx R(A)[Bx-Bx%FPF+i] := R(A+i), 1 <= i <= Bx%FPF+1
|
||||
OP_SETLISTO A Bx (see note)
|
||||
OP_CLOSE A close all variables in the stack up to (>=) R(A)
|
||||
OP_CLOSURE A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
luaP.opnames = {} -- opcode names
|
||||
luaP.OpCode = {} -- lookup name -> number
|
||||
luaP.ROpCode = {} -- lookup number -> name
|
||||
|
||||
local i = 0
|
||||
for v in string.gfind([[
|
||||
MOVE -- 0
|
||||
LOADK
|
||||
LOADBOOL
|
||||
LOADNIL
|
||||
GETUPVAL
|
||||
GETGLOBAL -- 5
|
||||
GETTABLE
|
||||
SETGLOBAL
|
||||
SETUPVAL
|
||||
SETTABLE
|
||||
NEWTABLE -- 10
|
||||
SELF
|
||||
ADD
|
||||
SUB
|
||||
MUL
|
||||
DIV -- 15
|
||||
MOD
|
||||
POW
|
||||
UNM
|
||||
NOT
|
||||
LEN -- 20
|
||||
CONCAT
|
||||
JMP
|
||||
EQ
|
||||
LT
|
||||
LE -- 25
|
||||
TEST
|
||||
TESTSET
|
||||
CALL
|
||||
TAILCALL
|
||||
RETURN -- 30
|
||||
FORLOOP
|
||||
FORPREP
|
||||
TFORLOOP
|
||||
SETLIST
|
||||
CLOSE -- 35
|
||||
CLOSURE
|
||||
VARARG
|
||||
]], "[%a]+") do
|
||||
local n = "OP_"..v
|
||||
luaP.opnames[i] = v
|
||||
luaP.OpCode[n] = i
|
||||
luaP.ROpCode[i] = n
|
||||
i = i + 1
|
||||
end
|
||||
luaP.NUM_OPCODES = i
|
||||
|
||||
--[[
|
||||
===========================================================================
|
||||
Notes:
|
||||
(1) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1,
|
||||
and can be 0: OP_CALL then sets 'top' to last_result+1, so
|
||||
next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use 'top'.
|
||||
|
||||
(2) In OP_RETURN, if (B == 0) then return up to 'top'
|
||||
|
||||
(3) For comparisons, B specifies what conditions the test should accept.
|
||||
|
||||
(4) All 'skips' (pc++) assume that next instruction is a jump
|
||||
|
||||
(5) OP_SETLISTO is used when the last item in a table constructor is a
|
||||
function, so the number of elements set is up to top of stack
|
||||
===========================================================================
|
||||
--]]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- masks for instruction properties
|
||||
------------------------------------------------------------------------
|
||||
-- was enum OpModeMask:
|
||||
luaP.OpModeBreg = 2 -- B is a register
|
||||
luaP.OpModeBrk = 3 -- B is a register/constant
|
||||
luaP.OpModeCrk = 4 -- C is a register/constant
|
||||
luaP.OpModesetA = 5 -- instruction set register A
|
||||
luaP.OpModeK = 6 -- Bx is a constant
|
||||
luaP.OpModeT = 1 -- operator is a test
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- get opcode mode, e.g. "iABC"
|
||||
------------------------------------------------------------------------
|
||||
function luaP:getOpMode(m)
|
||||
--printv(m)
|
||||
--printv(self.OpCode[m])
|
||||
--printv(self.opmodes [self.OpCode[m]+1])
|
||||
return self.OpMode[tonumber(string.sub(self.opmodes[self.OpCode[m] + 1], 7, 7))]
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- test an instruction property flag
|
||||
-- * b is a string, e.g. "OpModeBreg"
|
||||
------------------------------------------------------------------------
|
||||
function luaP:testOpMode(m, b)
|
||||
return (string.sub(self.opmodes[self.OpCode[m] + 1], self[b], self[b]) == "1")
|
||||
end
|
||||
|
||||
-- number of list items to accumulate before a SETLIST instruction
|
||||
-- (must be a power of 2)
|
||||
-- * used in lparser, lvm, ldebug, ltests
|
||||
luaP.LFIELDS_PER_FLUSH = 50 --FF updated to match 5.1
|
||||
|
||||
-- luaP_opnames[] is set above, as the luaP.opnames table
|
||||
-- opmode(t,b,bk,ck,sa,k,m) deleted
|
||||
|
||||
--[[--------------------------------------------------------------------
|
||||
Legend for luaP:opmodes:
|
||||
1 T -> T (is a test?)
|
||||
2 B -> B is a register
|
||||
3 b -> B is an RK register/constant combination
|
||||
4 C -> C is an RK register/constant combination
|
||||
5 A -> register A is set by the opcode
|
||||
6 K -> Bx is a constant
|
||||
7 m -> 1 if iABC layout,
|
||||
2 if iABx layout,
|
||||
3 if iAsBx layout
|
||||
----------------------------------------------------------------------]]
|
||||
|
||||
luaP.opmodes = {
|
||||
-- TBbCAKm opcode
|
||||
"0100101", -- OP_MOVE 0
|
||||
"0000112", -- OP_LOADK
|
||||
"0000101", -- OP_LOADBOOL
|
||||
"0100101", -- OP_LOADNIL
|
||||
"0000101", -- OP_GETUPVAL
|
||||
"0000112", -- OP_GETGLOBAL 5
|
||||
"0101101", -- OP_GETTABLE
|
||||
"0000012", -- OP_SETGLOBAL
|
||||
"0000001", -- OP_SETUPVAL
|
||||
"0011001", -- OP_SETTABLE
|
||||
"0000101", -- OP_NEWTABLE 10
|
||||
"0101101", -- OP_SELF
|
||||
"0011101", -- OP_ADD
|
||||
"0011101", -- OP_SUB
|
||||
"0011101", -- OP_MUL
|
||||
"0011101", -- OP_DIV 15
|
||||
"0011101", -- OP_MOD
|
||||
"0011101", -- OP_POW
|
||||
"0100101", -- OP_UNM
|
||||
"0100101", -- OP_NOT
|
||||
"0100101", -- OP_LEN 20
|
||||
"0101101", -- OP_CONCAT
|
||||
"0000003", -- OP_JMP
|
||||
"1011001", -- OP_EQ
|
||||
"1011001", -- OP_LT
|
||||
"1011001", -- OP_LE 25
|
||||
"1000101", -- OP_TEST
|
||||
"1100101", -- OP_TESTSET
|
||||
"0000001", -- OP_CALL
|
||||
"0000001", -- OP_TAILCALL
|
||||
"0000001", -- OP_RETURN 30
|
||||
"0000003", -- OP_FORLOOP
|
||||
"0000103", -- OP_FORPREP
|
||||
"1000101", -- OP_TFORLOOP
|
||||
"0000001", -- OP_SETLIST
|
||||
"0000001", -- OP_CLOSE 35
|
||||
"0000102", -- OP_CLOSURE
|
||||
"0000101" -- OP_VARARG
|
||||
}
|
||||
|
||||
return luaP
|
||||
86
Utils/luarocks/share/lua/5.1/metalua/compiler/globals.lua
Normal file
86
Utils/luarocks/share/lua/5.1/metalua/compiler/globals.lua
Normal file
@@ -0,0 +1,86 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--*-lua-*-----------------------------------------------------------------------
|
||||
-- Override Lua's default compilation functions, so that they support Metalua
|
||||
-- rather than only plain Lua
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local mlc = require 'metalua.compiler'
|
||||
|
||||
local M = { }
|
||||
|
||||
-- Original versions
|
||||
local original_lua_versions = {
|
||||
load = load,
|
||||
loadfile = loadfile,
|
||||
loadstring = loadstring,
|
||||
dofile = dofile }
|
||||
|
||||
local lua_loadstring = loadstring
|
||||
local lua_loadfile = loadfile
|
||||
|
||||
function M.loadstring(str, name)
|
||||
if type(str) ~= 'string' then error 'string expected' end
|
||||
if str:match '^\027LuaQ' then return lua_loadstring(str) end
|
||||
local n = str:match '^#![^\n]*\n()'
|
||||
if n then str=str:sub(n, -1) end
|
||||
-- FIXME: handle erroneous returns (return nil + error msg)
|
||||
return mlc.new():src_to_function(str, name)
|
||||
end
|
||||
|
||||
function M.loadfile(filename)
|
||||
local f, err_msg = io.open(filename, 'rb')
|
||||
if not f then return nil, err_msg end
|
||||
local success, src = pcall( f.read, f, '*a')
|
||||
pcall(f.close, f)
|
||||
if success then return M.loadstring (src, '@'..filename)
|
||||
else return nil, src end
|
||||
end
|
||||
|
||||
function M.load(f, name)
|
||||
local acc = { }
|
||||
while true do
|
||||
local x = f()
|
||||
if not x then break end
|
||||
assert(type(x)=='string', "function passed to load() must return strings")
|
||||
table.insert(acc, x)
|
||||
end
|
||||
return M.loadstring(table.concat(acc))
|
||||
end
|
||||
|
||||
function M.dostring(src)
|
||||
local f, msg = M.loadstring(src)
|
||||
if not f then error(msg) end
|
||||
return f()
|
||||
end
|
||||
|
||||
function M.dofile(name)
|
||||
local f, msg = M.loadfile(name)
|
||||
if not f then error(msg) end
|
||||
return f()
|
||||
end
|
||||
|
||||
-- Export replacement functions as globals
|
||||
for name, f in pairs(M) do _G[name] = f end
|
||||
|
||||
-- To be done *after* exportation
|
||||
M.lua = original_lua_versions
|
||||
|
||||
return M
|
||||
42
Utils/luarocks/share/lua/5.1/metalua/compiler/parser.lua
Normal file
42
Utils/luarocks/share/lua/5.1/metalua/compiler/parser.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Export all public APIs from sub-modules, squashed into a flat spacename
|
||||
|
||||
local MT = { __type='metalua.compiler.parser' }
|
||||
|
||||
local MODULE_REL_NAMES = { "annot.grammar", "expr", "meta", "misc",
|
||||
"stat", "table", "ext" }
|
||||
|
||||
local function new()
|
||||
local M = {
|
||||
lexer = require "metalua.compiler.parser.lexer" ();
|
||||
extensions = { } }
|
||||
for _, rel_name in ipairs(MODULE_REL_NAMES) do
|
||||
local abs_name = "metalua.compiler.parser."..rel_name
|
||||
local extender = require (abs_name)
|
||||
if not M.extensions[abs_name] then
|
||||
if type (extender) == 'function' then extender(M) end
|
||||
M.extensions[abs_name] = extender
|
||||
end
|
||||
end
|
||||
return setmetatable(M, MT)
|
||||
end
|
||||
|
||||
return { new = new }
|
||||
@@ -0,0 +1,48 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local checks = require 'checks'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local M = { }
|
||||
|
||||
function M.opt(mlc, primary, a_type)
|
||||
checks('table', 'table|function', 'string')
|
||||
return gg.sequence{
|
||||
primary,
|
||||
gg.onkeyword{ "#", function() return assert(mlc.annot[a_type]) end },
|
||||
builder = function(x)
|
||||
local t, annot = unpack(x)
|
||||
return annot and { tag='Annot', t, annot } or t
|
||||
end }
|
||||
end
|
||||
|
||||
-- split a list of "foo" and "`Annot{foo, annot}" into a list of "foo"
|
||||
-- and a list of "annot".
|
||||
-- No annot list is returned if none of the elements were annotated.
|
||||
function M.split(lst)
|
||||
local x, a, some = { }, { }, false
|
||||
for i, p in ipairs(lst) do
|
||||
if p.tag=='Annot' then
|
||||
some, x[i], a[i] = true, unpack(p)
|
||||
else x[i] = p end
|
||||
end
|
||||
if some then return x, a else return lst end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,112 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
return function(M)
|
||||
local _M = gg.future(M)
|
||||
M.lexer :add '->'
|
||||
local A = { }
|
||||
local _A = gg.future(A)
|
||||
M.annot = A
|
||||
|
||||
-- Type identifier: Lua keywords such as `"nil"` allowed.
|
||||
function M.annot.tid(lx)
|
||||
local w = lx :next()
|
||||
local t = w.tag
|
||||
if t=='Keyword' and w[1] :match '^[%a_][%w_]*$' or w.tag=='Id'
|
||||
then return {tag='TId'; lineinfo=w.lineinfo; w[1]}
|
||||
else return gg.parse_error (lx, 'tid expected') end
|
||||
end
|
||||
|
||||
local field_types = { var='TVar'; const='TConst';
|
||||
currently='TCurrently'; field='TField' }
|
||||
|
||||
-- TODO check lineinfo
|
||||
function M.annot.tf(lx)
|
||||
local tk = lx:next()
|
||||
local w = tk[1]
|
||||
local tag = field_types[w]
|
||||
if not tag then error ('Invalid field type '..w)
|
||||
elseif tag=='TField' then return {tag='TField'} else
|
||||
local te = M.te(lx)
|
||||
return {tag=tag; te}
|
||||
end
|
||||
end
|
||||
|
||||
M.annot.tebar_content = gg.list{
|
||||
name = 'tebar content',
|
||||
primary = _A.te,
|
||||
separators = { ",", ";" },
|
||||
terminators = ")" }
|
||||
|
||||
M.annot.tebar = gg.multisequence{
|
||||
name = 'annot.tebar',
|
||||
--{ '*', builder = 'TDynbar' }, -- maybe not user-available
|
||||
{ '(', _A.tebar_content, ')',
|
||||
builder = function(x) return x[1] end },
|
||||
{ _A.te }
|
||||
}
|
||||
|
||||
M.annot.te = gg.multisequence{
|
||||
name = 'annot.te',
|
||||
{ _A.tid, builder=function(x) return x[1] end },
|
||||
{ '*', builder = 'TDyn' },
|
||||
{ "[",
|
||||
gg.list{
|
||||
primary = gg.sequence{
|
||||
_M.expr, "=", _A.tf,
|
||||
builder = 'TPair'
|
||||
},
|
||||
separators = { ",", ";" },
|
||||
terminators = { "]", "|" } },
|
||||
gg.onkeyword{ "|", _A.tf },
|
||||
"]",
|
||||
builder = function(x)
|
||||
local fields, other = unpack(x)
|
||||
return { tag='TTable', other or {tag='TField'}, fields }
|
||||
end }, -- "[ ... ]"
|
||||
{ '(', _A.tebar_content, ')', '->', '(', _A.tebar_content, ')',
|
||||
builder = function(x)
|
||||
local p, r = unpack(x)
|
||||
return {tag='TFunction', p, r }
|
||||
end } }
|
||||
|
||||
M.annot.ts = gg.multisequence{
|
||||
name = 'annot.ts',
|
||||
{ 'return', _A.tebar_content, builder='TReturn' },
|
||||
{ _A.tid, builder = function(x)
|
||||
if x[1][1]=='pass' then return {tag='TPass'}
|
||||
else error "Bad statement type" end
|
||||
end } }
|
||||
|
||||
-- TODO: add parsers for statements:
|
||||
-- #return tebar
|
||||
-- #alias = te
|
||||
-- #ell = tf
|
||||
--[[
|
||||
M.annot.stat_annot = gg.sequence{
|
||||
gg.list{ primary=_A.tid, separators='.' },
|
||||
'=',
|
||||
XXX??,
|
||||
builder = 'Annot' }
|
||||
--]]
|
||||
|
||||
return M.annot
|
||||
end
|
||||
206
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/expr.lua
Normal file
206
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/expr.lua
Normal file
@@ -0,0 +1,206 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Exported API:
|
||||
-- * [mlp.expr()]
|
||||
-- * [mlp.expr_list()]
|
||||
-- * [mlp.func_val()]
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local pp = require 'metalua.pprint'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local annot = require 'metalua.compiler.parser.annot.generator'
|
||||
|
||||
return function(M)
|
||||
local _M = gg.future(M)
|
||||
local _table = gg.future(M, 'table')
|
||||
local _meta = gg.future(M, 'meta') -- TODO move to ext?
|
||||
local _annot = gg.future(M, 'annot') -- TODO move to annot
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Non-empty expression list. Actually, this isn't used here, but that's
|
||||
-- handy to give to users.
|
||||
--------------------------------------------------------------------------------
|
||||
M.expr_list = gg.list{ primary=_M.expr, separators="," }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Helpers for function applications / method applications
|
||||
--------------------------------------------------------------------------------
|
||||
M.func_args_content = gg.list{
|
||||
name = "function arguments",
|
||||
primary = _M.expr,
|
||||
separators = ",",
|
||||
terminators = ")" }
|
||||
|
||||
-- Used to parse methods
|
||||
M.method_args = gg.multisequence{
|
||||
name = "function argument(s)",
|
||||
{ "{", _table.content, "}" },
|
||||
{ "(", _M.func_args_content, ")", builder = unpack },
|
||||
{ "+{", _meta.quote_content, "}" },
|
||||
-- TODO lineinfo?
|
||||
function(lx) local r = M.opt_string(lx); return r and {r} or { } end }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- [func_val] parses a function, from opening parameters parenthese to
|
||||
-- "end" keyword included. Used for anonymous functions as well as
|
||||
-- function declaration statements (both local and global).
|
||||
--
|
||||
-- It's wrapped in a [_func_val] eta expansion, so that when expr
|
||||
-- parser uses the latter, they will notice updates of [func_val]
|
||||
-- definitions.
|
||||
--------------------------------------------------------------------------------
|
||||
M.func_params_content = gg.list{
|
||||
name="function parameters",
|
||||
gg.multisequence{ { "...", builder = "Dots" }, annot.opt(M, _M.id, 'te') },
|
||||
separators = ",", terminators = {")", "|"} }
|
||||
|
||||
-- TODO move to annot
|
||||
M.func_val = gg.sequence{
|
||||
name = "function body",
|
||||
"(", _M.func_params_content, ")", _M.block, "end",
|
||||
builder = function(x)
|
||||
local params, body = unpack(x)
|
||||
local annots, some = { }, false
|
||||
for i, p in ipairs(params) do
|
||||
if p.tag=='Annot' then
|
||||
params[i], annots[i], some = p[1], p[2], true
|
||||
else annots[i] = false end
|
||||
end
|
||||
if some then return { tag='Function', params, body, annots }
|
||||
else return { tag='Function', params, body } end
|
||||
end }
|
||||
|
||||
local func_val = function(lx) return M.func_val(lx) end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Default parser for primary expressions
|
||||
--------------------------------------------------------------------------------
|
||||
function M.id_or_literal (lx)
|
||||
local a = lx:next()
|
||||
if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then
|
||||
local msg
|
||||
if a.tag=='Eof' then
|
||||
msg = "End of file reached when an expression was expected"
|
||||
elseif a.tag=='Keyword' then
|
||||
msg = "An expression was expected, and `"..a[1]..
|
||||
"' can't start an expression"
|
||||
else
|
||||
msg = "Unexpected expr token " .. pp.tostring (a)
|
||||
end
|
||||
gg.parse_error (lx, msg)
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Builder generator for operators. Wouldn't be worth it if "|x|" notation
|
||||
-- were allowed, but then lua 5.1 wouldn't compile it
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- opf1 = |op| |_,a| `Op{ op, a }
|
||||
local function opf1 (op) return
|
||||
function (_,a) return { tag="Op", op, a } end end
|
||||
|
||||
-- opf2 = |op| |a,_,b| `Op{ op, a, b }
|
||||
local function opf2 (op) return
|
||||
function (a,_,b) return { tag="Op", op, a, b } end end
|
||||
|
||||
-- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed)
|
||||
local function opf2r (op) return
|
||||
function (a,_,b) return { tag="Op", op, b, a } end end
|
||||
|
||||
local function op_ne(a, _, b)
|
||||
-- This version allows to remove the "ne" operator from the AST definition.
|
||||
-- However, it doesn't always produce the exact same bytecode as Lua 5.1.
|
||||
return { tag="Op", "not",
|
||||
{ tag="Op", "eq", a, b, lineinfo= {
|
||||
first = a.lineinfo.first, last = b.lineinfo.last } } }
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- complete expression
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- FIXME: set line number. In [expr] transformers probably
|
||||
M.expr = gg.expr {
|
||||
name = "expression",
|
||||
primary = gg.multisequence{
|
||||
name = "expr primary",
|
||||
{ "(", _M.expr, ")", builder = "Paren" },
|
||||
{ "function", _M.func_val, builder = unpack },
|
||||
{ "-{", _meta.splice_content, "}", builder = unpack },
|
||||
{ "+{", _meta.quote_content, "}", builder = unpack },
|
||||
{ "nil", builder = "Nil" },
|
||||
{ "true", builder = "True" },
|
||||
{ "false", builder = "False" },
|
||||
{ "...", builder = "Dots" },
|
||||
{ "{", _table.content, "}", builder = unpack },
|
||||
_M.id_or_literal },
|
||||
|
||||
infix = {
|
||||
name = "expr infix op",
|
||||
{ "+", prec = 60, builder = opf2 "add" },
|
||||
{ "-", prec = 60, builder = opf2 "sub" },
|
||||
{ "*", prec = 70, builder = opf2 "mul" },
|
||||
{ "/", prec = 70, builder = opf2 "div" },
|
||||
{ "%", prec = 70, builder = opf2 "mod" },
|
||||
{ "^", prec = 90, builder = opf2 "pow", assoc = "right" },
|
||||
{ "..", prec = 40, builder = opf2 "concat", assoc = "right" },
|
||||
{ "==", prec = 30, builder = opf2 "eq" },
|
||||
{ "~=", prec = 30, builder = op_ne },
|
||||
{ "<", prec = 30, builder = opf2 "lt" },
|
||||
{ "<=", prec = 30, builder = opf2 "le" },
|
||||
{ ">", prec = 30, builder = opf2r "lt" },
|
||||
{ ">=", prec = 30, builder = opf2r "le" },
|
||||
{ "and",prec = 20, builder = opf2 "and" },
|
||||
{ "or", prec = 10, builder = opf2 "or" } },
|
||||
|
||||
prefix = {
|
||||
name = "expr prefix op",
|
||||
{ "not", prec = 80, builder = opf1 "not" },
|
||||
{ "#", prec = 80, builder = opf1 "len" },
|
||||
{ "-", prec = 80, builder = opf1 "unm" } },
|
||||
|
||||
suffix = {
|
||||
name = "expr suffix op",
|
||||
{ "[", _M.expr, "]", builder = function (tab, idx)
|
||||
return {tag="Index", tab, idx[1]} end},
|
||||
{ ".", _M.id, builder = function (tab, field)
|
||||
return {tag="Index", tab, _M.id2string(field[1])} end },
|
||||
{ "(", _M.func_args_content, ")", builder = function(f, args)
|
||||
return {tag="Call", f, unpack(args[1])} end },
|
||||
{ "{", _table.content, "}", builder = function (f, arg)
|
||||
return {tag="Call", f, arg[1]} end},
|
||||
{ ":", _M.id, _M.method_args, builder = function (obj, post)
|
||||
local m_name, args = unpack(post)
|
||||
return {tag="Invoke", obj, _M.id2string(m_name), unpack(args)} end},
|
||||
{ "+{", _meta.quote_content, "}", builder = function (f, arg)
|
||||
return {tag="Call", f, arg[1] } end },
|
||||
default = { name="opt_string_arg", parse = _M.opt_string, builder = function(f, arg)
|
||||
return {tag="Call", f, arg } end } } }
|
||||
return M
|
||||
end
|
||||
96
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/ext.lua
Normal file
96
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/ext.lua
Normal file
@@ -0,0 +1,96 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Non-Lua syntax extensions
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
return function(M)
|
||||
|
||||
local _M = gg.future(M)
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- Algebraic Datatypes
|
||||
----------------------------------------------------------------------------
|
||||
local function adt (lx)
|
||||
local node = _M.id (lx)
|
||||
local tagval = node[1]
|
||||
-- tagkey = `Pair{ `String "key", `String{ -{tagval} } }
|
||||
local tagkey = { tag="Pair", {tag="String", "tag"}, {tag="String", tagval} }
|
||||
if lx:peek().tag == "String" or lx:peek().tag == "Number" then
|
||||
-- TODO support boolean litterals
|
||||
return { tag="Table", tagkey, lx:next() }
|
||||
elseif lx:is_keyword (lx:peek(), "{") then
|
||||
local x = M.table.table (lx)
|
||||
table.insert (x, 1, tagkey)
|
||||
return x
|
||||
else return { tag="Table", tagkey } end
|
||||
end
|
||||
|
||||
M.adt = gg.sequence{ "`", adt, builder = unpack }
|
||||
|
||||
M.expr.primary :add(M.adt)
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Anonymous lambda
|
||||
----------------------------------------------------------------------------
|
||||
M.lambda_expr = gg.sequence{
|
||||
"|", _M.func_params_content, "|", _M.expr,
|
||||
builder = function (x)
|
||||
local li = x[2].lineinfo
|
||||
return { tag="Function", x[1],
|
||||
{ {tag="Return", x[2], lineinfo=li }, lineinfo=li } }
|
||||
end }
|
||||
|
||||
M.expr.primary :add (M.lambda_expr)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Allows to write "a `f` b" instead of "f(a, b)". Taken from Haskell.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.expr_in_backquotes (lx) return M.expr(lx, 35) end -- 35=limited precedence
|
||||
M.expr.infix :add{ name = "infix function",
|
||||
"`", _M.expr_in_backquotes, "`", prec = 35, assoc="left",
|
||||
builder = function(a, op, b) return {tag="Call", op[1], a, b} end }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- C-style op+assignments
|
||||
-- TODO: no protection against side-effects in LHS vars.
|
||||
--------------------------------------------------------------------------------
|
||||
local function op_assign(kw, op)
|
||||
local function rhs(a, b) return { tag="Op", op, a, b } end
|
||||
local function f(a,b)
|
||||
if #a ~= #b then gg.parse_error "assymetric operator+assignment" end
|
||||
local right = { }
|
||||
local r = { tag="Set", a, right }
|
||||
for i=1, #a do right[i] = { tag="Op", op, a[i], b[i] } end
|
||||
return r
|
||||
end
|
||||
M.lexer :add (kw)
|
||||
M.assignments[kw] = f
|
||||
end
|
||||
|
||||
local ops = { add='+='; sub='-='; mul='*='; div='/=' }
|
||||
for ast_op_name, keyword in pairs(ops) do op_assign(keyword, ast_op_name) end
|
||||
|
||||
return M
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2014 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Generate a new lua-specific lexer, derived from the generic lexer.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
local generic_lexer = require 'metalua.grammar.lexer'
|
||||
|
||||
return function()
|
||||
local lexer = generic_lexer.lexer :clone()
|
||||
|
||||
local keywords = {
|
||||
"and", "break", "do", "else", "elseif",
|
||||
"end", "false", "for", "function",
|
||||
"goto", -- Lua5.2
|
||||
"if",
|
||||
"in", "local", "nil", "not", "or", "repeat",
|
||||
"return", "then", "true", "until", "while",
|
||||
"...", "..", "==", ">=", "<=", "~=",
|
||||
"::", -- Lua5,2
|
||||
"+{", "-{" } -- Metalua
|
||||
|
||||
for _, w in ipairs(keywords) do lexer :add (w) end
|
||||
|
||||
return lexer
|
||||
end
|
||||
138
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/meta.lua
Normal file
138
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/meta.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2014 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- Compile-time metaprogramming features: splicing ASTs generated during compilation,
|
||||
-- AST quasi-quoting helpers.
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
return function(M)
|
||||
local _M = gg.future(M)
|
||||
M.meta={ }
|
||||
local _MM = gg.future(M.meta)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- External splicing: compile an AST into a chunk, load and evaluate
|
||||
-- that chunk, and replace the chunk by its result (which must also be
|
||||
-- an AST).
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: that's not part of the parser
|
||||
function M.meta.eval (ast)
|
||||
-- TODO: should there be one mlc per splice, or per parser instance?
|
||||
local mlc = require 'metalua.compiler'.new()
|
||||
local f = mlc :ast_to_function (ast, '=splice')
|
||||
local result=f(M) -- splices act on the current parser
|
||||
return result
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Going from an AST to an AST representing that AST
|
||||
-- the only hash-part key being lifted is `"tag"`.
|
||||
-- Doesn't lift subtrees protected inside a `Splice{ ... }.
|
||||
-- e.g. change `Foo{ 123 } into
|
||||
-- `Table{ `Pair{ `String "tag", `String "foo" }, `Number 123 }
|
||||
----------------------------------------------------------------------------
|
||||
local function lift (t)
|
||||
--print("QUOTING:", table.tostring(t, 60,'nohash'))
|
||||
local cases = { }
|
||||
function cases.table (t)
|
||||
local mt = { tag = "Table" }
|
||||
--table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } })
|
||||
if t.tag == "Splice" then
|
||||
assert (#t==1, "Invalid splice")
|
||||
local sp = t[1]
|
||||
return sp
|
||||
elseif t.tag then
|
||||
table.insert (mt, { tag="Pair", lift "tag", lift(t.tag) })
|
||||
end
|
||||
for _, v in ipairs (t) do
|
||||
table.insert (mt, lift(v))
|
||||
end
|
||||
return mt
|
||||
end
|
||||
function cases.number (t) return { tag = "Number", t, quote = true } end
|
||||
function cases.string (t) return { tag = "String", t, quote = true } end
|
||||
function cases.boolean (t) return { tag = t and "True" or "False", t, quote = true } end
|
||||
local f = cases [type(t)]
|
||||
if f then return f(t) else error ("Cannot quote an AST containing "..tostring(t)) end
|
||||
end
|
||||
M.meta.lift = lift
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- when this variable is false, code inside [-{...}] is compiled and
|
||||
-- avaluated immediately. When it's true (supposedly when we're
|
||||
-- parsing data inside a quasiquote), [-{foo}] is replaced by
|
||||
-- [`Splice{foo}], which will be unpacked by [quote()].
|
||||
--------------------------------------------------------------------------------
|
||||
local in_a_quote = false
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Parse the inside of a "-{ ... }"
|
||||
--------------------------------------------------------------------------------
|
||||
function M.meta.splice_content (lx)
|
||||
local parser_name = "expr"
|
||||
if lx:is_keyword (lx:peek(2), ":") then
|
||||
local a = lx:next()
|
||||
lx:next() -- skip ":"
|
||||
assert (a.tag=="Id", "Invalid splice parser name")
|
||||
parser_name = a[1]
|
||||
end
|
||||
-- TODO FIXME running a new parser with the old lexer?!
|
||||
local parser = require 'metalua.compiler.parser'.new()
|
||||
local ast = parser [parser_name](lx)
|
||||
if in_a_quote then -- only prevent quotation in this subtree
|
||||
--printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60))
|
||||
return { tag="Splice", ast }
|
||||
else -- convert in a block, eval, replace with result
|
||||
if parser_name == "expr" then ast = { { tag="Return", ast } }
|
||||
elseif parser_name == "stat" then ast = { ast }
|
||||
elseif parser_name ~= "block" then
|
||||
error ("splice content must be an expr, stat or block") end
|
||||
--printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60))
|
||||
return M.meta.eval (ast)
|
||||
end
|
||||
end
|
||||
|
||||
M.meta.splice = gg.sequence{ "-{", _MM.splice_content, "}", builder=unpack }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Parse the inside of a "+{ ... }"
|
||||
--------------------------------------------------------------------------------
|
||||
function M.meta.quote_content (lx)
|
||||
local parser
|
||||
if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content }
|
||||
local parser_name = M.id(lx)[1]
|
||||
parser = M[parser_name]
|
||||
lx:next() -- skip ":"
|
||||
else -- +{ content }
|
||||
parser = M.expr
|
||||
end
|
||||
|
||||
local prev_iq = in_a_quote
|
||||
in_a_quote = true
|
||||
--print("IN_A_QUOTE")
|
||||
local content = parser (lx)
|
||||
local q_content = M.meta.lift (content)
|
||||
in_a_quote = prev_iq
|
||||
return q_content
|
||||
end
|
||||
|
||||
return M
|
||||
end
|
||||
176
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/misc.lua
Normal file
176
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/misc.lua
Normal file
@@ -0,0 +1,176 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Summary: metalua parser, miscellaneous utility functions.
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Exported API:
|
||||
-- * [mlp.fget()]
|
||||
-- * [mlp.id()]
|
||||
-- * [mlp.opt_id()]
|
||||
-- * [mlp.id_list()]
|
||||
-- * [mlp.string()]
|
||||
-- * [mlp.opt_string()]
|
||||
-- * [mlp.id2string()]
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local pp = require 'metalua.pprint'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
-- TODO: replace splice-aware versions with naive ones, move etensions in ./meta
|
||||
|
||||
return function(M)
|
||||
local _M = gg.future(M)
|
||||
|
||||
--[[ metaprog-free versions:
|
||||
function M.id(lx)
|
||||
if lx:peek().tag~='Id' then gg.parse_error(lx, "Identifier expected")
|
||||
else return lx:next() end
|
||||
end
|
||||
|
||||
function M.opt_id(lx)
|
||||
if lx:peek().tag~='Id' then return lx:next() else return false end
|
||||
end
|
||||
|
||||
function M.string(lx)
|
||||
if lx:peek().tag~='String' then gg.parse_error(lx, "String expected")
|
||||
else return lx:next() end
|
||||
end
|
||||
|
||||
function M.opt_string(lx)
|
||||
if lx:peek().tag~='String' then return lx:next() else return false end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Converts an identifier into a string. Hopefully one day it'll handle
|
||||
-- splices gracefully, but that proves quite tricky.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.id2string (id)
|
||||
if id.tag == "Id" then id.tag = "String"; return id
|
||||
else error ("Identifier expected: "..table.tostring(id, 'nohash')) end
|
||||
end
|
||||
--]]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Try to read an identifier (possibly as a splice), or return [false] if no
|
||||
-- id is found.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.opt_id (lx)
|
||||
local a = lx:peek();
|
||||
if lx:is_keyword (a, "-{") then
|
||||
local v = M.meta.splice(lx)
|
||||
if v.tag ~= "Id" and v.tag ~= "Splice" then
|
||||
gg.parse_error(lx, "Bad id splice")
|
||||
end
|
||||
return v
|
||||
elseif a.tag == "Id" then return lx:next()
|
||||
else return false end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Mandatory reading of an id: causes an error if it can't read one.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.id (lx)
|
||||
return M.opt_id (lx) or gg.parse_error(lx,"Identifier expected")
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Common helper function
|
||||
--------------------------------------------------------------------------------
|
||||
M.id_list = gg.list { primary = _M.id, separators = "," }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Converts an identifier into a string. Hopefully one day it'll handle
|
||||
-- splices gracefully, but that proves quite tricky.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.id2string (id)
|
||||
--print("id2string:", disp.ast(id))
|
||||
if id.tag == "Id" then id.tag = "String"; return id
|
||||
elseif id.tag == "Splice" then
|
||||
error ("id2string on splice not implemented")
|
||||
-- Evaluating id[1] will produce `Id{ xxx },
|
||||
-- and we want it to produce `String{ xxx }.
|
||||
-- The following is the plain notation of:
|
||||
-- +{ `String{ `Index{ `Splice{ -{id[1]} }, `Number 1 } } }
|
||||
return { tag="String", { tag="Index", { tag="Splice", id[1] },
|
||||
{ tag="Number", 1 } } }
|
||||
else error ("Identifier expected: "..pp.tostring (id, {metalua_tag=1, hide_hash=1})) end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Read a string, possibly spliced, or return an error if it can't
|
||||
--------------------------------------------------------------------------------
|
||||
function M.string (lx)
|
||||
local a = lx:peek()
|
||||
if lx:is_keyword (a, "-{") then
|
||||
local v = M.meta.splice(lx)
|
||||
if v.tag ~= "String" and v.tag ~= "Splice" then
|
||||
gg.parse_error(lx,"Bad string splice")
|
||||
end
|
||||
return v
|
||||
elseif a.tag == "String" then return lx:next()
|
||||
else error "String expected" end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Try to read a string, or return false if it can't. No splice allowed.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.opt_string (lx)
|
||||
return lx:peek().tag == "String" and lx:next()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Chunk reader: block + Eof
|
||||
--------------------------------------------------------------------------------
|
||||
function M.skip_initial_sharp_comment (lx)
|
||||
-- Dirty hack: I'm happily fondling lexer's private parts
|
||||
-- FIXME: redundant with lexer:newstream()
|
||||
lx :sync()
|
||||
local i = lx.src:match ("^#.-\n()", lx.i)
|
||||
if i then
|
||||
lx.i = i
|
||||
lx.column_offset = i
|
||||
lx.line = lx.line and lx.line + 1 or 1
|
||||
end
|
||||
end
|
||||
|
||||
local function chunk (lx)
|
||||
if lx:peek().tag == 'Eof' then
|
||||
return { } -- handle empty files
|
||||
else
|
||||
M.skip_initial_sharp_comment (lx)
|
||||
local chunk = M.block (lx)
|
||||
if lx:peek().tag ~= "Eof" then
|
||||
gg.parse_error(lx, "End-of-file expected")
|
||||
end
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
|
||||
-- chunk is wrapped in a sequence so that it has a "transformer" field.
|
||||
M.chunk = gg.sequence { chunk, builder = unpack }
|
||||
|
||||
return M
|
||||
end
|
||||
279
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/stat.lua
Normal file
279
Utils/luarocks/share/lua/5.1/metalua/compiler/parser/stat.lua
Normal file
@@ -0,0 +1,279 @@
|
||||
------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Summary: metalua parser, statement/block parser. This is part of the
|
||||
-- definition of module [mlp].
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Exports API:
|
||||
-- * [mlp.stat()]
|
||||
-- * [mlp.block()]
|
||||
-- * [mlp.for_header()]
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local lexer = require 'metalua.grammar.lexer'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
local annot = require 'metalua.compiler.parser.annot.generator'
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- List of all keywords that indicate the end of a statement block. Users are
|
||||
-- likely to extend this list when designing extensions.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
return function(M)
|
||||
local _M = gg.future(M)
|
||||
|
||||
M.block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" }
|
||||
|
||||
-- FIXME: this must be handled from within GG!!!
|
||||
-- FIXME: there's no :add method in the list anyway. Added by gg.list?!
|
||||
function M.block_terminators :add(x)
|
||||
if type (x) == "table" then for _, y in ipairs(x) do self :add (y) end
|
||||
else table.insert (self, x) end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- list of statements, possibly followed by semicolons
|
||||
----------------------------------------------------------------------------
|
||||
M.block = gg.list {
|
||||
name = "statements block",
|
||||
terminators = M.block_terminators,
|
||||
primary = function (lx)
|
||||
-- FIXME use gg.optkeyword()
|
||||
local x = M.stat (lx)
|
||||
if lx:is_keyword (lx:peek(), ";") then lx:next() end
|
||||
return x
|
||||
end }
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Helper function for "return <expr_list>" parsing.
|
||||
-- Called when parsing return statements.
|
||||
-- The specific test for initial ";" is because it's not a block terminator,
|
||||
-- so without it gg.list would choke on "return ;" statements.
|
||||
-- We don't make a modified copy of block_terminators because this list
|
||||
-- is sometimes modified at runtime, and the return parser would get out of
|
||||
-- sync if it was relying on a copy.
|
||||
----------------------------------------------------------------------------
|
||||
local return_expr_list_parser = gg.multisequence{
|
||||
{ ";" , builder = function() return { } end },
|
||||
default = gg.list {
|
||||
_M.expr, separators = ",", terminators = M.block_terminators } }
|
||||
|
||||
|
||||
local for_vars_list = gg.list{
|
||||
name = "for variables list",
|
||||
primary = _M.id,
|
||||
separators = ",",
|
||||
terminators = "in" }
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- for header, between [for] and [do] (exclusive).
|
||||
-- Return the `Forxxx{...} AST, without the body element (the last one).
|
||||
----------------------------------------------------------------------------
|
||||
function M.for_header (lx)
|
||||
local vars = M.id_list(lx)
|
||||
if lx :is_keyword (lx:peek(), "=") then
|
||||
if #vars ~= 1 then
|
||||
gg.parse_error (lx, "numeric for only accepts one variable")
|
||||
end
|
||||
lx:next() -- skip "="
|
||||
local exprs = M.expr_list (lx)
|
||||
if #exprs < 2 or #exprs > 3 then
|
||||
gg.parse_error (lx, "numeric for requires 2 or 3 boundaries")
|
||||
end
|
||||
return { tag="Fornum", vars[1], unpack (exprs) }
|
||||
else
|
||||
if not lx :is_keyword (lx :next(), "in") then
|
||||
gg.parse_error (lx, '"=" or "in" expected in for loop')
|
||||
end
|
||||
local exprs = M.expr_list (lx)
|
||||
return { tag="Forin", vars, exprs }
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Function def parser helper: id ( . id ) *
|
||||
----------------------------------------------------------------------------
|
||||
local function fn_builder (list)
|
||||
local acc = list[1]
|
||||
local first = acc.lineinfo.first
|
||||
for i = 2, #list do
|
||||
local index = M.id2string(list[i])
|
||||
local li = lexer.new_lineinfo(first, index.lineinfo.last)
|
||||
acc = { tag="Index", acc, index, lineinfo=li }
|
||||
end
|
||||
return acc
|
||||
end
|
||||
local func_name = gg.list{ _M.id, separators = ".", builder = fn_builder }
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Function def parser helper: ( : id )?
|
||||
----------------------------------------------------------------------------
|
||||
local method_name = gg.onkeyword{ name = "method invocation", ":", _M.id,
|
||||
transformers = { function(x) return x and x.tag=='Id' and M.id2string(x) end } }
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- Function def builder
|
||||
----------------------------------------------------------------------------
|
||||
local function funcdef_builder(x)
|
||||
local name, method, func = unpack(x)
|
||||
if method then
|
||||
name = { tag="Index", name, method,
|
||||
lineinfo = {
|
||||
first = name.lineinfo.first,
|
||||
last = method.lineinfo.last } }
|
||||
table.insert (func[1], 1, {tag="Id", "self"})
|
||||
end
|
||||
local r = { tag="Set", {name}, {func} }
|
||||
r[1].lineinfo = name.lineinfo
|
||||
r[2].lineinfo = func.lineinfo
|
||||
return r
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- if statement builder
|
||||
----------------------------------------------------------------------------
|
||||
local function if_builder (x)
|
||||
local cond_block_pairs, else_block, r = x[1], x[2], {tag="If"}
|
||||
local n_pairs = #cond_block_pairs
|
||||
for i = 1, n_pairs do
|
||||
local cond, block = unpack(cond_block_pairs[i])
|
||||
r[2*i-1], r[2*i] = cond, block
|
||||
end
|
||||
if else_block then table.insert(r, #r+1, else_block) end
|
||||
return r
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- produce a list of (expr,block) pairs
|
||||
--------------------------------------------------------------------------------
|
||||
local elseifs_parser = gg.list {
|
||||
gg.sequence { _M.expr, "then", _M.block , name='elseif parser' },
|
||||
separators = "elseif",
|
||||
terminators = { "else", "end" }
|
||||
}
|
||||
|
||||
local annot_expr = gg.sequence {
|
||||
_M.expr,
|
||||
gg.onkeyword{ "#", gg.future(M, 'annot').tf },
|
||||
builder = function(x)
|
||||
local e, a = unpack(x)
|
||||
if a then return { tag='Annot', e, a }
|
||||
else return e end
|
||||
end }
|
||||
|
||||
local annot_expr_list = gg.list {
|
||||
primary = annot.opt(M, _M.expr, 'tf'), separators = ',' }
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- assignments and calls: statements that don't start with a keyword
|
||||
------------------------------------------------------------------------
|
||||
local function assign_or_call_stat_parser (lx)
|
||||
local e = annot_expr_list (lx)
|
||||
local a = lx:is_keyword(lx:peek())
|
||||
local op = a and M.assignments[a]
|
||||
-- TODO: refactor annotations
|
||||
if op then
|
||||
--FIXME: check that [e] is a LHS
|
||||
lx :next()
|
||||
local annots
|
||||
e, annots = annot.split(e)
|
||||
local v = M.expr_list (lx)
|
||||
if type(op)=="string" then return { tag=op, e, v, annots }
|
||||
else return op (e, v) end
|
||||
else
|
||||
assert (#e > 0)
|
||||
if #e > 1 then
|
||||
gg.parse_error (lx,
|
||||
"comma is not a valid statement separator; statement can be "..
|
||||
"separated by semicolons, or not separated at all")
|
||||
elseif e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then
|
||||
local typename
|
||||
if e[1].tag == 'Id' then
|
||||
typename = '("'..e[1][1]..'") is an identifier'
|
||||
elseif e[1].tag == 'Op' then
|
||||
typename = "is an arithmetic operation"
|
||||
else typename = "is of type '"..(e[1].tag or "<list>").."'" end
|
||||
gg.parse_error (lx,
|
||||
"This expression %s; "..
|
||||
"a statement was expected, and only function and method call "..
|
||||
"expressions can be used as statements", typename);
|
||||
end
|
||||
return e[1]
|
||||
end
|
||||
end
|
||||
|
||||
M.local_stat_parser = gg.multisequence{
|
||||
-- local function <name> <func_val>
|
||||
{ "function", _M.id, _M.func_val, builder =
|
||||
function(x)
|
||||
local vars = { x[1], lineinfo = x[1].lineinfo }
|
||||
local vals = { x[2], lineinfo = x[2].lineinfo }
|
||||
return { tag="Localrec", vars, vals }
|
||||
end },
|
||||
-- local <id_list> ( = <expr_list> )?
|
||||
default = gg.sequence{
|
||||
gg.list{
|
||||
primary = annot.opt(M, _M.id, 'tf'),
|
||||
separators = ',' },
|
||||
gg.onkeyword{ "=", _M.expr_list },
|
||||
builder = function(x)
|
||||
local annotated_left, right = unpack(x)
|
||||
local left, annotations = annot.split(annotated_left)
|
||||
return {tag="Local", left, right or { }, annotations }
|
||||
end } }
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- statement
|
||||
------------------------------------------------------------------------
|
||||
M.stat = gg.multisequence {
|
||||
name = "statement",
|
||||
{ "do", _M.block, "end", builder =
|
||||
function (x) return { tag="Do", unpack (x[1]) } end },
|
||||
{ "for", _M.for_header, "do", _M.block, "end", builder =
|
||||
function (x) x[1][#x[1]+1] = x[2]; return x[1] end },
|
||||
{ "function", func_name, method_name, _M.func_val, builder=funcdef_builder },
|
||||
{ "while", _M.expr, "do", _M.block, "end", builder = "While" },
|
||||
{ "repeat", _M.block, "until", _M.expr, builder = "Repeat" },
|
||||
{ "local", _M.local_stat_parser, builder = unpack },
|
||||
{ "return", return_expr_list_parser, builder =
|
||||
function(x) x[1].tag='Return'; return x[1] end },
|
||||
{ "break", builder = function() return { tag="Break" } end },
|
||||
{ "-{", gg.future(M, 'meta').splice_content, "}", builder = unpack },
|
||||
{ "if", gg.nonempty(elseifs_parser), gg.onkeyword{ "else", M.block }, "end",
|
||||
builder = if_builder },
|
||||
default = assign_or_call_stat_parser }
|
||||
|
||||
M.assignments = {
|
||||
["="] = "Set"
|
||||
}
|
||||
|
||||
function M.assignments:add(k, v) self[k] = v end
|
||||
|
||||
return M
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Exported API:
|
||||
-- * [M.table_bracket_field()]
|
||||
-- * [M.table_field()]
|
||||
-- * [M.table_content()]
|
||||
-- * [M.table()]
|
||||
--
|
||||
-- KNOWN BUG: doesn't handle final ";" or "," before final "}"
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
|
||||
return function(M)
|
||||
|
||||
M.table = { }
|
||||
local _table = gg.future(M.table)
|
||||
local _expr = gg.future(M).expr
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- `[key] = value` table field definition
|
||||
--------------------------------------------------------------------------------
|
||||
M.table.bracket_pair = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- table element parser: list value, `id = value` pair or `[value] = value` pair.
|
||||
--------------------------------------------------------------------------------
|
||||
function M.table.element (lx)
|
||||
if lx :is_keyword (lx :peek(), "[") then return M.table.bracket_pair(lx) end
|
||||
local e = M.expr (lx)
|
||||
if not lx :is_keyword (lx :peek(), "=") then return e end
|
||||
lx :next(); -- skip the "="
|
||||
local key = M.id2string(e) -- will fail on non-identifiers
|
||||
local val = M.expr(lx)
|
||||
local r = { tag="Pair", key, val }
|
||||
r.lineinfo = { first = key.lineinfo.first, last = val.lineinfo.last }
|
||||
return r
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- table constructor, without enclosing braces; returns a full table object
|
||||
-----------------------------------------------------------------------------
|
||||
M.table.content = gg.list {
|
||||
-- eta expansion to allow patching the element definition
|
||||
primary = _table.element,
|
||||
separators = { ",", ";" },
|
||||
terminators = "}",
|
||||
builder = "Table" }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- complete table constructor including [{...}]
|
||||
--------------------------------------------------------------------------------
|
||||
-- TODO beware, stat and expr use only table.content, this can't be patched.
|
||||
M.table.table = gg.sequence{ "{", _table.content, "}", builder = unpack }
|
||||
|
||||
return M
|
||||
end
|
||||
@@ -0,0 +1,282 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- This extension implements list comprehensions, similar to Haskell and
|
||||
-- Python syntax, to easily describe lists.
|
||||
--
|
||||
-- * x[a ... b] is the list { x[a], x[a+1], ..., x[b] }
|
||||
-- * { f()..., b } contains all the elements returned by f(), then b
|
||||
-- (allows to expand list fields other than the last one)
|
||||
-- * list comprehensions a la python, with "for" and "if" suffixes:
|
||||
-- {i+10*j for i=1,3 for j=1,3 if i~=j} is { 21, 31, 12, 32, 13, 23 }
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-{ extension ("match", ...) }
|
||||
|
||||
local SUPPORT_IMPROVED_LOOPS = true
|
||||
local SUPPORT_IMPROVED_INDEXES = false -- depends on deprecated table.isub
|
||||
local SUPPORT_CONTINUE = true
|
||||
local SUPPORT_COMP_LISTS = true
|
||||
|
||||
assert (SUPPORT_IMPROVED_LOOPS or not SUPPORT_CONTINUE,
|
||||
"Can't support 'continue' without improved loop headers")
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local Q = require 'metalua.treequery'
|
||||
|
||||
local function dots_list_suffix_builder (x) return `DotsSuffix{ x } end
|
||||
|
||||
local function for_list_suffix_builder (list_element, suffix)
|
||||
local new_header = suffix[1]
|
||||
match list_element with
|
||||
| `Comp{ _, acc } -> table.insert (acc, new_header); return list_element
|
||||
| _ -> return `Comp{ list_element, { new_header } }
|
||||
end
|
||||
end
|
||||
|
||||
local function if_list_suffix_builder (list_element, suffix)
|
||||
local new_header = `If{ suffix[1] }
|
||||
match list_element with
|
||||
| `Comp{ _, acc } -> table.insert (acc, new_header); return list_element
|
||||
| _ -> return `Comp{ list_element, { new_header } }
|
||||
end
|
||||
end
|
||||
|
||||
-- Builds a statement from a table element, which adds this element to
|
||||
-- a table `t`, potentially thanks to an alias `tinsert` to
|
||||
-- `table.insert`.
|
||||
-- @param core the part around which the loops are built.
|
||||
-- either `DotsSuffix{expr}, `Pair{ expr } or a plain expression
|
||||
-- @param list comprehension suffixes, in the order in which they appear
|
||||
-- either `Forin{ ... } or `Fornum{ ...} or `If{ ... }. In each case,
|
||||
-- it misses a last child node as its body.
|
||||
-- @param t a variable containing the table to fill
|
||||
-- @param tinsert a variable containing `table.insert`.
|
||||
--
|
||||
-- @return fill a statement which fills empty table `t` with the denoted element
|
||||
local function comp_list_builder(core, list, t, tinsert)
|
||||
local filler
|
||||
-- 1 - Build the loop's core: if it has suffix "...", every elements of the
|
||||
-- multi-return must be inserted, hence the extra [for] loop.
|
||||
match core with
|
||||
| `DotsSuffix{ element } ->
|
||||
local x = gg.gensym()
|
||||
filler = +{stat: for _, -{x} in pairs{ -{element} } do (-{tinsert})(-{t}, -{x}) end }
|
||||
| `Pair{ key, value } ->
|
||||
--filler = +{ -{t}[-{key}] = -{value} }
|
||||
filler = `Set{ { `Index{ t, key } }, { value } }
|
||||
| _ -> filler = +{ (-{tinsert})(-{t}, -{core}) }
|
||||
end
|
||||
|
||||
-- 2 - Stack the `if` and `for` control structures, from outside to inside.
|
||||
-- This is done in a destructive way for the elements of [list].
|
||||
for i = #list, 1, -1 do
|
||||
table.insert (list[i], {filler})
|
||||
filler = list[i]
|
||||
end
|
||||
|
||||
return filler
|
||||
end
|
||||
|
||||
local function table_content_builder (list)
|
||||
local special = false -- Does the table need a special builder?
|
||||
for _, element in ipairs(list) do
|
||||
local etag = element.tag
|
||||
if etag=='Comp' or etag=='DotsSuffix' then special=true; break end
|
||||
end
|
||||
if not special then list.tag='Table'; return list end
|
||||
|
||||
local t, tinsert = gg.gensym 'table', gg.gensym 'table_insert'
|
||||
local filler_block = { +{stat: local -{t}, -{tinsert} = { }, table.insert } }
|
||||
for _, element in ipairs(list) do
|
||||
local filler
|
||||
match element with
|
||||
| `Comp{ core, comp } -> filler = comp_list_builder(core, comp, t, tinsert)
|
||||
| _ -> filler = comp_list_builder(element, { }, t, tinsert)
|
||||
end
|
||||
table.insert(filler_block, filler)
|
||||
end
|
||||
return `Stat{ filler_block, t }
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Back-end for improved index operator.
|
||||
local function index_builder(a, suffix)
|
||||
match suffix[1] with
|
||||
-- Single index, no range: keep the native semantics
|
||||
| { { e, false } } -> return `Index{ a, e }
|
||||
-- Either a range, or multiple indexes, or both
|
||||
| ranges ->
|
||||
local r = `Call{ +{table.isub}, a }
|
||||
local function acc (x,y) table.insert (r,x); table.insert (r,y) end
|
||||
for _, seq in ipairs (ranges) do
|
||||
match seq with
|
||||
| { e, false } -> acc(e,e)
|
||||
| { e, f } -> acc(e,f)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Find continue statements in a loop body, change them into goto
|
||||
-- end-of-body.
|
||||
local function transform_continue_statements(body)
|
||||
local continue_statements = Q(body)
|
||||
:if_unknown() -- tolerate unknown 'Continue' statements
|
||||
:not_under ('Forin', 'Fornum', 'While', 'Repeat')
|
||||
:filter ('Continue')
|
||||
:list()
|
||||
if next(continue_statements) then
|
||||
local continue_label = gg.gensym 'continue' [1]
|
||||
table.insert(body, `Label{ continue_label })
|
||||
for _, statement in ipairs(continue_statements) do
|
||||
statement.tag = 'Goto'
|
||||
statement[1] = continue_label
|
||||
end
|
||||
return true
|
||||
else return false end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Back-end for loops with a multi-element header
|
||||
local function loop_builder(x)
|
||||
local first, elements, body = unpack(x)
|
||||
|
||||
-- Change continue statements into gotos.
|
||||
if SUPPORT_CONTINUE then transform_continue_statements(body) end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- If it's a regular loop, don't bloat the code
|
||||
if not next(elements) then
|
||||
table.insert(first, body)
|
||||
return first
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- There's no reason to treat the first element in a special way
|
||||
table.insert(elements, 1, first)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Change breaks into gotos that escape all loops at once.
|
||||
local exit_label = nil
|
||||
local function break_to_goto(break_node)
|
||||
if not exit_label then exit_label = gg.gensym 'break' [1] end
|
||||
break_node = break_node or { }
|
||||
break_node.tag = 'Goto'
|
||||
break_node[1] = exit_label
|
||||
return break_node
|
||||
end
|
||||
Q(body)
|
||||
:not_under('Function', 'Forin', 'Fornum', 'While', 'Repeat')
|
||||
:filter('Break')
|
||||
:foreach (break_to_goto)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Compile all headers elements, from last to first.
|
||||
-- invariant: `body` is a block (not a statement)
|
||||
local result = body
|
||||
for i = #elements, 1, -1 do
|
||||
local e = elements[i]
|
||||
match e with
|
||||
| `If{ cond } ->
|
||||
result = { `If{ cond, result } }
|
||||
| `Until{ cond } ->
|
||||
result = +{block: if -{cond} then -{break_to_goto()} else -{result} end }
|
||||
| `While{ cond } ->
|
||||
if i==1 then result = { `While{ cond, result } } -- top-level while
|
||||
else result = +{block: if -{cond} then -{result} else -{break_to_goto()} end } end
|
||||
| `Forin{ ... } | `Fornum{ ... } ->
|
||||
table.insert (e, result); result={e}
|
||||
| _-> require'metalua.pprint'.printf("Bad loop header element %s", e)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- If some breaks had to be changed into gotos, insert the label
|
||||
if exit_label then result = { result, `Label{ exit_label } } end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Improved "[...]" index operator:
|
||||
-- * support for multi-indexes ("foo[bar, gnat]")
|
||||
-- * support for ranges ("foo[bar ... gnat]")
|
||||
--------------------------------------------------------------------------------
|
||||
local function extend(M)
|
||||
|
||||
local _M = gg.future(M)
|
||||
|
||||
if SUPPORT_COMP_LISTS then
|
||||
-- support for "for" / "if" comprehension suffixes in literal tables
|
||||
local original_table_element = M.table.element
|
||||
M.table.element = gg.expr{ name="table cell",
|
||||
primary = original_table_element,
|
||||
suffix = { name="table cell suffix",
|
||||
{ "...", builder = dots_list_suffix_builder },
|
||||
{ "for", _M.for_header, builder = for_list_suffix_builder },
|
||||
{ "if", _M.expr, builder = if_list_suffix_builder } } }
|
||||
M.table.content.builder = table_content_builder
|
||||
end
|
||||
|
||||
if SUPPORT_IMPROVED_INDEXES then
|
||||
-- Support for ranges and multiple indices in bracket suffixes
|
||||
M.expr.suffix:del '['
|
||||
M.expr.suffix:add{ name="table index/range",
|
||||
"[", gg.list{
|
||||
gg.sequence { _M.expr, gg.onkeyword{ "...", _M.expr } } ,
|
||||
separators = { ",", ";" } },
|
||||
"]", builder = index_builder }
|
||||
end
|
||||
|
||||
if SUPPORT_IMPROVED_LOOPS then
|
||||
local original_for_header = M.for_header
|
||||
M.stat :del 'for'
|
||||
M.stat :del 'while'
|
||||
|
||||
M.loop_suffix = gg.multisequence{
|
||||
{ 'while', _M.expr, builder = |x| `Until{ `Op{ 'not', x[1] } } },
|
||||
{ 'until', _M.expr, builder = |x| `Until{ x[1] } },
|
||||
{ 'if', _M.expr, builder = |x| `If{ x[1] } },
|
||||
{ 'for', original_for_header, builder = |x| x[1] } }
|
||||
|
||||
M.loop_suffix_list = gg.list{ _M.loop_suffix, terminators='do' }
|
||||
|
||||
M.stat :add{
|
||||
'for', original_for_header, _M.loop_suffix_list, 'do', _M.block, 'end',
|
||||
builder = loop_builder }
|
||||
|
||||
M.stat :add{
|
||||
'while', _M.expr, _M.loop_suffix_list, 'do', _M.block, 'end',
|
||||
builder = |x| loop_builder{ `While{x[1]}, x[2], x[3] } }
|
||||
end
|
||||
|
||||
if SUPPORT_CONTINUE then
|
||||
M.lexer :add 'continue'
|
||||
M.stat :add{ 'continue', builder='Continue' }
|
||||
end
|
||||
end
|
||||
|
||||
return extend
|
||||
400
Utils/luarocks/share/lua/5.1/metalua/extension/match.mlua
Normal file
400
Utils/luarocks/share/lua/5.1/metalua/extension/match.mlua
Normal file
@@ -0,0 +1,400 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Glossary:
|
||||
--
|
||||
-- * term_seq: the tested stuff, a sequence of terms
|
||||
-- * pattern_element: might match one term of a term seq. Represented
|
||||
-- as expression ASTs.
|
||||
-- * pattern_seq: might match a term_seq
|
||||
-- * pattern_group: several pattern seqs, one of them might match
|
||||
-- the term seq.
|
||||
-- * case: pattern_group * guard option * block
|
||||
-- * match_statement: tested term_seq * case list
|
||||
--
|
||||
-- Hence a complete match statement is a:
|
||||
--
|
||||
-- { list(expr), list{ list(list(expr)), expr or false, block } }
|
||||
--
|
||||
-- Implementation hints
|
||||
-- ====================
|
||||
--
|
||||
-- The implementation is made as modular as possible, so that parts
|
||||
-- can be reused in other extensions. The priviledged way to share
|
||||
-- contextual information across functions is through the 'cfg' table
|
||||
-- argument. Its fields include:
|
||||
--
|
||||
-- * code: code generated from pattern. A pattern_(element|seq|group)
|
||||
-- is compiled as a sequence of instructions which will jump to
|
||||
-- label [cfg.on_failure] if the tested term doesn't match.
|
||||
--
|
||||
-- * on_failure: name of the label where the code will jump if the
|
||||
-- pattern doesn't match
|
||||
--
|
||||
-- * locals: names of local variables used by the pattern. This
|
||||
-- includes bound variables, and temporary variables used to
|
||||
-- destructure tables. Names are stored as keys of the table,
|
||||
-- values are meaningless.
|
||||
--
|
||||
-- * after_success: label where the code must jump after a pattern
|
||||
-- succeeded to capture a term, and the guard suceeded if there is
|
||||
-- any, and the conditional block has run.
|
||||
--
|
||||
-- * ntmp: number of temporary variables used to destructurate table
|
||||
-- in the current case.
|
||||
--
|
||||
-- Code generation is performed by acc_xxx() functions, which accumulate
|
||||
-- code in cfg.code:
|
||||
--
|
||||
-- * acc_test(test, cfg) will generate a jump to cfg.on_failure
|
||||
-- *when the test returns TRUE*
|
||||
--
|
||||
-- * acc_stat accumulates a statement
|
||||
--
|
||||
-- * acc_assign accumulate an assignment statement, and makes sure that
|
||||
-- the LHS variable the registered as local in cfg.locals.
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: hygiene wrt type()
|
||||
-- TODO: cfg.ntmp isn't reset as often as it could. I'm not even sure
|
||||
-- the corresponding locals are declared.
|
||||
|
||||
local checks = require 'checks'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local pp = require 'metalua.pprint'
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- This would have been best done through library 'metalua.walk',
|
||||
-- but walk depends on match, so we have to break the dependency.
|
||||
-- It replaces all instances of `...' in `ast' with `term', unless
|
||||
-- it appears in a function.
|
||||
----------------------------------------------------------------------
|
||||
local function replace_dots (ast, term)
|
||||
local function rec (node)
|
||||
for i, child in ipairs(node) do
|
||||
if type(child)~="table" then -- pass
|
||||
elseif child.tag=='Dots' then
|
||||
if term=='ambiguous' then
|
||||
error ("You can't use `...' on the right of a match case when it appears "..
|
||||
"more than once on the left")
|
||||
else node[i] = term end
|
||||
elseif child.tag=='Function' then return nil
|
||||
else rec(child) end
|
||||
end
|
||||
end
|
||||
return rec(ast)
|
||||
end
|
||||
|
||||
local tmpvar_base = gg.gensym 'submatch.' [1]
|
||||
|
||||
local function next_tmpvar(cfg)
|
||||
assert (cfg.ntmp, "No cfg.ntmp imbrication level in the match compiler")
|
||||
cfg.ntmp = cfg.ntmp+1
|
||||
return `Id{ tmpvar_base .. cfg.ntmp }
|
||||
end
|
||||
|
||||
-- Code accumulators
|
||||
local acc_stat = |x,cfg| table.insert (cfg.code, x)
|
||||
local acc_test = |x,cfg| acc_stat(+{stat: if -{x} then -{`Goto{cfg.on_failure}} end}, cfg)
|
||||
-- lhs :: `Id{ string }
|
||||
-- rhs :: expr
|
||||
local function acc_assign (lhs, rhs, cfg)
|
||||
assert(lhs.tag=='Id')
|
||||
cfg.locals[lhs[1]] = true
|
||||
acc_stat (`Set{ {lhs}, {rhs} }, cfg)
|
||||
end
|
||||
|
||||
local literal_tags = { String=1, Number=1, True=1, False=1, Nil=1 }
|
||||
|
||||
-- pattern :: `Id{ string }
|
||||
-- term :: expr
|
||||
local function id_pattern_element_builder (pattern, term, cfg)
|
||||
assert (pattern.tag == "Id")
|
||||
if pattern[1] == "_" then
|
||||
-- "_" is used as a dummy var ==> no assignment, no == checking
|
||||
cfg.locals._ = true
|
||||
elseif cfg.locals[pattern[1]] then
|
||||
-- This var is already bound ==> test for equality
|
||||
acc_test (+{ -{term} ~= -{pattern} }, cfg)
|
||||
else
|
||||
-- Free var ==> bind it, and remember it for latter linearity checking
|
||||
acc_assign (pattern, term, cfg)
|
||||
cfg.locals[pattern[1]] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- mutually recursive with table_pattern_element_builder
|
||||
local pattern_element_builder
|
||||
|
||||
-- pattern :: pattern and `Table{ }
|
||||
-- term :: expr
|
||||
local function table_pattern_element_builder (pattern, term, cfg)
|
||||
local seen_dots, len = false, 0
|
||||
acc_test (+{ type( -{term} ) ~= "table" }, cfg)
|
||||
for i = 1, #pattern do
|
||||
local key, sub_pattern
|
||||
if pattern[i].tag=="Pair" then -- Explicit key/value pair
|
||||
key, sub_pattern = unpack (pattern[i])
|
||||
assert (literal_tags[key.tag], "Invalid key")
|
||||
else -- Implicit key
|
||||
len, key, sub_pattern = len+1, `Number{ len+1 }, pattern[i]
|
||||
end
|
||||
|
||||
-- '...' can only appear in final position
|
||||
-- Could be fixed actually...
|
||||
assert (not seen_dots, "Wrongly placed `...' ")
|
||||
|
||||
if sub_pattern.tag == "Id" then
|
||||
-- Optimization: save a useless [ v(n+1)=v(n).key ]
|
||||
id_pattern_element_builder (sub_pattern, `Index{ term, key }, cfg)
|
||||
if sub_pattern[1] ~= "_" then
|
||||
acc_test (+{ -{sub_pattern} == nil }, cfg)
|
||||
end
|
||||
elseif sub_pattern.tag == "Dots" then
|
||||
-- Remember where the capture is, and thatt arity checking shouldn't occur
|
||||
seen_dots = true
|
||||
else
|
||||
-- Business as usual:
|
||||
local v2 = next_tmpvar(cfg)
|
||||
acc_assign (v2, `Index{ term, key }, cfg)
|
||||
pattern_element_builder (sub_pattern, v2, cfg)
|
||||
-- TODO: restore ntmp?
|
||||
end
|
||||
end
|
||||
if seen_dots then -- remember how to retrieve `...'
|
||||
-- FIXME: check, but there might be cases where the variable -{term}
|
||||
-- will be overridden in contrieved tables.
|
||||
-- ==> save it now, and clean the setting statement if unused
|
||||
if cfg.dots_replacement then cfg.dots_replacement = 'ambiguous'
|
||||
else cfg.dots_replacement = +{ select (-{`Number{len}}, unpack(-{term})) } end
|
||||
else -- Check arity
|
||||
acc_test (+{ #-{term} ~= -{`Number{len}} }, cfg)
|
||||
end
|
||||
end
|
||||
|
||||
-- mutually recursive with pattern_element_builder
|
||||
local eq_pattern_element_builder, regexp_pattern_element_builder
|
||||
|
||||
-- Concatenate code in [cfg.code], that will jump to label
|
||||
-- [cfg.on_failure] if [pattern] doesn't match [term]. [pattern]
|
||||
-- should be an identifier, or at least cheap to compute and
|
||||
-- side-effects free.
|
||||
--
|
||||
-- pattern :: pattern_element
|
||||
-- term :: expr
|
||||
function pattern_element_builder (pattern, term, cfg)
|
||||
if literal_tags[pattern.tag] then
|
||||
acc_test (+{ -{term} ~= -{pattern} }, cfg)
|
||||
elseif "Id" == pattern.tag then
|
||||
id_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Op" == pattern.tag and "div" == pattern[1] then
|
||||
regexp_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Op" == pattern.tag and "eq" == pattern[1] then
|
||||
eq_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Table" == pattern.tag then
|
||||
table_pattern_element_builder (pattern, term, cfg)
|
||||
else
|
||||
error ("Invalid pattern at "..
|
||||
tostring(pattern.lineinfo)..
|
||||
": "..pp.tostring(pattern, {hide_hash=true}))
|
||||
end
|
||||
end
|
||||
|
||||
function eq_pattern_element_builder (pattern, term, cfg)
|
||||
local _, pat1, pat2 = unpack (pattern)
|
||||
local ntmp_save = cfg.ntmp
|
||||
pattern_element_builder (pat1, term, cfg)
|
||||
cfg.ntmp = ntmp_save
|
||||
pattern_element_builder (pat2, term, cfg)
|
||||
end
|
||||
|
||||
-- pattern :: `Op{ 'div', string, list{`Id string} or `Id{ string }}
|
||||
-- term :: expr
|
||||
local function regexp_pattern_element_builder (pattern, term, cfg)
|
||||
local op, regexp, sub_pattern = unpack(pattern)
|
||||
|
||||
-- Sanity checks --
|
||||
assert (op=='div', "Don't know what to do with that op in a pattern")
|
||||
assert (regexp.tag=="String",
|
||||
"Left hand side operand for '/' in a pattern must be "..
|
||||
"a literal string representing a regular expression")
|
||||
if sub_pattern.tag=="Table" then
|
||||
for _, x in ipairs(sub_pattern) do
|
||||
assert (x.tag=="Id" or x.tag=='Dots',
|
||||
"Right hand side operand for '/' in a pattern must be "..
|
||||
"a list of identifiers")
|
||||
end
|
||||
else
|
||||
assert (sub_pattern.tag=="Id",
|
||||
"Right hand side operand for '/' in a pattern must be "..
|
||||
"an identifier or a list of identifiers")
|
||||
end
|
||||
|
||||
-- Regexp patterns can only match strings
|
||||
acc_test (+{ type(-{term}) ~= 'string' }, cfg)
|
||||
-- put all captures in a list
|
||||
local capt_list = +{ { string.strmatch(-{term}, -{regexp}) } }
|
||||
-- save them in a var_n for recursive decomposition
|
||||
local v2 = next_tmpvar(cfg)
|
||||
acc_stat (+{stat: local -{v2} = -{capt_list} }, cfg)
|
||||
-- was capture successful?
|
||||
acc_test (+{ not next (-{v2}) }, cfg)
|
||||
pattern_element_builder (sub_pattern, v2, cfg)
|
||||
end
|
||||
|
||||
|
||||
-- Jumps to [cfg.on_faliure] if pattern_seq doesn't match
|
||||
-- term_seq.
|
||||
local function pattern_seq_builder (pattern_seq, term_seq, cfg)
|
||||
if #pattern_seq ~= #term_seq then error ("Bad seq arity") end
|
||||
cfg.locals = { } -- reset bound variables between alternatives
|
||||
for i=1, #pattern_seq do
|
||||
cfg.ntmp = 1 -- reset the tmp var generator
|
||||
pattern_element_builder(pattern_seq[i], term_seq[i], cfg)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- for each case i:
|
||||
-- pattern_seq_builder_i:
|
||||
-- * on failure, go to on_failure_i
|
||||
-- * on success, go to on_success
|
||||
-- label on_success:
|
||||
-- block
|
||||
-- goto after_success
|
||||
-- label on_failure_i
|
||||
--------------------------------------------------
|
||||
local function case_builder (case, term_seq, cfg)
|
||||
local patterns_group, guard, block = unpack(case)
|
||||
local on_success = gg.gensym 'on_success' [1]
|
||||
for i = 1, #patterns_group do
|
||||
local pattern_seq = patterns_group[i]
|
||||
cfg.on_failure = gg.gensym 'match_fail' [1]
|
||||
cfg.dots_replacement = false
|
||||
pattern_seq_builder (pattern_seq, term_seq, cfg)
|
||||
if i<#patterns_group then
|
||||
acc_stat (`Goto{on_success}, cfg)
|
||||
acc_stat (`Label{cfg.on_failure}, cfg)
|
||||
end
|
||||
end
|
||||
acc_stat (`Label{on_success}, cfg)
|
||||
if guard then acc_test (+{not -{guard}}, cfg) end
|
||||
if cfg.dots_replacement then
|
||||
replace_dots (block, cfg.dots_replacement)
|
||||
end
|
||||
block.tag = 'Do'
|
||||
acc_stat (block, cfg)
|
||||
acc_stat (`Goto{cfg.after_success}, cfg)
|
||||
acc_stat (`Label{cfg.on_failure}, cfg)
|
||||
end
|
||||
|
||||
local function match_builder (x)
|
||||
local term_seq, cases = unpack(x)
|
||||
local cfg = {
|
||||
code = `Do{ },
|
||||
after_success = gg.gensym "_after_success" }
|
||||
|
||||
|
||||
-- Some sharing issues occur when modifying term_seq,
|
||||
-- so it's replaced by a copy new_term_seq.
|
||||
-- TODO: clean that up, and re-suppress the useless copies
|
||||
-- (cf. remarks about capture bug below).
|
||||
local new_term_seq = { }
|
||||
|
||||
local match_locals
|
||||
|
||||
-- Make sure that all tested terms are variables or literals
|
||||
for i=1, #term_seq do
|
||||
local t = term_seq[i]
|
||||
-- Capture problem: the following would compile wrongly:
|
||||
-- `match x with x -> end'
|
||||
-- Temporary workaround: suppress the condition, so that
|
||||
-- all external variables are copied into unique names.
|
||||
--if t.tag ~= 'Id' and not literal_tags[t.tag] then
|
||||
local v = gg.gensym 'v'
|
||||
if not match_locals then match_locals = `Local{ {v}, {t} } else
|
||||
table.insert(match_locals[1], v)
|
||||
table.insert(match_locals[2], t)
|
||||
end
|
||||
new_term_seq[i] = v
|
||||
--end
|
||||
end
|
||||
term_seq = new_term_seq
|
||||
|
||||
if match_locals then acc_stat(match_locals, cfg) end
|
||||
|
||||
for i=1, #cases do
|
||||
local case_cfg = {
|
||||
after_success = cfg.after_success,
|
||||
code = `Do{ }
|
||||
-- locals = { } -- unnecessary, done by pattern_seq_builder
|
||||
}
|
||||
case_builder (cases[i], term_seq, case_cfg)
|
||||
if next (case_cfg.locals) then
|
||||
local case_locals = { }
|
||||
table.insert (case_cfg.code, 1, `Local{ case_locals, { } })
|
||||
for v, _ in pairs (case_cfg.locals) do
|
||||
table.insert (case_locals, `Id{ v })
|
||||
end
|
||||
end
|
||||
acc_stat(case_cfg.code, cfg)
|
||||
end
|
||||
local li = `String{tostring(cases.lineinfo)}
|
||||
acc_stat(+{error('mismatch at '..-{li})}, cfg)
|
||||
acc_stat(`Label{cfg.after_success}, cfg)
|
||||
return cfg.code
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Syntactical front-end
|
||||
----------------------------------------------------------------------
|
||||
|
||||
local function extend(M)
|
||||
|
||||
local _M = gg.future(M)
|
||||
|
||||
checks('metalua.compiler.parser')
|
||||
M.lexer:add{ "match", "with", "->" }
|
||||
M.block.terminators:add "|"
|
||||
|
||||
local match_cases_list_parser = gg.list{ name = "match cases list",
|
||||
gg.sequence{ name = "match case",
|
||||
gg.list{ name = "match case patterns list",
|
||||
primary = _M.expr_list,
|
||||
separators = "|",
|
||||
terminators = { "->", "if" } },
|
||||
gg.onkeyword{ "if", _M.expr, consume = true },
|
||||
"->",
|
||||
_M.block },
|
||||
separators = "|",
|
||||
terminators = "end" }
|
||||
|
||||
M.stat:add{ name = "match statement",
|
||||
"match",
|
||||
_M.expr_list,
|
||||
"with", gg.optkeyword "|",
|
||||
match_cases_list_parser,
|
||||
"end",
|
||||
builder = |x| match_builder{ x[1], x[3] } }
|
||||
end
|
||||
|
||||
return extend
|
||||
834
Utils/luarocks/share/lua/5.1/metalua/grammar/generator.lua
Normal file
834
Utils/luarocks/share/lua/5.1/metalua/grammar/generator.lua
Normal file
@@ -0,0 +1,834 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Summary: parser generator. Collection of higher order functors,
|
||||
-- which allow to build and combine parsers. Relies on a lexer
|
||||
-- that supports the same API as the one exposed in mll.lua.
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Exported API:
|
||||
--
|
||||
-- Parser generators:
|
||||
-- * [gg.sequence()]
|
||||
-- * [gg.multisequence()]
|
||||
-- * [gg.expr()]
|
||||
-- * [gg.list()]
|
||||
-- * [gg.onkeyword()]
|
||||
-- * [gg.optkeyword()]
|
||||
--
|
||||
-- Other functions:
|
||||
-- * [gg.parse_error()]
|
||||
-- * [gg.make_parser()]
|
||||
-- * [gg.is_parser()]
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local M = { }
|
||||
|
||||
local checks = require 'checks'
|
||||
local lexer = require 'metalua.grammar.lexer'
|
||||
local pp = require 'metalua.pprint'
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier.
|
||||
-- The main purpose is to avoid variable capture in macros.
|
||||
--
|
||||
-- If a string is passed as an argument, theis string will be part of the
|
||||
-- id name (helpful for macro debugging)
|
||||
--------------------------------------------------------------------------------
|
||||
local gensymidx = 0
|
||||
|
||||
function M.gensym (arg)
|
||||
gensymidx = gensymidx + 1
|
||||
return { tag="Id", string.format(".%i.%s", gensymidx, arg or "")}
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- parser metatable, which maps __call to method parse, and adds some
|
||||
-- error tracing boilerplate.
|
||||
-------------------------------------------------------------------------------
|
||||
local parser_metatable = { }
|
||||
|
||||
function parser_metatable :__call (lx, ...)
|
||||
return self :parse (lx, ...)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Turn a table into a parser, mainly by setting the metatable.
|
||||
-------------------------------------------------------------------------------
|
||||
function M.make_parser(kind, p)
|
||||
p.kind = kind
|
||||
if not p.transformers then p.transformers = { } end
|
||||
function p.transformers:add (x)
|
||||
table.insert (self, x)
|
||||
end
|
||||
setmetatable (p, parser_metatable)
|
||||
return p
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Return true iff [x] is a parser.
|
||||
-- If it's a gg-generated parser, return the name of its kind.
|
||||
-------------------------------------------------------------------------------
|
||||
function M.is_parser (x)
|
||||
return type(x)=="function" or getmetatable(x)==parser_metatable and x.kind
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Parse a sequence, without applying builder nor transformers.
|
||||
-------------------------------------------------------------------------------
|
||||
local function raw_parse_sequence (lx, p)
|
||||
local r = { }
|
||||
for i=1, #p do
|
||||
local e=p[i]
|
||||
if type(e) == "string" then
|
||||
local kw = lx :next()
|
||||
if not lx :is_keyword (kw, e) then
|
||||
M.parse_error(
|
||||
lx, "A keyword was expected, probably `%s'.", e)
|
||||
end
|
||||
elseif M.is_parser (e) then
|
||||
table.insert (r, e(lx))
|
||||
else -- Invalid parser definition, this is *not* a parsing error
|
||||
error(string.format(
|
||||
"Sequence `%s': element #%i is neither a string nor a parser: %s",
|
||||
p.name, i, pp.tostring(e)))
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Parse a multisequence, without applying multisequence transformers.
|
||||
-- The sequences are completely parsed.
|
||||
-------------------------------------------------------------------------------
|
||||
local function raw_parse_multisequence (lx, sequence_table, default)
|
||||
local seq_parser = sequence_table[lx:is_keyword(lx:peek())]
|
||||
if seq_parser then return seq_parser (lx)
|
||||
elseif default then return default (lx)
|
||||
else return false end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Applies all transformers listed in parser on ast.
|
||||
-------------------------------------------------------------------------------
|
||||
local function transform (ast, parser, fli, lli)
|
||||
if parser.transformers then
|
||||
for _, t in ipairs (parser.transformers) do ast = t(ast) or ast end
|
||||
end
|
||||
if type(ast) == 'table' then
|
||||
local ali = ast.lineinfo
|
||||
if not ali or ali.first~=fli or ali.last~=lli then
|
||||
ast.lineinfo = lexer.new_lineinfo(fli, lli)
|
||||
end
|
||||
end
|
||||
return ast
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Generate a tracable parsing error (not implemented yet)
|
||||
-------------------------------------------------------------------------------
|
||||
function M.parse_error(lx, fmt, ...)
|
||||
local li = lx:lineinfo_left()
|
||||
local file, line, column, offset, positions
|
||||
if li then
|
||||
file, line, column, offset = li.source, li.line, li.column, li.offset
|
||||
positions = { first = li, last = li }
|
||||
else
|
||||
line, column, offset = -1, -1, -1
|
||||
end
|
||||
|
||||
local msg = string.format("line %i, char %i: "..fmt, line, column, ...)
|
||||
if file and file~='?' then msg = "file "..file..", "..msg end
|
||||
|
||||
local src = lx.src
|
||||
if offset>0 and src then
|
||||
local i, j = offset, offset
|
||||
while src:sub(i,i) ~= '\n' and i>=0 do i=i-1 end
|
||||
while src:sub(j,j) ~= '\n' and j<=#src do j=j+1 end
|
||||
local srcline = src:sub (i+1, j-1)
|
||||
local idx = string.rep (" ", column).."^"
|
||||
msg = string.format("%s\n>>> %s\n>>> %s", msg, srcline, idx)
|
||||
end
|
||||
--lx :kill()
|
||||
error(msg)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Sequence parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
-- Input fields:
|
||||
--
|
||||
-- * [builder]: how to build an AST out of sequence parts. let [x] be the list
|
||||
-- of subparser results (keywords are simply omitted). [builder] can be:
|
||||
-- - [nil], in which case the result of parsing is simply [x]
|
||||
-- - a string, which is then put as a tag on [x]
|
||||
-- - a function, which takes [x] as a parameter and returns an AST.
|
||||
--
|
||||
-- * [name]: the name of the parser. Used for debug messages
|
||||
--
|
||||
-- * [transformers]: a list of AST->AST functions, applied in order on ASTs
|
||||
-- returned by the parser.
|
||||
--
|
||||
-- * Table-part entries corresponds to keywords (strings) and subparsers
|
||||
-- (function and callable objects).
|
||||
--
|
||||
-- After creation, the following fields are added:
|
||||
-- * [parse] the parsing function lexer->AST
|
||||
-- * [kind] == "sequence"
|
||||
-- * [name] is set, if it wasn't in the input.
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.sequence (p)
|
||||
M.make_parser ("sequence", p)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Parsing method
|
||||
-------------------------------------------------------------------
|
||||
function p:parse (lx)
|
||||
|
||||
-- Raw parsing:
|
||||
local fli = lx:lineinfo_right()
|
||||
local seq = raw_parse_sequence (lx, self)
|
||||
local lli = lx:lineinfo_left()
|
||||
|
||||
-- Builder application:
|
||||
local builder, tb = self.builder, type (self.builder)
|
||||
if tb == "string" then seq.tag = builder
|
||||
elseif tb == "function" or builder and builder.__call then seq = builder(seq)
|
||||
elseif builder == nil then -- nothing
|
||||
else error ("Invalid builder of type "..tb.." in sequence") end
|
||||
seq = transform (seq, self, fli, lli)
|
||||
assert (not seq or seq.lineinfo)
|
||||
return seq
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Construction
|
||||
-------------------------------------------------------------------
|
||||
-- Try to build a proper name
|
||||
if p.name then
|
||||
-- don't touch existing name
|
||||
elseif type(p[1])=="string" then -- find name based on 1st keyword
|
||||
if #p==1 then p.name=p[1]
|
||||
elseif type(p[#p])=="string" then
|
||||
p.name = p[1] .. " ... " .. p[#p]
|
||||
else p.name = p[1] .. " ..." end
|
||||
else -- can't find a decent name
|
||||
p.name = "unnamed_sequence"
|
||||
end
|
||||
|
||||
return p
|
||||
end --</sequence>
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Multiple, keyword-driven, sequence parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
-- in [p], useful fields are:
|
||||
--
|
||||
-- * [transformers]: as usual
|
||||
--
|
||||
-- * [name]: as usual
|
||||
--
|
||||
-- * Table-part entries must be sequence parsers, or tables which can
|
||||
-- be turned into a sequence parser by [gg.sequence]. These
|
||||
-- sequences must start with a keyword, and this initial keyword
|
||||
-- must be different for each sequence. The table-part entries will
|
||||
-- be removed after [gg.multisequence] returns.
|
||||
--
|
||||
-- * [default]: the parser to run if the next keyword in the lexer is
|
||||
-- none of the registered initial keywords. If there's no default
|
||||
-- parser and no suitable initial keyword, the multisequence parser
|
||||
-- simply returns [false].
|
||||
--
|
||||
-- After creation, the following fields are added:
|
||||
--
|
||||
-- * [parse] the parsing function lexer->AST
|
||||
--
|
||||
-- * [sequences] the table of sequences, indexed by initial keywords.
|
||||
--
|
||||
-- * [add] method takes a sequence parser or a config table for
|
||||
-- [gg.sequence], and adds/replaces the corresponding sequence
|
||||
-- parser. If the keyword was already used, the former sequence is
|
||||
-- removed and a warning is issued.
|
||||
--
|
||||
-- * [get] method returns a sequence by its initial keyword
|
||||
--
|
||||
-- * [kind] == "multisequence"
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.multisequence (p)
|
||||
M.make_parser ("multisequence", p)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Add a sequence (might be just a config table for [gg.sequence])
|
||||
-------------------------------------------------------------------
|
||||
function p :add (s)
|
||||
-- compile if necessary:
|
||||
local keyword = type(s)=='table' and s[1]
|
||||
if type(s)=='table' and not M.is_parser(s) then M.sequence(s) end
|
||||
if M.is_parser(s)~='sequence' or type(keyword)~='string' then
|
||||
if self.default then -- two defaults
|
||||
error ("In a multisequence parser, all but one sequences "..
|
||||
"must start with a keyword")
|
||||
else self.default = s end -- first default
|
||||
else
|
||||
if self.sequences[keyword] then -- duplicate keyword
|
||||
-- TODO: warn that initial keyword `keyword` is overloaded in multiseq
|
||||
end
|
||||
self.sequences[keyword] = s
|
||||
end
|
||||
end -- </multisequence.add>
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Get the sequence starting with this keyword. [kw :: string]
|
||||
-------------------------------------------------------------------
|
||||
function p :get (kw) return self.sequences [kw] end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Remove the sequence starting with keyword [kw :: string]
|
||||
-------------------------------------------------------------------
|
||||
function p :del (kw)
|
||||
if not self.sequences[kw] then
|
||||
-- TODO: warn that we try to delete a non-existent entry
|
||||
end
|
||||
local removed = self.sequences[kw]
|
||||
self.sequences[kw] = nil
|
||||
return removed
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Parsing method
|
||||
-------------------------------------------------------------------
|
||||
function p :parse (lx)
|
||||
local fli = lx:lineinfo_right()
|
||||
local x = raw_parse_multisequence (lx, self.sequences, self.default)
|
||||
local lli = lx:lineinfo_left()
|
||||
return transform (x, self, fli, lli)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Construction
|
||||
-------------------------------------------------------------------
|
||||
-- Register the sequences passed to the constructor. They're going
|
||||
-- from the array part of the parser to the hash part of field
|
||||
-- [sequences]
|
||||
p.sequences = { }
|
||||
for i=1, #p do p :add (p[i]); p[i] = nil end
|
||||
|
||||
-- FIXME: why is this commented out?
|
||||
--if p.default and not is_parser(p.default) then sequence(p.default) end
|
||||
return p
|
||||
end --</multisequence>
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Expression parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Expression configuration relies on three tables: [prefix], [infix]
|
||||
-- and [suffix]. Moreover, the primary parser can be replaced by a
|
||||
-- table: in this case the [primary] table will be passed to
|
||||
-- [gg.multisequence] to create a parser.
|
||||
--
|
||||
-- Each of these tables is a modified multisequence parser: the
|
||||
-- differences with respect to regular multisequence config tables are:
|
||||
--
|
||||
-- * the builder takes specific parameters:
|
||||
-- - for [prefix], it takes the result of the prefix sequence parser,
|
||||
-- and the prefixed expression
|
||||
-- - for [infix], it takes the left-hand-side expression, the results
|
||||
-- of the infix sequence parser, and the right-hand-side expression.
|
||||
-- - for [suffix], it takes the suffixed expression, and the result
|
||||
-- of the suffix sequence parser.
|
||||
--
|
||||
-- * the default field is a list, with parameters:
|
||||
-- - [parser] the raw parsing function
|
||||
-- - [transformers], as usual
|
||||
-- - [prec], the operator's precedence
|
||||
-- - [assoc] for [infix] table, the operator's associativity, which
|
||||
-- can be "left", "right" or "flat" (default to left)
|
||||
--
|
||||
-- In [p], useful fields are:
|
||||
-- * [transformers]: as usual
|
||||
-- * [name]: as usual
|
||||
-- * [primary]: the atomic expression parser, or a multisequence config
|
||||
-- table (mandatory)
|
||||
-- * [prefix]: prefix operators config table, see above.
|
||||
-- * [infix]: infix operators config table, see above.
|
||||
-- * [suffix]: suffix operators config table, see above.
|
||||
--
|
||||
-- After creation, these fields are added:
|
||||
-- * [kind] == "expr"
|
||||
-- * [parse] as usual
|
||||
-- * each table is turned into a multisequence, and therefore has an
|
||||
-- [add] method
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.expr (p)
|
||||
M.make_parser ("expr", p)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- parser method.
|
||||
-- In addition to the lexer, it takes an optional precedence:
|
||||
-- it won't read expressions whose precedence is lower or equal
|
||||
-- to [prec].
|
||||
-------------------------------------------------------------------
|
||||
function p :parse (lx, prec)
|
||||
prec = prec or 0
|
||||
|
||||
------------------------------------------------------
|
||||
-- Extract the right parser and the corresponding
|
||||
-- options table, for (pre|in|suff)fix operators.
|
||||
-- Options include prec, assoc, transformers.
|
||||
------------------------------------------------------
|
||||
local function get_parser_info (tab)
|
||||
local p2 = tab :get (lx :is_keyword (lx :peek()))
|
||||
if p2 then -- keyword-based sequence found
|
||||
local function parser(lx) return raw_parse_sequence(lx, p2) end
|
||||
return parser, p2
|
||||
else -- Got to use the default parser
|
||||
local d = tab.default
|
||||
if d then return d.parse or d.parser, d
|
||||
else return false, false end
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- Look for a prefix sequence. Multiple prefixes are
|
||||
-- handled through the recursive [p.parse] call.
|
||||
-- Notice the double-transform: one for the primary
|
||||
-- expr, and one for the one with the prefix op.
|
||||
------------------------------------------------------
|
||||
local function handle_prefix ()
|
||||
local fli = lx :lineinfo_right()
|
||||
local p2_func, p2 = get_parser_info (self.prefix)
|
||||
local op = p2_func and p2_func (lx)
|
||||
if op then -- Keyword-based sequence found
|
||||
local ili = lx :lineinfo_right() -- Intermediate LineInfo
|
||||
local e = p2.builder (op, self :parse (lx, p2.prec))
|
||||
local lli = lx :lineinfo_left()
|
||||
return transform (transform (e, p2, ili, lli), self, fli, lli)
|
||||
else -- No prefix found, get a primary expression
|
||||
local e = self.primary(lx)
|
||||
local lli = lx :lineinfo_left()
|
||||
return transform (e, self, fli, lli)
|
||||
end
|
||||
end --</expr.parse.handle_prefix>
|
||||
|
||||
------------------------------------------------------
|
||||
-- Look for an infix sequence+right-hand-side operand.
|
||||
-- Return the whole binary expression result,
|
||||
-- or false if no operator was found.
|
||||
------------------------------------------------------
|
||||
local function handle_infix (e)
|
||||
local p2_func, p2 = get_parser_info (self.infix)
|
||||
if not p2 then return false end
|
||||
|
||||
-----------------------------------------
|
||||
-- Handle flattening operators: gather all operands
|
||||
-- of the series in [list]; when a different operator
|
||||
-- is found, stop, build from [list], [transform] and
|
||||
-- return.
|
||||
-----------------------------------------
|
||||
if (not p2.prec or p2.prec>prec) and p2.assoc=="flat" then
|
||||
local fli = lx:lineinfo_right()
|
||||
local pflat, list = p2, { e }
|
||||
repeat
|
||||
local op = p2_func(lx)
|
||||
if not op then break end
|
||||
table.insert (list, self:parse (lx, p2.prec))
|
||||
local _ -- We only care about checking that p2==pflat
|
||||
_, p2 = get_parser_info (self.infix)
|
||||
until p2 ~= pflat
|
||||
local e2 = pflat.builder (list)
|
||||
local lli = lx:lineinfo_left()
|
||||
return transform (transform (e2, pflat, fli, lli), self, fli, lli)
|
||||
|
||||
-----------------------------------------
|
||||
-- Handle regular infix operators: [e] the LHS is known,
|
||||
-- just gather the operator and [e2] the RHS.
|
||||
-- Result goes in [e3].
|
||||
-----------------------------------------
|
||||
elseif p2.prec and p2.prec>prec or
|
||||
p2.prec==prec and p2.assoc=="right" then
|
||||
local fli = e.lineinfo.first -- lx:lineinfo_right()
|
||||
local op = p2_func(lx)
|
||||
if not op then return false end
|
||||
local e2 = self:parse (lx, p2.prec)
|
||||
local e3 = p2.builder (e, op, e2)
|
||||
local lli = lx:lineinfo_left()
|
||||
return transform (transform (e3, p2, fli, lli), self, fli, lli)
|
||||
|
||||
-----------------------------------------
|
||||
-- Check for non-associative operators, and complain if applicable.
|
||||
-----------------------------------------
|
||||
elseif p2.assoc=="none" and p2.prec==prec then
|
||||
M.parse_error (lx, "non-associative operator!")
|
||||
|
||||
-----------------------------------------
|
||||
-- No infix operator suitable at that precedence
|
||||
-----------------------------------------
|
||||
else return false end
|
||||
|
||||
end --</expr.parse.handle_infix>
|
||||
|
||||
------------------------------------------------------
|
||||
-- Look for a suffix sequence.
|
||||
-- Return the result of suffix operator on [e],
|
||||
-- or false if no operator was found.
|
||||
------------------------------------------------------
|
||||
local function handle_suffix (e)
|
||||
-- FIXME bad fli, must take e.lineinfo.first
|
||||
local p2_func, p2 = get_parser_info (self.suffix)
|
||||
if not p2 then return false end
|
||||
if not p2.prec or p2.prec>=prec then
|
||||
--local fli = lx:lineinfo_right()
|
||||
local fli = e.lineinfo.first
|
||||
local op = p2_func(lx)
|
||||
if not op then return false end
|
||||
local lli = lx:lineinfo_left()
|
||||
e = p2.builder (e, op)
|
||||
e = transform (transform (e, p2, fli, lli), self, fli, lli)
|
||||
return e
|
||||
end
|
||||
return false
|
||||
end --</expr.parse.handle_suffix>
|
||||
|
||||
------------------------------------------------------
|
||||
-- Parser body: read suffix and (infix+operand)
|
||||
-- extensions as long as we're able to fetch more at
|
||||
-- this precedence level.
|
||||
------------------------------------------------------
|
||||
local e = handle_prefix()
|
||||
repeat
|
||||
local x = handle_suffix (e); e = x or e
|
||||
local y = handle_infix (e); e = y or e
|
||||
until not (x or y)
|
||||
|
||||
-- No transform: it already happened in operators handling
|
||||
return e
|
||||
end --</expr.parse>
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Construction
|
||||
-------------------------------------------------------------------
|
||||
if not p.primary then p.primary=p[1]; p[1]=nil end
|
||||
for _, t in ipairs{ "primary", "prefix", "infix", "suffix" } do
|
||||
if not p[t] then p[t] = { } end
|
||||
if not M.is_parser(p[t]) then M.multisequence(p[t]) end
|
||||
end
|
||||
function p:add(...) return self.primary:add(...) end
|
||||
return p
|
||||
end --</expr>
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- List parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
-- In [p], the following fields can be provided in input:
|
||||
--
|
||||
-- * [builder]: takes list of subparser results, returns AST
|
||||
-- * [transformers]: as usual
|
||||
-- * [name]: as usual
|
||||
--
|
||||
-- * [terminators]: list of strings representing the keywords which
|
||||
-- might mark the end of the list. When non-empty, the list is
|
||||
-- allowed to be empty. A string is treated as a single-element
|
||||
-- table, whose element is that string, e.g. ["do"] is the same as
|
||||
-- [{"do"}].
|
||||
--
|
||||
-- * [separators]: list of strings representing the keywords which can
|
||||
-- separate elements of the list. When non-empty, one of these
|
||||
-- keyword has to be found between each element. Lack of a separator
|
||||
-- indicates the end of the list. A string is treated as a
|
||||
-- single-element table, whose element is that string, e.g. ["do"]
|
||||
-- is the same as [{"do"}]. If [terminators] is empty/nil, then
|
||||
-- [separators] has to be non-empty.
|
||||
--
|
||||
-- After creation, the following fields are added:
|
||||
-- * [parse] the parsing function lexer->AST
|
||||
-- * [kind] == "list"
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.list (p)
|
||||
M.make_parser ("list", p)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Parsing method
|
||||
-------------------------------------------------------------------
|
||||
function p :parse (lx)
|
||||
|
||||
------------------------------------------------------
|
||||
-- Used to quickly check whether there's a terminator
|
||||
-- or a separator immediately ahead
|
||||
------------------------------------------------------
|
||||
local function peek_is_in (keywords)
|
||||
return keywords and lx:is_keyword(lx:peek(), unpack(keywords)) end
|
||||
|
||||
local x = { }
|
||||
local fli = lx :lineinfo_right()
|
||||
|
||||
-- if there's a terminator to start with, don't bother trying
|
||||
local is_empty_list = self.terminators and (peek_is_in (self.terminators) or lx:peek().tag=="Eof")
|
||||
if not is_empty_list then
|
||||
repeat
|
||||
local item = self.primary(lx)
|
||||
table.insert (x, item) -- read one element
|
||||
until
|
||||
-- There's a separator list specified, and next token isn't in it.
|
||||
-- Otherwise, consume it with [lx:next()]
|
||||
self.separators and not(peek_is_in (self.separators) and lx:next()) or
|
||||
-- Terminator token ahead
|
||||
peek_is_in (self.terminators) or
|
||||
-- Last reason: end of file reached
|
||||
lx:peek().tag=="Eof"
|
||||
end
|
||||
|
||||
local lli = lx:lineinfo_left()
|
||||
|
||||
-- Apply the builder. It can be a string, or a callable value,
|
||||
-- or simply nothing.
|
||||
local b = self.builder
|
||||
if b then
|
||||
if type(b)=="string" then x.tag = b -- b is a string, use it as a tag
|
||||
elseif type(b)=="function" then x=b(x)
|
||||
else
|
||||
local bmt = getmetatable(b)
|
||||
if bmt and bmt.__call then x=b(x) end
|
||||
end
|
||||
end
|
||||
return transform (x, self, fli, lli)
|
||||
end --</list.parse>
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Construction
|
||||
-------------------------------------------------------------------
|
||||
if not p.primary then p.primary = p[1]; p[1] = nil end
|
||||
if type(p.terminators) == "string" then p.terminators = { p.terminators }
|
||||
elseif p.terminators and #p.terminators == 0 then p.terminators = nil end
|
||||
if type(p.separators) == "string" then p.separators = { p.separators }
|
||||
elseif p.separators and #p.separators == 0 then p.separators = nil end
|
||||
|
||||
return p
|
||||
end --</list>
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Keyword-conditioned parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Only apply a parser if a given keyword is found. The result of
|
||||
-- [gg.onkeyword] parser is the result of the subparser (modulo
|
||||
-- [transformers] applications).
|
||||
--
|
||||
-- lineinfo: the keyword is *not* included in the boundaries of the
|
||||
-- resulting lineinfo. A review of all usages of gg.onkeyword() in the
|
||||
-- implementation of metalua has shown that it was the appropriate choice
|
||||
-- in every case.
|
||||
--
|
||||
-- Input fields:
|
||||
--
|
||||
-- * [name]: as usual
|
||||
--
|
||||
-- * [transformers]: as usual
|
||||
--
|
||||
-- * [peek]: if non-nil, the conditioning keyword is left in the lexeme
|
||||
-- stream instead of being consumed.
|
||||
--
|
||||
-- * [primary]: the subparser.
|
||||
--
|
||||
-- * [keywords]: list of strings representing triggering keywords.
|
||||
--
|
||||
-- * Table-part entries can contain strings, and/or exactly one parser.
|
||||
-- Strings are put in [keywords], and the parser is put in [primary].
|
||||
--
|
||||
-- After the call, the following fields will be set:
|
||||
--
|
||||
-- * [parse] the parsing method
|
||||
-- * [kind] == "onkeyword"
|
||||
-- * [primary]
|
||||
-- * [keywords]
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.onkeyword (p)
|
||||
M.make_parser ("onkeyword", p)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Parsing method
|
||||
-------------------------------------------------------------------
|
||||
function p :parse (lx)
|
||||
if lx :is_keyword (lx:peek(), unpack(self.keywords)) then
|
||||
local fli = lx:lineinfo_right()
|
||||
if not self.peek then lx:next() end
|
||||
local content = self.primary (lx)
|
||||
local lli = lx:lineinfo_left()
|
||||
local li = content.lineinfo or { }
|
||||
fli, lli = li.first or fli, li.last or lli
|
||||
return transform (content, p, fli, lli)
|
||||
else return false end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Construction
|
||||
-------------------------------------------------------------------
|
||||
if not p.keywords then p.keywords = { } end
|
||||
for _, x in ipairs(p) do
|
||||
if type(x)=="string" then table.insert (p.keywords, x)
|
||||
else assert (not p.primary and M.is_parser (x)); p.primary = x end
|
||||
end
|
||||
assert (next (p.keywords), "Missing trigger keyword in gg.onkeyword")
|
||||
assert (p.primary, 'no primary parser in gg.onkeyword')
|
||||
return p
|
||||
end --</onkeyword>
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Optional keyword consummer pseudo-parser generator
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- This doesn't return a real parser, just a function. That function parses
|
||||
-- one of the keywords passed as parameters, and returns it. It returns
|
||||
-- [false] if no matching keyword is found.
|
||||
--
|
||||
-- Notice that tokens returned by lexer already carry lineinfo, therefore
|
||||
-- there's no need to add them, as done usually through transform() calls.
|
||||
-------------------------------------------------------------------------------
|
||||
function M.optkeyword (...)
|
||||
local args = {...}
|
||||
if type (args[1]) == "table" then
|
||||
assert (#args == 1)
|
||||
args = args[1]
|
||||
end
|
||||
for _, v in ipairs(args) do assert (type(v)=="string") end
|
||||
return function (lx)
|
||||
local x = lx:is_keyword (lx:peek(), unpack (args))
|
||||
if x then lx:next(); return x
|
||||
else return false end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Run a parser with a special lexer
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- This doesn't return a real parser, just a function.
|
||||
-- First argument is the lexer class to be used with the parser,
|
||||
-- 2nd is the parser itself.
|
||||
-- The resulting parser returns whatever the argument parser does.
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
function M.with_lexer(new_lexer, parser)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Most gg functions take their parameters in a table, so it's
|
||||
-- better to silently accept when with_lexer{ } is called with
|
||||
-- its arguments in a list:
|
||||
-------------------------------------------------------------------
|
||||
if not parser and #new_lexer==2 and type(new_lexer[1])=='table' then
|
||||
return M.with_lexer(unpack(new_lexer))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Save the current lexer, switch it for the new one, run the parser,
|
||||
-- restore the previous lexer, even if the parser caused an error.
|
||||
-------------------------------------------------------------------
|
||||
return function (lx)
|
||||
local old_lexer = getmetatable(lx)
|
||||
lx:sync()
|
||||
setmetatable(lx, new_lexer)
|
||||
local status, result = pcall(parser, lx)
|
||||
lx:sync()
|
||||
setmetatable(lx, old_lexer)
|
||||
if status then return result else error(result) end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--
|
||||
-- Make sure a parser is used and returns successfully.
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
function M.nonempty(primary)
|
||||
local p = M.make_parser('non-empty list', { primary = primary, name=primary.name })
|
||||
function p :parse (lx)
|
||||
local fli = lx:lineinfo_right()
|
||||
local content = self.primary (lx)
|
||||
local lli = lx:lineinfo_left()
|
||||
local li = content.lineinfo or { }
|
||||
fli, lli = li.first or fli, li.last or lli
|
||||
if #content == 0 then
|
||||
M.parse_error (lx, "`%s' must not be empty.", self.name or "list")
|
||||
else
|
||||
return transform (content, self, fli, lli)
|
||||
end
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
local FUTURE_MT = { }
|
||||
function FUTURE_MT:__tostring() return "<Proxy parser module>" end
|
||||
function FUTURE_MT:__newindex(key, value) error "don't write in futures" end
|
||||
function FUTURE_MT :__index (parser_name)
|
||||
return function(...)
|
||||
local p, m = rawget(self, '__path'), self.__module
|
||||
if p then for _, name in ipairs(p) do
|
||||
m=rawget(m, name)
|
||||
if not m then error ("Submodule '"..name.."' undefined") end
|
||||
end end
|
||||
local f = rawget(m, parser_name)
|
||||
if not f then error ("Parser '"..parser_name.."' undefined") end
|
||||
return f(...)
|
||||
end
|
||||
end
|
||||
|
||||
function M.future(module, ...)
|
||||
checks('table')
|
||||
local path = ... and {...}
|
||||
if path then for _, x in ipairs(path) do
|
||||
assert(type(x)=='string', "Bad future arg")
|
||||
end end
|
||||
local self = { __module = module,
|
||||
__path = path }
|
||||
return setmetatable(self, FUTURE_MT)
|
||||
end
|
||||
|
||||
return M
|
||||
672
Utils/luarocks/share/lua/5.1/metalua/grammar/lexer.lua
Normal file
672
Utils/luarocks/share/lua/5.1/metalua/grammar/lexer.lua
Normal file
@@ -0,0 +1,672 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local checks = require 'checks'
|
||||
|
||||
local M = { }
|
||||
|
||||
local lexer = { alpha={ }, sym={ } }
|
||||
lexer.__index=lexer
|
||||
lexer.__type='lexer.stream'
|
||||
|
||||
M.lexer = lexer
|
||||
|
||||
|
||||
local debugf = function() end
|
||||
-- local debugf=printf
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Some locale settings produce bad results, e.g. French locale
|
||||
-- expect float numbers to use commas instead of periods.
|
||||
-- TODO: change number parser into something loclae-independent,
|
||||
-- locales are nasty.
|
||||
----------------------------------------------------------------------
|
||||
os.setlocale('C')
|
||||
|
||||
local MT = { }
|
||||
|
||||
M.metatables=MT
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Create a new metatable, for a new class of objects.
|
||||
----------------------------------------------------------------------
|
||||
local function new_metatable(name)
|
||||
local mt = { __type = 'lexer.'..name };
|
||||
mt.__index = mt
|
||||
MT[name] = mt
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Position: represent a point in a source file.
|
||||
----------------------------------------------------------------------
|
||||
new_metatable 'position'
|
||||
|
||||
local position_idx=1
|
||||
|
||||
function M.new_position(line, column, offset, source)
|
||||
checks('number', 'number', 'number', 'string')
|
||||
local id = position_idx; position_idx = position_idx+1
|
||||
return setmetatable({line=line, column=column, offset=offset,
|
||||
source=source, id=id}, MT.position)
|
||||
end
|
||||
|
||||
function MT.position :__tostring()
|
||||
return string.format("<%s%s|L%d|C%d|K%d>",
|
||||
self.comments and "C|" or "",
|
||||
self.source, self.line, self.column, self.offset)
|
||||
end
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Position factory: convert offsets into line/column/offset positions.
|
||||
----------------------------------------------------------------------
|
||||
new_metatable 'position_factory'
|
||||
|
||||
function M.new_position_factory(src, src_name)
|
||||
-- assert(type(src)=='string')
|
||||
-- assert(type(src_name)=='string')
|
||||
local lines = { 1 }
|
||||
for offset in src :gmatch '\n()' do table.insert(lines, offset) end
|
||||
local max = #src+1
|
||||
table.insert(lines, max+1) -- +1 includes Eof
|
||||
return setmetatable({ src_name=src_name, line2offset=lines, max=max },
|
||||
MT.position_factory)
|
||||
end
|
||||
|
||||
function MT.position_factory :get_position (offset)
|
||||
-- assert(type(offset)=='number')
|
||||
assert(offset<=self.max)
|
||||
local line2offset = self.line2offset
|
||||
local left = self.last_left or 1
|
||||
if offset<line2offset[left] then left=1 end
|
||||
local right = left+1
|
||||
if line2offset[right]<=offset then right = right+1 end
|
||||
if line2offset[right]<=offset then right = #line2offset end
|
||||
while true do
|
||||
-- print (" trying lines "..left.."/"..right..", offsets "..line2offset[left]..
|
||||
-- "/"..line2offset[right].." for offset "..offset)
|
||||
-- assert(line2offset[left]<=offset)
|
||||
-- assert(offset<line2offset[right])
|
||||
-- assert(left<right)
|
||||
if left+1==right then break end
|
||||
local middle = math.floor((left+right)/2)
|
||||
if line2offset[middle]<=offset then left=middle else right=middle end
|
||||
end
|
||||
-- assert(left+1==right)
|
||||
-- printf("found that offset %d is between %d and %d, hence on line %d",
|
||||
-- offset, line2offset[left], line2offset[right], left)
|
||||
local line = left
|
||||
local column = offset - line2offset[line] + 1
|
||||
self.last_left = left
|
||||
return M.new_position(line, column, offset, self.src_name)
|
||||
end
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Lineinfo: represent a node's range in a source file;
|
||||
-- embed information about prefix and suffix comments.
|
||||
----------------------------------------------------------------------
|
||||
new_metatable 'lineinfo'
|
||||
|
||||
function M.new_lineinfo(first, last)
|
||||
checks('lexer.position', 'lexer.position')
|
||||
return setmetatable({first=first, last=last}, MT.lineinfo)
|
||||
end
|
||||
|
||||
function MT.lineinfo :__tostring()
|
||||
local fli, lli = self.first, self.last
|
||||
local line = fli.line; if line~=lli.line then line =line ..'-'..lli.line end
|
||||
local column = fli.column; if column~=lli.column then column=column..'-'..lli.column end
|
||||
local offset = fli.offset; if offset~=lli.offset then offset=offset..'-'..lli.offset end
|
||||
return string.format("<%s%s|L%s|C%s|K%s%s>",
|
||||
fli.comments and "C|" or "",
|
||||
fli.source, line, column, offset,
|
||||
lli.comments and "|C" or "")
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Token: atomic Lua language element, with a category, a content,
|
||||
-- and some lineinfo relating it to its original source.
|
||||
----------------------------------------------------------------------
|
||||
new_metatable 'token'
|
||||
|
||||
function M.new_token(tag, content, lineinfo)
|
||||
--printf("TOKEN `%s{ %q, lineinfo = %s} boundaries %d, %d",
|
||||
-- tag, content, tostring(lineinfo), lineinfo.first.id, lineinfo.last.id)
|
||||
return setmetatable({tag=tag, lineinfo=lineinfo, content}, MT.token)
|
||||
end
|
||||
|
||||
function MT.token :__tostring()
|
||||
--return string.format("`%s{ %q, %s }", self.tag, self[1], tostring(self.lineinfo))
|
||||
return string.format("`%s %q", self.tag, self[1])
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Comment: series of comment blocks with associated lineinfo.
|
||||
-- To be attached to the tokens just before and just after them.
|
||||
----------------------------------------------------------------------
|
||||
new_metatable 'comment'
|
||||
|
||||
function M.new_comment(lines)
|
||||
local first = lines[1].lineinfo.first
|
||||
local last = lines[#lines].lineinfo.last
|
||||
local lineinfo = M.new_lineinfo(first, last)
|
||||
return setmetatable({lineinfo=lineinfo, unpack(lines)}, MT.comment)
|
||||
end
|
||||
|
||||
function MT.comment :text()
|
||||
local last_line = self[1].lineinfo.last.line
|
||||
local acc = { }
|
||||
for i, line in ipairs(self) do
|
||||
local nreturns = line.lineinfo.first.line - last_line
|
||||
table.insert(acc, ("\n"):rep(nreturns))
|
||||
table.insert(acc, line[1])
|
||||
end
|
||||
return table.concat(acc)
|
||||
end
|
||||
|
||||
function M.new_comment_line(text, lineinfo, nequals)
|
||||
checks('string', 'lexer.lineinfo', '?number')
|
||||
return { lineinfo = lineinfo, text, nequals }
|
||||
end
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Patterns used by [lexer :extract] to decompose the raw string into
|
||||
-- correctly tagged tokens.
|
||||
----------------------------------------------------------------------
|
||||
lexer.patterns = {
|
||||
spaces = "^[ \r\n\t]*()",
|
||||
short_comment = "^%-%-([^\n]*)\n?()",
|
||||
--final_short_comment = "^%-%-([^\n]*)()$",
|
||||
long_comment = "^%-%-%[(=*)%[\n?(.-)%]%1%]()",
|
||||
long_string = "^%[(=*)%[\n?(.-)%]%1%]()",
|
||||
number_mantissa = { "^%d+%.?%d*()", "^%d*%.%d+()" },
|
||||
number_mantissa_hex = { "^%x+%.?%x*()", "^%x*%.%x+()" }, --Lua5.1 and Lua5.2
|
||||
number_exponant = "^[eE][%+%-]?%d+()",
|
||||
number_exponant_hex = "^[pP][%+%-]?%d+()", --Lua5.2
|
||||
number_hex = "^0[xX]()",
|
||||
word = "^([%a_][%w_]*)()"
|
||||
}
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- unescape a whole string, applying [unesc_digits] and
|
||||
-- [unesc_letter] as many times as required.
|
||||
----------------------------------------------------------------------
|
||||
local function unescape_string (s)
|
||||
|
||||
-- Turn the digits of an escape sequence into the corresponding
|
||||
-- character, e.g. [unesc_digits("123") == string.char(123)].
|
||||
local function unesc_digits (backslashes, digits)
|
||||
if #backslashes%2==0 then
|
||||
-- Even number of backslashes, they escape each other, not the digits.
|
||||
-- Return them so that unesc_letter() can treat them
|
||||
return backslashes..digits
|
||||
else
|
||||
-- Remove the odd backslash, which escapes the number sequence.
|
||||
-- The rest will be returned and parsed by unesc_letter()
|
||||
backslashes = backslashes :sub (1,-2)
|
||||
end
|
||||
local k, j, i = digits :reverse() :byte(1, 3)
|
||||
local z = string.byte "0"
|
||||
local code = (k or z) + 10*(j or z) + 100*(i or z) - 111*z
|
||||
if code > 255 then
|
||||
error ("Illegal escape sequence '\\"..digits..
|
||||
"' in string: ASCII codes must be in [0..255]")
|
||||
end
|
||||
local c = string.char (code)
|
||||
if c == '\\' then c = '\\\\' end -- parsed by unesc_letter (test: "\092b" --> "\\b")
|
||||
return backslashes..c
|
||||
end
|
||||
|
||||
-- Turn hex digits of escape sequence into char.
|
||||
local function unesc_hex(backslashes, digits)
|
||||
if #backslashes%2==0 then
|
||||
return backslashes..'x'..digits
|
||||
else
|
||||
backslashes = backslashes :sub (1,-2)
|
||||
end
|
||||
local c = string.char(tonumber(digits,16))
|
||||
if c == '\\' then c = '\\\\' end -- parsed by unesc_letter (test: "\x5cb" --> "\\b")
|
||||
return backslashes..c
|
||||
end
|
||||
|
||||
-- Handle Lua 5.2 \z sequences
|
||||
local function unesc_z(backslashes, more)
|
||||
if #backslashes%2==0 then
|
||||
return backslashes..more
|
||||
else
|
||||
return backslashes :sub (1,-2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Take a letter [x], and returns the character represented by the
|
||||
-- sequence ['\\'..x], e.g. [unesc_letter "n" == "\n"].
|
||||
local function unesc_letter(x)
|
||||
local t = {
|
||||
a = "\a", b = "\b", f = "\f",
|
||||
n = "\n", r = "\r", t = "\t", v = "\v",
|
||||
["\\"] = "\\", ["'"] = "'", ['"'] = '"', ["\n"] = "\n" }
|
||||
return t[x] or x
|
||||
end
|
||||
|
||||
s = s: gsub ("(\\+)(z%s*)", unesc_z) -- Lua 5.2
|
||||
s = s: gsub ("(\\+)([0-9][0-9]?[0-9]?)", unesc_digits)
|
||||
s = s: gsub ("(\\+)x([0-9a-fA-F][0-9a-fA-F])", unesc_hex) -- Lua 5.2
|
||||
s = s: gsub ("\\(%D)",unesc_letter)
|
||||
return s
|
||||
end
|
||||
|
||||
lexer.extractors = {
|
||||
"extract_long_comment", "extract_short_comment",
|
||||
"extract_short_string", "extract_word", "extract_number",
|
||||
"extract_long_string", "extract_symbol" }
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Really extract next token from the raw string
|
||||
-- (and update the index).
|
||||
-- loc: offset of the position just after spaces and comments
|
||||
-- previous_i: offset in src before extraction began
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract ()
|
||||
local attached_comments = { }
|
||||
local function gen_token(...)
|
||||
local token = M.new_token(...)
|
||||
if #attached_comments>0 then -- attach previous comments to token
|
||||
local comments = M.new_comment(attached_comments)
|
||||
token.lineinfo.first.comments = comments
|
||||
if self.lineinfo_last_extracted then
|
||||
self.lineinfo_last_extracted.comments = comments
|
||||
end
|
||||
attached_comments = { }
|
||||
end
|
||||
token.lineinfo.first.facing = self.lineinfo_last_extracted
|
||||
self.lineinfo_last_extracted.facing = assert(token.lineinfo.first)
|
||||
self.lineinfo_last_extracted = assert(token.lineinfo.last)
|
||||
return token
|
||||
end
|
||||
while true do -- loop until a non-comment token is found
|
||||
|
||||
-- skip whitespaces
|
||||
self.i = self.src:match (self.patterns.spaces, self.i)
|
||||
if self.i>#self.src then
|
||||
local fli = self.posfact :get_position (#self.src+1)
|
||||
local lli = self.posfact :get_position (#self.src+1) -- ok?
|
||||
local tok = gen_token("Eof", "eof", M.new_lineinfo(fli, lli))
|
||||
tok.lineinfo.last.facing = lli
|
||||
return tok
|
||||
end
|
||||
local i_first = self.i -- loc = position after whitespaces
|
||||
|
||||
-- try every extractor until a token is found
|
||||
for _, extractor in ipairs(self.extractors) do
|
||||
local tag, content, xtra = self [extractor] (self)
|
||||
if tag then
|
||||
local fli = self.posfact :get_position (i_first)
|
||||
local lli = self.posfact :get_position (self.i-1)
|
||||
local lineinfo = M.new_lineinfo(fli, lli)
|
||||
if tag=='Comment' then
|
||||
local prev_comment = attached_comments[#attached_comments]
|
||||
if not xtra -- new comment is short
|
||||
and prev_comment and not prev_comment[2] -- prev comment is short
|
||||
and prev_comment.lineinfo.last.line+1==fli.line then -- adjascent lines
|
||||
-- concat with previous comment
|
||||
prev_comment[1] = prev_comment[1].."\n"..content -- TODO quadratic, BAD!
|
||||
prev_comment.lineinfo.last = lli
|
||||
else -- accumulate comment
|
||||
local comment = M.new_comment_line(content, lineinfo, xtra)
|
||||
table.insert(attached_comments, comment)
|
||||
end
|
||||
break -- back to skipping spaces
|
||||
else -- not a comment: real token, then
|
||||
return gen_token(tag, content, lineinfo)
|
||||
end -- if token is a comment
|
||||
end -- if token found
|
||||
end -- for each extractor
|
||||
end -- while token is a comment
|
||||
end -- :extract()
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract a short comment.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_short_comment()
|
||||
-- TODO: handle final_short_comment
|
||||
local content, j = self.src :match (self.patterns.short_comment, self.i)
|
||||
if content then self.i=j; return 'Comment', content, nil end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract a long comment.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_long_comment()
|
||||
local equals, content, j = self.src:match (self.patterns.long_comment, self.i)
|
||||
if j then self.i = j; return "Comment", content, #equals end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract a '...' or "..." short string.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_short_string()
|
||||
local k = self.src :sub (self.i,self.i) -- first char
|
||||
if k~=[[']] and k~=[["]] then return end -- no match'
|
||||
local i = self.i + 1
|
||||
local j = i
|
||||
while true do
|
||||
local x,y; x, j, y = self.src :match ("([\\\r\n"..k.."])()(.?)", j) -- next interesting char
|
||||
if x == '\\' then
|
||||
if y == 'z' then -- Lua 5.2 \z
|
||||
j = self.src :match ("^%s*()", j+1)
|
||||
else
|
||||
j=j+1 -- escaped char
|
||||
end
|
||||
elseif x == k then break -- end of string
|
||||
else
|
||||
assert (not x or x=='\r' or x=='\n')
|
||||
return nil, 'Unterminated string'
|
||||
end
|
||||
end
|
||||
self.i = j
|
||||
|
||||
return 'String', unescape_string (self.src :sub (i,j-2))
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract Id or Keyword.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_word()
|
||||
local word, j = self.src:match (self.patterns.word, self.i)
|
||||
if word then
|
||||
self.i = j
|
||||
return (self.alpha [word] and 'Keyword' or 'Id'), word
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract Number.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_number()
|
||||
local j = self.src:match(self.patterns.number_hex, self.i)
|
||||
if j then
|
||||
j = self.src:match (self.patterns.number_mantissa_hex[1], j) or
|
||||
self.src:match (self.patterns.number_mantissa_hex[2], j)
|
||||
if j then
|
||||
j = self.src:match (self.patterns.number_exponant_hex, j) or j
|
||||
end
|
||||
else
|
||||
j = self.src:match (self.patterns.number_mantissa[1], self.i) or
|
||||
self.src:match (self.patterns.number_mantissa[2], self.i)
|
||||
if j then
|
||||
j = self.src:match (self.patterns.number_exponant, j) or j
|
||||
end
|
||||
end
|
||||
if not j then return end
|
||||
-- Number found, interpret with tonumber() and return it
|
||||
local str = self.src:sub (self.i, j-1)
|
||||
-- :TODO: tonumber on Lua5.2 floating hex may or may not work on Lua5.1
|
||||
local n = tonumber (str)
|
||||
if not n then error(str.." is not a valid number according to tonumber()") end
|
||||
self.i = j
|
||||
return 'Number', n
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract long string.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_long_string()
|
||||
local _, content, j = self.src :match (self.patterns.long_string, self.i)
|
||||
if j then self.i = j; return 'String', content end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Extract symbol.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :extract_symbol()
|
||||
local k = self.src:sub (self.i,self.i)
|
||||
local symk = self.sym [k] -- symbols starting with `k`
|
||||
if not symk then
|
||||
self.i = self.i + 1
|
||||
return 'Keyword', k
|
||||
end
|
||||
for _, sym in pairs (symk) do
|
||||
if sym == self.src:sub (self.i, self.i + #sym - 1) then
|
||||
self.i = self.i + #sym
|
||||
return 'Keyword', sym
|
||||
end
|
||||
end
|
||||
self.i = self.i+1
|
||||
return 'Keyword', k
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Add a keyword to the list of keywords recognized by the lexer.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :add (w, ...)
|
||||
assert(not ..., "lexer :add() takes only one arg, although possibly a table")
|
||||
if type (w) == "table" then
|
||||
for _, x in ipairs (w) do self :add (x) end
|
||||
else
|
||||
if w:match (self.patterns.word .. "$") then self.alpha [w] = true
|
||||
elseif w:match "^%p%p+$" then
|
||||
local k = w:sub(1,1)
|
||||
local list = self.sym [k]
|
||||
if not list then list = { }; self.sym [k] = list end
|
||||
table.insert (list, w)
|
||||
elseif w:match "^%p$" then return
|
||||
else error "Invalid keyword" end
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Return the [n]th next token, without consuming it.
|
||||
-- [n] defaults to 1. If it goes pass the end of the stream, an EOF
|
||||
-- token is returned.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :peek (n)
|
||||
if not n then n=1 end
|
||||
if n > #self.peeked then
|
||||
for i = #self.peeked+1, n do
|
||||
self.peeked [i] = self :extract()
|
||||
end
|
||||
end
|
||||
return self.peeked [n]
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Return the [n]th next token, removing it as well as the 0..n-1
|
||||
-- previous tokens. [n] defaults to 1. If it goes pass the end of the
|
||||
-- stream, an EOF token is returned.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :next (n)
|
||||
n = n or 1
|
||||
self :peek (n)
|
||||
local a
|
||||
for i=1,n do
|
||||
a = table.remove (self.peeked, 1)
|
||||
-- TODO: is this used anywhere? I think not. a.lineinfo.last may be nil.
|
||||
--self.lastline = a.lineinfo.last.line
|
||||
end
|
||||
self.lineinfo_last_consumed = a.lineinfo.last
|
||||
return a
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Returns an object which saves the stream's current state.
|
||||
----------------------------------------------------------------------
|
||||
-- FIXME there are more fields than that to save
|
||||
function lexer :save () return { self.i; {unpack(self.peeked) } } end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Restore the stream's state, as saved by method [save].
|
||||
----------------------------------------------------------------------
|
||||
-- FIXME there are more fields than that to restore
|
||||
function lexer :restore (s) self.i=s[1]; self.peeked=s[2] end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Resynchronize: cancel any token in self.peeked, by emptying the
|
||||
-- list and resetting the indexes
|
||||
----------------------------------------------------------------------
|
||||
function lexer :sync()
|
||||
local p1 = self.peeked[1]
|
||||
if p1 then
|
||||
local li_first = p1.lineinfo.first
|
||||
if li_first.comments then li_first=li_first.comments.lineinfo.first end
|
||||
self.i = li_first.offset
|
||||
self.column_offset = self.i - li_first.column
|
||||
self.peeked = { }
|
||||
self.attached_comments = p1.lineinfo.first.comments or { }
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Take the source and offset of an old lexer.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :takeover(old)
|
||||
self :sync(); old :sync()
|
||||
for _, field in ipairs{ 'i', 'src', 'attached_comments', 'posfact' } do
|
||||
self[field] = old[field]
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Return the current position in the sources. This position is between
|
||||
-- two tokens, and can be within a space / comment area, and therefore
|
||||
-- have a non-null width. :lineinfo_left() returns the beginning of the
|
||||
-- separation area, :lineinfo_right() returns the end of that area.
|
||||
--
|
||||
-- ____ last consummed token ____ first unconsummed token
|
||||
-- / /
|
||||
-- XXXXX <spaces and comments> YYYYY
|
||||
-- \____ \____
|
||||
-- :lineinfo_left() :lineinfo_right()
|
||||
----------------------------------------------------------------------
|
||||
function lexer :lineinfo_right()
|
||||
return self :peek(1).lineinfo.first
|
||||
end
|
||||
|
||||
function lexer :lineinfo_left()
|
||||
return self.lineinfo_last_consumed
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Create a new lexstream.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :newstream (src_or_stream, name)
|
||||
name = name or "?"
|
||||
if type(src_or_stream)=='table' then -- it's a stream
|
||||
return setmetatable ({ }, self) :takeover (src_or_stream)
|
||||
elseif type(src_or_stream)=='string' then -- it's a source string
|
||||
local src = src_or_stream
|
||||
local pos1 = M.new_position(1, 1, 1, name)
|
||||
local stream = {
|
||||
src_name = name; -- Name of the file
|
||||
src = src; -- The source, as a single string
|
||||
peeked = { }; -- Already peeked, but not discarded yet, tokens
|
||||
i = 1; -- Character offset in src
|
||||
attached_comments = { },-- comments accumulator
|
||||
lineinfo_last_extracted = pos1,
|
||||
lineinfo_last_consumed = pos1,
|
||||
posfact = M.new_position_factory (src_or_stream, name)
|
||||
}
|
||||
setmetatable (stream, self)
|
||||
|
||||
-- Skip initial sharp-bang for Unix scripts
|
||||
-- FIXME: redundant with mlp.chunk()
|
||||
if src and src :match "^#!" then
|
||||
local endofline = src :find "\n"
|
||||
stream.i = endofline and (endofline + 1) or #src
|
||||
end
|
||||
return stream
|
||||
else
|
||||
assert(false, ":newstream() takes a source string or a stream, not a "..
|
||||
type(src_or_stream))
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- If there's no ... args, return the token a (whose truth value is
|
||||
-- true) if it's a `Keyword{ }, or nil. If there are ... args, they
|
||||
-- have to be strings. if the token a is a keyword, and it's content
|
||||
-- is one of the ... args, then returns it (it's truth value is
|
||||
-- true). If no a keyword or not in ..., return nil.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :is_keyword (a, ...)
|
||||
if not a or a.tag ~= "Keyword" then return false end
|
||||
local words = {...}
|
||||
if #words == 0 then return a[1] end
|
||||
for _, w in ipairs (words) do
|
||||
if w == a[1] then return w end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Cause an error if the next token isn't a keyword whose content
|
||||
-- is listed among ... args (which have to be strings).
|
||||
----------------------------------------------------------------------
|
||||
function lexer :check (...)
|
||||
local words = {...}
|
||||
local a = self :next()
|
||||
local function err ()
|
||||
error ("Got " .. tostring (a) ..
|
||||
", expected one of these keywords : '" ..
|
||||
table.concat (words,"', '") .. "'") end
|
||||
if not a or a.tag ~= "Keyword" then err () end
|
||||
if #words == 0 then return a[1] end
|
||||
for _, w in ipairs (words) do
|
||||
if w == a[1] then return w end
|
||||
end
|
||||
err ()
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
--
|
||||
----------------------------------------------------------------------
|
||||
function lexer :clone()
|
||||
local alpha_clone, sym_clone = { }, { }
|
||||
for word in pairs(self.alpha) do alpha_clone[word]=true end
|
||||
for letter, list in pairs(self.sym) do sym_clone[letter] = { unpack(list) } end
|
||||
local clone = { alpha=alpha_clone, sym=sym_clone }
|
||||
setmetatable(clone, self)
|
||||
clone.__index = clone
|
||||
return clone
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Cancel everything left in a lexer, all subsequent attempts at
|
||||
-- `:peek()` or `:next()` will return `Eof`.
|
||||
----------------------------------------------------------------------
|
||||
function lexer :kill()
|
||||
self.i = #self.src+1
|
||||
self.peeked = { }
|
||||
self.attached_comments = { }
|
||||
self.lineinfo_last = self.posfact :get_position (#self.src+1)
|
||||
end
|
||||
|
||||
return M
|
||||
133
Utils/luarocks/share/lua/5.1/metalua/loader.lua
Normal file
133
Utils/luarocks/share/lua/5.1/metalua/loader.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local M = require "package" -- extend Lua's basic "package" module
|
||||
local checks = require 'checks'
|
||||
|
||||
M.metalua_extension_prefix = 'metalua.extension.'
|
||||
|
||||
-- Initialize package.mpath from package.path
|
||||
M.mpath = M.mpath or os.getenv 'LUA_MPATH' or
|
||||
(M.path..";") :gsub("%.(lua[:;])", ".m%1") :sub(1, -2)
|
||||
|
||||
M.mcache = M.mcache or os.getenv 'LUA_MCACHE'
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- resc(k) returns "%"..k if it's a special regular expression char,
|
||||
-- or just k if it's normal.
|
||||
----------------------------------------------------------------------
|
||||
local regexp_magic = { }
|
||||
for k in ("^$()%.[]*+-?") :gmatch "." do regexp_magic[k]="%"..k end
|
||||
|
||||
local function resc(k) return regexp_magic[k] or k end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Take a Lua module name, return the open file and its name,
|
||||
-- or <false> and an error message.
|
||||
----------------------------------------------------------------------
|
||||
function M.findfile(name, path_string)
|
||||
local config_regexp = ("([^\n])\n"):rep(5):sub(1, -2)
|
||||
local dir_sep, path_sep, path_mark, execdir, igmark =
|
||||
M.config :match (config_regexp)
|
||||
name = name:gsub ('%.', dir_sep)
|
||||
local errors = { }
|
||||
local path_pattern = string.format('[^%s]+', resc(path_sep))
|
||||
for path in path_string:gmatch (path_pattern) do
|
||||
--printf('path = %s, rpath_mark=%s, name=%s', path, resc(path_mark), name)
|
||||
local filename = path:gsub (resc (path_mark), name)
|
||||
--printf('filename = %s', filename)
|
||||
local file = io.open (filename, 'rb')
|
||||
if file then return file, filename end
|
||||
table.insert(errors, string.format("\tno file %q", filename))
|
||||
end
|
||||
return false, '\n'..table.concat(errors, "\n")..'\n'
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Before compiling a metalua source module, try to find and load
|
||||
-- a more recent bytecode dump. Requires lfs
|
||||
----------------------------------------------------------------------
|
||||
local function metalua_cache_loader(name, src_filename, src)
|
||||
if not M.mcache:find('%?') then
|
||||
-- This is highly suspicious...
|
||||
print("WARNING: no '?' character in $LUA_MCACHE/package.mcache")
|
||||
end
|
||||
local mlc = require 'metalua.compiler'.new()
|
||||
local lfs = require 'lfs'
|
||||
local dir_sep = M.config:sub(1,1)
|
||||
local dst_filename = M.mcache :gsub ('%?', (name:gsub('%.', dir_sep)))
|
||||
local src_a = lfs.attributes(src_filename)
|
||||
local src_date = src_a and src_a.modification or 0
|
||||
local dst_a = lfs.attributes(dst_filename)
|
||||
local dst_date = dst_a and dst_a.modification or 0
|
||||
local delta = dst_date - src_date
|
||||
local bytecode, file, msg
|
||||
if delta <= 0 then
|
||||
--print ("(need to recompile "..src_filename.." into "..dst_filename..")")
|
||||
bytecode = mlc :src_to_bytecode (src, '@'..src_filename)
|
||||
for x in dst_filename :gmatch('()'..dir_sep) do
|
||||
lfs.mkdir(dst_filename:sub(1,x))
|
||||
end
|
||||
file, msg = io.open(dst_filename, 'wb')
|
||||
if not file then error(msg) end
|
||||
file :write (bytecode)
|
||||
file :close()
|
||||
else
|
||||
file, msg = io.open(dst_filename, 'rb')
|
||||
if not file then error(msg) end
|
||||
bytecode = file :read '*a'
|
||||
file :close()
|
||||
end
|
||||
return mlc :bytecode_to_function (bytecode, '@'..src_filename)
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Load a metalua source file.
|
||||
----------------------------------------------------------------------
|
||||
function M.metalua_loader (name)
|
||||
local file, filename_or_msg = M.findfile (name, M.mpath)
|
||||
if not file then return filename_or_msg end
|
||||
local luastring = file:read '*a'
|
||||
file:close()
|
||||
if M.mcache and pcall(require, 'lfs') then
|
||||
return metalua_cache_loader(name, filename_or_msg, luastring)
|
||||
else return require 'metalua.compiler'.new() :src_to_function (luastring, '@'..filename_or_msg) end
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Placed after lua/luac loader, so precompiled files have
|
||||
-- higher precedence.
|
||||
----------------------------------------------------------------------
|
||||
table.insert(M.loaders, M.metalua_loader)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Load an extension.
|
||||
----------------------------------------------------------------------
|
||||
function extension (name, mlp)
|
||||
local complete_name = M.metalua_extension_prefix..name
|
||||
local extend_func = require (complete_name)
|
||||
if not mlp.extensions[complete_name] then
|
||||
local ast =extend_func(mlp)
|
||||
mlp.extensions[complete_name] =extend_func
|
||||
return ast
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
295
Utils/luarocks/share/lua/5.1/metalua/pprint.lua
Normal file
295
Utils/luarocks/share/lua/5.1/metalua/pprint.lua
Normal file
@@ -0,0 +1,295 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
----------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------
|
||||
----------------------------------------------------------------------
|
||||
--
|
||||
-- Lua objects pretty-printer
|
||||
--
|
||||
----------------------------------------------------------------------
|
||||
----------------------------------------------------------------------
|
||||
|
||||
local M = { }
|
||||
|
||||
M.DEFAULT_CFG = {
|
||||
hide_hash = false; -- Print the non-array part of tables?
|
||||
metalua_tag = true; -- Use Metalua's backtick syntax sugar?
|
||||
fix_indent = nil; -- If a number, number of indentation spaces;
|
||||
-- If false, indent to the previous brace.
|
||||
line_max = nil; -- If a number, tries to avoid making lines with
|
||||
-- more than this number of chars.
|
||||
initial_indent = 0; -- If a number, starts at this level of indentation
|
||||
keywords = { }; -- Set of keywords which must not use Lua's field
|
||||
-- shortcuts {["foo"]=...} -> {foo=...}
|
||||
}
|
||||
|
||||
local function valid_id(cfg, x)
|
||||
if type(x) ~= "string" then return false end
|
||||
if not x:match "^[a-zA-Z_][a-zA-Z0-9_]*$" then return false end
|
||||
if cfg.keywords and cfg.keywords[x] then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
local __tostring_cache = setmetatable({ }, {__mode='k'})
|
||||
|
||||
-- Retrieve the string produced by `__tostring` metamethod if present,
|
||||
-- return `false` otherwise. Cached in `__tostring_cache`.
|
||||
local function __tostring(x)
|
||||
local the_string = __tostring_cache[x]
|
||||
if the_string~=nil then return the_string end
|
||||
local mt = getmetatable(x)
|
||||
if mt then
|
||||
local __tostring = mt.__tostring
|
||||
if __tostring then
|
||||
the_string = __tostring(x)
|
||||
__tostring_cache[x] = the_string
|
||||
return the_string
|
||||
end
|
||||
end
|
||||
if x~=nil then __tostring_cache[x] = false end -- nil is an illegal key
|
||||
return false
|
||||
end
|
||||
|
||||
local xlen -- mutually recursive with `xlen_type`
|
||||
|
||||
local xlen_cache = setmetatable({ }, {__mode='k'})
|
||||
|
||||
-- Helpers for the `xlen` function
|
||||
local xlen_type = {
|
||||
["nil"] = function ( ) return 3 end;
|
||||
number = function (x) return #tostring(x) end;
|
||||
boolean = function (x) return x and 4 or 5 end;
|
||||
string = function (x) return #string.format("%q",x) end;
|
||||
}
|
||||
|
||||
function xlen_type.table (adt, cfg, nested)
|
||||
local custom_string = __tostring(adt)
|
||||
if custom_string then return #custom_string end
|
||||
|
||||
-- Circular referenced objects are printed with the plain
|
||||
-- `tostring` function in nested positions.
|
||||
if nested [adt] then return #tostring(adt) end
|
||||
nested [adt] = true
|
||||
|
||||
local has_tag = cfg.metalua_tag and valid_id(cfg, adt.tag)
|
||||
local alen = #adt
|
||||
local has_arr = alen>0
|
||||
local has_hash = false
|
||||
local x = 0
|
||||
|
||||
if not cfg.hide_hash then
|
||||
-- first pass: count hash-part
|
||||
for k, v in pairs(adt) do
|
||||
if k=="tag" and has_tag then
|
||||
-- this is the tag -> do nothing!
|
||||
elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 and k>0 then
|
||||
-- array-part pair -> do nothing!
|
||||
else
|
||||
has_hash = true
|
||||
if valid_id(cfg, k) then x=x+#k
|
||||
else x = x + xlen (k, cfg, nested) + 2 end -- count surrounding brackets
|
||||
x = x + xlen (v, cfg, nested) + 5 -- count " = " and ", "
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
|
||||
|
||||
nested[adt] = false -- No more nested calls
|
||||
|
||||
if not (has_tag or has_arr or has_hash) then return 3 end
|
||||
if has_tag then x=x+#adt.tag+1 end
|
||||
if not (has_arr or has_hash) then return x end
|
||||
if not has_hash and alen==1 and type(adt[1])~="table" then
|
||||
return x-2 -- substract extraneous ", "
|
||||
end
|
||||
return x+2 -- count "{ " and " }", substract extraneous ", "
|
||||
end
|
||||
|
||||
|
||||
-- Compute the number of chars it would require to display the table
|
||||
-- on a single line. Helps to decide whether some carriage returns are
|
||||
-- required. Since the size of each sub-table is required many times,
|
||||
-- it's cached in [xlen_cache].
|
||||
xlen = function (x, cfg, nested)
|
||||
-- no need to compute length for 1-line prints
|
||||
if not cfg.line_max then return 0 end
|
||||
nested = nested or { }
|
||||
if x==nil then return #"nil" end
|
||||
local len = xlen_cache[x]
|
||||
if len then return len end
|
||||
local f = xlen_type[type(x)]
|
||||
if not f then return #tostring(x) end
|
||||
len = f (x, cfg, nested)
|
||||
xlen_cache[x] = len
|
||||
return len
|
||||
end
|
||||
|
||||
local function consider_newline(p, len)
|
||||
if not p.cfg.line_max then return end
|
||||
if p.current_offset + len <= p.cfg.line_max then return end
|
||||
if p.indent < p.current_offset then
|
||||
p:acc "\n"; p:acc ((" "):rep(p.indent))
|
||||
p.current_offset = p.indent
|
||||
end
|
||||
end
|
||||
|
||||
local acc_value
|
||||
|
||||
local acc_type = {
|
||||
["nil"] = function(p) p:acc("nil") end;
|
||||
number = function(p, adt) p:acc (tostring (adt)) end;
|
||||
string = function(p, adt) p:acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end;
|
||||
boolean = function(p, adt) p:acc (adt and "true" or "false") end }
|
||||
|
||||
-- Indentation:
|
||||
-- * if `cfg.fix_indent` is set to a number:
|
||||
-- * add this number of space for each level of depth
|
||||
-- * return to the line as soon as it flushes things further left
|
||||
-- * if not, tabulate to one space after the opening brace.
|
||||
-- * as a result, it never saves right-space to return before first element
|
||||
|
||||
function acc_type.table(p, adt)
|
||||
if p.nested[adt] then p:acc(tostring(adt)); return end
|
||||
p.nested[adt] = true
|
||||
|
||||
local has_tag = p.cfg.metalua_tag and valid_id(p.cfg, adt.tag)
|
||||
local alen = #adt
|
||||
local has_arr = alen>0
|
||||
local has_hash = false
|
||||
|
||||
local previous_indent = p.indent
|
||||
|
||||
if has_tag then p:acc("`"); p:acc(adt.tag) end
|
||||
|
||||
local function indent(p)
|
||||
if not p.cfg.fix_indent then p.indent = p.current_offset
|
||||
else p.indent = p.indent + p.cfg.fix_indent end
|
||||
end
|
||||
|
||||
-- First pass: handle hash-part
|
||||
if not p.cfg.hide_hash then
|
||||
for k, v in pairs(adt) do
|
||||
|
||||
if has_tag and k=='tag' then -- pass the 'tag' field
|
||||
elseif type(k)=="number" and k<=alen and k>0 and math.fmod(k,1)==0 then
|
||||
-- pass array-part keys (consecutive ints less than `#adt`)
|
||||
else -- hash-part keys
|
||||
if has_hash then p:acc ", " else -- 1st hash-part pair ever found
|
||||
p:acc "{ "; indent(p)
|
||||
end
|
||||
|
||||
-- Determine whether a newline is required
|
||||
local is_id, expected_len=valid_id(p.cfg, k)
|
||||
if is_id then expected_len=#k+xlen(v, p.cfg, p.nested)+#" = , "
|
||||
else expected_len = xlen(k, p.cfg, p.nested)+xlen(v, p.cfg, p.nested)+#"[] = , " end
|
||||
consider_newline(p, expected_len)
|
||||
|
||||
-- Print the key
|
||||
if is_id then p:acc(k); p:acc " = " else
|
||||
p:acc "["; acc_value (p, k); p:acc "] = "
|
||||
end
|
||||
|
||||
acc_value (p, v) -- Print the value
|
||||
has_hash = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Now we know whether there's a hash-part, an array-part, and a tag.
|
||||
-- Tag and hash-part are already printed if they're present.
|
||||
if not has_tag and not has_hash and not has_arr then p:acc "{ }";
|
||||
elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
|
||||
else
|
||||
assert (has_hash or has_arr) -- special case { } already handled
|
||||
local no_brace = false
|
||||
if has_hash and has_arr then p:acc ", "
|
||||
elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
|
||||
-- No brace required; don't print "{", remember not to print "}"
|
||||
p:acc (" "); acc_value (p, adt[1]) -- indent= indent+(cfg.fix_indent or 0))
|
||||
no_brace = true
|
||||
elseif not has_hash then
|
||||
-- Braces required, but not opened by hash-part handler yet
|
||||
p:acc "{ "; indent(p)
|
||||
end
|
||||
|
||||
-- 2nd pass: array-part
|
||||
if not no_brace and has_arr then
|
||||
local expected_len = xlen(adt[1], p.cfg, p.nested)
|
||||
consider_newline(p, expected_len)
|
||||
acc_value(p, adt[1]) -- indent+(cfg.fix_indent or 0)
|
||||
for i=2, alen do
|
||||
p:acc ", ";
|
||||
consider_newline(p, xlen(adt[i], p.cfg, p.nested))
|
||||
acc_value (p, adt[i]) --indent+(cfg.fix_indent or 0)
|
||||
end
|
||||
end
|
||||
if not no_brace then p:acc " }" end
|
||||
end
|
||||
p.nested[adt] = false -- No more nested calls
|
||||
p.indent = previous_indent
|
||||
end
|
||||
|
||||
|
||||
function acc_value(p, v)
|
||||
local custom_string = __tostring(v)
|
||||
if custom_string then p:acc(custom_string) else
|
||||
local f = acc_type[type(v)]
|
||||
if f then f(p, v) else p:acc(tostring(v)) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- FIXME: new_indent seems to be always nil?!s detection
|
||||
-- FIXME: accumulator function should be configurable,
|
||||
-- so that print() doesn't need to bufferize the whole string
|
||||
-- before starting to print.
|
||||
function M.tostring(t, cfg)
|
||||
|
||||
cfg = cfg or M.DEFAULT_CFG or { }
|
||||
|
||||
local p = {
|
||||
cfg = cfg;
|
||||
indent = 0;
|
||||
current_offset = cfg.initial_indent or 0;
|
||||
buffer = { };
|
||||
nested = { };
|
||||
acc = function(self, str)
|
||||
table.insert(self.buffer, str)
|
||||
self.current_offset = self.current_offset + #str
|
||||
end;
|
||||
}
|
||||
acc_value(p, t)
|
||||
return table.concat(p.buffer)
|
||||
end
|
||||
|
||||
function M.print(...) return print(M.tostring(...)) end
|
||||
function M.sprintf(fmt, ...)
|
||||
local args={...}
|
||||
for i, v in pairs(args) do
|
||||
local t=type(v)
|
||||
if t=='table' then args[i]=M.tostring(v)
|
||||
elseif t=='nil' then args[i]='nil' end
|
||||
end
|
||||
return string.format(fmt, unpack(args))
|
||||
end
|
||||
|
||||
function M.printf(...) print(M.sprintf(...)) end
|
||||
|
||||
return M
|
||||
108
Utils/luarocks/share/lua/5.1/metalua/repl.mlua
Normal file
108
Utils/luarocks/share/lua/5.1/metalua/repl.mlua
Normal file
@@ -0,0 +1,108 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- Keep these global:
|
||||
PRINT_AST = true
|
||||
LINE_WIDTH = 60
|
||||
PROMPT = "M> "
|
||||
PROMPT2 = ">> "
|
||||
|
||||
local pp=require 'metalua.pprint'
|
||||
local M = { }
|
||||
|
||||
mlc = require 'metalua.compiler'.new()
|
||||
|
||||
local readline
|
||||
|
||||
do -- set readline() to a line reader, either editline otr a default
|
||||
local status, editline = pcall(require, 'editline')
|
||||
if status then
|
||||
local rl_handle = editline.init 'metalua'
|
||||
readline = |p| rl_handle:read(p)
|
||||
else
|
||||
local status, rl = pcall(require, 'readline')
|
||||
if status then
|
||||
rl.set_options{histfile='~/.metalua_history', keeplines=100, completion=false }
|
||||
readline = rl.readline
|
||||
else -- neither editline nor readline available
|
||||
function readline (p)
|
||||
io.write (p)
|
||||
io.flush ()
|
||||
return io.read '*l'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function reached_eof(lx, msg)
|
||||
return lx:peek().tag=='Eof' or msg:find "token `Eof"
|
||||
end
|
||||
|
||||
|
||||
function M.run()
|
||||
pp.printf ("Metalua, interactive REPLoop.\n"..
|
||||
"(c) 2006-2013 <metalua@gmail.com>")
|
||||
local lines = { }
|
||||
while true do
|
||||
local src, lx, ast, f, results, success
|
||||
repeat
|
||||
local line = readline(next(lines) and PROMPT2 or PROMPT)
|
||||
if not line then print(); os.exit(0) end -- line==nil iff eof on stdin
|
||||
if not next(lines) then
|
||||
line = line:gsub('^%s*=', 'return ')
|
||||
end
|
||||
table.insert(lines, line)
|
||||
src = table.concat (lines, "\n")
|
||||
until #line>0
|
||||
lx = mlc :src_to_lexstream(src)
|
||||
success, ast = pcall(mlc.lexstream_to_ast, mlc, lx)
|
||||
if success then
|
||||
success, f = pcall(mlc.ast_to_function, mlc, ast, '=stdin')
|
||||
if success then
|
||||
results = { xpcall(f, debug.traceback) }
|
||||
success = table.remove (results, 1)
|
||||
if success then
|
||||
-- Success!
|
||||
for _, x in ipairs(results) do
|
||||
pp.print(x, {line_max=LINE_WIDTH, metalua_tag=true})
|
||||
end
|
||||
lines = { }
|
||||
else
|
||||
print "Evaluation error:"
|
||||
print (results[1])
|
||||
lines = { }
|
||||
end
|
||||
else
|
||||
print "Can't compile into bytecode:"
|
||||
print (f)
|
||||
lines = { }
|
||||
end
|
||||
else
|
||||
-- If lx has been read entirely, try to read
|
||||
-- another line before failing.
|
||||
if not reached_eof(lx, ast) then
|
||||
print "Can't compile source into AST:"
|
||||
print (ast)
|
||||
lines = { }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
488
Utils/luarocks/share/lua/5.1/metalua/treequery.mlua
Normal file
488
Utils/luarocks/share/lua/5.1/metalua/treequery.mlua
Normal file
@@ -0,0 +1,488 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local walk = require 'metalua.treequery.walk'
|
||||
|
||||
local M = { }
|
||||
-- support for old-style modules
|
||||
treequery = M
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
--
|
||||
-- multimap helper mmap: associate a key to a set of values
|
||||
--
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
local function mmap_add (mmap, node, x)
|
||||
if node==nil then return false end
|
||||
local set = mmap[node]
|
||||
if set then set[x] = true
|
||||
else mmap[node] = {[x]=true} end
|
||||
end
|
||||
|
||||
-- currently unused, I throw the whole set away
|
||||
local function mmap_remove (mmap, node, x)
|
||||
local set = mmap[node]
|
||||
if not set then return false
|
||||
elseif not set[x] then return false
|
||||
elseif next(set) then set[x]=nil
|
||||
else mmap[node] = nil end
|
||||
return true
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
--
|
||||
-- TreeQuery object.
|
||||
--
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
local ACTIVE_SCOPE = setmetatable({ }, {__mode="k"})
|
||||
|
||||
-- treequery metatable
|
||||
local Q = { }; Q.__index = Q
|
||||
|
||||
--- treequery constructor
|
||||
-- the resultingg object will allow to filter ans operate on the AST
|
||||
-- @param root the AST to visit
|
||||
-- @return a treequery visitor instance
|
||||
function M.treequery(root)
|
||||
return setmetatable({
|
||||
root = root,
|
||||
unsatisfied = 0,
|
||||
predicates = { },
|
||||
until_up = { },
|
||||
from_up = { },
|
||||
up_f = false,
|
||||
down_f = false,
|
||||
filters = { },
|
||||
}, Q)
|
||||
end
|
||||
|
||||
-- helper to share the implementations of positional filters
|
||||
local function add_pos_filter(self, position, inverted, inclusive, f, ...)
|
||||
if type(f)=='string' then f = M.has_tag(f, ...) end
|
||||
if not inverted then self.unsatisfied += 1 end
|
||||
local x = {
|
||||
pred = f,
|
||||
position = position,
|
||||
satisfied = false,
|
||||
inverted = inverted or false,
|
||||
inclusive = inclusive or false }
|
||||
table.insert(self.predicates, x)
|
||||
return self
|
||||
end
|
||||
|
||||
function Q :if_unknown(f)
|
||||
self.unknown_handler = f or (||nil)
|
||||
return self
|
||||
end
|
||||
|
||||
-- TODO: offer an API for inclusive pos_filters
|
||||
|
||||
--- select nodes which are after one which satisfies predicate f
|
||||
Q.after = |self, f, ...| add_pos_filter(self, 'after', false, false, f, ...)
|
||||
--- select nodes which are not after one which satisfies predicate f
|
||||
Q.not_after = |self, f, ...| add_pos_filter(self, 'after', true, false, f, ...)
|
||||
--- select nodes which are under one which satisfies predicate f
|
||||
Q.under = |self, f, ...| add_pos_filter(self, 'under', false, false, f, ...)
|
||||
--- select nodes which are not under one which satisfies predicate f
|
||||
Q.not_under = |self, f, ...| add_pos_filter(self, 'under', true, false, f, ...)
|
||||
|
||||
--- select nodes which satisfy predicate f
|
||||
function Q :filter(f, ...)
|
||||
if type(f)=='string' then f = M.has_tag(f, ...) end
|
||||
table.insert(self.filters, f);
|
||||
return self
|
||||
end
|
||||
|
||||
--- select nodes which satisfy predicate f
|
||||
function Q :filter_not(f, ...)
|
||||
if type(f)=='string' then f = M.has_tag(f, ...) end
|
||||
table.insert(self.filters, |...| not f(...))
|
||||
return self
|
||||
end
|
||||
|
||||
-- private helper: apply filters and execute up/down callbacks when applicable
|
||||
function Q :execute()
|
||||
local cfg = { }
|
||||
-- TODO: optimize away not_under & not_after by pruning the tree
|
||||
function cfg.down(...)
|
||||
--printf ("[down]\t%s\t%s", self.unsatisfied, table.tostring((...)))
|
||||
ACTIVE_SCOPE[...] = cfg.scope
|
||||
local satisfied = self.unsatisfied==0
|
||||
for _, x in ipairs(self.predicates) do
|
||||
if not x.satisfied and x.pred(...) then
|
||||
x.satisfied = true
|
||||
local node, parent = ...
|
||||
local inc = x.inverted and 1 or -1
|
||||
if x.position=='under' then
|
||||
-- satisfied from after we get down this node...
|
||||
self.unsatisfied += inc
|
||||
-- ...until before we get up this node
|
||||
mmap_add(self.until_up, node, x)
|
||||
elseif x.position=='after' then
|
||||
-- satisfied from after we get up this node...
|
||||
mmap_add(self.from_up, node, x)
|
||||
-- ...until before we get up this node's parent
|
||||
mmap_add(self.until_up, parent, x)
|
||||
elseif x.position=='under_or_after' then
|
||||
-- satisfied from after we get down this node...
|
||||
self.satisfied += inc
|
||||
-- ...until before we get up this node's parent...
|
||||
mmap_add(self.until_up, parent, x)
|
||||
else
|
||||
error "position not understood"
|
||||
end -- position
|
||||
if x.inclusive then satisfied = self.unsatisfied==0 end
|
||||
end -- predicate passed
|
||||
end -- for predicates
|
||||
|
||||
if satisfied then
|
||||
for _, f in ipairs(self.filters) do
|
||||
if not f(...) then satisfied=false; break end
|
||||
end
|
||||
if satisfied and self.down_f then self.down_f(...) end
|
||||
end
|
||||
end
|
||||
|
||||
function cfg.up(...)
|
||||
--printf ("[up]\t%s", table.tostring((...)))
|
||||
|
||||
-- Remove predicates which are due before we go up this node
|
||||
local preds = self.until_up[...]
|
||||
if preds then
|
||||
for x, _ in pairs(preds) do
|
||||
local inc = x.inverted and -1 or 1
|
||||
self.unsatisfied += inc
|
||||
x.satisfied = false
|
||||
end
|
||||
self.until_up[...] = nil
|
||||
end
|
||||
|
||||
-- Execute the up callback
|
||||
-- TODO: cache the filter passing result from the down callback
|
||||
-- TODO: skip if there's no callback
|
||||
local satisfied = self.unsatisfied==0
|
||||
if satisfied then
|
||||
for _, f in ipairs(self.filters) do
|
||||
if not f(self, ...) then satisfied=false; break end
|
||||
end
|
||||
if satisfied and self.up_f then self.up_f(...) end
|
||||
end
|
||||
|
||||
-- Set predicate which are due after we go up this node
|
||||
local preds = self.from_up[...]
|
||||
if preds then
|
||||
for p, _ in pairs(preds) do
|
||||
local inc = p.inverted and 1 or -1
|
||||
self.unsatisfied += inc
|
||||
end
|
||||
self.from_up[...] = nil
|
||||
end
|
||||
ACTIVE_SCOPE[...] = nil
|
||||
end
|
||||
|
||||
function cfg.binder(id_node, ...)
|
||||
--printf(" >>> Binder called on %s, %s", table.tostring(id_node),
|
||||
-- table.tostring{...}:sub(2,-2))
|
||||
cfg.down(id_node, ...)
|
||||
cfg.up(id_node, ...)
|
||||
--printf("down/up on binder done")
|
||||
end
|
||||
|
||||
cfg.unknown = self.unknown_handler
|
||||
|
||||
--function cfg.occurrence (binder, occ)
|
||||
-- if binder then OCC2BIND[occ] = binder[1] end
|
||||
--printf(" >>> %s is an occurrence of %s", occ[1], table.tostring(binder and binder[2]))
|
||||
--end
|
||||
|
||||
--function cfg.binder(...) cfg.down(...); cfg.up(...) end
|
||||
return walk.guess(cfg, self.root)
|
||||
end
|
||||
|
||||
--- Execute a function on each selected node
|
||||
-- @down: function executed when we go down a node, i.e. before its children
|
||||
-- have been examined.
|
||||
-- @up: function executed when we go up a node, i.e. after its children
|
||||
-- have been examined.
|
||||
function Q :foreach(down, up)
|
||||
if not up and not down then
|
||||
error "iterator missing"
|
||||
end
|
||||
self.up_f = up
|
||||
self.down_f = down
|
||||
return self :execute()
|
||||
end
|
||||
|
||||
--- Return the list of nodes selected by a given treequery.
|
||||
function Q :list()
|
||||
local acc = { }
|
||||
self :foreach(|x| table.insert(acc, x))
|
||||
return acc
|
||||
end
|
||||
|
||||
--- Return the first matching element
|
||||
-- TODO: dirty hack, to implement properly with a 'break' return.
|
||||
-- Also, it won't behave correctly if a predicate causes an error,
|
||||
-- or if coroutines are involved.
|
||||
function Q :first()
|
||||
local result = { }
|
||||
local function f(...) result = {...}; error() end
|
||||
pcall(|| self :foreach(f))
|
||||
return unpack(result)
|
||||
end
|
||||
|
||||
--- Pretty printer for queries
|
||||
function Q :__tostring() return "<treequery>" end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
--
|
||||
-- Predicates.
|
||||
--
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
--- Return a predicate which is true if the tested node's tag is among the
|
||||
-- one listed as arguments
|
||||
-- @param ... a sequence of tag names
|
||||
function M.has_tag(...)
|
||||
local args = {...}
|
||||
if #args==1 then
|
||||
local tag = ...
|
||||
return (|node| node.tag==tag)
|
||||
--return function(self, node) printf("node %s has_tag %s?", table.tostring(node), tag); return node.tag==tag end
|
||||
else
|
||||
local tags = { }
|
||||
for _, tag in ipairs(args) do tags[tag]=true end
|
||||
return function(node)
|
||||
local node_tag = node.tag
|
||||
return node_tag and tags[node_tag]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Predicate to test whether a node represents an expression.
|
||||
M.is_expr = M.has_tag('Nil', 'Dots', 'True', 'False', 'Number','String',
|
||||
'Function', 'Table', 'Op', 'Paren', 'Call', 'Invoke',
|
||||
'Id', 'Index')
|
||||
|
||||
-- helper for is_stat
|
||||
local STAT_TAGS = { Do=1, Set=1, While=1, Repeat=1, If=1, Fornum=1,
|
||||
Forin=1, Local=1, Localrec=1, Return=1, Break=1 }
|
||||
|
||||
--- Predicate to test whether a node represents a statement.
|
||||
-- It is context-aware, i.e. it recognizes `Call and `Invoke nodes
|
||||
-- used in a statement context as such.
|
||||
function M.is_stat(node, parent)
|
||||
local tag = node.tag
|
||||
if not tag then return false
|
||||
elseif STAT_TAGS[tag] then return true
|
||||
elseif tag=='Call' or tag=='Invoke' then return parent and parent.tag==nil
|
||||
else return false end
|
||||
end
|
||||
|
||||
--- Predicate to test whether a node represents a statements block.
|
||||
function M.is_block(node) return node.tag==nil end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
--
|
||||
-- Variables and scopes.
|
||||
--
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
local BINDER_PARENT_TAG = {
|
||||
Local=true, Localrec=true, Forin=true, Function=true }
|
||||
|
||||
--- Test whether a node is a binder. This is local predicate, although it
|
||||
-- might need to inspect the parent node.
|
||||
function M.is_binder(node, parent)
|
||||
--printf('is_binder(%s, %s)', table.tostring(node), table.tostring(parent))
|
||||
if node.tag ~= 'Id' or not parent then return false end
|
||||
if parent.tag=='Fornum' then return parent[1]==node end
|
||||
if not BINDER_PARENT_TAG[parent.tag] then return false end
|
||||
for _, binder in ipairs(parent[1]) do
|
||||
if binder==node then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Retrieve the binder associated to an occurrence within root.
|
||||
-- @param occurrence an Id node representing an occurrence in `root`.
|
||||
-- @param root the tree in which `node` and its binder occur.
|
||||
-- @return the binder node, and its ancestors up to root if found.
|
||||
-- @return nil if node is global (or not an occurrence) in `root`.
|
||||
function M.binder(occurrence, root)
|
||||
local cfg, id_name, result = { }, occurrence[1], { }
|
||||
function cfg.occurrence(id)
|
||||
if id == occurrence then result = cfg.scope :get(id_name) end
|
||||
-- TODO: break the walker
|
||||
end
|
||||
walk.guess(cfg, root)
|
||||
return unpack(result)
|
||||
end
|
||||
|
||||
--- Predicate to filter occurrences of a given binder.
|
||||
-- Warning: it relies on internal scope book-keeping,
|
||||
-- and for this reason, it only works as query method argument.
|
||||
-- It won't work outside of a query.
|
||||
-- @param binder the binder whose occurrences must be kept by predicate
|
||||
-- @return a predicate
|
||||
|
||||
-- function M.is_occurrence_of(binder)
|
||||
-- return function(node, ...)
|
||||
-- if node.tag ~= 'Id' then return nil end
|
||||
-- if M.is_binder(node, ...) then return nil end
|
||||
-- local scope = ACTIVE_SCOPE[node]
|
||||
-- if not scope then return nil end
|
||||
-- local result = scope :get (node[1]) or { }
|
||||
-- if result[1] ~= binder then return nil end
|
||||
-- return unpack(result)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
function M.is_occurrence_of(binder)
|
||||
return function(node, ...)
|
||||
local b = M.get_binder(node)
|
||||
return b and b==binder
|
||||
end
|
||||
end
|
||||
|
||||
function M.get_binder(occurrence, ...)
|
||||
if occurrence.tag ~= 'Id' then return nil end
|
||||
if M.is_binder(occurrence, ...) then return nil end
|
||||
local scope = ACTIVE_SCOPE[occurrence]
|
||||
local binder_hierarchy = scope :get(occurrence[1])
|
||||
return unpack (binder_hierarchy or { })
|
||||
end
|
||||
|
||||
--- Transform a predicate on a node into a predicate on this node's
|
||||
-- parent. For instance if p tests whether a node has property P,
|
||||
-- then parent(p) tests whether this node's parent has property P.
|
||||
-- The ancestor level is precised with n, with 1 being the node itself,
|
||||
-- 2 its parent, 3 its grand-parent etc.
|
||||
-- @param[optional] n the parent to examine, default=2
|
||||
-- @param pred the predicate to transform
|
||||
-- @return a predicate
|
||||
function M.parent(n, pred, ...)
|
||||
if type(n)~='number' then n, pred = 2, n end
|
||||
if type(pred)=='string' then pred = M.has_tag(pred, ...) end
|
||||
return function(self, ...)
|
||||
return select(n, ...) and pred(self, select(n, ...))
|
||||
end
|
||||
end
|
||||
|
||||
--- Transform a predicate on a node into a predicate on this node's
|
||||
-- n-th child.
|
||||
-- @param n the child's index number
|
||||
-- @param pred the predicate to transform
|
||||
-- @return a predicate
|
||||
function M.child(n, pred)
|
||||
return function(node, ...)
|
||||
local child = node[n]
|
||||
return child and pred(child, node, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--- Predicate to test the position of a node in its parent.
|
||||
-- The predicate succeeds if the node is the n-th child of its parent,
|
||||
-- and a <= n <= b.
|
||||
-- nth(a) is equivalent to nth(a, a).
|
||||
-- Negative indices are admitted, and count from the last child,
|
||||
-- as done for instance by string.sub().
|
||||
--
|
||||
-- TODO: This is wrong, this tests the table relationship rather than the
|
||||
-- AST node relationship.
|
||||
-- Must build a getindex helper, based on pattern matching, then build
|
||||
-- the predicate around it.
|
||||
--
|
||||
-- @param a lower bound
|
||||
-- @param a upper bound
|
||||
-- @return a predicate
|
||||
function M.is_nth(a, b)
|
||||
b = b or a
|
||||
return function(self, node, parent)
|
||||
if not parent then return false end
|
||||
local nchildren = #parent
|
||||
local a = a<=0 and nchildren+a+1 or a
|
||||
if a>nchildren then return false end
|
||||
local b = b<=0 and nchildren+b+1 or b>nchildren and nchildren or b
|
||||
for i=a,b do if parent[i]==node then return true end end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a list of the direct children of AST node `ast`.
|
||||
-- Children are only expressions, statements and blocks,
|
||||
-- not intermediates such as `Pair` nodes or internal lists
|
||||
-- in `Local` or `Set` nodes.
|
||||
-- Children are returned in parsing order, which isn't necessarily
|
||||
-- the same as source code order. For instance, the right-hand-side
|
||||
-- of a `Local` node is listed before the left-hand-side, because
|
||||
-- semantically the right is evaluated before the variables on the
|
||||
-- left enter scope.
|
||||
--
|
||||
-- @param ast the node whose children are needed
|
||||
-- @return a list of the direct children of `ast`
|
||||
function M.children(ast)
|
||||
local acc = { }
|
||||
local cfg = { }
|
||||
function cfg.down(x)
|
||||
if x~=ast then table.insert(acc, x); return 'break' end
|
||||
end
|
||||
walk.guess(cfg, ast)
|
||||
return acc
|
||||
end
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
--
|
||||
-- Comments parsing.
|
||||
--
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
local comment_extractor = |which_side| function (node)
|
||||
local x = node.lineinfo
|
||||
x = x and x[which_side]
|
||||
x = x and x.comments
|
||||
if not x then return nil end
|
||||
local lines = { }
|
||||
for _, record in ipairs(x) do
|
||||
table.insert(lines, record[1])
|
||||
end
|
||||
return table.concat(lines, '\n')
|
||||
end
|
||||
|
||||
M.comment_prefix = comment_extractor 'first'
|
||||
M.comment_suffix = comment_extractor 'last'
|
||||
|
||||
|
||||
--- Shortcut for the query constructor
|
||||
function M :__call(...) return self.treequery(...) end
|
||||
setmetatable(M, M)
|
||||
|
||||
return M
|
||||
266
Utils/luarocks/share/lua/5.1/metalua/treequery/walk.mlua
Normal file
266
Utils/luarocks/share/lua/5.1/metalua/treequery/walk.mlua
Normal file
@@ -0,0 +1,266 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2006-2013 Fabien Fleutot and others.
|
||||
--
|
||||
-- All rights reserved.
|
||||
--
|
||||
-- This program and the accompanying materials are made available
|
||||
-- under the terms of the Eclipse Public License v1.0 which
|
||||
-- accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- This program and the accompanying materials are also made available
|
||||
-- under the terms of the MIT public license which accompanies this
|
||||
-- distribution, and is available at http://www.lua.org/license.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Fabien Fleutot - API and implementation
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- Low level AST traversal library.
|
||||
--
|
||||
-- This library is a helper for the higher-level `treequery` library.
|
||||
-- It walks through every node of an AST, depth-first, and executes
|
||||
-- some callbacks contained in its `cfg` config table:
|
||||
--
|
||||
-- * `cfg.down(...)` is called when it walks down a node, and receive as
|
||||
-- parameters the node just entered, followed by its parent, grand-parent
|
||||
-- etc. until the root node.
|
||||
--
|
||||
-- * `cfg.up(...)` is called when it walks back up a node, and receive as
|
||||
-- parameters the node just entered, followed by its parent, grand-parent
|
||||
-- etc. until the root node.
|
||||
--
|
||||
-- * `cfg.occurrence(binder, id_node, ...)` is called when it visits
|
||||
-- an `` `Id{ }`` node which isn't a local variable creator. binder
|
||||
-- is a reference to its binder with its context. The binder is the
|
||||
-- `` `Id{ }`` node which created this local variable. By "binder
|
||||
-- and its context", we mean a list starting with the `` `Id{ }``,
|
||||
-- and followed by every ancestor of the binder node, up until the
|
||||
-- common root node. `binder` is nil if the variable is global.
|
||||
-- `id_node` is followed by its ancestor, up until the root node.
|
||||
--
|
||||
-- `cfg.scope` is maintained during the traversal, associating a
|
||||
-- variable name to the binder which creates it in the context of the
|
||||
-- node currently visited.
|
||||
--
|
||||
-- `walk.traverse.xxx` functions are in charge of the recursive
|
||||
-- descent into children nodes. They're private helpers. They are also
|
||||
-- in charge of calling appropriate `cfg.xxx` callbacks.
|
||||
|
||||
-{ extension ("match", ...) }
|
||||
|
||||
local pp = require 'metalua.pprint'
|
||||
|
||||
local M = { traverse = { }; tags = { }; debug = false }
|
||||
|
||||
local function table_transpose(t)
|
||||
local tt = { }; for a, b in pairs(t) do tt[b]=a end; return tt
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Standard tags: can be used to guess the type of an AST, or to check
|
||||
-- that the type of an AST is respected.
|
||||
--------------------------------------------------------------------------------
|
||||
M.tags.stat = table_transpose{
|
||||
'Do', 'Set', 'While', 'Repeat', 'Local', 'Localrec', 'Return',
|
||||
'Fornum', 'Forin', 'If', 'Break', 'Goto', 'Label',
|
||||
'Call', 'Invoke' }
|
||||
M.tags.expr = table_transpose{
|
||||
'Paren', 'Call', 'Invoke', 'Index', 'Op', 'Function', 'Stat',
|
||||
'Table', 'Nil', 'Dots', 'True', 'False', 'Number', 'String', 'Id' }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- These [M.traverse.xxx()] functions are in charge of actually going through
|
||||
-- ASTs. At each node, they make sure to call the appropriate walker.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function M.traverse.stat (cfg, x, ...)
|
||||
if M.debug then pp.printf("traverse stat %s", x) end
|
||||
local ancestors = {...}
|
||||
local B = |y| M.block (cfg, y, x, unpack(ancestors)) -- Block
|
||||
local S = |y| M.stat (cfg, y, x, unpack(ancestors)) -- Statement
|
||||
local E = |y| M.expr (cfg, y, x, unpack(ancestors)) -- Expression
|
||||
local EL = |y| M.expr_list (cfg, y, x, unpack(ancestors)) -- Expression List
|
||||
local IL = |y| M.binder_list (cfg, y, x, unpack(ancestors)) -- Id binders List
|
||||
local OS = || cfg.scope :save() -- Open scope
|
||||
local CS = || cfg.scope :restore() -- Close scope
|
||||
|
||||
match x with
|
||||
| {...} if x.tag == nil -> for _, y in ipairs(x) do M.stat(cfg, y, ...) end
|
||||
-- no tag --> node not inserted in the history ancestors
|
||||
| `Do{...} -> OS(x); for _, y in ipairs(x) do S(y) end; CS(x)
|
||||
| `Set{ lhs, rhs } -> EL(lhs); EL(rhs)
|
||||
| `While{ cond, body } -> E(cond); OS(); B(body); CS()
|
||||
| `Repeat{ body, cond } -> OS(body); B(body); E(cond); CS(body)
|
||||
| `Local{ lhs } -> IL(lhs)
|
||||
| `Local{ lhs, rhs } -> EL(rhs); IL(lhs)
|
||||
| `Localrec{ lhs, rhs } -> IL(lhs); EL(rhs)
|
||||
| `Fornum{ i, a, b, body } -> E(a); E(b); OS(); IL{i}; B(body); CS()
|
||||
| `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); OS(); IL{i}; B(body); CS()
|
||||
| `Forin{ i, rhs, body } -> EL(rhs); OS(); IL(i); B(body); CS()
|
||||
| `If{...} ->
|
||||
for i=1, #x-1, 2 do
|
||||
E(x[i]); OS(); B(x[i+1]); CS()
|
||||
end
|
||||
if #x%2 == 1 then
|
||||
OS(); B(x[#x]); CS()
|
||||
end
|
||||
| `Call{...}|`Invoke{...}|`Return{...} -> EL(x)
|
||||
| `Break | `Goto{ _ } | `Label{ _ } -> -- nothing
|
||||
| { tag=tag, ...} if M.tags.stat[tag]->
|
||||
M.malformed (cfg, x, unpack (ancestors))
|
||||
| _ ->
|
||||
M.unknown (cfg, x, unpack (ancestors))
|
||||
end
|
||||
end
|
||||
|
||||
function M.traverse.expr (cfg, x, ...)
|
||||
if M.debug then pp.printf("traverse expr %s", x) end
|
||||
local ancestors = {...}
|
||||
local B = |y| M.block (cfg, y, x, unpack(ancestors)) -- Block
|
||||
local S = |y| M.stat (cfg, y, x, unpack(ancestors)) -- Statement
|
||||
local E = |y| M.expr (cfg, y, x, unpack(ancestors)) -- Expression
|
||||
local EL = |y| M.expr_list (cfg, y, x, unpack(ancestors)) -- Expression List
|
||||
local IL = |y| M.binder_list (cfg, y, x, unpack(ancestors)) -- Id binders list
|
||||
local OS = || cfg.scope :save() -- Open scope
|
||||
local CS = || cfg.scope :restore() -- Close scope
|
||||
|
||||
match x with
|
||||
| `Paren{ e } -> E(e)
|
||||
| `Call{...} | `Invoke{...} -> EL(x)
|
||||
| `Index{ a, b } -> E(a); E(b)
|
||||
| `Op{ opid, ... } -> E(x[2]); if #x==3 then E(x[3]) end
|
||||
| `Function{ params, body } -> OS(body); IL(params); B(body); CS(body)
|
||||
| `Stat{ b, e } -> OS(b); B(b); E(e); CS(b)
|
||||
| `Id{ name } -> M.occurrence(cfg, x, unpack(ancestors))
|
||||
| `Table{ ... } ->
|
||||
for i = 1, #x do match x[i] with
|
||||
| `Pair{ k, v } -> E(k); E(v)
|
||||
| v -> E(v)
|
||||
end end
|
||||
| `Nil|`Dots|`True|`False|`Number{_}|`String{_} -> -- terminal node
|
||||
| { tag=tag, ...} if M.tags.expr[tag]-> M.malformed (cfg, x, unpack (ancestors))
|
||||
| _ -> M.unknown (cfg, x, unpack (ancestors))
|
||||
end
|
||||
end
|
||||
|
||||
function M.traverse.block (cfg, x, ...)
|
||||
assert(type(x)=='table', "traverse.block() expects a table")
|
||||
if x.tag then M.malformed(cfg, x, ...)
|
||||
else for _, y in ipairs(x) do M.stat(cfg, y, x, ...) end
|
||||
end
|
||||
end
|
||||
|
||||
function M.traverse.expr_list (cfg, x, ...)
|
||||
assert(type(x)=='table', "traverse.expr_list() expects a table")
|
||||
-- x doesn't appear in the ancestors
|
||||
for _, y in ipairs(x) do M.expr(cfg, y, ...) end
|
||||
end
|
||||
|
||||
function M.malformed(cfg, x, ...)
|
||||
local f = cfg.malformed or cfg.error
|
||||
if f then f(x, ...) else
|
||||
error ("Malformed node of tag "..(x.tag or '(nil)'))
|
||||
end
|
||||
end
|
||||
|
||||
function M.unknown(cfg, x, ...)
|
||||
local f = cfg.unknown or cfg.error
|
||||
if f then f(x, ...) else
|
||||
error ("Unknown node tag "..(x.tag or '(nil)'))
|
||||
end
|
||||
end
|
||||
|
||||
function M.occurrence(cfg, x, ...)
|
||||
if cfg.occurrence then cfg.occurrence(cfg.scope :get(x[1]), x, ...) end
|
||||
end
|
||||
|
||||
-- TODO: Is it useful to call each error handling function?
|
||||
function M.binder_list (cfg, id_list, ...)
|
||||
local f = cfg.binder
|
||||
local ferror = cfg.error or cfg.malformed or cfg.unknown
|
||||
for i, id_node in ipairs(id_list) do
|
||||
local down, up = cfg.down, cfg.up
|
||||
if id_node.tag == 'Id' then
|
||||
cfg.scope :set (id_node[1], { id_node, ... })
|
||||
if down then down(id_node, ...) end
|
||||
if f then f(id_node, ...) end
|
||||
if up then up(id_node, ...) end
|
||||
elseif i==#id_list and id_node.tag=='Dots' then
|
||||
if down then down(id_node, ...) end
|
||||
if up then up(id_node, ...) end
|
||||
-- Do nothing, those are valid `Dots
|
||||
elseif ferror then
|
||||
-- Traverse error handling function
|
||||
ferror(id_node, ...)
|
||||
else
|
||||
error("Invalid binders list")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Generic walker generator.
|
||||
-- * if `cfg' has an entry matching the tree name, use this entry
|
||||
-- * if not, try to use the entry whose name matched the ast kind
|
||||
-- * if an entry is a table, look for 'up' and 'down' entries
|
||||
-- * if it is a function, consider it as a `down' traverser.
|
||||
----------------------------------------------------------------------
|
||||
local walker_builder = function(traverse)
|
||||
assert(traverse)
|
||||
return function (cfg, ...)
|
||||
if not cfg.scope then cfg.scope = M.newscope() end
|
||||
local down, up = cfg.down, cfg.up
|
||||
local broken = down and down(...)
|
||||
if broken ~= 'break' then M.traverse[traverse] (cfg, ...) end
|
||||
if up then up(...) end
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Declare [M.stat], [M.expr], [M.block].
|
||||
-- `M.binder_list` is not here, because `cfg.up` and `cfg.down` must
|
||||
-- be called on individual binders, not on the list itself.
|
||||
-- It's therefore handled in `traverse.binder_list()`
|
||||
----------------------------------------------------------------------
|
||||
for _, w in ipairs{ "stat", "expr", "block" } do --, "malformed", "unknown" } do
|
||||
M[w] = walker_builder (w, M.traverse[w])
|
||||
end
|
||||
|
||||
-- Don't call up/down callbacks on expr lists
|
||||
M.expr_list = M.traverse.expr_list
|
||||
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Try to guess the type of the AST then choose the right walkker.
|
||||
----------------------------------------------------------------------
|
||||
function M.guess (cfg, x, ...)
|
||||
assert(type(x)=='table', "arg #2 in a walker must be an AST")
|
||||
if M.tags.expr[x.tag] then return M.expr(cfg, x, ...) end
|
||||
if M.tags.stat[x.tag] then return M.stat(cfg, x, ...) end
|
||||
if not x.tag then return M.block(cfg, x, ...) end
|
||||
error ("Can't guess the AST type from tag "..(x.tag or '<none>'))
|
||||
end
|
||||
|
||||
local S = { }; S.__index = S
|
||||
|
||||
function M.newscope()
|
||||
local instance = { current = { } }
|
||||
instance.stack = { instance.current }
|
||||
setmetatable (instance, S)
|
||||
return instance
|
||||
end
|
||||
|
||||
function S :save(...)
|
||||
local current_copy = { }
|
||||
for a, b in pairs(self.current) do current_copy[a]=b end
|
||||
table.insert (self.stack, current_copy)
|
||||
if ... then return self :add(...) end
|
||||
end
|
||||
|
||||
function S :restore() self.current = table.remove (self.stack) end
|
||||
function S :get (var_name) return self.current[var_name] end
|
||||
function S :set (key, val) self.current[key] = val end
|
||||
|
||||
return M
|
||||
241
Utils/luarocks/share/lua/5.1/models/apimodel.lua
Normal file
241
Utils/luarocks/share/lua/5.1/models/apimodel.lua
Normal file
@@ -0,0 +1,241 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local M = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- API MODEL
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function M._file()
|
||||
local file = {
|
||||
-- FIELDS
|
||||
tag = "file",
|
||||
name = nil, -- string
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
types = {}, -- map from typename to type
|
||||
globalvars = {}, -- map from varname to item
|
||||
returns = {}, -- list of return
|
||||
|
||||
-- FUNCTIONS
|
||||
addtype = function (self,type)
|
||||
self.types[type.name] = type
|
||||
type.parent = self
|
||||
end,
|
||||
|
||||
mergetype = function (self,newtype,erase,erasesourcerangefield)
|
||||
local currenttype = self.types[newtype.name]
|
||||
if currenttype then
|
||||
-- merge recordtypedef
|
||||
if currenttype.tag =="recordtypedef" and newtype.tag == "recordtypedef" then
|
||||
-- merge fields
|
||||
for fieldname ,field in pairs( newtype.fields) do
|
||||
local currentfield = currenttype.fields[fieldname]
|
||||
if erase or not currentfield then
|
||||
currenttype:addfield(field)
|
||||
elseif erasesourcerangefield then
|
||||
if field.sourcerange.min and field.sourcerange.max then
|
||||
currentfield.sourcerange.min = field.sourcerange.min
|
||||
currentfield.sourcerange.max = field.sourcerange.max
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- merge descriptions and source ranges
|
||||
if erase then
|
||||
if newtype.description or newtype.description == "" then currenttype.description = newtype.description end
|
||||
if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end
|
||||
if newtype.sourcerange.min and newtype.sourcerange.max then
|
||||
currenttype.sourcerange.min = newtype.sourcerange.min
|
||||
currenttype.sourcerange.max = newtype.sourcerange.max
|
||||
end
|
||||
end
|
||||
-- merge functiontypedef
|
||||
elseif currenttype.tag == "functiontypedef" and newtype.tag == "functiontypedef" then
|
||||
-- merge params
|
||||
for i, param1 in ipairs(newtype.params) do
|
||||
local missing = true
|
||||
for j, param2 in ipairs(currenttype.params) do
|
||||
if param1.name == param2.name then
|
||||
missing = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if missing then
|
||||
table.insert(currenttype.params,param1)
|
||||
end
|
||||
end
|
||||
|
||||
-- merge descriptions and source ranges
|
||||
if erase then
|
||||
if newtype.description or newtype.description == "" then currenttype.description = newtype.description end
|
||||
if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end
|
||||
if newtype.sourcerange.min and newtype.sourcerange.max then
|
||||
currenttype.sourcerange.min = newtype.sourcerange.min
|
||||
currenttype.sourcerange.max = newtype.sourcerange.max
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self:addtype(newtype)
|
||||
end
|
||||
end,
|
||||
|
||||
addglobalvar = function (self,item)
|
||||
self.globalvars[item.name] = item
|
||||
item.parent = self
|
||||
end,
|
||||
|
||||
moduletyperef = function (self)
|
||||
if self and self.returns[1] and self.returns[1].types[1] then
|
||||
local typeref = self.returns[1].types[1]
|
||||
return typeref
|
||||
end
|
||||
end
|
||||
}
|
||||
return file
|
||||
end
|
||||
|
||||
function M._recordtypedef(name)
|
||||
local recordtype = {
|
||||
-- FIELDS
|
||||
tag = "recordtypedef",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
fields = {}, -- map from fieldname to field
|
||||
sourcerange = {min=0,max=0},
|
||||
|
||||
-- FUNCTIONS
|
||||
addfield = function (self,field)
|
||||
self.fields[field.name] = field
|
||||
field.parent = self
|
||||
end
|
||||
}
|
||||
return recordtype
|
||||
end
|
||||
|
||||
function M._functiontypedef(name)
|
||||
return {
|
||||
tag = "functiontypedef",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
params = {}, -- list of parameter
|
||||
returns = {} -- list of return
|
||||
}
|
||||
end
|
||||
|
||||
function M._parameter(name)
|
||||
return {
|
||||
tag = "parameter",
|
||||
name = name, -- string (mandatory)
|
||||
description = "", -- string
|
||||
type = nil -- typeref (external or internal or primitive typeref)
|
||||
}
|
||||
end
|
||||
|
||||
function M._item(name)
|
||||
return {
|
||||
-- FIELDS
|
||||
tag = "item",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
type = nil, -- typeref (external or internal or primitive typeref)
|
||||
occurrences = {}, -- list of identifier (see internalmodel)
|
||||
sourcerange = {min=0, max=0},
|
||||
|
||||
-- This is A TRICK
|
||||
-- This value is ALWAYS nil, except for internal purposes (short references).
|
||||
external = nil,
|
||||
|
||||
-- FUNCTIONS
|
||||
addoccurence = function (self,occ)
|
||||
table.insert(self.occurrences,occ)
|
||||
occ.definition = self
|
||||
end,
|
||||
|
||||
resolvetype = function (self,file)
|
||||
if self and self.type then
|
||||
if self.type.tag =="internaltyperef" then
|
||||
-- if file is not given try to retrieve it.
|
||||
if not file then
|
||||
if self.parent and self.parent.tag == 'recordtypedef' then
|
||||
file = self.parent.parent
|
||||
elseif self.parent.tag == 'file' then
|
||||
file = self.parent
|
||||
end
|
||||
end
|
||||
if file then return file.types[self.type.typename] end
|
||||
elseif self.type.tag =="inlinetyperef" then
|
||||
return self.type.def
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function M._externaltypref(modulename, typename)
|
||||
return {
|
||||
tag = "externaltyperef",
|
||||
modulename = modulename, -- string
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._internaltyperef(typename)
|
||||
return {
|
||||
tag = "internaltyperef",
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._primitivetyperef(typename)
|
||||
return {
|
||||
tag = "primitivetyperef",
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._moduletyperef(modulename,returnposition)
|
||||
return {
|
||||
tag = "moduletyperef",
|
||||
modulename = modulename, -- string
|
||||
returnposition = returnposition -- number
|
||||
}
|
||||
end
|
||||
|
||||
function M._exprtyperef(expression,returnposition)
|
||||
return {
|
||||
tag = "exprtyperef",
|
||||
expression = expression, -- expression (see internal model)
|
||||
returnposition = returnposition -- number
|
||||
}
|
||||
end
|
||||
|
||||
function M._inlinetyperef(definition)
|
||||
return {
|
||||
tag = "inlinetyperef",
|
||||
def = definition, -- expression (see internal model)
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
function M._return(description)
|
||||
return {
|
||||
tag = "return",
|
||||
description = description or "", -- string
|
||||
types = {} -- list of typref (external or internal or primitive typeref)
|
||||
}
|
||||
end
|
||||
return M
|
||||
459
Utils/luarocks/share/lua/5.1/models/apimodelbuilder.lua
Normal file
459
Utils/luarocks/share/lua/5.1/models/apimodelbuilder.lua
Normal file
@@ -0,0 +1,459 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local apimodel = require "models.apimodel"
|
||||
local ldp = require "models.ldparser"
|
||||
local Q = require "metalua.treequery"
|
||||
|
||||
local M = {}
|
||||
|
||||
local handledcomments={} -- cache to know the comment already handled
|
||||
|
||||
----
|
||||
-- UTILITY METHODS
|
||||
local primitivetypes = {
|
||||
['boolean'] = true,
|
||||
['function'] = true,
|
||||
['nil'] = true,
|
||||
['number'] = true,
|
||||
['string'] = true,
|
||||
['table'] = true,
|
||||
['thread'] = true,
|
||||
['userdata'] = true
|
||||
}
|
||||
|
||||
-- get or create the typedef with the name "name"
|
||||
local function gettypedef(_file,name,kind,sourcerangemin,sourcerangemax)
|
||||
local kind = kind or "recordtypedef"
|
||||
local _typedef = _file.types[name]
|
||||
if _typedef then
|
||||
if _typedef.tag == kind then return _typedef end
|
||||
else
|
||||
if kind == "recordtypedef" and name ~= "global" then
|
||||
local _recordtypedef = apimodel._recordtypedef(name)
|
||||
|
||||
-- define sourcerange
|
||||
_recordtypedef.sourcerange.min = sourcerangemin
|
||||
_recordtypedef.sourcerange.max = sourcerangemax
|
||||
|
||||
-- add to file if a name is defined
|
||||
if _recordtypedef.name then _file:addtype(_recordtypedef) end
|
||||
return _recordtypedef
|
||||
elseif kind == "functiontypedef" then
|
||||
-- TODO support function
|
||||
return nil
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- create a typeref from the typref doc_tag
|
||||
local function createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
local _typeref
|
||||
if dt_typeref.tag == "typeref" then
|
||||
if dt_typeref.module then
|
||||
-- manage external type
|
||||
_typeref = apimodel._externaltypref()
|
||||
_typeref.modulename = dt_typeref.module
|
||||
_typeref.typename = dt_typeref.type
|
||||
else
|
||||
if primitivetypes[dt_typeref.type] then
|
||||
-- manage primitive type
|
||||
_typeref = apimodel._primitivetyperef()
|
||||
_typeref.typename = dt_typeref.type
|
||||
else
|
||||
-- manage internal type
|
||||
_typeref = apimodel._internaltyperef()
|
||||
_typeref.typename = dt_typeref.type
|
||||
if _file then
|
||||
gettypedef(_file, _typeref.typename, "recordtypedef", sourcerangemin,sourcerangemax)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return _typeref
|
||||
end
|
||||
|
||||
-- create a return from the return doc_tag
|
||||
local function createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
local _return = apimodel._return()
|
||||
|
||||
_return.description = dt_return.description
|
||||
|
||||
-- manage typeref
|
||||
if dt_return.types then
|
||||
for _, dt_typeref in ipairs(dt_return.types) do
|
||||
local _typeref = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
if _typeref then
|
||||
table.insert(_return.types,_typeref)
|
||||
end
|
||||
end
|
||||
end
|
||||
return _return
|
||||
end
|
||||
|
||||
-- create a item from the field doc_tag
|
||||
local function createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
local _item = apimodel._item(dt_field.name)
|
||||
|
||||
if dt_field.shortdescription then
|
||||
_item.shortdescription = dt_field.shortdescription
|
||||
_item.description = dt_field.description
|
||||
else
|
||||
_item.shortdescription = dt_field.description
|
||||
end
|
||||
|
||||
-- manage typeref
|
||||
local dt_typeref = dt_field.type
|
||||
if dt_typeref then
|
||||
_item.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
return _item
|
||||
end
|
||||
|
||||
-- create a param from the param doc_tag
|
||||
local function createparam(dt_param,_file,sourcerangemin,sourcerangemax)
|
||||
if not dt_param.name then return nil end
|
||||
|
||||
local _parameter = apimodel._parameter(dt_param.name)
|
||||
_parameter.description = dt_param.description
|
||||
|
||||
-- manage typeref
|
||||
local dt_typeref = dt_param.type
|
||||
if dt_typeref then
|
||||
_parameter.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
return _parameter
|
||||
end
|
||||
|
||||
-- get or create the typedef with the name "name"
|
||||
function M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
if scope and not scope.module then
|
||||
if _item.name then
|
||||
if scope.type == "global" then
|
||||
_file:addglobalvar(_item)
|
||||
else
|
||||
local _recordtypedef = gettypedef (_file, scope.type ,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
_recordtypedef:addfield(_item)
|
||||
end
|
||||
else
|
||||
-- if no item name precise we store the scope in the item to be able to add it to the right parent later
|
||||
_item.scope = scope
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Function type counter
|
||||
local i = 0
|
||||
|
||||
-- Reset function type counter
|
||||
local function resetfunctiontypeidgenerator()
|
||||
i = 0
|
||||
end
|
||||
|
||||
-- Provides an unique index for a function type
|
||||
local function generatefunctiontypeid()
|
||||
i = i + 1
|
||||
return i
|
||||
end
|
||||
|
||||
-- generate a function type name
|
||||
local function generatefunctiontypename(_functiontypedef)
|
||||
local name = {"__"}
|
||||
if _functiontypedef.returns and _functiontypedef.returns[1] then
|
||||
local ret = _functiontypedef.returns[1]
|
||||
for _, type in ipairs(ret.types) do
|
||||
if type.typename then
|
||||
if type.modulename then
|
||||
table.insert(name,type.modulename)
|
||||
end
|
||||
table.insert(name,"#")
|
||||
table.insert(name,type.typename)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
table.insert(name,"=")
|
||||
if _functiontypedef.params then
|
||||
for _, param in ipairs(_functiontypedef.params) do
|
||||
local type = param.type
|
||||
if type then
|
||||
if type.typename then
|
||||
if type.modulename then
|
||||
table.insert(name,type.modulename)
|
||||
end
|
||||
table.insert(name,"#")
|
||||
table.insert(name,type.typename)
|
||||
else
|
||||
table.insert(name,"#unknown")
|
||||
end
|
||||
end
|
||||
table.insert(name,"[")
|
||||
table.insert(name,param.name)
|
||||
table.insert(name,"]")
|
||||
end
|
||||
end
|
||||
table.insert(name,"__")
|
||||
table.insert(name, generatefunctiontypeid())
|
||||
return table.concat(name)
|
||||
end
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
-- create the module api
|
||||
function M.createmoduleapi(ast,modulename)
|
||||
|
||||
-- Initialise function type naming
|
||||
resetfunctiontypeidgenerator()
|
||||
|
||||
local _file = apimodel._file()
|
||||
|
||||
local _comment2apiobj = {}
|
||||
|
||||
local function handlecomment(comment)
|
||||
|
||||
-- Extract information from tagged comments
|
||||
local parsedcomment = ldp.parse(comment[1])
|
||||
if not parsedcomment then return nil end
|
||||
|
||||
-- Get tags from the languages
|
||||
local regulartags = parsedcomment.tags
|
||||
|
||||
-- Will contain last API object generated from comments
|
||||
local _lastapiobject
|
||||
|
||||
-- if comment is an ld comment
|
||||
if regulartags then
|
||||
-- manage "module" comment
|
||||
if regulartags["module"] then
|
||||
-- get name
|
||||
_file.name = regulartags["module"][1].name or modulename
|
||||
_lastapiobject = _file
|
||||
|
||||
-- manage descriptions
|
||||
_file.shortdescription = parsedcomment.shortdescription
|
||||
_file.description = parsedcomment.description
|
||||
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
|
||||
-- manage returns
|
||||
if regulartags ["return"] then
|
||||
for _, dt_return in ipairs(regulartags ["return"]) do
|
||||
local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_file.returns,_return)
|
||||
end
|
||||
end
|
||||
-- if no returns on module create a defaultreturn of type #modulename
|
||||
if #_file.returns == 0 and _file.name then
|
||||
-- create internal type ref
|
||||
local _typeref = apimodel._internaltyperef()
|
||||
_typeref.typename = _file.name
|
||||
|
||||
-- create return
|
||||
local _return = apimodel._return()
|
||||
table.insert(_return.types,_typeref)
|
||||
|
||||
-- add return
|
||||
table.insert(_file.returns,_return)
|
||||
|
||||
--create recordtypedef is not define
|
||||
gettypedef(_file,_typeref.typename,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
end
|
||||
-- manage "type" comment
|
||||
elseif regulartags["type"] and regulartags["type"][1].name ~= "global" then
|
||||
local dt_type = regulartags["type"][1];
|
||||
-- create record type if it doesn't exist
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local _recordtypedef = gettypedef (_file, dt_type.name ,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
_lastapiobject = _recordtypedef
|
||||
|
||||
-- re-set sourcerange in case the type was created before the type tag
|
||||
_recordtypedef.sourcerange.min = sourcerangemin
|
||||
_recordtypedef.sourcerange.max = sourcerangemax
|
||||
|
||||
-- manage description
|
||||
_recordtypedef.shortdescription = parsedcomment.shortdescription
|
||||
_recordtypedef.description = parsedcomment.description
|
||||
|
||||
-- manage fields
|
||||
if regulartags["field"] then
|
||||
for _, dt_field in ipairs(regulartags["field"]) do
|
||||
local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
-- define sourcerange only if we create it
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
if _item then _recordtypedef:addfield(_item) end
|
||||
end
|
||||
end
|
||||
elseif regulartags["field"] then
|
||||
local dt_field = regulartags["field"][1]
|
||||
|
||||
-- create item
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
_item.shortdescription = parsedcomment.shortdescription
|
||||
_item.description = parsedcomment.description
|
||||
_lastapiobject = _item
|
||||
|
||||
-- define sourcerange
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
|
||||
-- add item to its parent
|
||||
local scope = regulartags["field"][1].parent
|
||||
M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
elseif regulartags["function"] or regulartags["param"] or regulartags["return"] then
|
||||
-- create item
|
||||
local _item = apimodel._item()
|
||||
_item.shortdescription = parsedcomment.shortdescription
|
||||
_item.description = parsedcomment.description
|
||||
_lastapiobject = _item
|
||||
|
||||
-- set name
|
||||
if regulartags["function"] then _item.name = regulartags["function"][1].name end
|
||||
|
||||
-- define sourcerange
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
|
||||
|
||||
-- create function type
|
||||
local _functiontypedef = apimodel._functiontypedef()
|
||||
_functiontypedef.shortdescription = parsedcomment.shortdescription
|
||||
_functiontypedef.description = parsedcomment.description
|
||||
|
||||
|
||||
-- manage params
|
||||
if regulartags["param"] then
|
||||
for _, dt_param in ipairs(regulartags["param"]) do
|
||||
local _param = createparam(dt_param,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_functiontypedef.params,_param)
|
||||
end
|
||||
end
|
||||
|
||||
-- manage returns
|
||||
if regulartags["return"] then
|
||||
for _, dt_return in ipairs(regulartags["return"]) do
|
||||
local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_functiontypedef.returns,_return)
|
||||
end
|
||||
end
|
||||
|
||||
-- add type name
|
||||
_functiontypedef.name = generatefunctiontypename(_functiontypedef)
|
||||
_file:addtype(_functiontypedef)
|
||||
|
||||
-- create ref to this type
|
||||
local _internaltyperef = apimodel._internaltyperef()
|
||||
_internaltyperef.typename = _functiontypedef.name
|
||||
_item.type=_internaltyperef
|
||||
|
||||
-- add item to its parent
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local scope = (regulartags["function"] and regulartags["function"][1].parent) or nil
|
||||
M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
end
|
||||
|
||||
-- when we could not know which type of api object it is, we suppose this is an item
|
||||
if not _lastapiobject then
|
||||
_lastapiobject = apimodel._item()
|
||||
_lastapiobject.shortdescription = parsedcomment.shortdescription
|
||||
_lastapiobject.description = parsedcomment.description
|
||||
_lastapiobject.sourcerange.min = comment.lineinfo.first.offset
|
||||
_lastapiobject.sourcerange.max = comment.lineinfo.last.offset
|
||||
end
|
||||
|
||||
--
|
||||
-- Store user defined tags
|
||||
--
|
||||
local thirdtags = parsedcomment and parsedcomment.unknowntags
|
||||
if thirdtags then
|
||||
-- Define a storage index for user defined tags on current API element
|
||||
if not _lastapiobject.metadata then _lastapiobject.metadata = {} end
|
||||
|
||||
-- Loop over user defined tags
|
||||
for usertag, taglist in pairs(thirdtags) do
|
||||
if not _lastapiobject.metadata[ usertag ] then
|
||||
_lastapiobject.metadata[ usertag ] = {
|
||||
tag = usertag
|
||||
}
|
||||
end
|
||||
for _, tag in ipairs( taglist ) do
|
||||
table.insert(_lastapiobject.metadata[usertag], tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if we create an api object linked it to
|
||||
_comment2apiobj[comment] =_lastapiobject
|
||||
end
|
||||
|
||||
local function parsecomment(node, parent, ...)
|
||||
-- check for comments before this node
|
||||
if node.lineinfo and node.lineinfo.first.comments then
|
||||
local comments = node.lineinfo.first.comments
|
||||
-- check all comments
|
||||
for _,comment in ipairs(comments) do
|
||||
-- if not already handled
|
||||
if not handledcomments[comment] then
|
||||
handlecomment(comment)
|
||||
handledcomments[comment]=true
|
||||
end
|
||||
end
|
||||
end
|
||||
-- check for comments after this node
|
||||
if node.lineinfo and node.lineinfo.last.comments then
|
||||
local comments = node.lineinfo.last.comments
|
||||
-- check all comments
|
||||
for _,comment in ipairs(comments) do
|
||||
-- if not already handled
|
||||
if not handledcomments[comment] then
|
||||
handlecomment(comment)
|
||||
handledcomments[comment]=true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Q(ast):filter(function(x) return x.tag~=nil end):foreach(parsecomment)
|
||||
return _file, _comment2apiobj
|
||||
end
|
||||
|
||||
|
||||
function M.extractlocaltype ( commentblock,_file)
|
||||
if not commentblock then return nil end
|
||||
|
||||
local stringcomment = commentblock[1]
|
||||
|
||||
local parsedtag = ldp.parseinlinecomment(stringcomment)
|
||||
if parsedtag then
|
||||
local sourcerangemin = commentblock.lineinfo.first.offset
|
||||
local sourcerangemax = commentblock.lineinfo.last.offset
|
||||
|
||||
return createtyperef(parsedtag,_file,sourcerangemin,sourcerangemax), parsedtag.description
|
||||
end
|
||||
|
||||
return nil, stringcomment
|
||||
end
|
||||
|
||||
M.generatefunctiontypename = generatefunctiontypename
|
||||
|
||||
return M
|
||||
65
Utils/luarocks/share/lua/5.1/models/internalmodel.lua
Normal file
65
Utils/luarocks/share/lua/5.1/models/internalmodel.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local M = {}
|
||||
|
||||
function M._internalcontent()
|
||||
return {
|
||||
content = nil, -- block
|
||||
unknownglobalvars = {}, -- list of item
|
||||
tag = "MInternalContent"
|
||||
}
|
||||
end
|
||||
|
||||
function M._block()
|
||||
return {
|
||||
content = {}, -- list of expr (identifier, index, call, invoke, block)
|
||||
localvars = {}, -- list of {var=item, scope ={min,max}}
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MBlock"
|
||||
}
|
||||
end
|
||||
|
||||
function M._identifier()
|
||||
return {
|
||||
definition = nil, -- item
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MIdentifier"
|
||||
}
|
||||
end
|
||||
|
||||
function M._index(key, value)
|
||||
return {
|
||||
left= key, -- expr (identifier, index, call, invoke, block)
|
||||
right= value, -- string
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MIndex"
|
||||
}
|
||||
end
|
||||
|
||||
function M._call(funct)
|
||||
return {
|
||||
func = funct, -- expr (identifier, index, call, invoke, block)
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MCall"
|
||||
}
|
||||
end
|
||||
|
||||
function M._invoke(name, expr)
|
||||
return {
|
||||
functionname = name, -- string
|
||||
record = expr, -- expr (identifier, index, call, invoke, block)
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MInvoke"
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
861
Utils/luarocks/share/lua/5.1/models/internalmodelbuilder.mlua
Normal file
861
Utils/luarocks/share/lua/5.1/models/internalmodelbuilder.mlua
Normal file
@@ -0,0 +1,861 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
-{ extension ('match', ...) }
|
||||
|
||||
local Q = require 'metalua.treequery'
|
||||
|
||||
local internalmodel = require 'models.internalmodel'
|
||||
local apimodel = require 'models.apimodel'
|
||||
local apimodelbuilder = require 'models.apimodelbuilder'
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Analyzes an AST and returns two tables
|
||||
-- * `locals`, which associates `Id{ } nodes which create a local variable
|
||||
-- to a list of the `Id{ } occurrence nodes of that variable;
|
||||
-- * `globals` which associates variable names to occurrences of
|
||||
-- global variables having that name.
|
||||
function bindings(ast)
|
||||
local locals, globals = { }, { }
|
||||
local function f(id, ...)
|
||||
local name = id[1]
|
||||
if Q.is_binder(id, ...) then
|
||||
local binder = ... -- parent is the binder
|
||||
locals[binder] = locals[binder] or { }
|
||||
locals[binder][name]={ }
|
||||
else
|
||||
local _, binder = Q.get_binder(id, ...)
|
||||
if binder then -- this is a local
|
||||
table.insert(locals[binder][name], id)
|
||||
else
|
||||
local g = globals[name]
|
||||
if g then table.insert(g, id) else globals[name]={id} end
|
||||
end
|
||||
end
|
||||
end
|
||||
Q(ast) :filter('Id') :foreach(f)
|
||||
return locals, globals
|
||||
end
|
||||
|
||||
-- --------------------------------------
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return the comment linked before to this node
|
||||
-- ----------------------------------------------------------
|
||||
local function getlinkedcommentbefore(node)
|
||||
local function _getlinkedcomment(node,line)
|
||||
if node and node.lineinfo and node.lineinfo.first.line == line then
|
||||
-- get the last comment before (the nearest of code)
|
||||
local comments = node.lineinfo.first.comments
|
||||
local comment = comments and comments[#comments]
|
||||
if comment and comment.lineinfo.last.line == line-1 then
|
||||
-- ignore the comment if there are code before on the same line
|
||||
if node.lineinfo.first.facing and (node.lineinfo.first.facing.line ~= comment.lineinfo.first.line) then
|
||||
return comment
|
||||
end
|
||||
else
|
||||
return _getlinkedcomment(node.parent,line)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if node.lineinfo and node.lineinfo.first.line then
|
||||
return _getlinkedcomment(node,node.lineinfo.first.line)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return the comment linked after to this node
|
||||
-- ----------------------------------------------------------
|
||||
local function getlinkedcommentafter(node)
|
||||
local function _getlinkedcomment(node,line)
|
||||
if node and node.lineinfo and node.lineinfo.last.line == line then
|
||||
-- get the first comment after (the nearest of code)
|
||||
local comments = node.lineinfo.last.comments
|
||||
local comment = comments and comments[1]
|
||||
if comment and comment.lineinfo.first.line == line then
|
||||
return comment
|
||||
else
|
||||
return _getlinkedcomment(node.parent,line)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if node.lineinfo and node.lineinfo.last.line then
|
||||
return _getlinkedcomment(node,node.lineinfo.last.line)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return true if this node is a block for the internal representation
|
||||
-- ----------------------------------------------------------
|
||||
local supported_b = {
|
||||
Function = true,
|
||||
Do = true,
|
||||
While = true,
|
||||
Fornum = true,
|
||||
Forin = true,
|
||||
Repeat = true,
|
||||
}
|
||||
local function supportedblock(node, parent)
|
||||
return supported_b[ node.tag ] or
|
||||
(parent and parent.tag == "If" and node.tag == nil)
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create a block from the metalua node
|
||||
-- ----------------------------------------------------------
|
||||
local function createblock(block, parent)
|
||||
local _block = internalmodel._block()
|
||||
match block with
|
||||
| `Function{param, body}
|
||||
| `Do{...}
|
||||
| `Fornum {identifier, min, max, body}
|
||||
| `Forin {identifiers, exprs, body}
|
||||
| `Repeat {body, expr} ->
|
||||
_block.sourcerange.min = block.lineinfo.first.offset
|
||||
_block.sourcerange.max = block.lineinfo.last.offset
|
||||
| `While {expr, body} ->
|
||||
_block.sourcerange.min = body.lineinfo.first.facing.offset
|
||||
_block.sourcerange.max = body.lineinfo.last.facing.offset
|
||||
| _ ->
|
||||
if parent and parent.tag == "If" and block.tag == nil then
|
||||
_block.sourcerange.min = block.lineinfo.first.facing.offset
|
||||
_block.sourcerange.max = block.lineinfo.last.facing.offset
|
||||
end
|
||||
end
|
||||
return _block
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return true if this node is a expression in the internal representation
|
||||
-- ----------------------------------------------------------
|
||||
local supported_e = {
|
||||
Index = true,
|
||||
Id = true,
|
||||
Call = true,
|
||||
Invoke = true
|
||||
}
|
||||
local function supportedexpr(node)
|
||||
return supported_e[ node.tag ]
|
||||
end
|
||||
|
||||
local idto_block = {} -- cache from metalua id to internal model block
|
||||
local idto_identifier = {} -- cache from metalua id to internal model indentifier
|
||||
local expreto_expression = {} -- cache from metalua expression to internal model expression
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create an expression from a metalua node
|
||||
-- ----------------------------------------------------------
|
||||
local function createexpr(expr,_block)
|
||||
local _expr = nil
|
||||
|
||||
match expr with
|
||||
| `Id { name } ->
|
||||
-- we store the block which hold this node
|
||||
-- to be able to define
|
||||
idto_block[expr]= _block
|
||||
|
||||
-- if expr has not line info, it means expr has no representation in the code
|
||||
-- so we don't need it.
|
||||
if not expr.lineinfo then return nil end
|
||||
|
||||
-- create identifier
|
||||
local _identifier = internalmodel._identifier()
|
||||
idto_identifier[expr]= _identifier
|
||||
_expr = _identifier
|
||||
| `Index { innerexpr, `String{fieldname} } ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create index
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._index(_expression,fieldname) end
|
||||
| `Call{innerexpr, ...} ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create call
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._call(_expression) end
|
||||
| `Invoke{innerexpr,`String{functionname},...} ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create invoke
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._invoke(functionname,_expression) end
|
||||
| _ ->
|
||||
end
|
||||
|
||||
if _expr then
|
||||
_expr.sourcerange.min = expr.lineinfo.first.offset
|
||||
_expr.sourcerange.max = expr.lineinfo.last.offset
|
||||
|
||||
expreto_expression[expr] = _expr
|
||||
end
|
||||
|
||||
return _expr
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create block and expression node
|
||||
-- ----------------------------------------------------------
|
||||
local function createtreestructure(ast)
|
||||
-- create internal content
|
||||
local _internalcontent = internalmodel._internalcontent()
|
||||
|
||||
-- create root block
|
||||
local _block = internalmodel._block()
|
||||
local _blocks = { _block }
|
||||
_block.sourcerange.min = ast.lineinfo.first.facing.offset
|
||||
-- TODO remove the math.max when we support partial AST
|
||||
_block.sourcerange.max = math.max(ast.lineinfo.last.facing.offset, 10000)
|
||||
|
||||
_internalcontent.content = _block
|
||||
|
||||
-- visitor function (down)
|
||||
local function down (node,parent)
|
||||
if supportedblock(node,parent) then
|
||||
-- create the block
|
||||
local _block = createblock(node,parent)
|
||||
-- add it to parent block
|
||||
table.insert(_blocks[#_blocks].content, _block)
|
||||
-- enqueue the last block to know the "current" block
|
||||
table.insert(_blocks,_block)
|
||||
elseif supportedexpr(node) then
|
||||
-- we handle expression only if it was not already do
|
||||
if not expreto_expression[node] then
|
||||
-- create expr
|
||||
local _expression = createexpr(node,_blocks[#_blocks])
|
||||
-- add it to parent block
|
||||
if _expression then
|
||||
table.insert(_blocks[#_blocks].content, _expression)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- visitor function (up)
|
||||
local function up (node, parent)
|
||||
if supportedblock(node,parent) then
|
||||
-- dequeue the last block to know the "current" block
|
||||
table.remove(_blocks,#_blocks)
|
||||
end
|
||||
end
|
||||
|
||||
-- visit ast and build internal model
|
||||
Q(ast):foreach(down,up)
|
||||
|
||||
return _internalcontent
|
||||
end
|
||||
|
||||
local getitem
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create the type from the node and position
|
||||
-- ----------------------------------------------------------
|
||||
local function createtype(node,position,comment2apiobj,file)
|
||||
-- create module type ref
|
||||
match node with
|
||||
| `Call{ `Id "require", `String {modulename}} ->
|
||||
return apimodel._moduletyperef(modulename,position)
|
||||
| `Function {params, body} ->
|
||||
-- create the functiontypedef from code
|
||||
local _functiontypedef = apimodel._functiontypedef()
|
||||
for _, p in ipairs(params) do
|
||||
-- create parameters
|
||||
local paramname
|
||||
if p.tag=="Dots" then
|
||||
paramname = "..."
|
||||
else
|
||||
paramname = p[1]
|
||||
end
|
||||
local _param = apimodel._parameter(paramname)
|
||||
table.insert(_functiontypedef.params,_param)
|
||||
end
|
||||
_functiontypedef.name = "___" -- no name for inline type
|
||||
|
||||
return apimodel._inlinetyperef(_functiontypedef)
|
||||
| `String {value} ->
|
||||
local typeref = apimodel._primitivetyperef("string")
|
||||
return typeref
|
||||
| `Number {value} ->
|
||||
local typeref = apimodel._primitivetyperef("number")
|
||||
return typeref
|
||||
| `True | `False ->
|
||||
local typeref = apimodel._primitivetyperef("boolean")
|
||||
return typeref
|
||||
| `Table {...} ->
|
||||
-- create recordtypedef from code
|
||||
local _recordtypedef = apimodel._recordtypedef("___") -- no name for inline type
|
||||
-- for each element of the table
|
||||
for i=1,select("#", ...) do
|
||||
local pair = select(i, ...)
|
||||
-- if this is a pair we create a new item in the type
|
||||
if pair.tag == "Pair" then
|
||||
-- create an item
|
||||
local _item = getitem(pair,nil, comment2apiobj,file)
|
||||
if _item then
|
||||
_recordtypedef:addfield(_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
return apimodel._inlinetyperef(_recordtypedef)
|
||||
| _ ->
|
||||
end
|
||||
-- if node is an expression supported
|
||||
local supportedexpr = expreto_expression[node]
|
||||
if supportedexpr then
|
||||
-- create expression type ref
|
||||
return apimodel._exprtyperef(supportedexpr,position)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function completeapidoctype(apidoctype,itemname,init,file,comment2apiobj)
|
||||
if not apidoctype.name then
|
||||
apidoctype.name = itemname
|
||||
file:mergetype(apidoctype)
|
||||
end
|
||||
|
||||
-- create type from code
|
||||
local typeref = createtype(init,1,comment2apiobj,file)
|
||||
if typeref and typeref.tag == "inlinetyperef"
|
||||
and typeref.def.tag == "recordtypedef" then
|
||||
|
||||
-- set the name
|
||||
typeref.def.name = apidoctype.name
|
||||
|
||||
-- merge the type with priority to documentation except for source range
|
||||
file:mergetype(typeref.def,false,true)
|
||||
end
|
||||
end
|
||||
|
||||
local function completeapidocitem (apidocitem, itemname, init, file, binder, comment2apiobj)
|
||||
-- manage the case item has no name
|
||||
if not apidocitem.name then
|
||||
apidocitem.name = itemname
|
||||
|
||||
-- if item has no name this means it could not be attach to a parent
|
||||
if apidocitem.scope then
|
||||
apimodelbuilder.additemtoparent(file,apidocitem,apidocitem.scope,apidocitem.sourcerange.min,apidocitem.sourcerange.max)
|
||||
apidocitem.scope = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- for function try to merge definition
|
||||
local apitype = apidocitem:resolvetype(file)
|
||||
if apitype and apitype.tag == "functiontypedef" then
|
||||
local codetype = createtype(init,1,comment2apiobj,file)
|
||||
if codetype and codetype.tag =="inlinetyperef" then
|
||||
codetype.def.name = apitype.name
|
||||
file:mergetype(codetype.def)
|
||||
end
|
||||
end
|
||||
|
||||
-- manage the case item has no type
|
||||
if not apidocitem.type then
|
||||
-- extract typing from comment
|
||||
local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
|
||||
if type then
|
||||
apidocitem.type = type
|
||||
else
|
||||
-- if not found extracttype from code
|
||||
apidocitem.type = createtype(init,1,comment2apiobj,file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create or get the item finding in the binder with the given itemname
|
||||
-- return also the ast node corresponding to this item
|
||||
-- ----------------------------------------------------------
|
||||
getitem = function (binder, itemname, comment2apiobj, file)
|
||||
|
||||
-- local function to create item
|
||||
local function createitem(itemname, astnode, itemtype, description)
|
||||
local _item = apimodel._item(itemname)
|
||||
if description then _item.description = description end
|
||||
_item.type = itemtype
|
||||
if astnode and astnode.lineinfo then
|
||||
_item.sourcerange.min = astnode.lineinfo.first.offset
|
||||
_item.sourcerange.max = astnode.lineinfo.last.offset
|
||||
end
|
||||
return _item, astnode
|
||||
end
|
||||
|
||||
-- try to match binder with known patter of item declaration
|
||||
match binder with
|
||||
| `Pair {string, init}
|
||||
| `Set { {`Index { right , string}}, {init,...}} if string and string.tag =="String" ->
|
||||
-- Pair and set is for searching field from type ..
|
||||
-- if the itemname is given this mean we search for a local or a global not a field type.
|
||||
if not itemname then
|
||||
local itemname = string[1]
|
||||
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidocitem(apiobj, itemname, init,file,binder,comment2apiobj)
|
||||
-- for item use code source range rather than doc source range
|
||||
if string and string.lineinfo then
|
||||
apiobj.sourcerange.min = string.lineinfo.first.offset
|
||||
apiobj.sourcerange.max = string.lineinfo.last.offset
|
||||
end
|
||||
return apiobj, string
|
||||
end
|
||||
elseif apiobj.tag=="recordtypedef" then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidoctype(apiobj, itemname, init,file,comment2apiobj)
|
||||
return createitem(itemname, string, apimodel._internaltyperef(apiobj.name), nil)
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
local desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, string, type, desc )
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, string, createtype(init,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
| `Set {ids, inits}
|
||||
| `Local {ids, inits} ->
|
||||
-- if this is a single local var declaration
|
||||
-- we check if there are a comment block linked and try to extract the type
|
||||
if #ids == 1 then
|
||||
local currentid, currentinit = ids[1],inits[1]
|
||||
-- ignore non Ids node
|
||||
if currentid.tag ~= 'Id' or currentid[1] ~= itemname then return nil end
|
||||
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
-- use code to complete api information if it's necessary
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
completeapidocitem(apiobj, itemname, currentinit,file,binder,comment2apiobj)
|
||||
-- if this is a global var or if is has no parent
|
||||
-- we do not create a new item
|
||||
if not apiobj.parent or apiobj.parent == file then
|
||||
-- for item use code source range rather than doc source range
|
||||
if currentid and currentid.lineinfo then
|
||||
apiobj.sourcerange.min = currentid.lineinfo.first.offset
|
||||
apiobj.sourcerange.max = currentid.lineinfo.last.offset
|
||||
end
|
||||
return apiobj, currentid
|
||||
else
|
||||
return createitem(itemname, currentid, apiobj.type, nil)
|
||||
end
|
||||
end
|
||||
elseif apiobj.tag=="recordtypedef" then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidoctype(apiobj, itemname, currentinit,file,comment2apiobj)
|
||||
return createitem(itemname, currentid, apimodel._internaltyperef(apiobj.name), nil)
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, currentid, type, desc)
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, currentid, createtype(currentinit,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
-- else we use code to extract the type
|
||||
local init,returnposition = nil,1
|
||||
for i,id in ipairs(ids) do
|
||||
-- calculate the current return position
|
||||
if init and (init.tag == "Call" or init.tag == "Invoke") then
|
||||
-- if previous init was a call or an invoke
|
||||
-- we increment the returnposition
|
||||
returnposition= returnposition+1
|
||||
else
|
||||
-- if init is not a function call
|
||||
-- we change the init used to determine the type
|
||||
init = inits[i]
|
||||
end
|
||||
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- create type from init node and return position
|
||||
return createitem (itemname, id, createtype(init,returnposition,comment2apiobj,file),nil)
|
||||
end
|
||||
end
|
||||
| `Function {params, body} ->
|
||||
for i,id in ipairs(params) do
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- extract param's type from luadocumentation
|
||||
local obj = comment2apiobj[getlinkedcommentbefore(binder)]
|
||||
if obj and obj.tag=="item" then
|
||||
local typedef = obj:resolvetype(file)
|
||||
if typedef and typedef.tag =="functiontypedef" then
|
||||
for j, param in ipairs(typedef.params) do
|
||||
if i==j then
|
||||
if i ==1 and itemname == "self" and param.type == nil
|
||||
and obj.parent and obj.parent.tag == "recordtypedef" and obj.parent.name then
|
||||
param.type = apimodel._internaltyperef(obj.parent.name)
|
||||
end
|
||||
-- TODO perhaps we must clone the typeref
|
||||
return createitem(itemname,id, param.type,param.description)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
end
|
||||
| `Forin {ids, expr, body} ->
|
||||
for i,id in ipairs(ids) do
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- return data : we can not guess the type for now
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
end
|
||||
| `Fornum {id, ...} ->
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- return data : we can not guess the type for now
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
| `Localrec {{id}, {func}} ->
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidocitem(apiobj, itemname, func,file,binder,comment2apiobj)
|
||||
return createitem(itemname,id,apiobj.type,nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, id, type, desc)
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, id, createtype(func,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
| _ ->
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- Search from Id node to Set node to find field of type.
|
||||
--
|
||||
-- Lua code : table.field1.field2 = 12
|
||||
-- looks like that in metalua :
|
||||
-- `Set{
|
||||
-- `Index { `Index { `Id "table", `String "field1" },
|
||||
-- `String "field2"},
|
||||
-- `Number "12"}
|
||||
-- ----------------------------------------------------------
|
||||
local function searchtypefield(node,_currentitem,comment2apiobj,file)
|
||||
|
||||
-- we are just interested :
|
||||
-- by item which is field of recordtypedef
|
||||
-- by ast node which are Index
|
||||
if _currentitem then
|
||||
local type = _currentitem:resolvetype(file)
|
||||
if type and type.tag == "recordtypedef" then
|
||||
if node and node.tag == "Index" then
|
||||
local rightpart = node[2]
|
||||
local _newcurrentitem = type.fields[rightpart[1]]
|
||||
|
||||
if _newcurrentitem then
|
||||
-- if this index represent a known field of the type we continue to search
|
||||
searchtypefield (node.parent,_newcurrentitem,comment2apiobj,file)
|
||||
else
|
||||
-- if not, this is perhaps a new field, but
|
||||
-- to be a new field this index must be include in a Set
|
||||
if node.parent and node.parent.tag =="Set" then
|
||||
-- in this case we create the new item ans add it to the type
|
||||
local set = node.parent
|
||||
local item, string = getitem(set,nil, comment2apiobj,file)
|
||||
-- add this item to the type, only if it has no parent and if this type does not contain already this field
|
||||
if item and not item.parent and string and not type.fields[string[1]] then
|
||||
type:addfield(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create local vars, global vars and linked it with theirs occurences
|
||||
-- ----------------------------------------------------------
|
||||
local function createvardefinitions(_internalcontent,ast,file,comment2apiobj)
|
||||
-- use bindings to get locals and globals definition
|
||||
local locals, globals = bindings( ast )
|
||||
|
||||
-- create locals var
|
||||
for binder, namesAndOccurrences in pairs(locals) do
|
||||
for name, occurrences in pairs(namesAndOccurrences) do
|
||||
-- get item, id
|
||||
local _item, id = getitem(binder, name,comment2apiobj,file)
|
||||
if id then
|
||||
-- add definition as occurence
|
||||
-- we consider the identifier in the binder as an occurence
|
||||
local _identifierdef = idto_identifier[id]
|
||||
if _identifierdef then
|
||||
table.insert(_item.occurrences, _identifierdef)
|
||||
_identifierdef.definition = _item
|
||||
end
|
||||
|
||||
-- add occurences
|
||||
for _,occurrence in ipairs(occurrences) do
|
||||
searchtypefield(occurrence.parent, _item,comment2apiobj,file)
|
||||
local _identifier = idto_identifier[occurrence]
|
||||
if _identifier then
|
||||
table.insert(_item.occurrences, _identifier)
|
||||
_identifier.definition = _item
|
||||
end
|
||||
end
|
||||
|
||||
-- add item to block
|
||||
local _block = idto_block[id]
|
||||
table.insert(_block.localvars,{item=_item,scope = {min=0,max=0}})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- create globals var
|
||||
for name, occurrences in pairs( globals ) do
|
||||
|
||||
-- get or create definition
|
||||
local _item = file.globalvars[name]
|
||||
local binder = occurrences[1].parent
|
||||
if not _item then
|
||||
-- global declaration is only if the first occurence in left part of a 'Set'
|
||||
if binder and binder.tag == "Set" then
|
||||
_item = getitem(binder, name,comment2apiobj,file)
|
||||
end
|
||||
|
||||
-- if we find and item this is a global var declaration
|
||||
if _item then
|
||||
file:addglobalvar(_item)
|
||||
else
|
||||
-- else it is an unknown global var
|
||||
_item = apimodel._item(name)
|
||||
local _firstoccurrence = idto_identifier[occurrences[1]]
|
||||
if _firstoccurrence then
|
||||
_item.sourcerange.min = _firstoccurrence.sourcerange.min
|
||||
_item.sourcerange.max = _firstoccurrence.sourcerange.max
|
||||
end
|
||||
table.insert(_internalcontent.unknownglobalvars,_item)
|
||||
end
|
||||
else
|
||||
-- if the global var definition already exists, we just try to it
|
||||
if binder then
|
||||
match binder with
|
||||
| `Set {ids, inits} ->
|
||||
-- manage case only if there are 1 element in the Set
|
||||
if #ids == 1 then
|
||||
local currentid, currentinit = ids[1],inits[1]
|
||||
-- ignore non Ids node and bad name
|
||||
if currentid.tag == 'Id' and currentid[1] == name then
|
||||
completeapidocitem(_item, name, currentinit,file,binder,comment2apiobj)
|
||||
|
||||
if currentid and currentid.lineinfo then
|
||||
_item.sourcerange.min = currentid.lineinfo.first.offset
|
||||
_item.sourcerange.max = currentid.lineinfo.last.offset
|
||||
end
|
||||
end
|
||||
end
|
||||
| _ ->
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- add occurences
|
||||
for _,occurence in ipairs(occurrences) do
|
||||
local _identifier = idto_identifier[occurence]
|
||||
searchtypefield(occurence.parent, _item,comment2apiobj,file)
|
||||
if _identifier then
|
||||
table.insert(_item.occurrences, _identifier)
|
||||
_identifier.definition = _item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- add parent to all ast node
|
||||
-- ----------------------------------------------------------
|
||||
local function addparents(ast)
|
||||
-- visitor function (down)
|
||||
local function down (node,parent)
|
||||
node.parent = parent
|
||||
end
|
||||
|
||||
-- visit ast and build internal model
|
||||
Q(ast):foreach(down,up)
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- try to detect a module declaration from code
|
||||
-- ----------------------------------------------------------
|
||||
local function searchmodule(ast,file,comment2apiobj,modulename)
|
||||
-- if the last statement is a return
|
||||
if ast then
|
||||
local laststatement = ast[#ast]
|
||||
if laststatement and laststatement.tag == "Return" then
|
||||
-- and if the first expression returned is an identifier.
|
||||
local firstexpr = laststatement[1]
|
||||
if firstexpr and firstexpr.tag == "Id" then
|
||||
-- get identifier in internal model
|
||||
local _identifier = idto_identifier [firstexpr]
|
||||
-- the definition should be an inline type
|
||||
if _identifier
|
||||
and _identifier.definition
|
||||
and _identifier.definition.type
|
||||
and _identifier.definition.type.tag == "inlinetyperef"
|
||||
and _identifier.definition.type.def.tag == "recordtypedef" then
|
||||
|
||||
--set modulename if needed
|
||||
if not file.name then file.name = modulename end
|
||||
|
||||
-- create or merge type
|
||||
local _type = _identifier.definition.type.def
|
||||
_type.name = modulename
|
||||
|
||||
-- if file (module) has no documentation add item documentation to it
|
||||
-- else add it to the type.
|
||||
if not file.description or file.description == "" then
|
||||
file.description = _identifier.definition.description
|
||||
else
|
||||
_type.description = _identifier.definition.description
|
||||
end
|
||||
_identifier.definition.description = ""
|
||||
if not file.shortdescription or file.shortdescription == "" then
|
||||
file.shortdescription = _identifier.definition.shortdescription
|
||||
else
|
||||
_type.shortdescription = _identifier.definition.shortdescription
|
||||
end
|
||||
_identifier.definition.shortdescription = ""
|
||||
|
||||
-- WORKAROUND FOR BUG 421622: [outline]module selection in outline does not select it in texteditor
|
||||
--_type.sourcerange.min = _identifier.definition.sourcerange.min
|
||||
--_type.sourcerange.max = _identifier.definition.sourcerange.max
|
||||
|
||||
-- merge the type with priority to documentation except for source range
|
||||
file:mergetype(_type,false,true)
|
||||
|
||||
-- create return if needed
|
||||
if not file.returns[1] then
|
||||
file.returns[1] = apimodel._return()
|
||||
file.returns[1].types = { apimodel._internaltyperef(modulename) }
|
||||
end
|
||||
|
||||
-- change the type of the identifier
|
||||
_identifier.definition.type = apimodel._internaltyperef(modulename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create the internalcontent from an ast metalua
|
||||
-- ----------------------------------------------------------
|
||||
function M.createinternalcontent (ast,file,comment2apiobj,modulename)
|
||||
-- init cache
|
||||
idto_block = {}
|
||||
idto_identifier = {}
|
||||
expreto_expression = {}
|
||||
comment2apiobj = comment2apiobj or {}
|
||||
file = file or apimodel._file()
|
||||
|
||||
-- execute code safely to be sure to clean cache correctly
|
||||
local internalcontent
|
||||
local ok, errmsg = pcall(function ()
|
||||
-- add parent to all node
|
||||
addparents(ast)
|
||||
|
||||
-- create block and expression node
|
||||
internalcontent = createtreestructure(ast)
|
||||
|
||||
-- create Local vars, global vars and linked occurences (Items)
|
||||
createvardefinitions(internalcontent,ast,file,comment2apiobj)
|
||||
|
||||
-- try to dectect module information from code
|
||||
local moduletyperef = file:moduletyperef()
|
||||
if moduletyperef and moduletyperef.tag == "internaltyperef" then
|
||||
modulename = moduletyperef.typename or modulename
|
||||
end
|
||||
if modulename then
|
||||
searchmodule(ast,file,comment2apiobj,modulename)
|
||||
end
|
||||
end)
|
||||
|
||||
-- clean cache
|
||||
idto_block = {}
|
||||
idto_identifier = {}
|
||||
expreto_expression = {}
|
||||
|
||||
-- if not ok raise an error
|
||||
if not ok then error (errmsg) end
|
||||
|
||||
return internalcontent
|
||||
end
|
||||
|
||||
return M
|
||||
656
Utils/luarocks/share/lua/5.1/models/ldparser.lua
Normal file
656
Utils/luarocks/share/lua/5.1/models/ldparser.lua
Normal file
@@ -0,0 +1,656 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2013 Sierra Wireless and others.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Sierra Wireless - initial API and implementation
|
||||
-------------------------------------------------------------------------------
|
||||
local mlc = require ('metalua.compiler').new()
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local lexer = require 'metalua.grammar.lexer'
|
||||
local mlp = mlc.parser
|
||||
|
||||
local M = {} -- module
|
||||
local lx -- lexer used to parse tag
|
||||
local registeredparsers -- table {tagname => {list de parsers}}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- raise an error if result contains a node error
|
||||
-- ----------------------------------------------------
|
||||
local function raiserror(result)
|
||||
for i, node in ipairs(result) do
|
||||
assert(not node or node.tag ~= "Error")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- copy key and value from one table to an other
|
||||
-- ----------------------------------------------------
|
||||
local function copykey(tablefrom, tableto)
|
||||
for key, value in pairs(tablefrom) do
|
||||
if key ~= "lineinfos" then
|
||||
tableto[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- Handle keyword and identifiers as word
|
||||
-- ----------------------------------------------------
|
||||
local function parseword(lx)
|
||||
local word = lx :peek()
|
||||
local tag = word.tag
|
||||
|
||||
if tag=='Keyword' or tag=='Id' then
|
||||
lx:next()
|
||||
return {tag='Word', lineinfo=word.lineinfo, word[1]}
|
||||
else
|
||||
return gg.parse_error(lx,'Id or Keyword expected')
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse an id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local idparser = gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1][1] }
|
||||
end,
|
||||
parseword
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a modulename (id.)?id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local modulenameparser = gg.list({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local ids = {}
|
||||
for i, id in ipairs(result) do
|
||||
table.insert(ids,id.name)
|
||||
end
|
||||
return {name = table.concat(ids,".")}
|
||||
end,
|
||||
primary = idparser,
|
||||
separators = '.'
|
||||
})
|
||||
-- ----------------------------------------------------
|
||||
-- parse a typename (id.)?id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typenameparser= modulenameparser
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse an internaltype ref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local internaltyperefparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {tag = "typeref",type=result[1].name}
|
||||
end,
|
||||
"#", typenameparser
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse en external type ref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local externaltyperefparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {tag = "typeref",module=result[1].name,type=result[2].name}
|
||||
end,
|
||||
modulenameparser,"#", typenameparser
|
||||
})
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a typeref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typerefparser = gg.multisequence{
|
||||
internaltyperefparser,
|
||||
externaltyperefparser}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a list of typeref
|
||||
-- return a list of table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typereflistparser = gg.list({
|
||||
primary = typerefparser,
|
||||
separators = ','
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- TODO use a more generic way to parse (modifier if not always a typeref)
|
||||
-- TODO support more than one modifier
|
||||
-- ----------------------------------------------------
|
||||
local modifiersparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {[result[1].name]=result[2]}
|
||||
end,
|
||||
"[", idparser , "=" , internaltyperefparser , "]"
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a return tag
|
||||
-- ----------------------------------------------------
|
||||
local returnparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { types= result[1]}
|
||||
end,
|
||||
'@','return', typereflistparser
|
||||
}),
|
||||
-- parser without typerefs
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { types = {}}
|
||||
end,
|
||||
'@','return'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a param tag
|
||||
-- ----------------------------------------------------
|
||||
local paramparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[2].name, type = result[1]}
|
||||
end,
|
||||
'@','param', typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- full parser without type
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','param', idparser
|
||||
}),
|
||||
|
||||
-- Parser for `Dots
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = '...' }
|
||||
end,
|
||||
'@','param', '...'
|
||||
}),
|
||||
}
|
||||
-- ----------------------------------------------------
|
||||
-- parse a field tag
|
||||
-- ----------------------------------------------------
|
||||
local fieldparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.type = result[2]
|
||||
tag.name = result[3].name
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.type = result[2]
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, typerefparser
|
||||
}),
|
||||
|
||||
-- parser without type
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.name = result[2].name
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without type and name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser
|
||||
}),
|
||||
|
||||
-- parser without modifiers
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[2].name, type = result[1]}
|
||||
end,
|
||||
'@','field', typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without modifiers and name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {type = result[1]}
|
||||
end,
|
||||
'@','field', typerefparser
|
||||
}),
|
||||
|
||||
-- parser without type and modifiers
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','field', idparser
|
||||
}),
|
||||
|
||||
-- parser with nothing
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','field'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a function tag
|
||||
-- TODO use a more generic way to parse modifier !
|
||||
-- ----------------------------------------------------
|
||||
local functionparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.name = result[2].name
|
||||
return tag
|
||||
end,
|
||||
'@','function', modifiersparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
return tag
|
||||
end,
|
||||
'@','function', modifiersparser
|
||||
}),
|
||||
|
||||
-- parser without modifier
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
tag.name = result[1].name
|
||||
return tag
|
||||
end,
|
||||
'@','function', idparser
|
||||
}),
|
||||
|
||||
-- empty parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','function'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a type tag
|
||||
-- ----------------------------------------------------
|
||||
local typeparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','type',typenameparser
|
||||
}),
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','type'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a module tag
|
||||
-- ----------------------------------------------------
|
||||
local moduleparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name }
|
||||
end,
|
||||
'@','module', modulenameparser
|
||||
}),
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','module'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a third tag
|
||||
-- ----------------------------------------------------
|
||||
local thirdtagsparser = gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1][1] }
|
||||
end,
|
||||
'@', mlp.id
|
||||
})
|
||||
-- ----------------------------------------------------
|
||||
-- init parser
|
||||
-- ----------------------------------------------------
|
||||
local function initparser()
|
||||
-- register parsers
|
||||
-- each tag name has several parsers
|
||||
registeredparsers = {
|
||||
["module"] = moduleparsers,
|
||||
["return"] = returnparsers,
|
||||
["type"] = typeparsers,
|
||||
["field"] = fieldparsers,
|
||||
["function"] = functionparsers,
|
||||
["param"] = paramparsers
|
||||
}
|
||||
|
||||
-- create lexer used for parsing
|
||||
lx = lexer.lexer:clone()
|
||||
lx.extractors = {
|
||||
-- "extract_long_comment",
|
||||
-- "extract_short_comment",
|
||||
-- "extract_long_string",
|
||||
"extract_short_string",
|
||||
"extract_word",
|
||||
"extract_number",
|
||||
"extract_symbol"
|
||||
}
|
||||
|
||||
-- Add dots as keyword
|
||||
local tagnames = { '...' }
|
||||
|
||||
-- Add tag names as key word
|
||||
for tagname, _ in pairs(registeredparsers) do
|
||||
table.insert(tagnames,tagname)
|
||||
end
|
||||
lx:add(tagnames)
|
||||
|
||||
return lx, parsers
|
||||
end
|
||||
|
||||
initparser()
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- get the string pattern to remove for each line of description
|
||||
-- the goal is to fix the indentation problems
|
||||
-- ----------------------------------------------------
|
||||
local function getstringtoremove (stringcomment,commentstart)
|
||||
local _,_,capture = string.find(stringcomment,"\n?([ \t]*)@[^{]+",commentstart)
|
||||
if not capture then
|
||||
_,_,capture = string.find(stringcomment,"^([ \t]*)",commentstart)
|
||||
end
|
||||
capture = string.gsub(capture,"(.)","%1?")
|
||||
return capture
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse comment tag partition and return table structure
|
||||
-- ----------------------------------------------------
|
||||
local function parsetag(part)
|
||||
if part.comment:find("^@") then
|
||||
-- check if the part start by a supported tag
|
||||
for tagname,parsers in pairs(registeredparsers) do
|
||||
if (part.comment:find("^@"..tagname)) then
|
||||
-- try the registered parsers for this tag
|
||||
local result
|
||||
for i, parser in ipairs(parsers) do
|
||||
local valid, tag = pcall(parser, lx:newstream(part.comment, tagname .. 'tag lexer'))
|
||||
if valid then
|
||||
-- add tagname
|
||||
tag.tagname = tagname
|
||||
|
||||
-- add description
|
||||
local endoffset = tag.lineinfo.last.offset
|
||||
tag.description = part.comment:sub(endoffset+2,-1)
|
||||
return tag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- Parse third party tags.
|
||||
--
|
||||
-- Enable to parse a tag not defined in language.
|
||||
-- So for, accepted format is: @sometagname adescription
|
||||
-- ----------------------------------------------------
|
||||
local function parsethirdtag( part )
|
||||
|
||||
-- Check it there is someting to process
|
||||
if not part.comment:find("^@") then
|
||||
return nil, 'No tag to parse'
|
||||
end
|
||||
|
||||
-- Apply parser
|
||||
local status, parsedtag = pcall(thirdtagsparser, lx:newstream(part.comment, 'Third party tag lexer'))
|
||||
if not status then
|
||||
return nil, "Unable to parse given string."
|
||||
end
|
||||
|
||||
-- Retrieve description
|
||||
local endoffset = parsedtag.lineinfo.last.offset
|
||||
local tag = {
|
||||
description = part.comment:sub(endoffset+2,-1)
|
||||
}
|
||||
return parsedtag.name, tag
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------
|
||||
-- split string comment in several part
|
||||
-- return list of {comment = string, offset = number}
|
||||
-- the first part is the part before the first tag
|
||||
-- the others are the part from a tag to the next one
|
||||
-- ----------------------------------------------------
|
||||
local function split(stringcomment,commentstart)
|
||||
local partstart = commentstart
|
||||
local result = {}
|
||||
|
||||
-- manage case where the comment start by @
|
||||
-- (we must ignore the inline see tag @{..})
|
||||
local at_startoffset, at_endoffset = stringcomment:find("^[ \t]*@[^{]",partstart)
|
||||
if at_endoffset then
|
||||
partstart = at_endoffset-1 -- we start before the @ and the non '{' character
|
||||
end
|
||||
|
||||
-- split comment
|
||||
-- (we must ignore the inline see tag @{..})
|
||||
repeat
|
||||
at_startoffset, at_endoffset = stringcomment:find("\n[ \t]*@[^{]",partstart)
|
||||
local partend
|
||||
if at_startoffset then
|
||||
partend= at_startoffset-1 -- the end is before the separator pattern (just before the \n)
|
||||
else
|
||||
partend = #stringcomment -- we don't find any pattern so the end is the end of the string
|
||||
end
|
||||
table.insert(result, { comment = stringcomment:sub (partstart,partend) ,
|
||||
offset = partstart})
|
||||
if at_endoffset then
|
||||
partstart = at_endoffset-1 -- the new start is befire the @ and the non { char
|
||||
end
|
||||
until not at_endoffset
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a comment block and return a table
|
||||
-- ----------------------------------------------------
|
||||
function M.parse(stringcomment)
|
||||
|
||||
local _comment = {description="", shortdescription=""}
|
||||
|
||||
-- clean windows carriage return
|
||||
stringcomment = string.gsub(stringcomment,"\r\n","\n")
|
||||
|
||||
-- check if it's a ld comment
|
||||
-- get the begin of the comment
|
||||
-- ============================
|
||||
if not stringcomment:find("^-") then
|
||||
-- if this comment don't start by -, we will not handle it.
|
||||
return nil
|
||||
end
|
||||
|
||||
-- retrieve the real start
|
||||
local commentstart = 2 --after the first hyphen
|
||||
-- if the first line is an empty comment line with at least 3 hyphens we ignore it
|
||||
local _ , endoffset = stringcomment:find("^-+[ \t]*\n")
|
||||
if endoffset then
|
||||
commentstart = endoffset+1
|
||||
end
|
||||
|
||||
-- clean comments
|
||||
-- ===================
|
||||
-- remove line of "-"
|
||||
stringcomment = string.sub(stringcomment,commentstart)
|
||||
-- clean indentation
|
||||
local pattern = getstringtoremove (stringcomment,1)
|
||||
stringcomment = string.gsub(stringcomment,"^"..pattern,"")
|
||||
stringcomment = string.gsub(stringcomment,"\n"..pattern,"\n")
|
||||
|
||||
-- split comment part
|
||||
-- ====================
|
||||
local commentparts = split(stringcomment, 1)
|
||||
|
||||
-- Extract descriptions
|
||||
-- ====================
|
||||
local firstpart = commentparts[1].comment
|
||||
if firstpart:find("^[^@]") or firstpart:find("^@{") then
|
||||
-- if the comment part don't start by @
|
||||
-- it's the part which contains descriptions
|
||||
-- (there are an exception for the in-line see tag @{..})
|
||||
local shortdescription, description = string.match(firstpart,'^(.-[.?])(%s.+)')
|
||||
-- store description
|
||||
if shortdescription then
|
||||
_comment.shortdescription = shortdescription
|
||||
-- clean description
|
||||
-- remove always the first space character
|
||||
-- (this manage the case short and long description is on the same line)
|
||||
description = string.gsub(description, "^[ \t]","")
|
||||
-- if first line is only an empty string remove it
|
||||
description = string.gsub(description, "^[ \t]*\n","")
|
||||
_comment.description = description
|
||||
else
|
||||
_comment.shortdescription = firstpart
|
||||
_comment.description = ""
|
||||
end
|
||||
end
|
||||
|
||||
-- Extract tags
|
||||
-- ===================
|
||||
-- Parse regular tags
|
||||
local tag
|
||||
for i, part in ipairs(commentparts) do
|
||||
tag = parsetag(part)
|
||||
--if it's a supported tag (so tag is not nil, it's a table)
|
||||
if tag then
|
||||
if not _comment.tags then _comment.tags = {} end
|
||||
if not _comment.tags[tag.tagname] then
|
||||
_comment.tags[tag.tagname] = {}
|
||||
end
|
||||
table.insert(_comment.tags[tag.tagname], tag)
|
||||
else
|
||||
|
||||
-- Try user defined tags, so far they will look like
|
||||
-- @identifier description
|
||||
local tagname, thirdtag = parsethirdtag( part )
|
||||
if tagname then
|
||||
--
|
||||
-- Append found tag
|
||||
--
|
||||
local reservedname = 'unknowntags'
|
||||
if not _comment.unknowntags then
|
||||
_comment.unknowntags = {}
|
||||
end
|
||||
|
||||
-- Create specific section for parsed tag
|
||||
if not _comment.unknowntags[tagname] then
|
||||
_comment.unknowntags[tagname] = {}
|
||||
end
|
||||
-- Append to specific section
|
||||
table.insert(_comment.unknowntags[tagname], thirdtag)
|
||||
end
|
||||
end
|
||||
end
|
||||
return _comment
|
||||
end
|
||||
|
||||
|
||||
function M.parseinlinecomment(stringcomment)
|
||||
--TODO this code is use to activate typage only on --- comments. (deactivate for now)
|
||||
-- if not stringcomment or not stringcomment:find("^-") then
|
||||
-- -- if this comment don't start by -, we will not handle it.
|
||||
-- return nil
|
||||
-- end
|
||||
-- -- remove the first '-'
|
||||
-- stringcomment = string.sub(stringcomment,2)
|
||||
-- print (stringcomment)
|
||||
-- io.flush()
|
||||
local valid, parsedtag = pcall(typerefparser, lx:newstream(stringcomment, 'typeref parser'))
|
||||
if valid then
|
||||
local endoffset = parsedtag.lineinfo.last.offset
|
||||
parsedtag.description = stringcomment:sub(endoffset+2,-1)
|
||||
return parsedtag
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
546
Utils/luarocks/share/lua/5.1/pl/Date.lua
Normal file
546
Utils/luarocks/share/lua/5.1/pl/Date.lua
Normal file
@@ -0,0 +1,546 @@
|
||||
--- Date and Date Format classes.
|
||||
-- See <a href="../../index.html#date">the Guide</a>.
|
||||
-- @class module
|
||||
-- @name pl.Date
|
||||
-- @pragma nostrip
|
||||
|
||||
--[[
|
||||
module("pl.Date")
|
||||
]]
|
||||
|
||||
local class = require 'pl.class'
|
||||
local os_time, os_date = os.time, os.date
|
||||
local stringx = require 'pl.stringx'
|
||||
|
||||
local Date = class()
|
||||
Date.Format = class()
|
||||
|
||||
--- Date constructor.
|
||||
-- @param t this can be either <ul>
|
||||
-- <li>nil - use current date and time</li>
|
||||
-- <li>number - seconds since epoch (as returned by @{os.time})</li>
|
||||
-- <li>Date - copy constructor</li>
|
||||
-- <li>table - table containing year, month, etc as for os.time()
|
||||
-- You may leave out year, month or day, in which case current values will be used.
|
||||
-- </li>
|
||||
-- <li> two to six numbers: year, month, day, hour, min, sec
|
||||
-- </ul>
|
||||
-- @function Date
|
||||
function Date:_init(t,...)
|
||||
local time
|
||||
if select('#',...) > 0 then
|
||||
local extra = {...}
|
||||
local year = t
|
||||
t = {
|
||||
year = year,
|
||||
month = extra[1],
|
||||
day = extra[2],
|
||||
hour = extra[3],
|
||||
min = extra[4],
|
||||
sec = extra[5]
|
||||
}
|
||||
end
|
||||
if t == nil then
|
||||
time = os_time()
|
||||
elseif type(t) == 'number' then
|
||||
time = t
|
||||
elseif type(t) == 'table' then
|
||||
if getmetatable(t) == Date then -- copy ctor
|
||||
time = t.time
|
||||
else
|
||||
if not (t.year and t.month and t.year) then
|
||||
local lt = os.date('*t')
|
||||
if not t.year and not t.month and not t.day then
|
||||
t.year = lt.year
|
||||
t.month = lt.month
|
||||
t.day = lt.day
|
||||
else
|
||||
t.year = t.year or lt.year
|
||||
t.month = t.month or (t.day and lt.month or 1)
|
||||
t.day = t.day or 1
|
||||
end
|
||||
end
|
||||
time = os_time(t)
|
||||
end
|
||||
end
|
||||
self:set(time)
|
||||
end
|
||||
|
||||
local thour,tmin
|
||||
|
||||
--- get the time zone offset from UTC.
|
||||
-- @return hours ahead of UTC
|
||||
-- @return minutes ahead of UTC
|
||||
function Date.tzone ()
|
||||
if not thour then
|
||||
local t = os.time()
|
||||
local ut = os.date('!*t',t)
|
||||
local lt = os.date('*t',t)
|
||||
thour = lt.hour - ut.hour
|
||||
tmin = lt.min - ut.min
|
||||
end
|
||||
return thour, tmin
|
||||
end
|
||||
|
||||
--- convert this date to UTC.
|
||||
function Date:toUTC ()
|
||||
local th, tm = Date.tzone()
|
||||
self:add { hour = -th }
|
||||
|
||||
if tm > 0 then self:add {min = -tm} end
|
||||
end
|
||||
|
||||
--- convert this UTC date to local.
|
||||
function Date:toLocal ()
|
||||
local th, tm = Date.tzone()
|
||||
self:add { hour = th }
|
||||
if tm > 0 then self:add {min = tm} end
|
||||
end
|
||||
|
||||
--- set the current time of this Date object.
|
||||
-- @param t seconds since epoch
|
||||
function Date:set(t)
|
||||
self.time = t
|
||||
self.tab = os_date('*t',self.time)
|
||||
end
|
||||
|
||||
--- set the year.
|
||||
-- @param y Four-digit year
|
||||
-- @class function
|
||||
-- @name Date:year
|
||||
|
||||
--- set the month.
|
||||
-- @param m month
|
||||
-- @class function
|
||||
-- @name Date:month
|
||||
|
||||
--- set the day.
|
||||
-- @param d day
|
||||
-- @class function
|
||||
-- @name Date:day
|
||||
|
||||
--- set the hour.
|
||||
-- @param h hour
|
||||
-- @class function
|
||||
-- @name Date:hour
|
||||
|
||||
--- set the minutes.
|
||||
-- @param min minutes
|
||||
-- @class function
|
||||
-- @name Date:min
|
||||
|
||||
--- set the seconds.
|
||||
-- @param sec seconds
|
||||
-- @class function
|
||||
-- @name Date:sec
|
||||
|
||||
--- set the day of year.
|
||||
-- @class function
|
||||
-- @param yday day of year
|
||||
-- @name Date:yday
|
||||
|
||||
--- get the year.
|
||||
-- @param y Four-digit year
|
||||
-- @class function
|
||||
-- @name Date:year
|
||||
|
||||
--- get the month.
|
||||
-- @class function
|
||||
-- @name Date:month
|
||||
|
||||
--- get the day.
|
||||
-- @class function
|
||||
-- @name Date:day
|
||||
|
||||
--- get the hour.
|
||||
-- @class function
|
||||
-- @name Date:hour
|
||||
|
||||
--- get the minutes.
|
||||
-- @class function
|
||||
-- @name Date:min
|
||||
|
||||
--- get the seconds.
|
||||
-- @class function
|
||||
-- @name Date:sec
|
||||
|
||||
--- get the day of year.
|
||||
-- @class function
|
||||
-- @name Date:yday
|
||||
|
||||
|
||||
for _,c in ipairs{'year','month','day','hour','min','sec','yday'} do
|
||||
Date[c] = function(self,val)
|
||||
if val then
|
||||
self.tab[c] = val
|
||||
self:set(os_time(self.tab))
|
||||
return self
|
||||
else
|
||||
return self.tab[c]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- name of day of week.
|
||||
-- @param full abbreviated if true, full otherwise.
|
||||
-- @return string name
|
||||
function Date:weekday_name(full)
|
||||
return os_date(full and '%A' or '%a',self.time)
|
||||
end
|
||||
|
||||
--- name of month.
|
||||
-- @param full abbreviated if true, full otherwise.
|
||||
-- @return string name
|
||||
function Date:month_name(full)
|
||||
return os_date(full and '%B' or '%b',self.time)
|
||||
end
|
||||
|
||||
--- is this day on a weekend?.
|
||||
function Date:is_weekend()
|
||||
return self.tab.wday == 0 or self.tab.wday == 6
|
||||
end
|
||||
|
||||
--- add to a date object.
|
||||
-- @param t a table containing one of the following keys and a value:<br>
|
||||
-- year,month,day,hour,min,sec
|
||||
-- @return this date
|
||||
function Date:add(t)
|
||||
local key,val = next(t)
|
||||
self.tab[key] = self.tab[key] + val
|
||||
self:set(os_time(self.tab))
|
||||
return self
|
||||
end
|
||||
|
||||
--- last day of the month.
|
||||
-- @return int day
|
||||
function Date:last_day()
|
||||
local d = 28
|
||||
local m = self.tab.month
|
||||
while self.tab.month == m do
|
||||
d = d + 1
|
||||
self:add{day=1}
|
||||
end
|
||||
self:add{day=-1}
|
||||
return self
|
||||
end
|
||||
|
||||
--- difference between two Date objects.
|
||||
-- Note: currently the result is a regular @{Date} object,
|
||||
-- but also has `interval` field set, which means a more
|
||||
-- appropriate string rep is used.
|
||||
-- @param other Date object
|
||||
-- @return a Date object
|
||||
function Date:diff(other)
|
||||
local dt = self.time - other.time
|
||||
if dt < 0 then error("date difference is negative!",2) end
|
||||
local date = Date(dt)
|
||||
date.interval = true
|
||||
return date
|
||||
end
|
||||
|
||||
--- long numerical ISO data format version of this date.
|
||||
function Date:__tostring()
|
||||
if not self.interval then
|
||||
return os_date('%Y-%m-%d %H:%M:%S',self.time)
|
||||
else
|
||||
local t, res = self.tab, ''
|
||||
local y,m,d = t.year - 1970, t.month - 1, t.day - 1
|
||||
if y > 0 then res = res .. y .. ' years ' end
|
||||
if m > 0 then res = res .. m .. ' months ' end
|
||||
if d > 0 then res = res .. d .. ' days ' end
|
||||
if y == 0 and m == 0 then
|
||||
local h = t.hour - Date.tzone() -- not accounting for UTC mins!
|
||||
if h > 0 then res = res .. h .. ' hours ' end
|
||||
if t.min > 0 then res = res .. t.min .. ' min ' end
|
||||
if t.sec > 0 then res = res .. t.sec .. ' sec ' end
|
||||
end
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
--- equality between Date objects.
|
||||
function Date:__eq(other)
|
||||
return self.time == other.time
|
||||
end
|
||||
|
||||
--- equality between Date objects.
|
||||
function Date:__lt(other)
|
||||
return self.time < other.time
|
||||
end
|
||||
|
||||
|
||||
------------ Date.Format class: parsing and renderinig dates ------------
|
||||
|
||||
-- short field names, explicit os.date names, and a mask for allowed field repeats
|
||||
local formats = {
|
||||
d = {'day',{true,true}},
|
||||
y = {'year',{false,true,false,true}},
|
||||
m = {'month',{true,true}},
|
||||
H = {'hour',{true,true}},
|
||||
M = {'min',{true,true}},
|
||||
S = {'sec',{true,true}},
|
||||
}
|
||||
|
||||
--
|
||||
|
||||
--- Date.Format constructor.
|
||||
-- @param fmt. A string where the following fields are significant: <ul>
|
||||
-- <li>d day (either d or dd)</li>
|
||||
-- <li>y year (either yy or yyy)</li>
|
||||
-- <li>m month (either m or mm)</li>
|
||||
-- <li>H hour (either H or HH)</li>
|
||||
-- <li>M minute (either M or MM)</li>
|
||||
-- <li>S second (either S or SS)</li>
|
||||
-- </ul>
|
||||
-- Alternatively, if fmt is nil then this returns a flexible date parser
|
||||
-- that tries various date/time schemes in turn:
|
||||
-- <ol>
|
||||
-- <li> <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>,
|
||||
-- like 2010-05-10 12:35:23Z or 2008-10-03T14:30+02<li>
|
||||
-- <li> times like 15:30 or 8.05pm (assumed to be today's date)</li>
|
||||
-- <li> dates like 28/10/02 (European order!) or 5 Feb 2012 </li>
|
||||
-- <li> month name like march or Mar (case-insensitive, first 3 letters);
|
||||
-- here the day will be 1 and the year this current year </li>
|
||||
-- </ol>
|
||||
-- A date in format 3 can be optionally followed by a time in format 2.
|
||||
-- Please see test-date.lua in the tests folder for more examples.
|
||||
-- @usage df = Date.Format("yyyy-mm-dd HH:MM:SS")
|
||||
-- @class function
|
||||
-- @name Date.Format
|
||||
function Date.Format:_init(fmt)
|
||||
if not fmt then return end
|
||||
local append = table.insert
|
||||
local D,PLUS,OPENP,CLOSEP = '\001','\002','\003','\004'
|
||||
local vars,used = {},{}
|
||||
local patt,outf = {},{}
|
||||
local i = 1
|
||||
while i < #fmt do
|
||||
local ch = fmt:sub(i,i)
|
||||
local df = formats[ch]
|
||||
if df then
|
||||
if used[ch] then error("field appeared twice: "..ch,2) end
|
||||
used[ch] = true
|
||||
-- this field may be repeated
|
||||
local _,inext = fmt:find(ch..'+',i+1)
|
||||
local cnt = not _ and 1 or inext-i+1
|
||||
if not df[2][cnt] then error("wrong number of fields: "..ch,2) end
|
||||
-- single chars mean 'accept more than one digit'
|
||||
local p = cnt==1 and (D..PLUS) or (D):rep(cnt)
|
||||
append(patt,OPENP..p..CLOSEP)
|
||||
append(vars,ch)
|
||||
if ch == 'y' then
|
||||
append(outf,cnt==2 and '%y' or '%Y')
|
||||
else
|
||||
append(outf,'%'..ch)
|
||||
end
|
||||
i = i + cnt
|
||||
else
|
||||
append(patt,ch)
|
||||
append(outf,ch)
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
-- escape any magic characters
|
||||
fmt = table.concat(patt):gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')
|
||||
-- replace markers with their magic equivalents
|
||||
fmt = fmt:gsub(D,'%%d'):gsub(PLUS,'+'):gsub(OPENP,'('):gsub(CLOSEP,')')
|
||||
self.fmt = fmt
|
||||
self.outf = table.concat(outf)
|
||||
self.vars = vars
|
||||
|
||||
end
|
||||
|
||||
local parse_date
|
||||
|
||||
--- parse a string into a Date object.
|
||||
-- @param str a date string
|
||||
-- @return date object
|
||||
function Date.Format:parse(str)
|
||||
if not self.fmt then
|
||||
return parse_date(str,self.us)
|
||||
end
|
||||
local res = {str:match(self.fmt)}
|
||||
if #res==0 then return nil, 'cannot parse '..str end
|
||||
local tab = {}
|
||||
for i,v in ipairs(self.vars) do
|
||||
local name = formats[v][1] -- e.g. 'y' becomes 'year'
|
||||
tab[name] = tonumber(res[i])
|
||||
end
|
||||
-- os.date() requires these fields; if not present, we assume
|
||||
-- that the time set is for the current day.
|
||||
if not (tab.year and tab.month and tab.year) then
|
||||
local today = Date()
|
||||
tab.year = tab.year or today:year()
|
||||
tab.month = tab.month or today:month()
|
||||
tab.day = tab.day or today:month()
|
||||
end
|
||||
local Y = tab.year
|
||||
if Y < 100 then -- classic Y2K pivot
|
||||
tab.year = Y + (Y < 35 and 2000 or 1999)
|
||||
elseif not Y then
|
||||
tab.year = 1970
|
||||
end
|
||||
--dump(tab)
|
||||
return Date(tab)
|
||||
end
|
||||
|
||||
--- convert a Date object into a string.
|
||||
-- @param d a date object, or a time value as returned by @{os.time}
|
||||
-- @return string
|
||||
function Date.Format:tostring(d)
|
||||
local tm = type(d) == 'number' and d or d.time
|
||||
if self.outf then
|
||||
return os.date(self.outf,tm)
|
||||
else
|
||||
return tostring(Date(d))
|
||||
end
|
||||
end
|
||||
|
||||
function Date.Format:US_order(yesno)
|
||||
self.us = yesno
|
||||
end
|
||||
|
||||
local months = {jan=1,feb=2,mar=3,apr=4,may=5,jun=6,jul=7,aug=8,sep=9,oct=10,nov=11,dec=12}
|
||||
|
||||
--[[
|
||||
Allowed patterns:
|
||||
- [day] [monthname] [year] [time]
|
||||
- [day]/[month][/year] [time]
|
||||
|
||||
]]
|
||||
|
||||
|
||||
local is_word = stringx.isalpha
|
||||
local is_number = stringx.isdigit
|
||||
local function tonum(s,l1,l2,kind)
|
||||
kind = kind or ''
|
||||
local n = tonumber(s)
|
||||
if not n then error(("%snot a number: '%s'"):format(kind,s)) end
|
||||
if n < l1 or n > l2 then
|
||||
error(("%s out of range: %s is not between %d and %d"):format(kind,s,l1,l2))
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
local function parse_iso_end(p,ns,sec)
|
||||
-- may be fractional part of seconds
|
||||
local _,nfrac,secfrac = p:find('^%.%d+',ns+1)
|
||||
if secfrac then
|
||||
sec = sec .. secfrac
|
||||
p = p:sub(nfrac+1)
|
||||
else
|
||||
p = p:sub(ns+1)
|
||||
end
|
||||
-- ISO 8601 dates may end in Z (for UTC) or [+-][isotime]
|
||||
if p:match 'z$' then return sec, {h=0,m=0} end -- we're UTC!
|
||||
p = p:gsub(':','') -- turn 00:30 to 0030
|
||||
local _,_,sign,offs = p:find('^([%+%-])(%d+)')
|
||||
if not sign then return sec, nil end -- not UTC
|
||||
|
||||
if #offs == 2 then offs = offs .. '00' end -- 01 to 0100
|
||||
local tz = { h = tonumber(offs:sub(1,2)), m = tonumber(offs:sub(3,4)) }
|
||||
if sign == '-' then tz.h = -tz.h; tz.m = -tz.m end
|
||||
return sec, tz
|
||||
end
|
||||
|
||||
local function parse_date_unsafe (s,US)
|
||||
s = s:gsub('T',' ') -- ISO 8601
|
||||
local parts = stringx.split(s:lower())
|
||||
local i,p = 1,parts[1]
|
||||
local function nextp() i = i + 1; p = parts[i] end
|
||||
local year,min,hour,sec,apm
|
||||
local tz
|
||||
local _,nxt,day, month = p:find '^(%d+)/(%d+)'
|
||||
if day then
|
||||
-- swop for US case
|
||||
if US then
|
||||
day, month = month, day
|
||||
end
|
||||
_,_,year = p:find('^/(%d+)',nxt+1)
|
||||
nextp()
|
||||
else -- ISO
|
||||
year,month,day = p:match('^(%d+)%-(%d+)%-(%d+)')
|
||||
if year then
|
||||
nextp()
|
||||
end
|
||||
end
|
||||
if p and not year and is_number(p) then -- has to be date
|
||||
day = p
|
||||
nextp()
|
||||
end
|
||||
if p and is_word(p) then
|
||||
p = p:sub(1,3)
|
||||
local mon = months[p]
|
||||
if mon then
|
||||
month = mon
|
||||
else error("not a month: " .. p) end
|
||||
nextp()
|
||||
end
|
||||
if p and not year and is_number(p) then
|
||||
year = p
|
||||
nextp()
|
||||
end
|
||||
|
||||
if p then -- time is hh:mm[:ss], hhmm[ss] or H.M[am|pm]
|
||||
_,nxt,hour,min = p:find '^(%d+):(%d+)'
|
||||
local ns
|
||||
if nxt then -- are there seconds?
|
||||
_,ns,sec = p:find ('^:(%d+)',nxt+1)
|
||||
--if ns then
|
||||
sec,tz = parse_iso_end(p,ns or nxt,sec)
|
||||
--end
|
||||
else -- might be h.m
|
||||
_,ns,hour,min = p:find '^(%d+)%.(%d+)'
|
||||
if ns then
|
||||
apm = p:match '[ap]m$'
|
||||
else -- or hhmm[ss]
|
||||
local hourmin
|
||||
_,nxt,hourmin = p:find ('^(%d+)')
|
||||
if nxt then
|
||||
hour = hourmin:sub(1,2)
|
||||
min = hourmin:sub(3,4)
|
||||
sec = hourmin:sub(5,6)
|
||||
if #sec == 0 then sec = nil end
|
||||
sec,tz = parse_iso_end(p,nxt,sec)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local today
|
||||
if not (year and month and day) then
|
||||
today = Date()
|
||||
end
|
||||
day = day and tonum(day,1,31,'day') or (month and 1 or today:day())
|
||||
month = month and tonum(month,1,12,'month') or today:month()
|
||||
year = year and tonumber(year) or today:year()
|
||||
if year < 100 then -- two-digit year pivot
|
||||
year = year + (year < 35 and 2000 or 1900)
|
||||
end
|
||||
hour = hour and tonum(hour,1,apm and 12 or 24,'hour') or 12
|
||||
if apm == 'pm' then
|
||||
hour = hour + 12
|
||||
end
|
||||
min = min and tonum(min,1,60) or 0
|
||||
sec = sec and tonum(sec,1,60) or 0
|
||||
local res = Date {year = year, month = month, day = day, hour = hour, min = min, sec = sec}
|
||||
if tz then -- ISO 8601 UTC time
|
||||
res:toUTC()
|
||||
res:add {hour = tz.h}
|
||||
if tz.m ~= 0 then res:add {min = tz.m} end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function parse_date (s)
|
||||
local ok, d = pcall(parse_date_unsafe,s)
|
||||
if not ok then -- error
|
||||
d = d:gsub('.-:%d+: ','')
|
||||
return nil, d
|
||||
else
|
||||
return d
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return Date
|
||||
|
||||
553
Utils/luarocks/share/lua/5.1/pl/List.lua
Normal file
553
Utils/luarocks/share/lua/5.1/pl/List.lua
Normal file
@@ -0,0 +1,553 @@
|
||||
--- Python-style list class. <p>
|
||||
-- Based on original code by Nick Trout.
|
||||
-- <p>
|
||||
-- <b>Please Note</b>: methods that change the list will return the list.
|
||||
-- This is to allow for method chaining, but please note that <tt>ls = ls:sort()</tt>
|
||||
-- does not mean that a new copy of the list is made. In-place (mutable) methods
|
||||
-- are marked as returning 'the list' in this documentation.
|
||||
-- <p>
|
||||
-- See the Guide for further <a href="../../index.html#list">discussion</a>
|
||||
-- <p>
|
||||
-- See <a href="http://www.python.org/doc/current/tut/tut.html">http://www.python.org/doc/current/tut/tut.html</a>, section 5.1
|
||||
-- <p>
|
||||
-- <b>Note</b>: The comments before some of the functions are from the Python docs
|
||||
-- and contain Python code.
|
||||
-- <p>
|
||||
-- Written for Lua version 4.0 <br />
|
||||
-- Redone for Lua 5.1, Steve Donovan.
|
||||
-- @class module
|
||||
-- @name pl.List
|
||||
-- @pragma nostrip
|
||||
|
||||
local tinsert,tremove,concat,tsort = table.insert,table.remove,table.concat,table.sort
|
||||
local setmetatable, getmetatable,type,tostring,assert,string,next = setmetatable,getmetatable,type,tostring,assert,string,next
|
||||
local write = io.write
|
||||
local tablex = require 'pl.tablex'
|
||||
local filter,imap,imap2,reduce,transform,tremovevalues = tablex.filter,tablex.imap,tablex.imap2,tablex.reduce,tablex.transform,tablex.removevalues
|
||||
local tablex = tablex
|
||||
local tsub = tablex.sub
|
||||
local utils = require 'pl.utils'
|
||||
local function_arg = utils.function_arg
|
||||
local is_type = utils.is_type
|
||||
local split = utils.split
|
||||
local assert_arg = utils.assert_arg
|
||||
local normalize_slice = tablex._normalize_slice
|
||||
|
||||
--[[
|
||||
module ('pl.List',utils._module)
|
||||
]]
|
||||
|
||||
local Multimap = utils.stdmt.MultiMap
|
||||
-- metatable for our list objects
|
||||
local List = utils.stdmt.List
|
||||
List.__index = List
|
||||
List._class = List
|
||||
|
||||
local iter
|
||||
|
||||
-- we give the metatable its own metatable so that we can call it like a function!
|
||||
setmetatable(List,{
|
||||
__call = function (tbl,arg)
|
||||
return List.new(arg)
|
||||
end,
|
||||
})
|
||||
|
||||
local function makelist (t,obj)
|
||||
local klass = List
|
||||
if obj then
|
||||
klass = getmetatable(obj)
|
||||
end
|
||||
return setmetatable(t,klass)
|
||||
end
|
||||
|
||||
local function is_list(t)
|
||||
return getmetatable(t) == List
|
||||
end
|
||||
|
||||
local function simple_table(t)
|
||||
return type(t) == 'table' and not is_list(t) and #t > 0
|
||||
end
|
||||
|
||||
function List:_init (src)
|
||||
if src then
|
||||
for v in iter(src) do
|
||||
tinsert(self,v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Create a new list. Can optionally pass a table;
|
||||
-- passing another instance of List will cause a copy to be created
|
||||
-- we pass anything which isn't a simple table to iterate() to work out
|
||||
-- an appropriate iterator @see List.iterate
|
||||
-- @param t An optional list-like table
|
||||
-- @return a new List
|
||||
-- @usage ls = List(); ls = List {1,2,3,4}
|
||||
function List.new(t)
|
||||
local ls
|
||||
if not simple_table(t) then
|
||||
ls = {}
|
||||
List._init(ls,t)
|
||||
else
|
||||
ls = t
|
||||
end
|
||||
makelist(ls)
|
||||
return ls
|
||||
end
|
||||
|
||||
function List:clone()
|
||||
local ls = makelist({},self)
|
||||
List._init(ls,self)
|
||||
return ls
|
||||
end
|
||||
|
||||
function List.default_map_with(T)
|
||||
return function(self,name)
|
||||
local f = T[name]
|
||||
if f then
|
||||
return function(self,...)
|
||||
return self:map(f,...)
|
||||
end
|
||||
else
|
||||
error("method not found: "..name,2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Add an item to the end of the list.
|
||||
-- @param i An item
|
||||
-- @return the list
|
||||
function List:append(i)
|
||||
tinsert(self,i)
|
||||
return self
|
||||
end
|
||||
|
||||
List.push = tinsert
|
||||
|
||||
--- Extend the list by appending all the items in the given list.
|
||||
-- equivalent to 'a[len(a):] = L'.
|
||||
-- @param L Another List
|
||||
-- @return the list
|
||||
function List:extend(L)
|
||||
assert_arg(1,L,'table')
|
||||
for i = 1,#L do tinsert(self,L[i]) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Insert an item at a given position. i is the index of the
|
||||
-- element before which to insert.
|
||||
-- @param i index of element before whichh to insert
|
||||
-- @param x A data item
|
||||
-- @return the list
|
||||
function List:insert(i, x)
|
||||
assert_arg(1,i,'number')
|
||||
tinsert(self,i,x)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Insert an item at the begining of the list.
|
||||
-- @param x a data item
|
||||
-- @return the list
|
||||
function List:put (x)
|
||||
return self:insert(1,x)
|
||||
end
|
||||
|
||||
--- Remove an element given its index.
|
||||
-- (equivalent of Python's del s[i])
|
||||
-- @param i the index
|
||||
-- @return the list
|
||||
function List:remove (i)
|
||||
assert_arg(1,i,'number')
|
||||
tremove(self,i)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the first item from the list whose value is given.
|
||||
-- (This is called 'remove' in Python; renamed to avoid confusion
|
||||
-- with table.remove)
|
||||
-- Return nil if there is no such item.
|
||||
-- @param x A data value
|
||||
-- @return the list
|
||||
function List:remove_value(x)
|
||||
for i=1,#self do
|
||||
if self[i]==x then tremove(self,i) return self end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the item at the given position in the list, and return it.
|
||||
-- If no index is specified, a:pop() returns the last item in the list.
|
||||
-- The item is also removed from the list.
|
||||
-- @param i An index
|
||||
-- @return the item
|
||||
function List:pop(i)
|
||||
if not i then i = #self end
|
||||
assert_arg(1,i,'number')
|
||||
return tremove(self,i)
|
||||
end
|
||||
|
||||
List.get = List.pop
|
||||
|
||||
--- Return the index in the list of the first item whose value is given.
|
||||
-- Return nil if there is no such item.
|
||||
-- @class function
|
||||
-- @name List:index
|
||||
-- @param x A data value
|
||||
-- @param idx where to start search (default 1)
|
||||
-- @return the index, or nil if not found.
|
||||
|
||||
local tfind = tablex.find
|
||||
List.index = tfind
|
||||
|
||||
--- does this list contain the value?.
|
||||
-- @param x A data value
|
||||
-- @return true or false
|
||||
function List:contains(x)
|
||||
return tfind(self,x) and true or false
|
||||
end
|
||||
|
||||
--- Return the number of times value appears in the list.
|
||||
-- @param x A data value
|
||||
-- @return number of times x appears
|
||||
function List:count(x)
|
||||
local cnt=0
|
||||
for i=1,#self do
|
||||
if self[i]==x then cnt=cnt+1 end
|
||||
end
|
||||
return cnt
|
||||
end
|
||||
|
||||
--- Sort the items of the list, in place.
|
||||
-- @param cmp an optional comparison function; '<' is used if not given.
|
||||
-- @return the list
|
||||
function List:sort(cmp)
|
||||
tsort(self,cmp)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Reverse the elements of the list, in place.
|
||||
-- @return the list
|
||||
function List:reverse()
|
||||
local t = self
|
||||
local n = #t
|
||||
local n2 = n/2
|
||||
for i = 1,n2 do
|
||||
local k = n-i+1
|
||||
t[i],t[k] = t[k],t[i]
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Emulate list slicing. like 'list[first:last]' in Python.
|
||||
-- If first or last are negative then they are relative to the end of the list
|
||||
-- eg. slice(-2) gives last 2 entries in a list, and
|
||||
-- slice(-4,-2) gives from -4th to -2nd
|
||||
-- @param first An index
|
||||
-- @param last An index
|
||||
-- @return a new List
|
||||
function List:slice(first,last)
|
||||
return tsub(self,first,last)
|
||||
end
|
||||
|
||||
--- empty the list.
|
||||
-- @return the list
|
||||
function List:clear()
|
||||
for i=1,#self do tremove(self,i) end
|
||||
return self
|
||||
end
|
||||
|
||||
local eps = 1.0e-10
|
||||
|
||||
--- Emulate Python's range(x) function.
|
||||
-- Include it in List table for tidiness
|
||||
-- @param start A number
|
||||
-- @param finish A number greater than start; if zero, then 0..start-1
|
||||
-- @param incr an optional increment (may be less than 1)
|
||||
-- @usage List.range(0,3) == List {0,1,2,3}
|
||||
function List.range(start,finish,incr)
|
||||
if not finish then
|
||||
start = 0
|
||||
finish = finish - 1
|
||||
end
|
||||
if incr then
|
||||
if not utils.is_integer(incr) then finish = finish + eps end
|
||||
else
|
||||
incr = 1
|
||||
end
|
||||
assert_arg(1,start,'number')
|
||||
assert_arg(2,finish,'number')
|
||||
local t = List.new()
|
||||
for i=start,finish,incr do tinsert(t,i) end
|
||||
return t
|
||||
end
|
||||
|
||||
--- list:len() is the same as #list.
|
||||
function List:len()
|
||||
return #self
|
||||
end
|
||||
|
||||
-- Extended operations --
|
||||
|
||||
--- Remove a subrange of elements.
|
||||
-- equivalent to 'del s[i1:i2]' in Python.
|
||||
-- @param i1 start of range
|
||||
-- @param i2 end of range
|
||||
-- @return the list
|
||||
function List:chop(i1,i2)
|
||||
return tremovevalues(self,i1,i2)
|
||||
end
|
||||
|
||||
--- Insert a sublist into a list
|
||||
-- equivalent to 's[idx:idx] = list' in Python
|
||||
-- @param idx index
|
||||
-- @param list list to insert
|
||||
-- @return the list
|
||||
-- @usage l = List{10,20}; l:splice(2,{21,22}); assert(l == List{10,21,22,20})
|
||||
function List:splice(idx,list)
|
||||
assert_arg(1,idx,'number')
|
||||
idx = idx - 1
|
||||
local i = 1
|
||||
for v in iter(list) do
|
||||
tinsert(self,i+idx,v)
|
||||
i = i + 1
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- general slice assignment s[i1:i2] = seq.
|
||||
-- @param i1 start index
|
||||
-- @param i2 end index
|
||||
-- @param seq a list
|
||||
-- @return the list
|
||||
function List:slice_assign(i1,i2,seq)
|
||||
assert_arg(1,i1,'number')
|
||||
assert_arg(1,i2,'number')
|
||||
i1,i2 = normalize_slice(self,i1,i2)
|
||||
if i2 >= i1 then self:chop(i1,i2) end
|
||||
self:splice(i1,seq)
|
||||
return self
|
||||
end
|
||||
|
||||
--- concatenation operator.
|
||||
-- @param L another List
|
||||
-- @return a new list consisting of the list with the elements of the new list appended
|
||||
function List:__concat(L)
|
||||
assert_arg(1,L,'table')
|
||||
local ls = self:clone()
|
||||
ls:extend(L)
|
||||
return ls
|
||||
end
|
||||
|
||||
--- equality operator ==. True iff all elements of two lists are equal.
|
||||
-- @param L another List
|
||||
-- @return true or false
|
||||
function List:__eq(L)
|
||||
if #self ~= #L then return false end
|
||||
for i = 1,#self do
|
||||
if self[i] ~= L[i] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- join the elements of a list using a delimiter. <br>
|
||||
-- This method uses tostring on all elements.
|
||||
-- @param delim a delimiter string, can be empty.
|
||||
-- @return a string
|
||||
function List:join (delim)
|
||||
delim = delim or ''
|
||||
assert_arg(1,delim,'string')
|
||||
return concat(imap(tostring,self),delim)
|
||||
end
|
||||
|
||||
--- join a list of strings. <br>
|
||||
-- Uses table.concat directly.
|
||||
-- @class function
|
||||
-- @name List:concat
|
||||
-- @param delim a delimiter
|
||||
-- @return a string
|
||||
List.concat = concat
|
||||
|
||||
local function tostring_q(val)
|
||||
local s = tostring(val)
|
||||
if type(val) == 'string' then
|
||||
s = '"'..s..'"'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
--- how our list should be rendered as a string. Uses join().
|
||||
-- @see List:join
|
||||
function List:__tostring()
|
||||
return '{'..self:join(',',tostring_q)..'}'
|
||||
end
|
||||
|
||||
--[[
|
||||
-- NOTE: this works, but is unreliable. If you leave the loop before finishing,
|
||||
-- then the iterator is not reset.
|
||||
--- can iterate over a list directly.
|
||||
-- @usage for v in ls do print(v) end
|
||||
function List:__call()
|
||||
if not self.key then self.key = 1 end
|
||||
local value = self[self.key]
|
||||
self.key = self.key + 1
|
||||
if not value then self.key = nil end
|
||||
return value
|
||||
end
|
||||
--]]
|
||||
|
||||
--[[
|
||||
function List.__call(t,v,i)
|
||||
i = (i or 0) + 1
|
||||
v = t[i]
|
||||
if v then return i, v end
|
||||
end
|
||||
--]]
|
||||
|
||||
--- call the function for each element of the list.
|
||||
-- @param fun a function or callable object
|
||||
-- @param ... optional values to pass to function
|
||||
function List:foreach (fun,...)
|
||||
local t = self
|
||||
fun = function_arg(1,fun)
|
||||
for i = 1,#t do
|
||||
fun(t[i],...)
|
||||
end
|
||||
end
|
||||
|
||||
--- create a list of all elements which match a function.
|
||||
-- @param fun a boolean function
|
||||
-- @param arg optional argument to be passed as second argument of the predicate
|
||||
-- @return a new filtered list.
|
||||
function List:filter (fun,arg)
|
||||
return makelist(filter(self,fun,arg),self)
|
||||
end
|
||||
|
||||
--- split a string using a delimiter.
|
||||
-- @param s the string
|
||||
-- @param delim the delimiter (default spaces)
|
||||
-- @return a List of strings
|
||||
-- @see pl.utils.split
|
||||
function List.split (s,delim)
|
||||
assert_arg(1,s,'string')
|
||||
return makelist(split(s,delim))
|
||||
end
|
||||
|
||||
--- apply a function to all elements.
|
||||
-- Any extra arguments will be passed to the function
|
||||
-- @param fun a function of at least one argument
|
||||
-- @param ... arbitrary extra arguments.
|
||||
-- @return a new list: {f(x) for x in self}
|
||||
-- @see pl.tablex.imap
|
||||
function List:map (fun,...)
|
||||
return makelist(imap(fun,self,...),self)
|
||||
end
|
||||
|
||||
--- apply a function to all elements, in-place.
|
||||
-- Any extra arguments are passed to the function.
|
||||
-- @param fun A function that takes at least one argument
|
||||
-- @param ... arbitrary extra arguments.
|
||||
function List:transform (fun,...)
|
||||
transform(fun,self,...)
|
||||
end
|
||||
|
||||
--- apply a function to elements of two lists.
|
||||
-- Any extra arguments will be passed to the function
|
||||
-- @param fun a function of at least two arguments
|
||||
-- @param ls another list
|
||||
-- @param ... arbitrary extra arguments.
|
||||
-- @return a new list: {f(x,y) for x in self, for x in arg1}
|
||||
-- @see pl.tablex.imap2
|
||||
function List:map2 (fun,ls,...)
|
||||
return makelist(imap2(fun,self,ls,...),self)
|
||||
end
|
||||
|
||||
--- apply a named method to all elements.
|
||||
-- Any extra arguments will be passed to the method.
|
||||
-- @param name name of method
|
||||
-- @param ... extra arguments
|
||||
-- @return a new list of the results
|
||||
-- @see pl.seq.mapmethod
|
||||
function List:mapm (name,...)
|
||||
local res = {}
|
||||
local t = self
|
||||
for i = 1,#t do
|
||||
local val = t[i]
|
||||
local fn = val[name]
|
||||
if not fn then error(type(val).." does not have method "..name,2) end
|
||||
res[i] = fn(val,...)
|
||||
end
|
||||
return makelist(res,self)
|
||||
end
|
||||
|
||||
--- 'reduce' a list using a binary function.
|
||||
-- @param fun a function of two arguments
|
||||
-- @return result of the function
|
||||
-- @see pl.tablex.reduce
|
||||
function List:reduce (fun)
|
||||
return reduce(fun,self)
|
||||
end
|
||||
|
||||
--- partition a list using a classifier function.
|
||||
-- The function may return nil, but this will be converted to the string key '<nil>'.
|
||||
-- @param fun a function of at least one argument
|
||||
-- @param ... will also be passed to the function
|
||||
-- @return a table where the keys are the returned values, and the values are Lists
|
||||
-- of values where the function returned that key. It is given the type of Multimap.
|
||||
-- @see pl.MultiMap
|
||||
function List:partition (fun,...)
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for i = 1,#self do
|
||||
local val = self[i]
|
||||
local klass = fun(val,...)
|
||||
if klass == nil then klass = '<nil>' end
|
||||
if not res[klass] then res[klass] = List() end
|
||||
res[klass]:append(val)
|
||||
end
|
||||
return setmetatable(res,Multimap)
|
||||
end
|
||||
|
||||
--- return an iterator over all values.
|
||||
function List:iter ()
|
||||
return iter(self)
|
||||
end
|
||||
|
||||
--- Create an iterator over a seqence.
|
||||
-- This captures the Python concept of 'sequence'.
|
||||
-- For tables, iterates over all values with integer indices.
|
||||
-- @param seq a sequence; a string (over characters), a table, a file object (over lines) or an iterator function
|
||||
-- @usage for x in iterate {1,10,22,55} do io.write(x,',') end ==> 1,10,22,55
|
||||
-- @usage for ch in iterate 'help' do do io.write(ch,' ') end ==> h e l p
|
||||
function List.iterate(seq)
|
||||
if type(seq) == 'string' then
|
||||
local idx = 0
|
||||
local n = #seq
|
||||
local sub = string.sub
|
||||
return function ()
|
||||
idx = idx + 1
|
||||
if idx > n then return nil
|
||||
else
|
||||
return sub(seq,idx,idx)
|
||||
end
|
||||
end
|
||||
elseif type(seq) == 'table' then
|
||||
local idx = 0
|
||||
local n = #seq
|
||||
return function()
|
||||
idx = idx + 1
|
||||
if idx > n then return nil
|
||||
else
|
||||
return seq[idx]
|
||||
end
|
||||
end
|
||||
elseif type(seq) == 'function' then
|
||||
return seq
|
||||
elseif type(seq) == 'userdata' and io.type(seq) == 'file' then
|
||||
return seq:lines()
|
||||
end
|
||||
end
|
||||
iter = List.iterate
|
||||
|
||||
return List
|
||||
|
||||
108
Utils/luarocks/share/lua/5.1/pl/Map.lua
Normal file
108
Utils/luarocks/share/lua/5.1/pl/Map.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
--- A Map class.
|
||||
-- @class module
|
||||
-- @name pl.Map
|
||||
|
||||
--[[
|
||||
module ('pl.Map')
|
||||
]]
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local stdmt = utils.stdmt
|
||||
local is_callable = utils.is_callable
|
||||
local tmakeset,deepcompare,merge,keys,difference,tupdate = tablex.makeset,tablex.deepcompare,tablex.merge,tablex.keys,tablex.difference,tablex.update
|
||||
|
||||
local pretty_write = require 'pl.pretty' . write
|
||||
local Map = stdmt.Map
|
||||
local Set = stdmt.Set
|
||||
local List = stdmt.List
|
||||
|
||||
local class = require 'pl.class'
|
||||
|
||||
-- the Map class ---------------------
|
||||
class(nil,nil,Map)
|
||||
|
||||
local function makemap (m)
|
||||
return setmetatable(m,Map)
|
||||
end
|
||||
|
||||
function Map:_init (t)
|
||||
local mt = getmetatable(t)
|
||||
if mt == Set or mt == Map then
|
||||
self:update(t)
|
||||
else
|
||||
return t -- otherwise assumed to be a map-like table
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function makelist(t)
|
||||
return setmetatable(t,List)
|
||||
end
|
||||
|
||||
--- list of keys.
|
||||
Map.keys = tablex.keys
|
||||
|
||||
--- list of values.
|
||||
Map.values = tablex.values
|
||||
|
||||
--- return an iterator over all key-value pairs.
|
||||
function Map:iter ()
|
||||
return pairs(self)
|
||||
end
|
||||
|
||||
--- return a List of all key-value pairs, sorted by the keys.
|
||||
function Map:items()
|
||||
local ls = makelist(tablex.pairmap (function (k,v) return makelist {k,v} end, self))
|
||||
ls:sort(function(t1,t2) return t1[1] < t2[1] end)
|
||||
return ls
|
||||
end
|
||||
|
||||
-- Will return the existing value, or if it doesn't exist it will set
|
||||
-- a default value and return it.
|
||||
function Map:setdefault(key, defaultval)
|
||||
return self[key] or self:set(key,defaultval) or defaultval
|
||||
end
|
||||
|
||||
--- size of map.
|
||||
-- note: this is a relatively expensive operation!
|
||||
-- @class function
|
||||
-- @name Map:len
|
||||
Map.len = tablex.size
|
||||
|
||||
--- put a value into the map.
|
||||
-- @param key the key
|
||||
-- @param val the value
|
||||
function Map:set (key,val)
|
||||
self[key] = val
|
||||
end
|
||||
|
||||
--- get a value from the map.
|
||||
-- @param key the key
|
||||
-- @return the value, or nil if not found.
|
||||
function Map:get (key)
|
||||
return rawget(self,key)
|
||||
end
|
||||
|
||||
local index_by = tablex.index_by
|
||||
|
||||
-- get a list of values indexed by a list of keys.
|
||||
-- @param keys a list-like table of keys
|
||||
-- @return a new list
|
||||
function Map:getvalues (keys)
|
||||
return makelist(index_by(self,keys))
|
||||
end
|
||||
|
||||
Map.iter = pairs
|
||||
|
||||
Map.update = tablex.update
|
||||
|
||||
function Map:__eq (m)
|
||||
-- note we explicitly ask deepcompare _not_ to use __eq!
|
||||
return deepcompare(self,m,true)
|
||||
end
|
||||
|
||||
function Map:__tostring ()
|
||||
return pretty_write(self,'')
|
||||
end
|
||||
|
||||
return Map
|
||||
65
Utils/luarocks/share/lua/5.1/pl/MultiMap.lua
Normal file
65
Utils/luarocks/share/lua/5.1/pl/MultiMap.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
--- MultiMap, a Map which has multiple values per key. <br>
|
||||
-- @class module
|
||||
-- @name pl.MultiMap
|
||||
|
||||
--[[
|
||||
module ('pl.MultiMap')
|
||||
]]
|
||||
|
||||
local classes = require 'pl.class'
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local List = require 'pl.List'
|
||||
|
||||
local index_by,tsort,concat = tablex.index_by,table.sort,table.concat
|
||||
local append,extend,slice = List.append,List.extend,List.slice
|
||||
local append = table.insert
|
||||
local is_type = utils.is_type
|
||||
|
||||
local class = require 'pl.class'
|
||||
local Map = require 'pl.Map'
|
||||
|
||||
-- MultiMap is a standard MT
|
||||
local MultiMap = utils.stdmt.MultiMap
|
||||
|
||||
class(Map,nil,MultiMap)
|
||||
MultiMap._name = 'MultiMap'
|
||||
|
||||
function MultiMap:_init (t)
|
||||
if not t then return end
|
||||
self:update(t)
|
||||
end
|
||||
|
||||
--- update a MultiMap using a table.
|
||||
-- @param t either a Multimap or a map-like table.
|
||||
-- @return the map
|
||||
function MultiMap:update (t)
|
||||
utils.assert_arg(1,t,'table')
|
||||
if Map:class_of(t) then
|
||||
for k,v in pairs(t) do
|
||||
self[k] = List()
|
||||
self[k]:append(v)
|
||||
end
|
||||
else
|
||||
for k,v in pairs(t) do
|
||||
self[k] = List(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- add a new value to a key. Setting a nil value removes the key.
|
||||
-- @param key the key
|
||||
-- @param val the value
|
||||
-- @return the map
|
||||
function MultiMap:set (key,val)
|
||||
if val == nil then
|
||||
self[key] = nil
|
||||
else
|
||||
if not self[key] then
|
||||
self[key] = List()
|
||||
end
|
||||
self[key]:append(val)
|
||||
end
|
||||
end
|
||||
|
||||
return MultiMap
|
||||
150
Utils/luarocks/share/lua/5.1/pl/OrderedMap.lua
Normal file
150
Utils/luarocks/share/lua/5.1/pl/OrderedMap.lua
Normal file
@@ -0,0 +1,150 @@
|
||||
--- OrderedMap, a pl.Map which preserves ordering.
|
||||
-- @class module
|
||||
-- @name pl.OrderedMap
|
||||
|
||||
--[[
|
||||
module ('pl.OrderedMap')
|
||||
]]
|
||||
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local List = require 'pl.List'
|
||||
local index_by,tsort,concat = tablex.index_by,table.sort,table.concat
|
||||
|
||||
local class = require 'pl.class'
|
||||
local Map = require 'pl.Map'
|
||||
|
||||
local OrderedMap = class(Map)
|
||||
OrderedMap._name = 'OrderedMap'
|
||||
|
||||
--- construct an OrderedMap.
|
||||
-- Will throw an error if the argument is bad.
|
||||
-- @param t optional initialization table, same as for @{OrderedMap:update}
|
||||
function OrderedMap:_init (t)
|
||||
self._keys = List()
|
||||
if t then
|
||||
local map,err = self:update(t)
|
||||
if not map then error(err,2) end
|
||||
end
|
||||
end
|
||||
|
||||
local assert_arg,raise = utils.assert_arg,utils.raise
|
||||
|
||||
--- update an OrderedMap using a table. <br>
|
||||
-- If the table is itself an OrderedMap, then its entries will be appended. <br>
|
||||
-- if it s a table of the form <code>{{key1=val1},{key2=val2},...}</code> these will be appended. <br>
|
||||
-- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary.
|
||||
-- @param t a table.
|
||||
-- @return the map, or nil in case of error
|
||||
-- @return the error message
|
||||
function OrderedMap:update (t)
|
||||
assert_arg(1,t,'table')
|
||||
if OrderedMap:class_of(t) then
|
||||
for k,v in t:iter() do
|
||||
self:set(k,v)
|
||||
end
|
||||
elseif #t > 0 then -- an array must contain {key=val} tables
|
||||
if type(t[1]) == 'table' then
|
||||
for _,pair in ipairs(t) do
|
||||
local key,value = next(pair)
|
||||
if not key then return raise 'empty pair initialization table' end
|
||||
self:set(key,value)
|
||||
end
|
||||
else
|
||||
return raise 'cannot use an array to initialize an OrderedMap'
|
||||
end
|
||||
else
|
||||
for k,v in pairs(t) do
|
||||
self:set(k,v)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- set the key's value. This key will be appended at the end of the map. <br>
|
||||
-- If the value is nil, then the key is removed.
|
||||
-- @param key the key
|
||||
-- @param val the value
|
||||
-- @return the map
|
||||
function OrderedMap:set (key,val)
|
||||
if not self[key] and val ~= nil then -- ensure that keys are unique
|
||||
self._keys:append(key)
|
||||
elseif val == nil then -- removing a key-value pair
|
||||
self._keys:remove_value(key)
|
||||
end
|
||||
self[key] = val
|
||||
return self
|
||||
end
|
||||
|
||||
--- insert a key/value pair before a given position.
|
||||
-- Note: if the map already contains the key, then this effectively
|
||||
-- moves the item to the new position by first removing at the old position.
|
||||
-- Has no effect if the key does not exist and val is nil
|
||||
-- @param pos a position starting at 1
|
||||
-- @param key the key
|
||||
-- @param val the value; if nil use the old value
|
||||
function OrderedMap:insert (pos,key,val)
|
||||
local oldval = self[key]
|
||||
val = val or oldval
|
||||
if oldval then
|
||||
self._keys:remove_value(key)
|
||||
end
|
||||
if val then
|
||||
self._keys:insert(pos,key)
|
||||
self[key] = val
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- return the keys in order.
|
||||
-- (Not a copy!)
|
||||
-- @return List
|
||||
function OrderedMap:keys ()
|
||||
return self._keys
|
||||
end
|
||||
|
||||
--- return the values in order.
|
||||
-- this is relatively expensive.
|
||||
-- @return List
|
||||
function OrderedMap:values ()
|
||||
return List(index_by(self,self._keys))
|
||||
end
|
||||
|
||||
--- sort the keys.
|
||||
-- @param cmp a comparison function as for @{table.sort}
|
||||
-- @return the map
|
||||
function OrderedMap:sort (cmp)
|
||||
tsort(self._keys,cmp)
|
||||
return self
|
||||
end
|
||||
|
||||
--- iterate over key-value pairs in order.
|
||||
function OrderedMap:iter ()
|
||||
local i = 0
|
||||
local keys = self._keys
|
||||
local n,idx = #keys
|
||||
return function()
|
||||
i = i + 1
|
||||
if i > #keys then return nil end
|
||||
idx = keys[i]
|
||||
return idx,self[idx]
|
||||
end
|
||||
end
|
||||
|
||||
function OrderedMap:__tostring ()
|
||||
local res = {}
|
||||
for i,v in ipairs(self._keys) do
|
||||
local val = self[v]
|
||||
local vs = tostring(val)
|
||||
if type(val) ~= 'number' then
|
||||
vs = '"'..vs..'"'
|
||||
end
|
||||
res[i] = tostring(v)..'='..vs
|
||||
end
|
||||
return '{'..concat(res,',')..'}'
|
||||
end
|
||||
|
||||
return OrderedMap
|
||||
|
||||
|
||||
|
||||
127
Utils/luarocks/share/lua/5.1/pl/Set.lua
Normal file
127
Utils/luarocks/share/lua/5.1/pl/Set.lua
Normal file
@@ -0,0 +1,127 @@
|
||||
---- A Set class.
|
||||
-- @class module
|
||||
-- @name pl.Set
|
||||
|
||||
--[[
|
||||
module ('pl.Set')
|
||||
]]
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local stdmt = utils.stdmt
|
||||
local tmakeset,deepcompare,merge,keys,difference,tupdate = tablex.makeset,tablex.deepcompare,tablex.merge,tablex.keys,tablex.difference,tablex.update
|
||||
local Map = stdmt.Map
|
||||
local Set = stdmt.Set
|
||||
local List = stdmt.List
|
||||
local class = require 'pl.class'
|
||||
|
||||
-- the Set class --------------------
|
||||
class(Map,nil,Set)
|
||||
|
||||
local function makeset (t)
|
||||
return setmetatable(t,Set)
|
||||
end
|
||||
|
||||
--- create a set. <br>
|
||||
-- @param t may be a Set, Map or list-like table.
|
||||
-- @class function
|
||||
-- @name Set
|
||||
function Set:_init (t)
|
||||
local mt = getmetatable(t)
|
||||
if mt == Set or mt == Map then
|
||||
for k in pairs(t) do self[k] = true end
|
||||
else
|
||||
for _,v in ipairs(t) do self[v] = true end
|
||||
end
|
||||
end
|
||||
|
||||
function Set:__tostring ()
|
||||
return '['..self:keys():join ','..']'
|
||||
end
|
||||
|
||||
--- add a value to a set.
|
||||
-- @param key a value
|
||||
function Set:set (key)
|
||||
self[key] = true
|
||||
end
|
||||
|
||||
--- remove a value from a set.
|
||||
-- @param key a value
|
||||
function Set:unset (key)
|
||||
self[key] = nil
|
||||
end
|
||||
|
||||
--- get a list of the values in a set.
|
||||
-- @class function
|
||||
-- @name Set:values
|
||||
Set.values = Map.keys
|
||||
|
||||
--- map a function over the values of a set.
|
||||
-- @param fn a function
|
||||
-- @param ... extra arguments to pass to the function.
|
||||
-- @return a new set
|
||||
function Set:map (fn,...)
|
||||
fn = utils.function_arg(1,fn)
|
||||
local res = {}
|
||||
for k in pairs(self) do
|
||||
res[fn(k,...)] = true
|
||||
end
|
||||
return makeset(res)
|
||||
end
|
||||
|
||||
--- union of two sets (also +).
|
||||
-- @param set another set
|
||||
-- @return a new set
|
||||
function Set:union (set)
|
||||
return merge(self,set,true)
|
||||
end
|
||||
Set.__add = Set.union
|
||||
|
||||
--- intersection of two sets (also *).
|
||||
-- @param set another set
|
||||
-- @return a new set
|
||||
function Set:intersection (set)
|
||||
return merge(self,set,false)
|
||||
end
|
||||
Set.__mul = Set.intersection
|
||||
|
||||
--- new set with elements in the set that are not in the other (also -).
|
||||
-- @param set another set
|
||||
-- @return a new set
|
||||
function Set:difference (set)
|
||||
return difference(self,set,false)
|
||||
end
|
||||
Set.__sub = Set.difference
|
||||
|
||||
-- a new set with elements in _either_ the set _or_ other but not both (also ^).
|
||||
-- @param set another set
|
||||
-- @return a new set
|
||||
function Set:symmetric_difference (set)
|
||||
return difference(self,set,true)
|
||||
end
|
||||
Set.__pow = Set.symmetric_difference
|
||||
|
||||
--- is the first set a subset of the second?.
|
||||
-- @return true or false
|
||||
function Set:issubset (set)
|
||||
for k in pairs(self) do
|
||||
if not set[k] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
Set.__lt = Set.subset
|
||||
|
||||
--- is the set empty?.
|
||||
-- @return true or false
|
||||
function Set:issempty ()
|
||||
return next(self) == nil
|
||||
end
|
||||
|
||||
--- are the sets disjoint? (no elements in common).
|
||||
-- Uses naive definition, i.e. that intersection is empty
|
||||
-- @param set another set
|
||||
-- @return true or false
|
||||
function Set:isdisjoint (set)
|
||||
return self:intersection(set):isempty()
|
||||
end
|
||||
|
||||
return Set
|
||||
143
Utils/luarocks/share/lua/5.1/pl/app.lua
Normal file
143
Utils/luarocks/share/lua/5.1/pl/app.lua
Normal file
@@ -0,0 +1,143 @@
|
||||
--- Application support functions.
|
||||
-- <p>See <a href="../../index.html#app">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.app
|
||||
|
||||
local io,package,require = _G.io, _G.package, _G.require
|
||||
local utils = require 'pl.utils'
|
||||
local path = require 'pl.path'
|
||||
local lfs = require 'lfs'
|
||||
|
||||
|
||||
local app = {}
|
||||
|
||||
local function check_script_name ()
|
||||
if _G.arg == nil then error('no command line args available\nWas this run from a main script?') end
|
||||
return _G.arg[0]
|
||||
end
|
||||
|
||||
--- add the current script's path to the Lua module path.
|
||||
-- Applies to both the source and the binary module paths. It makes it easy for
|
||||
-- the main file of a multi-file program to access its modules in the same directory.
|
||||
-- `base` allows these modules to be put in a specified subdirectory, to allow for
|
||||
-- cleaner deployment and resolve potential conflicts between a script name and its
|
||||
-- library directory.
|
||||
-- @param base optional base directory.
|
||||
-- @return the current script's path with a trailing slash
|
||||
function app.require_here (base)
|
||||
local p = path.dirname(check_script_name())
|
||||
if not path.isabs(p) then
|
||||
p = path.join(lfs.currentdir(),p)
|
||||
end
|
||||
if p:sub(-1,-1) ~= path.sep then
|
||||
p = p..path.sep
|
||||
end
|
||||
if base then
|
||||
p = p..base..path.sep
|
||||
end
|
||||
local so_ext = path.is_windows and 'dll' or 'so'
|
||||
local lsep = package.path:find '^;' and '' or ';'
|
||||
local csep = package.cpath:find '^;' and '' or ';'
|
||||
package.path = ('%s?.lua;%s?%sinit.lua%s%s'):format(p,p,path.sep,lsep,package.path)
|
||||
package.cpath = ('%s?.%s%s%s'):format(p,so_ext,csep,package.cpath)
|
||||
return p
|
||||
end
|
||||
|
||||
--- return a suitable path for files private to this application.
|
||||
-- These will look like '~/.SNAME/file', with '~' as with expanduser and
|
||||
-- SNAME is the name of the script without .lua extension.
|
||||
-- @param file a filename (w/out path)
|
||||
-- @return a full pathname, or nil
|
||||
-- @return 'cannot create' error
|
||||
function app.appfile (file)
|
||||
local sname = path.basename(check_script_name())
|
||||
local name,ext = path.splitext(sname)
|
||||
local dir = path.join(path.expanduser('~'),'.'..name)
|
||||
if not path.isdir(dir) then
|
||||
local ret = lfs.mkdir(dir)
|
||||
if not ret then return utils.raise ('cannot create '..dir) end
|
||||
end
|
||||
return path.join(dir,file)
|
||||
end
|
||||
|
||||
--- return string indicating operating system.
|
||||
-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux')
|
||||
function app.platform()
|
||||
if path.is_windows then
|
||||
return 'Windows'
|
||||
else
|
||||
local f = io.popen('uname')
|
||||
local res = f:read()
|
||||
if res == 'Darwin' then res = 'OSX' end
|
||||
f:close()
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
--- parse command-line arguments into flags and parameters.
|
||||
-- Understands GNU-style command-line flags; short (-f) and long (--flag).
|
||||
-- These may be given a value with either '=' or ':' (-k:2,--alpha=3.2,-n2);
|
||||
-- note that a number value can be given without a space.
|
||||
-- Multiple short args can be combined like so: (-abcd).
|
||||
-- @param args an array of strings (default is the global 'arg')
|
||||
-- @param flags_with_values any flags that take values, e.g. <code>{out=true}</code>
|
||||
-- @return a table of flags (flag=value pairs)
|
||||
-- @return an array of parameters
|
||||
-- @raise if args is nil, then the global `args` must be available!
|
||||
function app.parse_args (args,flags_with_values)
|
||||
if not args then
|
||||
args = _G.arg
|
||||
if not args then error "Not in a main program: 'arg' not found" end
|
||||
end
|
||||
flags_with_values = flags_with_values or {}
|
||||
local _args = {}
|
||||
local flags = {}
|
||||
local i = 1
|
||||
while i <= #args do
|
||||
local a = args[i]
|
||||
local v = a:match('^-(.+)')
|
||||
local is_long
|
||||
if v then -- we have a flag
|
||||
if v:find '^-' then
|
||||
is_long = true
|
||||
v = v:sub(2)
|
||||
end
|
||||
if flags_with_values[v] then
|
||||
if i == #_args or args[i+1]:find '^-' then
|
||||
return utils.raise ("no value for '"..v.."'")
|
||||
end
|
||||
flags[v] = args[i+1]
|
||||
i = i + 1
|
||||
else
|
||||
-- a value can be indicated with = or :
|
||||
local var,val = utils.splitv (v,'[=:]')
|
||||
var = var or v
|
||||
val = val or true
|
||||
if not is_long then
|
||||
if #var > 1 then
|
||||
if var:find '.%d+' then -- short flag, number value
|
||||
val = var:sub(2)
|
||||
var = var:sub(1,1)
|
||||
else -- multiple short flags
|
||||
for i = 1,#var do
|
||||
flags[var:sub(i,i)] = true
|
||||
end
|
||||
val = nil -- prevents use of var as a flag below
|
||||
end
|
||||
else -- single short flag (can have value, defaults to true)
|
||||
val = val or true
|
||||
end
|
||||
end
|
||||
if val then
|
||||
flags[var] = val
|
||||
end
|
||||
end
|
||||
else
|
||||
_args[#_args+1] = a
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return flags,_args
|
||||
end
|
||||
|
||||
return app
|
||||
391
Utils/luarocks/share/lua/5.1/pl/array2d.lua
Normal file
391
Utils/luarocks/share/lua/5.1/pl/array2d.lua
Normal file
@@ -0,0 +1,391 @@
|
||||
--- Operations on two-dimensional arrays.
|
||||
-- @class module
|
||||
-- @name pl.array2d
|
||||
|
||||
local require, type,tonumber,assert,tostring,io,ipairs,string,table =
|
||||
_G.require, _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table
|
||||
local ops = require 'pl.operator'
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
|
||||
local imap,tmap,reduce,keys,tmap2,tset,index_by = tablex.imap,tablex.map,tablex.reduce,tablex.keys,tablex.map2,tablex.set,tablex.index_by
|
||||
local remove = table.remove
|
||||
local perm = require 'pl.permute'
|
||||
local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg
|
||||
local byte = string.byte
|
||||
local stdout = io.stdout
|
||||
|
||||
--[[
|
||||
module ('pl.array2d',utils._module)
|
||||
]]
|
||||
|
||||
local array2d = {}
|
||||
|
||||
--- extract a column from the 2D array.
|
||||
-- @param a 2d array
|
||||
-- @param key an index or key
|
||||
-- @return 1d array
|
||||
function array2d.column (a,key)
|
||||
assert_arg(1,a,'table')
|
||||
return imap(ops.index,a,key)
|
||||
end
|
||||
local column = array2d.column
|
||||
|
||||
--- map a function over a 2D array
|
||||
-- @param f a function of at least one argument
|
||||
-- @param a 2d array
|
||||
-- @param arg an optional extra argument to be passed to the function.
|
||||
-- @return 2d array
|
||||
function array2d.map (f,a,arg)
|
||||
assert_arg(1,a,'table')
|
||||
f = utils.function_arg(1,f)
|
||||
return imap(function(row) return imap(f,row,arg) end, a)
|
||||
end
|
||||
|
||||
--- reduce the rows using a function.
|
||||
-- @param f a binary function
|
||||
-- @param a 2d array
|
||||
-- @return 1d array
|
||||
-- @see pl.tablex.reduce
|
||||
function array2d.reduce_rows (f,a)
|
||||
assert_arg(1,a,'table')
|
||||
return tmap(function(row) return reduce(f,row) end, a)
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- reduce the columns using a function.
|
||||
-- @param f a binary function
|
||||
-- @param a 2d array
|
||||
-- @return 1d array
|
||||
-- @see pl.tablex.reduce
|
||||
function array2d.reduce_cols (f,a)
|
||||
assert_arg(1,a,'table')
|
||||
return tmap(function(c) return reduce(f,column(a,c)) end, keys(a[1]))
|
||||
end
|
||||
|
||||
--- reduce a 2D array into a scalar, using two operations.
|
||||
-- @param opc operation to reduce the final result
|
||||
-- @param opr operation to reduce the rows
|
||||
-- @param a 2D array
|
||||
function array2d.reduce2 (opc,opr,a)
|
||||
assert_arg(3,a,'table')
|
||||
local tmp = array2d.reduce_rows(opr,a)
|
||||
return reduce(opc,tmp)
|
||||
end
|
||||
|
||||
local function dimension (t)
|
||||
return type(t[1])=='table' and 2 or 1
|
||||
end
|
||||
|
||||
--- map a function over two arrays.
|
||||
-- They can be both or either 2D arrays
|
||||
-- @param f function of at least two arguments
|
||||
-- @param ad order of first array
|
||||
-- @param bd order of second array
|
||||
-- @param a 1d or 2d array
|
||||
-- @param b 1d or 2d array
|
||||
-- @param arg optional extra argument to pass to function
|
||||
-- @return 2D array, unless both arrays are 1D
|
||||
function array2d.map2 (f,ad,bd,a,b,arg)
|
||||
assert_arg(1,a,'table')
|
||||
assert_arg(2,b,'table')
|
||||
f = utils.function_arg(1,f)
|
||||
--local ad,bd = dimension(a),dimension(b)
|
||||
if ad == 1 and bd == 2 then
|
||||
return imap(function(row)
|
||||
return tmap2(f,a,row,arg)
|
||||
end, b)
|
||||
elseif ad == 2 and bd == 1 then
|
||||
return imap(function(row)
|
||||
return tmap2(f,row,b,arg)
|
||||
end, a)
|
||||
elseif ad == 1 and bd == 1 then
|
||||
return tmap2(f,a,b)
|
||||
elseif ad == 2 and bd == 2 then
|
||||
return tmap2(function(rowa,rowb)
|
||||
return tmap2(f,rowa,rowb,arg)
|
||||
end, a,b)
|
||||
end
|
||||
end
|
||||
|
||||
--- cartesian product of two 1d arrays.
|
||||
-- @param f a function of 2 arguments
|
||||
-- @param t1 a 1d table
|
||||
-- @param t2 a 1d table
|
||||
-- @return 2d table
|
||||
-- @usage product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
|
||||
function array2d.product (f,t1,t2)
|
||||
f = utils.function_arg(1,f)
|
||||
assert_arg(2,t1,'table')
|
||||
assert_arg(3,t2,'table')
|
||||
local res, map = {}, tablex.map
|
||||
for i,v in ipairs(t2) do
|
||||
res[i] = map(f,t1,v)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- flatten a 2D array.
|
||||
-- (this goes over columns first.)
|
||||
-- @param t 2d table
|
||||
-- @return a 1d table
|
||||
-- @usage flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
|
||||
function array2d.flatten (t)
|
||||
local res = {}
|
||||
local k = 1
|
||||
for _,a in ipairs(t) do -- for all rows
|
||||
for i = 1,#a do
|
||||
res[k] = a[i]
|
||||
k = k + 1
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- swap two rows of an array.
|
||||
-- @param t a 2d array
|
||||
-- @param i1 a row index
|
||||
-- @param i2 a row index
|
||||
function array2d.swap_rows (t,i1,i2)
|
||||
assert_arg(1,t,'table')
|
||||
t[i1],t[i2] = t[i2],t[i1]
|
||||
end
|
||||
|
||||
--- swap two columns of an array.
|
||||
-- @param t a 2d array
|
||||
-- @param j1 a column index
|
||||
-- @param j2 a column index
|
||||
function array2d.swap_cols (t,j1,j2)
|
||||
assert_arg(1,t,'table')
|
||||
for i = 1,#t do
|
||||
local row = t[i]
|
||||
row[j1],row[j2] = row[j2],row[j1]
|
||||
end
|
||||
end
|
||||
|
||||
--- extract the specified rows.
|
||||
-- @param t 2d array
|
||||
-- @param ridx a table of row indices
|
||||
function array2d.extract_rows (t,ridx)
|
||||
return index_by(t,ridx)
|
||||
end
|
||||
|
||||
--- extract the specified columns.
|
||||
-- @param t 2d array
|
||||
-- @param cidx a table of column indices
|
||||
function array2d.extract_cols (t,cidx)
|
||||
assert_arg(1,t,'table')
|
||||
for i = 1,#t do
|
||||
t[i] = index_by(t[i],cidx)
|
||||
end
|
||||
end
|
||||
|
||||
--- remove a row from an array.
|
||||
-- @class function
|
||||
-- @name array2d.remove_row
|
||||
-- @param t a 2d array
|
||||
-- @param i a row index
|
||||
array2d.remove_row = remove
|
||||
|
||||
--- remove a column from an array.
|
||||
-- @param t a 2d array
|
||||
-- @param j a column index
|
||||
function array2d.remove_col (t,j)
|
||||
assert_arg(1,t,'table')
|
||||
for i = 1,#t do
|
||||
remove(t[i],j)
|
||||
end
|
||||
end
|
||||
|
||||
local Ai = byte 'A'
|
||||
|
||||
local function _parse (s)
|
||||
local c,r
|
||||
if s:sub(1,1) == 'R' then
|
||||
r,c = s:match 'R(%d+)C(%d+)'
|
||||
r,c = tonumber(r),tonumber(c)
|
||||
else
|
||||
c,r = s:match '(.)(.)'
|
||||
c = byte(c) - byte 'A' + 1
|
||||
r = tonumber(r)
|
||||
end
|
||||
assert(c ~= nil and r ~= nil,'bad cell specifier: '..s)
|
||||
return r,c
|
||||
end
|
||||
|
||||
--- parse a spreadsheet range.
|
||||
-- The range can be specified either as 'A1:B2' or 'R1C1:R2C2';
|
||||
-- a special case is a single element (e.g 'A1' or 'R1C1')
|
||||
-- @param s a range.
|
||||
-- @return start col
|
||||
-- @return start row
|
||||
-- @return end col
|
||||
-- @return end row
|
||||
function array2d.parse_range (s)
|
||||
if s:find ':' then
|
||||
local start,finish = splitv(s,':')
|
||||
local i1,j1 = _parse(start)
|
||||
local i2,j2 = _parse(finish)
|
||||
return i1,j1,i2,j2
|
||||
else -- single value
|
||||
local i,j = _parse(s)
|
||||
return i,j
|
||||
end
|
||||
end
|
||||
|
||||
--- get a slice of a 2D array using spreadsheet range notation. @see parse_range
|
||||
-- @param t a 2D array
|
||||
-- @param rstr range expression
|
||||
-- @return a slice
|
||||
-- @see array2d.parse_range
|
||||
-- @see array2d.slice
|
||||
function array2d.range (t,rstr)
|
||||
assert_arg(1,t,'table')
|
||||
local i1,j1,i2,j2 = array2d.parse_range(rstr)
|
||||
if i2 then
|
||||
return array2d.slice(t,i1,j1,i2,j2)
|
||||
else -- single value
|
||||
return t[i1][j1]
|
||||
end
|
||||
end
|
||||
|
||||
local function default_range (t,i1,j1,i2,j2)
|
||||
assert(t and type(t)=='table','not a table')
|
||||
i1,j1 = i1 or 1, j1 or 1
|
||||
i2,j2 = i2 or #t, j2 or #t[1]
|
||||
return i1,j1,i2,j2
|
||||
end
|
||||
|
||||
--- get a slice of a 2D array. Note that if the specified range has
|
||||
-- a 1D result, the rank of the result will be 1.
|
||||
-- @param t a 2D array
|
||||
-- @param i1 start row (default 1)
|
||||
-- @param j1 start col (default 1)
|
||||
-- @param i2 end row (default N)
|
||||
-- @param j2 end col (default M)
|
||||
-- @return an array, 2D in general but 1D in special cases.
|
||||
function array2d.slice (t,i1,j1,i2,j2)
|
||||
assert_arg(1,t,'table')
|
||||
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
|
||||
local res = {}
|
||||
for i = i1,i2 do
|
||||
local val
|
||||
local row = t[i]
|
||||
if j1 == j2 then
|
||||
val = row[j1]
|
||||
else
|
||||
val = {}
|
||||
for j = j1,j2 do
|
||||
val[#val+1] = row[j]
|
||||
end
|
||||
end
|
||||
res[#res+1] = val
|
||||
end
|
||||
if i1 == i2 then res = res[1] end
|
||||
return res
|
||||
end
|
||||
|
||||
--- set a specified range of an array to a value.
|
||||
-- @param t a 2D array
|
||||
-- @param value the value
|
||||
-- @param i1 start row (default 1)
|
||||
-- @param j1 start col (default 1)
|
||||
-- @param i2 end row (default N)
|
||||
-- @param j2 end col (default M)
|
||||
function array2d.set (t,value,i1,j1,i2,j2)
|
||||
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
|
||||
for i = i1,i2 do
|
||||
tset(t[i],value)
|
||||
end
|
||||
end
|
||||
|
||||
--- write a 2D array to a file.
|
||||
-- @param t a 2D array
|
||||
-- @param f a file object (default stdout)
|
||||
-- @param fmt a format string (default is just to use tostring)
|
||||
-- @param i1 start row (default 1)
|
||||
-- @param j1 start col (default 1)
|
||||
-- @param i2 end row (default N)
|
||||
-- @param j2 end col (default M)
|
||||
function array2d.write (t,f,fmt,i1,j1,i2,j2)
|
||||
assert_arg(1,t,'table')
|
||||
f = f or stdout
|
||||
local rowop
|
||||
if fmt then
|
||||
rowop = function(row,j) fprintf(f,fmt,row[j]) end
|
||||
else
|
||||
rowop = function(row,j) f:write(tostring(row[j]),' ') end
|
||||
end
|
||||
local function newline()
|
||||
f:write '\n'
|
||||
end
|
||||
array2d.forall(t,rowop,newline,i1,j1,i2,j2)
|
||||
end
|
||||
|
||||
--- perform an operation for all values in a 2D array.
|
||||
-- @param t 2D array
|
||||
-- @param row_op function to call on each value
|
||||
-- @param end_row_op function to call at end of each row
|
||||
-- @param i1 start row (default 1)
|
||||
-- @param j1 start col (default 1)
|
||||
-- @param i2 end row (default N)
|
||||
-- @param j2 end col (default M)
|
||||
function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2)
|
||||
assert_arg(1,t,'table')
|
||||
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
|
||||
for i = i1,i2 do
|
||||
local row = t[i]
|
||||
for j = j1,j2 do
|
||||
row_op(row,j)
|
||||
end
|
||||
if end_row_op then end_row_op(i) end
|
||||
end
|
||||
end
|
||||
|
||||
--- iterate over all elements in a 2D array, with optional indices.
|
||||
-- @param a 2D array
|
||||
-- @param indices with indices (default false)
|
||||
-- @param i1 start row (default 1)
|
||||
-- @param j1 start col (default 1)
|
||||
-- @param i2 end row (default N)
|
||||
-- @param j2 end col (default M)
|
||||
-- @return either value or i,j,value depending on indices
|
||||
function array2d.iter (a,indices,i1,j1,i2,j2)
|
||||
assert_arg(1,a,'table')
|
||||
local norowset = not (i2 and j2)
|
||||
i1,j1,i2,j2 = default_range(a,i1,j1,i2,j2)
|
||||
local n,i,j = i2-i1+1,i1-1,j1-1
|
||||
local row,nr = nil,0
|
||||
local onr = j2 - j1 + 1
|
||||
return function()
|
||||
j = j + 1
|
||||
if j > nr then
|
||||
j = j1
|
||||
i = i + 1
|
||||
if i > i2 then return nil end
|
||||
row = a[i]
|
||||
nr = norowset and #row or onr
|
||||
end
|
||||
if indices then
|
||||
return i,j,row[j]
|
||||
else
|
||||
return row[j]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function array2d.columns (a)
|
||||
assert_arg(1,a,'table')
|
||||
local n = a[1][1]
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if i > n then return nil end
|
||||
return column(a,i)
|
||||
end
|
||||
end
|
||||
|
||||
return array2d
|
||||
|
||||
|
||||
155
Utils/luarocks/share/lua/5.1/pl/class.lua
Normal file
155
Utils/luarocks/share/lua/5.1/pl/class.lua
Normal file
@@ -0,0 +1,155 @@
|
||||
--- Provides a reuseable and convenient framework for creating classes in Lua.
|
||||
-- Two possible notations: <br> <code> B = class(A) </code> or <code> class.B(A) </code>. <br>
|
||||
-- <p>The latter form creates a named class. </p>
|
||||
-- See the Guide for further <a href="../../index.html#class">discussion</a>
|
||||
-- @module pl.class
|
||||
|
||||
local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
|
||||
_G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
|
||||
-- this trickery is necessary to prevent the inheritance of 'super' and
|
||||
-- the resulting recursive call problems.
|
||||
local function call_ctor (c,obj,...)
|
||||
-- nice alias for the base class ctor
|
||||
local base = rawget(c,'_base')
|
||||
if base then obj.super = rawget(base,'_init') end
|
||||
local res = c._init(obj,...)
|
||||
obj.super = nil
|
||||
return res
|
||||
end
|
||||
|
||||
local function is_a(self,klass)
|
||||
local m = getmetatable(self)
|
||||
if not m then return false end --*can't be an object!
|
||||
while m do
|
||||
if m == klass then return true end
|
||||
m = rawget(m,'_base')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function class_of(klass,obj)
|
||||
if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
|
||||
return klass.is_a(obj,klass)
|
||||
end
|
||||
|
||||
local function _class_tostring (obj)
|
||||
local mt = obj._class
|
||||
local name = rawget(mt,'_name')
|
||||
setmetatable(obj,nil)
|
||||
local str = tostring(obj)
|
||||
setmetatable(obj,mt)
|
||||
if name then str = name ..str:gsub('table','') end
|
||||
return str
|
||||
end
|
||||
|
||||
local function tupdate(td,ts)
|
||||
for k,v in pairs(ts) do
|
||||
td[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
local function _class(base,c_arg,c)
|
||||
c = c or {} -- a new class instance, which is the metatable for all objects of this type
|
||||
-- the class will be the metatable for all its objects,
|
||||
-- and they will look up their methods in it.
|
||||
local mt = {} -- a metatable for the class instance
|
||||
|
||||
if type(base) == 'table' then
|
||||
-- our new class is a shallow copy of the base class!
|
||||
tupdate(c,base)
|
||||
c._base = base
|
||||
-- inherit the 'not found' handler, if present
|
||||
if rawget(c,'_handler') then mt.__index = c._handler end
|
||||
elseif base ~= nil then
|
||||
error("must derive from a table type",3)
|
||||
end
|
||||
|
||||
c.__index = c
|
||||
setmetatable(c,mt)
|
||||
c._init = nil
|
||||
|
||||
if base and rawget(base,'_class_init') then
|
||||
base._class_init(c,c_arg)
|
||||
end
|
||||
|
||||
-- expose a ctor which can be called by <classname>(<args>)
|
||||
mt.__call = function(class_tbl,...)
|
||||
local obj = {}
|
||||
setmetatable(obj,c)
|
||||
|
||||
if rawget(c,'_init') then -- explicit constructor
|
||||
local res = call_ctor(c,obj,...)
|
||||
if res then -- _if_ a ctor returns a value, it becomes the object...
|
||||
obj = res
|
||||
setmetatable(obj,c)
|
||||
end
|
||||
elseif base and rawget(base,'_init') then -- default constructor
|
||||
-- make sure that any stuff from the base class is initialized!
|
||||
call_ctor(base,obj,...)
|
||||
end
|
||||
|
||||
if base and rawget(base,'_post_init') then
|
||||
base._post_init(obj)
|
||||
end
|
||||
|
||||
if not rawget(c,'__tostring') then
|
||||
c.__tostring = _class_tostring
|
||||
end
|
||||
return obj
|
||||
end
|
||||
-- Call Class.catch to set a handler for methods/properties not found in the class!
|
||||
c.catch = function(handler)
|
||||
c._handler = handler
|
||||
mt.__index = handler
|
||||
end
|
||||
c.is_a = is_a
|
||||
c.class_of = class_of
|
||||
c._class = c
|
||||
-- any object can have a specified delegate which is called with unrecognized methods
|
||||
-- if _handler exists and obj[key] is nil, then pass onto handler!
|
||||
c.delegate = function(self,obj)
|
||||
mt.__index = function(tbl,key)
|
||||
local method = obj[key]
|
||||
if method then
|
||||
return function(self,...)
|
||||
return method(obj,...)
|
||||
end
|
||||
elseif self._handler then
|
||||
return self._handler(tbl,key)
|
||||
end
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
--- create a new class, derived from a given base class. <br>
|
||||
-- Supporting two class creation syntaxes:
|
||||
-- either <code>Name = class(base)</code> or <code>class.Name(base)</code>
|
||||
-- @class function
|
||||
-- @name class
|
||||
-- @param base optional base class
|
||||
-- @param c_arg optional parameter to class ctor
|
||||
-- @param c optional table to be used as class
|
||||
local class
|
||||
class = setmetatable({},{
|
||||
__call = function(fun,...)
|
||||
return _class(...)
|
||||
end,
|
||||
__index = function(tbl,key)
|
||||
if key == 'class' then
|
||||
io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n')
|
||||
return class
|
||||
end
|
||||
local env = _G
|
||||
return function(...)
|
||||
local c = _class(...)
|
||||
c._name = key
|
||||
rawset(env,key,c)
|
||||
return c
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
return class
|
||||
|
||||
288
Utils/luarocks/share/lua/5.1/pl/comprehension.lua
Normal file
288
Utils/luarocks/share/lua/5.1/pl/comprehension.lua
Normal file
@@ -0,0 +1,288 @@
|
||||
--- List comprehensions implemented in Lua. <p>
|
||||
--
|
||||
-- See the <a href="http://lua-users.org/wiki/ListComprehensions">wiki page</a>
|
||||
-- <pre class=example>
|
||||
-- local C= require 'pl.comprehension' . new()
|
||||
--
|
||||
-- C ('x for x=1,10') ()
|
||||
-- ==> {1,2,3,4,5,6,7,8,9,10}
|
||||
-- C 'x^2 for x=1,4' ()
|
||||
-- ==> {1,4,9,16}
|
||||
-- C '{x,x^2} for x=1,4' ()
|
||||
-- ==> {{1,1},{2,4},{3,9},{4,16}}
|
||||
-- C '2*x for x' {1,2,3}
|
||||
-- ==> {2,4,6}
|
||||
-- dbl = C '2*x for x'
|
||||
-- dbl {10,20,30}
|
||||
-- ==> {20,40,60}
|
||||
-- C 'x for x if x % 2 == 0' {1,2,3,4,5}
|
||||
-- ==> {2,4}
|
||||
-- C '{x,y} for x = 1,2 for y = 1,2' ()
|
||||
-- ==> {{1,1},{1,2},{2,1},{2,2}}
|
||||
-- C '{x,y} for x for y' ({1,2},{10,20})
|
||||
-- ==> {{1,10},{1,20},{2,10},{2,20}}
|
||||
-- assert(C 'sum(x^2 for x)' {2,3,4} == 2^2+3^2+4^2)
|
||||
-- </pre>
|
||||
--
|
||||
-- <p> (c) 2008 David Manura. Licensed under the same terms as Lua (MIT license).
|
||||
-- <p> -- See <a href="../../index.html#T31">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.comprehension
|
||||
|
||||
local utils = require 'pl.utils'
|
||||
|
||||
--~ local _VERSION, assert, getfenv, ipairs, load, math, pcall, require, setmetatable, table, tonumber =
|
||||
--~ _G._VERSION, _G.assert, _G.getfenv, _G.ipairs, _G.load, _G.math, _G.pcall, _G.require, _G.setmetatable, _G.table, _G.tonumber
|
||||
|
||||
local status,lb = pcall(require, "pl.luabalanced")
|
||||
if not status then
|
||||
lb = require 'luabalanced'
|
||||
end
|
||||
|
||||
local math_max = math.max
|
||||
local table_concat = table.concat
|
||||
|
||||
-- fold operations
|
||||
-- http://en.wikipedia.org/wiki/Fold_(higher-order_function)
|
||||
local ops = {
|
||||
list = {init=' {} ', accum=' __result[#__result+1] = (%s) '},
|
||||
table = {init=' {} ', accum=' local __k, __v = %s __result[__k] = __v '},
|
||||
sum = {init=' 0 ', accum=' __result = __result + (%s) '},
|
||||
min = {init=' nil ', accum=' local __tmp = %s ' ..
|
||||
' if __result then if __tmp < __result then ' ..
|
||||
'__result = __tmp end else __result = __tmp end '},
|
||||
max = {init=' nil ', accum=' local __tmp = %s ' ..
|
||||
' if __result then if __tmp > __result then ' ..
|
||||
'__result = __tmp end else __result = __tmp end '},
|
||||
}
|
||||
|
||||
|
||||
-- Parses comprehension string expr.
|
||||
-- Returns output expression list <out> string, array of for types
|
||||
-- ('=', 'in' or nil) <fortypes>, array of input variable name
|
||||
-- strings <invarlists>, array of input variable value strings
|
||||
-- <invallists>, array of predicate expression strings <preds>,
|
||||
-- operation name string <opname>, and number of placeholder
|
||||
-- parameters <max_param>.
|
||||
--
|
||||
-- The is equivalent to the mathematical set-builder notation:
|
||||
--
|
||||
-- <opname> { <out> | <invarlist> in <invallist> , <preds> }
|
||||
--
|
||||
-- @usage "x^2 for x" -- array values
|
||||
-- @usage "x^2 for x=1,10,2" -- numeric for
|
||||
-- @usage "k^v for k,v in pairs(_1)" -- iterator for
|
||||
-- @usage "(x+y)^2 for x for y if x > y" -- nested
|
||||
--
|
||||
local function parse_comprehension(expr)
|
||||
local t = {}
|
||||
local pos = 1
|
||||
|
||||
-- extract opname (if exists)
|
||||
local opname
|
||||
local tok, post = expr:match('^%s*([%a_][%w_]*)%s*%(()', pos)
|
||||
local pose = #expr + 1
|
||||
if tok then
|
||||
local tok2, posb = lb.match_bracketed(expr, post-1)
|
||||
assert(tok2, 'syntax error')
|
||||
if expr:match('^%s*$', posb) then
|
||||
opname = tok
|
||||
pose = posb - 1
|
||||
pos = post
|
||||
end
|
||||
end
|
||||
opname = opname or "list"
|
||||
|
||||
-- extract out expression list
|
||||
local out; out, pos = lb.match_explist(expr, pos)
|
||||
assert(out, "syntax error: missing expression list")
|
||||
out = table_concat(out, ', ')
|
||||
|
||||
-- extract "for" clauses
|
||||
local fortypes = {}
|
||||
local invarlists = {}
|
||||
local invallists = {}
|
||||
while 1 do
|
||||
local post = expr:match('^%s*for%s+()', pos)
|
||||
if not post then break end
|
||||
pos = post
|
||||
|
||||
-- extract input vars
|
||||
local iv; iv, pos = lb.match_namelist(expr, pos)
|
||||
assert(#iv > 0, 'syntax error: zero variables')
|
||||
for _,ident in ipairs(iv) do
|
||||
assert(not ident:match'^__',
|
||||
"identifier " .. ident .. " may not contain __ prefix")
|
||||
end
|
||||
invarlists[#invarlists+1] = iv
|
||||
|
||||
-- extract '=' or 'in' (optional)
|
||||
local fortype, post = expr:match('^(=)%s*()', pos)
|
||||
if not fortype then fortype, post = expr:match('^(in)%s+()', pos) end
|
||||
if fortype then
|
||||
pos = post
|
||||
-- extract input value range
|
||||
local il; il, pos = lb.match_explist(expr, pos)
|
||||
assert(#il > 0, 'syntax error: zero expressions')
|
||||
assert(fortype ~= '=' or #il == 2 or #il == 3,
|
||||
'syntax error: numeric for requires 2 or three expressions')
|
||||
fortypes[#invarlists] = fortype
|
||||
invallists[#invarlists] = il
|
||||
else
|
||||
fortypes[#invarlists] = false
|
||||
invallists[#invarlists] = false
|
||||
end
|
||||
end
|
||||
assert(#invarlists > 0, 'syntax error: missing "for" clause')
|
||||
|
||||
-- extract "if" clauses
|
||||
local preds = {}
|
||||
while 1 do
|
||||
local post = expr:match('^%s*if%s+()', pos)
|
||||
if not post then break end
|
||||
pos = post
|
||||
local pred; pred, pos = lb.match_expression(expr, pos)
|
||||
assert(pred, 'syntax error: predicated expression not found')
|
||||
preds[#preds+1] = pred
|
||||
end
|
||||
|
||||
-- extract number of parameter variables (name matching "_%d+")
|
||||
local stmp = ''; lb.gsub(expr, function(u, sin) -- strip comments/strings
|
||||
if u == 'e' then stmp = stmp .. ' ' .. sin .. ' ' end
|
||||
end)
|
||||
local max_param = 0; stmp:gsub('[%a_][%w_]*', function(s)
|
||||
local s = s:match('^_(%d+)$')
|
||||
if s then max_param = math_max(max_param, tonumber(s)) end
|
||||
end)
|
||||
|
||||
if pos ~= pose then
|
||||
assert(false, "syntax error: unrecognized " .. expr:sub(pos))
|
||||
end
|
||||
|
||||
--DEBUG:
|
||||
--print('----\n', string.format("%q", expr), string.format("%q", out), opname)
|
||||
--for k,v in ipairs(invarlists) do print(k,v, invallists[k]) end
|
||||
--for k,v in ipairs(preds) do print(k,v) end
|
||||
|
||||
return out, fortypes, invarlists, invallists, preds, opname, max_param
|
||||
end
|
||||
|
||||
|
||||
-- Create Lua code string representing comprehension.
|
||||
-- Arguments are in the form returned by parse_comprehension.
|
||||
local function code_comprehension(
|
||||
out, fortypes, invarlists, invallists, preds, opname, max_param
|
||||
)
|
||||
local op = assert(ops[opname])
|
||||
local code = op.accum:gsub('%%s', out)
|
||||
|
||||
for i=#preds,1,-1 do local pred = preds[i]
|
||||
code = ' if ' .. pred .. ' then ' .. code .. ' end '
|
||||
end
|
||||
for i=#invarlists,1,-1 do
|
||||
if not fortypes[i] then
|
||||
local arrayname = '__in' .. i
|
||||
local idx = '__idx' .. i
|
||||
code =
|
||||
' for ' .. idx .. ' = 1, #' .. arrayname .. ' do ' ..
|
||||
' local ' .. invarlists[i][1] .. ' = ' .. arrayname .. '['..idx..'] ' ..
|
||||
code .. ' end '
|
||||
else
|
||||
code =
|
||||
' for ' ..
|
||||
table_concat(invarlists[i], ', ') ..
|
||||
' ' .. fortypes[i] .. ' ' ..
|
||||
table_concat(invallists[i], ', ') ..
|
||||
' do ' .. code .. ' end '
|
||||
end
|
||||
end
|
||||
code = ' local __result = ( ' .. op.init .. ' ) ' .. code
|
||||
return code
|
||||
end
|
||||
|
||||
|
||||
-- Convert code string represented by code_comprehension
|
||||
-- into Lua function. Also must pass ninputs = #invarlists,
|
||||
-- max_param, and invallists (from parse_comprehension).
|
||||
-- Uses environment env.
|
||||
local function wrap_comprehension(code, ninputs, max_param, invallists, env)
|
||||
assert(ninputs > 0)
|
||||
local ts = {}
|
||||
for i=1,max_param do
|
||||
ts[#ts+1] = '_' .. i
|
||||
end
|
||||
for i=1,ninputs do
|
||||
if not invallists[i] then
|
||||
local name = '__in' .. i
|
||||
ts[#ts+1] = name
|
||||
end
|
||||
end
|
||||
if #ts > 0 then
|
||||
code = ' local ' .. table_concat(ts, ', ') .. ' = ... ' .. code
|
||||
end
|
||||
code = code .. ' return __result '
|
||||
--print('DEBUG:', code)
|
||||
local f, err = utils.load(code,'tmp','t',env)
|
||||
if not f then assert(false, err .. ' with generated code ' .. code) end
|
||||
return f
|
||||
end
|
||||
|
||||
|
||||
-- Build Lua function from comprehension string.
|
||||
-- Uses environment env.
|
||||
local function build_comprehension(expr, env)
|
||||
local out, fortypes, invarlists, invallists, preds, opname, max_param
|
||||
= parse_comprehension(expr)
|
||||
local code = code_comprehension(
|
||||
out, fortypes, invarlists, invallists, preds, opname, max_param)
|
||||
local f = wrap_comprehension(code, #invarlists, max_param, invallists, env)
|
||||
return f
|
||||
end
|
||||
|
||||
|
||||
-- Creates new comprehension cache.
|
||||
-- Any list comprehension function created are set to the environment
|
||||
-- env (defaults to caller of new).
|
||||
local function new(env)
|
||||
-- Note: using a single global comprehension cache would have had
|
||||
-- security implications (e.g. retrieving cached functions created
|
||||
-- in other environments).
|
||||
-- The cache lookup function could have instead been written to retrieve
|
||||
-- the caller's environment, lookup up the cache private to that
|
||||
-- environment, and then looked up the function in that cache.
|
||||
-- That would avoid the need for this <new> call to
|
||||
-- explicitly manage caches; however, that might also have an undue
|
||||
-- performance penalty.
|
||||
|
||||
if not env then
|
||||
env = getfenv(2)
|
||||
end
|
||||
|
||||
local mt = {}
|
||||
local cache = setmetatable({}, mt)
|
||||
|
||||
-- Index operator builds, caches, and returns Lua function
|
||||
-- corresponding to comprehension expression string.
|
||||
--
|
||||
-- Example: f = comprehension['x^2 for x']
|
||||
--
|
||||
function mt:__index(expr)
|
||||
local f = build_comprehension(expr, env)
|
||||
self[expr] = f -- cache
|
||||
return f
|
||||
end
|
||||
|
||||
-- Convenience syntax.
|
||||
-- Allows comprehension 'x^2 for x' instead of comprehension['x^2 for x'].
|
||||
mt.__call = mt.__index
|
||||
|
||||
cache.new = new
|
||||
|
||||
return cache
|
||||
end
|
||||
|
||||
|
||||
local comprehension = {}
|
||||
comprehension.new = new
|
||||
|
||||
return comprehension
|
||||
169
Utils/luarocks/share/lua/5.1/pl/config.lua
Normal file
169
Utils/luarocks/share/lua/5.1/pl/config.lua
Normal file
@@ -0,0 +1,169 @@
|
||||
--- Reads configuration files into a Lua table. <p>
|
||||
-- Understands INI files, classic Unix config files, and simple
|
||||
-- delimited columns of values. <p>
|
||||
-- <pre class=example>
|
||||
-- # test.config
|
||||
-- # Read timeout in seconds
|
||||
-- read.timeout=10
|
||||
-- # Write timeout in seconds
|
||||
-- write.timeout=5
|
||||
-- #acceptable ports
|
||||
-- ports = 1002,1003,1004
|
||||
--
|
||||
-- -- readconfig.lua
|
||||
-- require 'pl'
|
||||
-- local t = config.read 'test.config'
|
||||
-- print(pretty.write(t))
|
||||
--
|
||||
-- ### output #####
|
||||
-- {
|
||||
-- ports = {
|
||||
-- 1002,
|
||||
-- 1003,
|
||||
-- 1004
|
||||
-- },
|
||||
-- write_timeout = 5,
|
||||
-- read_timeout = 10
|
||||
-- }
|
||||
-- </pre>
|
||||
-- See the Guide for further <a href="../../index.html#config">discussion</a>
|
||||
-- @class module
|
||||
-- @name pl.config
|
||||
|
||||
local type,tonumber,ipairs,io, table = _G.type,_G.tonumber,_G.ipairs,_G.io,_G.table
|
||||
|
||||
local function split(s,re)
|
||||
local res = {}
|
||||
local t_insert = table.insert
|
||||
re = '[^'..re..']+'
|
||||
for k in s:gmatch(re) do t_insert(res,k) end
|
||||
return res
|
||||
end
|
||||
|
||||
local function strip(s)
|
||||
return s:gsub('^%s+',''):gsub('%s+$','')
|
||||
end
|
||||
|
||||
local function strip_quotes (s)
|
||||
return s:gsub("['\"](.*)['\"]",'%1')
|
||||
end
|
||||
|
||||
local config = {}
|
||||
|
||||
--- like io.lines(), but allows for lines to be continued with '\'.
|
||||
-- @param file a file-like object (anything where read() returns the next line) or a filename.
|
||||
-- Defaults to stardard input.
|
||||
-- @return an iterator over the lines, or nil
|
||||
-- @return error 'not a file-like object' or 'file is nil'
|
||||
function config.lines(file)
|
||||
local f,openf,err
|
||||
local line = ''
|
||||
if type(file) == 'string' then
|
||||
f,err = io.open(file,'r')
|
||||
if not f then return nil,err end
|
||||
openf = true
|
||||
else
|
||||
f = file or io.stdin
|
||||
if not file.read then return nil, 'not a file-like object' end
|
||||
end
|
||||
if not f then return nil, 'file is nil' end
|
||||
return function()
|
||||
local l = f:read()
|
||||
while l do
|
||||
-- does the line end with '\'?
|
||||
local i = l:find '\\%s*$'
|
||||
if i then -- if so,
|
||||
line = line..l:sub(1,i-1)
|
||||
elseif line == '' then
|
||||
return l
|
||||
else
|
||||
l = line..l
|
||||
line = ''
|
||||
return l
|
||||
end
|
||||
l = f:read()
|
||||
end
|
||||
if openf then f:close() end
|
||||
end
|
||||
end
|
||||
|
||||
--- read a configuration file into a table
|
||||
-- @param file either a file-like object or a string, which must be a filename
|
||||
-- @param cnfg a configuration table that may contain these fields:
|
||||
-- <ul>
|
||||
-- <li> variablilize make names into valid Lua identifiers (default true)</li>
|
||||
-- <li> convert_numbers try to convert values into numbers (default true)</li>
|
||||
-- <li> trim_space ensure that there is no starting or trailing whitespace with values (default true)</li>
|
||||
-- <li> trim_quotes remove quotes from strings (default false)</li>
|
||||
-- <li> list_delim delimiter to use when separating columns (default ',')</li>
|
||||
-- </ul>
|
||||
-- @return a table containing items, or nil
|
||||
-- @return error message (same as @{config.lines}
|
||||
function config.read(file,cnfg)
|
||||
local f,openf,err
|
||||
cnfg = cnfg or {}
|
||||
local function check_cnfg (var,def)
|
||||
local val = cnfg[var]
|
||||
if val == nil then return def else return val end
|
||||
end
|
||||
local t = {}
|
||||
local top_t = t
|
||||
local variablilize = check_cnfg ('variabilize',true)
|
||||
local list_delim = check_cnfg('list_delim',',')
|
||||
local convert_numbers = check_cnfg('convert_numbers',true)
|
||||
local trim_space = check_cnfg('trim_space',true)
|
||||
local trim_quotes = check_cnfg('trim_quotes',false)
|
||||
local ignore_assign = check_cnfg('ignore_assign',false)
|
||||
|
||||
local function process_name(key)
|
||||
if variablilize then
|
||||
key = key:gsub('[^%w]','_')
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
local function process_value(value)
|
||||
if list_delim and value:find(list_delim) then
|
||||
value = split(value,list_delim)
|
||||
for i,v in ipairs(value) do
|
||||
value[i] = process_value(v)
|
||||
end
|
||||
elseif convert_numbers and value:find('^[%d%+%-]') then
|
||||
local val = tonumber(value)
|
||||
if val then value = val end
|
||||
end
|
||||
if type(value) == 'string' then
|
||||
if trim_space then value = strip(value) end
|
||||
if trim_quotes then value = strip_quotes(value) end
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local iter,err = config.lines(file)
|
||||
if not iter then return nil,err end
|
||||
for line in iter do
|
||||
-- strips comments
|
||||
local ci = line:find('%s*[#;]')
|
||||
if ci then line = line:sub(1,ci-1) end
|
||||
-- and ignore blank lines
|
||||
if line:find('^%s*$') then
|
||||
elseif line:find('^%[') then -- section!
|
||||
local section = process_name(line:match('%[([^%]]+)%]'))
|
||||
t = top_t
|
||||
t[section] = {}
|
||||
t = t[section]
|
||||
else
|
||||
local i1,i2 = line:find('%s*=%s*')
|
||||
if i1 and not ignore_assign then -- key,value assignment
|
||||
local key = process_name(line:sub(1,i1-1))
|
||||
local value = process_value(line:sub(i2+1))
|
||||
t[key] = value
|
||||
else -- a plain list of values...
|
||||
t[#t+1] = process_value(line)
|
||||
end
|
||||
end
|
||||
end
|
||||
return top_t
|
||||
end
|
||||
|
||||
return config
|
||||
588
Utils/luarocks/share/lua/5.1/pl/data.lua
Normal file
588
Utils/luarocks/share/lua/5.1/pl/data.lua
Normal file
@@ -0,0 +1,588 @@
|
||||
--- Reading and querying simple tabular data.
|
||||
-- <pre class=example>
|
||||
-- data.read 'test.txt'
|
||||
-- ==> {{10,20},{2,5},{40,50},fieldnames={'x','y'},delim=','}
|
||||
-- </pre>
|
||||
-- Provides a way of creating basic SQL-like queries.
|
||||
-- <pre class=example>
|
||||
-- require 'pl'
|
||||
-- local d = data.read('xyz.txt')
|
||||
-- local q = d:select('x,y,z where x > 3 and z < 2 sort by y')
|
||||
-- for x,y,z in q do
|
||||
-- print(x,y,z)
|
||||
-- end
|
||||
-- </pre>
|
||||
-- <p>See <a href="../../index.html#data">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.data
|
||||
|
||||
local utils = require 'pl.utils'
|
||||
local _DEBUG = rawget(_G,'_DEBUG')
|
||||
|
||||
local patterns,function_arg,usplit = utils.patterns,utils.function_arg,utils.split
|
||||
local append,concat = table.insert,table.concat
|
||||
local gsub = string.gsub
|
||||
local io = io
|
||||
local _G,print,loadstring,type,tonumber,ipairs,setmetatable,pcall,error,setfenv = _G,print,loadstring,type,tonumber,ipairs,setmetatable,pcall,error,setfenv
|
||||
|
||||
--[[
|
||||
module ('pl.data',utils._module)
|
||||
]]
|
||||
|
||||
local data = {}
|
||||
|
||||
local parse_select
|
||||
|
||||
local function count(s,chr)
|
||||
chr = utils.escape(chr)
|
||||
local _,cnt = s:gsub(chr,' ')
|
||||
return cnt
|
||||
end
|
||||
|
||||
local function rstrip(s)
|
||||
return s:gsub('%s+$','')
|
||||
end
|
||||
|
||||
local function make_list(l)
|
||||
return setmetatable(l,utils.stdmt.List)
|
||||
end
|
||||
|
||||
local function split(s,delim)
|
||||
return make_list(usplit(s,delim))
|
||||
end
|
||||
|
||||
local function map(fun,t)
|
||||
local res = {}
|
||||
for i = 1,#t do
|
||||
append(res,fun(t[i]))
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function find(t,v)
|
||||
for i = 1,#t do
|
||||
if v == t[i] then return i end
|
||||
end
|
||||
end
|
||||
|
||||
local DataMT = {
|
||||
column_by_name = function(self,name)
|
||||
if type(name) == 'number' then
|
||||
name = '$'..name
|
||||
end
|
||||
local arr = {}
|
||||
for res in data.query(self,name) do
|
||||
append(arr,res)
|
||||
end
|
||||
return make_list(arr)
|
||||
end,
|
||||
|
||||
copy_select = function(self,condn)
|
||||
condn = parse_select(condn,self)
|
||||
local iter = data.query(self,condn)
|
||||
local res = {}
|
||||
local row = make_list{iter()}
|
||||
while #row > 0 do
|
||||
append(res,row)
|
||||
row = make_list{iter()}
|
||||
end
|
||||
res.delim = self.delim
|
||||
return data.new(res,split(condn.fields,','))
|
||||
end,
|
||||
|
||||
column_names = function(self)
|
||||
return self.fieldnames
|
||||
end,
|
||||
}
|
||||
DataMT.__index = DataMT
|
||||
|
||||
--- return a particular column as a list of values (Method). <br>
|
||||
-- @param name either name of column, or numerical index.
|
||||
-- @class function
|
||||
-- @name Data.column_by_name
|
||||
|
||||
--- return a query iterator on this data object (Method). <br>
|
||||
-- @param condn the query expression
|
||||
-- @class function
|
||||
-- @name Data.select
|
||||
-- @see data.query
|
||||
|
||||
--- return a new data object based on this query (Method). <br>
|
||||
-- @param condn the query expression
|
||||
-- @class function
|
||||
-- @name Data.copy_select
|
||||
|
||||
--- return the field names of this data object (Method). <br>
|
||||
-- @class function
|
||||
-- @name Data.column_names
|
||||
|
||||
--- write out a row (Method). <br>
|
||||
-- @param f file-like object
|
||||
-- @class function
|
||||
-- @name Data.write_row
|
||||
|
||||
--- write data out to file(Method). <br>
|
||||
-- @param f file-like object
|
||||
-- @class function
|
||||
-- @name Data.write
|
||||
|
||||
|
||||
-- [guessing delimiter] We check for comma, tab and spaces in that order.
|
||||
-- [issue] any other delimiters to be checked?
|
||||
local delims = {',','\t',' ',';'}
|
||||
|
||||
local function guess_delim (line)
|
||||
for _,delim in ipairs(delims) do
|
||||
if count(line,delim) > 0 then
|
||||
return delim == ' ' and '%s+' or delim
|
||||
end
|
||||
end
|
||||
return ' '
|
||||
end
|
||||
|
||||
-- [file parameter] If it's a string, we try open as a filename. If nil, then
|
||||
-- either stdin or stdout depending on the mode. Otherwise, check if this is
|
||||
-- a file-like object (implements read or write depending)
|
||||
local function open_file (f,mode)
|
||||
local opened, err
|
||||
local reading = mode == 'r'
|
||||
if type(f) == 'string' then
|
||||
if f == 'stdin' then
|
||||
f = io.stdin
|
||||
elseif f == 'stdout' then
|
||||
f = io.stdout
|
||||
else
|
||||
f,err = io.open(f,mode)
|
||||
if not f then return nil,err end
|
||||
opened = true
|
||||
end
|
||||
end
|
||||
if f and ((reading and not f.read) or (not reading and not f.write)) then
|
||||
return nil, "not a file-like object"
|
||||
end
|
||||
return f,nil,opened
|
||||
end
|
||||
|
||||
local function all_n ()
|
||||
|
||||
end
|
||||
|
||||
--- read a delimited file in a Lua table.
|
||||
-- By default, attempts to treat first line as separated list of fieldnames.
|
||||
-- @param file a filename or a file-like object (default stdin)
|
||||
-- @param cnfg options table: can override delim (a string pattern), fieldnames (a list),
|
||||
-- specify no_convert (default is to convert), numfields (indices of columns known
|
||||
-- to be numbers) and thousands_dot (thousands separator in Excel CSV is '.')
|
||||
function data.read(file,cnfg)
|
||||
local convert,err,opened
|
||||
local D = {}
|
||||
if not cnfg then cnfg = {} end
|
||||
local f,err,opened = open_file(file,'r')
|
||||
if not f then return nil, err end
|
||||
local thousands_dot = cnfg.thousands_dot
|
||||
|
||||
local function try_tonumber(x)
|
||||
if thousands_dot then x = x:gsub('%.(...)','%1') end
|
||||
return tonumber(x)
|
||||
end
|
||||
|
||||
local line = f:read()
|
||||
if not line then return nil, "empty file" end
|
||||
-- first question: what is the delimiter?
|
||||
D.delim = cnfg.delim and cnfg.delim or guess_delim(line)
|
||||
local delim = D.delim
|
||||
local collect_end = cnfg.last_field_collect
|
||||
local numfields = cnfg.numfields
|
||||
-- some space-delimited data starts with a space. This should not be a column,
|
||||
-- although it certainly would be for comma-separated, etc.
|
||||
local strip
|
||||
if delim == '%s+' and line:find(delim) == 1 then
|
||||
strip = function(s) return s:gsub('^%s+','') end
|
||||
line = strip(line)
|
||||
end
|
||||
-- first line will usually be field names. Unless fieldnames are specified,
|
||||
-- we check if it contains purely numerical values for the case of reading
|
||||
-- plain data files.
|
||||
if not cnfg.fieldnames then
|
||||
local fields = split(line,delim)
|
||||
local nums = map(tonumber,fields)
|
||||
if #nums == #fields then
|
||||
convert = tonumber
|
||||
append(D,nums)
|
||||
numfields = {}
|
||||
for i = 1,#nums do numfields[i] = i end
|
||||
else
|
||||
cnfg.fieldnames = fields
|
||||
end
|
||||
line = f:read()
|
||||
if strip then line = strip(line) end
|
||||
elseif type(cnfg.fieldnames) == 'string' then
|
||||
cnfg.fieldnames = split(cnfg.fieldnames,delim)
|
||||
end
|
||||
-- at this point, the column headers have been read in. If the first
|
||||
-- row consisted of numbers, it has already been added to the dataset.
|
||||
if cnfg.fieldnames then
|
||||
D.fieldnames = cnfg.fieldnames
|
||||
-- [conversion] unless @no_convert, we need the numerical field indices
|
||||
-- of the first data row. Can also be specified by @numfields.
|
||||
if not cnfg.no_convert then
|
||||
if not numfields then
|
||||
numfields = {}
|
||||
local fields = split(line,D.delim)
|
||||
for i = 1,#fields do
|
||||
if tonumber(fields[i]) then
|
||||
append(numfields,i)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #numfields > 0 then -- there are numerical fields
|
||||
-- note that using dot as the thousands separator (@thousands_dot)
|
||||
-- requires a special conversion function!
|
||||
convert = thousands_dot and try_tonumber or tonumber
|
||||
end
|
||||
end
|
||||
end
|
||||
-- keep going until finished
|
||||
while line do
|
||||
if not line:find ('^%s*$') then
|
||||
if strip then line = strip(line) end
|
||||
local fields = split(line,delim)
|
||||
if convert then
|
||||
for k = 1,#numfields do
|
||||
local i = numfields[k]
|
||||
local val = convert(fields[i])
|
||||
if val == nil then
|
||||
return nil, "not a number: "..fields[i]
|
||||
else
|
||||
fields[i] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
-- [collecting end field] If @last_field_collect then we will collect
|
||||
-- all extra space-delimited fields into a single last field.
|
||||
if collect_end and #fields > #D.fieldnames then
|
||||
local ends,N = {},#D.fieldnames
|
||||
for i = N+1,#fields do
|
||||
append(ends,fields[i])
|
||||
end
|
||||
ends = concat(ends,' ')
|
||||
local cfields = {}
|
||||
for i = 1,N do cfields[i] = fields[i] end
|
||||
cfields[N] = cfields[N]..' '..ends
|
||||
fields = cfields
|
||||
end
|
||||
append(D,fields)
|
||||
end
|
||||
line = f:read()
|
||||
end
|
||||
if opened then f:close() end
|
||||
if delim == '%s+' then D.delim = ' ' end
|
||||
if not D.fieldnames then D.fieldnames = {} end
|
||||
return data.new(D)
|
||||
end
|
||||
|
||||
local function write_row (data,f,row)
|
||||
f:write(concat(row,data.delim),'\n')
|
||||
end
|
||||
|
||||
DataMT.write_row = write_row
|
||||
|
||||
local function write (data,file)
|
||||
local f,err,opened = open_file(file,'w')
|
||||
if not f then return nil, err end
|
||||
if #data.fieldnames > 0 then
|
||||
f:write(concat(data.fieldnames,data.delim),'\n')
|
||||
end
|
||||
for i = 1,#data do
|
||||
write_row(data,f,data[i])
|
||||
end
|
||||
if opened then f:close() end
|
||||
end
|
||||
|
||||
DataMT.write = write
|
||||
|
||||
local function massage_fieldnames (fields)
|
||||
-- [fieldnames must be valid Lua identifiers] fix 0.8 was %A
|
||||
for i = 1,#fields do
|
||||
fields[i] = fields[i]:gsub('%W','_')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- create a new dataset from a table of rows. <br>
|
||||
-- Can specify the fieldnames, else the table must have a field called
|
||||
-- 'fieldnames', which is either a string of delimiter-separated names,
|
||||
-- or a table of names. <br>
|
||||
-- If the table does not have a field called 'delim', then an attempt will be
|
||||
-- made to guess it from the fieldnames string, defaults otherwise to tab.
|
||||
-- @param d the table.
|
||||
-- @param fieldnames optional fieldnames
|
||||
-- @return the table.
|
||||
function data.new (d,fieldnames)
|
||||
d.fieldnames = d.fieldnames or fieldnames
|
||||
if not d.delim and type(d.fieldnames) == 'string' then
|
||||
d.delim = guess_delim(d.fieldnames)
|
||||
d.fieldnames = split(d.fieldnames,d.delim)
|
||||
end
|
||||
d.fieldnames = make_list(d.fieldnames)
|
||||
massage_fieldnames(d.fieldnames)
|
||||
setmetatable(d,DataMT)
|
||||
-- a query with just the fieldname will return a sequence
|
||||
-- of values, which seq.copy turns into a table.
|
||||
return d
|
||||
end
|
||||
|
||||
local sorted_query = [[
|
||||
return function (t)
|
||||
local i = 0
|
||||
local v
|
||||
local ls = {}
|
||||
for i,v in ipairs(t) do
|
||||
if CONDITION then
|
||||
ls[#ls+1] = v
|
||||
end
|
||||
end
|
||||
table.sort(ls,function(v1,v2)
|
||||
return SORT_EXPR
|
||||
end)
|
||||
local n = #ls
|
||||
return function()
|
||||
i = i + 1
|
||||
v = ls[i]
|
||||
if i > n then return end
|
||||
return FIELDLIST
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
-- question: is this optimized case actually worth the extra code?
|
||||
local simple_query = [[
|
||||
return function (t)
|
||||
local n = #t
|
||||
local i = 0
|
||||
local v
|
||||
return function()
|
||||
repeat
|
||||
i = i + 1
|
||||
v = t[i]
|
||||
until i > n or CONDITION
|
||||
if i > n then return end
|
||||
return FIELDLIST
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
local function is_string (s)
|
||||
return type(s) == 'string'
|
||||
end
|
||||
|
||||
local field_error
|
||||
|
||||
local function fieldnames_as_string (data)
|
||||
return concat(data.fieldnames,',')
|
||||
end
|
||||
|
||||
local function massage_fields(data,f)
|
||||
local idx
|
||||
if f:find '^%d+$' then
|
||||
idx = tonumber(f)
|
||||
else
|
||||
idx = find(data.fieldnames,f)
|
||||
end
|
||||
if idx then
|
||||
return 'v['..idx..']'
|
||||
else
|
||||
field_error = f..' not found in '..fieldnames_as_string(data)
|
||||
return f
|
||||
end
|
||||
end
|
||||
|
||||
local List = require 'pl.List'
|
||||
|
||||
local function process_select (data,parms)
|
||||
--- preparing fields ----
|
||||
local res,ret
|
||||
field_error = nil
|
||||
local fields = parms.fields
|
||||
local numfields = fields:find '%$' or #data.fieldnames == 0
|
||||
if fields:find '^%s*%*%s*' then
|
||||
if not numfields then
|
||||
fields = fieldnames_as_string(data)
|
||||
else
|
||||
local ncol = #data[1]
|
||||
fields = {}
|
||||
for i = 1,ncol do append(fields,'$'..i) end
|
||||
fields = concat(fields,',')
|
||||
end
|
||||
end
|
||||
local idpat = patterns.IDEN
|
||||
if numfields then
|
||||
idpat = '%$(%d+)'
|
||||
else
|
||||
-- massage field names to replace non-identifier chars
|
||||
fields = rstrip(fields):gsub('[^,%w]','_')
|
||||
end
|
||||
local massage_fields = utils.bind1(massage_fields,data)
|
||||
ret = gsub(fields,idpat,massage_fields)
|
||||
if field_error then return nil,field_error end
|
||||
parms.fields = fields
|
||||
parms.proc_fields = ret
|
||||
parms.where = parms.where or 'true'
|
||||
if is_string(parms.where) then
|
||||
parms.where = gsub(parms.where,idpat,massage_fields)
|
||||
field_error = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
parse_select = function(s,data)
|
||||
local endp
|
||||
local parms = {}
|
||||
local w1,w2 = s:find('where ')
|
||||
local s1,s2 = s:find('sort by ')
|
||||
if w1 then -- where clause!
|
||||
endp = (s1 or 0)-1
|
||||
parms.where = s:sub(w2+1,endp)
|
||||
end
|
||||
if s1 then -- sort by clause (must be last!)
|
||||
parms.sort_by = s:sub(s2+1)
|
||||
end
|
||||
endp = (w1 or s1 or 0)-1
|
||||
parms.fields = s:sub(1,endp)
|
||||
local status,err = process_select(data,parms)
|
||||
if not status then return nil,err
|
||||
else return parms end
|
||||
end
|
||||
|
||||
--- create a query iterator from a select string.
|
||||
-- Select string has this format: <br>
|
||||
-- FIELDLIST [ where LUA-CONDN [ sort by FIELD] ]<br>
|
||||
-- FIELDLIST is a comma-separated list of valid fields, or '*'. <br> <br>
|
||||
-- The condition can also be a table, with fields 'fields' (comma-sep string or
|
||||
-- table), 'sort_by' (string) and 'where' (Lua expression string or function)
|
||||
-- @param data table produced by read
|
||||
-- @param condn select string or table
|
||||
-- @param context a list of tables to be searched when resolving functions
|
||||
-- @param return_row if true, wrap the results in a row table
|
||||
-- @return an iterator over the specified fields, or nil
|
||||
-- @return an error message
|
||||
function data.query(data,condn,context,return_row)
|
||||
local err
|
||||
if is_string(condn) then
|
||||
condn,err = parse_select(condn,data)
|
||||
if not condn then return nil,err end
|
||||
elseif type(condn) == 'table' then
|
||||
if type(condn.fields) == 'table' then
|
||||
condn.fields = concat(condn.fields,',')
|
||||
end
|
||||
if not condn.proc_fields then
|
||||
local status,err = process_select(data,condn)
|
||||
if not status then return nil,err end
|
||||
end
|
||||
else
|
||||
return nil, "condition must be a string or a table"
|
||||
end
|
||||
local query, k
|
||||
if condn.sort_by then -- use sorted_query
|
||||
query = sorted_query
|
||||
else
|
||||
query = simple_query
|
||||
end
|
||||
local fields = condn.proc_fields or condn.fields
|
||||
if return_row then
|
||||
fields = '{'..fields..'}'
|
||||
end
|
||||
query,k = query:gsub('FIELDLIST',fields)
|
||||
if is_string(condn.where) then
|
||||
query = query:gsub('CONDITION',condn.where)
|
||||
condn.where = nil
|
||||
else
|
||||
query = query:gsub('CONDITION','_condn(v)')
|
||||
condn.where = function_arg(0,condn.where,'condition.where must be callable')
|
||||
end
|
||||
if condn.sort_by then
|
||||
local expr,sort_var,sort_dir
|
||||
local sort_by = condn.sort_by
|
||||
local i1,i2 = sort_by:find('%s+')
|
||||
if i1 then
|
||||
sort_var,sort_dir = sort_by:sub(1,i1-1),sort_by:sub(i2+1)
|
||||
else
|
||||
sort_var = sort_by
|
||||
sort_dir = 'asc'
|
||||
end
|
||||
if sort_var:match '^%$' then sort_var = sort_var:sub(2) end
|
||||
sort_var = massage_fields(data,sort_var)
|
||||
if field_error then return nil,field_error end
|
||||
if sort_dir == 'asc' then
|
||||
sort_dir = '<'
|
||||
else
|
||||
sort_dir = '>'
|
||||
end
|
||||
expr = ('%s %s %s'):format(sort_var:gsub('v','v1'),sort_dir,sort_var:gsub('v','v2'))
|
||||
query = query:gsub('SORT_EXPR',expr)
|
||||
end
|
||||
if condn.where then
|
||||
query = 'return function(_condn) '..query..' end'
|
||||
end
|
||||
if _DEBUG then print(query) end
|
||||
|
||||
local fn,err = loadstring(query,'tmp')
|
||||
if not fn then return nil,err end
|
||||
fn = fn() -- get the function
|
||||
if condn.where then
|
||||
fn = fn(condn.where)
|
||||
end
|
||||
local qfun = fn(data)
|
||||
if context then
|
||||
-- [specifying context for condition] @context is a list of tables which are
|
||||
-- 'injected'into the condition's custom context
|
||||
append(context,_G)
|
||||
local lookup = {}
|
||||
setfenv(qfun,lookup)
|
||||
setmetatable(lookup,{
|
||||
__index = function(tbl,key)
|
||||
-- _G.print(tbl,key)
|
||||
for k,t in ipairs(context) do
|
||||
if t[key] then return t[key] end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
return qfun
|
||||
end
|
||||
|
||||
|
||||
DataMT.select = data.query
|
||||
DataMT.select_row = function(d,condn,context)
|
||||
return data.query(d,condn,context,true)
|
||||
end
|
||||
|
||||
--- Filter input using a query.
|
||||
-- @param Q a query string
|
||||
-- @param infile filename or file-like object
|
||||
-- @param outfile filename or file-like object
|
||||
-- @param dont_fail true if you want to return an error, not just fail
|
||||
function data.filter (Q,infile,outfile,dont_fail)
|
||||
local err
|
||||
local d = data.read(infile or 'stdin')
|
||||
local out = open_file(outfile or 'stdout')
|
||||
local iter,err = d:select(Q)
|
||||
local delim = d.delim
|
||||
if not iter then
|
||||
err = 'error: '..err
|
||||
if dont_fail then
|
||||
return nil,err
|
||||
else
|
||||
utils.quit(1,err)
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local res = {iter()}
|
||||
if #res == 0 then break end
|
||||
out:write(concat(res,delim),'\n')
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
|
||||
478
Utils/luarocks/share/lua/5.1/pl/dir.lua
Normal file
478
Utils/luarocks/share/lua/5.1/pl/dir.lua
Normal file
@@ -0,0 +1,478 @@
|
||||
--- Useful functions for getting directory contents and matching them against wildcards.
|
||||
-- @class module
|
||||
-- @name pl.dir
|
||||
|
||||
local utils = require 'pl.utils'
|
||||
local path = require 'pl.path'
|
||||
local is_windows = path.is_windows
|
||||
local tablex = require 'pl.tablex'
|
||||
local ldir = path.dir
|
||||
local chdir = path.chdir
|
||||
local mkdir = path.mkdir
|
||||
local rmdir = path.rmdir
|
||||
local sub = string.sub
|
||||
local os,pcall,ipairs,pairs,require,setmetatable,_G = os,pcall,ipairs,pairs,require,setmetatable,_G
|
||||
local remove = os.remove
|
||||
local append = table.insert
|
||||
local wrap = coroutine.wrap
|
||||
local yield = coroutine.yield
|
||||
local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise
|
||||
local List = utils.stdmt.List
|
||||
|
||||
--[[
|
||||
module ('pl.dir',utils._module)
|
||||
]]
|
||||
|
||||
local dir = {}
|
||||
|
||||
local function assert_dir (n,val)
|
||||
assert_arg(n,val,'string',path.isdir,'not a directory')
|
||||
end
|
||||
|
||||
local function assert_file (n,val)
|
||||
assert_arg(n,val,'string',path.isfile,'not a file')
|
||||
end
|
||||
|
||||
local function filemask(mask)
|
||||
mask = utils.escape(mask)
|
||||
return mask:gsub('%%%*','.+'):gsub('%%%?','.')..'$'
|
||||
end
|
||||
|
||||
--- does the filename match the shell pattern?.
|
||||
-- (cf. fnmatch.fnmatch in Python, 11.8)
|
||||
-- @param file A file name
|
||||
-- @param pattern A shell pattern
|
||||
-- @return true or false
|
||||
-- @raise file and pattern must be strings
|
||||
function dir.fnmatch(file,pattern)
|
||||
assert_string(1,file)
|
||||
assert_string(2,pattern)
|
||||
return path.normcase(file):find(filemask(pattern)) ~= nil
|
||||
end
|
||||
|
||||
--- return a list of all files which match the pattern.
|
||||
-- (cf. fnmatch.filter in Python, 11.8)
|
||||
-- @param files A table containing file names
|
||||
-- @param pattern A shell pattern.
|
||||
-- @return list of files
|
||||
-- @raise file and pattern must be strings
|
||||
function dir.filter(files,pattern)
|
||||
assert_arg(1,files,'table')
|
||||
assert_string(2,pattern)
|
||||
local res = {}
|
||||
local mask = filemask(pattern)
|
||||
for i,f in ipairs(files) do
|
||||
if f:find(mask) then append(res,f) end
|
||||
end
|
||||
return setmetatable(res,List)
|
||||
end
|
||||
|
||||
local function _listfiles(dir,filemode,match)
|
||||
local res = {}
|
||||
local check = utils.choose(filemode,path.isfile,path.isdir)
|
||||
if not dir then dir = '.' end
|
||||
for f in ldir(dir) do
|
||||
if f ~= '.' and f ~= '..' then
|
||||
local p = path.join(dir,f)
|
||||
if check(p) and (not match or match(p)) then
|
||||
append(res,p)
|
||||
end
|
||||
end
|
||||
end
|
||||
return setmetatable(res,List)
|
||||
end
|
||||
|
||||
--- return a list of all files in a directory which match the a shell pattern.
|
||||
-- @param dir A directory. If not given, all files in current directory are returned.
|
||||
-- @param mask A shell pattern. If not given, all files are returned.
|
||||
-- @return lsit of files
|
||||
-- @raise dir and mask must be strings
|
||||
function dir.getfiles(dir,mask)
|
||||
assert_dir(1,dir)
|
||||
assert_string(2,mask)
|
||||
local match
|
||||
if mask then
|
||||
mask = filemask(mask)
|
||||
match = function(f)
|
||||
return f:find(mask)
|
||||
end
|
||||
end
|
||||
return _listfiles(dir,true,match)
|
||||
end
|
||||
|
||||
--- return a list of all subdirectories of the directory.
|
||||
-- @param dir A directory
|
||||
-- @return a list of directories
|
||||
-- @raise dir must be a string
|
||||
function dir.getdirectories(dir)
|
||||
assert_dir(1,dir)
|
||||
return _listfiles(dir,false)
|
||||
end
|
||||
|
||||
local function quote_argument (f)
|
||||
f = path.normcase(f)
|
||||
if f:find '%s' then
|
||||
return '"'..f..'"'
|
||||
else
|
||||
return f
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local alien,ffi,ffi_checked,CopyFile,MoveFile,GetLastError,win32_errors,cmd_tmpfile
|
||||
|
||||
local function execute_command(cmd,parms)
|
||||
if not cmd_tmpfile then cmd_tmpfile = path.tmpname () end
|
||||
local err = path.is_windows and ' > ' or ' 2> '
|
||||
cmd = cmd..' '..parms..err..cmd_tmpfile
|
||||
local ret = utils.execute(cmd)
|
||||
if not ret then
|
||||
return false,(utils.readfile(cmd_tmpfile):gsub('\n(.*)',''))
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function find_ffi_copyfile ()
|
||||
if not ffi_checked then
|
||||
ffi_checked = true
|
||||
local res
|
||||
res,alien = pcall(require,'alien')
|
||||
if not res then
|
||||
alien = nil
|
||||
res, ffi = pcall(require,'ffi')
|
||||
end
|
||||
if not res then
|
||||
ffi = nil
|
||||
return
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
if alien then
|
||||
-- register the Win32 CopyFile and MoveFile functions
|
||||
local kernel = alien.load('kernel32.dll')
|
||||
CopyFile = kernel.CopyFileA
|
||||
CopyFile:types{'string','string','int',ret='int',abi='stdcall'}
|
||||
MoveFile = kernel.MoveFileA
|
||||
MoveFile:types{'string','string',ret='int',abi='stdcall'}
|
||||
GetLastError = kernel.GetLastError
|
||||
GetLastError:types{ret ='int', abi='stdcall'}
|
||||
elseif ffi then
|
||||
ffi.cdef [[
|
||||
int CopyFileA(const char *src, const char *dest, int iovr);
|
||||
int MoveFileA(const char *src, const char *dest);
|
||||
int GetLastError();
|
||||
]]
|
||||
CopyFile = ffi.C.CopyFileA
|
||||
MoveFile = ffi.C.MoveFileA
|
||||
GetLastError = ffi.C.GetLastError
|
||||
end
|
||||
win32_errors = {
|
||||
ERROR_FILE_NOT_FOUND = 2,
|
||||
ERROR_PATH_NOT_FOUND = 3,
|
||||
ERROR_ACCESS_DENIED = 5,
|
||||
ERROR_WRITE_PROTECT = 19,
|
||||
ERROR_BAD_UNIT = 20,
|
||||
ERROR_NOT_READY = 21,
|
||||
ERROR_WRITE_FAULT = 29,
|
||||
ERROR_READ_FAULT = 30,
|
||||
ERROR_SHARING_VIOLATION = 32,
|
||||
ERROR_LOCK_VIOLATION = 33,
|
||||
ERROR_HANDLE_DISK_FULL = 39,
|
||||
ERROR_BAD_NETPATH = 53,
|
||||
ERROR_NETWORK_BUSY = 54,
|
||||
ERROR_DEV_NOT_EXIST = 55,
|
||||
ERROR_FILE_EXISTS = 80,
|
||||
ERROR_OPEN_FAILED = 110,
|
||||
ERROR_INVALID_NAME = 123,
|
||||
ERROR_BAD_PATHNAME = 161,
|
||||
ERROR_ALREADY_EXISTS = 183,
|
||||
}
|
||||
end
|
||||
|
||||
local function two_arguments (f1,f2)
|
||||
return quote_argument(f1)..' '..quote_argument(f2)
|
||||
end
|
||||
|
||||
local function file_op (is_copy,src,dest,flag)
|
||||
if flag == 1 and path.exists(dest) then
|
||||
return false,"cannot overwrite destination"
|
||||
end
|
||||
if is_windows then
|
||||
-- if we haven't tried to load Alien/LuaJIT FFI before, then do so
|
||||
find_ffi_copyfile()
|
||||
-- fallback if there's no Alien, just use DOS commands *shudder*
|
||||
-- 'rename' involves a copy and then deleting the source.
|
||||
if not CopyFile then
|
||||
src = path.normcase(src)
|
||||
dest = path.normcase(dest)
|
||||
local cmd = is_copy and 'copy' or 'rename'
|
||||
local res, err = execute_command('copy',two_arguments(src,dest))
|
||||
if not res then return nil,err end
|
||||
if not is_copy then
|
||||
return execute_command('del',quote_argument(src))
|
||||
end
|
||||
else
|
||||
if path.isdir(dest) then
|
||||
dest = path.join(dest,path.basename(src))
|
||||
end
|
||||
local ret
|
||||
if is_copy then ret = CopyFile(src,dest,flag)
|
||||
else ret = MoveFile(src,dest) end
|
||||
if ret == 0 then
|
||||
local err = GetLastError()
|
||||
for name,value in pairs(win32_errors) do
|
||||
if value == err then return false,name end
|
||||
end
|
||||
return false,"Error #"..err
|
||||
else return true
|
||||
end
|
||||
end
|
||||
else -- for Unix, just use cp for now
|
||||
return execute_command(is_copy and 'cp' or 'mv',
|
||||
two_arguments(src,dest))
|
||||
end
|
||||
end
|
||||
|
||||
--- copy a file.
|
||||
-- @param src source file
|
||||
-- @param dest destination file or directory
|
||||
-- @param flag true if you want to force the copy (default)
|
||||
-- @return true if operation succeeded
|
||||
-- @raise src and dest must be strings
|
||||
function dir.copyfile (src,dest,flag)
|
||||
assert_string(1,src)
|
||||
assert_string(2,dest)
|
||||
flag = flag==nil or flag
|
||||
return file_op(true,src,dest,flag and 0 or 1)
|
||||
end
|
||||
|
||||
--- move a file.
|
||||
-- @param src source file
|
||||
-- @param dest destination file or directory
|
||||
-- @return true if operation succeeded
|
||||
-- @raise src and dest must be strings
|
||||
function dir.movefile (src,dest)
|
||||
assert_string(1,src)
|
||||
assert_string(2,dest)
|
||||
return file_op(false,src,dest,0)
|
||||
end
|
||||
|
||||
local function _dirfiles(dir,attrib)
|
||||
local dirs = {}
|
||||
local files = {}
|
||||
for f in ldir(dir) do
|
||||
if f ~= '.' and f ~= '..' then
|
||||
local p = path.join(dir,f)
|
||||
local mode = attrib(p,'mode')
|
||||
if mode=='directory' then
|
||||
append(dirs,f)
|
||||
else
|
||||
append(files,f)
|
||||
end
|
||||
end
|
||||
end
|
||||
return setmetatable(dirs,List),setmetatable(files,List)
|
||||
end
|
||||
|
||||
|
||||
local function _walker(root,bottom_up,attrib)
|
||||
local dirs,files = _dirfiles(root,attrib)
|
||||
if not bottom_up then yield(root,dirs,files) end
|
||||
for i,d in ipairs(dirs) do
|
||||
_walker(root..path.sep..d,bottom_up,attrib)
|
||||
end
|
||||
if bottom_up then yield(root,dirs,files) end
|
||||
end
|
||||
|
||||
--- return an iterator which walks through a directory tree starting at root.
|
||||
-- The iterator returns (root,dirs,files)
|
||||
-- Note that dirs and files are lists of names (i.e. you must say path.join(root,d)
|
||||
-- to get the actual full path)
|
||||
-- If bottom_up is false (or not present), then the entries at the current level are returned
|
||||
-- before we go deeper. This means that you can modify the returned list of directories before
|
||||
-- continuing.
|
||||
-- This is a clone of os.walk from the Python libraries.
|
||||
-- @param root A starting directory
|
||||
-- @param bottom_up False if we start listing entries immediately.
|
||||
-- @param follow_links follow symbolic links
|
||||
-- @return an iterator returning root,dirs,files
|
||||
-- @raise root must be a string
|
||||
function dir.walk(root,bottom_up,follow_links)
|
||||
assert_string(1,root)
|
||||
if not path.isdir(root) then return raise 'not a directory' end
|
||||
local attrib
|
||||
if path.is_windows or not follow_links then
|
||||
attrib = path.attrib
|
||||
else
|
||||
attrib = path.link_attrib
|
||||
end
|
||||
return wrap(function () _walker(root,bottom_up,attrib) end)
|
||||
end
|
||||
|
||||
--- remove a whole directory tree.
|
||||
-- @param fullpath A directory path
|
||||
-- @return true or nil
|
||||
-- @return error if failed
|
||||
-- @raise fullpath must be a string
|
||||
function dir.rmtree(fullpath)
|
||||
assert_string(1,fullpath)
|
||||
if not path.isdir(fullpath) then return raise 'not a directory' end
|
||||
if path.islink(fullpath) then return false,'will not follow symlink' end
|
||||
for root,dirs,files in dir.walk(fullpath,true) do
|
||||
for i,f in ipairs(files) do
|
||||
remove(path.join(root,f))
|
||||
end
|
||||
rmdir(root)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local dirpat
|
||||
if path.is_windows then
|
||||
dirpat = '(.+)\\[^\\]+$'
|
||||
else
|
||||
dirpat = '(.+)/[^/]+$'
|
||||
end
|
||||
|
||||
local _makepath
|
||||
function _makepath(p)
|
||||
-- windows root drive case
|
||||
if p:find '^%a:[\\]*$' then
|
||||
return true
|
||||
end
|
||||
if not path.isdir(p) then
|
||||
local subp = p:match(dirpat)
|
||||
if not _makepath(subp) then return raise ('cannot create '..subp) end
|
||||
return mkdir(p)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- create a directory path.
|
||||
-- This will create subdirectories as necessary!
|
||||
-- @param p A directory path
|
||||
-- @return a valid created path
|
||||
-- @raise p must be a string
|
||||
function dir.makepath (p)
|
||||
assert_string(1,p)
|
||||
return _makepath(path.normcase(path.abspath(p)))
|
||||
end
|
||||
|
||||
|
||||
--- clone a directory tree. Will always try to create a new directory structure
|
||||
-- if necessary.
|
||||
-- @param path1 the base path of the source tree
|
||||
-- @param path2 the new base path for the destination
|
||||
-- @param file_fun an optional function to apply on all files
|
||||
-- @param verbose an optional boolean to control the verbosity of the output.
|
||||
-- It can also be a logging function that behaves like print()
|
||||
-- @return true, or nil
|
||||
-- @return error message, or list of failed directory creations
|
||||
-- @return list of failed file operations
|
||||
-- @raise path1 and path2 must be strings
|
||||
-- @usage clonetree('.','../backup',copyfile)
|
||||
function dir.clonetree (path1,path2,file_fun,verbose)
|
||||
assert_string(1,path1)
|
||||
assert_string(2,path2)
|
||||
if verbose == true then verbose = print end
|
||||
local abspath,normcase,isdir,join = path.abspath,path.normcase,path.isdir,path.join
|
||||
local faildirs,failfiles = {},{}
|
||||
if not isdir(path1) then return raise 'source is not a valid directory' end
|
||||
path1 = abspath(normcase(path1))
|
||||
path2 = abspath(normcase(path2))
|
||||
if verbose then verbose('normalized:',path1,path2) end
|
||||
-- particularly NB that the new path isn't fully contained in the old path
|
||||
if path1 == path2 then return raise "paths are the same" end
|
||||
local i1,i2 = path2:find(path1,1,true)
|
||||
if i2 == #path1 and path2:sub(i2+1,i2+1) == path.sep then
|
||||
return raise 'destination is a subdirectory of the source'
|
||||
end
|
||||
local cp = path.common_prefix (path1,path2)
|
||||
local idx = #cp
|
||||
if idx == 0 then -- no common path, but watch out for Windows paths!
|
||||
if path1:sub(2,2) == ':' then idx = 3 end
|
||||
end
|
||||
for root,dirs,files in dir.walk(path1) do
|
||||
local opath = path2..root:sub(idx)
|
||||
if verbose then verbose('paths:',opath,root) end
|
||||
if not isdir(opath) then
|
||||
local ret = dir.makepath(opath)
|
||||
if not ret then append(faildirs,opath) end
|
||||
if verbose then verbose('creating:',opath,ret) end
|
||||
end
|
||||
if file_fun then
|
||||
for i,f in ipairs(files) do
|
||||
local p1 = join(root,f)
|
||||
local p2 = join(opath,f)
|
||||
local ret = file_fun(p1,p2)
|
||||
if not ret then append(failfiles,p2) end
|
||||
if verbose then
|
||||
verbose('files:',p1,p2,ret)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true,faildirs,failfiles
|
||||
end
|
||||
|
||||
--- return an iterator over all entries in a directory tree
|
||||
-- @param d a directory
|
||||
-- @return an iterator giving pathname and mode (true for dir, false otherwise)
|
||||
-- @raise d must be a non-empty string
|
||||
function dir.dirtree( d )
|
||||
assert( d and d ~= "", "directory parameter is missing or empty" )
|
||||
local exists, isdir = path.exists, path.isdir
|
||||
local sep = path.sep
|
||||
|
||||
local last = sub ( d, -1 )
|
||||
if last == sep or last == '/' then
|
||||
d = sub( d, 1, -2 )
|
||||
end
|
||||
|
||||
local function yieldtree( dir )
|
||||
for entry in ldir( dir ) do
|
||||
if entry ~= "." and entry ~= ".." then
|
||||
entry = dir .. sep .. entry
|
||||
if exists(entry) then -- Just in case a symlink is broken.
|
||||
local is_dir = isdir(entry)
|
||||
yield( entry, is_dir )
|
||||
if is_dir then
|
||||
yieldtree( entry )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return wrap( function() yieldtree( d ) end )
|
||||
end
|
||||
|
||||
|
||||
--- Recursively returns all the file starting at <i>path</i>. It can optionally take a shell pattern and
|
||||
-- only returns files that match <i>pattern</i>. If a pattern is given it will do a case insensitive search.
|
||||
-- @param start_path {string} A directory. If not given, all files in current directory are returned.
|
||||
-- @param pattern {string} A shell pattern. If not given, all files are returned.
|
||||
-- @return Table containing all the files found recursively starting at <i>path</i> and filtered by <i>pattern</i>.
|
||||
-- @raise start_path must be a string
|
||||
function dir.getallfiles( start_path, pattern )
|
||||
assert( type( start_path ) == "string", "bad argument #1 to 'GetAllFiles' (Expected string but recieved " .. type( start_path ) .. ")" )
|
||||
pattern = pattern or ""
|
||||
|
||||
local files = {}
|
||||
local normcase = path.normcase
|
||||
for filename, mode in dir.dirtree( start_path ) do
|
||||
if not mode then
|
||||
local mask = filemask( pattern )
|
||||
if normcase(filename):find( mask ) then
|
||||
files[#files + 1] = filename
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return files
|
||||
end
|
||||
|
||||
return dir
|
||||
69
Utils/luarocks/share/lua/5.1/pl/file.lua
Normal file
69
Utils/luarocks/share/lua/5.1/pl/file.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
--- File manipulation functions: reading, writing, moving and copying.
|
||||
-- @class module
|
||||
-- @name pl.file
|
||||
local os = os
|
||||
local utils = require 'pl.utils'
|
||||
local dir = require 'pl.dir'
|
||||
local path = require 'pl.path'
|
||||
|
||||
--[[
|
||||
module ('pl.file',utils._module)
|
||||
]]
|
||||
local file = {}
|
||||
|
||||
--- return the contents of a file as a string
|
||||
-- @class function
|
||||
-- @name file.read
|
||||
-- @param filename The file path
|
||||
-- @return file contents
|
||||
file.read = utils.readfile
|
||||
|
||||
--- write a string to a file
|
||||
-- @class function
|
||||
-- @name file.write
|
||||
-- @param filename The file path
|
||||
-- @param str The string
|
||||
file.write = utils.writefile
|
||||
|
||||
--- copy a file.
|
||||
-- @class function
|
||||
-- @name file.copy
|
||||
-- @param src source file
|
||||
-- @param dest destination file
|
||||
-- @param flag true if you want to force the copy (default)
|
||||
-- @return true if operation succeeded
|
||||
file.copy = dir.copyfile
|
||||
|
||||
--- move a file.
|
||||
-- @class function
|
||||
-- @name file.move
|
||||
-- @param src source file
|
||||
-- @param dest destination file
|
||||
-- @return true if operation succeeded, else false and the reason for the error.
|
||||
file.move = dir.movefile
|
||||
|
||||
--- Return the time of last access as the number of seconds since the epoch.
|
||||
-- @class function
|
||||
-- @name file.access_time
|
||||
-- @param path A file path
|
||||
file.access_time = path.getatime
|
||||
|
||||
---Return when the file was created.
|
||||
-- @class function
|
||||
-- @name file.creation_time
|
||||
-- @param path A file path
|
||||
file.creation_time = path.getctime
|
||||
|
||||
--- Return the time of last modification
|
||||
-- @class function
|
||||
-- @name file.modified_time
|
||||
-- @param path A file path
|
||||
file.modified_time = path.getmtime
|
||||
|
||||
--- Delete a file
|
||||
-- @class function
|
||||
-- @name file.delete
|
||||
-- @param path A file path
|
||||
file.delete = os.remove
|
||||
|
||||
return file
|
||||
379
Utils/luarocks/share/lua/5.1/pl/func.lua
Normal file
379
Utils/luarocks/share/lua/5.1/pl/func.lua
Normal file
@@ -0,0 +1,379 @@
|
||||
--- Functional helpers like composition, binding and placeholder expressions.
|
||||
-- Placeholder expressions are useful for short anonymous functions, and were
|
||||
-- inspired by the Boost Lambda library.
|
||||
-- <pre class=example>
|
||||
-- utils.import 'pl.func'
|
||||
-- ls = List{10,20,30}
|
||||
-- = ls:map(_1+1)
|
||||
-- {11,21,31}
|
||||
-- </pre>
|
||||
-- They can also be used to <em>bind</em> particular arguments of a function.
|
||||
-- <pre class = example>
|
||||
-- p = bind(print,'start>',_0)
|
||||
-- p(10,20,30)
|
||||
-- start> 10 20 30
|
||||
-- </pre>
|
||||
-- See <a href="../../index.html#func">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.func
|
||||
local type,select,setmetatable,getmetatable,rawset = type,select,setmetatable,getmetatable,rawset
|
||||
local concat,append = table.concat,table.insert
|
||||
local max = math.max
|
||||
local print,tostring = print,tostring
|
||||
local pairs,ipairs,loadstring,rawget,unpack = pairs,ipairs,loadstring,rawget,unpack
|
||||
local _G = _G
|
||||
local utils = require 'pl.utils'
|
||||
local tablex = require 'pl.tablex'
|
||||
local map = tablex.map
|
||||
local _DEBUG = rawget(_G,'_DEBUG')
|
||||
local assert_arg = utils.assert_arg
|
||||
|
||||
--[[
|
||||
module ('pl.func',utils._module)
|
||||
]]
|
||||
|
||||
local func = {}
|
||||
|
||||
-- metatable for Placeholder Expressions (PE)
|
||||
local _PEMT = {}
|
||||
|
||||
local function P (t)
|
||||
setmetatable(t,_PEMT)
|
||||
return t
|
||||
end
|
||||
|
||||
func.PE = P
|
||||
|
||||
local function isPE (obj)
|
||||
return getmetatable(obj) == _PEMT
|
||||
end
|
||||
|
||||
func.isPE = isPE
|
||||
|
||||
-- construct a placeholder variable (e.g _1 and _2)
|
||||
local function PH (idx)
|
||||
return P {op='X',repr='_'..idx, index=idx}
|
||||
end
|
||||
|
||||
-- construct a constant placeholder variable (e.g _C1 and _C2)
|
||||
local function CPH (idx)
|
||||
return P {op='X',repr='_C'..idx, index=idx}
|
||||
end
|
||||
|
||||
func._1,func._2,func._3,func._4,func._5 = PH(1),PH(2),PH(3),PH(4),PH(5)
|
||||
func._0 = P{op='X',repr='...',index=0}
|
||||
|
||||
function func.Var (name)
|
||||
local ls = utils.split(name,'[%s,]+')
|
||||
local res = {}
|
||||
for _,n in ipairs(ls) do
|
||||
append(res,P{op='X',repr=n,index=0})
|
||||
end
|
||||
return unpack(res)
|
||||
end
|
||||
|
||||
function func._ (value)
|
||||
return P{op='X',repr=value,index='wrap'}
|
||||
end
|
||||
|
||||
local repr
|
||||
|
||||
func.Nil = func.Var 'nil'
|
||||
|
||||
function _PEMT.__index(obj,key)
|
||||
return P{op='[]',obj,key}
|
||||
end
|
||||
|
||||
function _PEMT.__call(fun,...)
|
||||
return P{op='()',fun,...}
|
||||
end
|
||||
|
||||
function _PEMT.__tostring (e)
|
||||
return repr(e)
|
||||
end
|
||||
|
||||
function _PEMT.__unm(arg)
|
||||
return P{op='-',arg}
|
||||
end
|
||||
|
||||
function func.Not (arg)
|
||||
return P{op='not',arg}
|
||||
end
|
||||
|
||||
function func.Len (arg)
|
||||
return P{op='#',arg}
|
||||
end
|
||||
|
||||
|
||||
local function binreg(context,t)
|
||||
for name,op in pairs(t) do
|
||||
rawset(context,name,function(x,y)
|
||||
return P{op=op,x,y}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function import_name (name,fun,context)
|
||||
rawset(context,name,function(...)
|
||||
return P{op='()',fun,...}
|
||||
end)
|
||||
end
|
||||
|
||||
local imported_functions = {}
|
||||
|
||||
local function is_global_table (n)
|
||||
return type(_G[n]) == 'table'
|
||||
end
|
||||
|
||||
--- wrap a table of functions. This makes them available for use in
|
||||
-- placeholder expressions.
|
||||
-- @param tname a table name
|
||||
-- @param context context to put results, defaults to environment of caller
|
||||
function func.import(tname,context)
|
||||
assert_arg(1,tname,'string',is_global_table,'arg# 1: not a name of a global table')
|
||||
local t = _G[tname]
|
||||
context = context or _G
|
||||
for name,fun in pairs(t) do
|
||||
import_name(name,fun,context)
|
||||
imported_functions[fun] = name
|
||||
end
|
||||
end
|
||||
|
||||
--- register a function for use in placeholder expressions.
|
||||
-- @param fun a function
|
||||
-- @param name an optional name
|
||||
-- @return a placeholder functiond
|
||||
function func.register (fun,name)
|
||||
assert_arg(1,fun,'function')
|
||||
if name then
|
||||
assert_arg(2,name,'string')
|
||||
imported_functions[fun] = name
|
||||
end
|
||||
return function(...)
|
||||
return P{op='()',fun,...}
|
||||
end
|
||||
end
|
||||
|
||||
function func.lookup_imported_name (fun)
|
||||
return imported_functions[fun]
|
||||
end
|
||||
|
||||
local function _arg(...) return ... end
|
||||
|
||||
function func.Args (...)
|
||||
return P{op='()',_arg,...}
|
||||
end
|
||||
|
||||
-- binary and unary operators, with their precedences (see 2.5.6)
|
||||
local operators = {
|
||||
['or'] = 0,
|
||||
['and'] = 1,
|
||||
['=='] = 2, ['~='] = 2, ['<'] = 2, ['>'] = 2, ['<='] = 2, ['>='] = 2,
|
||||
['..'] = 3,
|
||||
['+'] = 4, ['-'] = 4,
|
||||
['*'] = 5, ['/'] = 5, ['%'] = 5,
|
||||
['not'] = 6, ['#'] = 6, ['-'] = 6,
|
||||
['^'] = 7
|
||||
}
|
||||
|
||||
-- comparisons (as prefix functions)
|
||||
binreg (func,{And='and',Or='or',Eq='==',Lt='<',Gt='>',Le='<=',Ge='>='})
|
||||
|
||||
-- standard binary operators (as metamethods)
|
||||
binreg (_PEMT,{__add='+',__sub='-',__mul='*',__div='/',__mod='%',__pow='^',__concat='..'})
|
||||
|
||||
binreg (_PEMT,{__eq='=='})
|
||||
|
||||
--- all elements of a table except the first.
|
||||
-- @param ls a list-like table.
|
||||
function func.tail (ls)
|
||||
assert_arg(1,ls,'table')
|
||||
local res = {}
|
||||
for i = 2,#ls do
|
||||
append(res,ls[i])
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- create a string representation of a placeholder expression.
|
||||
-- @param e a placeholder expression
|
||||
-- @param lastpred not used
|
||||
function repr (e,lastpred)
|
||||
local tail = func.tail
|
||||
if isPE(e) then
|
||||
local pred = operators[e.op]
|
||||
local ls = map(repr,e,pred)
|
||||
if pred then --unary or binary operator
|
||||
if #ls ~= 1 then
|
||||
local s = concat(ls,' '..e.op..' ')
|
||||
if lastpred and lastpred > pred then
|
||||
s = '('..s..')'
|
||||
end
|
||||
return s
|
||||
else
|
||||
return e.op..' '..ls[1]
|
||||
end
|
||||
else -- either postfix, or a placeholder
|
||||
if e.op == '[]' then
|
||||
return ls[1]..'['..ls[2]..']'
|
||||
elseif e.op == '()' then
|
||||
local fn
|
||||
if ls[1] ~= nil then -- was _args, undeclared!
|
||||
fn = ls[1]
|
||||
else
|
||||
fn = ''
|
||||
end
|
||||
return fn..'('..concat(tail(ls),',')..')'
|
||||
else
|
||||
return e.repr
|
||||
end
|
||||
end
|
||||
elseif type(e) == 'string' then
|
||||
return '"'..e..'"'
|
||||
elseif type(e) == 'function' then
|
||||
local name = func.lookup_imported_name(e)
|
||||
if name then return name else return tostring(e) end
|
||||
else
|
||||
return tostring(e) --should not really get here!
|
||||
end
|
||||
end
|
||||
func.repr = repr
|
||||
|
||||
-- collect all the non-PE values in this PE into vlist, and replace each occurence
|
||||
-- with a constant PH (_C1, etc). Return the maximum placeholder index found.
|
||||
local collect_values
|
||||
function collect_values (e,vlist)
|
||||
if isPE(e) then
|
||||
if e.op ~= 'X' then
|
||||
local m = 0
|
||||
for i,subx in ipairs(e) do
|
||||
local pe = isPE(subx)
|
||||
if pe then
|
||||
if subx.op == 'X' and subx.index == 'wrap' then
|
||||
subx = subx.repr
|
||||
pe = false
|
||||
else
|
||||
m = max(m,collect_values(subx,vlist))
|
||||
end
|
||||
end
|
||||
if not pe then
|
||||
append(vlist,subx)
|
||||
e[i] = CPH(#vlist)
|
||||
end
|
||||
end
|
||||
return m
|
||||
else -- was a placeholder, it has an index...
|
||||
return e.index
|
||||
end
|
||||
else -- plain value has no placeholder dependence
|
||||
return 0
|
||||
end
|
||||
end
|
||||
func.collect_values = collect_values
|
||||
|
||||
--- instantiate a PE into an actual function. First we find the largest placeholder used,
|
||||
-- e.g. _2; from this a list of the formal parameters can be build. Then we collect and replace
|
||||
-- any non-PE values from the PE, and build up a constant binding list.
|
||||
-- Finally, the expression can be compiled, and e.__PE_function is set.
|
||||
-- @param e a placeholder expression
|
||||
-- @return a function
|
||||
function func.instantiate (e)
|
||||
local consts,values,parms = {},{},{}
|
||||
local rep, err, fun
|
||||
local n = func.collect_values(e,values)
|
||||
for i = 1,#values do
|
||||
append(consts,'_C'..i)
|
||||
if _DEBUG then print(i,values[i]) end
|
||||
end
|
||||
for i =1,n do
|
||||
append(parms,'_'..i)
|
||||
end
|
||||
consts = concat(consts,',')
|
||||
parms = concat(parms,',')
|
||||
rep = repr(e)
|
||||
local fstr = ('return function(%s) return function(%s) return %s end end'):format(consts,parms,rep)
|
||||
if _DEBUG then print(fstr) end
|
||||
fun,err = loadstring(fstr,'fun')
|
||||
if not fun then return nil,err end
|
||||
fun = fun() -- get wrapper
|
||||
fun = fun(unpack(values)) -- call wrapper (values could be empty)
|
||||
e.__PE_function = fun
|
||||
return fun
|
||||
end
|
||||
|
||||
--- instantiate a PE unless it has already been done.
|
||||
-- @param e a placeholder expression
|
||||
-- @return the function
|
||||
function func.I(e)
|
||||
if rawget(e,'__PE_function') then
|
||||
return e.__PE_function
|
||||
else return func.instantiate(e)
|
||||
end
|
||||
end
|
||||
|
||||
utils.add_function_factory(_PEMT,func.I)
|
||||
|
||||
--- bind the first parameter of the function to a value.
|
||||
-- @class function
|
||||
-- @name func.curry
|
||||
-- @param fn a function of one or more arguments
|
||||
-- @param p a value
|
||||
-- @return a function of one less argument
|
||||
-- @usage (curry(math.max,10))(20) == math.max(10,20)
|
||||
func.curry = utils.bind1
|
||||
|
||||
--- create a function which chains two functions.
|
||||
-- @param f a function of at least one argument
|
||||
-- @param g a function of at least one argument
|
||||
-- @return a function
|
||||
-- @usage printf = compose(io.write,string.format)
|
||||
function func.compose (f,g)
|
||||
return function(...) return f(g(...)) end
|
||||
end
|
||||
|
||||
--- bind the arguments of a function to given values.
|
||||
-- bind(fn,v,_2) is equivalent to curry(fn,v).
|
||||
-- @param fn a function of at least one argument
|
||||
-- @param ... values or placeholder variables
|
||||
-- @return a function
|
||||
-- @usage (bind(f,_1,a))(b) == f(a,b)
|
||||
-- @usage (bind(f,_2,_1))(a,b) == f(b,a)
|
||||
function func.bind(fn,...)
|
||||
local args = table.pack(...)
|
||||
local holders,parms,bvalues,values = {},{},{'fn'},{}
|
||||
local nv,maxplace,varargs = 1,0,false
|
||||
for i = 1,args.n do
|
||||
local a = args[i]
|
||||
if isPE(a) and a.op == 'X' then
|
||||
append(holders,a.repr)
|
||||
maxplace = max(maxplace,a.index)
|
||||
if a.index == 0 then varargs = true end
|
||||
else
|
||||
local v = '_v'..nv
|
||||
append(bvalues,v)
|
||||
append(holders,v)
|
||||
append(values,a)
|
||||
nv = nv + 1
|
||||
end
|
||||
end
|
||||
for np = 1,maxplace do
|
||||
append(parms,'_'..np)
|
||||
end
|
||||
if varargs then append(parms,'...') end
|
||||
bvalues = concat(bvalues,',')
|
||||
parms = concat(parms,',')
|
||||
holders = concat(holders,',')
|
||||
local fstr = ([[
|
||||
return function (%s)
|
||||
return function(%s) return fn(%s) end
|
||||
end
|
||||
]]):format(bvalues,parms,holders)
|
||||
if _DEBUG then print(fstr) end
|
||||
local res,err = loadstring(fstr)
|
||||
res = res()
|
||||
return res(fn,unpack(values))
|
||||
end
|
||||
|
||||
return func
|
||||
|
||||
|
||||
47
Utils/luarocks/share/lua/5.1/pl/init.lua
Normal file
47
Utils/luarocks/share/lua/5.1/pl/init.lua
Normal file
@@ -0,0 +1,47 @@
|
||||
--------------
|
||||
-- entry point for loading all PL libraries only on demand.
|
||||
-- Requiring 'pl' means that whenever a module is accesssed (e.g. utils.split)
|
||||
-- then that module is dynamically loaded. The submodules are all brought into
|
||||
-- the global space.
|
||||
-- @class module
|
||||
-- @name pl
|
||||
|
||||
local modules = {
|
||||
utils = true,path=true,dir=true,tablex=true,stringio=true,sip=true,
|
||||
input=true,seq=true,lexer=true,stringx=true,
|
||||
config=true,pretty=true,data=true,func=true,text=true,
|
||||
operator=true,lapp=true,array2d=true,
|
||||
comprehension=true,xml=true,
|
||||
test = true, app = true, file = true, class = true, List = true,
|
||||
Map = true, Set = true, OrderedMap = true, MultiMap = true,
|
||||
Date = true,
|
||||
-- classes --
|
||||
}
|
||||
_G.utils = require 'pl.utils'
|
||||
|
||||
for name,klass in pairs(_G.utils.stdmt) do
|
||||
klass.__index = function(t,key)
|
||||
return require ('pl.'..name)[key]
|
||||
end;
|
||||
end
|
||||
|
||||
local _hook
|
||||
setmetatable(_G,{
|
||||
hook = function(handler)
|
||||
_hook = handler
|
||||
end,
|
||||
__index = function(t,name)
|
||||
local found = modules[name]
|
||||
-- either true, or the name of the module containing this class.
|
||||
-- either way, we load the required module and make it globally available.
|
||||
if found then
|
||||
-- e..g pretty.dump causes pl.pretty to become available as 'pretty'
|
||||
rawset(_G,name,require('pl.'..name))
|
||||
return _G[name]
|
||||
elseif _hook then
|
||||
return _hook(t,name)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
if _G.PENLIGHT_STRICT then require 'pl.strict' end
|
||||
172
Utils/luarocks/share/lua/5.1/pl/input.lua
Normal file
172
Utils/luarocks/share/lua/5.1/pl/input.lua
Normal file
@@ -0,0 +1,172 @@
|
||||
--- Iterators for extracting words or numbers from an input source.
|
||||
-- <pre class=example>
|
||||
-- require 'pl'
|
||||
-- local total,n = <a href="pl.seq.html#sum">seq.sum</a>(input.numbers())
|
||||
-- print('average',total/n)
|
||||
-- </pre>
|
||||
-- <p> See <a href="../../index.html#lexer">here</a>
|
||||
-- @class module
|
||||
-- @name pl.input
|
||||
local strfind = string.find
|
||||
local strsub = string.sub
|
||||
local strmatch = string.match
|
||||
local pairs,type,unpack,tonumber = pairs,type,unpack,tonumber
|
||||
local utils = require 'pl.utils'
|
||||
local patterns = utils.patterns
|
||||
local io = io
|
||||
local assert_arg = utils.assert_arg
|
||||
|
||||
--[[
|
||||
module ('pl.input',utils._module)
|
||||
]]
|
||||
|
||||
local input = {}
|
||||
|
||||
--- create an iterator over all tokens.
|
||||
-- based on allwords from PiL, 7.1
|
||||
-- @param getter any function that returns a line of text
|
||||
-- @param pattern
|
||||
-- @param fn Optionally can pass a function to process each token as it/s found.
|
||||
-- @return an iterator
|
||||
function input.alltokens (getter,pattern,fn)
|
||||
local line = getter() -- current line
|
||||
local pos = 1 -- current position in the line
|
||||
return function () -- iterator function
|
||||
while line do -- repeat while there are lines
|
||||
local s, e = strfind(line, pattern, pos)
|
||||
if s then -- found a word?
|
||||
pos = e + 1 -- next position is after this token
|
||||
local res = strsub(line, s, e) -- return the token
|
||||
if fn then res = fn(res) end
|
||||
return res
|
||||
else
|
||||
line = getter() -- token not found; try next line
|
||||
pos = 1 -- restart from first position
|
||||
end
|
||||
end
|
||||
return nil -- no more lines: end of traversal
|
||||
end
|
||||
end
|
||||
local alltokens = input.alltokens
|
||||
|
||||
-- question: shd this _split_ a string containing line feeds?
|
||||
|
||||
--- create a function which grabs the next value from a source. If the source is a string, then the getter
|
||||
-- will return the string and thereafter return nil. If not specified then the source is assumed to be stdin.
|
||||
-- @param f a string or a file-like object (i.e. has a read() method which returns the next line)
|
||||
-- @return a getter function
|
||||
function input.create_getter(f)
|
||||
if f then
|
||||
if type(f) == 'string' then
|
||||
local ls = utils.split(f,'\n')
|
||||
local i,n = 0,#ls
|
||||
return function()
|
||||
i = i + 1
|
||||
if i > n then return nil end
|
||||
return ls[i]
|
||||
end
|
||||
else
|
||||
-- anything that supports the read() method!
|
||||
if not f.read then error('not a file-like object') end
|
||||
return function() return f:read() end
|
||||
end
|
||||
else
|
||||
return io.read -- i.e. just read from stdin
|
||||
end
|
||||
end
|
||||
|
||||
--- generate a sequence of numbers from a source.
|
||||
-- @param f A source
|
||||
-- @return An iterator
|
||||
function input.numbers(f)
|
||||
return alltokens(input.create_getter(f),
|
||||
'('..patterns.FLOAT..')',tonumber)
|
||||
end
|
||||
|
||||
--- generate a sequence of words from a source.
|
||||
-- @param f A source
|
||||
-- @return An iterator
|
||||
function input.words(f)
|
||||
return alltokens(input.create_getter(f),"%w+")
|
||||
end
|
||||
|
||||
local function apply_tonumber (no_fail,...)
|
||||
local args = {...}
|
||||
for i = 1,#args do
|
||||
local n = tonumber(args[i])
|
||||
if n == nil then
|
||||
if not no_fail then return nil,args[i] end
|
||||
else
|
||||
args[i] = n
|
||||
end
|
||||
end
|
||||
return args
|
||||
end
|
||||
|
||||
--- parse an input source into fields.
|
||||
-- By default, will fail if it cannot convert a field to a number.
|
||||
-- @param ids a list of field indices, or a maximum field index
|
||||
-- @param delim delimiter to parse fields (default space)
|
||||
-- @param f a source @see create_getter
|
||||
-- @param opts option table, {no_fail=true}
|
||||
-- @return an iterator with the field values
|
||||
-- @usage for x,y in fields {2,3} do print(x,y) end -- 2nd and 3rd fields from stdin
|
||||
function input.fields (ids,delim,f,opts)
|
||||
local sep
|
||||
local s
|
||||
local getter = input.create_getter(f)
|
||||
local no_fail = opts and opts.no_fail
|
||||
local no_convert = opts and opts.no_convert
|
||||
if not delim or delim == ' ' then
|
||||
delim = '%s'
|
||||
sep = '%s+'
|
||||
s = '%s*'
|
||||
else
|
||||
sep = delim
|
||||
s = ''
|
||||
end
|
||||
local max_id = 0
|
||||
if type(ids) == 'table' then
|
||||
for i,id in pairs(ids) do
|
||||
if id > max_id then max_id = id end
|
||||
end
|
||||
else
|
||||
max_id = ids
|
||||
ids = {}
|
||||
for i = 1,max_id do ids[#ids+1] = i end
|
||||
end
|
||||
local pat = '[^'..delim..']*'
|
||||
local k = 1
|
||||
for i = 1,max_id do
|
||||
if ids[k] == i then
|
||||
k = k + 1
|
||||
s = s..'('..pat..')'
|
||||
else
|
||||
s = s..pat
|
||||
end
|
||||
if i < max_id then
|
||||
s = s..sep
|
||||
end
|
||||
end
|
||||
local linecount = 1
|
||||
return function()
|
||||
local line,results,err
|
||||
repeat
|
||||
line = getter()
|
||||
linecount = linecount + 1
|
||||
if not line then return nil end
|
||||
if no_convert then
|
||||
results = {strmatch(line,s)}
|
||||
else
|
||||
results,err = apply_tonumber(no_fail,strmatch(line,s))
|
||||
if not results then
|
||||
utils.quit("line "..(linecount-1)..": cannot convert '"..err.."' to number")
|
||||
end
|
||||
end
|
||||
until #results > 0
|
||||
return unpack(results)
|
||||
end
|
||||
end
|
||||
|
||||
return input
|
||||
|
||||
350
Utils/luarocks/share/lua/5.1/pl/lapp.lua
Normal file
350
Utils/luarocks/share/lua/5.1/pl/lapp.lua
Normal file
@@ -0,0 +1,350 @@
|
||||
--- Simple command-line parsing using human-readable specification.
|
||||
-- Supports GNU-style parameters.
|
||||
-- <pre class=example>
|
||||
-- lapp = require 'pl.lapp'
|
||||
-- local args = lapp [[
|
||||
-- Does some calculations
|
||||
-- -o,--offset (default 0.0) Offset to add to scaled number
|
||||
-- -s,--scale (number) Scaling factor
|
||||
-- <number> (number ) Number to be scaled
|
||||
-- ]]
|
||||
--
|
||||
-- print(args.offset + args.scale * args.number)
|
||||
-- </pre>
|
||||
-- Lines begining with '-' are flags; there may be a short and a long name;
|
||||
-- lines begining wih '<var>' are arguments. Anything in parens after
|
||||
-- the flag/argument is either a default, a type name or a range constraint.
|
||||
-- <p>See <a href="../../index.html#lapp">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.lapp
|
||||
|
||||
local status,sip = pcall(require,'pl.sip')
|
||||
if not status then
|
||||
sip = require 'sip'
|
||||
end
|
||||
local match = sip.match_at_start
|
||||
local append,tinsert = table.insert,table.insert
|
||||
|
||||
--[[
|
||||
module('pl.lapp')
|
||||
]]
|
||||
|
||||
local function lines(s) return s:gmatch('([^\n]*)\n') end
|
||||
local function lstrip(str) return str:gsub('^%s+','') end
|
||||
local function strip(str) return lstrip(str):gsub('%s+$','') end
|
||||
local function at(s,k) return s:sub(k,k) end
|
||||
local function isdigit(s) return s:find('^%d+$') == 1 end
|
||||
|
||||
local lapp = {}
|
||||
|
||||
local open_files,parms,aliases,parmlist,usage,windows,script
|
||||
|
||||
lapp.callback = false -- keep Strict happy
|
||||
|
||||
local filetypes = {
|
||||
stdin = {io.stdin,'file-in'}, stdout = {io.stdout,'file-out'},
|
||||
stderr = {io.stderr,'file-out'}
|
||||
}
|
||||
|
||||
--- controls whether to dump usage on error.
|
||||
-- Defaults to true
|
||||
lapp.show_usage_error = true
|
||||
|
||||
--- quit this script immediately.
|
||||
-- @param msg optional message
|
||||
-- @param no_usage suppress 'usage' display
|
||||
function lapp.quit(msg,no_usage)
|
||||
if msg then
|
||||
io.stderr:write(msg..'\n\n')
|
||||
end
|
||||
if not no_usage then
|
||||
io.stderr:write(usage)
|
||||
end
|
||||
os.exit(1);
|
||||
end
|
||||
|
||||
--- print an error to stderr and quit.
|
||||
-- @param msg a message
|
||||
-- @param no_usage suppress 'usage' display
|
||||
function lapp.error(msg,no_usage)
|
||||
if not lapp.show_usage_error then
|
||||
no_usage = true
|
||||
end
|
||||
lapp.quit(script..':'..msg,no_usage)
|
||||
end
|
||||
|
||||
--- open a file.
|
||||
-- This will quit on error, and keep a list of file objects for later cleanup.
|
||||
-- @param file filename
|
||||
-- @param opt same as second parameter of <code>io.open</code>
|
||||
function lapp.open (file,opt)
|
||||
local val,err = io.open(file,opt)
|
||||
if not val then lapp.error(err,true) end
|
||||
append(open_files,val)
|
||||
return val
|
||||
end
|
||||
|
||||
--- quit if the condition is false.
|
||||
-- @param condn a condition
|
||||
-- @param msg an optional message
|
||||
function lapp.assert(condn,msg)
|
||||
if not condn then
|
||||
lapp.error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function range_check(x,min,max,parm)
|
||||
lapp.assert(min <= x and max >= x,parm..' out of range')
|
||||
end
|
||||
|
||||
local function xtonumber(s)
|
||||
local val = tonumber(s)
|
||||
if not val then lapp.error("unable to convert to number: "..s) end
|
||||
return val
|
||||
end
|
||||
|
||||
local function is_filetype(type)
|
||||
return type == 'file-in' or type == 'file-out'
|
||||
end
|
||||
|
||||
local types
|
||||
|
||||
local function convert_parameter(ps,val)
|
||||
if ps.converter then
|
||||
val = ps.converter(val)
|
||||
end
|
||||
if ps.type == 'number' then
|
||||
val = xtonumber(val)
|
||||
elseif is_filetype(ps.type) then
|
||||
val = lapp.open(val,(ps.type == 'file-in' and 'r') or 'w' )
|
||||
elseif ps.type == 'boolean' then
|
||||
val = true
|
||||
end
|
||||
if ps.constraint then
|
||||
ps.constraint(val)
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
--- add a new type to Lapp. These appear in parens after the value like
|
||||
-- a range constraint, e.g. '<ival> (integer) Process PID'
|
||||
-- @param name name of type
|
||||
-- @param converter either a function to convert values, or a Lua type name.
|
||||
-- @param constraint optional function to verify values, should use lapp.error
|
||||
-- if failed.
|
||||
function lapp.add_type (name,converter,constraint)
|
||||
types[name] = {converter=converter,constraint=constraint}
|
||||
end
|
||||
|
||||
local function force_short(short)
|
||||
lapp.assert(#short==1,short..": short parameters should be one character")
|
||||
end
|
||||
|
||||
local function process_default (sval)
|
||||
local val = tonumber(sval)
|
||||
if val then -- we have a number!
|
||||
return val,'number'
|
||||
elseif filetypes[sval] then
|
||||
local ft = filetypes[sval]
|
||||
return ft[1],ft[2]
|
||||
else
|
||||
if sval:match '^["\']' then sval = sval:sub(2,-2) end
|
||||
return sval,'string'
|
||||
end
|
||||
end
|
||||
|
||||
--- process a Lapp options string.
|
||||
-- Usually called as lapp().
|
||||
-- @param str the options text
|
||||
-- @return a table with parameter-value pairs
|
||||
function lapp.process_options_string(str)
|
||||
local results = {}
|
||||
local opts = {at_start=true}
|
||||
local varargs
|
||||
open_files = {}
|
||||
parms = {}
|
||||
aliases = {}
|
||||
parmlist = {}
|
||||
types = {}
|
||||
|
||||
local function check_varargs(s)
|
||||
local res,cnt = s:gsub('^%.%.%.%s*','')
|
||||
return res, (cnt > 0)
|
||||
end
|
||||
|
||||
local function set_result(ps,parm,val)
|
||||
if not ps.varargs then
|
||||
results[parm] = val
|
||||
else
|
||||
if not results[parm] then
|
||||
results[parm] = { val }
|
||||
else
|
||||
append(results[parm],val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
usage = str
|
||||
|
||||
for line in lines(str) do
|
||||
local res = {}
|
||||
local optspec,optparm,i1,i2,defval,vtype,constraint,rest
|
||||
line = lstrip(line)
|
||||
local function check(str)
|
||||
return match(str,line,res)
|
||||
end
|
||||
|
||||
-- flags: either '-<short>', '-<short>,--<long>' or '--<long>'
|
||||
if check '-$v{short}, --$v{long} $' or check '-$v{short} $' or check '--$v{long} $' then
|
||||
if res.long then
|
||||
optparm = res.long
|
||||
if res.short then aliases[res.short] = optparm end
|
||||
else
|
||||
optparm = res.short
|
||||
end
|
||||
if res.short then force_short(res.short) end
|
||||
res.rest, varargs = check_varargs(res.rest)
|
||||
elseif check '$<{name} $' then -- is it <parameter_name>?
|
||||
-- so <input file...> becomes input_file ...
|
||||
optparm,rest = res.name:match '([^%.]+)(.*)'
|
||||
optparm = optparm:gsub('%A','_')
|
||||
varargs = rest == '...'
|
||||
append(parmlist,optparm)
|
||||
end
|
||||
if res.rest then -- this is not a pure doc line
|
||||
line = res.rest
|
||||
res = {}
|
||||
-- do we have (default <val>) or (<type>)?
|
||||
if match('$({def} $',line,res) or match('$({def}',line,res) then
|
||||
local typespec = strip(res.def)
|
||||
if match('default $',typespec,res) then
|
||||
defval,vtype = process_default(res[1])
|
||||
elseif match('$f{min}..$f{max}',typespec,res) then
|
||||
local min,max = res.min,res.max
|
||||
vtype = 'number'
|
||||
constraint = function(x)
|
||||
range_check(x,min,max,optparm)
|
||||
end
|
||||
else -- () just contains type of required parameter
|
||||
vtype = typespec
|
||||
end
|
||||
else -- must be a plain flag, no extra parameter required
|
||||
defval = false
|
||||
vtype = 'boolean'
|
||||
end
|
||||
local ps = {
|
||||
type = vtype,
|
||||
defval = defval,
|
||||
required = defval == nil,
|
||||
comment = res.rest or optparm,
|
||||
constraint = constraint,
|
||||
varargs = varargs
|
||||
}
|
||||
varargs = nil
|
||||
if types[vtype] then
|
||||
local converter = types[vtype].converter
|
||||
if type(converter) == 'string' then
|
||||
ps.type = converter
|
||||
else
|
||||
ps.converter = converter
|
||||
end
|
||||
ps.constraint = types[vtype].constraint
|
||||
end
|
||||
parms[optparm] = ps
|
||||
end
|
||||
end
|
||||
-- cool, we have our parms, let's parse the command line args
|
||||
local iparm = 1
|
||||
local iextra = 1
|
||||
local i = 1
|
||||
local parm,ps,val
|
||||
|
||||
while i <= #arg do
|
||||
local theArg = arg[i]
|
||||
local res = {}
|
||||
-- look for a flag, -<short flags> or --<long flag>
|
||||
if match('--$v{long}',theArg,res) or match('-$v{short}',theArg,res) then
|
||||
if res.long then -- long option
|
||||
parm = res.long
|
||||
elseif #res.short == 1 then
|
||||
parm = res.short
|
||||
else
|
||||
local parmstr = res.short
|
||||
parm = at(parmstr,1)
|
||||
if isdigit(at(parmstr,2)) then
|
||||
-- a short option followed by a digit is an exception (for AW;))
|
||||
-- push ahead into the arg array
|
||||
tinsert(arg,i+1,parmstr:sub(2))
|
||||
else
|
||||
-- push multiple flags into the arg array!
|
||||
for k = 2,#parmstr do
|
||||
tinsert(arg,i+k-1,'-'..at(parmstr,k))
|
||||
end
|
||||
end
|
||||
end
|
||||
if parm == 'h' or parm == 'help' then
|
||||
lapp.quit()
|
||||
end
|
||||
if aliases[parm] then parm = aliases[parm] end
|
||||
else -- a parameter
|
||||
parm = parmlist[iparm]
|
||||
if not parm then
|
||||
-- extra unnamed parameters are indexed starting at 1
|
||||
parm = iextra
|
||||
ps = { type = 'string' }
|
||||
parms[parm] = ps
|
||||
iextra = iextra + 1
|
||||
else
|
||||
ps = parms[parm]
|
||||
end
|
||||
if not ps.varargs then
|
||||
iparm = iparm + 1
|
||||
end
|
||||
val = theArg
|
||||
end
|
||||
ps = parms[parm]
|
||||
if not ps then lapp.error("unrecognized parameter: "..parm) end
|
||||
if ps.type ~= 'boolean' then -- we need a value! This should follow
|
||||
if not val then
|
||||
i = i + 1
|
||||
val = arg[i]
|
||||
end
|
||||
lapp.assert(val,parm.." was expecting a value")
|
||||
end
|
||||
ps.used = true
|
||||
val = convert_parameter(ps,val)
|
||||
set_result(ps,parm,val)
|
||||
if is_filetype(ps.type) then
|
||||
set_result(ps,parm..'_name',theArg)
|
||||
end
|
||||
if lapp.callback then
|
||||
lapp.callback(parm,theArg,res)
|
||||
end
|
||||
i = i + 1
|
||||
val = nil
|
||||
end
|
||||
-- check unused parms, set defaults and check if any required parameters were missed
|
||||
for parm,ps in pairs(parms) do
|
||||
if not ps.used then
|
||||
if ps.required then lapp.error("missing required parameter: "..parm) end
|
||||
set_result(ps,parm,ps.defval)
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
if arg then
|
||||
script = arg[0]:gsub('.+[\\/]',''):gsub('%.%a+$','')
|
||||
else
|
||||
script = "inter"
|
||||
end
|
||||
|
||||
|
||||
setmetatable(lapp, {
|
||||
__call = function(tbl,str) return lapp.process_options_string(str) end,
|
||||
})
|
||||
|
||||
|
||||
return lapp
|
||||
|
||||
|
||||
461
Utils/luarocks/share/lua/5.1/pl/lexer.lua
Normal file
461
Utils/luarocks/share/lua/5.1/pl/lexer.lua
Normal file
@@ -0,0 +1,461 @@
|
||||
--- Lexical scanner for creating a sequence of tokens from text. <br>
|
||||
-- <p><code>lexer.scan(s)</code> returns an iterator over all tokens found in the
|
||||
-- string <code>s</code>. This iterator returns two values, a token type string
|
||||
-- (such as 'string' for quoted string, 'iden' for identifier) and the value of the
|
||||
-- token.
|
||||
-- <p>
|
||||
-- Versions specialized for Lua and C are available; these also handle block comments
|
||||
-- and classify keywords as 'keyword' tokens. For example:
|
||||
-- <pre class=example>
|
||||
-- > s = 'for i=1,n do'
|
||||
-- > for t,v in lexer.lua(s) do print(t,v) end
|
||||
-- keyword for
|
||||
-- iden i
|
||||
-- = =
|
||||
-- number 1
|
||||
-- , ,
|
||||
-- iden n
|
||||
-- keyword do
|
||||
-- </pre>
|
||||
-- See the Guide for further <a href="../../index.html#lexer">discussion</a> <br>
|
||||
-- @class module
|
||||
-- @name pl.lexer
|
||||
|
||||
local yield,wrap = coroutine.yield,coroutine.wrap
|
||||
local strfind = string.find
|
||||
local strsub = string.sub
|
||||
local append = table.insert
|
||||
--[[
|
||||
module ('pl.lexer',utils._module)
|
||||
]]
|
||||
|
||||
local function assert_arg(idx,val,tp)
|
||||
if type(val) ~= tp then
|
||||
error("argument "..idx.." must be "..tp, 2)
|
||||
end
|
||||
end
|
||||
|
||||
local lexer = {}
|
||||
|
||||
local NUMBER1 = '^[%+%-]?%d+%.?%d*[eE][%+%-]?%d+'
|
||||
local NUMBER2 = '^[%+%-]?%d+%.?%d*'
|
||||
local NUMBER3 = '^0x[%da-fA-F]+'
|
||||
local NUMBER4 = '^%d+%.?%d*[eE][%+%-]?%d+'
|
||||
local NUMBER5 = '^%d+%.?%d*'
|
||||
local IDEN = '^[%a_][%w_]*'
|
||||
local WSPACE = '^%s+'
|
||||
local STRING1 = [[^'.-[^\\]']]
|
||||
local STRING2 = [[^".-[^\\]"]]
|
||||
local STRING3 = "^((['\"])%2)" -- empty string
|
||||
local PREPRO = '^#.-[^\\]\n'
|
||||
|
||||
local plain_matches,lua_matches,cpp_matches,lua_keyword,cpp_keyword
|
||||
|
||||
local function tdump(tok)
|
||||
return yield(tok,tok)
|
||||
end
|
||||
|
||||
local function ndump(tok,options)
|
||||
if options and options.number then
|
||||
tok = tonumber(tok)
|
||||
end
|
||||
return yield("number",tok)
|
||||
end
|
||||
|
||||
-- regular strings, single or double quotes; usually we want them
|
||||
-- without the quotes
|
||||
local function sdump(tok,options)
|
||||
if options and options.string then
|
||||
tok = tok:sub(2,-2)
|
||||
end
|
||||
return yield("string",tok)
|
||||
end
|
||||
|
||||
-- long Lua strings need extra work to get rid of the quotes
|
||||
local function sdump_l(tok,options)
|
||||
if options and options.string then
|
||||
tok = tok:sub(3,-3)
|
||||
end
|
||||
return yield("string",tok)
|
||||
end
|
||||
|
||||
local function chdump(tok,options)
|
||||
if options and options.string then
|
||||
tok = tok:sub(2,-2)
|
||||
end
|
||||
return yield("char",tok)
|
||||
end
|
||||
|
||||
local function cdump(tok)
|
||||
return yield('comment',tok)
|
||||
end
|
||||
|
||||
local function wsdump (tok)
|
||||
return yield("space",tok)
|
||||
end
|
||||
|
||||
local function pdump (tok)
|
||||
return yield('prepro',tok)
|
||||
end
|
||||
|
||||
local function plain_vdump(tok)
|
||||
return yield("iden",tok)
|
||||
end
|
||||
|
||||
local function lua_vdump(tok)
|
||||
if lua_keyword[tok] then
|
||||
return yield("keyword",tok)
|
||||
else
|
||||
return yield("iden",tok)
|
||||
end
|
||||
end
|
||||
|
||||
local function cpp_vdump(tok)
|
||||
if cpp_keyword[tok] then
|
||||
return yield("keyword",tok)
|
||||
else
|
||||
return yield("iden",tok)
|
||||
end
|
||||
end
|
||||
|
||||
--- create a plain token iterator from a string or file-like object.
|
||||
-- @param s the string
|
||||
-- @param matches an optional match table (set of pattern-action pairs)
|
||||
-- @param filter a table of token types to exclude, by default {space=true}
|
||||
-- @param options a table of options; by default, {number=true,string=true},
|
||||
-- which means convert numbers and strip string quotes.
|
||||
function lexer.scan (s,matches,filter,options)
|
||||
--assert_arg(1,s,'string')
|
||||
local file = type(s) ~= 'string' and s
|
||||
filter = filter or {space=true}
|
||||
options = options or {number=true,string=true}
|
||||
if filter then
|
||||
if filter.space then filter[wsdump] = true end
|
||||
if filter.comments then
|
||||
filter[cdump] = true
|
||||
end
|
||||
end
|
||||
if not matches then
|
||||
if not plain_matches then
|
||||
plain_matches = {
|
||||
{WSPACE,wsdump},
|
||||
{NUMBER3,ndump},
|
||||
{IDEN,plain_vdump},
|
||||
{NUMBER1,ndump},
|
||||
{NUMBER2,ndump},
|
||||
{STRING3,sdump},
|
||||
{STRING1,sdump},
|
||||
{STRING2,sdump},
|
||||
{'^.',tdump}
|
||||
}
|
||||
end
|
||||
matches = plain_matches
|
||||
end
|
||||
local function lex ()
|
||||
local i1,i2,idx,res1,res2,tok,pat,fun,capt
|
||||
local line = 1
|
||||
if file then s = file:read()..'\n' end
|
||||
local sz = #s
|
||||
local idx = 1
|
||||
--print('sz',sz)
|
||||
while true do
|
||||
for _,m in ipairs(matches) do
|
||||
pat = m[1]
|
||||
fun = m[2]
|
||||
i1,i2 = strfind(s,pat,idx)
|
||||
if i1 then
|
||||
tok = strsub(s,i1,i2)
|
||||
idx = i2 + 1
|
||||
if not (filter and filter[fun]) then
|
||||
lexer.finished = idx > sz
|
||||
res1,res2 = fun(tok,options)
|
||||
end
|
||||
if res1 then
|
||||
local tp = type(res1)
|
||||
-- insert a token list
|
||||
if tp=='table' then
|
||||
yield('','')
|
||||
for _,t in ipairs(res1) do
|
||||
yield(t[1],t[2])
|
||||
end
|
||||
elseif tp == 'string' then -- or search up to some special pattern
|
||||
i1,i2 = strfind(s,res1,idx)
|
||||
if i1 then
|
||||
tok = strsub(s,i1,i2)
|
||||
idx = i2 + 1
|
||||
yield('',tok)
|
||||
else
|
||||
yield('','')
|
||||
idx = sz + 1
|
||||
end
|
||||
--if idx > sz then return end
|
||||
else
|
||||
yield(line,idx)
|
||||
end
|
||||
end
|
||||
if idx > sz then
|
||||
if file then
|
||||
--repeat -- next non-empty line
|
||||
line = line + 1
|
||||
s = file:read()
|
||||
if not s then return end
|
||||
--until not s:match '^%s*$'
|
||||
s = s .. '\n'
|
||||
idx ,sz = 1,#s
|
||||
break
|
||||
else
|
||||
return
|
||||
end
|
||||
else break end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return wrap(lex)
|
||||
end
|
||||
|
||||
local function isstring (s)
|
||||
return type(s) == 'string'
|
||||
end
|
||||
|
||||
--- insert tokens into a stream.
|
||||
-- @param tok a token stream
|
||||
-- @param a1 a string is the type, a table is a token list and
|
||||
-- a function is assumed to be a token-like iterator (returns type & value)
|
||||
-- @param a2 a string is the value
|
||||
function lexer.insert (tok,a1,a2)
|
||||
if not a1 then return end
|
||||
local ts
|
||||
if isstring(a1) and isstring(a2) then
|
||||
ts = {{a1,a2}}
|
||||
elseif type(a1) == 'function' then
|
||||
ts = {}
|
||||
for t,v in a1() do
|
||||
append(ts,{t,v})
|
||||
end
|
||||
else
|
||||
ts = a1
|
||||
end
|
||||
tok(ts)
|
||||
end
|
||||
|
||||
--- get everything in a stream upto a newline.
|
||||
-- @param tok a token stream
|
||||
-- @return a string
|
||||
function lexer.getline (tok)
|
||||
local t,v = tok('.-\n')
|
||||
return v
|
||||
end
|
||||
|
||||
--- get current line number. <br>
|
||||
-- Only available if the input source is a file-like object.
|
||||
-- @param tok a token stream
|
||||
-- @return the line number and current column
|
||||
function lexer.lineno (tok)
|
||||
return tok(0)
|
||||
end
|
||||
|
||||
--- get the rest of the stream.
|
||||
-- @param tok a token stream
|
||||
-- @return a string
|
||||
function lexer.getrest (tok)
|
||||
local t,v = tok('.+')
|
||||
return v
|
||||
end
|
||||
|
||||
--- get the Lua keywords as a set-like table.
|
||||
-- So <code>res["and"]</code> etc would be <code>true</code>.
|
||||
-- @return a table
|
||||
function lexer.get_keywords ()
|
||||
if not lua_keyword then
|
||||
lua_keyword = {
|
||||
["and"] = true, ["break"] = true, ["do"] = true,
|
||||
["else"] = true, ["elseif"] = true, ["end"] = true,
|
||||
["false"] = true, ["for"] = true, ["function"] = true,
|
||||
["if"] = true, ["in"] = true, ["local"] = true, ["nil"] = true,
|
||||
["not"] = true, ["or"] = true, ["repeat"] = true,
|
||||
["return"] = true, ["then"] = true, ["true"] = true,
|
||||
["until"] = true, ["while"] = true
|
||||
}
|
||||
end
|
||||
return lua_keyword
|
||||
end
|
||||
|
||||
|
||||
--- create a Lua token iterator from a string or file-like object.
|
||||
-- Will return the token type and value.
|
||||
-- @param s the string
|
||||
-- @param filter a table of token types to exclude, by default {space=true,comments=true}
|
||||
-- @param options a table of options; by default, {number=true,string=true},
|
||||
-- which means convert numbers and strip string quotes.
|
||||
function lexer.lua(s,filter,options)
|
||||
filter = filter or {space=true,comments=true}
|
||||
lexer.get_keywords()
|
||||
if not lua_matches then
|
||||
lua_matches = {
|
||||
{WSPACE,wsdump},
|
||||
{NUMBER3,ndump},
|
||||
{IDEN,lua_vdump},
|
||||
{NUMBER4,ndump},
|
||||
{NUMBER5,ndump},
|
||||
{STRING3,sdump},
|
||||
{STRING1,sdump},
|
||||
{STRING2,sdump},
|
||||
{'^%-%-%[%[.-%]%]',cdump},
|
||||
{'^%-%-.-\n',cdump},
|
||||
{'^%[%[.-%]%]',sdump_l},
|
||||
{'^==',tdump},
|
||||
{'^~=',tdump},
|
||||
{'^<=',tdump},
|
||||
{'^>=',tdump},
|
||||
{'^%.%.%.',tdump},
|
||||
{'^%.%.',tdump},
|
||||
{'^.',tdump}
|
||||
}
|
||||
end
|
||||
return lexer.scan(s,lua_matches,filter,options)
|
||||
end
|
||||
|
||||
--- create a C/C++ token iterator from a string or file-like object.
|
||||
-- Will return the token type type and value.
|
||||
-- @param s the string
|
||||
-- @param filter a table of token types to exclude, by default {space=true,comments=true}
|
||||
-- @param options a table of options; by default, {number=true,string=true},
|
||||
-- which means convert numbers and strip string quotes.
|
||||
function lexer.cpp(s,filter,options)
|
||||
filter = filter or {comments=true}
|
||||
if not cpp_keyword then
|
||||
cpp_keyword = {
|
||||
["class"] = true, ["break"] = true, ["do"] = true, ["sizeof"] = true,
|
||||
["else"] = true, ["continue"] = true, ["struct"] = true,
|
||||
["false"] = true, ["for"] = true, ["public"] = true, ["void"] = true,
|
||||
["private"] = true, ["protected"] = true, ["goto"] = true,
|
||||
["if"] = true, ["static"] = true, ["const"] = true, ["typedef"] = true,
|
||||
["enum"] = true, ["char"] = true, ["int"] = true, ["bool"] = true,
|
||||
["long"] = true, ["float"] = true, ["true"] = true, ["delete"] = true,
|
||||
["double"] = true, ["while"] = true, ["new"] = true,
|
||||
["namespace"] = true, ["try"] = true, ["catch"] = true,
|
||||
["switch"] = true, ["case"] = true, ["extern"] = true,
|
||||
["return"] = true,["default"] = true,['unsigned'] = true,['signed'] = true,
|
||||
["union"] = true, ["volatile"] = true, ["register"] = true,["short"] = true,
|
||||
}
|
||||
end
|
||||
if not cpp_matches then
|
||||
cpp_matches = {
|
||||
{WSPACE,wsdump},
|
||||
{PREPRO,pdump},
|
||||
{NUMBER3,ndump},
|
||||
{IDEN,cpp_vdump},
|
||||
{NUMBER4,ndump},
|
||||
{NUMBER5,ndump},
|
||||
{STRING3,sdump},
|
||||
{STRING1,chdump},
|
||||
{STRING2,sdump},
|
||||
{'^//.-\n',cdump},
|
||||
{'^/%*.-%*/',cdump},
|
||||
{'^==',tdump},
|
||||
{'^!=',tdump},
|
||||
{'^<=',tdump},
|
||||
{'^>=',tdump},
|
||||
{'^->',tdump},
|
||||
{'^&&',tdump},
|
||||
{'^||',tdump},
|
||||
{'^%+%+',tdump},
|
||||
{'^%-%-',tdump},
|
||||
{'^%+=',tdump},
|
||||
{'^%-=',tdump},
|
||||
{'^%*=',tdump},
|
||||
{'^/=',tdump},
|
||||
{'^|=',tdump},
|
||||
{'^%^=',tdump},
|
||||
{'^::',tdump},
|
||||
{'^.',tdump}
|
||||
}
|
||||
end
|
||||
return lexer.scan(s,cpp_matches,filter,options)
|
||||
end
|
||||
|
||||
--- get a list of parameters separated by a delimiter from a stream.
|
||||
-- @param tok the token stream
|
||||
-- @param endtoken end of list (default ')'). Can be '\n'
|
||||
-- @param delim separator (default ',')
|
||||
-- @return a list of token lists.
|
||||
function lexer.get_separated_list(tok,endtoken,delim)
|
||||
endtoken = endtoken or ')'
|
||||
delim = delim or ','
|
||||
local parm_values = {}
|
||||
local level = 1 -- used to count ( and )
|
||||
local tl = {}
|
||||
local function tappend (tl,t,val)
|
||||
val = val or t
|
||||
append(tl,{t,val})
|
||||
end
|
||||
local is_end
|
||||
if endtoken == '\n' then
|
||||
is_end = function(t,val)
|
||||
return t == 'space' and val:find '\n'
|
||||
end
|
||||
else
|
||||
is_end = function (t)
|
||||
return t == endtoken
|
||||
end
|
||||
end
|
||||
local token,value
|
||||
while true do
|
||||
token,value=tok()
|
||||
if not token then return nil,'EOS' end -- end of stream is an error!
|
||||
if is_end(token,value) and level == 1 then
|
||||
append(parm_values,tl)
|
||||
break
|
||||
elseif token == '(' then
|
||||
level = level + 1
|
||||
tappend(tl,'(')
|
||||
elseif token == ')' then
|
||||
level = level - 1
|
||||
if level == 0 then -- finished with parm list
|
||||
append(parm_values,tl)
|
||||
break
|
||||
else
|
||||
tappend(tl,')')
|
||||
end
|
||||
elseif token == delim and level == 1 then
|
||||
append(parm_values,tl) -- a new parm
|
||||
tl = {}
|
||||
else
|
||||
tappend(tl,token,value)
|
||||
end
|
||||
end
|
||||
return parm_values,{token,value}
|
||||
end
|
||||
|
||||
--- get the next non-space token from the stream.
|
||||
-- @param tok the token stream.
|
||||
function lexer.skipws (tok)
|
||||
local t,v = tok()
|
||||
while t == 'space' do
|
||||
t,v = tok()
|
||||
end
|
||||
return t,v
|
||||
end
|
||||
|
||||
local skipws = lexer.skipws
|
||||
|
||||
--- get the next token, which must be of the expected type.
|
||||
-- Throws an error if this type does not match!
|
||||
-- @param tok the token stream
|
||||
-- @param expected_type the token type
|
||||
-- @param no_skip_ws whether we should skip whitespace
|
||||
function lexer.expecting (tok,expected_type,no_skip_ws)
|
||||
assert_arg(1,tok,'function')
|
||||
assert_arg(2,expected_type,'string')
|
||||
local t,v
|
||||
if no_skip_ws then
|
||||
t,v = tok()
|
||||
else
|
||||
t,v = skipws(tok)
|
||||
end
|
||||
if t ~= expected_type then error ("expecting "..expected_type,2) end
|
||||
return v
|
||||
end
|
||||
|
||||
return lexer
|
||||
264
Utils/luarocks/share/lua/5.1/pl/luabalanced.lua
Normal file
264
Utils/luarocks/share/lua/5.1/pl/luabalanced.lua
Normal file
@@ -0,0 +1,264 @@
|
||||
--- Extract delimited Lua sequences from strings.
|
||||
-- Inspired by Damian Conway's Text::Balanced in Perl. <br/>
|
||||
-- <ul>
|
||||
-- <li>[1] <a href="http://lua-users.org/wiki/LuaBalanced">Lua Wiki Page</a></li>
|
||||
-- <li>[2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm</li>
|
||||
-- </ul> <br/>
|
||||
-- <pre class=example>
|
||||
-- local lb = require "pl.luabalanced"
|
||||
-- --Extract Lua expression starting at position 4.
|
||||
-- print(lb.match_expression("if x^2 + x > 5 then print(x) end", 4))
|
||||
-- --> x^2 + x > 5 16
|
||||
-- --Extract Lua string starting at (default) position 1.
|
||||
-- print(lb.match_string([["test\"123" .. "more"]]))
|
||||
-- --> "test\"123" 12
|
||||
-- </pre>
|
||||
-- (c) 2008, David Manura, Licensed under the same terms as Lua (MIT license).
|
||||
-- @class module
|
||||
-- @name pl.luabalanced
|
||||
|
||||
local M = {}
|
||||
|
||||
local assert = assert
|
||||
local table_concat = table.concat
|
||||
|
||||
-- map opening brace <-> closing brace.
|
||||
local ends = { ['('] = ')', ['{'] = '}', ['['] = ']' }
|
||||
local begins = {}; for k,v in pairs(ends) do begins[v] = k end
|
||||
|
||||
|
||||
-- Match Lua string in string <s> starting at position <pos>.
|
||||
-- Returns <string>, <posnew>, where <string> is the matched
|
||||
-- string (or nil on no match) and <posnew> is the character
|
||||
-- following the match (or <pos> on no match).
|
||||
-- Supports all Lua string syntax: "...", '...', [[...]], [=[...]=], etc.
|
||||
local function match_string(s, pos)
|
||||
pos = pos or 1
|
||||
local posa = pos
|
||||
local c = s:sub(pos,pos)
|
||||
if c == '"' or c == "'" then
|
||||
pos = pos + 1
|
||||
while 1 do
|
||||
pos = assert(s:find("[" .. c .. "\\]", pos), 'syntax error')
|
||||
if s:sub(pos,pos) == c then
|
||||
local part = s:sub(posa, pos)
|
||||
return part, pos + 1
|
||||
else
|
||||
pos = pos + 2
|
||||
end
|
||||
end
|
||||
else
|
||||
local sc = s:match("^%[(=*)%[", pos)
|
||||
if sc then
|
||||
local _; _, pos = s:find("%]" .. sc .. "%]", pos)
|
||||
assert(pos)
|
||||
local part = s:sub(posa, pos)
|
||||
return part, pos + 1
|
||||
else
|
||||
return nil, pos
|
||||
end
|
||||
end
|
||||
end
|
||||
M.match_string = match_string
|
||||
|
||||
|
||||
-- Match bracketed Lua expression, e.g. "(...)", "{...}", "[...]", "[[...]]",
|
||||
-- [=[...]=], etc.
|
||||
-- Function interface is similar to match_string.
|
||||
local function match_bracketed(s, pos)
|
||||
pos = pos or 1
|
||||
local posa = pos
|
||||
local ca = s:sub(pos,pos)
|
||||
if not ends[ca] then
|
||||
return nil, pos
|
||||
end
|
||||
local stack = {}
|
||||
while 1 do
|
||||
pos = s:find('[%(%{%[%)%}%]\"\']', pos)
|
||||
assert(pos, 'syntax error: unbalanced')
|
||||
local c = s:sub(pos,pos)
|
||||
if c == '"' or c == "'" then
|
||||
local part; part, pos = match_string(s, pos)
|
||||
assert(part)
|
||||
elseif ends[c] then -- open
|
||||
local mid, posb
|
||||
if c == '[' then mid, posb = s:match('^%[(=*)%[()', pos) end
|
||||
if mid then
|
||||
pos = s:match('%]' .. mid .. '%]()', posb)
|
||||
assert(pos, 'syntax error: long string not terminated')
|
||||
if #stack == 0 then
|
||||
local part = s:sub(posa, pos-1)
|
||||
return part, pos
|
||||
end
|
||||
else
|
||||
stack[#stack+1] = c
|
||||
pos = pos + 1
|
||||
end
|
||||
else -- close
|
||||
assert(stack[#stack] == assert(begins[c]), 'syntax error: unbalanced')
|
||||
stack[#stack] = nil
|
||||
if #stack == 0 then
|
||||
local part = s:sub(posa, pos)
|
||||
return part, pos+1
|
||||
end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
M.match_bracketed = match_bracketed
|
||||
|
||||
|
||||
-- Match Lua comment, e.g. "--...\n", "--[[...]]", "--[=[...]=]", etc.
|
||||
-- Function interface is similar to match_string.
|
||||
local function match_comment(s, pos)
|
||||
pos = pos or 1
|
||||
if s:sub(pos, pos+1) ~= '--' then
|
||||
return nil, pos
|
||||
end
|
||||
pos = pos + 2
|
||||
local partt, post = match_string(s, pos)
|
||||
if partt then
|
||||
return '--' .. partt, post
|
||||
end
|
||||
local part; part, pos = s:match('^([^\n]*\n?)()', pos)
|
||||
return '--' .. part, pos
|
||||
end
|
||||
|
||||
|
||||
-- Match Lua expression, e.g. "a + b * c[e]".
|
||||
-- Function interface is similar to match_string.
|
||||
local wordop = {['and']=true, ['or']=true, ['not']=true}
|
||||
local is_compare = {['>']=true, ['<']=true, ['~']=true}
|
||||
local function match_expression(s, pos)
|
||||
pos = pos or 1
|
||||
local posa = pos
|
||||
local lastident
|
||||
local poscs, posce
|
||||
while pos do
|
||||
local c = s:sub(pos,pos)
|
||||
if c == '"' or c == "'" or c == '[' and s:find('^[=%[]', pos+1) then
|
||||
local part; part, pos = match_string(s, pos)
|
||||
assert(part, 'syntax error')
|
||||
elseif c == '-' and s:sub(pos+1,pos+1) == '-' then
|
||||
-- note: handle adjacent comments in loop to properly support
|
||||
-- backtracing (poscs/posce).
|
||||
poscs = pos
|
||||
while s:sub(pos,pos+1) == '--' do
|
||||
local part; part, pos = match_comment(s, pos)
|
||||
assert(part)
|
||||
pos = s:match('^%s*()', pos)
|
||||
posce = pos
|
||||
end
|
||||
elseif c == '(' or c == '{' or c == '[' then
|
||||
local part; part, pos = match_bracketed(s, pos)
|
||||
elseif c == '=' and s:sub(pos+1,pos+1) == '=' then
|
||||
pos = pos + 2 -- skip over two-char op containing '='
|
||||
elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then
|
||||
pos = pos + 1 -- skip over two-char op containing '='
|
||||
elseif c:match'^[%)%}%];,=]' then
|
||||
local part = s:sub(posa, pos-1)
|
||||
return part, pos
|
||||
elseif c:match'^[%w_]' then
|
||||
local newident,newpos = s:match('^([%w_]+)()', pos)
|
||||
if pos ~= posa and not wordop[newident] then -- non-first ident
|
||||
local pose = ((posce == pos) and poscs or pos) - 1
|
||||
while s:match('^%s', pose) do pose = pose - 1 end
|
||||
local ce = s:sub(pose,pose)
|
||||
if ce:match'[%)%}\'\"%]]' or
|
||||
ce:match'[%w_]' and not wordop[lastident]
|
||||
then
|
||||
local part = s:sub(posa, pos-1)
|
||||
return part, pos
|
||||
end
|
||||
end
|
||||
lastident, pos = newident, newpos
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
pos = s:find('[%(%{%[%)%}%]\"\';,=%w_%-]', pos)
|
||||
end
|
||||
local part = s:sub(posa, #s)
|
||||
return part, #s+1
|
||||
end
|
||||
M.match_expression = match_expression
|
||||
|
||||
|
||||
-- Match name list (zero or more names). E.g. "a,b,c"
|
||||
-- Function interface is similar to match_string,
|
||||
-- but returns array as match.
|
||||
local function match_namelist(s, pos)
|
||||
pos = pos or 1
|
||||
local list = {}
|
||||
while 1 do
|
||||
local c = #list == 0 and '^' or '^%s*,%s*'
|
||||
local item, post = s:match(c .. '([%a_][%w_]*)%s*()', pos)
|
||||
if item then pos = post else break end
|
||||
list[#list+1] = item
|
||||
end
|
||||
return list, pos
|
||||
end
|
||||
M.match_namelist = match_namelist
|
||||
|
||||
|
||||
-- Match expression list (zero or more expressions). E.g. "a+b,b*c".
|
||||
-- Function interface is similar to match_string,
|
||||
-- but returns array as match.
|
||||
local function match_explist(s, pos)
|
||||
pos = pos or 1
|
||||
local list = {}
|
||||
while 1 do
|
||||
if #list ~= 0 then
|
||||
local post = s:match('^%s*,%s*()', pos)
|
||||
if post then pos = post else break end
|
||||
end
|
||||
local item; item, pos = match_expression(s, pos)
|
||||
assert(item, 'syntax error')
|
||||
list[#list+1] = item
|
||||
end
|
||||
return list, pos
|
||||
end
|
||||
M.match_explist = match_explist
|
||||
|
||||
|
||||
-- Replace snippets of code in Lua code string <s>
|
||||
-- using replacement function f(u,sin) --> sout.
|
||||
-- <u> is the type of snippet ('c' = comment, 's' = string,
|
||||
-- 'e' = any other code).
|
||||
-- Snippet is replaced with <sout> (unless <sout> is nil or false, in
|
||||
-- which case the original snippet is kept)
|
||||
-- This is somewhat analogous to string.gsub .
|
||||
local function gsub(s, f)
|
||||
local pos = 1
|
||||
local posa = 1
|
||||
local sret = ''
|
||||
while 1 do
|
||||
pos = s:find('[%-\'\"%[]', pos)
|
||||
if not pos then break end
|
||||
if s:match('^%-%-', pos) then
|
||||
local exp = s:sub(posa, pos-1)
|
||||
if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
|
||||
local comment; comment, pos = match_comment(s, pos)
|
||||
sret = sret .. (f('c', assert(comment)) or comment)
|
||||
posa = pos
|
||||
else
|
||||
local posb = s:find('^[\'\"%[]', pos)
|
||||
local str
|
||||
if posb then str, pos = match_string(s, posb) end
|
||||
if str then
|
||||
local exp = s:sub(posa, posb-1)
|
||||
if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
|
||||
sret = sret .. (f('s', str) or str)
|
||||
posa = pos
|
||||
else
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
local exp = s:sub(posa)
|
||||
if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
|
||||
return sret
|
||||
end
|
||||
M.gsub = gsub
|
||||
|
||||
|
||||
return M
|
||||
197
Utils/luarocks/share/lua/5.1/pl/operator.lua
Normal file
197
Utils/luarocks/share/lua/5.1/pl/operator.lua
Normal file
@@ -0,0 +1,197 @@
|
||||
--- Lua operators available as functions.
|
||||
-- (similar to the Python module of the same name)<br>
|
||||
-- There is a module field <code>optable</code> which maps the operator strings
|
||||
-- onto these functions, e.g. <pre class=example>operator.optable['()']==operator.call</pre>
|
||||
-- <p>Operator strings like '>' and '{}' can be passed to most Penlight functions
|
||||
-- expecting a function argument.</p>
|
||||
-- @class module
|
||||
-- @name pl.operator
|
||||
|
||||
local strfind = string.find
|
||||
local utils = require 'pl.utils'
|
||||
|
||||
local operator = {}
|
||||
|
||||
--- apply function to some arguments ()
|
||||
-- @param fn a function or callable object
|
||||
-- @param ... arguments
|
||||
function operator.call(fn,...)
|
||||
return fn(...)
|
||||
end
|
||||
|
||||
--- get the indexed value from a table []
|
||||
-- @param t a table or any indexable object
|
||||
-- @param k the key
|
||||
function operator.index(t,k)
|
||||
return t[k]
|
||||
end
|
||||
|
||||
--- returns true if arguments are equal ==
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.eq(a,b)
|
||||
return a==b
|
||||
end
|
||||
|
||||
--- returns true if arguments are not equal ~=
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.neq(a,b)
|
||||
return a~=b
|
||||
end
|
||||
|
||||
--- returns true if a is less than b <
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.lt(a,b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
--- returns true if a is less or equal to b <=
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.le(a,b)
|
||||
return a <= b
|
||||
end
|
||||
|
||||
--- returns true if a is greater than b >
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.gt(a,b)
|
||||
return a > b
|
||||
end
|
||||
|
||||
--- returns true if a is greater or equal to b >=
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.ge(a,b)
|
||||
return a >= b
|
||||
end
|
||||
|
||||
--- returns length of string or table #
|
||||
-- @param a a string or a table
|
||||
function operator.len(a)
|
||||
return #a
|
||||
end
|
||||
|
||||
--- add two values +
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.add(a,b)
|
||||
return a+b
|
||||
end
|
||||
|
||||
--- subtract b from a -
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.sub(a,b)
|
||||
return a-b
|
||||
end
|
||||
|
||||
--- multiply two values *
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.mul(a,b)
|
||||
return a*b
|
||||
end
|
||||
|
||||
--- divide first value by second /
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.div(a,b)
|
||||
return a/b
|
||||
end
|
||||
|
||||
--- raise first to the power of second ^
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.pow(a,b)
|
||||
return a^b
|
||||
end
|
||||
|
||||
--- modulo; remainder of a divided by b %
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.mod(a,b)
|
||||
return a%b
|
||||
end
|
||||
|
||||
--- concatenate two values (either strings or __concat defined) ..
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.concat(a,b)
|
||||
return a..b
|
||||
end
|
||||
|
||||
--- return the negative of a value -
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.unm(a)
|
||||
return -a
|
||||
end
|
||||
|
||||
--- false if value evaluates as true not
|
||||
-- @param a value
|
||||
function operator.lnot(a)
|
||||
return not a
|
||||
end
|
||||
|
||||
--- true if both values evaluate as true and
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.land(a,b)
|
||||
return a and b
|
||||
end
|
||||
|
||||
--- true if either value evaluate as true or
|
||||
-- @param a value
|
||||
-- @param b value
|
||||
function operator.lor(a,b)
|
||||
return a or b
|
||||
end
|
||||
|
||||
--- make a table from the arguments {}
|
||||
-- @param ... non-nil arguments
|
||||
-- @return a table
|
||||
function operator.table (...)
|
||||
return {...}
|
||||
end
|
||||
|
||||
--- match two strings ~
|
||||
-- uses @{string.find}
|
||||
function operator.match (a,b)
|
||||
return strfind(a,b)~=nil
|
||||
end
|
||||
|
||||
--- the null operation.
|
||||
-- @param ... arguments
|
||||
-- @return the arguments
|
||||
function operator.nop (...)
|
||||
return ...
|
||||
end
|
||||
|
||||
operator.optable = {
|
||||
['+']=operator.add,
|
||||
['-']=operator.sub,
|
||||
['*']=operator.mul,
|
||||
['/']=operator.div,
|
||||
['%']=operator.mod,
|
||||
['^']=operator.pow,
|
||||
['..']=operator.concat,
|
||||
['()']=operator.call,
|
||||
['[]']=operator.index,
|
||||
['<']=operator.lt,
|
||||
['<=']=operator.le,
|
||||
['>']=operator.gt,
|
||||
['>=']=operator.ge,
|
||||
['==']=operator.eq,
|
||||
['~=']=operator.neq,
|
||||
['#']=operator.len,
|
||||
['and']=operator.land,
|
||||
['or']=operator.lor,
|
||||
['{}']=operator.table,
|
||||
['~']=operator.match,
|
||||
['']=operator.nop,
|
||||
}
|
||||
|
||||
return operator
|
||||
335
Utils/luarocks/share/lua/5.1/pl/path.lua
Normal file
335
Utils/luarocks/share/lua/5.1/pl/path.lua
Normal file
@@ -0,0 +1,335 @@
|
||||
--- Path manipulation and file queries. <br>
|
||||
-- This is modelled after Python's os.path library (11.1)
|
||||
-- @class module
|
||||
-- @name pl.path
|
||||
|
||||
-- imports and locals
|
||||
local _G = _G
|
||||
local sub = string.sub
|
||||
local getenv = os.getenv
|
||||
local tmpnam = os.tmpname
|
||||
local attributes, currentdir, link_attrib
|
||||
local package = package
|
||||
local io = io
|
||||
local append = table.insert
|
||||
local ipairs = ipairs
|
||||
local utils = require 'pl.utils'
|
||||
local assert_arg,assert_string,raise = utils.assert_arg,utils.assert_string,utils.raise
|
||||
|
||||
--[[
|
||||
module ('pl.path',utils._module)
|
||||
]]
|
||||
|
||||
local path, attrib
|
||||
|
||||
if _G.luajava then
|
||||
path = require 'pl.platf.luajava'
|
||||
else
|
||||
path = {}
|
||||
|
||||
local res,lfs = _G.pcall(_G.require,'lfs')
|
||||
if res then
|
||||
attributes = lfs.attributes
|
||||
currentdir = lfs.currentdir
|
||||
link_attrib = lfs.symlinkattributes
|
||||
else
|
||||
error("pl.path requires LuaFileSystem")
|
||||
end
|
||||
|
||||
attrib = attributes
|
||||
path.attrib = attrib
|
||||
path.link_attrib = link_attrib
|
||||
path.dir = lfs.dir
|
||||
path.mkdir = lfs.mkdir
|
||||
path.rmdir = lfs.rmdir
|
||||
path.chdir = lfs.chdir
|
||||
|
||||
--- is this a directory?
|
||||
-- @param P A file path
|
||||
function path.isdir(P)
|
||||
if P:match("\\$") then
|
||||
P = P:sub(1,-2)
|
||||
end
|
||||
return attrib(P,'mode') == 'directory'
|
||||
end
|
||||
|
||||
--- is this a file?.
|
||||
-- @param P A file path
|
||||
function path.isfile(P)
|
||||
return attrib(P,'mode') == 'file'
|
||||
end
|
||||
|
||||
-- is this a symbolic link?
|
||||
-- @param P A file path
|
||||
function path.islink(P)
|
||||
if link_attrib then
|
||||
return link_attrib(P,'mode')=='link'
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- return size of a file.
|
||||
-- @param P A file path
|
||||
function path.getsize(P)
|
||||
return attrib(P,'size')
|
||||
end
|
||||
|
||||
--- does a path exist?.
|
||||
-- @param P A file path
|
||||
-- @return the file path if it exists, nil otherwise
|
||||
function path.exists(P)
|
||||
return attrib(P,'mode') ~= nil and P
|
||||
end
|
||||
|
||||
--- Return the time of last access as the number of seconds since the epoch.
|
||||
-- @param P A file path
|
||||
function path.getatime(P)
|
||||
return attrib(P,'access')
|
||||
end
|
||||
|
||||
--- Return the time of last modification
|
||||
-- @param P A file path
|
||||
function path.getmtime(P)
|
||||
return attrib(P,'modification')
|
||||
end
|
||||
|
||||
---Return the system's ctime.
|
||||
-- @param P A file path
|
||||
function path.getctime(P)
|
||||
return path.attrib(P,'change')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function at(s,i)
|
||||
return sub(s,i,i)
|
||||
end
|
||||
|
||||
path.is_windows = utils.dir_separator == '\\'
|
||||
|
||||
local other_sep
|
||||
-- !constant sep is the directory separator for this platform.
|
||||
if path.is_windows then
|
||||
path.sep = '\\'; other_sep = '/'
|
||||
path.dirsep = ';'
|
||||
else
|
||||
path.sep = '/'
|
||||
path.dirsep = ':'
|
||||
end
|
||||
local sep,dirsep = path.sep,path.dirsep
|
||||
|
||||
--- are we running Windows?
|
||||
-- @class field
|
||||
-- @name path.is_windows
|
||||
|
||||
--- path separator for this platform.
|
||||
-- @class field
|
||||
-- @name path.sep
|
||||
|
||||
--- separator for PATH for this platform
|
||||
-- @class field
|
||||
-- @name path.dirsep
|
||||
|
||||
--- given a path, return the directory part and a file part.
|
||||
-- if there's no directory part, the first value will be empty
|
||||
-- @param P A file path
|
||||
function path.splitpath(P)
|
||||
assert_string(1,P)
|
||||
local i = #P
|
||||
local ch = at(P,i)
|
||||
while i > 0 and ch ~= sep and ch ~= other_sep do
|
||||
i = i - 1
|
||||
ch = at(P,i)
|
||||
end
|
||||
if i == 0 then
|
||||
return '',P
|
||||
else
|
||||
return sub(P,1,i-1), sub(P,i+1)
|
||||
end
|
||||
end
|
||||
|
||||
--- return an absolute path.
|
||||
-- @param P A file path
|
||||
function path.abspath(P)
|
||||
assert_string(1,P)
|
||||
if not currentdir then return P end
|
||||
P = P:gsub('[\\/]$','')
|
||||
local pwd = currentdir()
|
||||
if not path.isabs(P) then
|
||||
return path.join(pwd,P)
|
||||
elseif path.is_windows and at(P,2) ~= ':' and at(P,2) ~= '\\' then
|
||||
return pwd:sub(1,2)..P
|
||||
else
|
||||
return P
|
||||
end
|
||||
end
|
||||
|
||||
--- given a path, return the root part and the extension part.
|
||||
-- if there's no extension part, the second value will be empty
|
||||
-- @param P A file path
|
||||
function path.splitext(P)
|
||||
assert_string(1,P)
|
||||
local i = #P
|
||||
local ch = at(P,i)
|
||||
while i > 0 and ch ~= '.' do
|
||||
if ch == sep or ch == other_sep then
|
||||
return P,''
|
||||
end
|
||||
i = i - 1
|
||||
ch = at(P,i)
|
||||
end
|
||||
if i == 0 then
|
||||
return P,''
|
||||
else
|
||||
return sub(P,1,i-1),sub(P,i)
|
||||
end
|
||||
end
|
||||
|
||||
--- return the directory part of a path
|
||||
-- @param P A file path
|
||||
function path.dirname(P)
|
||||
assert_string(1,P)
|
||||
local p1,p2 = path.splitpath(P)
|
||||
return p1
|
||||
end
|
||||
|
||||
--- return the file part of a path
|
||||
-- @param P A file path
|
||||
function path.basename(P)
|
||||
assert_string(1,P)
|
||||
local p1,p2 = path.splitpath(P)
|
||||
return p2
|
||||
end
|
||||
|
||||
--- get the extension part of a path.
|
||||
-- @param P A file path
|
||||
function path.extension(P)
|
||||
assert_string(1,P)
|
||||
local p1,p2 = path.splitext(P)
|
||||
return p2
|
||||
end
|
||||
|
||||
--- is this an absolute path?.
|
||||
-- @param P A file path
|
||||
function path.isabs(P)
|
||||
assert_string(1,P)
|
||||
if path.is_windows then
|
||||
return at(P,1) == '/' or at(P,1)=='\\' or at(P,2)==':'
|
||||
else
|
||||
return at(P,1) == '/'
|
||||
end
|
||||
end
|
||||
|
||||
--- return the P resulting from combining the two paths.
|
||||
-- if the second is already an absolute path, then it returns it.
|
||||
-- @param p1 A file path
|
||||
-- @param p2 A file path
|
||||
function path.join(p1,p2)
|
||||
assert_string(1,p1)
|
||||
assert_string(2,p2)
|
||||
if path.isabs(p2) then return p2 end
|
||||
local endc = at(p1,#p1)
|
||||
if endc ~= path.sep and endc ~= other_sep then
|
||||
p1 = p1..path.sep
|
||||
end
|
||||
return p1..p2
|
||||
end
|
||||
|
||||
--- normalize the case of a pathname. On Unix, this returns the path unchanged;
|
||||
-- for Windows, it converts the path to lowercase, and it also converts forward slashes
|
||||
-- to backward slashes.
|
||||
-- @param P A file path
|
||||
function path.normcase(P)
|
||||
assert_string(1,P)
|
||||
if path.is_windows then
|
||||
return (P:lower():gsub('/','\\'))
|
||||
else
|
||||
return P
|
||||
end
|
||||
end
|
||||
|
||||
--- normalize a path name.
|
||||
-- A//B, A/./B and A/foo/../B all become A/B.
|
||||
-- @param P a file path
|
||||
function path.normpath (P)
|
||||
assert_string(1,P)
|
||||
if path.is_windows then
|
||||
P = P:gsub('/','\\')
|
||||
return (P:gsub('[^\\]+\\%.%.\\',''):gsub('\\%.?\\','\\'))
|
||||
else
|
||||
return (P:gsub('[^/]+/%.%./',''):gsub('/%.?/','/'))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Replace a starting '~' with the user's home directory.
|
||||
-- In windows, if HOME isn't set, then USERPROFILE is used in preference to
|
||||
-- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows.
|
||||
-- @param P A file path
|
||||
function path.expanduser(P)
|
||||
assert_string(1,P)
|
||||
if at(P,1) == '~' then
|
||||
local home = getenv('HOME')
|
||||
if not home then -- has to be Windows
|
||||
home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH')
|
||||
end
|
||||
return home..sub(P,2)
|
||||
else
|
||||
return P
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Return a suitable full path to a new temporary file name.
|
||||
-- unlike os.tmpnam(), it always gives you a writeable path (uses %TMP% on Windows)
|
||||
function path.tmpname ()
|
||||
local res = tmpnam()
|
||||
if path.is_windows then res = getenv('TMP')..res end
|
||||
return res
|
||||
end
|
||||
|
||||
--- return the largest common prefix path of two paths.
|
||||
-- @param path1 a file path
|
||||
-- @param path2 a file path
|
||||
function path.common_prefix (path1,path2)
|
||||
assert_string(1,path1)
|
||||
assert_string(2,path2)
|
||||
-- get them in order!
|
||||
if #path1 > #path2 then path2,path1 = path1,path2 end
|
||||
for i = 1,#path1 do
|
||||
local c1 = at(path1,i)
|
||||
if c1 ~= at(path2,i) then
|
||||
local cp = path1:sub(1,i-1)
|
||||
if at(path1,i-1) ~= sep then
|
||||
cp = path.dirname(cp)
|
||||
end
|
||||
return cp
|
||||
end
|
||||
end
|
||||
if at(path2,#path1+1) ~= sep then path1 = path.dirname(path1) end
|
||||
return path1
|
||||
--return ''
|
||||
end
|
||||
|
||||
|
||||
--- return the full path where a particular Lua module would be found.
|
||||
-- Both package.path and package.cpath is searched, so the result may
|
||||
-- either be a Lua file or a shared libarary.
|
||||
-- @param mod name of the module
|
||||
-- @return on success: path of module, lua or binary
|
||||
-- @return on error: nil,error string
|
||||
function path.package_path(mod)
|
||||
assert_string(1,mod)
|
||||
local res
|
||||
mod = mod:gsub('%.',sep)
|
||||
res = package.searchpath(mod,package.path)
|
||||
if res then return res,true end
|
||||
res = package.searchpath(mod,package.cpath)
|
||||
if res then return res,false end
|
||||
return raise 'cannot find module on path'
|
||||
end
|
||||
|
||||
|
||||
---- finis -----
|
||||
return path
|
||||
65
Utils/luarocks/share/lua/5.1/pl/permute.lua
Normal file
65
Utils/luarocks/share/lua/5.1/pl/permute.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
--- Permutation operations.
|
||||
-- @class module
|
||||
-- @name pl.permute
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local copy = tablex.deepcopy
|
||||
local append = table.insert
|
||||
local coroutine = coroutine
|
||||
local resume = coroutine.resume
|
||||
local assert_arg = utils.assert_arg
|
||||
|
||||
--[[
|
||||
module ('pl.permute',utils._module)
|
||||
]]
|
||||
|
||||
local permute = {}
|
||||
|
||||
-- PiL, 9.3
|
||||
|
||||
local permgen
|
||||
permgen = function (a, n, fn)
|
||||
if n == 0 then
|
||||
fn(a)
|
||||
else
|
||||
for i=1,n do
|
||||
-- put i-th element as the last one
|
||||
a[n], a[i] = a[i], a[n]
|
||||
|
||||
-- generate all permutations of the other elements
|
||||
permgen(a, n - 1, fn)
|
||||
|
||||
-- restore i-th element
|
||||
a[n], a[i] = a[i], a[n]
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- an iterator over all permutations of the elements of a list.
|
||||
-- Please note that the same list is returned each time, so do not keep references!
|
||||
-- @param a list-like table
|
||||
-- @return an iterator which provides the next permutation as a list
|
||||
function permute.iter (a)
|
||||
assert_arg(1,a,'table')
|
||||
local n = #a
|
||||
local co = coroutine.create(function () permgen(a, n, coroutine.yield) end)
|
||||
return function () -- iterator
|
||||
local code, res = resume(co)
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
--- construct a table containing all the permutations of a list.
|
||||
-- @param a list-like table
|
||||
-- @return a table of tables
|
||||
-- @usage permute.table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}}
|
||||
function permute.table (a)
|
||||
assert_arg(1,a,'table')
|
||||
local res = {}
|
||||
local n = #a
|
||||
permgen(a,n,function(t) append(res,copy(t)) end)
|
||||
return res
|
||||
end
|
||||
|
||||
return permute
|
||||
101
Utils/luarocks/share/lua/5.1/pl/platf/luajava.lua
Normal file
101
Utils/luarocks/share/lua/5.1/pl/platf/luajava.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
-- experimental support for LuaJava
|
||||
--
|
||||
local path = {}
|
||||
|
||||
|
||||
path.link_attrib = nil
|
||||
|
||||
local File = luajava.bindClass("java.io.File")
|
||||
local Array = luajava.bindClass('java.lang.reflect.Array')
|
||||
|
||||
local function file(s)
|
||||
return luajava.new(File,s)
|
||||
end
|
||||
|
||||
function path.dir(P)
|
||||
local ls = file(P):list()
|
||||
print(ls)
|
||||
local idx,n = -1,Array:getLength(ls)
|
||||
return function ()
|
||||
idx = idx + 1
|
||||
if idx == n then return nil
|
||||
else
|
||||
return Array:get(ls,idx)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function path.mkdir(P)
|
||||
return file(P):mkdir()
|
||||
end
|
||||
|
||||
function path.rmdir(P)
|
||||
return file(P):delete()
|
||||
end
|
||||
|
||||
--- is this a directory?
|
||||
-- @param P A file path
|
||||
function path.isdir(P)
|
||||
if P:match("\\$") then
|
||||
P = P:sub(1,-2)
|
||||
end
|
||||
return file(P):isDirectory()
|
||||
end
|
||||
|
||||
--- is this a file?.
|
||||
-- @param P A file path
|
||||
function path.isfile(P)
|
||||
return file(P):isFile()
|
||||
end
|
||||
|
||||
-- is this a symbolic link?
|
||||
-- Direct support for symbolic links is not provided.
|
||||
-- see http://stackoverflow.com/questions/813710/java-1-6-determine-symbolic-links
|
||||
-- and the caveats therein.
|
||||
-- @param P A file path
|
||||
function path.islink(P)
|
||||
local f = file(P)
|
||||
local canon
|
||||
local parent = f:getParent()
|
||||
if not parent then
|
||||
canon = f
|
||||
else
|
||||
parent = f.getParentFile():getCanonicalFile()
|
||||
canon = luajava.new(File,parent,f:getName())
|
||||
end
|
||||
return canon:getCanonicalFile() ~= canon:getAbsoluteFile()
|
||||
end
|
||||
|
||||
--- return size of a file.
|
||||
-- @param P A file path
|
||||
function path.getsize(P)
|
||||
return file(P):length()
|
||||
end
|
||||
|
||||
--- does a path exist?.
|
||||
-- @param P A file path
|
||||
-- @return the file path if it exists, nil otherwise
|
||||
function path.exists(P)
|
||||
return file(P):exists() and P
|
||||
end
|
||||
|
||||
--- Return the time of last access as the number of seconds since the epoch.
|
||||
-- @param P A file path
|
||||
function path.getatime(P)
|
||||
return path.getmtime(P)
|
||||
end
|
||||
|
||||
--- Return the time of last modification
|
||||
-- @param P A file path
|
||||
function path.getmtime(P)
|
||||
-- Java time is no. of millisec since the epoch
|
||||
return file(P):lastModified()/1000
|
||||
end
|
||||
|
||||
---Return the system's ctime.
|
||||
-- @param P A file path
|
||||
function path.getctime(P)
|
||||
return path.getmtime(P)
|
||||
end
|
||||
|
||||
return path
|
||||
224
Utils/luarocks/share/lua/5.1/pl/pretty.lua
Normal file
224
Utils/luarocks/share/lua/5.1/pl/pretty.lua
Normal file
@@ -0,0 +1,224 @@
|
||||
--- Pretty-printing Lua tables.
|
||||
-- Also provides a sandboxed Lua table reader and
|
||||
-- a function to present large numbers in human-friendly format.
|
||||
-- @class module
|
||||
-- @name pl.pretty
|
||||
|
||||
local append = table.insert
|
||||
local concat = table.concat
|
||||
local utils = require 'pl.utils'
|
||||
local lexer = require 'pl.lexer'
|
||||
local assert_arg = utils.assert_arg
|
||||
|
||||
local pretty = {}
|
||||
|
||||
--- read a string representation of a Lua table.
|
||||
-- Uses load(), but tries to be cautious about loading arbitrary code!
|
||||
-- It is expecting a string of the form '{...}', with perhaps some whitespace
|
||||
-- before or after the curly braces. An empty environment is used, and
|
||||
-- any occurance of the keyword 'function' will be considered a problem.
|
||||
-- @param s {string} string of the form '{...}', with perhaps some whitespace
|
||||
-- before or after the curly braces.
|
||||
function pretty.read(s)
|
||||
assert_arg(1,s,'string')
|
||||
if not s:find '^%s*%b{}%s*$' then return nil,"not a Lua table" end
|
||||
if s:find '[^\'"%w_]function[^\'"%w_]' then
|
||||
local tok = lexer.lua(s)
|
||||
for t,v in tok do
|
||||
if t == 'keyword' then
|
||||
return nil,"cannot have Lua keywords in table definition"
|
||||
end
|
||||
end
|
||||
end
|
||||
local chunk,err = utils.load('return '..s,'tbl','t',{})
|
||||
if not chunk then return nil,err end
|
||||
return chunk()
|
||||
end
|
||||
|
||||
local function quote_if_necessary (v)
|
||||
if not v then return ''
|
||||
else
|
||||
if v:find ' ' then v = '"'..v..'"' end
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
local keywords
|
||||
|
||||
|
||||
--- Create a string representation of a Lua table.
|
||||
-- This function never fails, but may complain by returning an
|
||||
-- extra value. Normally puts out one item per line, using
|
||||
-- the provided indent; set the second parameter to '' if
|
||||
-- you want output on one line.
|
||||
-- @param tbl {table} Table to serialize to a string.
|
||||
-- @param space {string} (optional) The indent to use.
|
||||
-- Defaults to two spaces.
|
||||
-- @param not_clever {bool} (optional) Use for plain output, e.g {['key']=1}.
|
||||
-- Defaults to false.
|
||||
-- @return a string
|
||||
-- @return a possible error message
|
||||
function pretty.write (tbl,space,not_clever)
|
||||
if type(tbl) ~= 'table' then
|
||||
local res = tostring(tbl)
|
||||
if type(tbl) == 'string' then res = '"'..res..'"' end
|
||||
return res, 'not a table'
|
||||
end
|
||||
if not keywords then
|
||||
keywords = lexer.get_keywords()
|
||||
end
|
||||
local set = ' = '
|
||||
if space == '' then set = '=' end
|
||||
space = space or ' '
|
||||
local lines = {}
|
||||
local line = ''
|
||||
local tables = {}
|
||||
|
||||
local function is_identifier (s)
|
||||
return (s:find('^[%a_][%w_]*$')) and not keywords[s]
|
||||
end
|
||||
|
||||
local function put(s)
|
||||
if #s > 0 then
|
||||
line = line..s
|
||||
end
|
||||
end
|
||||
|
||||
local function putln (s)
|
||||
if #line > 0 then
|
||||
line = line..s
|
||||
append(lines,line)
|
||||
line = ''
|
||||
else
|
||||
append(lines,s)
|
||||
end
|
||||
end
|
||||
|
||||
local function eat_last_comma ()
|
||||
local n,lastch = #lines
|
||||
local lastch = lines[n]:sub(-1,-1)
|
||||
if lastch == ',' then
|
||||
lines[n] = lines[n]:sub(1,-2)
|
||||
end
|
||||
end
|
||||
|
||||
local function quote (s)
|
||||
return ('%q'):format(tostring(s))
|
||||
end
|
||||
|
||||
local function index (numkey,key)
|
||||
if not numkey then key = quote(key) end
|
||||
return '['..key..']'
|
||||
end
|
||||
|
||||
local writeit
|
||||
writeit = function (t,oldindent,indent)
|
||||
local tp = type(t)
|
||||
if tp ~= 'string' and tp ~= 'table' then
|
||||
putln(quote_if_necessary(tostring(t))..',')
|
||||
elseif tp == 'string' then
|
||||
if t:find('\n') then
|
||||
putln('[[\n'..t..']],')
|
||||
else
|
||||
putln(quote(t)..',')
|
||||
end
|
||||
elseif tp == 'table' then
|
||||
if tables[t] then
|
||||
putln('<cycle>,')
|
||||
return
|
||||
end
|
||||
tables[t] = true
|
||||
local newindent = indent..space
|
||||
putln('{')
|
||||
local used = {}
|
||||
if not not_clever then
|
||||
for i,val in ipairs(t) do
|
||||
put(indent)
|
||||
writeit(val,indent,newindent)
|
||||
used[i] = true
|
||||
end
|
||||
end
|
||||
for key,val in pairs(t) do
|
||||
local numkey = type(key) == 'number'
|
||||
if not_clever then
|
||||
key = tostring(key)
|
||||
put(indent..index(numkey,key)..set)
|
||||
writeit(val,indent,newindent)
|
||||
else
|
||||
if not numkey or not used[key] then -- non-array indices
|
||||
if numkey or not is_identifier(key) then
|
||||
key = index(numkey,key)
|
||||
end
|
||||
put(indent..key..set)
|
||||
writeit(val,indent,newindent)
|
||||
end
|
||||
end
|
||||
end
|
||||
eat_last_comma()
|
||||
putln(oldindent..'},')
|
||||
else
|
||||
putln(tostring(t)..',')
|
||||
end
|
||||
end
|
||||
writeit(tbl,'',space)
|
||||
eat_last_comma()
|
||||
return concat(lines,#space > 0 and '\n' or '')
|
||||
end
|
||||
|
||||
--- Dump a Lua table out to a file or stdout.
|
||||
-- @param t {table} The table to write to a file or stdout.
|
||||
-- @param ... {string} (optional) File name to write too. Defaults to writing
|
||||
-- to stdout.
|
||||
function pretty.dump (t,...)
|
||||
if select('#',...)==0 then
|
||||
print(pretty.write(t))
|
||||
return true
|
||||
else
|
||||
return utils.writefile(...,pretty.write(t))
|
||||
end
|
||||
end
|
||||
|
||||
local memp,nump = {'B','KiB','MiB','GiB'},{'','K','M','B'}
|
||||
|
||||
local comma
|
||||
function comma (val)
|
||||
local thou = math.floor(val/1000)
|
||||
if thou > 0 then return comma(thou)..','..(val % 1000)
|
||||
else return tostring(val) end
|
||||
end
|
||||
|
||||
--- format large numbers nicely for human consumption.
|
||||
-- @param num a number
|
||||
-- @param kind one of 'M' (memory in KiB etc), 'N' (postfixes are 'K','M' and 'B')
|
||||
-- and 'T' (use commas as thousands separator)
|
||||
-- @param prec number of digits to use for 'M' and 'N' (default 1)
|
||||
function pretty.number (num,kind,prec)
|
||||
local fmt = '%.'..(prec or 1)..'f%s'
|
||||
if kind == 'T' then
|
||||
return comma(num)
|
||||
else
|
||||
local postfixes, fact
|
||||
if kind == 'M' then
|
||||
fact = 1024
|
||||
postfixes = memp
|
||||
else
|
||||
fact = 1000
|
||||
postfixes = nump
|
||||
end
|
||||
local div = fact
|
||||
local k = 1
|
||||
while num >= div and k <= #postfixes do
|
||||
div = div * fact
|
||||
k = k + 1
|
||||
end
|
||||
div = div / fact
|
||||
if k > #postfixes then k = k - 1; div = div/fact end
|
||||
if k > 1 then
|
||||
return fmt:format(num/div,postfixes[k] or 'duh')
|
||||
else
|
||||
return num..postfixes[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pretty
|
||||
527
Utils/luarocks/share/lua/5.1/pl/seq.lua
Normal file
527
Utils/luarocks/share/lua/5.1/pl/seq.lua
Normal file
@@ -0,0 +1,527 @@
|
||||
--- Manipulating sequences as iterators.
|
||||
-- @class module
|
||||
-- @name pl.seq
|
||||
|
||||
local next,assert,type,pairs,tonumber,type,setmetatable,getmetatable,_G = next,assert,type,pairs,tonumber,type,setmetatable,getmetatable,_G
|
||||
local strfind = string.find
|
||||
local strmatch = string.match
|
||||
local format = string.format
|
||||
local mrandom = math.random
|
||||
local remove,tsort,tappend = table.remove,table.sort,table.insert
|
||||
local io = io
|
||||
local utils = require 'pl.utils'
|
||||
local function_arg = utils.function_arg
|
||||
local _List = utils.stdmt.List
|
||||
local _Map = utils.stdmt.Map
|
||||
local assert_arg = utils.assert_arg
|
||||
require 'debug'
|
||||
|
||||
--[[
|
||||
module("pl.seq",utils._module)
|
||||
]]
|
||||
|
||||
local seq = {}
|
||||
|
||||
-- given a number, return a function(y) which returns true if y > x
|
||||
-- @param x a number
|
||||
function seq.greater_than(x)
|
||||
return function(v)
|
||||
return tonumber(v) > x
|
||||
end
|
||||
end
|
||||
|
||||
-- given a number, returns a function(y) which returns true if y < x
|
||||
-- @param x a number
|
||||
function seq.less_than(x)
|
||||
return function(v)
|
||||
return tonumber(v) < x
|
||||
end
|
||||
end
|
||||
|
||||
-- given any value, return a function(y) which returns true if y == x
|
||||
-- @param x a value
|
||||
function seq.equal_to(x)
|
||||
if type(x) == "number" then
|
||||
return function(v)
|
||||
return tonumber(v) == x
|
||||
end
|
||||
else
|
||||
return function(v)
|
||||
return v == x
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- given a string, return a function(y) which matches y against the string.
|
||||
-- @param s a string
|
||||
function seq.matching(s)
|
||||
return function(v)
|
||||
return strfind(v,s)
|
||||
end
|
||||
end
|
||||
|
||||
--- sequence adaptor for a table. Note that if any generic function is
|
||||
-- passed a table, it will automatically use seq.list()
|
||||
-- @param t a list-like table
|
||||
-- @usage sum(list(t)) is the sum of all elements of t
|
||||
-- @usage for x in list(t) do...end
|
||||
function seq.list(t)
|
||||
assert_arg(1,t,'table')
|
||||
local key,value
|
||||
return function()
|
||||
key,value = next(t,key)
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
--- return the keys of the table.
|
||||
-- @param t a list-like table
|
||||
-- @return iterator over keys
|
||||
function seq.keys(t)
|
||||
assert_arg(1,t,'table')
|
||||
local key,value
|
||||
return function()
|
||||
key,value = next(t,key)
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
local list = seq.list
|
||||
local function default_iter(iter)
|
||||
if type(iter) == 'table' then return list(iter)
|
||||
else return iter end
|
||||
end
|
||||
|
||||
seq.iter = default_iter
|
||||
|
||||
--- create an iterator over a numerical range. Like the standard Python function xrange.
|
||||
-- @param start a number
|
||||
-- @param finish a number greater than start
|
||||
function seq.range(start,finish)
|
||||
local i = start - 1
|
||||
return function()
|
||||
i = i + 1
|
||||
if i > finish then return nil
|
||||
else return i end
|
||||
end
|
||||
end
|
||||
|
||||
-- count the number of elements in the sequence which satisfy the predicate
|
||||
-- @param iter a sequence
|
||||
-- @param condn a predicate function (must return either true or false)
|
||||
-- @param optional argument to be passed to predicate as second argument.
|
||||
function seq.count(iter,condn,arg)
|
||||
local i = 0
|
||||
seq.foreach(iter,function(val)
|
||||
if condn(val,arg) then i = i + 1 end
|
||||
end)
|
||||
return i
|
||||
end
|
||||
|
||||
--- return the minimum and the maximum value of the sequence.
|
||||
-- @param iter a sequence
|
||||
function seq.minmax(iter)
|
||||
local vmin,vmax = 1e70,-1e70
|
||||
for v in default_iter(iter) do
|
||||
v = tonumber(v)
|
||||
if v < vmin then vmin = v end
|
||||
if v > vmax then vmax = v end
|
||||
end
|
||||
return vmin,vmax
|
||||
end
|
||||
|
||||
--- return the sum and element count of the sequence.
|
||||
-- @param iter a sequence
|
||||
-- @param fn an optional function to apply to the values
|
||||
function seq.sum(iter,fn)
|
||||
local s = 0
|
||||
local i = 0
|
||||
for v in default_iter(iter) do
|
||||
if fn then v = fn(v) end
|
||||
s = s + v
|
||||
i = i + 1
|
||||
end
|
||||
return s,i
|
||||
end
|
||||
|
||||
--- create a table from the sequence. (This will make the result a List.)
|
||||
-- @param iter a sequence
|
||||
-- @return a List
|
||||
-- @usage copy(list(ls)) is equal to ls
|
||||
-- @usage copy(list {1,2,3}) == List{1,2,3}
|
||||
function seq.copy(iter)
|
||||
local res = {}
|
||||
for v in default_iter(iter) do
|
||||
tappend(res,v)
|
||||
end
|
||||
setmetatable(res,_List)
|
||||
return res
|
||||
end
|
||||
|
||||
--- create a table of pairs from the double-valued sequence.
|
||||
-- @param iter a double-valued sequence
|
||||
-- @param i1 used to capture extra iterator values
|
||||
-- @param i2 as with pairs & ipairs
|
||||
-- @usage copy2(ipairs{10,20,30}) == {{1,10},{2,20},{3,30}}
|
||||
-- @return a list-like table
|
||||
function seq.copy2 (iter,i1,i2)
|
||||
local res = {}
|
||||
for v1,v2 in iter,i1,i2 do
|
||||
tappend(res,{v1,v2})
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- create a table of 'tuples' from a multi-valued sequence.
|
||||
-- A generalization of copy2 above
|
||||
-- @param iter a multiple-valued sequence
|
||||
-- @return a list-like table
|
||||
function seq.copy_tuples (iter)
|
||||
iter = default_iter(iter)
|
||||
local res = {}
|
||||
local row = {iter()}
|
||||
while #row > 0 do
|
||||
tappend(res,row)
|
||||
row = {iter()}
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- return an iterator of random numbers.
|
||||
-- @param n the length of the sequence
|
||||
-- @param l same as the first optional argument to math.random
|
||||
-- @param u same as the second optional argument to math.random
|
||||
-- @return a sequnce
|
||||
function seq.random(n,l,u)
|
||||
local rand
|
||||
assert(type(n) == 'number')
|
||||
if u then
|
||||
rand = function() return mrandom(l,u) end
|
||||
elseif l then
|
||||
rand = function() return mrandom(l) end
|
||||
else
|
||||
rand = mrandom
|
||||
end
|
||||
|
||||
return function()
|
||||
if n == 0 then return nil
|
||||
else
|
||||
n = n - 1
|
||||
return rand()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- return an iterator to the sorted elements of a sequence.
|
||||
-- @param iter a sequence
|
||||
-- @param comp an optional comparison function (comp(x,y) is true if x < y)
|
||||
function seq.sort(iter,comp)
|
||||
local t = seq.copy(iter)
|
||||
tsort(t,comp)
|
||||
return list(t)
|
||||
end
|
||||
|
||||
--- return an iterator which returns elements of two sequences.
|
||||
-- @param iter1 a sequence
|
||||
-- @param iter2 a sequence
|
||||
-- @usage for x,y in seq.zip(ls1,ls2) do....end
|
||||
function seq.zip(iter1,iter2)
|
||||
iter1 = default_iter(iter1)
|
||||
iter2 = default_iter(iter2)
|
||||
return function()
|
||||
return iter1(),iter2()
|
||||
end
|
||||
end
|
||||
|
||||
--- A table where the key/values are the values and value counts of the sequence.
|
||||
-- This version works with 'hashable' values like strings and numbers. <br>
|
||||
-- pl.tablex.count_map is more general.
|
||||
-- @param iter a sequence
|
||||
-- @return a map-like table
|
||||
-- @return a table
|
||||
-- @see pl.tablex.count_map
|
||||
function seq.count_map(iter)
|
||||
local t = {}
|
||||
local v
|
||||
for s in default_iter(iter) do
|
||||
v = t[s]
|
||||
if v then t[s] = v + 1
|
||||
else t[s] = 1 end
|
||||
end
|
||||
return setmetatable(t,_Map)
|
||||
end
|
||||
|
||||
-- given a sequence, return all the unique values in that sequence.
|
||||
-- @param iter a sequence
|
||||
-- @param returns_table true if we return a table, not a sequence
|
||||
-- @return a sequence or a table; defaults to a sequence.
|
||||
function seq.unique(iter,returns_table)
|
||||
local t = seq.count_map(iter)
|
||||
local res = {}
|
||||
for k in pairs(t) do tappend(res,k) end
|
||||
table.sort(res)
|
||||
if returns_table then
|
||||
return res
|
||||
else
|
||||
return list(res)
|
||||
end
|
||||
end
|
||||
|
||||
-- print out a sequence @iter, with a separator @sep (default space)
|
||||
-- and maximum number of values per line @nfields (default 7)
|
||||
-- @fmt is an optional format function to create a representation of each value.
|
||||
function seq.printall(iter,sep,nfields,fmt)
|
||||
local write = io.write
|
||||
if not sep then sep = ' ' end
|
||||
if not nfields then
|
||||
if sep == '\n' then nfields = 1e30
|
||||
else nfields = 7 end
|
||||
end
|
||||
if fmt then
|
||||
local fstr = fmt
|
||||
fmt = function(v) return format(fstr,v) end
|
||||
end
|
||||
local k = 1
|
||||
for v in default_iter(iter) do
|
||||
if fmt then v = fmt(v) end
|
||||
if k < nfields then
|
||||
write(v,sep)
|
||||
k = k + 1
|
||||
else
|
||||
write(v,'\n')
|
||||
k = 1
|
||||
end
|
||||
end
|
||||
write '\n'
|
||||
end
|
||||
|
||||
-- return an iterator running over every element of two sequences (concatenation).
|
||||
-- @param iter1 a sequence
|
||||
-- @param iter2 a sequence
|
||||
function seq.splice(iter1,iter2)
|
||||
iter1 = default_iter(iter1)
|
||||
iter2 = default_iter(iter2)
|
||||
local iter = iter1
|
||||
return function()
|
||||
local ret = iter()
|
||||
if ret == nil then
|
||||
if iter == iter1 then
|
||||
iter = iter2
|
||||
return iter()
|
||||
else return nil end
|
||||
else
|
||||
return ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- return a sequence where every element of a sequence has been transformed
|
||||
-- by a function. If you don't supply an argument, then the function will
|
||||
-- receive both values of a double-valued sequence, otherwise behaves rather like
|
||||
-- tablex.map.
|
||||
-- @param fn a function to apply to elements; may take two arguments
|
||||
-- @param iter a sequence of one or two values
|
||||
-- @param arg optional argument to pass to function.
|
||||
function seq.map(fn,iter,arg)
|
||||
fn = function_arg(1,fn)
|
||||
iter = default_iter(iter)
|
||||
return function()
|
||||
local v1,v2 = iter()
|
||||
if v1 == nil then return nil end
|
||||
if arg then return fn(v1,arg) or false
|
||||
else return fn(v1,v2) or false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- filter a sequence using a predicate function
|
||||
-- @param iter a sequence of one or two values
|
||||
-- @param pred a boolean function; may take two arguments
|
||||
-- @param arg optional argument to pass to function.
|
||||
function seq.filter (iter,pred,arg)
|
||||
pred = function_arg(2,pred)
|
||||
return function ()
|
||||
local v1,v2
|
||||
while true do
|
||||
v1,v2 = iter()
|
||||
if v1 == nil then return nil end
|
||||
if arg then
|
||||
if pred(v1,arg) then return v1,v2 end
|
||||
else
|
||||
if pred(v1,v2) then return v1,v2 end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- 'reduce' a sequence using a binary function.
|
||||
-- @param fun a function of two arguments
|
||||
-- @param iter a sequence
|
||||
-- @param oldval optional initial value
|
||||
-- @usage seq.reduce(operator.add,seq.list{1,2,3,4}) == 10
|
||||
-- @usage seq.reduce('-',{1,2,3,4,5}) == -13
|
||||
function seq.reduce (fun,iter,oldval)
|
||||
fun = function_arg(1,fun)
|
||||
iter = default_iter(iter)
|
||||
if not oldval then
|
||||
oldval = iter()
|
||||
end
|
||||
local val = oldval
|
||||
for v in iter do
|
||||
val = fun(val,v)
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
--- take the first n values from the sequence.
|
||||
-- @param iter a sequence of one or two values
|
||||
-- @param n number of items to take
|
||||
-- @return a sequence of at most n items
|
||||
function seq.take (iter,n)
|
||||
local i = 1
|
||||
iter = default_iter(iter)
|
||||
return function()
|
||||
if i > n then return end
|
||||
local val1,val2 = iter()
|
||||
if not val1 then return end
|
||||
i = i + 1
|
||||
return val1,val2
|
||||
end
|
||||
end
|
||||
|
||||
--- skip the first n values of a sequence
|
||||
-- @param iter a sequence of one or more values
|
||||
-- @param n number of items to skip
|
||||
function seq.skip (iter,n)
|
||||
n = n or 1
|
||||
for i = 1,n do iter() end
|
||||
return iter
|
||||
end
|
||||
|
||||
--- a sequence with a sequence count and the original value. <br>
|
||||
-- enum(copy(ls)) is a roundabout way of saying ipairs(ls).
|
||||
-- @param iter a single or double valued sequence
|
||||
-- @return sequence of (i,v), i = 1..n and v is from iter.
|
||||
function seq.enum (iter)
|
||||
local i = 0
|
||||
iter = default_iter(iter)
|
||||
return function ()
|
||||
local val1,val2 = iter()
|
||||
if not val1 then return end
|
||||
i = i + 1
|
||||
return i,val1,val2
|
||||
end
|
||||
end
|
||||
|
||||
--- map using a named method over a sequence.
|
||||
-- @param iter a sequence
|
||||
-- @param name the method name
|
||||
-- @param arg1 optional first extra argument
|
||||
-- @param arg2 optional second extra argument
|
||||
function seq.mapmethod (iter,name,arg1,arg2)
|
||||
iter = default_iter(iter)
|
||||
return function()
|
||||
local val = iter()
|
||||
if not val then return end
|
||||
local fn = val[name]
|
||||
if not fn then error(type(val).." does not have method "..name) end
|
||||
return fn(val,arg1,arg2)
|
||||
end
|
||||
end
|
||||
|
||||
--- a sequence of (last,current) values from another sequence.
|
||||
-- This will return S(i-1),S(i) if given S(i)
|
||||
-- @param iter a sequence
|
||||
function seq.last (iter)
|
||||
iter = default_iter(iter)
|
||||
local l = iter()
|
||||
if l == nil then return nil end
|
||||
return function ()
|
||||
local val,ll
|
||||
val = iter()
|
||||
if val == nil then return nil end
|
||||
ll = l
|
||||
l = val
|
||||
return val,ll
|
||||
end
|
||||
end
|
||||
|
||||
--- call the function on each element of the sequence.
|
||||
-- @param iter a sequence with up to 3 values
|
||||
-- @param fn a function
|
||||
function seq.foreach(iter,fn)
|
||||
fn = function_arg(2,fn)
|
||||
for i1,i2,i3 in default_iter(iter) do fn(i1,i2,i3) end
|
||||
end
|
||||
|
||||
---------------------- Sequence Adapters ---------------------
|
||||
|
||||
local SMT
|
||||
local callable = utils.is_callable
|
||||
|
||||
local function SW (iter,...)
|
||||
if callable(iter) then
|
||||
return setmetatable({iter=iter},SMT)
|
||||
else
|
||||
return iter,...
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- can't directly look these up in seq because of the wrong argument order...
|
||||
local map,reduce,mapmethod = seq.map, seq.reduce, seq.mapmethod
|
||||
local overrides = {
|
||||
map = function(self,fun,arg)
|
||||
return map(fun,self,arg)
|
||||
end,
|
||||
reduce = function(self,fun)
|
||||
return reduce(fun,self)
|
||||
end
|
||||
}
|
||||
|
||||
SMT = {
|
||||
__index = function (tbl,key)
|
||||
local s = overrides[key] or seq[key]
|
||||
if s then
|
||||
return function(sw,...) return SW(s(sw.iter,...)) end
|
||||
else
|
||||
return function(sw,...) return SW(mapmethod(sw.iter,key,...)) end
|
||||
end
|
||||
end,
|
||||
__call = function (sw)
|
||||
return sw.iter()
|
||||
end,
|
||||
}
|
||||
|
||||
setmetatable(seq,{
|
||||
__call = function(tbl,iter)
|
||||
if not callable(iter) then
|
||||
if type(iter) == 'table' then iter = seq.list(iter)
|
||||
else return iter
|
||||
end
|
||||
end
|
||||
return setmetatable({iter=iter},SMT)
|
||||
end
|
||||
})
|
||||
|
||||
--- create a wrapped iterator over all lines in the file.
|
||||
-- @param f either a filename or nil (for standard input)
|
||||
-- @return a sequence wrapper
|
||||
function seq.lines (f)
|
||||
local iter = f and io.lines(f) or io.lines()
|
||||
return SW(iter)
|
||||
end
|
||||
|
||||
function seq.import ()
|
||||
_G.debug.setmetatable(function() end,{
|
||||
__index = function(tbl,key)
|
||||
local s = overrides[key] or seq[key]
|
||||
if s then return s
|
||||
else
|
||||
return function(s,...) return seq.mapmethod(s,key,...) end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return seq
|
||||
335
Utils/luarocks/share/lua/5.1/pl/sip.lua
Normal file
335
Utils/luarocks/share/lua/5.1/pl/sip.lua
Normal file
@@ -0,0 +1,335 @@
|
||||
--- Simple Input Patterns (SIP). <p>
|
||||
-- SIP patterns start with '$', then a
|
||||
-- one-letter type, and then an optional variable in curly braces. <p>
|
||||
-- Example:
|
||||
-- <pre class=example>
|
||||
-- sip.match('$v=$q','name="dolly"',res)
|
||||
-- ==> res=={'name','dolly'}
|
||||
-- sip.match('($q{first},$q{second})','("john","smith")',res)
|
||||
-- ==> res=={second='smith',first='john'}
|
||||
-- </pre>
|
||||
-- <pre>
|
||||
-- <b>Type names</b>
|
||||
-- v identifier
|
||||
-- i integer
|
||||
-- f floating-point
|
||||
-- q quoted string
|
||||
-- ([{< match up to closing bracket
|
||||
-- </pre>
|
||||
-- <p>
|
||||
-- See <a href="../../index.html#sip">the Guide</a>
|
||||
-- @class module
|
||||
-- @name pl.sip
|
||||
|
||||
local append,concat = table.insert,table.concat
|
||||
local concat = table.concat
|
||||
local ipairs,loadstring,type,unpack = ipairs,loadstring,type,unpack
|
||||
local io,_G = io,_G
|
||||
local print,rawget = print,rawget
|
||||
|
||||
local patterns = {
|
||||
FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*',
|
||||
INTEGER = '[+%-%d]%d*',
|
||||
IDEN = '[%a_][%w_]*',
|
||||
FILE = '[%a%.\\][:%][%w%._%-\\]*'
|
||||
}
|
||||
|
||||
local function assert_arg(idx,val,tp)
|
||||
if type(val) ~= tp then
|
||||
error("argument "..idx.." must be "..tp, 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
module ('pl.sip',utils._module)
|
||||
]]
|
||||
|
||||
local sip = {}
|
||||
|
||||
local brackets = {['<'] = '>', ['('] = ')', ['{'] = '}', ['['] = ']' }
|
||||
local stdclasses = {a=1,c=0,d=1,l=1,p=0,u=1,w=1,x=1,s=0}
|
||||
|
||||
local _patterns = {}
|
||||
|
||||
|
||||
local function group(s)
|
||||
return '('..s..')'
|
||||
end
|
||||
|
||||
-- escape all magic characters except $, which has special meaning
|
||||
-- Also, un-escape any characters after $, so $( passes through as is.
|
||||
local function escape (spec)
|
||||
--_G.print('spec',spec)
|
||||
local res = spec:gsub('[%-%.%+%[%]%(%)%^%%%?%*]','%%%1'):gsub('%$%%(%S)','$%1')
|
||||
--_G.print('res',res)
|
||||
return res
|
||||
end
|
||||
|
||||
local function imcompressible (s)
|
||||
return s:gsub('%s+','\001')
|
||||
end
|
||||
|
||||
-- [handling of spaces in patterns]
|
||||
-- spaces may be 'compressed' (i.e will match zero or more spaces)
|
||||
-- unless this occurs within a number or an identifier. So we mark
|
||||
-- the four possible imcompressible patterns first and then replace.
|
||||
-- The possible alnum patterns are v,f,a,d,x,l and u.
|
||||
local function compress_spaces (s)
|
||||
s = s:gsub('%$[vifadxlu]%s+%$[vfadxlu]',imcompressible)
|
||||
s = s:gsub('[%w_]%s+[%w_]',imcompressible)
|
||||
s = s:gsub('[%w_]%s+%$[vfadxlu]',imcompressible)
|
||||
s = s:gsub('%$[vfadxlu]%s+[%w_]',imcompressible)
|
||||
s = s:gsub('%s+','%%s*')
|
||||
s = s:gsub('\001',' ')
|
||||
return s
|
||||
end
|
||||
|
||||
--- convert a SIP pattern into the equivalent Lua string pattern.
|
||||
-- @param spec a SIP pattern
|
||||
-- @param options a table; only the <code>at_start</code> field is
|
||||
-- currently meaningful and esures that the pattern is anchored
|
||||
-- at the start of the string.
|
||||
-- @return a Lua string pattern.
|
||||
function sip.create_pattern (spec,options)
|
||||
assert_arg(1,spec,'string')
|
||||
local fieldnames,fieldtypes = {},{}
|
||||
|
||||
if type(spec) == 'string' then
|
||||
spec = escape(spec)
|
||||
else
|
||||
local res = {}
|
||||
for i,s in ipairs(spec) do
|
||||
res[i] = escape(s)
|
||||
end
|
||||
spec = concat(res,'.-')
|
||||
end
|
||||
|
||||
local kount = 1
|
||||
|
||||
local function addfield (name,type)
|
||||
if not name then name = kount end
|
||||
if fieldnames then append(fieldnames,name) end
|
||||
if fieldtypes then fieldtypes[name] = type end
|
||||
kount = kount + 1
|
||||
end
|
||||
|
||||
local named_vars, pattern
|
||||
named_vars = spec:find('{%a+}')
|
||||
pattern = '%$%S'
|
||||
|
||||
if options and options.at_start then
|
||||
spec = '^'..spec
|
||||
end
|
||||
if spec:sub(-1,-1) == '$' then
|
||||
spec = spec:sub(1,-2)..'$r'
|
||||
if named_vars then spec = spec..'{rest}' end
|
||||
end
|
||||
|
||||
|
||||
local names
|
||||
|
||||
if named_vars then
|
||||
names = {}
|
||||
spec = spec:gsub('{(%a+)}',function(name)
|
||||
append(names,name)
|
||||
return ''
|
||||
end)
|
||||
end
|
||||
spec = compress_spaces(spec)
|
||||
|
||||
local k = 1
|
||||
local err
|
||||
local r = (spec:gsub(pattern,function(s)
|
||||
local type,name
|
||||
type = s:sub(2,2)
|
||||
if names then name = names[k]; k=k+1 end
|
||||
-- this kludge is necessary because %q generates two matches, and
|
||||
-- we want to ignore the first. Not a problem for named captures.
|
||||
if not names and type == 'q' then
|
||||
addfield(nil,'Q')
|
||||
else
|
||||
addfield(name,type)
|
||||
end
|
||||
local res
|
||||
if type == 'v' then
|
||||
res = group(patterns.IDEN)
|
||||
elseif type == 'i' then
|
||||
res = group(patterns.INTEGER)
|
||||
elseif type == 'f' then
|
||||
res = group(patterns.FLOAT)
|
||||
elseif type == 'r' then
|
||||
res = '(%S.*)'
|
||||
elseif type == 'q' then
|
||||
-- some Lua pattern matching voodoo; we want to match '...' as
|
||||
-- well as "...", and can use the fact that %n will match a
|
||||
-- previous capture. Adding the extra field above comes from needing
|
||||
-- to accomodate the extra spurious match (which is either ' or ")
|
||||
addfield(name,type)
|
||||
res = '(["\'])(.-)%'..(kount-2)
|
||||
elseif type == 'p' then
|
||||
res = '([%a]?[:]?[\\/%.%w_]+)'
|
||||
else
|
||||
local endbracket = brackets[type]
|
||||
if endbracket then
|
||||
res = '(%b'..type..endbracket..')'
|
||||
elseif stdclasses[type] or stdclasses[type:lower()] then
|
||||
res = '(%'..type..'+)'
|
||||
else
|
||||
err = "unknown format type or character class"
|
||||
end
|
||||
end
|
||||
return res
|
||||
end))
|
||||
--print(r,err)
|
||||
if err then
|
||||
return nil,err
|
||||
else
|
||||
return r,fieldnames,fieldtypes
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function tnumber (s)
|
||||
return s == 'd' or s == 'i' or s == 'f'
|
||||
end
|
||||
|
||||
function sip.create_spec_fun(spec,options)
|
||||
local fieldtypes,fieldnames
|
||||
local ls = {}
|
||||
spec,fieldnames,fieldtypes = sip.create_pattern(spec,options)
|
||||
if not spec then return spec,fieldnames end
|
||||
local named_vars = type(fieldnames[1]) == 'string'
|
||||
for i = 1,#fieldnames do
|
||||
append(ls,'mm'..i)
|
||||
end
|
||||
local fun = ('return (function(s,res)\n\tlocal %s = s:match(%q)\n'):format(concat(ls,','),spec)
|
||||
fun = fun..'\tif not mm1 then return false end\n'
|
||||
local k=1
|
||||
for i,f in ipairs(fieldnames) do
|
||||
if f ~= '_' then
|
||||
local var = 'mm'..i
|
||||
if tnumber(fieldtypes[f]) then
|
||||
var = 'tonumber('..var..')'
|
||||
elseif brackets[fieldtypes[f]] then
|
||||
var = var..':sub(2,-2)'
|
||||
end
|
||||
if named_vars then
|
||||
fun = ('%s\tres.%s = %s\n'):format(fun,f,var)
|
||||
else
|
||||
if fieldtypes[f] ~= 'Q' then -- we skip the string-delim capture
|
||||
fun = ('%s\tres[%d] = %s\n'):format(fun,k,var)
|
||||
k = k + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return fun..'\treturn true\nend)\n', named_vars
|
||||
end
|
||||
|
||||
--- convert a SIP pattern into a matching function.
|
||||
-- The returned function takes two arguments, the line and an empty table.
|
||||
-- If the line matched the pattern, then this function return true
|
||||
-- and the table is filled with field-value pairs.
|
||||
-- @param spec a SIP pattern
|
||||
-- @param options optional table; {anywhere=true} will stop pattern anchoring at start
|
||||
-- @return a function if successful, or nil,<error>
|
||||
function sip.compile(spec,options)
|
||||
assert_arg(1,spec,'string')
|
||||
local fun,names = sip.create_spec_fun(spec,options)
|
||||
if not fun then return nil,names end
|
||||
if rawget(_G,'_DEBUG') then print(fun) end
|
||||
local chunk,err = loadstring(fun,'tmp')
|
||||
if err then return nil,err end
|
||||
return chunk(),names
|
||||
end
|
||||
|
||||
local cache = {}
|
||||
|
||||
--- match a SIP pattern against a string.
|
||||
-- @param spec a SIP pattern
|
||||
-- @param line a string
|
||||
-- @param res a table to receive values
|
||||
-- @param options (optional) option table
|
||||
-- @return true or false
|
||||
function sip.match (spec,line,res,options)
|
||||
assert_arg(1,spec,'string')
|
||||
assert_arg(2,line,'string')
|
||||
assert_arg(3,res,'table')
|
||||
if not cache[spec] then
|
||||
cache[spec] = sip.compile(spec,options)
|
||||
end
|
||||
return cache[spec](line,res)
|
||||
end
|
||||
|
||||
--- match a SIP pattern against the start of a string.
|
||||
-- @param spec a SIP pattern
|
||||
-- @param line a string
|
||||
-- @param res a table to receive values
|
||||
-- @return true or false
|
||||
function sip.match_at_start (spec,line,res)
|
||||
return sip.match(spec,line,res,{at_start=true})
|
||||
end
|
||||
|
||||
--- given a pattern and a file object, return an iterator over the results
|
||||
-- @param spec a SIP pattern
|
||||
-- @param f a file - use standard input if not specified.
|
||||
function sip.fields (spec,f)
|
||||
assert_arg(1,spec,'string')
|
||||
f = f or io.stdin
|
||||
local fun,err = sip.compile(spec)
|
||||
if not fun then return nil,err end
|
||||
local res = {}
|
||||
return function()
|
||||
while true do
|
||||
local line = f:read()
|
||||
if not line then return end
|
||||
if fun(line,res) then
|
||||
local values = res
|
||||
res = {}
|
||||
return unpack(values)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- register a match which will be used in the read function.
|
||||
-- @param spec a SIP pattern
|
||||
-- @param fun a function to be called with the results of the match
|
||||
-- @see read
|
||||
function sip.pattern (spec,fun)
|
||||
assert_arg(1,spec,'string')
|
||||
local pat,named = sip.compile(spec)
|
||||
append(_patterns,{pat=pat,named=named,callback=fun or false})
|
||||
end
|
||||
|
||||
--- enter a loop which applies all registered matches to the input file.
|
||||
-- @param f a file object; if nil, then io.stdin is assumed.
|
||||
function sip.read (f)
|
||||
local owned,err
|
||||
f = f or io.stdin
|
||||
if type(f) == 'string' then
|
||||
f,err = io.open(f)
|
||||
if not f then return nil,err end
|
||||
owned = true
|
||||
end
|
||||
local res = {}
|
||||
for line in f:lines() do
|
||||
for _,item in ipairs(_patterns) do
|
||||
if item.pat(line,res) then
|
||||
if item.callback then
|
||||
if item.named then
|
||||
item.callback(res)
|
||||
else
|
||||
item.callback(unpack(res))
|
||||
end
|
||||
end
|
||||
res = {}
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if owned then f:close() end
|
||||
end
|
||||
|
||||
return sip
|
||||
71
Utils/luarocks/share/lua/5.1/pl/strict.lua
Normal file
71
Utils/luarocks/share/lua/5.1/pl/strict.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
--- Checks uses of undeclared global variables.
|
||||
-- All global variables must be 'declared' through a regular assignment
|
||||
-- (even assigning nil will do) in a main chunk before being used
|
||||
-- anywhere or assigned to inside a function.
|
||||
-- @class module
|
||||
-- @name pl.strict
|
||||
|
||||
require 'debug'
|
||||
local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget
|
||||
local handler,hooked
|
||||
|
||||
local mt = getmetatable(_G)
|
||||
if mt == nil then
|
||||
mt = {}
|
||||
setmetatable(_G, mt)
|
||||
elseif mt.hook then
|
||||
hooked = true
|
||||
end
|
||||
|
||||
-- predeclaring _PROMPT keeps the Lua Interpreter happy
|
||||
mt.__declared = {_PROMPT=true}
|
||||
|
||||
local function what ()
|
||||
local d = getinfo(3, "S")
|
||||
return d and d.what or "C"
|
||||
end
|
||||
|
||||
mt.__newindex = function (t, n, v)
|
||||
if not mt.__declared[n] then
|
||||
local w = what()
|
||||
if w ~= "main" and w ~= "C" then
|
||||
error("assign to undeclared variable '"..n.."'", 2)
|
||||
end
|
||||
mt.__declared[n] = true
|
||||
end
|
||||
rawset(t, n, v)
|
||||
end
|
||||
|
||||
handler = function(t,n)
|
||||
if not mt.__declared[n] and what() ~= "C" then
|
||||
error("variable '"..n.."' is not declared", 2)
|
||||
end
|
||||
return rawget(t, n)
|
||||
end
|
||||
|
||||
function package.strict (mod)
|
||||
local mt = getmetatable(mod)
|
||||
if mt == nil then
|
||||
mt = {}
|
||||
setmetatable(mod, mt)
|
||||
end
|
||||
mt.__declared = {}
|
||||
mt.__newindex = function(t, n, v)
|
||||
mt.__declared[n] = true
|
||||
rawset(t, n, v)
|
||||
end
|
||||
mt.__index = function(t,n)
|
||||
if not mt.__declared[n] then
|
||||
error("variable '"..n.."' is not declared", 2)
|
||||
end
|
||||
return rawget(t, n)
|
||||
end
|
||||
end
|
||||
|
||||
if not hooked then
|
||||
mt.__index = handler
|
||||
else
|
||||
mt.hook(handler)
|
||||
end
|
||||
|
||||
|
||||
144
Utils/luarocks/share/lua/5.1/pl/stringio.lua
Normal file
144
Utils/luarocks/share/lua/5.1/pl/stringio.lua
Normal file
@@ -0,0 +1,144 @@
|
||||
--- Reading and writing strings using file-like objects. <br>
|
||||
-- <pre class=example>
|
||||
-- f = stringio.open(text)
|
||||
-- l1 = f:read() -- read first line
|
||||
-- n,m = f:read ('*n','*n') -- read two numbers
|
||||
-- for line in f:lines() do print(line) end -- iterate over all lines
|
||||
-- f = stringio.create()
|
||||
-- f:write('hello')
|
||||
-- f:write('dolly')
|
||||
-- assert(f:value(),'hellodolly')
|
||||
-- </pre>
|
||||
-- See <a href="../../index.html#stringio">the Guide</a>.
|
||||
-- @class module
|
||||
-- @name pl.stringio
|
||||
|
||||
local getmetatable,tostring,unpack,tonumber = getmetatable,tostring,unpack,tonumber
|
||||
local concat,append = table.concat,table.insert
|
||||
|
||||
local stringio = {}
|
||||
|
||||
--- Writer class
|
||||
local SW = {}
|
||||
SW.__index = SW
|
||||
|
||||
local function xwrite(self,...)
|
||||
local args = {...} --arguments may not be nil!
|
||||
for i = 1, #args do
|
||||
append(self.tbl,args[i])
|
||||
end
|
||||
end
|
||||
|
||||
function SW:write(arg1,arg2,...)
|
||||
if arg2 then
|
||||
xwrite(self,arg1,arg2,...)
|
||||
else
|
||||
append(self.tbl,arg1)
|
||||
end
|
||||
end
|
||||
|
||||
function SW:writef(fmt,...)
|
||||
self:write(fmt:format(...))
|
||||
end
|
||||
|
||||
function SW:value()
|
||||
return concat(self.tbl)
|
||||
end
|
||||
|
||||
function SW:close() -- for compatibility only
|
||||
end
|
||||
|
||||
function SW:seek()
|
||||
end
|
||||
|
||||
--- Reader class
|
||||
local SR = {}
|
||||
SR.__index = SR
|
||||
|
||||
function SR:_read(fmt)
|
||||
local i,str = self.i,self.str
|
||||
local sz = #str
|
||||
if i >= sz then return nil end
|
||||
local res
|
||||
if fmt == nil or fmt == '*l' then
|
||||
local idx = str:find('\n',i) or (sz+1)
|
||||
res = str:sub(i,idx-1)
|
||||
self.i = idx+1
|
||||
elseif fmt == '*a' then
|
||||
res = str:sub(i)
|
||||
self.i = sz
|
||||
elseif fmt == '*n' then
|
||||
local _,i2,i2,idx
|
||||
_,idx = str:find ('%s*%d+',i)
|
||||
_,i2 = str:find ('%.%d+',idx+1)
|
||||
if i2 then idx = i2 end
|
||||
_,i2 = str:find ('[eE][%+%-]*%d+',idx+1)
|
||||
if i2 then idx = i2 end
|
||||
local val = str:sub(i,idx)
|
||||
res = tonumber(val)
|
||||
self.i = idx+1
|
||||
elseif type(fmt) == 'number' then
|
||||
res = str:sub(i,i+fmt-1)
|
||||
self.i = i + fmt
|
||||
else
|
||||
error("bad read format",2)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function SR:read(...)
|
||||
local fmts = {...}
|
||||
if #fmts <= 1 then
|
||||
return self:_read(fmts[1])
|
||||
else
|
||||
local res = {}
|
||||
for i = 1, #fmts do
|
||||
res[i] = self:_read(fmts[i])
|
||||
end
|
||||
return unpack(res)
|
||||
end
|
||||
end
|
||||
|
||||
function SR:seek(whence,offset)
|
||||
local base
|
||||
whence = whence or 'cur'
|
||||
offset = offset or 0
|
||||
if whence == 'set' then
|
||||
base = 1
|
||||
elseif whence == 'cur' then
|
||||
base = self.i
|
||||
elseif whence == 'end' then
|
||||
base = #self.str
|
||||
end
|
||||
self.i = base + offset
|
||||
return self.i
|
||||
end
|
||||
|
||||
function SR:lines()
|
||||
return function()
|
||||
return self:read()
|
||||
end
|
||||
end
|
||||
|
||||
function SR:close() -- for compatibility only
|
||||
end
|
||||
|
||||
--- create a file-like object which can be used to construct a string.
|
||||
-- The resulting object has an extra <code>value()</code> method for
|
||||
-- retrieving the string value.
|
||||
-- @usage f = create(); f:write('hello, dolly\n'); print(f:value())
|
||||
function stringio.create()
|
||||
return setmetatable({tbl={}},SW)
|
||||
end
|
||||
|
||||
--- create a file-like object for reading from a given string.
|
||||
-- @param s The input string.
|
||||
function stringio.open(s)
|
||||
return setmetatable({str=s,i=1},SR)
|
||||
end
|
||||
|
||||
function stringio.lines(s)
|
||||
return stringio.open(s):lines()
|
||||
end
|
||||
|
||||
return stringio
|
||||
441
Utils/luarocks/share/lua/5.1/pl/stringx.lua
Normal file
441
Utils/luarocks/share/lua/5.1/pl/stringx.lua
Normal file
@@ -0,0 +1,441 @@
|
||||
--- Python-style string library. <p>
|
||||
-- see 3.6.1 of the Python reference. <p>
|
||||
-- If you want to make these available as string methods, then say
|
||||
-- <code>stringx.import()</code> to bring them into the standard <code>string</code>
|
||||
-- table.
|
||||
-- @class module
|
||||
-- @name pl.stringx
|
||||
local string = string
|
||||
local find = string.find
|
||||
local type,setmetatable,getmetatable,ipairs,unpack = type,setmetatable,getmetatable,ipairs,unpack
|
||||
local error,tostring = error,tostring
|
||||
local gsub = string.gsub
|
||||
local rep = string.rep
|
||||
local sub = string.sub
|
||||
local concat = table.concat
|
||||
local utils = require 'pl.utils'
|
||||
local escape = utils.escape
|
||||
local ceil = math.ceil
|
||||
local _G = _G
|
||||
local assert_arg,usplit,list_MT = utils.assert_arg,utils.split,utils.stdmt.List
|
||||
local lstrip
|
||||
|
||||
local function assert_string (n,s)
|
||||
assert_arg(n,s,'string')
|
||||
end
|
||||
|
||||
local function non_empty(s)
|
||||
return #s > 0
|
||||
end
|
||||
|
||||
local function assert_nonempty_string(n,s)
|
||||
assert_arg(n,s,'string',non_empty,'must be a non-empty string')
|
||||
end
|
||||
|
||||
--[[
|
||||
module ('pl.stringx',utils._module)
|
||||
]]
|
||||
|
||||
local stringx = {}
|
||||
|
||||
--- does s only contain alphabetic characters?.
|
||||
-- @param s a string
|
||||
function stringx.isalpha(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^%a+$') == 1
|
||||
end
|
||||
|
||||
--- does s only contain digits?.
|
||||
-- @param s a string
|
||||
function stringx.isdigit(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^%d+$') == 1
|
||||
end
|
||||
|
||||
--- does s only contain alphanumeric characters?.
|
||||
-- @param s a string
|
||||
function stringx.isalnum(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^%w+$') == 1
|
||||
end
|
||||
|
||||
--- does s only contain spaces?.
|
||||
-- @param s a string
|
||||
function stringx.isspace(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^%s+$') == 1
|
||||
end
|
||||
|
||||
--- does s only contain lower case characters?.
|
||||
-- @param s a string
|
||||
function stringx.islower(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^[%l%s]+$') == 1
|
||||
end
|
||||
|
||||
--- does s only contain upper case characters?.
|
||||
-- @param s a string
|
||||
function stringx.isupper(s)
|
||||
assert_string(1,s)
|
||||
return find(s,'^[%u%s]+$') == 1
|
||||
end
|
||||
|
||||
--- concatenate the strings using this string as a delimiter.
|
||||
-- @param self the string
|
||||
-- @param seq a table of strings or numbers
|
||||
-- @usage (' '):join {1,2,3} == '1 2 3'
|
||||
function stringx.join (self,seq)
|
||||
assert_string(1,self)
|
||||
return concat(seq,self)
|
||||
end
|
||||
|
||||
--- does string start with the substring?.
|
||||
-- @param self the string
|
||||
-- @param s2 a string
|
||||
function stringx.startswith(self,s2)
|
||||
assert_string(1,self)
|
||||
assert_string(2,s2)
|
||||
return find(self,s2,1,true) == 1
|
||||
end
|
||||
|
||||
local function _find_all(s,sub,first,last)
|
||||
if sub == '' then return #s+1,#s end
|
||||
local i1,i2 = find(s,sub,first,true)
|
||||
local res
|
||||
local k = 0
|
||||
while i1 do
|
||||
res = i1
|
||||
k = k + 1
|
||||
i1,i2 = find(s,sub,i2+1,true)
|
||||
if last and i1 > last then break end
|
||||
end
|
||||
return res,k
|
||||
end
|
||||
|
||||
--- does string end with the given substring?.
|
||||
-- @param s a string
|
||||
-- @param send a substring or a table of suffixes
|
||||
function stringx.endswith(s,send)
|
||||
assert_string(1,s)
|
||||
if type(send) == 'string' then
|
||||
return #s >= #send and s:find(send, #s-#send+1, true) and true or false
|
||||
elseif type(send) == 'table' then
|
||||
local endswith = stringx.endswith
|
||||
for _,suffix in ipairs(send) do
|
||||
if endswith(s,suffix) then return true end
|
||||
end
|
||||
return false
|
||||
else
|
||||
error('argument #2: either a substring or a table of suffixes expected')
|
||||
end
|
||||
end
|
||||
|
||||
-- break string into a list of lines
|
||||
-- @param self the string
|
||||
-- @param keepends (currently not used)
|
||||
function stringx.splitlines (self,keepends)
|
||||
assert_string(1,self)
|
||||
local res = usplit(self,'[\r\n]')
|
||||
-- we are currently hacking around a problem with utils.split (see stringx.split)
|
||||
if #res == 0 then res = {''} end
|
||||
return setmetatable(res,list_MT)
|
||||
end
|
||||
|
||||
local function tab_expand (self,n)
|
||||
return (gsub(self,'([^\t]*)\t', function(s)
|
||||
return s..(' '):rep(n - #s % n)
|
||||
end))
|
||||
end
|
||||
|
||||
--- replace all tabs in s with n spaces. If not specified, n defaults to 8.
|
||||
-- with 0.9.5 this now correctly expands to the next tab stop (if you really
|
||||
-- want to just replace tabs, use :gsub('\t',' ') etc)
|
||||
-- @param self the string
|
||||
-- @param n number of spaces to expand each tab, (default 8)
|
||||
function stringx.expandtabs(self,n)
|
||||
assert_string(1,self)
|
||||
n = n or 8
|
||||
if not self:find '\n' then return tab_expand(self,n) end
|
||||
local res,i = {},1
|
||||
for line in stringx.lines(self) do
|
||||
res[i] = tab_expand(line,n)
|
||||
i = i + 1
|
||||
end
|
||||
return table.concat(res,'\n')
|
||||
end
|
||||
|
||||
--- find index of first instance of sub in s from the left.
|
||||
-- @param self the string
|
||||
-- @param sub substring
|
||||
-- @param i1 start index
|
||||
function stringx.lfind(self,sub,i1)
|
||||
assert_string(1,self)
|
||||
assert_string(2,sub)
|
||||
local idx = find(self,sub,i1,true)
|
||||
if idx then return idx else return nil end
|
||||
end
|
||||
|
||||
--- find index of first instance of sub in s from the right.
|
||||
-- @param self the string
|
||||
-- @param sub substring
|
||||
-- @param first first index
|
||||
-- @param last last index
|
||||
function stringx.rfind(self,sub,first,last)
|
||||
assert_string(1,self)
|
||||
assert_string(2,sub)
|
||||
local idx = _find_all(self,sub,first,last)
|
||||
if idx then return idx else return nil end
|
||||
end
|
||||
|
||||
--- replace up to n instances of old by new in the string s.
|
||||
-- if n is not present, replace all instances.
|
||||
-- @param s the string
|
||||
-- @param old the target substring
|
||||
-- @param new the substitution
|
||||
-- @param n optional maximum number of substitutions
|
||||
-- @return result string
|
||||
-- @return the number of substitutions
|
||||
function stringx.replace(s,old,new,n)
|
||||
assert_string(1,s)
|
||||
assert_string(1,old)
|
||||
return (gsub(s,escape(old),new:gsub('%%','%%%%'),n))
|
||||
end
|
||||
|
||||
--- split a string into a list of strings using a delimiter.
|
||||
-- @class function
|
||||
-- @name split
|
||||
-- @param self the string
|
||||
-- @param re a delimiter (defaults to whitespace)
|
||||
-- @param n maximum number of results
|
||||
-- @usage #(('one two'):split()) == 2
|
||||
-- @usage ('one,two,three'):split(',') == List{'one','two','three'}
|
||||
-- @usage ('one,two,three'):split(',',2) == List{'one','two,three'}
|
||||
function stringx.split(self,re,n)
|
||||
local s = self
|
||||
local plain = true
|
||||
if not re then -- default spaces
|
||||
s = lstrip(s)
|
||||
plain = false
|
||||
end
|
||||
local res = usplit(s,re,plain,n)
|
||||
if re and re ~= '' and find(s,re,-#re,true) then
|
||||
res[#res+1] = ""
|
||||
end
|
||||
return setmetatable(res,list_MT)
|
||||
end
|
||||
|
||||
--- split a string using a pattern. Note that at least one value will be returned!
|
||||
-- @param self the string
|
||||
-- @param re a Lua string pattern (defaults to whitespace)
|
||||
-- @return the parts of the string
|
||||
-- @usage a,b = line:splitv('=')
|
||||
function stringx.splitv (self,re)
|
||||
assert_string(1,self)
|
||||
return utils.splitv(self,re)
|
||||
end
|
||||
|
||||
local function copy(self)
|
||||
return self..''
|
||||
end
|
||||
|
||||
--- count all instances of substring in string.
|
||||
-- @param self the string
|
||||
-- @param sub substring
|
||||
function stringx.count(self,sub)
|
||||
assert_string(1,self)
|
||||
local i,k = _find_all(self,sub,1)
|
||||
return k
|
||||
end
|
||||
|
||||
local function _just(s,w,ch,left,right)
|
||||
local n = #s
|
||||
if w > n then
|
||||
if not ch then ch = ' ' end
|
||||
local f1,f2
|
||||
if left and right then
|
||||
local ln = ceil((w-n)/2)
|
||||
local rn = w - n - ln
|
||||
f1 = rep(ch,ln)
|
||||
f2 = rep(ch,rn)
|
||||
elseif right then
|
||||
f1 = rep(ch,w-n)
|
||||
f2 = ''
|
||||
else
|
||||
f2 = rep(ch,w-n)
|
||||
f1 = ''
|
||||
end
|
||||
return f1..s..f2
|
||||
else
|
||||
return copy(s)
|
||||
end
|
||||
end
|
||||
|
||||
--- left-justify s with width w.
|
||||
-- @param self the string
|
||||
-- @param w width of justification
|
||||
-- @param ch padding character, default ' '
|
||||
function stringx.ljust(self,w,ch)
|
||||
assert_string(1,self)
|
||||
assert_arg(2,w,'number')
|
||||
return _just(self,w,ch,true,false)
|
||||
end
|
||||
|
||||
--- right-justify s with width w.
|
||||
-- @param s the string
|
||||
-- @param w width of justification
|
||||
-- @param ch padding character, default ' '
|
||||
function stringx.rjust(s,w,ch)
|
||||
assert_string(1,s)
|
||||
assert_arg(2,w,'number')
|
||||
return _just(s,w,ch,false,true)
|
||||
end
|
||||
|
||||
--- center-justify s with width w.
|
||||
-- @param s the string
|
||||
-- @param w width of justification
|
||||
-- @param ch padding character, default ' '
|
||||
function stringx.center(s,w,ch)
|
||||
assert_string(1,s)
|
||||
assert_arg(2,w,'number')
|
||||
return _just(s,w,ch,true,true)
|
||||
end
|
||||
|
||||
local function _strip(s,left,right,chrs)
|
||||
if not chrs then
|
||||
chrs = '%s'
|
||||
else
|
||||
chrs = '['..escape(chrs)..']'
|
||||
end
|
||||
if left then
|
||||
local i1,i2 = find(s,'^'..chrs..'*')
|
||||
if i2 >= i1 then
|
||||
s = sub(s,i2+1)
|
||||
end
|
||||
end
|
||||
if right then
|
||||
local i1,i2 = find(s,chrs..'*$')
|
||||
if i2 >= i1 then
|
||||
s = sub(s,1,i1-1)
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
--- trim any whitespace on the left of s.
|
||||
-- @param self the string
|
||||
-- @param chrs default space, can be a string of characters to be trimmed
|
||||
function stringx.lstrip(self,chrs)
|
||||
assert_string(1,self)
|
||||
return _strip(self,true,false,chrs)
|
||||
end
|
||||
lstrip = stringx.lstrip
|
||||
|
||||
--- trim any whitespace on the right of s.
|
||||
-- @param s the string
|
||||
-- @param chrs default space, can be a string of characters to be trimmed
|
||||
function stringx.rstrip(s,chrs)
|
||||
assert_string(1,s)
|
||||
return _strip(s,false,true,chrs)
|
||||
end
|
||||
|
||||
--- trim any whitespace on both left and right of s.
|
||||
-- @param self the string
|
||||
-- @param chrs default space, can be a string of characters to be trimmed
|
||||
function stringx.strip(self,chrs)
|
||||
assert_string(1,self)
|
||||
return _strip(self,true,true,chrs)
|
||||
end
|
||||
|
||||
-- The partition functions split a string using a delimiter into three parts:
|
||||
-- the part before, the delimiter itself, and the part afterwards
|
||||
local function _partition(p,delim,fn)
|
||||
local i1,i2 = fn(p,delim)
|
||||
if not i1 or i1 == -1 then
|
||||
return p,'',''
|
||||
else
|
||||
if not i2 then i2 = i1 end
|
||||
return sub(p,1,i1-1),sub(p,i1,i2),sub(p,i2+1)
|
||||
end
|
||||
end
|
||||
|
||||
--- partition the string using first occurance of a delimiter
|
||||
-- @param self the string
|
||||
-- @param ch delimiter
|
||||
-- @return part before ch
|
||||
-- @return ch
|
||||
-- @return part after ch
|
||||
function stringx.partition(self,ch)
|
||||
assert_string(1,self)
|
||||
assert_nonempty_string(2,ch)
|
||||
return _partition(self,ch,stringx.lfind)
|
||||
end
|
||||
|
||||
--- partition the string p using last occurance of a delimiter
|
||||
-- @param self the string
|
||||
-- @param ch delimiter
|
||||
-- @return part before ch
|
||||
-- @return ch
|
||||
-- @return part after ch
|
||||
function stringx.rpartition(self,ch)
|
||||
assert_string(1,self)
|
||||
assert_nonempty_string(2,ch)
|
||||
return _partition(self,ch,stringx.rfind)
|
||||
end
|
||||
|
||||
--- return the 'character' at the index.
|
||||
-- @param self the string
|
||||
-- @param idx an index (can be negative)
|
||||
-- @return a substring of length 1 if successful, empty string otherwise.
|
||||
function stringx.at(self,idx)
|
||||
assert_string(1,self)
|
||||
assert_arg(2,idx,'number')
|
||||
return sub(self,idx,idx)
|
||||
end
|
||||
|
||||
--- return an interator over all lines in a string
|
||||
-- @param self the string
|
||||
-- @return an iterator
|
||||
function stringx.lines (self)
|
||||
assert_string(1,self)
|
||||
local s = self
|
||||
if not s:find '\n$' then s = s..'\n' end
|
||||
return s:gmatch('([^\n]*)\n')
|
||||
end
|
||||
|
||||
--- iniital word letters uppercase ('title case').
|
||||
-- Here 'words' mean chunks of non-space characters.
|
||||
-- @param self the string
|
||||
-- @return a string with each word's first letter uppercase
|
||||
function stringx.title(self)
|
||||
return (self:gsub('(%S)(%S*)',function(f,r)
|
||||
return f:upper()..r:lower()
|
||||
end))
|
||||
end
|
||||
|
||||
stringx.capitalize = stringx.title
|
||||
|
||||
local elipsis = '...'
|
||||
local n_elipsis = #elipsis
|
||||
|
||||
--- return a shorted version of a string.
|
||||
-- @param self the string
|
||||
-- @param sz the maxinum size allowed
|
||||
-- @param tail true if we want to show the end of the string (head otherwise)
|
||||
function stringx.shorten(self,sz,tail)
|
||||
if #self > sz then
|
||||
if sz < n_elipsis then return elipsis:sub(1,sz) end
|
||||
if tail then
|
||||
local i = #self - sz + 1 + n_elipsis
|
||||
return elipsis .. self:sub(i)
|
||||
else
|
||||
return self:sub(1,sz-n_elipsis) .. elipsis
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function stringx.import(dont_overload)
|
||||
utils.import(stringx,string)
|
||||
end
|
||||
|
||||
return stringx
|
||||
766
Utils/luarocks/share/lua/5.1/pl/tablex.lua
Normal file
766
Utils/luarocks/share/lua/5.1/pl/tablex.lua
Normal file
@@ -0,0 +1,766 @@
|
||||
--- Extended operations on Lua tables.
|
||||
-- @class module
|
||||
-- @name pl.tablex
|
||||
local getmetatable,setmetatable,require = getmetatable,setmetatable,require
|
||||
local append,remove = table.insert,table.remove
|
||||
local min,max = math.min,math.max
|
||||
local pairs,type,unpack,next,ipairs,select,tostring = pairs,type,unpack,next,ipairs,select,tostring
|
||||
local utils = require ('pl.utils')
|
||||
local function_arg = utils.function_arg
|
||||
local Set = utils.stdmt.Set
|
||||
local List = utils.stdmt.List
|
||||
local Map = utils.stdmt.Map
|
||||
local assert_arg = utils.assert_arg
|
||||
|
||||
--[[
|
||||
module ('pl.tablex',utils._module)
|
||||
]]
|
||||
|
||||
local tablex = {}
|
||||
|
||||
-- generally, functions that make copies of tables try to preserve the metatable.
|
||||
-- However, when the source has no obvious type, then we attach appropriate metatables
|
||||
-- like List, Map, etc to the result.
|
||||
local function setmeta (res,tbl,def)
|
||||
return setmetatable(res,getmetatable(tbl) or def)
|
||||
end
|
||||
|
||||
local function makelist (res)
|
||||
return setmetatable(res,List)
|
||||
end
|
||||
|
||||
--- copy a table into another, in-place.
|
||||
-- @param t1 destination table
|
||||
-- @param t2 source table
|
||||
-- @return first table
|
||||
function tablex.update (t1,t2)
|
||||
assert_arg(1,t1,'table')
|
||||
assert_arg(2,t2,'table')
|
||||
for k,v in pairs(t2) do
|
||||
t1[k] = v
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
--- total number of elements in this table. <br>
|
||||
-- Note that this is distinct from #t, which is the number
|
||||
-- of values in the array part; this value will always
|
||||
-- be greater or equal. The difference gives the size of
|
||||
-- the hash part, for practical purposes.
|
||||
-- @param t a table
|
||||
-- @return the size
|
||||
function tablex.size (t)
|
||||
assert_arg(1,t,'table')
|
||||
local i = 0
|
||||
for k in pairs(t) do i = i + 1 end
|
||||
return i
|
||||
end
|
||||
|
||||
--- make a shallow copy of a table
|
||||
-- @param t source table
|
||||
-- @return new table
|
||||
function tablex.copy (t)
|
||||
assert_arg(1,t,'table')
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
res[k] = v
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- make a deep copy of a table, recursively copying all the keys and fields.
|
||||
-- This will also set the copied table's metatable to that of the original.
|
||||
-- @param t A table
|
||||
-- @return new table
|
||||
function tablex.deepcopy(t)
|
||||
assert_arg(1,t,'table')
|
||||
if type(t) ~= 'table' then return t end
|
||||
local mt = getmetatable(t)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
v = tablex.deepcopy(v)
|
||||
end
|
||||
res[k] = v
|
||||
end
|
||||
setmetatable(res,mt)
|
||||
return res
|
||||
end
|
||||
|
||||
local abs = math.abs
|
||||
|
||||
--- compare two values.
|
||||
-- if they are tables, then compare their keys and fields recursively.
|
||||
-- @param t1 A value
|
||||
-- @param t2 A value
|
||||
-- @param ignore_mt if true, ignore __eq metamethod (default false)
|
||||
-- @param eps if defined, then used for any number comparisons
|
||||
-- @return true or false
|
||||
function tablex.deepcompare(t1,t2,ignore_mt,eps)
|
||||
local ty1 = type(t1)
|
||||
local ty2 = type(t2)
|
||||
if ty1 ~= ty2 then return false end
|
||||
-- non-table types can be directly compared
|
||||
if ty1 ~= 'table' then
|
||||
if ty1 == 'number' and eps then return abs(t1-t2) < eps end
|
||||
return t1 == t2
|
||||
end
|
||||
-- as well as tables which have the metamethod __eq
|
||||
local mt = getmetatable(t1)
|
||||
if not ignore_mt and mt and mt.__eq then return t1 == t2 end
|
||||
for k1,v1 in pairs(t1) do
|
||||
local v2 = t2[k1]
|
||||
if v2 == nil or not tablex.deepcompare(v1,v2,ignore_mt,eps) then return false end
|
||||
end
|
||||
for k2,v2 in pairs(t2) do
|
||||
local v1 = t1[k2]
|
||||
if v1 == nil or not tablex.deepcompare(v1,v2,ignore_mt,eps) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- compare two list-like tables using a predicate.
|
||||
-- @param t1 a table
|
||||
-- @param t2 a table
|
||||
-- @param cmp A comparison function
|
||||
function tablex.compare (t1,t2,cmp)
|
||||
assert_arg(1,t1,'table')
|
||||
assert_arg(2,t2,'table')
|
||||
if #t1 ~= #t2 then return false end
|
||||
cmp = function_arg(3,cmp)
|
||||
for k in ipairs(t1) do
|
||||
if not cmp(t1[k],t2[k]) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- compare two list-like tables using an optional predicate, without regard for element order.
|
||||
-- @param t1 a list-like table
|
||||
-- @param t2 a list-like table
|
||||
-- @param cmp A comparison function (may be nil)
|
||||
function tablex.compare_no_order (t1,t2,cmp)
|
||||
assert_arg(1,t1,'table')
|
||||
assert_arg(2,t2,'table')
|
||||
if cmp then cmp = function_arg(3,cmp) end
|
||||
if #t1 ~= #t2 then return false end
|
||||
local visited = {}
|
||||
for i = 1,#t1 do
|
||||
local val = t1[i]
|
||||
local gotcha
|
||||
for j = 1,#t2 do if not visited[j] then
|
||||
local match
|
||||
if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end
|
||||
if match then
|
||||
gotcha = j
|
||||
break
|
||||
end
|
||||
end end
|
||||
if not gotcha then return false end
|
||||
visited[gotcha] = true
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--- return the index of a value in a list.
|
||||
-- Like string.find, there is an optional index to start searching,
|
||||
-- which can be negative.
|
||||
-- @param t A list-like table (i.e. with numerical indices)
|
||||
-- @param val A value
|
||||
-- @param idx index to start; -1 means last element,etc (default 1)
|
||||
-- @return index of value or nil if not found
|
||||
-- @usage find({10,20,30},20) == 2
|
||||
-- @usage find({'a','b','a','c'},'a',2) == 3
|
||||
|
||||
function tablex.find(t,val,idx)
|
||||
assert_arg(1,t,'table')
|
||||
idx = idx or 1
|
||||
if idx < 0 then idx = #t + idx + 1 end
|
||||
for i = idx,#t do
|
||||
if t[i] == val then return i end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- return the index of a value in a list, searching from the end.
|
||||
-- Like string.find, there is an optional index to start searching,
|
||||
-- which can be negative.
|
||||
-- @param t A list-like table (i.e. with numerical indices)
|
||||
-- @param val A value
|
||||
-- @param idx index to start; -1 means last element,etc (default 1)
|
||||
-- @return index of value or nil if not found
|
||||
-- @usage rfind({10,10,10},10) == 3
|
||||
function tablex.rfind(t,val,idx)
|
||||
assert_arg(1,t,'table')
|
||||
idx = idx or #t
|
||||
if idx < 0 then idx = #t + idx + 1 end
|
||||
for i = idx,1,-1 do
|
||||
if t[i] == val then return i end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- return the index (or key) of a value in a table using a comparison function.
|
||||
-- @param t A table
|
||||
-- @param cmp A comparison function
|
||||
-- @param arg an optional second argument to the function
|
||||
-- @return index of value, or nil if not found
|
||||
-- @return value returned by comparison function
|
||||
function tablex.find_if(t,cmp,arg)
|
||||
assert_arg(1,t,'table')
|
||||
cmp = function_arg(2,cmp)
|
||||
for k,v in pairs(t) do
|
||||
local c = cmp(v,arg)
|
||||
if c then return k,c end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- return a list of all values in a table indexed by another list.
|
||||
-- @param tbl a table
|
||||
-- @param idx an index table (a list of keys)
|
||||
-- @return a list-like table
|
||||
-- @usage index_by({10,20,30,40},{2,4}) == {20,40}
|
||||
-- @usage index_by({one=1,two=2,three=3},{'one','three'}) == {1,3}
|
||||
function tablex.index_by(tbl,idx)
|
||||
assert_arg(1,tbl,'table')
|
||||
assert_arg(2,idx,'table')
|
||||
local res = {}
|
||||
for _,i in ipairs(idx) do
|
||||
append(res,tbl[i])
|
||||
end
|
||||
return setmeta(res,tbl,List)
|
||||
end
|
||||
|
||||
--- apply a function to all values of a table.
|
||||
-- This returns a table of the results.
|
||||
-- Any extra arguments are passed to the function.
|
||||
-- @param fun A function that takes at least one argument
|
||||
-- @param t A table
|
||||
-- @param ... optional arguments
|
||||
-- @usage map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4}
|
||||
function tablex.map(fun,t,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
res[k] = fun(v,...)
|
||||
end
|
||||
return setmeta(res,t)
|
||||
end
|
||||
|
||||
--- apply a function to all values of a list.
|
||||
-- This returns a table of the results.
|
||||
-- Any extra arguments are passed to the function.
|
||||
-- @param fun A function that takes at least one argument
|
||||
-- @param t a table (applies to array part)
|
||||
-- @param ... optional arguments
|
||||
-- @return a list-like table
|
||||
-- @usage imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900}
|
||||
function tablex.imap(fun,t,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for i = 1,#t do
|
||||
res[i] = fun(t[i],...) or false
|
||||
end
|
||||
return setmeta(res,t,List)
|
||||
end
|
||||
|
||||
--- apply a named method to values from a table.
|
||||
-- @param name the method name
|
||||
-- @param t a list-like table
|
||||
-- @param ... any extra arguments to the method
|
||||
function tablex.map_named_method (name,t,...)
|
||||
assert_arg(1,name,'string')
|
||||
assert_arg(2,t,'table')
|
||||
local res = {}
|
||||
for i = 1,#t do
|
||||
local val = t[i]
|
||||
local fun = val[name]
|
||||
res[i] = fun(val,...)
|
||||
end
|
||||
return setmeta(res,t,List)
|
||||
end
|
||||
|
||||
|
||||
--- apply a function to all values of a table, in-place.
|
||||
-- Any extra arguments are passed to the function.
|
||||
-- @param fun A function that takes at least one argument
|
||||
-- @param t a table
|
||||
-- @param ... extra arguments
|
||||
function tablex.transform (fun,t,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(1,fun)
|
||||
for k,v in pairs(t) do
|
||||
t[v] = fun(v,...)
|
||||
end
|
||||
end
|
||||
|
||||
--- generate a table of all numbers in a range
|
||||
-- @param start number
|
||||
-- @param finish number
|
||||
-- @param step optional increment (default 1 for increasing, -1 for decreasing)
|
||||
function tablex.range (start,finish,step)
|
||||
local res = {}
|
||||
local k = 1
|
||||
if not step then
|
||||
if finish > start then step = finish > start and 1 or -1 end
|
||||
end
|
||||
for i=start,finish,step do res[k]=i; k=k+1 end
|
||||
return res
|
||||
end
|
||||
|
||||
--- apply a function to values from two tables.
|
||||
-- @param fun a function of at least two arguments
|
||||
-- @param t1 a table
|
||||
-- @param t2 a table
|
||||
-- @param ... extra arguments
|
||||
-- @return a table
|
||||
-- @usage map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44}
|
||||
function tablex.map2 (fun,t1,t2,...)
|
||||
assert_arg(1,t1,'table')
|
||||
assert_arg(2,t2,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for k,v in pairs(t1) do
|
||||
res[k] = fun(v,t2[k],...)
|
||||
end
|
||||
return setmeta(res,t1,List)
|
||||
end
|
||||
|
||||
--- apply a function to values from two arrays.
|
||||
-- @param fun a function of at least two arguments
|
||||
-- @param t1 a list-like table
|
||||
-- @param t2 a list-like table
|
||||
-- @param ... extra arguments
|
||||
-- @usage imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23}
|
||||
function tablex.imap2 (fun,t1,t2,...)
|
||||
assert_arg(2,t1,'table')
|
||||
assert_arg(3,t2,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for i = 1,#t1 do
|
||||
res[i] = fun(t1[i],t2[i],...)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- 'reduce' a list using a binary function.
|
||||
-- @param fun a function of two arguments
|
||||
-- @param t a list-like table
|
||||
-- @return the result of the function
|
||||
-- @usage reduce('+',{1,2,3,4}) == 10
|
||||
function tablex.reduce (fun,t)
|
||||
assert_arg(2,t,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local n = #t
|
||||
local res = t[1]
|
||||
for i = 2,n do
|
||||
res = fun(res,t[i])
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- apply a function to all elements of a table.
|
||||
-- The arguments to the function will be the value,
|
||||
-- the key and <i>finally</i> any extra arguments passed to this function.
|
||||
-- Note that the Lua 5.0 function table.foreach passed the <i>key</i> first.
|
||||
-- @param t a table
|
||||
-- @param fun a function with at least one argument
|
||||
-- @param ... extra arguments
|
||||
function tablex.foreach(t,fun,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(2,fun)
|
||||
for k,v in pairs(t) do
|
||||
fun(v,k,...)
|
||||
end
|
||||
end
|
||||
|
||||
--- apply a function to all elements of a list-like table in order.
|
||||
-- The arguments to the function will be the value,
|
||||
-- the index and <i>finally</i> any extra arguments passed to this function
|
||||
-- @param t a table
|
||||
-- @param fun a function with at least one argument
|
||||
-- @param ... optional arguments
|
||||
function tablex.foreachi(t,fun,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(2,fun)
|
||||
for k,v in ipairs(t) do
|
||||
fun(v,k,...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Apply a function to a number of tables.
|
||||
-- A more general version of map
|
||||
-- The result is a table containing the result of applying that function to the
|
||||
-- ith value of each table. Length of output list is the minimum length of all the lists
|
||||
-- @param fun a function of n arguments
|
||||
-- @param ... n tables
|
||||
-- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333}
|
||||
-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300}
|
||||
-- @param fun A function that takes as many arguments as there are tables
|
||||
function tablex.mapn(fun,...)
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
local lists = {...}
|
||||
local minn = 1e40
|
||||
for i = 1,#lists do
|
||||
minn = min(minn,#(lists[i]))
|
||||
end
|
||||
for i = 1,minn do
|
||||
local args = {}
|
||||
for j = 1,#lists do
|
||||
args[#args+1] = lists[j][i]
|
||||
end
|
||||
res[#res+1] = fun(unpack(args))
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
--- call the function with the key and value pairs from a table.
|
||||
-- The function can return a value and a key (note the order!). If both
|
||||
-- are not nil, then this pair is inserted into the result. If only value is not nil, then
|
||||
-- it is appended to the result.
|
||||
-- @param fun A function which will be passed each key and value as arguments, plus any extra arguments to pairmap.
|
||||
-- @param t A table
|
||||
-- @param ... optional arguments
|
||||
-- @usage pairmap({fred=10,bonzo=20},function(k,v) return v end) is {10,20}
|
||||
-- @usage pairmap({one=1,two=2},function(k,v) return {k,v},k end) is {one={'one',1},two={'two',2}}
|
||||
function tablex.pairmap(fun,t,...)
|
||||
assert_arg(1,t,'table')
|
||||
fun = function_arg(1,fun)
|
||||
local res = {}
|
||||
for k,v in pairs(t) do
|
||||
local rv,rk = fun(k,v,...)
|
||||
if rk then
|
||||
res[rk] = rv
|
||||
else
|
||||
res[#res+1] = rv
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function keys_op(i,v) return i end
|
||||
|
||||
--- return all the keys of a table in arbitrary order.
|
||||
-- @param t A table
|
||||
function tablex.keys(t)
|
||||
assert_arg(1,t,'table')
|
||||
return makelist(tablex.pairmap(keys_op,t))
|
||||
end
|
||||
|
||||
local function values_op(i,v) return v end
|
||||
|
||||
--- return all the values of the table in arbitrary order
|
||||
-- @param t A table
|
||||
function tablex.values(t)
|
||||
assert_arg(1,t,'table')
|
||||
return makelist(tablex.pairmap(values_op,t))
|
||||
end
|
||||
|
||||
local function index_map_op (i,v) return i,v end
|
||||
|
||||
--- create an index map from a list-like table. The original values become keys,
|
||||
-- and the associated values are the indices into the original list.
|
||||
-- @param t a list-like table
|
||||
-- @return a map-like table
|
||||
function tablex.index_map (t)
|
||||
assert_arg(1,t,'table')
|
||||
return setmetatable(tablex.pairmap(index_map_op,t),Map)
|
||||
end
|
||||
|
||||
local function set_op(i,v) return true,v end
|
||||
|
||||
--- create a set from a list-like table. A set is a table where the original values
|
||||
-- become keys, and the associated values are all true.
|
||||
-- @param t a list-like table
|
||||
-- @return a set (a map-like table)
|
||||
function tablex.makeset (t)
|
||||
assert_arg(1,t,'table')
|
||||
return setmetatable(tablex.pairmap(set_op,t),Set)
|
||||
end
|
||||
|
||||
|
||||
--- combine two tables, either as union or intersection. Corresponds to
|
||||
-- set operations for sets () but more general. Not particularly
|
||||
-- useful for list-like tables.
|
||||
-- @param t1 a table
|
||||
-- @param t2 a table
|
||||
-- @param dup true for a union, false for an intersection.
|
||||
-- @usage merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34}
|
||||
-- @usage merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23}
|
||||
-- @see tablex.index_map
|
||||
function tablex.merge (t1,t2,dup)
|
||||
assert_arg(1,t1,'table')
|
||||
assert_arg(2,t2,'table')
|
||||
local res = {}
|
||||
for k,v in pairs(t1) do
|
||||
if dup or t2[k] then res[k] = v end
|
||||
end
|
||||
for k,v in pairs(t2) do
|
||||
if dup or t1[k] then res[k] = v end
|
||||
end
|
||||
return setmeta(res,t1,Map)
|
||||
end
|
||||
|
||||
--- a new table which is the difference of two tables.
|
||||
-- With sets (where the values are all true) this is set difference and
|
||||
-- symmetric difference depending on the third parameter.
|
||||
-- @param s1 a map-like table or set
|
||||
-- @param s2 a map-like table or set
|
||||
-- @param symm symmetric difference (default false)
|
||||
-- @return a map-like table or set
|
||||
function tablex.difference (s1,s2,symm)
|
||||
assert_arg(1,s1,'table')
|
||||
assert_arg(2,s2,'table')
|
||||
local res = {}
|
||||
for k,v in pairs(s1) do
|
||||
if not s2[k] then res[k] = v end
|
||||
end
|
||||
if symm then
|
||||
for k,v in pairs(s2) do
|
||||
if not s1[k] then res[k] = v end
|
||||
end
|
||||
end
|
||||
return setmeta(res,s1,Map)
|
||||
end
|
||||
|
||||
--- A table where the key/values are the values and value counts of the table.
|
||||
-- @param t a list-like table
|
||||
-- @param cmp a function that defines equality (otherwise uses ==)
|
||||
-- @return a map-like table
|
||||
-- @see seq.count_map
|
||||
function tablex.count_map (t,cmp)
|
||||
assert_arg(1,t,'table')
|
||||
local res,mask = {},{}
|
||||
cmp = function_arg(2,cmp)
|
||||
local n = #t
|
||||
for i,v in ipairs(t) do
|
||||
if not mask[v] then
|
||||
mask[v] = true
|
||||
-- check this value against all other values
|
||||
res[v] = 1 -- there's at least one instance
|
||||
for j = i+1,n do
|
||||
local w = t[j]
|
||||
if cmp and cmp(v,w) or v == w then
|
||||
res[v] = res[v] + 1
|
||||
mask[w] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return setmetatable(res,Map)
|
||||
end
|
||||
|
||||
--- filter a table's values using a predicate function
|
||||
-- @param t a list-like table
|
||||
-- @param pred a boolean function
|
||||
-- @param arg optional argument to be passed as second argument of the predicate
|
||||
function tablex.filter (t,pred,arg)
|
||||
assert_arg(1,t,'table')
|
||||
pred = function_arg(2,pred)
|
||||
local res = {}
|
||||
for k,v in ipairs(t) do
|
||||
if pred(v,arg) then append(res,v) end
|
||||
end
|
||||
return setmeta(res,t,List)
|
||||
end
|
||||
|
||||
--- return a table where each element is a table of the ith values of an arbitrary
|
||||
-- number of tables. It is equivalent to a matrix transpose.
|
||||
-- @usage zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}}
|
||||
function tablex.zip(...)
|
||||
return tablex.mapn(function(...) return {...} end,...)
|
||||
end
|
||||
|
||||
local _copy
|
||||
function _copy (dest,src,idest,isrc,nsrc,clean_tail)
|
||||
idest = idest or 1
|
||||
isrc = isrc or 1
|
||||
local iend
|
||||
if not nsrc then
|
||||
nsrc = #src
|
||||
iend = #src
|
||||
else
|
||||
iend = isrc + min(nsrc-1,#src-isrc)
|
||||
end
|
||||
if dest == src then -- special case
|
||||
if idest > isrc and iend >= idest then -- overlapping ranges
|
||||
src = tablex.sub(src,isrc,nsrc)
|
||||
isrc = 1; iend = #src
|
||||
end
|
||||
end
|
||||
for i = isrc,iend do
|
||||
dest[idest] = src[i]
|
||||
idest = idest + 1
|
||||
end
|
||||
if clean_tail then
|
||||
tablex.clear(dest,idest)
|
||||
end
|
||||
return dest
|
||||
end
|
||||
|
||||
--- copy an array into another one, resizing the destination if necessary. <br>
|
||||
-- @param dest a list-like table
|
||||
-- @param src a list-like table
|
||||
-- @param idest where to start copying values from source (default 1)
|
||||
-- @param isrc where to start copying values into destination (default 1)
|
||||
-- @param nsrc number of elements to copy from source (default source size)
|
||||
function tablex.icopy (dest,src,idest,isrc,nsrc)
|
||||
assert_arg(1,dest,'table')
|
||||
assert_arg(2,src,'table')
|
||||
return _copy(dest,src,idest,isrc,nsrc,true)
|
||||
end
|
||||
|
||||
--- copy an array into another one. <br>
|
||||
-- @param dest a list-like table
|
||||
-- @param src a list-like table
|
||||
-- @param idest where to start copying values from source (default 1)
|
||||
-- @param isrc where to start copying values into destination (default 1)
|
||||
-- @param nsrc number of elements to copy from source (default source size)
|
||||
function tablex.move (dest,src,idest,isrc,nsrc)
|
||||
assert_arg(1,dest,'table')
|
||||
assert_arg(2,src,'table')
|
||||
return _copy(dest,src,idest,isrc,nsrc,false)
|
||||
end
|
||||
|
||||
function tablex._normalize_slice(self,first,last)
|
||||
local sz = #self
|
||||
if not first then first=1 end
|
||||
if first<0 then first=sz+first+1 end
|
||||
-- make the range _inclusive_!
|
||||
if not last then last=sz end
|
||||
if last < 0 then last=sz+1+last end
|
||||
return first,last
|
||||
end
|
||||
|
||||
--- Extract a range from a table, like 'string.sub'.
|
||||
-- If first or last are negative then they are relative to the end of the list
|
||||
-- eg. sub(t,-2) gives last 2 entries in a list, and
|
||||
-- sub(t,-4,-2) gives from -4th to -2nd
|
||||
-- @param t a list-like table
|
||||
-- @param first An index
|
||||
-- @param last An index
|
||||
-- @return a new List
|
||||
function tablex.sub(t,first,last)
|
||||
assert_arg(1,t,'table')
|
||||
first,last = tablex._normalize_slice(t,first,last)
|
||||
local res={}
|
||||
for i=first,last do append(res,t[i]) end
|
||||
return setmeta(res,t,List)
|
||||
end
|
||||
|
||||
--- set an array range to a value. If it's a function we use the result
|
||||
-- of applying it to the indices.
|
||||
-- @param t a list-like table
|
||||
-- @param val a value
|
||||
-- @param i1 start range (default 1)
|
||||
-- @param i2 end range (default table size)
|
||||
function tablex.set (t,val,i1,i2)
|
||||
i1,i2 = i1 or 1,i2 or #t
|
||||
if utils.is_callable(val) then
|
||||
for i = i1,i2 do
|
||||
t[i] = val(i)
|
||||
end
|
||||
else
|
||||
for i = i1,i2 do
|
||||
t[i] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- create a new array of specified size with initial value.
|
||||
-- @param n size
|
||||
-- @param val initial value (can be nil, but don't expect # to work!)
|
||||
-- @return the table
|
||||
function tablex.new (n,val)
|
||||
local res = {}
|
||||
tablex.set(res,val,1,n)
|
||||
return res
|
||||
end
|
||||
|
||||
--- clear out the contents of a table.
|
||||
-- @param t a table
|
||||
-- @param istart optional start position
|
||||
function tablex.clear(t,istart)
|
||||
istart = istart or 1
|
||||
for i = istart,#t do remove(t) end
|
||||
end
|
||||
|
||||
--- insert values into a table. <br>
|
||||
-- insertvalues(t, [pos,] values) <br>
|
||||
-- similar to table.insert but inserts values from given table "values",
|
||||
-- not the object itself, into table "t" at position "pos".
|
||||
function tablex.insertvalues(t, ...)
|
||||
local pos, values
|
||||
if select('#', ...) == 1 then
|
||||
pos,values = #t+1, ...
|
||||
else
|
||||
pos,values = ...
|
||||
end
|
||||
if #values > 0 then
|
||||
for i=#t,pos,-1 do
|
||||
t[i+#values] = t[i]
|
||||
end
|
||||
local offset = 1 - pos
|
||||
for i=pos,pos+#values-1 do
|
||||
t[i] = values[i + offset]
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
--- remove a range of values from a table.
|
||||
-- @param t a list-like table
|
||||
-- @param i1 start index
|
||||
-- @param i2 end index
|
||||
-- @return the table
|
||||
function tablex.removevalues (t,i1,i2)
|
||||
i1,i2 = tablex._normalize_slice(t,i1,i2)
|
||||
for i = i1,i2 do
|
||||
remove(t,i1)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local _find
|
||||
_find = function (t,value,tables)
|
||||
for k,v in pairs(t) do
|
||||
if v == value then return k end
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
if not tables[v] and type(v) == 'table' then
|
||||
tables[v] = true
|
||||
local res = _find(v,value,tables)
|
||||
if res then
|
||||
res = tostring(res)
|
||||
if type(k) ~= 'string' then
|
||||
return '['..k..']'..res
|
||||
else
|
||||
return k..'.'..res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- find a value in a table by recursive search.
|
||||
-- @param t the table
|
||||
-- @param value the value
|
||||
-- @param exclude any tables to avoid searching
|
||||
-- @usage search(_G,math.sin,{package.path}) == 'math.sin'
|
||||
-- @return a fieldspec, e.g. 'a.b' or 'math.sin'
|
||||
function tablex.search (t,value,exclude)
|
||||
assert_arg(1,t,'table')
|
||||
local tables = {[t]=true}
|
||||
if exclude then
|
||||
for _,v in pairs(exclude) do tables[v] = true end
|
||||
end
|
||||
return _find(t,value,tables)
|
||||
end
|
||||
|
||||
return tablex
|
||||
99
Utils/luarocks/share/lua/5.1/pl/template.lua
Normal file
99
Utils/luarocks/share/lua/5.1/pl/template.lua
Normal file
@@ -0,0 +1,99 @@
|
||||
--- A template preprocessor.
|
||||
-- Originally by <a href="http://lua-users.org/wiki/SlightlyLessSimpleLuaPreprocessor">Ricki Lake</a>
|
||||
-- <p>There are two rules: <ul>
|
||||
-- <li>lines starting with # are Lua</li>
|
||||
-- <li> otherwise, `$(expr)` is the result of evaluating `expr`</li>
|
||||
-- </ul>
|
||||
-- <pre class=example>
|
||||
-- # for i = 1,3 do
|
||||
-- $(i) Hello, Word!
|
||||
-- # end
|
||||
-- </pre>
|
||||
-- Other escape characters can be used, when the defaults conflict
|
||||
-- with the output language.
|
||||
-- <pre class=example>
|
||||
-- > for _,n in pairs{'one','two','three'} do
|
||||
-- static int l_${n} (luaState *state);
|
||||
-- > end
|
||||
-- </pre>
|
||||
-- See <a href="../../index.html#rici_templates">the Guide</a>.
|
||||
-- @class module
|
||||
-- @name pl.template
|
||||
|
||||
--[[
|
||||
module('pl.template')
|
||||
]]
|
||||
|
||||
local utils = require 'pl.utils'
|
||||
local append,format = table.insert,string.format
|
||||
|
||||
local function parseHashLines(chunk,brackets,esc)
|
||||
local exec_pat = "()$(%b"..brackets..")()"
|
||||
|
||||
local function parseDollarParen(pieces, chunk, s, e)
|
||||
local s = 1
|
||||
for term, executed, e in chunk:gmatch (exec_pat) do
|
||||
executed = '('..executed:sub(2,-2)..')'
|
||||
append(pieces,
|
||||
format("%q..(%s or '')..",chunk:sub(s, term - 1), executed))
|
||||
s = e
|
||||
end
|
||||
append(pieces, format("%q", chunk:sub(s)))
|
||||
end
|
||||
|
||||
local esc_pat = esc.."+([^\n]*\n?)"
|
||||
local esc_pat1, esc_pat2 = "^"..esc_pat, "\n"..esc_pat
|
||||
local pieces, s = {"return function(_put) ", n = 1}, 1
|
||||
while true do
|
||||
local ss, e, lua = chunk:find (esc_pat1, s)
|
||||
if not e then
|
||||
ss, e, lua = chunk:find(esc_pat2, s)
|
||||
append(pieces, "_put(")
|
||||
parseDollarParen(pieces, chunk:sub(s, ss))
|
||||
append(pieces, ")")
|
||||
if not e then break end
|
||||
end
|
||||
append(pieces, lua)
|
||||
s = e + 1
|
||||
end
|
||||
append(pieces, " end")
|
||||
return table.concat(pieces)
|
||||
end
|
||||
|
||||
local template = {}
|
||||
|
||||
--- expand the template using the specified environment.
|
||||
-- @param str the template string
|
||||
-- @param env the environment (by default empty). <br>
|
||||
-- There are three special fields in the environment table <ul>
|
||||
-- <li><code>_parent</code> continue looking up in this table</li>
|
||||
-- <li><code>_brackets</code>; default is '()', can be any suitable bracket pair</li>
|
||||
-- <li><code>_escape</code>; default is '#' </li>
|
||||
-- </ul>
|
||||
function template.substitute(str,env)
|
||||
env = env or {}
|
||||
if rawget(env,"_parent") then
|
||||
setmetatable(env,{__index = env._parent})
|
||||
end
|
||||
local brackets = rawget(env,"_brackets") or '()'
|
||||
local escape = rawget(env,"_escape") or '#'
|
||||
local code = parseHashLines(str,brackets,escape)
|
||||
local fn,err = utils.load(code,'TMP','t',env)
|
||||
if not fn then return nil,err end
|
||||
fn = fn()
|
||||
local out = {}
|
||||
local res,err = xpcall(function() fn(function(s)
|
||||
out[#out+1] = s
|
||||
end) end,debug.traceback)
|
||||
if not res then
|
||||
if env._debug then print(code) end
|
||||
return nil,err
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
return template
|
||||
|
||||
|
||||
|
||||
|
||||
116
Utils/luarocks/share/lua/5.1/pl/test.lua
Normal file
116
Utils/luarocks/share/lua/5.1/pl/test.lua
Normal file
@@ -0,0 +1,116 @@
|
||||
--- Useful test utilities.
|
||||
-- @module pl.test
|
||||
|
||||
local tablex = require 'pl.tablex'
|
||||
local utils = require 'pl.utils'
|
||||
local pretty = require 'pl.pretty'
|
||||
local path = require 'pl.path'
|
||||
local print,type = print,type
|
||||
local clock = os.clock
|
||||
local debug = require 'debug'
|
||||
local io,debug = io,debug
|
||||
|
||||
local function dump(x)
|
||||
if type(x) == 'table' and not (getmetatable(x) and getmetatable(x).__tostring) then
|
||||
return pretty.write(x,' ',true)
|
||||
else
|
||||
return tostring(x)
|
||||
end
|
||||
end
|
||||
|
||||
local test = {}
|
||||
|
||||
local function complain (x,y,msg)
|
||||
local i = debug.getinfo(3)
|
||||
local err = io.stderr
|
||||
err:write(path.basename(i.short_src)..':'..i.currentline..': assertion failed\n')
|
||||
err:write("got:\t",dump(x),'\n')
|
||||
err:write("needed:\t",dump(y),'\n')
|
||||
utils.quit(1,msg or "these values were not equal")
|
||||
end
|
||||
|
||||
--- like assert, except takes two arguments that must be equal and can be tables.
|
||||
-- If they are plain tables, it will use tablex.deepcompare.
|
||||
-- @param x any value
|
||||
-- @param y a value equal to x
|
||||
-- @param eps an optional tolerance for numerical comparisons
|
||||
function test.asserteq (x,y,eps)
|
||||
local res = x == y
|
||||
if not res then
|
||||
res = tablex.deepcompare(x,y,true,eps)
|
||||
end
|
||||
if not res then
|
||||
complain(x,y)
|
||||
end
|
||||
end
|
||||
|
||||
--- assert that the first string matches the second.
|
||||
-- @param s1 a string
|
||||
-- @param s2 a string
|
||||
function test.assertmatch (s1,s2)
|
||||
if not s1:match(s2) then
|
||||
complain (s1,s2,"these strings did not match")
|
||||
end
|
||||
end
|
||||
|
||||
function test.assertraise(fn,e)
|
||||
local ok, err = pcall(unpack(fn))
|
||||
if not err or err:match(e)==nil then
|
||||
complain (err,e,"these errors did not match")
|
||||
end
|
||||
end
|
||||
|
||||
--- a version of asserteq that takes two pairs of values.
|
||||
-- <code>x1==y1 and x2==y2</code> must be true. Useful for functions that naturally
|
||||
-- return two values.
|
||||
-- @param x1 any value
|
||||
-- @param x2 any value
|
||||
-- @param y1 any value
|
||||
-- @param y2 any value
|
||||
function test.asserteq2 (x1,x2,y1,y2)
|
||||
if x1 ~= y1 then complain(x1,y1) end
|
||||
if x2 ~= y2 then complain(x2,y2) end
|
||||
end
|
||||
|
||||
-- tuple type --
|
||||
|
||||
local tuple_mt = {}
|
||||
|
||||
function tuple_mt.__tostring(self)
|
||||
local ts = {}
|
||||
for i=1, self.n do
|
||||
local s = self[i]
|
||||
ts[i] = type(s) == 'string' and string.format('%q', s) or tostring(s)
|
||||
end
|
||||
return 'tuple(' .. table.concat(ts, ', ') .. ')'
|
||||
end
|
||||
|
||||
function tuple_mt.__eq(a, b)
|
||||
if a.n ~= b.n then return false end
|
||||
for i=1, a.n do
|
||||
if a[i] ~= b[i] then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- encode an arbitrary argument list as a tuple.
|
||||
-- This can be used to compare to other argument lists, which is
|
||||
-- very useful for testing functions which return a number of values.
|
||||
-- @usage asserteq(tuple( ('ab'):find 'a'), tuple(1,1))
|
||||
function test.tuple(...)
|
||||
return setmetatable({n=select('#', ...), ...}, tuple_mt)
|
||||
end
|
||||
|
||||
--- Time a function. Call the function a given number of times, and report the number of seconds taken,
|
||||
-- together with a message. Any extra arguments will be passed to the function.
|
||||
-- @param msg a descriptive message
|
||||
-- @param n number of times to call the function
|
||||
-- @param fun the function
|
||||
-- @param ... optional arguments to fun
|
||||
function test.timer(msg,n,fun,...)
|
||||
local start = clock()
|
||||
for i = 1,n do fun(...) end
|
||||
utils.printf("%s: took %7.2f sec\n",msg,clock()-start)
|
||||
end
|
||||
|
||||
return test
|
||||
241
Utils/luarocks/share/lua/5.1/pl/text.lua
Normal file
241
Utils/luarocks/share/lua/5.1/pl/text.lua
Normal file
@@ -0,0 +1,241 @@
|
||||
--- Text processing utilities. <p>
|
||||
-- This provides a Template class (modeled after the same from the Python
|
||||
-- libraries, see string.Template). It also provides similar functions to those
|
||||
-- found in the textwrap module.
|
||||
-- See <a href="../../index.html#templates">the Guide</a>.
|
||||
-- <p>
|
||||
-- Calling <code>text.format_operator()</code> overloads the % operator for strings to give Python/Ruby style formated output.
|
||||
-- This is extended to also do template-like substitution for map-like data.
|
||||
-- <pre class=example>
|
||||
-- > require 'pl.text'.format_operator()
|
||||
-- > = '%s = %5.3f' % {'PI',math.pi}
|
||||
-- PI = 3.142
|
||||
-- > = '$name = $value' % {name='dog',value='Pluto'}
|
||||
-- dog = Pluto
|
||||
-- </pre>
|
||||
-- @class module
|
||||
-- @name pl.text
|
||||
|
||||
local gsub = string.gsub
|
||||
local concat,append = table.concat,table.insert
|
||||
local utils = require 'pl.utils'
|
||||
local bind1,usplit,assert_arg,is_callable = utils.bind1,utils.split,utils.assert_arg,utils.is_callable
|
||||
|
||||
local function lstrip(str) return (str:gsub('^%s+','')) end
|
||||
local function strip(str) return (lstrip(str):gsub('%s+$','')) end
|
||||
local function make_list(l) return setmetatable(l,utils.stdmt.List) end
|
||||
local function split(s,delim) return make_list(usplit(s,delim)) end
|
||||
|
||||
local function imap(f,t,...)
|
||||
local res = {}
|
||||
for i = 1,#t do res[i] = f(t[i],...) end
|
||||
return res
|
||||
end
|
||||
|
||||
--[[
|
||||
module ('pl.text',utils._module)
|
||||
]]
|
||||
|
||||
local text = {}
|
||||
|
||||
local function _indent (s,sp)
|
||||
local sl = split(s,'\n')
|
||||
return concat(imap(bind1('..',sp),sl),'\n')..'\n'
|
||||
end
|
||||
|
||||
--- indent a multiline string.
|
||||
-- @param s the string
|
||||
-- @param n the size of the indent
|
||||
-- @param ch the character to use when indenting (default ' ')
|
||||
-- @return indented string
|
||||
function text.indent (s,n,ch)
|
||||
assert_arg(1,s,'string')
|
||||
assert_arg(2,s,'number')
|
||||
return _indent(s,string.rep(ch or ' ',n))
|
||||
end
|
||||
|
||||
--- dedent a multiline string by removing any initial indent.
|
||||
-- useful when working with [[..]] strings.
|
||||
-- @param s the string
|
||||
-- @return a string with initial indent zero.
|
||||
function text.dedent (s)
|
||||
assert_arg(1,s,'string')
|
||||
local sl = split(s,'\n')
|
||||
local i1,i2 = sl[1]:find('^%s*')
|
||||
sl = imap(string.sub,sl,i2+1)
|
||||
return concat(sl,'\n')..'\n'
|
||||
end
|
||||
|
||||
--- format a paragraph into lines so that they fit into a line width.
|
||||
-- It will not break long words, so lines can be over the length
|
||||
-- to that extent.
|
||||
-- @param s the string
|
||||
-- @param width the margin width, default 70
|
||||
-- @return a list of lines
|
||||
function text.wrap (s,width)
|
||||
assert_arg(1,s,'string')
|
||||
width = width or 70
|
||||
s = s:gsub('\n',' ')
|
||||
local i,nxt = 1
|
||||
local lines,line = {}
|
||||
while i < #s do
|
||||
nxt = i+width
|
||||
if s:find("[%w']",nxt) then -- inside a word
|
||||
nxt = s:find('%W',nxt+1) -- so find word boundary
|
||||
end
|
||||
line = s:sub(i,nxt)
|
||||
i = i + #line
|
||||
append(lines,strip(line))
|
||||
end
|
||||
return make_list(lines)
|
||||
end
|
||||
|
||||
--- format a paragraph so that it fits into a line width.
|
||||
-- @param s the string
|
||||
-- @param width the margin width, default 70
|
||||
-- @return a string
|
||||
-- @see wrap
|
||||
function text.fill (s,width)
|
||||
return concat(text.wrap(s,width),'\n') .. '\n'
|
||||
end
|
||||
|
||||
local Template = {}
|
||||
text.Template = Template
|
||||
Template.__index = Template
|
||||
setmetatable(Template, {
|
||||
__call = function(obj,tmpl)
|
||||
return Template.new(tmpl)
|
||||
end})
|
||||
|
||||
function Template.new(tmpl)
|
||||
assert_arg(1,tmpl,'string')
|
||||
local res = {}
|
||||
res.tmpl = tmpl
|
||||
setmetatable(res,Template)
|
||||
return res
|
||||
end
|
||||
|
||||
local function _substitute(s,tbl,safe)
|
||||
local subst
|
||||
if is_callable(tbl) then
|
||||
subst = tbl
|
||||
else
|
||||
function subst(f)
|
||||
local s = tbl[f]
|
||||
if not s then
|
||||
if safe then
|
||||
return f
|
||||
else
|
||||
error("not present in table "..f)
|
||||
end
|
||||
else
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
local res = gsub(s,'%${([%w_]+)}',subst)
|
||||
return (gsub(res,'%$([%w_]+)',subst))
|
||||
end
|
||||
|
||||
--- substitute values into a template, throwing an error.
|
||||
-- This will throw an error if no name is found.
|
||||
-- @param tbl a table of name-value pairs.
|
||||
function Template:substitute(tbl)
|
||||
assert_arg(1,tbl,'table')
|
||||
return _substitute(self.tmpl,tbl,false)
|
||||
end
|
||||
|
||||
--- substitute values into a template.
|
||||
-- This version just passes unknown names through.
|
||||
-- @param tbl a table of name-value pairs.
|
||||
function Template:safe_substitute(tbl)
|
||||
assert_arg(1,tbl,'table')
|
||||
return _substitute(self.tmpl,tbl,true)
|
||||
end
|
||||
|
||||
--- substitute values into a template, preserving indentation. <br>
|
||||
-- If the value is a multiline string _or_ a template, it will insert
|
||||
-- the lines at the correct indentation. <br>
|
||||
-- Furthermore, if a template, then that template will be subsituted
|
||||
-- using the same table.
|
||||
-- @param tbl a table of name-value pairs.
|
||||
function Template:indent_substitute(tbl)
|
||||
assert_arg(1,tbl,'table')
|
||||
if not self.strings then
|
||||
self.strings = split(self.tmpl,'\n')
|
||||
end
|
||||
-- the idea is to substitute line by line, grabbing any spaces as
|
||||
-- well as the $var. If the value to be substituted contains newlines,
|
||||
-- then we split that into lines and adjust the indent before inserting.
|
||||
local function subst(line)
|
||||
return line:gsub('(%s*)%$([%w_]+)',function(sp,f)
|
||||
local subtmpl
|
||||
local s = tbl[f]
|
||||
if not s then error("not present in table "..f) end
|
||||
if getmetatable(s) == Template then
|
||||
subtmpl = s
|
||||
s = s.tmpl
|
||||
else
|
||||
s = tostring(s)
|
||||
end
|
||||
if s:find '\n' then
|
||||
s = _indent(s,sp)
|
||||
end
|
||||
if subtmpl then return _substitute(s,tbl)
|
||||
else return s
|
||||
end
|
||||
end)
|
||||
end
|
||||
local lines = imap(subst,self.strings)
|
||||
return concat(lines,'\n')..'\n'
|
||||
end
|
||||
|
||||
------- Python-style formatting operator ------
|
||||
-- (see <a href="http://lua-users.org/wiki/StringInterpolation">the lua-users wiki</a>) --
|
||||
|
||||
function text.format_operator()
|
||||
|
||||
local format = string.format
|
||||
|
||||
-- a more forgiving version of string.format, which applies
|
||||
-- tostring() to any value with a %s format.
|
||||
local function formatx (fmt,...)
|
||||
local args = {...}
|
||||
local i = 1
|
||||
for p in fmt:gmatch('%%.') do
|
||||
if p == '%s' and type(args[i]) ~= 'string' then
|
||||
args[i] = tostring(args[i])
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return format(fmt,unpack(args))
|
||||
end
|
||||
|
||||
local function basic_subst(s,t)
|
||||
return (s:gsub('%$([%w_]+)',t))
|
||||
end
|
||||
|
||||
-- Note this goes further than the original, and will allow these cases:
|
||||
-- 1. a single value
|
||||
-- 2. a list of values
|
||||
-- 3. a map of var=value pairs
|
||||
-- 4. a function, as in gsub
|
||||
-- For the second two cases, it uses $-variable substituion.
|
||||
getmetatable("").__mod = function(a, b)
|
||||
if b == nil then
|
||||
return a
|
||||
elseif type(b) == "table" and getmetatable(b) == nil then
|
||||
if #b == 0 then -- assume a map-like table
|
||||
return _substitute(a,b,true)
|
||||
else
|
||||
return formatx(a,unpack(b))
|
||||
end
|
||||
elseif type(b) == 'function' then
|
||||
return basic_subst(a,b)
|
||||
else
|
||||
return formatx(a,b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return text
|
||||
529
Utils/luarocks/share/lua/5.1/pl/utils.lua
Normal file
529
Utils/luarocks/share/lua/5.1/pl/utils.lua
Normal file
@@ -0,0 +1,529 @@
|
||||
--- Generally useful routines.
|
||||
-- See <a href="../../index.html#utils">the Guide</a>.
|
||||
-- @class module
|
||||
-- @name pl.utils
|
||||
local format,gsub,byte = string.format,string.gsub,string.byte
|
||||
local clock = os.clock
|
||||
local stdout = io.stdout
|
||||
local append = table.insert
|
||||
|
||||
local collisions = {}
|
||||
|
||||
local utils = {}
|
||||
|
||||
utils._VERSION = "0.9.4"
|
||||
|
||||
utils.dir_separator = _G.package.config:sub(1,1)
|
||||
|
||||
--- end this program gracefully.
|
||||
-- @param code The exit code or a message to be printed
|
||||
-- @param ... extra arguments for message's format'
|
||||
-- @see utils.fprintf
|
||||
function utils.quit(code,...)
|
||||
if type(code) == 'string' then
|
||||
utils.fprintf(io.stderr,code,...)
|
||||
code = -1
|
||||
else
|
||||
utils.fprintf(io.stderr,...)
|
||||
end
|
||||
io.stderr:write('\n')
|
||||
os.exit(code)
|
||||
end
|
||||
|
||||
--- print an arbitrary number of arguments using a format.
|
||||
-- @param fmt The format (see string.format)
|
||||
-- @param ... Extra arguments for format
|
||||
function utils.printf(fmt,...)
|
||||
utils.fprintf(stdout,fmt,...)
|
||||
end
|
||||
|
||||
--- write an arbitrary number of arguments to a file using a format.
|
||||
-- @param f File handle to write to.
|
||||
-- @param fmt The format (see string.format).
|
||||
-- @param ... Extra arguments for format
|
||||
function utils.fprintf(f,fmt,...)
|
||||
utils.assert_string(2,fmt)
|
||||
f:write(format(fmt,...))
|
||||
end
|
||||
|
||||
local function import_symbol(T,k,v,libname)
|
||||
local key = rawget(T,k)
|
||||
-- warn about collisions!
|
||||
if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then
|
||||
utils.printf("warning: '%s.%s' overrides existing symbol\n",libname,k)
|
||||
end
|
||||
rawset(T,k,v)
|
||||
end
|
||||
|
||||
local function lookup_lib(T,t)
|
||||
for k,v in pairs(T) do
|
||||
if v == t then return k end
|
||||
end
|
||||
return '?'
|
||||
end
|
||||
|
||||
local already_imported = {}
|
||||
|
||||
--- take a table and 'inject' it into the local namespace.
|
||||
-- @param t The Table
|
||||
-- @param T An optional destination table (defaults to callers environment)
|
||||
function utils.import(t,T)
|
||||
T = T or _G
|
||||
t = t or utils
|
||||
if type(t) == 'string' then
|
||||
t = require (t)
|
||||
end
|
||||
local libname = lookup_lib(T,t)
|
||||
if already_imported[t] then return end
|
||||
already_imported[t] = libname
|
||||
for k,v in pairs(t) do
|
||||
import_symbol(T,k,v,libname)
|
||||
end
|
||||
end
|
||||
|
||||
utils.patterns = {
|
||||
FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*',
|
||||
INTEGER = '[+%-%d]%d*',
|
||||
IDEN = '[%a_][%w_]*',
|
||||
FILE = '[%a%.\\][:%][%w%._%-\\]*'
|
||||
}
|
||||
|
||||
--- escape any 'magic' characters in a string
|
||||
-- @param s The input string
|
||||
function utils.escape(s)
|
||||
utils.assert_string(1,s)
|
||||
return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'))
|
||||
end
|
||||
|
||||
--- return either of two values, depending on a condition.
|
||||
-- @param cond A condition
|
||||
-- @param value1 Value returned if cond is true
|
||||
-- @param value2 Value returned if cond is false (can be optional)
|
||||
function utils.choose(cond,value1,value2)
|
||||
if cond then return value1
|
||||
else return value2
|
||||
end
|
||||
end
|
||||
|
||||
local raise
|
||||
|
||||
--- return the contents of a file as a string
|
||||
-- @param filename The file path
|
||||
-- @param is_bin open in binary mode
|
||||
-- @return file contents
|
||||
function utils.readfile(filename,is_bin)
|
||||
local mode = is_bin and 'b' or ''
|
||||
utils.assert_string(1,filename)
|
||||
local f,err = io.open(filename,'r'..mode)
|
||||
if not f then return utils.raise (err) end
|
||||
local res,err = f:read('*a')
|
||||
f:close()
|
||||
if not res then return raise (err) end
|
||||
return res
|
||||
end
|
||||
|
||||
--- write a string to a file
|
||||
-- @param filename The file path
|
||||
-- @param str The string
|
||||
-- @return true or nil
|
||||
-- @return error message
|
||||
-- @raise error if filename or str aren't strings
|
||||
function utils.writefile(filename,str)
|
||||
utils.assert_string(1,filename)
|
||||
utils.assert_string(2,str)
|
||||
local f,err = io.open(filename,'w')
|
||||
if not f then return raise(err) end
|
||||
f:write(str)
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
--- return the contents of a file as a list of lines
|
||||
-- @param filename The file path
|
||||
-- @return file contents as a table
|
||||
-- @raise errror if filename is not a string
|
||||
function utils.readlines(filename)
|
||||
utils.assert_string(1,filename)
|
||||
local f,err = io.open(filename,'r')
|
||||
if not f then return raise(err) end
|
||||
local res = {}
|
||||
for line in f:lines() do
|
||||
append(res,line)
|
||||
end
|
||||
f:close()
|
||||
return res
|
||||
end
|
||||
|
||||
--- split a string into a list of strings separated by a delimiter.
|
||||
-- @param s The input string
|
||||
-- @param re A Lua string pattern; defaults to '%s+'
|
||||
-- @param plain don't use Lua patterns
|
||||
-- @param n optional maximum number of splits
|
||||
-- @return a list-like table
|
||||
-- @raise error if s is not a string
|
||||
function utils.split(s,re,plain,n)
|
||||
utils.assert_string(1,s)
|
||||
local find,sub,append = string.find, string.sub, table.insert
|
||||
local i1,ls = 1,{}
|
||||
if not re then re = '%s+' end
|
||||
if re == '' then return {s} end
|
||||
while true do
|
||||
local i2,i3 = find(s,re,i1,plain)
|
||||
if not i2 then
|
||||
local last = sub(s,i1)
|
||||
if last ~= '' then append(ls,last) end
|
||||
if #ls == 1 and ls[1] == '' then
|
||||
return {}
|
||||
else
|
||||
return ls
|
||||
end
|
||||
end
|
||||
append(ls,sub(s,i1,i2-1))
|
||||
if n and #ls == n then
|
||||
ls[#ls] = sub(s,i1)
|
||||
return ls
|
||||
end
|
||||
i1 = i3+1
|
||||
end
|
||||
end
|
||||
|
||||
--- split a string into a number of values.
|
||||
-- @param s the string
|
||||
-- @param re the delimiter, default space
|
||||
-- @return n values
|
||||
-- @usage first,next = splitv('jane:doe',':')
|
||||
-- @see split
|
||||
function utils.splitv (s,re)
|
||||
return unpack(utils.split(s,re))
|
||||
end
|
||||
|
||||
local lua52 = table.pack ~= nil
|
||||
local lua51_load = load
|
||||
|
||||
if not lua52 then -- define Lua 5.2 style load()
|
||||
function utils.load(str,src,mode,env)
|
||||
local chunk,err
|
||||
if type(str) == 'string' then
|
||||
chunk,err = loadstring(str,src)
|
||||
else
|
||||
chunk,err = lua51_load(str,src)
|
||||
end
|
||||
if chunk and env then setfenv(chunk,env) end
|
||||
return chunk,err
|
||||
end
|
||||
else
|
||||
utils.load = load
|
||||
-- setfenv/getfenv replacements for Lua 5.2
|
||||
-- by Sergey Rozhenko
|
||||
-- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
|
||||
-- Roberto Ierusalimschy notes that it is possible for getfenv to return nil
|
||||
-- in the case of a function with no globals:
|
||||
-- http://lua-users.org/lists/lua-l/2010-06/msg00315.html
|
||||
function setfenv(f, t)
|
||||
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
|
||||
local name
|
||||
local up = 0
|
||||
repeat
|
||||
up = up + 1
|
||||
name = debug.getupvalue(f, up)
|
||||
until name == '_ENV' or name == nil
|
||||
if name then
|
||||
debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue
|
||||
debug.setupvalue(f, up, t)
|
||||
end
|
||||
end
|
||||
|
||||
function getfenv(f)
|
||||
f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
|
||||
local name, val
|
||||
local up = 0
|
||||
repeat
|
||||
up = up + 1
|
||||
name, val = debug.getupvalue(f, up)
|
||||
until name == '_ENV' or name == nil
|
||||
return val
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- execute a shell command.
|
||||
-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
|
||||
-- @param cmd a shell command
|
||||
-- @return true if successful
|
||||
-- @return actual return code
|
||||
function utils.execute (cmd)
|
||||
local res1,res2,res2 = os.execute(cmd)
|
||||
if not lua52 then
|
||||
return res1==0,res1
|
||||
else
|
||||
return res1,res2
|
||||
end
|
||||
end
|
||||
|
||||
if not lua52 then
|
||||
function table.pack (...)
|
||||
local n = select('#',...)
|
||||
return {n=n; ...},n
|
||||
end
|
||||
local sep = package.config:sub(1,1)
|
||||
function package.searchpath (mod,path)
|
||||
mod = mod:gsub('%.',sep)
|
||||
for m in path:gmatch('[^;]+') do
|
||||
local nm = m:gsub('?',mod)
|
||||
local f = io.open(nm,'r')
|
||||
if f then f:close(); return nm end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not table.pack then table.pack = _G.pack end
|
||||
if not rawget(_G,"pack") then _G.pack = table.pack end
|
||||
|
||||
--- take an arbitrary set of arguments and make into a table.
|
||||
-- This returns the table and the size; works fine for nil arguments
|
||||
-- @param ... arguments
|
||||
-- @return table
|
||||
-- @return table size
|
||||
-- @usage local t,n = utils.args(...)
|
||||
|
||||
--- 'memoize' a function (cache returned value for next call).
|
||||
-- This is useful if you have a function which is relatively expensive,
|
||||
-- but you don't know in advance what values will be required, so
|
||||
-- building a table upfront is wasteful/impossible.
|
||||
-- @param func a function of at least one argument
|
||||
-- @return a function with at least one argument, which is used as the key.
|
||||
function utils.memoize(func)
|
||||
return setmetatable({}, {
|
||||
__index = function(self, k, ...)
|
||||
local v = func(k,...)
|
||||
self[k] = v
|
||||
return v
|
||||
end,
|
||||
__call = function(self, k) return self[k] end
|
||||
})
|
||||
end
|
||||
|
||||
--- is the object either a function or a callable object?.
|
||||
-- @param obj Object to check.
|
||||
function utils.is_callable (obj)
|
||||
return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call
|
||||
end
|
||||
|
||||
--- is the object of the specified type?.
|
||||
-- If the type is a string, then use type, otherwise compare with metatable
|
||||
-- @param obj An object to check
|
||||
-- @param tp String of what type it should be
|
||||
function utils.is_type (obj,tp)
|
||||
if type(tp) == 'string' then return type(obj) == tp end
|
||||
local mt = getmetatable(obj)
|
||||
return tp == mt
|
||||
end
|
||||
|
||||
local fileMT = getmetatable(io.stdout)
|
||||
|
||||
--- a string representation of a type.
|
||||
-- For tables with metatables, we assume that the metatable has a `_name`
|
||||
-- field. Knows about Lua file objects.
|
||||
-- @param obj an object
|
||||
-- @return a string like 'number', 'table' or 'List'
|
||||
function utils.type (obj)
|
||||
local t = type(obj)
|
||||
if t == 'table' or t == 'userdata' then
|
||||
local mt = getmetatable(obj)
|
||||
if mt == fileMT then
|
||||
return 'file'
|
||||
else
|
||||
return mt._name or "unknown "..t
|
||||
end
|
||||
else
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
--- is this number an integer?
|
||||
-- @param x a number
|
||||
-- @raise error if x is not a number
|
||||
function utils.is_integer (x)
|
||||
return math.ceil(x)==x
|
||||
end
|
||||
|
||||
utils.stdmt = {
|
||||
List = {_name='List'}, Map = {_name='Map'},
|
||||
Set = {_name='Set'}, MultiMap = {_name='MultiMap'}
|
||||
}
|
||||
|
||||
local _function_factories = {}
|
||||
|
||||
--- associate a function factory with a type.
|
||||
-- A function factory takes an object of the given type and
|
||||
-- returns a function for evaluating it
|
||||
-- @param mt metatable
|
||||
-- @param fun a callable that returns a function
|
||||
function utils.add_function_factory (mt,fun)
|
||||
_function_factories[mt] = fun
|
||||
end
|
||||
|
||||
local function _string_lambda(f)
|
||||
local raise = utils.raise
|
||||
if f:find '^|' or f:find '_' then
|
||||
local args,body = f:match '|([^|]*)|(.+)'
|
||||
if f:find '_' then
|
||||
args = '_'
|
||||
body = f
|
||||
else
|
||||
if not args then return raise 'bad string lambda' end
|
||||
end
|
||||
local fstr = 'return function('..args..') return '..body..' end'
|
||||
local fn,err = loadstring(fstr)
|
||||
if not fn then return raise(err) end
|
||||
fn = fn()
|
||||
return fn
|
||||
else return raise 'not a string lambda'
|
||||
end
|
||||
end
|
||||
|
||||
--- an anonymous function as a string. This string is either of the form
|
||||
-- '|args| expression' or is a function of one argument, '_'
|
||||
-- @param lf function as a string
|
||||
-- @return a function
|
||||
-- @usage string_lambda '|x|x+1' (2) == 3
|
||||
-- @usage string_lambda '_+1 (2) == 3
|
||||
utils.string_lambda = utils.memoize(_string_lambda)
|
||||
|
||||
local ops
|
||||
|
||||
--- process a function argument.
|
||||
-- This is used throughout Penlight and defines what is meant by a function:
|
||||
-- Something that is callable, or an operator string as defined by <code>pl.operator</code>,
|
||||
-- such as '>' or '#'. If a function factory has been registered for the type, it will
|
||||
-- be called to get the function.
|
||||
-- @param idx argument index
|
||||
-- @param f a function, operator string, or callable object
|
||||
-- @param msg optional error message
|
||||
-- @return a callable
|
||||
-- @raise if idx is not a number or if f is not callable
|
||||
-- @see utils.is_callable
|
||||
function utils.function_arg (idx,f,msg)
|
||||
utils.assert_arg(1,idx,'number')
|
||||
local tp = type(f)
|
||||
if tp == 'function' then return f end -- no worries!
|
||||
-- ok, a string can correspond to an operator (like '==')
|
||||
if tp == 'string' then
|
||||
if not ops then ops = require 'pl.operator'.optable end
|
||||
local fn = ops[f]
|
||||
if fn then return fn end
|
||||
local fn, err = utils.string_lambda(f)
|
||||
if not fn then error(err..': '..f) end
|
||||
return fn
|
||||
elseif tp == 'table' or tp == 'userdata' then
|
||||
local mt = getmetatable(f)
|
||||
if not mt then error('not a callable object',2) end
|
||||
local ff = _function_factories[mt]
|
||||
if not ff then
|
||||
if not mt.__call then error('not a callable object',2) end
|
||||
return f
|
||||
else
|
||||
return ff(f) -- we have a function factory for this type!
|
||||
end
|
||||
end
|
||||
if not msg then msg = " must be callable" end
|
||||
if idx > 0 then
|
||||
error("argument "..idx..": "..msg,2)
|
||||
else
|
||||
error(msg,2)
|
||||
end
|
||||
end
|
||||
|
||||
--- bind the first argument of the function to a value.
|
||||
-- @param fn a function of at least two values (may be an operator string)
|
||||
-- @param p a value
|
||||
-- @return a function such that f(x) is fn(p,x)
|
||||
-- @raise same as @{function_arg}
|
||||
-- @see pl.func.curry
|
||||
function utils.bind1 (fn,p)
|
||||
fn = utils.function_arg(1,fn)
|
||||
return function(...) return fn(p,...) end
|
||||
end
|
||||
|
||||
--- assert that the given argument is in fact of the correct type.
|
||||
-- @param n argument index
|
||||
-- @param val the value
|
||||
-- @param tp the type
|
||||
-- @param verify an optional verfication function
|
||||
-- @param msg an optional custom message
|
||||
-- @param lev optional stack position for trace, default 2
|
||||
-- @raise if the argument n is not the correct type
|
||||
-- @usage assert_arg(1,t,'table')
|
||||
-- @usage assert_arg(n,val,'string',path.isdir,'not a directory')
|
||||
function utils.assert_arg (n,val,tp,verify,msg,lev)
|
||||
if type(val) ~= tp then
|
||||
error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),2)
|
||||
end
|
||||
if verify and not verify(val) then
|
||||
error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2)
|
||||
end
|
||||
end
|
||||
|
||||
--- assert the common case that the argument is a string.
|
||||
-- @param n argument index
|
||||
-- @param val a value that must be a string
|
||||
-- @raise val must be a string
|
||||
function utils.assert_string (n,val)
|
||||
utils.assert_arg(n,val,'string',nil,nil,nil,3)
|
||||
end
|
||||
|
||||
local err_mode = 'default'
|
||||
|
||||
--- control the error strategy used by Penlight.
|
||||
-- Controls how <code>utils.raise</code> works; the default is for it
|
||||
-- to return nil and the error string, but if the mode is 'error' then
|
||||
-- it will throw an error. If mode is 'quit' it will immediately terminate
|
||||
-- the program.
|
||||
-- @param mode - either 'default', 'quit' or 'error'
|
||||
-- @see utils.raise
|
||||
function utils.on_error (mode)
|
||||
err_mode = mode
|
||||
end
|
||||
|
||||
--- used by Penlight functions to return errors. Its global behaviour is controlled
|
||||
-- by <code>utils.on_error</code>
|
||||
-- @param err the error string.
|
||||
-- @see utils.on_error
|
||||
function utils.raise (err)
|
||||
if err_mode == 'default' then return nil,err
|
||||
elseif err_mode == 'quit' then utils.quit(err)
|
||||
else error(err,2)
|
||||
end
|
||||
end
|
||||
|
||||
raise = utils.raise
|
||||
|
||||
--- load a code string or bytecode chunk.
|
||||
-- @param code Lua code as a string or bytecode
|
||||
-- @param name for source errors
|
||||
-- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default)
|
||||
-- @param env the environment for the new chunk (default nil)
|
||||
-- @return compiled chunk
|
||||
-- @return error message (chunk is nil)
|
||||
-- @function utils.load
|
||||
|
||||
|
||||
--- Lua 5.2 Compatible Functions
|
||||
-- @section lua52
|
||||
|
||||
--- pack an argument list into a table.
|
||||
-- @param ... any arguments
|
||||
-- @return a table with field n set to the length
|
||||
-- @return the length
|
||||
-- @function table.pack
|
||||
|
||||
------
|
||||
-- return the full path where a Lua module name would be matched.
|
||||
-- @param mod module name, possibly dotted
|
||||
-- @param path a path in the same form as package.path or package.cpath
|
||||
-- @see path.package_path
|
||||
-- @function package.searchpath
|
||||
|
||||
return utils
|
||||
|
||||
|
||||
676
Utils/luarocks/share/lua/5.1/pl/xml.lua
Normal file
676
Utils/luarocks/share/lua/5.1/pl/xml.lua
Normal file
@@ -0,0 +1,676 @@
|
||||
--- XML LOM Utilities.
|
||||
-- This implements some useful things on LOM documents, such as returned by lxp.lom.parse.
|
||||
-- In particular, it can convert LOM back into XML text, with optional pretty-printing control.
|
||||
-- It's based on stanza.lua from Prosody http://hg.prosody.im/trunk/file/4621c92d2368/util/stanza.lua)
|
||||
--
|
||||
-- Can be used as a lightweight one-stop-shop for simple XML processing; a simple XML parser is included
|
||||
-- but the default is to use lxp.lom if it can be found.
|
||||
-- <pre>
|
||||
-- Prosody IM
|
||||
-- Copyright (C) 2008-2010 Matthew Wild
|
||||
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||
--
|
||||
-- classic Lua XML parser by Roberto Ierusalimschy.
|
||||
-- modified to output LOM format.
|
||||
-- http://lua-users.org/wiki/LuaXml
|
||||
-- </pre>
|
||||
-- @module pl.xml
|
||||
|
||||
local t_insert = table.insert;
|
||||
local t_concat = table.concat;
|
||||
local t_remove = table.remove;
|
||||
local s_format = string.format;
|
||||
local s_match = string.match;
|
||||
local tostring = tostring;
|
||||
local setmetatable = setmetatable;
|
||||
local getmetatable = getmetatable;
|
||||
local pairs = pairs;
|
||||
local ipairs = ipairs;
|
||||
local type = type;
|
||||
local next = next;
|
||||
local print = print;
|
||||
local unpack = unpack or table.unpack;
|
||||
local s_gsub = string.gsub;
|
||||
local s_char = string.char;
|
||||
local s_find = string.find;
|
||||
local os = os;
|
||||
local pcall,require,io = pcall,require,io
|
||||
local split = require 'pl.utils'.split
|
||||
|
||||
local _M = {}
|
||||
local Doc = { __type = "doc" };
|
||||
Doc.__index = Doc;
|
||||
|
||||
--- create a new document node.
|
||||
-- @param tag the tag name
|
||||
-- @param attr optional attributes (table of name-value pairs)
|
||||
function _M.new(tag, attr)
|
||||
local doc = { tag = tag, attr = attr or {}, last_add = {}};
|
||||
return setmetatable(doc, Doc);
|
||||
end
|
||||
|
||||
--- parse an XML document. By default, this uses lxp.lom.parse, but
|
||||
-- falls back to basic_parse, or if use_basic is true
|
||||
-- @param text_or_file file or string representation
|
||||
-- @param is_file whether text_or_file is a file name or not
|
||||
-- @param use_basic do a basic parse
|
||||
-- @return a parsed LOM document with the document metatatables set
|
||||
-- @return nil, error the error can either be a file error or a parse error
|
||||
function _M.parse(text_or_file, is_file, use_basic)
|
||||
local parser,status,lom
|
||||
if use_basic then parser = _M.basic_parse
|
||||
else
|
||||
status,lom = pcall(require,'lxp.lom')
|
||||
if not status then parser = _M.basic_parse else parser = lom.parse end
|
||||
end
|
||||
if is_file then
|
||||
local f,err = io.open(text_or_file)
|
||||
if not f then return nil,err end
|
||||
text_or_file = f:read '*a'
|
||||
f:close()
|
||||
end
|
||||
local doc,err = parser(text_or_file)
|
||||
if not doc then return nil,err end
|
||||
if lom then
|
||||
_M.walk(doc,false,function(_,d)
|
||||
setmetatable(d,Doc)
|
||||
end)
|
||||
end
|
||||
return doc
|
||||
end
|
||||
|
||||
---- convenient function to add a document node, This updates the last inserted position.
|
||||
-- @param tag a tag name
|
||||
-- @param attrs optional set of attributes (name-string pairs)
|
||||
function Doc:addtag(tag, attrs)
|
||||
local s = _M.new(tag, attrs);
|
||||
(self.last_add[#self.last_add] or self):add_direct_child(s);
|
||||
t_insert(self.last_add, s);
|
||||
return self;
|
||||
end
|
||||
|
||||
--- convenient function to add a text node. This updates the last inserted position.
|
||||
-- @param text a string
|
||||
function Doc:text(text)
|
||||
(self.last_add[#self.last_add] or self):add_direct_child(text);
|
||||
return self;
|
||||
end
|
||||
|
||||
---- go up one level in a document
|
||||
function Doc:up()
|
||||
t_remove(self.last_add);
|
||||
return self;
|
||||
end
|
||||
|
||||
function Doc:reset()
|
||||
local last_add = self.last_add;
|
||||
for i = 1,#last_add do
|
||||
last_add[i] = nil;
|
||||
end
|
||||
return self;
|
||||
end
|
||||
|
||||
--- append a child to a document directly.
|
||||
-- @param child a child node (either text or a document)
|
||||
function Doc:add_direct_child(child)
|
||||
t_insert(self, child);
|
||||
end
|
||||
|
||||
--- append a child to a document at the last element added
|
||||
-- @param child a child node (either text or a document)
|
||||
function Doc:add_child(child)
|
||||
(self.last_add[#self.last_add] or self):add_direct_child(child);
|
||||
return self;
|
||||
end
|
||||
|
||||
--accessing attributes: useful not to have to expose implementation (attr)
|
||||
--but also can allow attr to be nil in any future optimizations
|
||||
|
||||
--- set attributes of a document node.
|
||||
-- @param t a table containing attribute/value pairs
|
||||
function Doc:set_attribs (t)
|
||||
for k,v in pairs(t) do
|
||||
self.attr[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
--- set a single attribute of a document node.
|
||||
-- @param a attribute
|
||||
-- @param v its value
|
||||
function Doc:set_attrib(a,v)
|
||||
self.attr[a] = v
|
||||
end
|
||||
|
||||
--- access the attributes of a document node.
|
||||
function Doc:get_attribs()
|
||||
return self.attr
|
||||
end
|
||||
|
||||
--- function to create an element with a given tag name and a set of children.
|
||||
-- @param tag a tag name
|
||||
-- @param items either text or a table where the hash part is the attributes and the list part is the children.
|
||||
function _M.elem(tag,items)
|
||||
local s = _M.new(tag)
|
||||
if type(items) == 'string' then items = {items} end
|
||||
if _M.is_tag(items) then
|
||||
t_insert(s,items)
|
||||
elseif type(items) == 'table' then
|
||||
for k,v in pairs(items) do
|
||||
if type(k) == 'string' then
|
||||
s.attr[k] = v
|
||||
t_insert(s.attr,k)
|
||||
else
|
||||
s[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
--- given a list of names, return a number of element constructors.
|
||||
-- @param list a list of names, or a comma-separated string.
|
||||
-- @usage local parent,children = doc.tags 'parent,children' <br>
|
||||
-- doc = parent {child 'one', child 'two'}
|
||||
function _M.tags(list)
|
||||
local ctors = {}
|
||||
local elem = _M.elem
|
||||
if type(list) == 'string' then list = split(list,'%s*,%s*') end
|
||||
for _,tag in ipairs(list) do
|
||||
local ctor = function(items) return _M.elem(tag,items) end
|
||||
t_insert(ctors,ctor)
|
||||
end
|
||||
return unpack(ctors)
|
||||
end
|
||||
|
||||
local templ_cache = {}
|
||||
|
||||
local function is_data(data)
|
||||
return #data == 0 or type(data[1]) ~= 'table'
|
||||
end
|
||||
|
||||
local function prepare_data(data)
|
||||
-- a hack for ensuring that $1 maps to first element of data, etc.
|
||||
-- Either this or could change the gsub call just below.
|
||||
for i,v in ipairs(data) do
|
||||
data[tostring(i)] = v
|
||||
end
|
||||
end
|
||||
|
||||
--- create a substituted copy of a document,
|
||||
-- @param templ may be a document or a string representation which will be parsed and cached
|
||||
-- @param data a table of name-value pairs or a list of such tables
|
||||
-- @return an XML document
|
||||
function Doc.subst(templ, data)
|
||||
if type(data) ~= 'table' or not next(data) then return nil, "data must be a non-empty table" end
|
||||
if is_data(data) then
|
||||
prepare_data(data)
|
||||
end
|
||||
if type(templ) == 'string' then
|
||||
if templ_cache[templ] then
|
||||
templ = templ_cache[templ]
|
||||
else
|
||||
local str,err = templ
|
||||
templ,err = _M.parse(str)
|
||||
if not templ then return nil,err end
|
||||
templ_cache[str] = templ
|
||||
end
|
||||
end
|
||||
local function _subst(item)
|
||||
return _M.clone(templ,function(s)
|
||||
return s:gsub('%$(%w+)',item)
|
||||
end)
|
||||
end
|
||||
if is_data(data) then return _subst(data) end
|
||||
local list = {}
|
||||
for _,item in ipairs(data) do
|
||||
prepare_data(item)
|
||||
t_insert(list,_subst(item))
|
||||
end
|
||||
if data.tag then
|
||||
list = _M.elem(data.tag,list)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
|
||||
--- get the first child with a given tag name.
|
||||
-- @param tag the tag name
|
||||
function Doc:child_with_name(tag)
|
||||
for _, child in ipairs(self) do
|
||||
if child.tag == tag then return child; end
|
||||
end
|
||||
end
|
||||
|
||||
local _children_with_name
|
||||
function _children_with_name(self,tag,list,recurse)
|
||||
for _, child in ipairs(self) do if type(child) == 'table' then
|
||||
if child.tag == tag then t_insert(list,child) end
|
||||
if recurse then _children_with_name(child,tag,list,recurse) end
|
||||
end end
|
||||
end
|
||||
|
||||
--- get all elements in a document that have a given tag.
|
||||
-- @param tag a tag name
|
||||
-- @param dont_recurse optionally only return the immediate children with this tag name
|
||||
-- @return a list of elements
|
||||
function Doc:get_elements_with_name(tag,dont_recurse)
|
||||
local res = {}
|
||||
_children_with_name(self,tag,res,not dont_recurse)
|
||||
return res
|
||||
end
|
||||
|
||||
-- iterate over all children of a document node, including text nodes.
|
||||
function Doc:children()
|
||||
local i = 0;
|
||||
return function (a)
|
||||
i = i + 1
|
||||
return a[i];
|
||||
end, self, i;
|
||||
end
|
||||
|
||||
-- return the first child element of a node, if it exists.
|
||||
function Doc:first_childtag()
|
||||
if #self == 0 then return end
|
||||
for _,t in ipairs(self) do
|
||||
if type(t) == 'table' then return t end
|
||||
end
|
||||
end
|
||||
|
||||
function Doc:matching_tags(tag, xmlns)
|
||||
xmlns = xmlns or self.attr.xmlns;
|
||||
local tags = self;
|
||||
local start_i, max_i = 1, #tags;
|
||||
return function ()
|
||||
for i=start_i,max_i do
|
||||
v = tags[i];
|
||||
if (not tag or v.tag == tag)
|
||||
and (not xmlns or xmlns == v.attr.xmlns) then
|
||||
start_i = i+1;
|
||||
return v;
|
||||
end
|
||||
end
|
||||
end, tags, i;
|
||||
end
|
||||
|
||||
--- iterate over all child elements of a document node.
|
||||
function Doc:childtags()
|
||||
local i = 0;
|
||||
return function (a)
|
||||
local v
|
||||
repeat
|
||||
i = i + 1
|
||||
v = self[i]
|
||||
if v and type(v) == 'table' then return v; end
|
||||
until not v
|
||||
end, self[1], i;
|
||||
end
|
||||
|
||||
--- visit child element of a node and call a function, possibility modifying the document.
|
||||
-- @param callback a function passed the node (text or element). If it returns nil, that node will be removed.
|
||||
-- If it returns a value, that will replace the current node.
|
||||
function Doc:maptags(callback)
|
||||
local is_tag = _M.is_tag
|
||||
local i = 1;
|
||||
while i <= #self do
|
||||
if is_tag(self[i]) then
|
||||
local ret = callback(self[i]);
|
||||
if ret == nil then
|
||||
t_remove(self, i);
|
||||
else
|
||||
self[i] = ret;
|
||||
i = i + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
return self;
|
||||
end
|
||||
|
||||
local xml_escape
|
||||
do
|
||||
local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
|
||||
function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
|
||||
_M.xml_escape = xml_escape;
|
||||
end
|
||||
|
||||
-- pretty printing
|
||||
-- if indent, then put each new tag on its own line
|
||||
-- if attr_indent, put each new attribute on its own line
|
||||
local function _dostring(t, buf, self, xml_escape, parentns, idn, indent, attr_indent)
|
||||
local nsid = 0;
|
||||
local tag = t.tag
|
||||
local lf,alf = ""," "
|
||||
if indent then lf = '\n'..idn end
|
||||
if attr_indent then alf = '\n'..idn..attr_indent end
|
||||
t_insert(buf, lf.."<"..tag);
|
||||
for k, v in pairs(t.attr) do
|
||||
if type(k) ~= 'number' then -- LOM attr table has list-like part
|
||||
if s_find(k, "\1", 1, true) then
|
||||
local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
|
||||
nsid = nsid + 1;
|
||||
t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
|
||||
elseif not(k == "xmlns" and v == parentns) then
|
||||
t_insert(buf, alf..k.."='"..xml_escape(v).."'");
|
||||
end
|
||||
end
|
||||
end
|
||||
local len,has_children = #t;
|
||||
if len == 0 then
|
||||
local out = "/>"
|
||||
if attr_indent then out = '\n'..idn..out end
|
||||
t_insert(buf, out);
|
||||
else
|
||||
t_insert(buf, ">");
|
||||
for n=1,len do
|
||||
local child = t[n];
|
||||
if child.tag then
|
||||
self(child, buf, self, xml_escape, t.attr.xmlns,idn and idn..indent, indent, attr_indent );
|
||||
has_children = true
|
||||
else -- text element
|
||||
t_insert(buf, xml_escape(child));
|
||||
end
|
||||
end
|
||||
t_insert(buf, (has_children and lf or '').."</"..tag..">");
|
||||
end
|
||||
end
|
||||
|
||||
---- pretty-print an XML document
|
||||
--- @param t an XML document
|
||||
--- @param idn an initial indent (indents are all strings)
|
||||
--- @param indent an indent for each level
|
||||
--- @param attr_indent if given, indent each attribute pair and put on a separate line
|
||||
--- @return a string representation
|
||||
function _M.tostring(t,idn,indent, attr_indent)
|
||||
local buf = {};
|
||||
_dostring(t, buf, _dostring, xml_escape, nil,idn,indent, attr_indent);
|
||||
return t_concat(buf);
|
||||
end
|
||||
|
||||
Doc.__tostring = _M.tostring
|
||||
|
||||
--- get the full text value of an element
|
||||
function Doc:get_text()
|
||||
local res = {}
|
||||
for i,el in ipairs(self) do
|
||||
if type(el) == 'string' then t_insert(res,el) end
|
||||
end
|
||||
return t_concat(res);
|
||||
end
|
||||
|
||||
--- make a copy of a document
|
||||
-- @param doc the original document
|
||||
-- @param strsubst an optional function for handling string copying which could do substitution, etc.
|
||||
function _M.clone(doc, strsubst)
|
||||
local lookup_table = {};
|
||||
local function _copy(object)
|
||||
if type(object) ~= "table" then
|
||||
if strsubst and type(object) == 'string' then return strsubst(object)
|
||||
else return object;
|
||||
end
|
||||
elseif lookup_table[object] then
|
||||
return lookup_table[object];
|
||||
end
|
||||
local new_table = {};
|
||||
lookup_table[object] = new_table;
|
||||
for index, value in pairs(object) do
|
||||
new_table[_copy(index)] = _copy(value); -- is cloning keys much use, hm?
|
||||
end
|
||||
return setmetatable(new_table, getmetatable(object));
|
||||
end
|
||||
|
||||
return _copy(doc)
|
||||
end
|
||||
|
||||
--- compare two documents.
|
||||
-- @param t1 any value
|
||||
-- @param t2 any value
|
||||
function _M.compare(t1,t2)
|
||||
local ty1 = type(t1)
|
||||
local ty2 = type(t2)
|
||||
if ty1 ~= ty2 then return false, 'type mismatch' end
|
||||
if ty1 == 'string' then
|
||||
return t1 == t2 and true or 'text '..t1..' ~= text '..t2
|
||||
end
|
||||
if ty1 ~= 'table' or ty2 ~= 'table' then return false, 'not a document' end
|
||||
if t1.tag ~= t2.tag then return false, 'tag '..t1.tag..' ~= tag '..t2.tag end
|
||||
if #t1 ~= #t2 then return false, 'size '..#t1..' ~= size '..#t2..' for tag '..t1.tag end
|
||||
-- compare attributes
|
||||
for k,v in pairs(t1.attr) do
|
||||
if t2.attr[k] ~= v then return false, 'mismatch attrib' end
|
||||
end
|
||||
for k,v in pairs(t2.attr) do
|
||||
if t1.attr[k] ~= v then return false, 'mismatch attrib' end
|
||||
end
|
||||
-- compare children
|
||||
for i = 1,#t1 do
|
||||
local yes,err = _M.compare(t1[i],t2[i])
|
||||
if not yes then return err end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- is this value a document element?
|
||||
-- @param d any value
|
||||
function _M.is_tag(d)
|
||||
return type(d) == 'table' and type(d.tag) == 'string'
|
||||
end
|
||||
|
||||
--- call the desired function recursively over the document.
|
||||
-- @param doc the document
|
||||
-- @param depth_first visit child notes first, then the current node
|
||||
-- @param operation a function which will receive the current tag name and current node.
|
||||
function _M.walk (doc, depth_first, operation)
|
||||
if not depth_first then operation(doc.tag,doc) end
|
||||
for _,d in ipairs(doc) do
|
||||
if _M.is_tag(d) then
|
||||
_M.walk(d,depth_first,operation)
|
||||
end
|
||||
end
|
||||
if depth_first then operation(doc.tag,doc) end
|
||||
end
|
||||
|
||||
local escapes = { quot = "\"", apos = "'", lt = "<", gt = ">", amp = "&" }
|
||||
local function unescape(str) return (str:gsub( "&(%a+);", escapes)); end
|
||||
|
||||
local function parseargs(s)
|
||||
local arg = {}
|
||||
s:gsub("([%w:]+)%s*=%s*([\"'])(.-)%2", function (w, _, a)
|
||||
arg[w] = unescape(a)
|
||||
end)
|
||||
return arg
|
||||
end
|
||||
|
||||
--- Parse a simple XML document using a pure Lua parser based on Robero Ierusalimschy's original version.
|
||||
-- @param s the XML document to be parsed.
|
||||
-- @param all_text if true, preserves all whitespace. Otherwise only text containing non-whitespace is included.
|
||||
function _M.basic_parse(s,all_text)
|
||||
local t_insert,t_remove = table.insert,table.remove
|
||||
local s_find,s_sub = string.find,string.sub
|
||||
local stack = {}
|
||||
local top = {}
|
||||
t_insert(stack, top)
|
||||
local ni,c,label,xarg, empty
|
||||
local i, j = 1, 1
|
||||
-- we're not interested in <?xml version="1.0"?>
|
||||
local _,istart = s_find(s,'^%s*<%?[^%?]+%?>%s*')
|
||||
if istart then i = istart+1 end
|
||||
while true do
|
||||
ni,j,c,label,xarg, empty = s_find(s, "<(%/?)([%w:%-_]+)(.-)(%/?)>", i)
|
||||
if not ni then break end
|
||||
local text = s_sub(s, i, ni-1)
|
||||
if all_text or not s_find(text, "^%s*$") then
|
||||
t_insert(top, unescape(text))
|
||||
end
|
||||
if empty == "/" then -- empty element tag
|
||||
t_insert(top, setmetatable({tag=label, attr=parseargs(xarg), empty=1},Doc))
|
||||
elseif c == "" then -- start tag
|
||||
top = setmetatable({tag=label, attr=parseargs(xarg)},Doc)
|
||||
t_insert(stack, top) -- new level
|
||||
else -- end tag
|
||||
local toclose = t_remove(stack) -- remove top
|
||||
top = stack[#stack]
|
||||
if #stack < 1 then
|
||||
error("nothing to close with "..label)
|
||||
end
|
||||
if toclose.tag ~= label then
|
||||
error("trying to close "..toclose.tag.." with "..label)
|
||||
end
|
||||
t_insert(top, toclose)
|
||||
end
|
||||
i = j+1
|
||||
end
|
||||
local text = s_sub(s, i)
|
||||
if all_text or not s_find(text, "^%s*$") then
|
||||
t_insert(stack[#stack], unescape(text))
|
||||
end
|
||||
if #stack > 1 then
|
||||
error("unclosed "..stack[#stack].tag)
|
||||
end
|
||||
local res = stack[1]
|
||||
return type(res[1])=='string' and res[2] or res[1]
|
||||
end
|
||||
|
||||
local function empty(attr) return not attr or not next(attr) end
|
||||
local function is_text(s) return type(s) == 'string' end
|
||||
local function is_element(d) return type(d) == 'table' and d.tag ~= nil end
|
||||
|
||||
-- returns the key,value pair from a table if it has exactly one entry
|
||||
local function has_one_element(t)
|
||||
local key,value = next(t)
|
||||
if next(t,key) ~= nil then return false end
|
||||
return key,value
|
||||
end
|
||||
|
||||
local function append_capture(res,tbl)
|
||||
if not empty(tbl) then -- no point in capturing empty tables...
|
||||
local key
|
||||
if tbl._ then -- if $_ was set then it is meant as the top-level key for the captured table
|
||||
key = tbl._
|
||||
tbl._ = nil
|
||||
if empty(tbl) then return end
|
||||
end
|
||||
-- a table with only one pair {[0]=value} shall be reduced to that value
|
||||
local numkey,val = has_one_element(tbl)
|
||||
if numkey == 0 then tbl = val end
|
||||
if key then
|
||||
res[key] = tbl
|
||||
else -- otherwise, we append the captured table
|
||||
t_insert(res,tbl)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function make_number(pat)
|
||||
if pat:find '^%d+$' then -- $1 etc means use this as an array location
|
||||
pat = tonumber(pat)
|
||||
end
|
||||
return pat
|
||||
end
|
||||
|
||||
local function capture_attrib(res,pat,value)
|
||||
pat = make_number(pat:sub(2))
|
||||
res[pat] = value
|
||||
return true
|
||||
end
|
||||
|
||||
local match
|
||||
function match(d,pat,res,keep_going)
|
||||
local ret = true
|
||||
if d == nil then return false end
|
||||
-- attribute string matching is straight equality, except if the pattern is a $ capture,
|
||||
-- which always succeeds.
|
||||
if type(d) == 'string' then
|
||||
if type(pat) ~= 'string' then return false end
|
||||
if _M.debug then print(d,pat) end
|
||||
if pat:find '^%$' then
|
||||
return capture_attrib(res,pat,d)
|
||||
else
|
||||
return d == pat
|
||||
end
|
||||
else
|
||||
if _M.debug then print(d.tag,pat.tag) end
|
||||
-- this is an element node. For a match to succeed, the attributes must
|
||||
-- match as well.
|
||||
-- a tagname in the pattern ending with '-' is a wildcard and matches like an attribute
|
||||
local tagpat = pat.tag:match '^(.-)%-$'
|
||||
if tagpat then
|
||||
tagpat = make_number(tagpat)
|
||||
res[tagpat] = d.tag
|
||||
end
|
||||
if d.tag == pat.tag or tagpat then
|
||||
|
||||
if not empty(pat.attr) then
|
||||
if empty(d.attr) then ret = false
|
||||
else
|
||||
for prop,pval in pairs(pat.attr) do
|
||||
local dval = d.attr[prop]
|
||||
if not match(dval,pval,res) then ret = false; break end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- the pattern may have child nodes. We match partially, so that {P1,P2} shall match {X,P1,X,X,P2,..}
|
||||
if ret and #pat > 0 then
|
||||
local i,j = 1,1
|
||||
local function next_elem()
|
||||
j = j + 1 -- next child element of data
|
||||
if is_text(d[j]) then j = j + 1 end
|
||||
return j <= #d
|
||||
end
|
||||
repeat
|
||||
local p = pat[i]
|
||||
-- repeated {{<...>}} patterns shall match one or more elements
|
||||
-- so e.g. {P+} will match {X,X,P,P,X,P,X,X,X}
|
||||
if is_element(p) and p.repeated then
|
||||
local found
|
||||
repeat
|
||||
local tbl = {}
|
||||
ret = match(d[j],p,tbl,false)
|
||||
if ret then
|
||||
found = false --true
|
||||
append_capture(res,tbl)
|
||||
end
|
||||
until not next_elem() or (found and not ret)
|
||||
i = i + 1
|
||||
else
|
||||
ret = match(d[j],p,res,false)
|
||||
if ret then i = i + 1 end
|
||||
end
|
||||
until not next_elem() or i > #pat -- run out of elements or patterns to match
|
||||
-- if every element in our pattern matched ok, then it's been a successful match
|
||||
if i > #pat then return true end
|
||||
end
|
||||
if ret then return true end
|
||||
else
|
||||
ret = false
|
||||
end
|
||||
-- keep going anyway - look at the children!
|
||||
if keep_going then
|
||||
for child in d:childtags() do
|
||||
ret = match(child,pat,res,keep_going)
|
||||
if ret then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function Doc:match(pat)
|
||||
if is_text(pat) then
|
||||
pat = _M.parse(pat,false,true)
|
||||
end
|
||||
_M.walk(pat,false,function(_,d)
|
||||
if is_text(d[1]) and is_element(d[2]) and is_text(d[3]) and
|
||||
d[1]:find '%s*{{' and d[3]:find '}}%s*' then
|
||||
t_remove(d,1)
|
||||
t_remove(d,2)
|
||||
d[1].repeated = true
|
||||
end
|
||||
end)
|
||||
|
||||
local res = {}
|
||||
local ret = match(self,pat,res,true)
|
||||
return res,ret
|
||||
end
|
||||
|
||||
|
||||
return _M
|
||||
|
||||
106
Utils/luarocks/share/lua/5.1/template/file.lua
Normal file
106
Utils/luarocks/share/lua/5.1/template/file.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return[[#
|
||||
<div id="content">
|
||||
# --
|
||||
# -- Module name
|
||||
# --
|
||||
# if _file.name then
|
||||
<h$(i)>Module <code>$(_file.name)</code></h$(i)>
|
||||
# end
|
||||
# --
|
||||
# -- Descriptions
|
||||
# --
|
||||
# if _file.shortdescription then
|
||||
$( format(_file.shortdescription) )
|
||||
# end
|
||||
# if _file.description and #_file.description > 0 then
|
||||
$( format(_file.description) )
|
||||
# end
|
||||
# --
|
||||
# -- Handle "@usage" special tag
|
||||
# --
|
||||
#if _file.metadata and _file.metadata.usage then
|
||||
$( applytemplate(_file.metadata.usage, i+1) )
|
||||
#end
|
||||
# --
|
||||
# -- Show quick description of current type
|
||||
# --
|
||||
#
|
||||
# -- show quick description for globals
|
||||
# if not isempty(_file.globalvars) then
|
||||
<h$(i+1)>Global(s)</h$(i+1)>
|
||||
<table class="function_list">
|
||||
# for _, item in sortedpairs(_file.globalvars) do
|
||||
<tr>
|
||||
<td class="name" nowrap="nowrap">$( fulllinkto(item) )</td>
|
||||
<td class="summary">$( format(item.shortdescription) )</td>
|
||||
</tr>
|
||||
# end
|
||||
</table>
|
||||
# end
|
||||
#
|
||||
# -- get type corresponding to this file (module)
|
||||
# local currenttype
|
||||
# local typeref = _file:moduletyperef()
|
||||
# if typeref and typeref.tag == "internaltyperef" then
|
||||
# local typedef = _file.types[typeref.typename]
|
||||
# if typedef and typedef.tag == "recordtypedef" then
|
||||
# currenttype = typedef
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# -- show quick description type exposed by module
|
||||
# if currenttype and not isempty(currenttype.fields) then
|
||||
<h$(i+1)><a id="$(anchor(currenttype))" >Type <code>$(currenttype.name)</code></a></h$(i+1)>
|
||||
$( applytemplate(currenttype, i+2, 'index') )
|
||||
# end
|
||||
# --
|
||||
# -- Show quick description of other types
|
||||
# --
|
||||
# if _file.types then
|
||||
# for name, type in sortedpairs( _file.types ) do
|
||||
# if type ~= currenttype and type.tag == 'recordtypedef' and not isempty(type.fields) then
|
||||
<h$(i+1)><a id="$(anchor(type))">Type <code>$(name)</code></a></h$(i+1)>
|
||||
$( applytemplate(type, i+2, 'index') )
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# --
|
||||
# -- Long description of globals
|
||||
# --
|
||||
# if not isempty(_file.globalvars) then
|
||||
<h$(i+1)>Global(s)</h$(i+1)>
|
||||
# for name, item in sortedpairs(_file.globalvars) do
|
||||
$( applytemplate(item, i+2) )
|
||||
# end
|
||||
# end
|
||||
# --
|
||||
# -- Long description of current type
|
||||
# --
|
||||
# if currenttype then
|
||||
<h$(i+1)><a id="$(anchor(currenttype))" >Type <code>$(currenttype.name)</code></a></h$(i+1)>
|
||||
$( applytemplate(currenttype, i+2) )
|
||||
# end
|
||||
# --
|
||||
# -- Long description of other types
|
||||
# --
|
||||
# if not isempty( _file.types ) then
|
||||
# for name, type in sortedpairs( _file.types ) do
|
||||
# if type ~= currenttype and type.tag == 'recordtypedef' then
|
||||
<h$(i+1)><a id="$(anchor(type))" >Type <code>$(name)</code></a></h$(i+1)>
|
||||
$( applytemplate(type, i+2) )
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
</div>
|
||||
]]
|
||||
28
Utils/luarocks/share/lua/5.1/template/index.lua
Normal file
28
Utils/luarocks/share/lua/5.1/template/index.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return
|
||||
[[#
|
||||
#if _index.modules then
|
||||
<div id="content">
|
||||
<h2>Module$( #_index.modules > 1 and 's' )</h2>
|
||||
<table class="module_list">
|
||||
# for _, module in sortedpairs( _index.modules ) do
|
||||
# if module.tag ~= 'index' then
|
||||
<tr>
|
||||
<td class="name" nowrap="nowrap">$( fulllinkto(module) )</td>
|
||||
<td class="summary">$( module.description and format(module.shortdescription) )</td>
|
||||
</tr>
|
||||
# end
|
||||
# end
|
||||
</table>
|
||||
</div>
|
||||
#end ]]
|
||||
@@ -0,0 +1,23 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return [[#
|
||||
# if not isempty(_recordtypedef.fields) then
|
||||
<table class="function_list">
|
||||
# for _, item in sortedpairs( _recordtypedef.fields ) do
|
||||
<tr>
|
||||
<td class="name" nowrap="nowrap">$( fulllinkto(item) )</td>
|
||||
<td class="summary">$( format(item.shortdescription) )</td>
|
||||
</tr>
|
||||
# end
|
||||
</table>
|
||||
# end
|
||||
# ]]
|
||||
167
Utils/luarocks/share/lua/5.1/template/item.lua
Normal file
167
Utils/luarocks/share/lua/5.1/template/item.lua
Normal file
@@ -0,0 +1,167 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return
|
||||
[[<dl class="function">
|
||||
<dt>
|
||||
# --
|
||||
# -- Resolve item type definition
|
||||
# --
|
||||
# local typedef = _item:resolvetype()
|
||||
|
||||
# --
|
||||
# -- Show item type for internal type
|
||||
# --
|
||||
#if _item.type and (not typedef or typedef.tag ~= 'functiontypedef') then
|
||||
# --Show link only when available
|
||||
# local link = fulllinkto(_item.type)
|
||||
# if link then
|
||||
<em>$( link )</em>
|
||||
# else
|
||||
<em>$(prettyname(_item.type))</em>
|
||||
# end
|
||||
#end
|
||||
<a id="$(anchor(_item))" >
|
||||
<strong>$( prettyname(_item) )</strong>
|
||||
</a>
|
||||
</dt>
|
||||
<dd>
|
||||
# if _item.shortdescription then
|
||||
$( format(_item.shortdescription) )
|
||||
# end
|
||||
# if _item.description and #_item.description > 0 then
|
||||
$( format(_item.description) )
|
||||
# end
|
||||
#
|
||||
# --
|
||||
# -- For function definitions, describe parameters and return values
|
||||
# --
|
||||
#if typedef and typedef.tag == 'functiontypedef' then
|
||||
# --
|
||||
# -- Describe parameters
|
||||
# --
|
||||
# local fdef = typedef
|
||||
#
|
||||
# -- Adjust parameter count if first one is 'self'
|
||||
# local paramcount
|
||||
# if #fdef.params > 0 and isinvokable(_item) then
|
||||
# paramcount = #fdef.params - 1
|
||||
# else
|
||||
# paramcount = #fdef.params
|
||||
# end
|
||||
#
|
||||
# -- List parameters
|
||||
# if paramcount > 0 then
|
||||
<h$(i)>Parameter$( paramcount > 1 and 's' )</h$(i)>
|
||||
<ul>
|
||||
# for position, param in ipairs( fdef.params ) do
|
||||
# if not (position == 1 and isinvokable(_item)) then
|
||||
<li>
|
||||
# local paramline = "<code><em>"
|
||||
# if param.type then
|
||||
# local link = linkto( param.type )
|
||||
# local name = prettyname( param.type )
|
||||
# if link then
|
||||
# paramline = paramline .. '<a href=\"' .. link .. '\">' .. name .. "</a>"
|
||||
# else
|
||||
# paramline = paramline .. name
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# paramline = paramline .. " " .. param.name .. " "
|
||||
#
|
||||
# if param.optional then
|
||||
# paramline = paramline .. "optional" .. " "
|
||||
# end
|
||||
# if param.hidden then
|
||||
# paramline = paramline .. "hidden"
|
||||
# end
|
||||
#
|
||||
# paramline = paramline .. "</em></code>: "
|
||||
#
|
||||
# if param.description and #param.description > 0 then
|
||||
# paramline = paramline .. "\n" .. param.description
|
||||
# end
|
||||
#
|
||||
$( format (paramline))
|
||||
</li>
|
||||
# end
|
||||
# end
|
||||
</ul>
|
||||
# end
|
||||
#
|
||||
# --
|
||||
# -- Describe returns types
|
||||
# --
|
||||
# if fdef and #fdef.returns > 0 then
|
||||
<h$(i)>Return value$(#fdef.returns > 1 and 's')</h$(i)>
|
||||
# --
|
||||
# -- Format nice type list
|
||||
# --
|
||||
# local function niceparmlist( parlist )
|
||||
# local typelist = {}
|
||||
# for position, type in ipairs(parlist) do
|
||||
# local link = linkto( type )
|
||||
# local name = prettyname( type )
|
||||
# if link then
|
||||
# typelist[#typelist + 1] = '<a href="'..link..'">'..name..'</a>'
|
||||
# else
|
||||
# typelist[#typelist + 1] = name
|
||||
# end
|
||||
# -- Append end separator or separating comma
|
||||
# typelist[#typelist + 1] = position == #parlist and ':' or ', '
|
||||
# end
|
||||
# return table.concat( typelist )
|
||||
# end
|
||||
# --
|
||||
# -- Generate a list if they are several return clauses
|
||||
# --
|
||||
# if #fdef.returns > 1 then
|
||||
<ol>
|
||||
# for position, ret in ipairs(fdef.returns) do
|
||||
<li>
|
||||
# local returnline = "";
|
||||
#
|
||||
# local paramlist = niceparmlist(ret.types)
|
||||
# if #ret.types > 0 and #paramlist > 0 then
|
||||
# returnline = "<em>" .. paramlist .. "</em>"
|
||||
# end
|
||||
# returnline = returnline .. "\n" .. ret.description
|
||||
$( format (returnline))
|
||||
</li>
|
||||
# end
|
||||
</ol>
|
||||
# else
|
||||
# local paramlist = niceparmlist(fdef.returns[1].types)
|
||||
# local isreturn = fdef.returns and #fdef.returns > 0 and #paramlist > 0
|
||||
# local isdescription = fdef.returns and fdef.returns[1].description and #format(fdef.returns[1].description) > 0
|
||||
#
|
||||
# local returnline = "";
|
||||
# -- Show return type if provided
|
||||
# if isreturn then
|
||||
# returnline = "<em>"..paramlist.."</em>"
|
||||
# end
|
||||
# if isdescription then
|
||||
# returnline = returnline .. "\n" .. fdef.returns[1].description
|
||||
# end
|
||||
$( format(returnline))
|
||||
# end
|
||||
# end
|
||||
#end
|
||||
#
|
||||
#--
|
||||
#-- Show usage samples
|
||||
#--
|
||||
#if _item.metadata and _item.metadata.usage then
|
||||
$( applytemplate(_item.metadata.usage, i) )
|
||||
#end
|
||||
</dd>
|
||||
</dl>]]
|
||||
68
Utils/luarocks/share/lua/5.1/template/page.lua
Normal file
68
Utils/luarocks/share/lua/5.1/template/page.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return
|
||||
[[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
#if _page.headers and #_page.headers > 0 then
|
||||
<head>
|
||||
# for _, header in ipairs(_page.headers) do
|
||||
$(header)
|
||||
# end
|
||||
</head>
|
||||
#end
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="product">
|
||||
<div id="product_logo"></div>
|
||||
<div id="product_name"><big><b></b></big></div>
|
||||
<div id="product_description"></div>
|
||||
</div>
|
||||
<div id="main">
|
||||
# --
|
||||
# -- Generating lateral menu
|
||||
# --
|
||||
<div id="navigation">
|
||||
# local index = 'index'
|
||||
# if _page.modules then
|
||||
<h2>Modules</h2>
|
||||
# -- Check if an index is defined
|
||||
# if _page.modules [ index ] then
|
||||
# local module = _page.modules [ index ]
|
||||
<ul><li>
|
||||
# if module ~= _page.currentmodule then
|
||||
<a href="$( linkto(module) )">$(module.name)</a>
|
||||
# else
|
||||
$(module.name)
|
||||
# end
|
||||
</li></ul>
|
||||
# end
|
||||
#
|
||||
<ul>
|
||||
# -- Generating links for all modules
|
||||
# for _, module in sortedpairs( _page.modules ) do
|
||||
# -- Except for current one
|
||||
# if module.name ~= index then
|
||||
# if module ~= _page.currentmodule then
|
||||
<li><a href="$( linkto(module) )">$(module.name)</a></li>
|
||||
# else
|
||||
<li>$(module.name)</li>
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
</ul>
|
||||
# end
|
||||
</div>
|
||||
$( applytemplate(_page.currentmodule) )
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
]]
|
||||
36
Utils/luarocks/share/lua/5.1/template/recordtypedef.lua
Normal file
36
Utils/luarocks/share/lua/5.1/template/recordtypedef.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
return [[#
|
||||
# --
|
||||
# -- Descriptions
|
||||
# --
|
||||
#if _recordtypedef.shortdescription and #_recordtypedef.shortdescription > 0 then
|
||||
$( format( _recordtypedef.shortdescription ) )
|
||||
#end
|
||||
#if _recordtypedef.description and #_recordtypedef.description > 0 then
|
||||
$( format( _recordtypedef.description ) )
|
||||
#end
|
||||
#--
|
||||
#-- Describe usage
|
||||
#--
|
||||
#if _recordtypedef.metadata and _recordtypedef.metadata.usage then
|
||||
$( applytemplate(_recordtypedef.metadata.usage, i) )
|
||||
#end
|
||||
# --
|
||||
# -- Describe type fields
|
||||
# --
|
||||
#if not isempty( _recordtypedef.fields ) then
|
||||
<h$(i)>Field(s)</h$(i)>
|
||||
# for name, item in sortedpairs( _recordtypedef.fields ) do
|
||||
$( applytemplate(item, i) )
|
||||
# end
|
||||
#end ]]
|
||||
33
Utils/luarocks/share/lua/5.1/template/usage.lua
Normal file
33
Utils/luarocks/share/lua/5.1/template/usage.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Marc AUBRY <maubry@sierrawireless.com>
|
||||
-- - initial API and implementation
|
||||
--------------------------------------------------------------------------------
|
||||
return[[#
|
||||
#--
|
||||
#-- Show usage samples
|
||||
#--
|
||||
#if _usage then
|
||||
# if #_usage > 1 then
|
||||
# -- Show all usages
|
||||
<h$(i)>Usages:</h$(i)>
|
||||
<ul>
|
||||
# -- Loop over several usage description
|
||||
# for _, usage in ipairs(_usage) do
|
||||
<li><pre class="example"><code>$( securechevrons(usage.description) )</code></pre></li>
|
||||
# end
|
||||
</ul>
|
||||
# elseif #_usage == 1 then
|
||||
# -- Show unique usage sample
|
||||
<h$(i)>Usage:</h$(i)>
|
||||
# local usage = _usage[1]
|
||||
<pre class="example"><code>$( securechevrons(usage.description) )</code></pre>
|
||||
# end
|
||||
#end
|
||||
#]]
|
||||
470
Utils/luarocks/share/lua/5.1/template/utils.lua
Normal file
470
Utils/luarocks/share/lua/5.1/template/utils.lua
Normal file
@@ -0,0 +1,470 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012-2014 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local apimodel = require 'models.apimodel'
|
||||
|
||||
---
|
||||
-- @module docutils
|
||||
-- Handles link generation, node quick description.
|
||||
--
|
||||
-- Provides:
|
||||
-- * link generation
|
||||
-- * anchor generation
|
||||
-- * node quick description
|
||||
local M = {}
|
||||
|
||||
function M.isempty(map)
|
||||
local f = pairs(map)
|
||||
return f(map) == nil
|
||||
end
|
||||
|
||||
---
|
||||
-- Provide a handling function for all supported anchor types
|
||||
-- recordtypedef => #(typename)
|
||||
-- item (field of recordtypedef) => #(typename).itemname
|
||||
-- item (global) => itemname
|
||||
M.anchortypes = {
|
||||
recordtypedef = function (o) return string.format('#(%s)', o.name) end,
|
||||
item = function(o)
|
||||
if not o.parent or o.parent.tag == 'file' then
|
||||
-- Handle items referencing globals
|
||||
return o.name
|
||||
elseif o.parent and o.parent.tag == 'recordtypedef' then
|
||||
-- Handle items included in recordtypedef
|
||||
local recordtypedef = o.parent
|
||||
local recordtypedefanchor = M.anchor(recordtypedef)
|
||||
if not recordtypedefanchor then
|
||||
return nil, 'Unable to generate anchor for `recordtypedef parent.'
|
||||
end
|
||||
return string.format('%s.%s', recordtypedefanchor, o.name)
|
||||
end
|
||||
return nil, 'Unable to generate anchor for `item'
|
||||
end
|
||||
}
|
||||
|
||||
---
|
||||
-- Provides anchor string for an object of API mode
|
||||
--
|
||||
-- @function [parent = #docutils] anchor
|
||||
-- @param modelobject Object form API model
|
||||
-- @result #string Anchor for an API model object, this function __may rise an error__
|
||||
-- @usage # -- In a template
|
||||
-- # local anchorname = anchor(someobject)
|
||||
-- <a id="$(anchorname)" />
|
||||
function M.anchor( modelobject )
|
||||
local tag = modelobject.tag
|
||||
if M.anchortypes[ tag ] then
|
||||
return M.anchortypes[ tag ](modelobject)
|
||||
end
|
||||
return nil, string.format('No anchor available for `%s', tag)
|
||||
end
|
||||
|
||||
local function getexternalmodule( item )
|
||||
-- Get file which contains this item
|
||||
local file
|
||||
if item.parent then
|
||||
if item.parent.tag =='recordtypedef' then
|
||||
local recordtypedefparent = item.parent.parent
|
||||
if recordtypedefparent and recordtypedefparent.tag =='file'then
|
||||
file = recordtypedefparent
|
||||
end
|
||||
elseif item.parent.tag =='file' then
|
||||
file = item.parent
|
||||
else
|
||||
return nil, 'Unable to fetch item parent'
|
||||
end
|
||||
end
|
||||
return file
|
||||
end
|
||||
|
||||
---
|
||||
-- Provide a handling function for all supported link types
|
||||
--
|
||||
-- internaltyperef => ##(typename)
|
||||
-- => #anchor(recordtyperef)
|
||||
-- externaltyperef => modulename.html##(typename)
|
||||
-- => linkto(file)#anchor(recordtyperef)
|
||||
-- file(module) => modulename.html
|
||||
-- index => index.html
|
||||
-- recordtypedef => ##(typename)
|
||||
-- => #anchor(recordtyperef)
|
||||
-- item (internal field of recordtypedef) => ##(typename).itemname
|
||||
-- => #anchor(item)
|
||||
-- item (internal global) => #itemname
|
||||
-- => #anchor(item)
|
||||
-- item (external field of recordtypedef) => modulename.html##(typename).itemname
|
||||
-- => linkto(file)#anchor(item)
|
||||
-- item (externalglobal) => modulename.html#itemname
|
||||
-- => linkto(file)#anchor(item)
|
||||
M.linktypes = {
|
||||
internaltyperef = function(o) return string.format('##(%s)', o.typename) end,
|
||||
externaltyperef = function(o) return string.format('%s.html##(%s)', o.modulename, o.typename) end,
|
||||
file = function(o) return string.format('%s.html', o.name) end,
|
||||
index = function() return 'index.html' end,
|
||||
recordtypedef = function(o)
|
||||
local anchor = M.anchor(o)
|
||||
if not anchor then
|
||||
return nil, 'Unable to generate anchor for `recordtypedef.'
|
||||
end
|
||||
return string.format('#%s', anchor)
|
||||
end,
|
||||
item = function(o)
|
||||
|
||||
-- For every item get anchor
|
||||
local anchor = M.anchor(o)
|
||||
if not anchor then
|
||||
return nil, 'Unable to generate anchor for `item.'
|
||||
end
|
||||
|
||||
-- Built local link to item
|
||||
local linktoitem = string.format('#%s', anchor)
|
||||
|
||||
--
|
||||
-- For external item, prefix with the link to the module.
|
||||
--
|
||||
-- The "external item" concept is used only here for short/embedded
|
||||
-- notation purposed. This concept and the `.external` field SHALL NOT
|
||||
-- be used elsewhere.
|
||||
--
|
||||
if o.external then
|
||||
|
||||
-- Get link to file which contains this item
|
||||
local file = getexternalmodule( o )
|
||||
local linktofile = file and M.linkto( file )
|
||||
if not linktofile then
|
||||
return nil, 'Unable to generate link for external `item.'
|
||||
end
|
||||
|
||||
-- Built external link to item
|
||||
linktoitem = string.format("%s%s", linktofile, linktoitem)
|
||||
end
|
||||
|
||||
return linktoitem
|
||||
end
|
||||
}
|
||||
|
||||
---
|
||||
-- Generates text for HTML links from API model element
|
||||
--
|
||||
-- @function [parent = #docutils]
|
||||
-- @param modelobject Object form API model
|
||||
-- @result #string Links text for an API model element, this function __may rise an error__.
|
||||
-- @usage # -- In a template
|
||||
-- <a href="$( linkto(api) )">Some text</a>
|
||||
function M.linkto( apiobject )
|
||||
local tag = apiobject.tag
|
||||
if M.linktypes[ tag ] then
|
||||
return M.linktypes[tag](apiobject)
|
||||
end
|
||||
if not tag then
|
||||
return nil, 'Link generation is impossible as no tag has been provided.'
|
||||
end
|
||||
return nil, string.format('No link generation available for `%s.', tag)
|
||||
end
|
||||
|
||||
---
|
||||
-- Provide a handling function for all supported pretty name types
|
||||
-- primitivetyperef => #typename
|
||||
-- internaltyperef => #typename
|
||||
-- externaltyperef => modulename#typename
|
||||
-- file(module) => modulename
|
||||
-- index => index
|
||||
-- recordtypedef => typename
|
||||
-- item (internal function of recordtypedef) => typename.itemname(param1, param2,...)
|
||||
-- item (internal func with self of recordtypedef) => typename:itemname(param2)
|
||||
-- item (internal non func field of recordtypedef) => typename.itemname
|
||||
-- item (internal func global) => functionname(param1, param2,...)
|
||||
-- item (internal non func global) => itemname
|
||||
-- item (external function of recordtypedef) => modulename#typename.itemname(param1, param2,...)
|
||||
-- item (external func with self of recordtypedef) => modulename#typename:itemname(param2)
|
||||
-- item (external non func field of recordtypedef) => modulename#typename.itemname
|
||||
-- item (external func global) => functionname(param1, param2,...)
|
||||
-- item (external non func global) => itemname
|
||||
M.prettynametypes = {
|
||||
primitivetyperef = function(o) return string.format('#%s', o.typename) end,
|
||||
externaltyperef = function(o) return string.format('%s#%s', o.modulename, o.typename) end,
|
||||
index = function(o) return "index" end,
|
||||
file = function(o) return o.name end,
|
||||
recordtypedef = function(o) return o.name end,
|
||||
item = function( o )
|
||||
|
||||
-- Determine item name
|
||||
-- ----------------------
|
||||
local itemname = o.name
|
||||
|
||||
-- Determine scope
|
||||
-- ----------------------
|
||||
local parent = o.parent
|
||||
local isglobal = parent and parent.tag == 'file'
|
||||
local isfield = parent and parent.tag == 'recordtypedef'
|
||||
|
||||
-- Determine type name
|
||||
-- ----------------------
|
||||
|
||||
local typename = isfield and parent.name
|
||||
|
||||
-- Fetch item definition
|
||||
-- ----------------------
|
||||
-- Get file object
|
||||
local file
|
||||
if isglobal then
|
||||
file = parent
|
||||
elseif isfield then
|
||||
file = parent.parent
|
||||
end
|
||||
-- Get definition
|
||||
local definition = o:resolvetype (file)
|
||||
|
||||
|
||||
|
||||
-- Build prettyname
|
||||
-- ----------------------
|
||||
local prettyname
|
||||
if not definition or definition.tag ~= 'functiontypedef' then
|
||||
-- Fields
|
||||
if isglobal or not typename then
|
||||
prettyname = itemname
|
||||
else
|
||||
prettyname = string.format('%s.%s', typename, itemname)
|
||||
end
|
||||
else
|
||||
-- Functions
|
||||
-- Build parameter list
|
||||
local paramlist = {}
|
||||
local isinvokable = M.isinvokable(o)
|
||||
for position, param in ipairs(definition.params) do
|
||||
-- For non global function, when first parameter is 'self',
|
||||
-- it will not be part of listed parameters
|
||||
if not (position == 1 and isinvokable and isfield) then
|
||||
table.insert(paramlist, param.name)
|
||||
if position ~= #definition.params then
|
||||
table.insert(paramlist, ', ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if isglobal or not typename then
|
||||
prettyname = string.format('%s(%s)',itemname, table.concat(paramlist))
|
||||
else
|
||||
-- Determine function prefix operator,
|
||||
-- ':' if 'self' is first parameter, '.' else way
|
||||
local operator = isinvokable and ':' or '.'
|
||||
|
||||
-- Append function parameters
|
||||
prettyname = string.format('%s%s%s(%s)',typename, operator, itemname, table.concat(paramlist))
|
||||
end
|
||||
end
|
||||
|
||||
-- Manage external Item prettyname
|
||||
-- ----------------------
|
||||
local externalmodule = o.external and getexternalmodule( o )
|
||||
local externalmodulename = externalmodule and externalmodule.name
|
||||
|
||||
if externalmodulename then
|
||||
return string.format('%s#%s',externalmodulename,prettyname)
|
||||
else
|
||||
return prettyname
|
||||
end
|
||||
end
|
||||
}
|
||||
M.prettynametypes.internaltyperef = M.prettynametypes.primitivetyperef
|
||||
|
||||
---
|
||||
-- Check if the given item is a function that can be invoked
|
||||
function M.isinvokable(item)
|
||||
--test if the item is global
|
||||
if item.parent and item.parent.tag == 'file' then
|
||||
return false
|
||||
end
|
||||
-- check first param
|
||||
local definition = item:resolvetype()
|
||||
if definition and definition.tag == 'functiontypedef' then
|
||||
if (#definition.params > 0) then
|
||||
return definition.params[1].name == 'self'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- Provide human readable overview from an API model element
|
||||
--
|
||||
-- Resolve all element needed to summurize nicely an element form API model.
|
||||
-- @usage $ print( prettyname(item) )
|
||||
-- module:somefunction(secondparameter)
|
||||
-- @function [parent = #docutils]
|
||||
-- @param apiobject Object form API model
|
||||
-- @result #string Human readable description of given element.
|
||||
-- @result #nil, #string In case of error.
|
||||
function M.prettyname( apiobject )
|
||||
local tag = apiobject.tag
|
||||
if M.prettynametypes[tag] then
|
||||
return M.prettynametypes[tag](apiobject)
|
||||
elseif not tag then
|
||||
return nil, 'No pretty name available as no tag has been provided.'
|
||||
end
|
||||
return nil, string.format('No pretty name for `%s.', tag)
|
||||
end
|
||||
|
||||
---
|
||||
-- Just make a string print table in HTML.
|
||||
-- @function [parent = #docutils] securechevrons
|
||||
-- @param #string String to convert.
|
||||
-- @usage securechevrons('<markup>') => '<markup>'
|
||||
-- @return #string Converted string.
|
||||
function M.securechevrons( str )
|
||||
if not str then return nil, 'String expected.' end
|
||||
return string.gsub(str:gsub('<', '<'), '>', '>')
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Handling format of @{some#type} tag.
|
||||
-- Following functions enable to recognize several type of references between
|
||||
-- "{}".
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
---
|
||||
-- Provide API Model elements from string describing global elements
|
||||
-- such as:
|
||||
-- * `global#foo`
|
||||
-- * `foo#global.bar`
|
||||
local globals = function(str)
|
||||
-- Handling globals from modules
|
||||
for modulename, fieldname in str:gmatch('([%a%.%d_]+)#global%.([%a%.%d_]+)') do
|
||||
local item = apimodel._item(fieldname)
|
||||
local file = apimodel._file()
|
||||
file.name = modulename
|
||||
file:addglobalvar( item )
|
||||
return item
|
||||
end
|
||||
-- Handling other globals
|
||||
for name in str:gmatch('global#([%a%.%d_]+)') do
|
||||
-- print("globale", name)
|
||||
return apimodel._externaltypref('global', name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---
|
||||
-- Transform a string like `module#(type).field` in an API Model item
|
||||
local field = function( str )
|
||||
|
||||
-- Match `module#type.field`
|
||||
local mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#([%a%.%d_]+)%.([%a%.%d_]+)')()
|
||||
|
||||
-- Try matching `module#(type).field`
|
||||
if not mod then
|
||||
mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#%(([%a%.%d_]+)%)%.([%a%.%d_]+)')()
|
||||
if not mod then
|
||||
-- No match
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Build according `item
|
||||
local modulefielditem = apimodel._item( fieldname )
|
||||
local moduletype = apimodel._recordtypedef(typename)
|
||||
moduletype:addfield( modulefielditem )
|
||||
local typeref
|
||||
if #mod > 0 then
|
||||
local modulefile = apimodel._file()
|
||||
modulefile.name = mod
|
||||
modulefile:addtype( moduletype )
|
||||
typeref = apimodel._externaltypref(mod, typename)
|
||||
modulefielditem.external = true
|
||||
else
|
||||
typeref = apimodel._internaltyperef(typename)
|
||||
end
|
||||
modulefielditem.type = typeref
|
||||
return modulefielditem
|
||||
end
|
||||
|
||||
---
|
||||
-- Build an API internal reference from a string like: `#typeref`
|
||||
local internal = function ( typestring )
|
||||
for name in typestring:gmatch('#([%a%.%d_]+)') do
|
||||
-- Do not handle this name is it starts with reserved name "global"
|
||||
if name:find("global.") == 1 then return nil end
|
||||
return apimodel._internaltyperef(name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
---
|
||||
-- Build an API external reference from a string like: `mod.ule#type`
|
||||
local extern = function (type)
|
||||
|
||||
-- Match `mod.ule#ty.pe`
|
||||
local modulename, typename = type:gmatch('([%a%.%d_]+)#([%a%.%d_]+)')()
|
||||
|
||||
-- Trying `mod.ule#(ty.pe)`
|
||||
if not modulename then
|
||||
modulename, typename = type:gmatch('([%a%.%d_]+)#%(([%a%.%d_]+)%)')()
|
||||
|
||||
-- No match at all
|
||||
if not modulename then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return apimodel._externaltypref(modulename, typename)
|
||||
end
|
||||
|
||||
---
|
||||
-- Build an API external reference from a string like: `mod.ule`
|
||||
local file = function (type)
|
||||
for modulename in type:gmatch('([%a%.%d_]+)') do
|
||||
local file = apimodel._file()
|
||||
file.name = modulename
|
||||
return file
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Provide API Model element from a string
|
||||
-- @usage local externaltyperef = getelement("somemodule#somefield")
|
||||
function M.getelement( str )
|
||||
|
||||
-- Order matters, more restrictive are at begin of table
|
||||
local extractors = {
|
||||
globals,
|
||||
field,
|
||||
extern,
|
||||
internal,
|
||||
file
|
||||
}
|
||||
-- Loop over extractors.
|
||||
-- First valid result is used
|
||||
for _, extractor in ipairs( extractors ) do
|
||||
local result = extractor( str )
|
||||
if result then return result end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Iterator that iterates on the table in key ascending order.
|
||||
--
|
||||
-- @function [parent=#utils.table] sortedPairs
|
||||
-- @param t table to iterate.
|
||||
-- @return iterator function.
|
||||
function M.sortedpairs(t)
|
||||
local a = {}
|
||||
local insert = table.insert
|
||||
for n in pairs(t) do insert(a, n) end
|
||||
table.sort(a)
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
return a[i], t[a[i]]
|
||||
end
|
||||
end
|
||||
return M
|
||||
116
Utils/luarocks/share/lua/5.1/templateengine.lua
Normal file
116
Utils/luarocks/share/lua/5.1/templateengine.lua
Normal file
@@ -0,0 +1,116 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
---
|
||||
-- This library provide html description of elements from the externalapi
|
||||
local M = {}
|
||||
|
||||
-- Load template engine
|
||||
local pltemplate = require 'pl.template'
|
||||
|
||||
-- Markdown handling
|
||||
local markdown = require 'markdown'
|
||||
|
||||
-- apply template to the given element
|
||||
function M.applytemplate(elem, ident, templatetype)
|
||||
-- define environment
|
||||
local env = M.getenv(elem, ident)
|
||||
|
||||
-- load template
|
||||
local template = M.gettemplate(elem,templatetype)
|
||||
if not template then
|
||||
templatetype = templatetype and string.format(' "%s"', templatetype) or ''
|
||||
local elementname = string.format(' for %s', elem.tag or 'untagged element')
|
||||
error(string.format('Unable to load %s template %s', templatetype, elementname))
|
||||
end
|
||||
|
||||
-- apply template
|
||||
local str, err = pltemplate.substitute(template, env)
|
||||
|
||||
--manage errors
|
||||
if not str then
|
||||
local templateerror = templatetype and string.format(' parsing "%s" template ', templatetype) or ''
|
||||
error(string.format('An error occured%s for "%s"\n%s',templateerror, elem.tag, err))
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
-- get the a new environment for this element
|
||||
function M.getenv(elem, ident)
|
||||
local currentenv ={}
|
||||
for k,v in pairs(M.env) do currentenv[k] = v end
|
||||
if elem and elem.tag then
|
||||
currentenv['_'..elem.tag]= elem
|
||||
end
|
||||
currentenv['i']= ident or 1
|
||||
return currentenv
|
||||
end
|
||||
|
||||
-- get the template for this element
|
||||
function M.gettemplate(elem,templatetype)
|
||||
local tag = elem and elem.tag
|
||||
if tag then
|
||||
if templatetype then
|
||||
return require ("template." .. templatetype.. "." .. tag)
|
||||
else
|
||||
return require ("template." .. tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Allow user to format text in descriptions.
|
||||
-- Default implementation replaces @{---} tags with links and apply markdown.
|
||||
-- @return #string
|
||||
local function format(string)
|
||||
-- Allow to replace encountered tags with valid links
|
||||
local replace = function(found)
|
||||
local apiobj = M.env.getelement(found)
|
||||
if apiobj then
|
||||
return M.env.fulllinkto(apiobj)
|
||||
end
|
||||
return found
|
||||
end
|
||||
string = string:gsub('@{%s*(.-)%s*}', replace)
|
||||
return M.env.markdown( string )
|
||||
end
|
||||
---
|
||||
-- Provide a full link to an element using `prettyname` and `linkto`.
|
||||
-- Default implementation is for HTML.
|
||||
local function fulllinkto(o)
|
||||
local ref = M.env.linkto(o)
|
||||
local name = M.env.prettyname(o)
|
||||
if not ref then
|
||||
return name
|
||||
end
|
||||
return string.format('<a href="%s">%s</a>', ref, name)
|
||||
end
|
||||
--
|
||||
-- Define default template environnement
|
||||
--
|
||||
local defaultenv = {
|
||||
table = table,
|
||||
ipairs = ipairs,
|
||||
pairs = pairs,
|
||||
markdown = markdown,
|
||||
applytemplate = M.applytemplate,
|
||||
format = format,
|
||||
linkto = function(str) return str end,
|
||||
fulllinkto = fulllinkto,
|
||||
prettyname = function(s) return s end,
|
||||
getelement = function(s) return nil end
|
||||
}
|
||||
|
||||
-- this is the global env accessible in the templates
|
||||
-- env should be redefine by docgenerator user to add functions or redefine it.
|
||||
M.env = defaultenv
|
||||
return M
|
||||
Reference in New Issue
Block a user