├── .gitignore ├── LICENSE ├── README.md ├── defaultcss.lua ├── docgenerator.lua ├── extractors.lua ├── fs └── lfs.lua ├── lddextractor.lua ├── luadocumentor.lua ├── luarocks ├── .gitignore ├── README.md ├── luadocumentor-0.1.4-1.rockspec └── luadocumentor-0.1.5-1.rockspec ├── models ├── apimodel.lua ├── apimodelbuilder.lua ├── internalmodel.lua ├── internalmodelbuilder.mlua └── ldparser.lua ├── template ├── file.lua ├── functiontypedef.lua ├── index.lua ├── index │ └── recordtypedef.lua ├── item.lua ├── page.lua ├── recordtypedef.lua ├── usage.lua └── utils.lua └── templateengine.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.luac 2 | docs 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua Documentor 2 | 3 | LuaDocumentor allow users to generate HTML and API files from code documented 4 | using Lua documentation language. 5 | 6 | Documentation is 7 | [available here](http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor). 8 | -------------------------------------------------------------------------------- /defaultcss.lua: -------------------------------------------------------------------------------- 1 | return [[html { 2 | color: #000; 3 | background: #FFF; 4 | } 5 | 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 { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | table { 10 | border-collapse: collapse; 11 | border-spacing: 0; 12 | } 13 | fieldset,img { 14 | border: 0; 15 | } 16 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 17 | font-style: inherit; 18 | font-weight: inherit; 19 | } 20 | del,ins { 21 | text-decoration: none; 22 | } 23 | li { 24 | list-style: bullet; 25 | margin-left: 20px; 26 | } 27 | caption,th { 28 | text-align: left; 29 | } 30 | h1,h2,h3,h4,h5,h6 { 31 | font-size: 100%; 32 | font-weight: bold; 33 | } 34 | q:before,q:after { 35 | content: ''; 36 | } 37 | abbr,acronym { 38 | border: 0; 39 | font-variant: normal; 40 | } 41 | sup { 42 | vertical-align: baseline; 43 | } 44 | sub { 45 | vertical-align: baseline; 46 | } 47 | legend { 48 | color: #000; 49 | } 50 | input,button,textarea,select,optgroup,option { 51 | font-family: inherit; 52 | font-size: inherit; 53 | font-style: inherit; 54 | font-weight: inherit; 55 | } 56 | input,button,textarea,select {*font-size:100%; 57 | } 58 | /* END RESET */ 59 | 60 | body { 61 | margin-left: 1em; 62 | margin-right: 1em; 63 | font-family: arial, helvetica, geneva, sans-serif; 64 | background-color: #ffffff; margin: 0px; 65 | } 66 | 67 | code, tt { font-family: monospace; } 68 | 69 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 70 | 71 | p, ul { margin: 10px 0 0 10px;} 72 | 73 | strong { font-weight: bold;} 74 | 75 | em { font-style: italic;} 76 | 77 | h1 { 78 | font-size: 1.5em; 79 | margin: 25px 0 20px 0; 80 | } 81 | h2, h3, h4 { margin: 15px 0 10px 0; } 82 | h2 { font-size: 1.25em; } 83 | h3 { font-size: 1.15em; } 84 | h4 { font-size: 1.06em; } 85 | 86 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 87 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 88 | a:link:hover { text-decoration: underline; } 89 | 90 | hr { 91 | color:#cccccc; 92 | background: #00007f; 93 | height: 1px; 94 | } 95 | 96 | blockquote { margin-left: 3em; } 97 | 98 | ul { list-style-type: disc; } 99 | 100 | p.name { 101 | font-family: "Andale Mono", monospace; 102 | padding-top: 1em; 103 | } 104 | 105 | p:first-child { 106 | margin-top: 0px; 107 | } 108 | 109 | pre.example { 110 | background-color: rgb(245, 245, 245); 111 | border: 1px solid silver; 112 | padding: 10px; 113 | margin: 10px 0 10px 0; 114 | font-family: "Andale Mono", monospace; 115 | font-size: .85em; 116 | } 117 | 118 | pre { 119 | background-color: rgb(245, 245, 245); 120 | border: 1px solid silver; 121 | padding: 10px; 122 | margin: 10px 0 10px 0; 123 | font-family: "Andale Mono", monospace; 124 | } 125 | 126 | 127 | table.index { border: 1px #00007f; } 128 | table.index td { text-align: left; vertical-align: top; } 129 | 130 | #container { 131 | margin-left: 1em; 132 | margin-right: 1em; 133 | background-color: #f0f0f0; 134 | } 135 | 136 | #product { 137 | text-align: center; 138 | border-bottom: 1px solid #cccccc; 139 | background-color: #ffffff; 140 | } 141 | 142 | #product big { 143 | font-size: 2em; 144 | } 145 | 146 | #main { 147 | background-color: #f0f0f0; 148 | border-left: 2px solid #cccccc; 149 | } 150 | 151 | #navigation { 152 | float: left; 153 | width: 18em; 154 | vertical-align: top; 155 | background-color: #f0f0f0; 156 | overflow: scroll; 157 | position: fixed; 158 | height:100%; 159 | } 160 | 161 | #navigation h2 { 162 | background-color:#e7e7e7; 163 | font-size:1.1em; 164 | color:#000000; 165 | text-align: left; 166 | padding:0.2em; 167 | border-top:1px solid #dddddd; 168 | border-bottom:1px solid #dddddd; 169 | } 170 | 171 | #navigation ul 172 | { 173 | font-size:1em; 174 | list-style-type: none; 175 | margin: 1px 1px 10px 1px; 176 | } 177 | 178 | #navigation li { 179 | text-indent: -1em; 180 | display: block; 181 | margin: 3px 0px 0px 22px; 182 | } 183 | 184 | #navigation li li a { 185 | margin: 0px 3px 0px -1em; 186 | } 187 | 188 | #content { 189 | margin-left: 18em; 190 | padding: 1em; 191 | border-left: 2px solid #cccccc; 192 | border-right: 2px solid #cccccc; 193 | background-color: #ffffff; 194 | } 195 | 196 | #about { 197 | clear: both; 198 | padding: 5px; 199 | border-top: 2px solid #cccccc; 200 | background-color: #ffffff; 201 | } 202 | 203 | @media print { 204 | body { 205 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 206 | } 207 | a { font-weight: bold; color: #004080; text-decoration: underline; } 208 | 209 | #main { 210 | background-color: #ffffff; 211 | border-left: 0px; 212 | } 213 | 214 | #container { 215 | margin-left: 2%; 216 | margin-right: 2%; 217 | background-color: #ffffff; 218 | } 219 | 220 | #content { 221 | padding: 1em; 222 | background-color: #ffffff; 223 | } 224 | 225 | #navigation { 226 | display: none; 227 | } 228 | pre.example { 229 | font-family: "Andale Mono", monospace; 230 | font-size: 10pt; 231 | page-break-inside: avoid; 232 | } 233 | } 234 | 235 | table.module_list { 236 | border-width: 1px; 237 | border-style: solid; 238 | border-color: #cccccc; 239 | border-collapse: collapse; 240 | } 241 | table.module_list td { 242 | border-width: 1px; 243 | padding: 3px; 244 | border-style: solid; 245 | border-color: #cccccc; 246 | } 247 | table.module_list td.name { background-color: #f0f0f0; } 248 | table.module_list td.summary { width: 100%; } 249 | 250 | 251 | table.function_list { 252 | border-width: 1px; 253 | border-style: solid; 254 | border-color: #cccccc; 255 | border-collapse: collapse; 256 | } 257 | table.function_list td { 258 | border-width: 1px; 259 | padding: 3px; 260 | border-style: solid; 261 | border-color: #cccccc; 262 | } 263 | table.function_list td.name { background-color: #f0f0f0; } 264 | table.function_list td.summary { width: 100%; } 265 | 266 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 267 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 268 | dl.table h3, dl.function h3 {font-size: .95em;} 269 | 270 | ]] 271 | -------------------------------------------------------------------------------- /docgenerator.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | 13 | -- 14 | -- Load documentation generator and update its path 15 | -- 16 | local templateengine = require 'templateengine' 17 | for name, def in pairs( require 'template.utils' ) do 18 | templateengine.env [ name ] = def 19 | end 20 | 21 | -- Load documentation extractor and set handled languages 22 | local lddextractor = require 'lddextractor' 23 | 24 | local M = {} 25 | M.defaultsitemainpagename = 'index' 26 | 27 | function M.generatedocforfiles(filenames, cssname,noheuristic) 28 | if not filenames then return nil, 'No files provided.' end 29 | -- 30 | -- Generate API model elements for all files 31 | -- 32 | local generatedfiles = {} 33 | local wrongfiles = {} 34 | for _, filename in pairs( filenames ) do 35 | -- Load file content 36 | local file, error = io.open(filename, 'r') 37 | if not file then return nil, 'Unable to read "'..filename..'"\n'..err end 38 | local code = file:read('*all') 39 | file:close() 40 | -- Get module for current file 41 | local apimodule, err = lddextractor.generateapimodule(filename, code,noheuristic) 42 | 43 | -- Handle modules with module name 44 | if apimodule and apimodule.name then 45 | generatedfiles[ apimodule.name ] = apimodule 46 | elseif not apimodule then 47 | -- Track faulty files 48 | table.insert(wrongfiles, 'Unable to extract comments from "'..filename..'".\n'..err) 49 | elseif not apimodule.name then 50 | -- Do not generate documentation for unnamed modules 51 | table.insert(wrongfiles, 'Unable to create documentation for "'..filename..'", no module name provided.') 52 | end 53 | end 54 | -- 55 | -- Defining index, which will summarize all modules 56 | -- 57 | local index = { 58 | modules = generatedfiles, 59 | name = M.defaultsitemainpagename, 60 | tag='index' 61 | } 62 | generatedfiles[ M.defaultsitemainpagename ] = index 63 | 64 | -- 65 | -- Define page cursor 66 | -- 67 | local page = { 68 | currentmodule = nil, 69 | headers = { [[]] }, 70 | modules = generatedfiles, 71 | tag = 'page' 72 | } 73 | 74 | -- 75 | -- Iterate over modules, generating complete doc pages 76 | -- 77 | for _, module in pairs( generatedfiles ) do 78 | -- Update current cursor page 79 | page.currentmodule = module 80 | -- Generate page 81 | local content, error = templateengine.applytemplate(page) 82 | if not content then return nil, error end 83 | module.body = content 84 | end 85 | return generatedfiles, wrongfiles 86 | end 87 | return M 88 | -------------------------------------------------------------------------------- /extractors.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local M = {} 13 | require 'metalua.loader' 14 | local compiler = require 'metalua.compiler' 15 | local mlc = compiler.new() 16 | local Q = require 'metalua.treequery' 17 | 18 | -- Enable to retrieve all Javadoc-like comments from C code 19 | function M.c(code) 20 | if not code then return nil, 'No code provided' end 21 | local comments = {} 22 | -- Loop over comments stripping cosmetic '*' 23 | for comment in code:gmatch('%s*/%*%*+(.-)%*+/') do 24 | -- All Lua special comment are prefixed with an '-', 25 | -- so we also comment C comment to make them compliant 26 | table.insert(comments, '-'..comment) 27 | end 28 | return comments 29 | end 30 | 31 | -- Enable to retrieve "---" comments from Lua code 32 | function M.lua( code ) 33 | if not code then return nil, 'No code provided' end 34 | 35 | -- manage shebang 36 | if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end 37 | 38 | -- check for errors 39 | local f, err = loadstring(code,'source_to_check') 40 | if not f then 41 | return nil, 'Syntax error.\n' .. err 42 | end 43 | 44 | -- Get ast from file 45 | local status, ast = pcall(mlc.src_to_ast, mlc, code) 46 | -- 47 | -- Detect parsing errors 48 | -- 49 | if not status then 50 | return nil, 'There might be a syntax error.\n' .. ast 51 | end 52 | 53 | -- 54 | -- Extract commented nodes from AST 55 | -- 56 | 57 | -- Function enabling commented node selection 58 | local function acceptcommentednode(node) 59 | return node.lineinfo and ( node.lineinfo.last.comments or node.lineinfo.first.comments ) 60 | end 61 | 62 | -- Fetch commented node from AST 63 | local commentednodes = Q(ast):filter( acceptcommentednode ):list() 64 | 65 | -- Comment cache to avoid selecting same comment twice 66 | local commentcache = {} 67 | -- Will contain selected comments 68 | local comments = {} 69 | 70 | -- Loop over commented nodes 71 | for _, node in ipairs( commentednodes ) do 72 | 73 | -- A node can is relateds to comment before and after itself, 74 | -- the following gathers them. 75 | local commentlists = {} 76 | if node.lineinfo and node.lineinfo.first.comments then 77 | table.insert(commentlists, node.lineinfo.first.comments) 78 | end 79 | if node.lineinfo and node.lineinfo.last.comments then 80 | table.insert(commentlists, node.lineinfo.last.comments) 81 | end 82 | -- Now that we have comments before and fater the node, 83 | -- collect them in a single table 84 | for _, list in ipairs( commentlists ) do 85 | for _, commenttable in ipairs(list) do 86 | -- Only select special comments 87 | local firstcomment = #commenttable > 0 and #commenttable[1] > 0 and commenttable[1] 88 | if firstcomment:sub(1, 1) == '-' then 89 | for _, comment in ipairs( commenttable ) do 90 | -- Only comments which were not already collected 91 | if not commentcache[comment] then 92 | commentcache[comment] = true 93 | table.insert(comments, comment) 94 | end 95 | end 96 | end 97 | end 98 | end 99 | end 100 | return comments 101 | end 102 | return M 103 | -------------------------------------------------------------------------------- /fs/lfs.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local lfs = require 'lfs' 13 | local M = {} 14 | local function iswindows() 15 | local p = io.popen("echo %os%") 16 | if not p then 17 | return false 18 | end 19 | local result =p:read("*l") 20 | p:close() 21 | return result == "Windows_NT" 22 | end 23 | M.separator = iswindows() and [[\]] or [[/]] 24 | --- 25 | -- Will recursively browse given directories and list files encountered 26 | -- @param tab Table, list where files will be added 27 | -- @param dirorfiles list of path to browse in order to build list. 28 | -- Files from this list will be added to tab list. 29 | -- @return tab list, table containing all files from directories 30 | -- and files contained in dirorfile 31 | local function appendfiles(tab, dirorfile) 32 | 33 | -- Nothing to process 34 | if #dirorfile < 1 then return tab end 35 | 36 | -- Append all files to list 37 | local dirs = {} 38 | for _, path in ipairs( dirorfile ) do 39 | -- Determine element nature 40 | local elementnature = lfs.attributes (path, "mode") 41 | 42 | -- Handle files 43 | if elementnature == 'file' then 44 | table.insert(tab, path) 45 | else if elementnature == 'directory' then 46 | 47 | -- Check if folder is accessible 48 | local status, error = pcall(lfs.dir, path) 49 | if not status then return nil, error end 50 | 51 | -- 52 | -- Handle folders 53 | -- 54 | for diskelement in lfs.dir(path) do 55 | 56 | -- Format current file name 57 | local currentfilename 58 | if path:sub(#path) == M.separator then 59 | currentfilename = path .. diskelement 60 | else 61 | currentfilename = path .. M.separator .. diskelement 62 | end 63 | 64 | -- Handle folder elements 65 | local nature, err = lfs.attributes (currentfilename, "mode") 66 | -- Append file to current list 67 | if nature == 'file' then 68 | table.insert(tab, currentfilename) 69 | elseif nature == 'directory' then 70 | -- Avoid current and parent directory in order to avoid 71 | -- endless recursion 72 | if diskelement ~= '.' and diskelement ~= '..' then 73 | -- Handle subfolders 74 | table.insert(dirs, currentfilename) 75 | end 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -- If we only encountered files, going deeper is useless 82 | if #dirs == 0 then return tab end 83 | -- Append files from encountered directories 84 | return appendfiles(tab, dirs) 85 | end 86 | --- 87 | -- Provide a list of files from a directory 88 | -- @param list Table of directories to browse 89 | -- @return table of string, path to files contained in given directories 90 | function M.filelist(list) 91 | if not list then return nil, 'No directory list provided' end 92 | return appendfiles({}, list) 93 | end 94 | function M.checkdirectory( dirlist ) 95 | if not dirlist then return false end 96 | local missingdirs = {} 97 | for _, filename in ipairs( dirlist ) do 98 | if not lfs.attributes(filename, 'mode') then 99 | table.insert(missingdirs, filename) 100 | end 101 | end 102 | if #missingdirs > 0 then 103 | return false, missingdirs 104 | end 105 | return true 106 | end 107 | function M.fill(filename, content) 108 | -- 109 | -- Ensure parent directory exists 110 | -- 111 | local parent = filename:gmatch([[(.*)]] .. M.separator ..[[(.+)]])() 112 | local parentnature = lfs.attributes(parent, 'mode') 113 | -- Create parent directory while absent 114 | if not parentnature then 115 | lfs.mkdir( parent ) 116 | elseif parentnature ~= 'directory' then 117 | -- Notify that disk element already exists 118 | return nil, parent..' is a '..parentnature..'.' 119 | end 120 | 121 | -- Create actual file 122 | local file, error = io.open(filename, 'w') 123 | if not file then 124 | return nil, error 125 | end 126 | file:write( content ) 127 | file:close() 128 | return true 129 | end 130 | return M 131 | -------------------------------------------------------------------------------- /lddextractor.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | require 'metalua.loader' 13 | local compiler = require 'metalua.compiler' 14 | local mlc = compiler.new() 15 | local M = {} 16 | 17 | -- 18 | -- Define default supported languages 19 | -- 20 | M.supportedlanguages = {} 21 | local extractors = require 'extractors' 22 | 23 | -- Support Lua comment extracting 24 | for _,lua in ipairs({'lua', 'doclua'}) do 25 | M.supportedlanguages[lua] = extractors.lua 26 | end 27 | 28 | -- Support C comment extracting 29 | for _,c in ipairs({'c', 'cpp', 'c++'}) do 30 | M.supportedlanguages[c] = extractors.c 31 | end 32 | 33 | -- Extract comment from code, 34 | -- type of code is deduced from filename extension 35 | function M.extract(filename, code) 36 | -- Check parameters 37 | if not code then return nil, 'No code provided' end 38 | if type(filename) ~= "string" then 39 | return nil, 'No string for file name provided' 40 | end 41 | 42 | -- Extract file extension 43 | local fileextension = filename:gmatch('.*%.(.*)')() 44 | if not fileextension then 45 | return nil, 'File '..filename..' has no extension, could not determine how to extract documentation.' 46 | end 47 | 48 | -- Check if it is possible to extract documentation from these files 49 | local extractor = M.supportedlanguages[ fileextension ] 50 | if not extractor then 51 | return nil, 'Unable to extract documentation from '.. fileextension .. ' file.' 52 | end 53 | return extractor( code ) 54 | end 55 | -- Generate a file gathering only comments from given code 56 | function M.generatecommentfile(filename, code) 57 | local comments, error = M.extract(filename, code) 58 | if not comments then 59 | return nil, 'Unable to generate comment file.\n'..error 60 | end 61 | local filecontent = {} 62 | for _, comment in ipairs( comments ) do 63 | table.insert(filecontent, "--[[") 64 | table.insert(filecontent, comment) 65 | table.insert(filecontent, "\n]]\n\n") 66 | end 67 | return table.concat(filecontent)..'return nil\n' 68 | end 69 | -- Create API Model module from a 'comment only' lua file 70 | function M.generateapimodule(filename, code,noheuristic) 71 | if not filename then return nil, 'No file name given.' end 72 | if not code then return nil, 'No code provided.' end 73 | if type(filename) ~= "string" then return nil, 'No string for file name provided' end 74 | 75 | -- for non lua file get comment file 76 | if filename:gmatch('.*%.(.*)')() ~= 'lua' then 77 | local err 78 | code, err = M.generatecommentfile(filename, code) 79 | if not code then 80 | return nil, 'Unable to create api module for "'..filename..'".\n'..err 81 | end 82 | else 83 | -- manage shebang 84 | if code then code = code:gsub("^(#.-\n)", function (s) return string.rep(' ',string.len(s)) end) end 85 | 86 | -- check for errors 87 | local f, err = loadstring(code,'source_to_check') 88 | if not f then 89 | return nil, 'File'..filename..'contains syntax error.\n' .. err 90 | end 91 | end 92 | 93 | local status, ast = pcall(mlc.src_to_ast, mlc, code) 94 | if not status then 95 | return nil, 'Unable to compute ast for "'..filename..'".\n'..ast 96 | end 97 | 98 | -- Extract module name as the filename without extension 99 | local modulename 100 | local matcher = string.gmatch(filename,'.*/(.*)%..*$') 101 | if matcher then modulename = matcher() end 102 | 103 | -- Create api model 104 | local apimodelbuilder = require 'models.apimodelbuilder' 105 | local _file, comment2apiobj = apimodelbuilder.createmoduleapi(ast, modulename) 106 | 107 | -- Create internal model 108 | if not noheuristic then 109 | local internalmodelbuilder = require "models.internalmodelbuilder" 110 | local _internalcontent = internalmodelbuilder.createinternalcontent(ast,_file,comment2apiobj, modulename) 111 | end 112 | return _file 113 | end 114 | return M 115 | -------------------------------------------------------------------------------- /luadocumentor.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | -------------------------------------------------------------------------------- 3 | -- Copyright (c) 2012-2014 Sierra Wireless. 4 | -- All rights reserved. This program and the accompanying materials 5 | -- are made available under the terms of the Eclipse Public License v1.0 6 | -- which accompanies this distribution, and is available at 7 | -- http://www.eclipse.org/legal/epl-v10.html 8 | -- 9 | -- Contributors: 10 | -- Kevin KIN-FOO 11 | -- - initial API and implementation and initial documentation 12 | -------------------------------------------------------------------------------- 13 | 14 | -- Check interpreter version 15 | if _VERSION ~= "Lua 5.1" then 16 | print("Luadocumentor is only compatible with Lua 5.1") 17 | return 18 | end 19 | 20 | -- 21 | -- Defining help message. 22 | -- 23 | 24 | -- This message is compliant to 'lapp', which will match options and arguments 25 | -- from command line. 26 | local help = [[luadocumentor v0.1.4: tool for Lua Documentation Language 27 | -f, --format (default doc) Define output format : 28 | * doc: Will produce HTML documentation from specified file(s) or directories. 29 | * api: Will produce API file(s) from specified file(s) or directories. 30 | -d, --dir (default docs) Define an output directory. If the given directory doesn't exist, it will be created. 31 | -h, --help Display the help. 32 | -n, --noheuristic Do not use code analysis, use only comments to generate documentation. 33 | -s, --style (default !) The path of your own css file, if you don't want to use the default one. (usefull only for the doc format) 34 | [directories|files] Define the paths or the directories of inputs files. Only Lua or C files containing a @module tag will be considered. 35 | ]] 36 | local docgenerator = require 'docgenerator' 37 | local lddextractor = require 'lddextractor' 38 | local lapp = require 'pl.lapp' 39 | local args = lapp( help ) 40 | 41 | if not args or #args < 1 then 42 | print('No directory provided') 43 | return 44 | elseif args.help then 45 | -- Just print help 46 | print( help ) 47 | return 48 | end 49 | 50 | -- 51 | -- define css file name 52 | -- 53 | local cssfilename = "stylesheet.css" 54 | 55 | -- 56 | -- Parse files from given folders 57 | -- 58 | 59 | -- Check if all folders exist 60 | local fs = require 'fs.lfs' 61 | local allpresent, missing = fs.checkdirectory(args) 62 | 63 | -- Some of given directories are absent 64 | if missing then 65 | -- List missing directories 66 | print 'Unable to open' 67 | for _, file in ipairs( missing ) do 68 | print('\t'.. file) 69 | end 70 | return 71 | end 72 | 73 | -- Get files from given directories 74 | local filestoparse, error = fs.filelist( args ) 75 | if not filestoparse then 76 | print ( error ) 77 | return 78 | end 79 | 80 | -- 81 | -- Generate documentation only files 82 | -- 83 | if args.format == 'api' then 84 | for _, filename in ipairs( filestoparse ) do 85 | 86 | -- Loading file content 87 | print('Dealing with "'..filename..'".') 88 | local file, error = io.open(filename, 'r') 89 | if not file then 90 | print ('Unable to open "'..filename.."'.\n"..error) 91 | else 92 | local code = file:read('*all') 93 | file:close() 94 | 95 | -- 96 | -- Creating comment file 97 | -- 98 | local commentfile, error = lddextractor.generatecommentfile(filename, code) 99 | 100 | -- Getting module name 101 | -- Optimize me 102 | local module, moduleerror = lddextractor.generateapimodule(filename, code) 103 | if not commentfile then 104 | print('Unable to create documentation file for "'..filename..'"\n'..error) 105 | elseif not module or not module.name then 106 | local error = moduleerror and '\n'..moduleerror or '' 107 | print('Unable to compute module name for "'..filename..'".'..error) 108 | else 109 | -- 110 | -- Flush documentation file on disk 111 | -- 112 | local path = args.dir..fs.separator..module.name..'.lua' 113 | local status, err = fs.fill(path, commentfile) 114 | if not status then 115 | print(err) 116 | end 117 | end 118 | end 119 | end 120 | print('Done') 121 | return 122 | end 123 | 124 | -- Deal only supported output types 125 | if args.format ~= 'doc' then 126 | print ('"'..args.format..'" format is not handled.') 127 | return 128 | end 129 | -- Generate html form files 130 | local parsedfiles, unparsed = docgenerator.generatedocforfiles(filestoparse, cssfilename,args.noheuristic) 131 | 132 | -- Show warnings on unparsed files 133 | if #unparsed > 0 then 134 | for _, faultyfile in ipairs( unparsed ) do 135 | print( faultyfile ) 136 | end 137 | end 138 | -- This loop is just for counting parsed files 139 | -- TODO: Find a more elegant way to do it 140 | local parsedfilescount = 0 141 | for _, p in pairs(parsedfiles) do 142 | parsedfilescount = parsedfilescount + 1 143 | end 144 | print (parsedfilescount .. ' file(s) parsed.') 145 | 146 | -- Create html files 147 | local generated = 0 148 | for _, apifile in pairs ( parsedfiles ) do 149 | local status, err = fs.fill(args.dir..fs.separator..apifile.name..'.html', apifile.body) 150 | if status then 151 | generated = generated + 1 152 | else 153 | print( 'Unable to create '..apifile.name..'.html on disk.') 154 | end 155 | end 156 | print (generated .. ' file(s) generated.') 157 | 158 | -- Copying css 159 | local csscontent 160 | if args.style == '!' then 161 | csscontent = require 'defaultcss' 162 | else 163 | local css, error = io.open(args.style, 'r') 164 | if not css then 165 | print('Unable to open "'..args.style .. '".\n'..error) 166 | return 167 | end 168 | csscontent = css:read("*all") 169 | css:close() 170 | end 171 | 172 | local status, error = fs.fill(args.dir..fs.separator..cssfilename, csscontent) 173 | if not status then 174 | print(error) 175 | return 176 | end 177 | print('Adding css') 178 | print('Done') 179 | -------------------------------------------------------------------------------- /luarocks/.gitignore: -------------------------------------------------------------------------------- 1 | *.src.rock 2 | -------------------------------------------------------------------------------- /luarocks/README.md: -------------------------------------------------------------------------------- 1 | Generating rockspec can be tricky. Here are my magic commands. 2 | * `luarocks pack luadocumentor-X-Y.rockspec` 3 | * `luarocks install --local luadocumentor-X-Y.src.rock` to check if it worked 4 | -------------------------------------------------------------------------------- /luarocks/luadocumentor-0.1.4-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'LuaDocumentor' 2 | version = '0.1.4-1' 3 | description = { 4 | summary = 'LuaDocumentor allow users to generate HTML and API files from code documented using Lua documentation language.', 5 | detailed = [[ 6 | This is an example for the LuaRocks tutorial. 7 | Here we would put a detailed, typically 8 | paragraph-long description. 9 | ]], 10 | homepage = 'http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor', 11 | license = 'EPL' 12 | } 13 | source = { 14 | url = 'git://github.com/LuaDevelopmentTools/luadocumentor.git', 15 | tag = 'v0.1.4-1' 16 | } 17 | dependencies = { 18 | 'lua ~> 5.1', 19 | 'luafilesystem ~> 1.6', 20 | 'markdown ~> 0.32', 21 | 'metalua-compiler ~> 0.7.2', 22 | 'penlight ~> 0.9.8' 23 | } 24 | build = { 25 | type = 'builtin', 26 | install = { 27 | bin = { 28 | luadocumentor = 'luadocumentor.lua' 29 | }, 30 | lua = { 31 | ['models.internalmodelbuilder'] = 'models/internalmodelbuilder.mlua' 32 | } 33 | }, 34 | modules = { 35 | defaultcss = 'defaultcss.lua', 36 | docgenerator = 'docgenerator.lua', 37 | extractors = 'extractors.lua', 38 | lddextractor = 'lddextractor.lua', 39 | templateengine = 'templateengine.lua', 40 | 41 | ['fs.lfs'] = 'fs/lfs.lua', 42 | 43 | ['models.apimodel'] = 'models/apimodel.lua', 44 | ['models.apimodelbuilder'] = 'models/apimodelbuilder.lua', 45 | ['models.internalmodel'] = 'models/internalmodel.lua', 46 | ['models.ldparser'] = 'models/ldparser.lua', 47 | 48 | ['template.file'] = 'template/file.lua', 49 | ['template.index'] = 'template/index.lua', 50 | ['template.index.recordtypedef'] = 'template/index/recordtypedef.lua', 51 | ['template.item'] = 'template/item.lua', 52 | ['template.page'] = 'template/page.lua', 53 | ['template.recordtypedef'] = 'template/recordtypedef.lua', 54 | ['template.usage'] = 'template/usage.lua', 55 | ['template.utils'] = 'template/utils.lua', 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /luarocks/luadocumentor-0.1.5-1.rockspec: -------------------------------------------------------------------------------- 1 | package = 'LuaDocumentor' 2 | version = '0.1.5-1' 3 | description = { 4 | summary = 'LuaDocumentor allow users to generate HTML and API files from code documented using Lua documentation language.', 5 | detailed = [[ 6 | This is an example for the LuaRocks tutorial. 7 | Here we would put a detailed, typically 8 | paragraph-long description. 9 | ]], 10 | homepage = 'http://wiki.eclipse.org/Koneki/LDT/User_Area/LuaDocumentor', 11 | license = 'EPL' 12 | } 13 | source = { 14 | url = 'git://github.com/LuaDevelopmentTools/luadocumentor.git', 15 | tag = 'v0.1.5-1' 16 | } 17 | dependencies = { 18 | 'lua ~> 5.1', 19 | 'luafilesystem ~> 1.6', 20 | 'markdown ~> 0.32', 21 | 'metalua-compiler ~> 0.7', 22 | 'penlight ~> 0.9' 23 | } 24 | build = { 25 | type = 'builtin', 26 | install = { 27 | bin = { 28 | luadocumentor = 'luadocumentor.lua' 29 | }, 30 | lua = { 31 | ['models.internalmodelbuilder'] = 'models/internalmodelbuilder.mlua' 32 | } 33 | }, 34 | modules = { 35 | defaultcss = 'defaultcss.lua', 36 | docgenerator = 'docgenerator.lua', 37 | extractors = 'extractors.lua', 38 | lddextractor = 'lddextractor.lua', 39 | templateengine = 'templateengine.lua', 40 | 41 | ['fs.lfs'] = 'fs/lfs.lua', 42 | 43 | ['models.apimodel'] = 'models/apimodel.lua', 44 | ['models.apimodelbuilder'] = 'models/apimodelbuilder.lua', 45 | ['models.internalmodel'] = 'models/internalmodel.lua', 46 | ['models.ldparser'] = 'models/ldparser.lua', 47 | 48 | ['template.file'] = 'template/file.lua', 49 | ['template.index'] = 'template/index.lua', 50 | ['template.index.recordtypedef'] = 'template/index/recordtypedef.lua', 51 | ['template.item'] = 'template/item.lua', 52 | ['template.page'] = 'template/page.lua', 53 | ['template.recordtypedef'] = 'template/recordtypedef.lua', 54 | ['template.usage'] = 'template/usage.lua', 55 | ['template.utils'] = 'template/utils.lua', 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /models/apimodel.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2011-2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Simon BERNARD 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local M = {} 13 | 14 | -------------------------------------------------------------------------------- 15 | -- API MODEL 16 | -------------------------------------------------------------------------------- 17 | 18 | function M._file() 19 | local file = { 20 | -- FIELDS 21 | tag = "file", 22 | name = nil, -- string 23 | shortdescription = "", -- string 24 | description = "", -- string 25 | types = {}, -- map from typename to type 26 | globalvars = {}, -- map from varname to item 27 | returns = {}, -- list of return 28 | 29 | -- FUNCTIONS 30 | addtype = function (self,type) 31 | self.types[type.name] = type 32 | type.parent = self 33 | end, 34 | 35 | mergetype = function (self,newtype,erase,erasesourcerangefield) 36 | local currenttype = self.types[newtype.name] 37 | if currenttype then 38 | -- merge recordtypedef 39 | if currenttype.tag =="recordtypedef" and newtype.tag == "recordtypedef" then 40 | -- merge fields 41 | for fieldname ,field in pairs( newtype.fields) do 42 | local currentfield = currenttype.fields[fieldname] 43 | if erase or not currentfield then 44 | currenttype:addfield(field) 45 | elseif erasesourcerangefield then 46 | if field.sourcerange.min and field.sourcerange.max then 47 | currentfield.sourcerange.min = field.sourcerange.min 48 | currentfield.sourcerange.max = field.sourcerange.max 49 | end 50 | end 51 | end 52 | 53 | -- merge descriptions and source ranges 54 | if erase then 55 | if newtype.description or newtype.description == "" then currenttype.description = newtype.description end 56 | if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end 57 | if newtype.sourcerange.min and newtype.sourcerange.max then 58 | currenttype.sourcerange.min = newtype.sourcerange.min 59 | currenttype.sourcerange.max = newtype.sourcerange.max 60 | end 61 | end 62 | -- merge functiontypedef 63 | elseif currenttype.tag == "functiontypedef" and newtype.tag == "functiontypedef" then 64 | -- merge params 65 | for i, param1 in ipairs(newtype.params) do 66 | local missing = true 67 | for j, param2 in ipairs(currenttype.params) do 68 | if param1.name == param2.name then 69 | missing = false 70 | break 71 | end 72 | end 73 | if missing then 74 | table.insert(currenttype.params,param1) 75 | end 76 | end 77 | 78 | -- merge descriptions and source ranges 79 | if erase then 80 | if newtype.description or newtype.description == "" then currenttype.description = newtype.description end 81 | if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end 82 | if newtype.sourcerange.min and newtype.sourcerange.max then 83 | currenttype.sourcerange.min = newtype.sourcerange.min 84 | currenttype.sourcerange.max = newtype.sourcerange.max 85 | end 86 | end 87 | end 88 | else 89 | self:addtype(newtype) 90 | end 91 | end, 92 | 93 | addglobalvar = function (self,item) 94 | self.globalvars[item.name] = item 95 | item.parent = self 96 | end, 97 | 98 | moduletyperef = function (self) 99 | if self and self.returns[1] and self.returns[1].types[1] then 100 | local typeref = self.returns[1].types[1] 101 | return typeref 102 | end 103 | end 104 | } 105 | return file 106 | end 107 | 108 | function M._recordtypedef(name) 109 | local recordtype = { 110 | -- FIELDS 111 | tag = "recordtypedef", 112 | name = name, -- string (mandatory) 113 | shortdescription = "", -- string 114 | description = "", -- string 115 | fields = {}, -- map from fieldname to field 116 | sourcerange = {min=0,max=0}, 117 | supertype = nil, -- supertype of the type def (inheritance), should be a recordtypedef ref 118 | defaultkeytyperef = nil, -- the default typeref of key 119 | defaultvaluetyperef = nil, -- the default typeref of value 120 | structurekind = nil, -- kind of structure of the type: could be nil, "map" or "list" 121 | structuredescription = nil, -- description of the structure 122 | call = nil, -- typeref to the function use as __call on the type. 123 | -- FUNCTIONS 124 | addfield = function (self,field) 125 | self.fields[field.name] = field 126 | field.parent = self 127 | end, 128 | 129 | getcalldef = function( self) 130 | if self.call and self.call.tag == 'internaltyperef' then 131 | if self.parent and self.parent.tag == 'file' then 132 | local file = self.parent 133 | return file.types[self.call.typename] 134 | end 135 | end 136 | end 137 | } 138 | return recordtype 139 | end 140 | 141 | function M._functiontypedef(name) 142 | return { 143 | tag = "functiontypedef", 144 | name = name, -- string (mandatory) 145 | shortdescription = "", -- string 146 | description = "", -- string 147 | params = {}, -- list of parameter 148 | returns = {} -- list of return 149 | } 150 | end 151 | 152 | function M._parameter(name) 153 | return { 154 | tag = "parameter", 155 | name = name, -- string (mandatory) 156 | description = "", -- string 157 | type = nil -- typeref (external or internal or primitive typeref) 158 | } 159 | end 160 | 161 | function M._item(name) 162 | return { 163 | -- FIELDS 164 | tag = "item", 165 | name = name, -- string (mandatory) 166 | shortdescription = "", -- string 167 | description = "", -- string 168 | type = nil, -- typeref (external or internal or primitive typeref) 169 | occurrences = {}, -- list of identifier (see internalmodel) 170 | sourcerange = {min=0, max=0}, 171 | 172 | -- This is A TRICK 173 | -- This value is ALWAYS nil, except for internal purposes (short references). 174 | external = nil, 175 | 176 | -- FUNCTIONS 177 | addoccurence = function (self,occ) 178 | table.insert(self.occurrences,occ) 179 | occ.definition = self 180 | end, 181 | 182 | resolvetype = function (self,file) 183 | if self and self.type then 184 | if self.type.tag =="internaltyperef" then 185 | -- if file is not given try to retrieve it. 186 | if not file then 187 | if self.parent and self.parent.tag == 'recordtypedef' then 188 | file = self.parent.parent 189 | elseif self.parent.tag == 'file' then 190 | file = self.parent 191 | end 192 | end 193 | if file then return file.types[self.type.typename] end 194 | elseif self.type.tag =="inlinetyperef" then 195 | return self.type.def 196 | end 197 | end 198 | end 199 | } 200 | end 201 | 202 | function M._externaltypref(modulename, typename) 203 | return { 204 | tag = "externaltyperef", 205 | modulename = modulename, -- string 206 | typename = typename -- string 207 | } 208 | end 209 | 210 | function M._internaltyperef(typename) 211 | return { 212 | tag = "internaltyperef", 213 | typename = typename -- string 214 | } 215 | end 216 | 217 | function M._primitivetyperef(typename) 218 | return { 219 | tag = "primitivetyperef", 220 | typename = typename -- string 221 | } 222 | end 223 | 224 | function M._moduletyperef(modulename,returnposition) 225 | return { 226 | tag = "moduletyperef", 227 | modulename = modulename, -- string 228 | returnposition = returnposition -- number 229 | } 230 | end 231 | 232 | function M._exprtyperef(expression,returnposition) 233 | return { 234 | tag = "exprtyperef", 235 | expression = expression, -- expression (see internal model) 236 | returnposition = returnposition -- number 237 | } 238 | end 239 | 240 | function M._inlinetyperef(definition) 241 | return { 242 | tag = "inlinetyperef", 243 | def = definition, -- expression (see internal model) 244 | 245 | } 246 | end 247 | 248 | function M._return(description) 249 | return { 250 | tag = "return", 251 | description = description or "", -- string 252 | types = {} -- list of typref (external or internal or primitive typeref) 253 | } 254 | end 255 | return M 256 | -------------------------------------------------------------------------------- /models/apimodelbuilder.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2011-2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Simon BERNARD 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local apimodel = require "models.apimodel" 13 | local ldp = require "models.ldparser" 14 | local Q = require "metalua.treequery" 15 | 16 | local M = {} 17 | 18 | local handledcomments={} -- cache to know the comment already handled 19 | 20 | ---- 21 | -- UTILITY METHODS 22 | local primitivetypes = { 23 | ['boolean'] = true, 24 | ['function'] = true, 25 | ['nil'] = true, 26 | ['number'] = true, 27 | ['string'] = true, 28 | ['thread'] = true, 29 | ['userdata'] = true, 30 | ['list'] = true, 31 | ['map'] = true, 32 | ['any'] = true 33 | 34 | } 35 | 36 | -- get or create the typedef with the name "name" 37 | local function gettypedef(_file,name,kind,sourcerangemin,sourcerangemax) 38 | local kind = kind or "recordtypedef" 39 | local _typedef = _file.types[name] 40 | if _typedef then 41 | if _typedef.tag == kind then return _typedef end 42 | else 43 | if kind == "recordtypedef" and name ~= "global" then 44 | local _recordtypedef = apimodel._recordtypedef(name) 45 | 46 | -- define sourcerange 47 | _recordtypedef.sourcerange.min = sourcerangemin 48 | _recordtypedef.sourcerange.max = sourcerangemax 49 | 50 | -- add to file if a name is defined 51 | if _recordtypedef.name then _file:addtype(_recordtypedef) end 52 | return _recordtypedef 53 | elseif kind == "functiontypedef" then 54 | -- TODO support function 55 | return nil 56 | else 57 | return nil 58 | end 59 | end 60 | return nil 61 | end 62 | 63 | 64 | -- create a typeref from the typref doc_tag 65 | local function createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) 66 | local _typeref 67 | if dt_typeref.tag == "typeref" then 68 | if dt_typeref.module then 69 | -- manage external type 70 | _typeref = apimodel._externaltypref() 71 | _typeref.modulename = dt_typeref.module 72 | _typeref.typename = dt_typeref.type 73 | else 74 | if dt_typeref.type == "table" then 75 | -- manage special table type 76 | _typeref = apimodel._inlinetyperef(apimodel._recordtypedef("table")) 77 | elseif dt_typeref.type == "list" or dt_typeref.type == "map" then 78 | -- manage structures 79 | local structuretypedef = apimodel._recordtypedef(dt_typeref) 80 | structuretypedef.defaultvaluetyperef = createtyperef(dt_typeref.valuetype) 81 | if dt_typeref.type == "map" then 82 | structuretypedef.defaultkeytyperef = createtyperef(dt_typeref.keytype) 83 | end 84 | structuretypedef.structurekind = dt_typeref.type 85 | structuretypedef.name = dt_typeref.type 86 | _typeref = apimodel._inlinetyperef(structuretypedef) 87 | elseif primitivetypes[dt_typeref.type] then 88 | -- manage primitive types 89 | _typeref = apimodel._primitivetyperef() 90 | _typeref.typename = dt_typeref.type 91 | else 92 | -- manage internal type 93 | _typeref = apimodel._internaltyperef() 94 | _typeref.typename = dt_typeref.type 95 | if _file then 96 | gettypedef(_file, _typeref.typename, "recordtypedef", sourcerangemin,sourcerangemax) 97 | end 98 | end 99 | end 100 | end 101 | return _typeref 102 | end 103 | 104 | -- create a return from the return doc_tag 105 | local function createreturn(dt_return,_file,sourcerangemin,sourcerangemax) 106 | local _return = apimodel._return() 107 | 108 | _return.description = dt_return.description 109 | 110 | -- manage typeref 111 | if dt_return.types then 112 | for _, dt_typeref in ipairs(dt_return.types) do 113 | local _typeref = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) 114 | if _typeref then 115 | table.insert(_return.types,_typeref) 116 | end 117 | end 118 | end 119 | return _return 120 | end 121 | 122 | -- create a item from the field doc_tag 123 | local function createfield(dt_field,_file,sourcerangemin,sourcerangemax) 124 | local _item = apimodel._item(dt_field.name) 125 | 126 | if dt_field.shortdescription then 127 | _item.shortdescription = dt_field.shortdescription 128 | _item.description = dt_field.description 129 | else 130 | _item.shortdescription = dt_field.description 131 | end 132 | 133 | -- manage typeref 134 | local dt_typeref = dt_field.type 135 | if dt_typeref then 136 | _item.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) 137 | end 138 | return _item 139 | end 140 | 141 | -- create a param from the param doc_tag 142 | local function createparam(dt_param,_file,sourcerangemin,sourcerangemax) 143 | if not dt_param.name then return nil end 144 | 145 | local _parameter = apimodel._parameter(dt_param.name) 146 | _parameter.description = dt_param.description 147 | 148 | -- manage typeref 149 | local dt_typeref = dt_param.type 150 | if dt_typeref then 151 | _parameter.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax) 152 | end 153 | return _parameter 154 | end 155 | 156 | -- get or create the typedef with the name "name" 157 | function M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) 158 | if scope and not scope.module then 159 | if _item.name then 160 | if scope.type == "global" then 161 | _file:addglobalvar(_item) 162 | else 163 | local _recordtypedef = gettypedef (_file, scope.type ,"recordtypedef",sourcerangemin,sourcerangemax) 164 | _recordtypedef:addfield(_item) 165 | end 166 | else 167 | -- if no item name precise we store the scope in the item to be able to add it to the right parent later 168 | _item.scope = scope 169 | end 170 | end 171 | end 172 | 173 | -- Function type counter 174 | local i = 0 175 | 176 | -- Reset function type counter 177 | local function resetfunctiontypeidgenerator() 178 | i = 0 179 | end 180 | 181 | -- Provides an unique index for a function type 182 | local function generatefunctiontypeid() 183 | i = i + 1 184 | return i 185 | end 186 | 187 | -- generate a function type name 188 | local function generatefunctiontypename(_functiontypedef) 189 | local name = {"__"} 190 | if _functiontypedef.returns and _functiontypedef.returns[1] then 191 | local ret = _functiontypedef.returns[1] 192 | for _, type in ipairs(ret.types) do 193 | if type.typename then 194 | if type.modulename then 195 | table.insert(name,type.modulename) 196 | end 197 | table.insert(name,"#") 198 | table.insert(name,type.typename) 199 | end 200 | end 201 | 202 | end 203 | table.insert(name,"=") 204 | if _functiontypedef.params then 205 | for _, param in ipairs(_functiontypedef.params) do 206 | local type = param.type 207 | if type then 208 | if type.typename then 209 | if type.modulename then 210 | table.insert(name,type.modulename) 211 | end 212 | table.insert(name,"#") 213 | table.insert(name,type.typename) 214 | else 215 | table.insert(name,"#unknown") 216 | end 217 | end 218 | table.insert(name,"[") 219 | table.insert(name,param.name) 220 | table.insert(name,"]") 221 | end 222 | end 223 | table.insert(name,"__") 224 | table.insert(name, generatefunctiontypeid()) 225 | return table.concat(name) 226 | end 227 | 228 | -- 229 | -- Store user defined tags 230 | -- 231 | local function attachmetadata(apiobj, parsedcomment) 232 | local thirdtags = parsedcomment and parsedcomment.unknowntags 233 | if thirdtags then 234 | -- Define a storage index for user defined tags on current API element 235 | if not apiobj.metadata then apiobj.metadata = {} end 236 | 237 | -- Loop over user defined tags 238 | for usertag, taglist in pairs(thirdtags) do 239 | if not apiobj.metadata[ usertag ] then 240 | apiobj.metadata[ usertag ] = { 241 | tag = usertag 242 | } 243 | end 244 | for _, tag in ipairs( taglist ) do 245 | table.insert(apiobj.metadata[usertag], tag) 246 | end 247 | end 248 | end 249 | end 250 | 251 | 252 | ------------------------------------------------------ 253 | -- create the module api 254 | function M.createmoduleapi(ast,modulename) 255 | 256 | -- Initialise function type naming 257 | resetfunctiontypeidgenerator() 258 | 259 | local _file = apimodel._file() 260 | 261 | local _comment2apiobj = {} 262 | 263 | local function handlecomment(comment) 264 | 265 | -- Extract information from tagged comments 266 | local parsedcomment = ldp.parse(comment[1]) 267 | if not parsedcomment then return nil end 268 | 269 | -- Get tags from the languages 270 | local regulartags = parsedcomment.tags 271 | 272 | -- Will contain last API object generated from comments 273 | local _lastapiobject 274 | 275 | -- if comment is an ld comment 276 | if regulartags then 277 | -- manage "module" comment 278 | if regulartags["module"] then 279 | -- get name 280 | _file.name = regulartags["module"][1].name or modulename 281 | _lastapiobject = _file 282 | 283 | -- manage descriptions 284 | _file.shortdescription = parsedcomment.shortdescription 285 | _file.description = parsedcomment.description 286 | 287 | local sourcerangemin = comment.lineinfo.first.offset 288 | local sourcerangemax = comment.lineinfo.last.offset 289 | 290 | -- manage returns 291 | if regulartags ["return"] then 292 | for _, dt_return in ipairs(regulartags ["return"]) do 293 | local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax) 294 | table.insert(_file.returns,_return) 295 | end 296 | end 297 | -- if no returns on module create a defaultreturn of type #modulename 298 | if #_file.returns == 0 and _file.name then 299 | -- create internal type ref 300 | local _typeref = apimodel._internaltyperef() 301 | _typeref.typename = _file.name 302 | 303 | -- create return 304 | local _return = apimodel._return() 305 | table.insert(_return.types,_typeref) 306 | 307 | -- add return 308 | table.insert(_file.returns,_return) 309 | 310 | --create recordtypedef is not define 311 | local _moduletypedef = gettypedef(_file,_typeref.typename,"recordtypedef",sourcerangemin,sourcerangemax) 312 | 313 | -- manage extends (inheritance) and structure tags 314 | if _moduletypedef and _moduletypedef.tag == "recordtypedef" then 315 | if regulartags["extends"] and regulartags["extends"][1] then 316 | local supertype = regulartags["extends"][1].type 317 | if supertype then _moduletypedef.supertype = createtyperef(supertype) end 318 | end 319 | if regulartags["map"] and regulartags["map"][1] then 320 | local keytype = regulartags["map"][1].keytype 321 | local valuetype = regulartags["map"][1].valuetype 322 | if keytype and valuetype then 323 | _moduletypedef.defaultkeytyperef = createtyperef(keytype) 324 | _moduletypedef.defaultvaluetyperef = createtyperef(valuetype) 325 | _moduletypedef.structurekind = "map" 326 | _moduletypedef.structuredescription = regulartags["map"][1].description 327 | end 328 | elseif regulartags["list"] and regulartags["list"][1] then 329 | local type = regulartags["list"][1].type 330 | if type then 331 | _moduletypedef.defaultvaluetyperef = createtyperef(type) 332 | _moduletypedef.structurekind = "list" 333 | _moduletypedef.structuredescription = regulartags["list"][1].description 334 | end 335 | end 336 | end 337 | end 338 | -- manage "type" comment 339 | elseif regulartags["type"] and regulartags["type"][1].name ~= "global" then 340 | local dt_type = regulartags["type"][1]; 341 | -- create record type if it doesn't exist 342 | local sourcerangemin = comment.lineinfo.first.offset 343 | local sourcerangemax = comment.lineinfo.last.offset 344 | local _recordtypedef = gettypedef (_file, dt_type.name ,"recordtypedef",sourcerangemin,sourcerangemax) 345 | _lastapiobject = _recordtypedef 346 | 347 | -- re-set sourcerange in case the type was created before the type tag 348 | _recordtypedef.sourcerange.min = sourcerangemin 349 | _recordtypedef.sourcerange.max = sourcerangemax 350 | 351 | -- manage description 352 | _recordtypedef.shortdescription = parsedcomment.shortdescription 353 | _recordtypedef.description = parsedcomment.description 354 | 355 | -- manage fields 356 | if regulartags["field"] then 357 | for _, dt_field in ipairs(regulartags["field"]) do 358 | local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax) 359 | -- define sourcerange only if we create it 360 | _item.sourcerange.min = sourcerangemin 361 | _item.sourcerange.max = sourcerangemax 362 | if _item and _item.name then 363 | _recordtypedef:addfield(_item) end 364 | end 365 | end 366 | 367 | -- manage extends (inheritance) 368 | if regulartags["extends"] and regulartags["extends"][1] then 369 | local supertype = regulartags["extends"][1].type 370 | if supertype then _recordtypedef.supertype = createtyperef(supertype) end 371 | end 372 | 373 | -- manage structure tag 374 | if regulartags["map"] and regulartags["map"][1] then 375 | local keytype = regulartags["map"][1].keytype 376 | local valuetype = regulartags["map"][1].valuetype 377 | if keytype and valuetype then 378 | _recordtypedef.defaultkeytyperef = createtyperef(keytype) 379 | _recordtypedef.defaultvaluetyperef = createtyperef(valuetype) 380 | _recordtypedef.structurekind = "map" 381 | _recordtypedef.structuredescription = regulartags["map"][1].description 382 | end 383 | elseif regulartags["list"] and regulartags["list"][1] then 384 | local type = regulartags["list"][1].type 385 | if type then 386 | _recordtypedef.defaultvaluetyperef = createtyperef(type) 387 | _recordtypedef.structurekind = "list" 388 | _recordtypedef.structuredescription = regulartags["list"][1].description 389 | end 390 | end 391 | elseif regulartags["field"] then 392 | local dt_field = regulartags["field"][1] 393 | 394 | -- create item 395 | local sourcerangemin = comment.lineinfo.first.offset 396 | local sourcerangemax = comment.lineinfo.last.offset 397 | local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax) 398 | _item.shortdescription = parsedcomment.shortdescription 399 | _item.description = parsedcomment.description 400 | _lastapiobject = _item 401 | 402 | -- define sourcerange 403 | _item.sourcerange.min = sourcerangemin 404 | _item.sourcerange.max = sourcerangemax 405 | 406 | -- add item to its parent 407 | local scope = regulartags["field"][1].parent 408 | M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) 409 | elseif regulartags["function"] or regulartags["param"] or regulartags["return"] or regulartags["callof"] then 410 | -- create item 411 | local _item = apimodel._item() 412 | _item.shortdescription = parsedcomment.shortdescription 413 | _item.description = parsedcomment.description 414 | _lastapiobject = _item 415 | 416 | -- set name 417 | if regulartags["function"] then _item.name = regulartags["function"][1].name end 418 | 419 | -- define sourcerange 420 | local sourcerangemin = comment.lineinfo.first.offset 421 | local sourcerangemax = comment.lineinfo.last.offset 422 | _item.sourcerange.min = sourcerangemin 423 | _item.sourcerange.max = sourcerangemax 424 | 425 | 426 | -- create function type 427 | local _functiontypedef = apimodel._functiontypedef() 428 | _functiontypedef.shortdescription = parsedcomment.shortdescription 429 | _functiontypedef.description = parsedcomment.description 430 | 431 | 432 | -- manage params 433 | if regulartags["param"] then 434 | for _, dt_param in ipairs(regulartags["param"]) do 435 | local _param = createparam(dt_param,_file,sourcerangemin,sourcerangemax) 436 | table.insert(_functiontypedef.params,_param) 437 | end 438 | end 439 | 440 | -- manage returns 441 | if regulartags["return"] then 442 | for _, dt_return in ipairs(regulartags["return"]) do 443 | local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax) 444 | table.insert(_functiontypedef.returns,_return) 445 | end 446 | end 447 | 448 | -- add type name 449 | _functiontypedef.name = generatefunctiontypename(_functiontypedef) 450 | attachmetadata(_functiontypedef, parsedcomment) 451 | _file:addtype(_functiontypedef) 452 | 453 | -- create ref to this type 454 | local _internaltyperef = apimodel._internaltyperef() 455 | _internaltyperef.typename = _functiontypedef.name 456 | _item.type=_internaltyperef 457 | 458 | -- add item to its parent 459 | local sourcerangemin = comment.lineinfo.first.offset 460 | local sourcerangemax = comment.lineinfo.last.offset 461 | local scope = (regulartags["function"] and regulartags["function"][1].parent) or nil 462 | M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) 463 | 464 | -- manage callof 465 | if regulartags["callof"] and regulartags["callof"][1] and regulartags["callof"][1].type then 466 | -- get the type which will be callable ! 467 | local _internaltyperef = createtyperef(regulartags["callof"][1].type) 468 | if _internaltyperef and _internaltyperef.tag == "internaltyperef" then 469 | local _typedeftypedef = gettypedef(_file,_internaltyperef.typename,"recordtypedef",sourcerangemin,sourcerangemax) 470 | if _typedeftypedef then 471 | -- refer the function used when the type is called 472 | local _internaltyperef = apimodel._internaltyperef() 473 | _internaltyperef.typename = _functiontypedef.name 474 | _typedeftypedef.call =_internaltyperef 475 | end 476 | end 477 | end 478 | end 479 | end 480 | 481 | -- when we could not know which type of api object it is, we suppose this is an item 482 | if not _lastapiobject then 483 | _lastapiobject = apimodel._item() 484 | _lastapiobject.shortdescription = parsedcomment.shortdescription 485 | _lastapiobject.description = parsedcomment.description 486 | _lastapiobject.sourcerange.min = comment.lineinfo.first.offset 487 | _lastapiobject.sourcerange.max = comment.lineinfo.last.offset 488 | end 489 | 490 | attachmetadata(_lastapiobject, parsedcomment) 491 | 492 | -- if we create an api object linked it to 493 | _comment2apiobj[comment] =_lastapiobject 494 | end 495 | 496 | local function parsecomment(node, parent, ...) 497 | -- check for comments before this node 498 | if node.lineinfo and node.lineinfo.first.comments then 499 | local comments = node.lineinfo.first.comments 500 | -- check all comments 501 | for _,comment in ipairs(comments) do 502 | -- if not already handled 503 | if not handledcomments[comment] then 504 | handlecomment(comment) 505 | handledcomments[comment]=true 506 | end 507 | end 508 | end 509 | -- check for comments after this node 510 | if node.lineinfo and node.lineinfo.last.comments then 511 | local comments = node.lineinfo.last.comments 512 | -- check all comments 513 | for _,comment in ipairs(comments) do 514 | -- if not already handled 515 | if not handledcomments[comment] then 516 | handlecomment(comment) 517 | handledcomments[comment]=true 518 | end 519 | end 520 | end 521 | end 522 | Q(ast):filter(function(x) return x.tag~=nil end):foreach(parsecomment) 523 | return _file, _comment2apiobj 524 | end 525 | 526 | 527 | function M.extractlocaltype ( commentblock,_file) 528 | if not commentblock then return nil end 529 | 530 | local stringcomment = commentblock[1] 531 | 532 | local parsedtag = ldp.parseinlinecomment(stringcomment) 533 | if parsedtag then 534 | local sourcerangemin = commentblock.lineinfo.first.offset 535 | local sourcerangemax = commentblock.lineinfo.last.offset 536 | 537 | return createtyperef(parsedtag,_file,sourcerangemin,sourcerangemax), parsedtag.description 538 | end 539 | 540 | return nil, stringcomment 541 | end 542 | 543 | M.generatefunctiontypename = generatefunctiontypename 544 | 545 | return M 546 | -------------------------------------------------------------------------------- /models/internalmodel.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local M = {} 13 | 14 | function M._internalcontent() 15 | return { 16 | content = nil, -- block 17 | unknownglobalvars = {}, -- list of item 18 | tag = "MInternalContent" 19 | } 20 | end 21 | 22 | function M._block() 23 | return { 24 | content = {}, -- list of expr (identifier, index, call, invoke, block) 25 | localvars = {}, -- list of {var=item, scope ={min,max}} 26 | sourcerange = {min=0,max=0}, 27 | tag = "MBlock" 28 | } 29 | end 30 | 31 | function M._identifier() 32 | return { 33 | definition = nil, -- item 34 | sourcerange = {min=0,max=0}, 35 | tag = "MIdentifier" 36 | } 37 | end 38 | 39 | function M._index(key, value) 40 | return { 41 | left= key, -- expr (identifier, index, call, invoke, block) 42 | right= value, -- string 43 | sourcerange = {min=0,max=0}, 44 | tag = "MIndex" 45 | } 46 | end 47 | 48 | function M._call(funct) 49 | return { 50 | func = funct, -- expr (identifier, index, call, invoke, block) 51 | sourcerange = {min=0,max=0}, 52 | tag = "MCall" 53 | } 54 | end 55 | 56 | function M._invoke(name, expr) 57 | return { 58 | functionname = name, -- string 59 | record = expr, -- expr (identifier, index, call, invoke, block) 60 | sourcerange = {min=0,max=0}, 61 | tag = "MInvoke" 62 | } 63 | end 64 | 65 | return M 66 | -------------------------------------------------------------------------------- /models/internalmodelbuilder.mlua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2011-2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Simon BERNARD 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | -{ extension ('match', ...) } 13 | 14 | local Q = require 'metalua.treequery' 15 | 16 | local internalmodel = require 'models.internalmodel' 17 | local apimodel = require 'models.apimodel' 18 | local apimodelbuilder = require 'models.apimodelbuilder' 19 | 20 | local M = {} 21 | 22 | -- Analyzes an AST and returns two tables 23 | -- * `locals`, which associates `Id{ } nodes which create a local variable 24 | -- to a list of the `Id{ } occurrence nodes of that variable; 25 | -- * `globals` which associates variable names to occurrences of 26 | -- global variables having that name. 27 | function bindings(ast) 28 | local locals, globals = { }, { } 29 | local function f(id, ...) 30 | local name = id[1] 31 | if Q.is_binder(id, ...) then 32 | local binder = ... -- parent is the binder 33 | locals[binder] = locals[binder] or { } 34 | locals[binder][name]={ } 35 | else 36 | local _, binder = Q.get_binder(id, ...) 37 | if binder then -- this is a local 38 | table.insert(locals[binder][name], id) 39 | else 40 | local g = globals[name] 41 | if g then table.insert(g, id) else globals[name]={id} end 42 | end 43 | end 44 | end 45 | Q(ast) :filter('Id') :foreach(f) 46 | return locals, globals 47 | end 48 | 49 | -- -------------------------------------- 50 | 51 | -- ---------------------------------------------------------- 52 | -- return the comment linked before to this node 53 | -- ---------------------------------------------------------- 54 | local function getlinkedcommentbefore(node) 55 | local function _getlinkedcomment(node,line) 56 | if node and node.lineinfo and node.lineinfo.first.line == line then 57 | -- get the last comment before (the nearest of code) 58 | local comments = node.lineinfo.first.comments 59 | local comment = comments and comments[#comments] 60 | if comment and comment.lineinfo.last.line == line-1 then 61 | -- ignore the comment if there are code before on the same line 62 | if node.lineinfo.first.facing and (node.lineinfo.first.facing.line ~= comment.lineinfo.first.line) then 63 | return comment 64 | end 65 | else 66 | return _getlinkedcomment(node.parent,line) 67 | end 68 | end 69 | return nil 70 | end 71 | 72 | if node.lineinfo and node.lineinfo.first.line then 73 | return _getlinkedcomment(node,node.lineinfo.first.line) 74 | else 75 | return nil 76 | end 77 | end 78 | 79 | -- ---------------------------------------------------------- 80 | -- return the comment linked after to this node 81 | -- ---------------------------------------------------------- 82 | local function getlinkedcommentafter(node) 83 | local function _getlinkedcomment(node,line) 84 | if node and node.lineinfo and node.lineinfo.last.line == line then 85 | -- get the first comment after (the nearest of code) 86 | local comments = node.lineinfo.last.comments 87 | local comment = comments and comments[1] 88 | if comment and comment.lineinfo.first.line == line then 89 | return comment 90 | else 91 | return _getlinkedcomment(node.parent,line) 92 | end 93 | end 94 | return nil 95 | end 96 | 97 | if node.lineinfo and node.lineinfo.last.line then 98 | return _getlinkedcomment(node,node.lineinfo.last.line) 99 | else 100 | return nil 101 | end 102 | end 103 | 104 | -- ---------------------------------------------------------- 105 | -- return true if this node is a block for the internal representation 106 | -- ---------------------------------------------------------- 107 | local supported_b = { 108 | Function = true, 109 | Do = true, 110 | While = true, 111 | Fornum = true, 112 | Forin = true, 113 | Repeat = true, 114 | } 115 | local function supportedblock(node, parent) 116 | return supported_b[ node.tag ] or 117 | (parent and parent.tag == "If" and node.tag == nil) 118 | end 119 | 120 | -- ---------------------------------------------------------- 121 | -- create a block from the metalua node 122 | -- ---------------------------------------------------------- 123 | local function createblock(block, parent) 124 | local _block = internalmodel._block() 125 | match block with 126 | | `Function{param, body} 127 | | `Do{...} 128 | | `Fornum {identifier, min, max, body} 129 | | `Forin {identifiers, exprs, body} 130 | | `Repeat {body, expr} -> 131 | _block.sourcerange.min = block.lineinfo.first.offset 132 | _block.sourcerange.max = block.lineinfo.last.offset 133 | | `While {expr, body} -> 134 | _block.sourcerange.min = body.lineinfo.first.facing.offset 135 | _block.sourcerange.max = body.lineinfo.last.facing.offset 136 | | _ -> 137 | if parent and parent.tag == "If" and block.tag == nil then 138 | _block.sourcerange.min = block.lineinfo.first.facing.offset 139 | _block.sourcerange.max = block.lineinfo.last.facing.offset 140 | end 141 | end 142 | return _block 143 | end 144 | 145 | -- ---------------------------------------------------------- 146 | -- return true if this node is a expression in the internal representation 147 | -- ---------------------------------------------------------- 148 | local supported_e = { 149 | Index = true, 150 | Id = true, 151 | Call = true, 152 | Invoke = true 153 | } 154 | local function supportedexpr(node) 155 | return supported_e[ node.tag ] 156 | end 157 | 158 | local idto_block = {} -- cache from metalua id to internal model block 159 | local idto_identifier = {} -- cache from metalua id to internal model indentifier 160 | local expreto_expression = {} -- cache from metalua expression to internal model expression 161 | 162 | -- ---------------------------------------------------------- 163 | -- create an expression from a metalua node 164 | -- ---------------------------------------------------------- 165 | local function createexpr(expr,_block) 166 | local _expr = nil 167 | 168 | match expr with 169 | | `Id { name } -> 170 | -- we store the block which hold this node 171 | -- to be able to define 172 | idto_block[expr]= _block 173 | 174 | -- if expr has not line info, it means expr has no representation in the code 175 | -- so we don't need it. 176 | if not expr.lineinfo then return nil end 177 | 178 | -- create identifier 179 | local _identifier = internalmodel._identifier() 180 | idto_identifier[expr]= _identifier 181 | _expr = _identifier 182 | | `Index { innerexpr, rightpart } -> 183 | if not expr.lineinfo then return nil end 184 | -- create index 185 | local _expression = createexpr(innerexpr,_block) 186 | if _expression then 187 | if rightpart and rightpart.tag=='String' then 188 | _expr = internalmodel._index(_expression,rightpart[1]) 189 | else 190 | _expr = internalmodel._index(_expression,nil) 191 | end 192 | end 193 | | `Call{innerexpr, ...} -> 194 | if not expr.lineinfo then return nil end 195 | -- create call 196 | local _expression = createexpr(innerexpr,_block) 197 | if _expression then _expr = internalmodel._call(_expression) end 198 | | `Invoke{innerexpr,`String{functionname},...} -> 199 | if not expr.lineinfo then return nil end 200 | -- create invoke 201 | local _expression = createexpr(innerexpr,_block) 202 | if _expression then _expr = internalmodel._invoke(functionname,_expression) end 203 | | _ -> 204 | end 205 | 206 | if _expr then 207 | _expr.sourcerange.min = expr.lineinfo.first.offset 208 | _expr.sourcerange.max = expr.lineinfo.last.offset 209 | 210 | expreto_expression[expr] = _expr 211 | end 212 | 213 | return _expr 214 | end 215 | 216 | -- ---------------------------------------------------------- 217 | -- create block and expression node 218 | -- ---------------------------------------------------------- 219 | local function createtreestructure(ast) 220 | -- create internal content 221 | local _internalcontent = internalmodel._internalcontent() 222 | 223 | -- create root block 224 | local _block = internalmodel._block() 225 | local _blocks = { _block } 226 | _block.sourcerange.min = ast.lineinfo.first.facing.offset 227 | -- TODO remove the math.max when we support partial AST 228 | _block.sourcerange.max = math.max(ast.lineinfo.last.facing.offset, 10000) 229 | 230 | _internalcontent.content = _block 231 | 232 | -- visitor function (down) 233 | local function down (node,parent) 234 | if supportedblock(node,parent) then 235 | -- create the block 236 | local _block = createblock(node,parent) 237 | -- add it to parent block 238 | table.insert(_blocks[#_blocks].content, _block) 239 | -- enqueue the last block to know the "current" block 240 | table.insert(_blocks,_block) 241 | elseif supportedexpr(node) then 242 | -- we handle expression only if it was not already do 243 | if not expreto_expression[node] then 244 | -- create expr 245 | local _expression = createexpr(node,_blocks[#_blocks]) 246 | -- add it to parent block 247 | if _expression then 248 | table.insert(_blocks[#_blocks].content, _expression) 249 | end 250 | end 251 | end 252 | end 253 | 254 | -- visitor function (up) 255 | local function up (node, parent) 256 | if supportedblock(node,parent) then 257 | -- dequeue the last block to know the "current" block 258 | table.remove(_blocks,#_blocks) 259 | end 260 | end 261 | 262 | -- visit ast and build internal model 263 | Q(ast):foreach(down,up) 264 | 265 | return _internalcontent 266 | end 267 | 268 | local getitem 269 | 270 | -- ---------------------------------------------------------- 271 | -- create the type from the node and position 272 | -- ---------------------------------------------------------- 273 | local function createtype(node,position,comment2apiobj,file) 274 | -- create module type ref 275 | match node with 276 | | `Call{ `Id "require", `String {modulename}} -> 277 | return apimodel._moduletyperef(modulename,position) 278 | | `Function {params, body} -> 279 | -- create the functiontypedef from code 280 | local _functiontypedef = apimodel._functiontypedef() 281 | for _, p in ipairs(params) do 282 | -- create parameters 283 | local paramname 284 | if p.tag=="Dots" then 285 | paramname = "..." 286 | else 287 | paramname = p[1] 288 | end 289 | local _param = apimodel._parameter(paramname) 290 | table.insert(_functiontypedef.params,_param) 291 | end 292 | _functiontypedef.name = "___" -- no name for inline type 293 | 294 | return apimodel._inlinetyperef(_functiontypedef) 295 | | `String {value} -> 296 | local typeref = apimodel._primitivetyperef("string") 297 | return typeref 298 | | `Number {value} -> 299 | local typeref = apimodel._primitivetyperef("number") 300 | return typeref 301 | | `True | `False -> 302 | local typeref = apimodel._primitivetyperef("boolean") 303 | return typeref 304 | | `Table {...} -> 305 | -- create recordtypedef from code 306 | local _recordtypedef = apimodel._recordtypedef("table") 307 | -- for each element of the table 308 | for i=1,select("#", ...) do 309 | local pair = select(i, ...) 310 | -- if this is a pair we create a new item in the type 311 | if pair.tag == "Pair" then 312 | -- create an item 313 | local _item = getitem(pair,nil, comment2apiobj,file) 314 | if _item then 315 | _recordtypedef:addfield(_item) 316 | end 317 | end 318 | end 319 | return apimodel._inlinetyperef(_recordtypedef) 320 | | _ -> 321 | end 322 | -- if node is an expression supported 323 | local supportedexpr = expreto_expression[node] 324 | if supportedexpr then 325 | -- create expression type ref 326 | return apimodel._exprtyperef(supportedexpr,position) 327 | end 328 | 329 | end 330 | 331 | local function completeapidoctype(apidoctype,itemname,init,file,comment2apiobj) 332 | if not apidoctype.name then 333 | apidoctype.name = itemname 334 | file:mergetype(apidoctype) 335 | end 336 | 337 | -- create type from code 338 | local typeref = createtype(init,1,comment2apiobj,file) 339 | if typeref and typeref.tag == "inlinetyperef" 340 | and typeref.def.tag == "recordtypedef" then 341 | 342 | -- set the name 343 | typeref.def.name = apidoctype.name 344 | 345 | -- merge the type with priority to documentation except for source range 346 | file:mergetype(typeref.def,false,true) 347 | end 348 | end 349 | 350 | local function completeapidocitem (apidocitem, itemname, init, file, binder, comment2apiobj) 351 | -- manage the case item has no name 352 | if not apidocitem.name then 353 | apidocitem.name = itemname 354 | 355 | -- if item has no name this means it could not be attach to a parent 356 | if apidocitem.scope then 357 | apimodelbuilder.additemtoparent(file,apidocitem,apidocitem.scope,apidocitem.sourcerange.min,apidocitem.sourcerange.max) 358 | apidocitem.scope = nil 359 | end 360 | end 361 | 362 | -- for function try to merge definition 363 | local apitype = apidocitem:resolvetype(file) 364 | if apitype and apitype.tag == "functiontypedef" then 365 | local codetype = createtype(init,1,comment2apiobj,file) 366 | if codetype and codetype.tag =="inlinetyperef" then 367 | codetype.def.name = apitype.name 368 | file:mergetype(codetype.def) 369 | end 370 | end 371 | 372 | -- manage the case item has no type 373 | if not apidocitem.type then 374 | -- extract typing from comment 375 | local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) 376 | 377 | if type then 378 | apidocitem.type = type 379 | else 380 | -- if not found extracttype from code 381 | apidocitem.type = createtype(init,1,comment2apiobj,file) 382 | end 383 | 384 | local apitype = apidocitem:resolvetype(file) 385 | if apitype and apitype.tag == "functiontypedef" and apidocitem.metadata then 386 | apitype.metadata = apidocitem.metadata 387 | end 388 | end 389 | end 390 | 391 | -- ---------------------------------------------------------- 392 | -- create or get the item finding in the binder with the given itemname 393 | -- return also the ast node corresponding to this item 394 | -- ---------------------------------------------------------- 395 | getitem = function (binder, itemname, comment2apiobj, file) 396 | 397 | -- local function to create item 398 | local function createitem(itemname, astnode, itemtype, description) 399 | local _item = apimodel._item(itemname) 400 | if description then _item.description = description end 401 | _item.type = itemtype 402 | if astnode and astnode.lineinfo then 403 | _item.sourcerange.min = astnode.lineinfo.first.offset 404 | _item.sourcerange.max = astnode.lineinfo.last.offset 405 | end 406 | return _item, astnode 407 | end 408 | 409 | -- try to match binder with known patter of item declaration 410 | match binder with 411 | | `Pair {string, init} 412 | | `Set { {`Index { right , string}}, {init,...}} if string and string.tag =="String" -> 413 | -- Pair and set is for searching field from type .. 414 | -- if the itemname is given this mean we search for a local or a global not a field type. 415 | if not itemname then 416 | local itemname = string[1] 417 | 418 | -- check for luadoc typing 419 | local commentbefore = getlinkedcommentbefore(binder) 420 | local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment 421 | if apiobj then 422 | if apiobj.tag=="item" then 423 | if not apiobj.name or apiobj.name == itemname then 424 | -- use code to complete api information if it's necessary 425 | completeapidocitem(apiobj, itemname, init,file,binder,comment2apiobj) 426 | -- for item use code source range rather than doc source range 427 | if string and string.lineinfo then 428 | apiobj.sourcerange.min = string.lineinfo.first.offset 429 | apiobj.sourcerange.max = string.lineinfo.last.offset 430 | end 431 | return apiobj, string 432 | end 433 | elseif apiobj.tag=="recordtypedef" then 434 | -- use code to complete api information if it's necessary 435 | completeapidoctype(apiobj, itemname, init,file,comment2apiobj) 436 | return createitem(itemname, string, apimodel._internaltyperef(apiobj.name), nil) 437 | end 438 | 439 | -- if the apiobj could not be associated to the current obj, 440 | -- we do not use the documentation neither 441 | commentbefore = nil 442 | end 443 | 444 | -- else we use code to extract the type and description 445 | -- check for "local" typing 446 | local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) 447 | local desc = desc or (commentbefore and commentbefore[1]) 448 | if type then 449 | return createitem(itemname, string, type, desc ) 450 | else 451 | -- if no "local typing" extract type from code 452 | return createitem(itemname, string, createtype(init,1,comment2apiobj,file), desc) 453 | end 454 | end 455 | | `Set {ids, inits} 456 | | `Local {ids, inits} -> 457 | -- if this is a single local var declaration 458 | -- we check if there are a comment block linked and try to extract the type 459 | if #ids == 1 then 460 | local currentid, currentinit = ids[1],inits[1] 461 | -- ignore non Ids node 462 | if currentid.tag ~= 'Id' or currentid[1] ~= itemname then return nil end 463 | 464 | -- check for luadoc typing 465 | local commentbefore = getlinkedcommentbefore(binder) 466 | local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment 467 | if apiobj then 468 | if apiobj.tag=="item" then 469 | -- use code to complete api information if it's necessary 470 | if not apiobj.name or apiobj.name == itemname then 471 | completeapidocitem(apiobj, itemname, currentinit,file,binder,comment2apiobj) 472 | -- if this is a global var or if is has no parent 473 | -- we do not create a new item 474 | if not apiobj.parent or apiobj.parent == file then 475 | -- for item use code source range rather than doc source range 476 | if currentid and currentid.lineinfo then 477 | apiobj.sourcerange.min = currentid.lineinfo.first.offset 478 | apiobj.sourcerange.max = currentid.lineinfo.last.offset 479 | end 480 | return apiobj, currentid 481 | else 482 | return createitem(itemname, currentid, apiobj.type, nil) 483 | end 484 | end 485 | elseif apiobj.tag=="recordtypedef" then 486 | -- use code to complete api information if it's necessary 487 | completeapidoctype(apiobj, itemname, currentinit,file,comment2apiobj) 488 | return createitem(itemname, currentid, apimodel._internaltyperef(apiobj.name), nil) 489 | end 490 | 491 | -- if the apiobj could not be associated to the current obj, 492 | -- we do not use the documentation neither 493 | commentbefore = nil 494 | end 495 | 496 | -- else we use code to extract the type and description 497 | -- check for "local" typing 498 | local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) 499 | desc = desc or (commentbefore and commentbefore[1]) 500 | if type then 501 | return createitem(itemname, currentid, type, desc) 502 | else 503 | -- if no "local typing" extract type from code 504 | return createitem(itemname, currentid, createtype(currentinit,1,comment2apiobj,file), desc) 505 | end 506 | end 507 | -- else we use code to extract the type 508 | local init,returnposition = nil,1 509 | for i,id in ipairs(ids) do 510 | -- calculate the current return position 511 | if init and (init.tag == "Call" or init.tag == "Invoke") then 512 | -- if previous init was a call or an invoke 513 | -- we increment the returnposition 514 | returnposition= returnposition+1 515 | else 516 | -- if init is not a function call 517 | -- we change the init used to determine the type 518 | init = inits[i] 519 | end 520 | 521 | -- get the name of the current id 522 | local idname = id[1] 523 | 524 | -- if this is the good id 525 | if itemname == idname then 526 | -- create type from init node and return position 527 | return createitem (itemname, id, createtype(init,returnposition,comment2apiobj,file),nil) 528 | end 529 | end 530 | | `Function {params, body} -> 531 | for i,id in ipairs(params) do 532 | -- get the name of the current id 533 | local idname = id[1] 534 | -- if this is the good id 535 | if itemname == idname then 536 | -- extract param's type from luadocumentation 537 | local obj = comment2apiobj[getlinkedcommentbefore(binder)] 538 | if obj and obj.tag=="item" then 539 | local typedef = obj:resolvetype(file) 540 | if typedef and typedef.tag =="functiontypedef" then 541 | for j, param in ipairs(typedef.params) do 542 | if i==j then 543 | if i ==1 and itemname == "self" and param.type == nil 544 | and obj.parent and obj.parent.tag == "recordtypedef" and obj.parent.name then 545 | param.type = apimodel._internaltyperef(obj.parent.name) 546 | end 547 | -- TODO perhaps we must clone the typeref 548 | return createitem(itemname,id, param.type,param.description) 549 | end 550 | end 551 | end 552 | end 553 | return createitem(itemname,id) 554 | end 555 | end 556 | | `Forin {ids, expr, body} -> 557 | for i,id in ipairs(ids) do 558 | -- get the name of the current id 559 | local idname = id[1] 560 | -- if this is the good id 561 | if itemname == idname then 562 | -- return data : we can not guess the type for now 563 | return createitem(itemname,id) 564 | end 565 | end 566 | | `Fornum {id, ...} -> 567 | -- get the name of the current id 568 | local idname = id[1] 569 | -- if this is the good id 570 | if itemname == idname then 571 | -- return data : we can not guess the type for now 572 | return createitem(itemname,id) 573 | end 574 | | `Localrec {{id}, {func}} -> 575 | -- get the name of the current id 576 | local idname = id[1] 577 | -- if this is the good id 578 | if itemname == idname then 579 | -- check for luadoc typing 580 | local commentbefore = getlinkedcommentbefore(binder) 581 | local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment 582 | if apiobj then 583 | if apiobj.tag=="item" then 584 | if not apiobj.name or apiobj.name == itemname then 585 | -- use code to complete api information if it's necessary 586 | completeapidocitem(apiobj, itemname, func,file,binder,comment2apiobj) 587 | return createitem(itemname,id,apiobj.type,nil) 588 | end 589 | end 590 | 591 | -- if the apiobj could not be associated to the current obj, 592 | -- we do not use the documentation neither 593 | commentbefore = nil 594 | end 595 | 596 | -- else we use code to extract the type and description 597 | -- check for "local" typing 598 | local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file) 599 | desc = desc or (commentbefore and commentbefore[1]) 600 | if type then 601 | return createitem(itemname, id, type, desc) 602 | else 603 | -- if no "local typing" extract type from code 604 | return createitem(itemname, id, createtype(func,1,comment2apiobj,file), desc) 605 | end 606 | end 607 | | _ -> 608 | end 609 | end 610 | 611 | -- ---------------------------------------------------------- 612 | -- Search from Id node to Set node to find field of type. 613 | -- 614 | -- Lua code : table.field1.field2 = 12 615 | -- looks like that in metalua : 616 | -- `Set{ 617 | -- `Index { `Index { `Id "table", `String "field1" }, 618 | -- `String "field2"}, 619 | -- `Number "12"} 620 | -- ---------------------------------------------------------- 621 | local function searchtypefield(node,_currentitem,comment2apiobj,file) 622 | 623 | -- we are just interested : 624 | -- by item which is field of recordtypedef 625 | -- by ast node which are Index 626 | if _currentitem then 627 | local type = _currentitem:resolvetype(file) 628 | if type and type.tag == "recordtypedef" then 629 | if node and node.tag == "Index" then 630 | local rightpart = node[2] 631 | local _newcurrentitem = type.fields[rightpart[1]] 632 | 633 | if _newcurrentitem then 634 | -- if this index represent a known field of the type we continue to search 635 | searchtypefield (node.parent,_newcurrentitem,comment2apiobj,file) 636 | else 637 | -- if not, this is perhaps a new field, but 638 | -- to be a new field this index must be include in a Set 639 | if node.parent and node.parent.tag =="Set" then 640 | -- in this case we create the new item ans add it to the type 641 | local set = node.parent 642 | local item, string = getitem(set,nil, comment2apiobj,file) 643 | -- add this item to the type, only if it has no parent and if this type does not contain already this field 644 | if item and not item.parent and string and not type.fields[string[1]] then 645 | type:addfield(item) 646 | end 647 | end 648 | end 649 | end 650 | end 651 | end 652 | end 653 | 654 | -- ---------------------------------------------------------- 655 | -- create local vars, global vars and linked it with theirs occurences 656 | -- ---------------------------------------------------------- 657 | local function createvardefinitions(_internalcontent,ast,file,comment2apiobj) 658 | -- use bindings to get locals and globals definition 659 | local locals, globals = bindings( ast ) 660 | 661 | -- create locals var 662 | for binder, namesAndOccurrences in pairs(locals) do 663 | for name, occurrences in pairs(namesAndOccurrences) do 664 | -- get item, id 665 | local _item, id = getitem(binder, name,comment2apiobj,file) 666 | if id then 667 | -- add definition as occurence 668 | -- we consider the identifier in the binder as an occurence 669 | local _identifierdef = idto_identifier[id] 670 | if _identifierdef then 671 | table.insert(_item.occurrences, _identifierdef) 672 | _identifierdef.definition = _item 673 | end 674 | 675 | -- add occurences 676 | for _,occurrence in ipairs(occurrences) do 677 | searchtypefield(occurrence.parent, _item,comment2apiobj,file) 678 | local _identifier = idto_identifier[occurrence] 679 | if _identifier then 680 | table.insert(_item.occurrences, _identifier) 681 | _identifier.definition = _item 682 | end 683 | end 684 | 685 | -- add item to block 686 | local _block = idto_block[id] 687 | table.insert(_block.localvars,{item=_item,scope = {min=0,max=0}}) 688 | end 689 | end 690 | end 691 | 692 | -- create globals var 693 | for name, occurrences in pairs( globals ) do 694 | 695 | -- get or create definition 696 | local _item = file.globalvars[name] 697 | local binder = occurrences[1].parent 698 | if not _item then 699 | -- global declaration is only if the first occurence in left part of a 'Set' 700 | if binder and binder.tag == "Set" then 701 | _item = getitem(binder, name,comment2apiobj,file) 702 | end 703 | 704 | -- if we find and item this is a global var declaration 705 | if _item then 706 | file:addglobalvar(_item) 707 | else 708 | -- else it is an unknown global var 709 | _item = apimodel._item(name) 710 | local _firstoccurrence = idto_identifier[occurrences[1]] 711 | if _firstoccurrence then 712 | _item.sourcerange.min = _firstoccurrence.sourcerange.min 713 | _item.sourcerange.max = _firstoccurrence.sourcerange.max 714 | end 715 | table.insert(_internalcontent.unknownglobalvars,_item) 716 | end 717 | else 718 | -- if the global var definition already exists, we just try to it 719 | if binder then 720 | match binder with 721 | | `Set {ids, inits} -> 722 | -- manage case only if there are 1 element in the Set 723 | if #ids == 1 then 724 | local currentid, currentinit = ids[1],inits[1] 725 | -- ignore non Ids node and bad name 726 | if currentid.tag == 'Id' and currentid[1] == name then 727 | completeapidocitem(_item, name, currentinit,file,binder,comment2apiobj) 728 | 729 | if currentid and currentid.lineinfo then 730 | _item.sourcerange.min = currentid.lineinfo.first.offset 731 | _item.sourcerange.max = currentid.lineinfo.last.offset 732 | end 733 | end 734 | end 735 | | _ -> 736 | end 737 | end 738 | end 739 | 740 | -- add occurences 741 | for _,occurence in ipairs(occurrences) do 742 | local _identifier = idto_identifier[occurence] 743 | searchtypefield(occurence.parent, _item,comment2apiobj,file) 744 | if _identifier then 745 | table.insert(_item.occurrences, _identifier) 746 | _identifier.definition = _item 747 | end 748 | end 749 | end 750 | end 751 | 752 | -- ---------------------------------------------------------- 753 | -- add parent to all ast node 754 | -- ---------------------------------------------------------- 755 | local function addparents(ast) 756 | -- visitor function (down) 757 | local function down (node,parent) 758 | node.parent = parent 759 | end 760 | 761 | -- visit ast and build internal model 762 | Q(ast):foreach(down,up) 763 | end 764 | 765 | -- ---------------------------------------------------------- 766 | -- try to detect a module declaration from code 767 | -- ---------------------------------------------------------- 768 | local function searchmodule(ast,file,comment2apiobj,modulename) 769 | -- if the last statement is a return 770 | if ast then 771 | local laststatement = ast[#ast] 772 | if laststatement and laststatement.tag == "Return" then 773 | -- and if the first expression returned is an identifier. 774 | local firstexpr = laststatement[1] 775 | if firstexpr and firstexpr.tag == "Id" then 776 | -- get identifier in internal model 777 | local _identifier = idto_identifier [firstexpr] 778 | -- the definition should be an inline type 779 | if _identifier 780 | and _identifier.definition 781 | and _identifier.definition.type 782 | and _identifier.definition.type.tag == "inlinetyperef" 783 | and _identifier.definition.type.def.tag == "recordtypedef" then 784 | 785 | --set modulename if needed 786 | if not file.name then file.name = modulename end 787 | 788 | -- create or merge type 789 | local _type = _identifier.definition.type.def 790 | _type.name = modulename 791 | 792 | -- if file (module) has no documentation add item documentation to it 793 | -- else add it to the type. 794 | if not file.description or file.description == "" then 795 | file.description = _identifier.definition.description 796 | else 797 | _type.description = _identifier.definition.description 798 | end 799 | _identifier.definition.description = "" 800 | if not file.shortdescription or file.shortdescription == "" then 801 | file.shortdescription = _identifier.definition.shortdescription 802 | else 803 | _type.shortdescription = _identifier.definition.shortdescription 804 | end 805 | _identifier.definition.shortdescription = "" 806 | 807 | -- WORKAROUND FOR BUG 421622: [outline]module selection in outline does not select it in texteditor 808 | --_type.sourcerange.min = _identifier.definition.sourcerange.min 809 | --_type.sourcerange.max = _identifier.definition.sourcerange.max 810 | 811 | -- merge the type with priority to documentation except for source range 812 | file:mergetype(_type,false,true) 813 | 814 | -- create return if needed 815 | if not file.returns[1] then 816 | file.returns[1] = apimodel._return() 817 | file.returns[1].types = { apimodel._internaltyperef(modulename) } 818 | end 819 | 820 | -- change the type of the identifier 821 | _identifier.definition.type = apimodel._internaltyperef(modulename) 822 | end 823 | end 824 | end 825 | end 826 | end 827 | 828 | -- ---------------------------------------------------------- 829 | -- create the internalcontent from an ast metalua 830 | -- ---------------------------------------------------------- 831 | function M.createinternalcontent (ast,file,comment2apiobj,modulename) 832 | -- init cache 833 | idto_block = {} 834 | idto_identifier = {} 835 | expreto_expression = {} 836 | comment2apiobj = comment2apiobj or {} 837 | file = file or apimodel._file() 838 | 839 | -- execute code safely to be sure to clean cache correctly 840 | local internalcontent 841 | local ok, errmsg = pcall(function () 842 | -- add parent to all node 843 | addparents(ast) 844 | 845 | -- create block and expression node 846 | internalcontent = createtreestructure(ast) 847 | 848 | -- create Local vars, global vars and linked occurences (Items) 849 | createvardefinitions(internalcontent,ast,file,comment2apiobj) 850 | 851 | -- try to dectect module information from code 852 | local moduletyperef = file:moduletyperef() 853 | if moduletyperef and moduletyperef.tag == "internaltyperef" then 854 | modulename = moduletyperef.typename or modulename 855 | end 856 | if modulename then 857 | searchmodule(ast,file,comment2apiobj,modulename) 858 | end 859 | end) 860 | 861 | -- clean cache 862 | idto_block = {} 863 | idto_identifier = {} 864 | expreto_expression = {} 865 | 866 | -- if not ok raise an error 867 | if not ok then error (errmsg) end 868 | 869 | return internalcontent 870 | end 871 | 872 | return M 873 | -------------------------------------------------------------------------------- /models/ldparser.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | -- Copyright (c) 2011-2013 Sierra Wireless and others. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Sierra Wireless - initial API and implementation 10 | ------------------------------------------------------------------------------- 11 | local mlc = require ('metalua.compiler').new() 12 | local gg = require 'metalua.grammar.generator' 13 | local lexer = require 'metalua.grammar.lexer' 14 | local mlp = mlc.parser 15 | 16 | local M = {} -- module 17 | local lx -- lexer used to parse tag 18 | local registeredparsers -- table {tagname => {list de parsers}} 19 | 20 | -- ---------------------------------------------------- 21 | -- copy key and value from one table to an other 22 | -- ---------------------------------------------------- 23 | local function copykey(tablefrom, tableto) 24 | for key, value in pairs(tablefrom) do 25 | if key ~= "lineinfos" then 26 | tableto[key] = value 27 | end 28 | end 29 | end 30 | 31 | -- ---------------------------------------------------- 32 | -- Handle keyword and identifiers as word 33 | -- ---------------------------------------------------- 34 | local function parseword(lx) 35 | local word = lx :peek() 36 | local tag = word.tag 37 | 38 | if tag=='Keyword' or tag=='Id' then 39 | lx:next() 40 | return {tag='Word', lineinfo=word.lineinfo, word[1]} 41 | else 42 | return gg.parse_error(lx,'Id or Keyword expected') 43 | end 44 | end 45 | 46 | -- ---------------------------------------------------- 47 | -- parse an id 48 | -- return a table {name, lineinfo) 49 | -- ---------------------------------------------------- 50 | local idparser = gg.sequence({ 51 | builder = function (result) 52 | return { name = result[1][1] } 53 | end, 54 | parseword 55 | }) 56 | 57 | -- ---------------------------------------------------- 58 | -- parse a modulename (id.)?id 59 | -- return a table {name, lineinfo) 60 | -- ---------------------------------------------------- 61 | local modulenameparser = gg.list({ 62 | builder = function (result) 63 | local ids = {} 64 | for i, id in ipairs(result) do 65 | table.insert(ids,id.name) 66 | end 67 | return {name = table.concat(ids,".")} 68 | end, 69 | primary = idparser, 70 | separators = '.' 71 | }) 72 | -- ---------------------------------------------------- 73 | -- parse a typename (id.)?id 74 | -- return a table {name, lineinfo) 75 | -- ---------------------------------------------------- 76 | local typenameparser = modulenameparser 77 | 78 | -- ---------------------------------------------------- 79 | -- parse an internaltype ref 80 | -- ---------------------------------------------------- 81 | local internaltyperefparser = gg.sequence({ 82 | builder = function(result) 83 | return {tag = "typeref",type=result[1].name} 84 | end, 85 | "#", typenameparser 86 | }) 87 | 88 | -- ---------------------------------------------------- 89 | -- parse an internal typeref, without the first # 90 | -- ---------------------------------------------------- 91 | local sharplessinternaltyperefparser = gg.sequence({ 92 | builder = function(result) 93 | return {tag = "typeref",type=result[1].name} 94 | end, 95 | typenameparser 96 | }) 97 | 98 | -- ---------------------------------------------------- 99 | -- parse an external type ref 100 | -- ---------------------------------------------------- 101 | local externaltyperefparser = gg.sequence({ 102 | builder = function(result) 103 | return {tag = "typeref",module=result[1].name,type=result[2].name} 104 | end, 105 | modulenameparser,"#", typenameparser 106 | }) 107 | 108 | -- ---------------------------------------------------- 109 | -- enable recursive use of typeref parser 110 | -- ---------------------------------------------------- 111 | local typerefparser,_typerefparser 112 | typerefparser = function (...) return _typerefparser(...) end 113 | 114 | -- ---------------------------------------------------- 115 | -- parse a structure type, without the first # 116 | -- ---------------------------------------------------- 117 | local sharplesslisttyperefparser = gg.sequence({ 118 | builder = function(result) 119 | return {tag = "typeref", type="list", valuetype=result[1]} 120 | end, 121 | "list","<", typerefparser, ">" 122 | }) 123 | 124 | -- ---------------------------------------------------- 125 | -- parse a map type, without the first # 126 | -- ---------------------------------------------------- 127 | local sharplessmaptyperefparser = gg.sequence({ 128 | builder = function(result) 129 | return {tag = "typeref", type="map", keytype=result[1], valuetype=result[2]} 130 | end, 131 | "map","<", typerefparser, ",", typerefparser, ">" 132 | }) 133 | 134 | -- ---------------------------------------------------- 135 | -- parse typeref stating with a # 136 | -- The need to use the following parser is because the multisequence parser 137 | -- works only if the given parsers doesn't start with the same keyword (here '#'). 138 | -- ---------------------------------------------------- 139 | local sharptyperefparser = gg.sequence({ 140 | builder = function(result) 141 | return result[1] 142 | end, 143 | "#", 144 | gg.multisequence({ 145 | sharplesslisttyperefparser, 146 | sharplessmaptyperefparser, 147 | sharplessinternaltyperefparser 148 | }) 149 | }) 150 | 151 | -- ---------------------------------------------------- 152 | -- parse a typeref 153 | -- ---------------------------------------------------- 154 | _typerefparser = gg.multisequence({ 155 | sharptyperefparser, 156 | externaltyperefparser 157 | }) 158 | 159 | -- ---------------------------------------------------- 160 | -- parse a list of typeref 161 | -- return a list of table {name, lineinfo) 162 | -- ---------------------------------------------------- 163 | local typereflistparser = gg.list({ 164 | primary = typerefparser, 165 | separators = ',' 166 | }) 167 | 168 | -- ---------------------------------------------------- 169 | -- TODO use a more generic way to parse (modifier if not always a typeref) 170 | -- TODO support more than one modifier 171 | -- ---------------------------------------------------- 172 | local modifiersparser = gg.sequence({ 173 | builder = function(result) 174 | return {[result[1].name]=result[2]} 175 | end, 176 | "[", idparser , "=" , internaltyperefparser , "]" 177 | }) 178 | 179 | -- ---------------------------------------------------- 180 | -- parse a list tag 181 | -- ---------------------------------------------------- 182 | local listparsers = { 183 | -- full parser 184 | gg.sequence({ 185 | builder = function (result) 186 | return {type = result[1]} 187 | end, 188 | '@','list','<',typerefparser,'>' 189 | }), 190 | } 191 | 192 | -- ---------------------------------------------------- 193 | -- parse a map tag 194 | -- ---------------------------------------------------- 195 | local mapparsers = { 196 | -- full parser 197 | gg.sequence({ 198 | builder = function (result) 199 | return {keytype = result[1],valuetype = result[2]} 200 | end, 201 | '@','map','<',typerefparser,',',typerefparser,'>' 202 | }), 203 | } 204 | 205 | -- ---------------------------------------------------- 206 | -- parse a extends tag 207 | -- ---------------------------------------------------- 208 | local extendsparsers = { 209 | -- full parser 210 | gg.sequence({ 211 | builder = function (result) 212 | return {type = result[1]} 213 | end, 214 | '@','extends', typerefparser 215 | }), 216 | } 217 | 218 | -- ---------------------------------------------------- 219 | -- parse a callof tag 220 | -- ---------------------------------------------------- 221 | local callofparsers = { 222 | -- full parser 223 | gg.sequence({ 224 | builder = function (result) 225 | return {type = result[1]} 226 | end, 227 | '@','callof', internaltyperefparser 228 | }), 229 | } 230 | 231 | -- ---------------------------------------------------- 232 | -- parse a return tag 233 | -- ---------------------------------------------------- 234 | local returnparsers = { 235 | -- full parser 236 | gg.sequence({ 237 | builder = function (result) 238 | return { types= result[1]} 239 | end, 240 | '@','return', typereflistparser 241 | }), 242 | -- parser without typerefs 243 | gg.sequence({ 244 | builder = function (result) 245 | return { types = {}} 246 | end, 247 | '@','return' 248 | }) 249 | } 250 | 251 | -- ---------------------------------------------------- 252 | -- parse a param tag 253 | -- ---------------------------------------------------- 254 | local paramparsers = { 255 | -- full parser 256 | gg.sequence({ 257 | builder = function (result) 258 | return { name = result[2].name, type = result[1]} 259 | end, 260 | '@','param', typerefparser, idparser 261 | }), 262 | 263 | -- reject the case were only a type without name 264 | gg.sequence({ 265 | builder = function (result) 266 | return {tag="Error"} 267 | end, 268 | '@','param', '#' 269 | }), 270 | 271 | -- parser without type 272 | gg.sequence({ 273 | builder = function (result) 274 | return { name = result[1].name} 275 | end, 276 | '@','param', idparser 277 | }), 278 | 279 | -- Parser for `Dots 280 | gg.sequence({ 281 | builder = function (result) 282 | return { name = '...' } 283 | end, 284 | '@','param', '...' 285 | }), 286 | } 287 | -- ---------------------------------------------------- 288 | -- parse a field tag 289 | -- ---------------------------------------------------- 290 | local fieldparsers = { 291 | -- full parser 292 | gg.sequence({ 293 | builder = function (result) 294 | local tag = {} 295 | copykey(result[1],tag) 296 | tag.type = result[2] 297 | tag.name = result[3].name 298 | return tag 299 | end, 300 | '@','field', modifiersparser, typerefparser, idparser 301 | }), 302 | 303 | -- reject the case where the type name is empty 304 | gg.sequence({ 305 | builder = function (result) 306 | return {tag = "Error"} 307 | end, 308 | '@','field',modifiersparser, '#' 309 | }), 310 | 311 | -- parser without name 312 | gg.sequence({ 313 | builder = function (result) 314 | local tag = {} 315 | copykey(result[1],tag) 316 | tag.type = result[2] 317 | return tag 318 | end, 319 | '@','field', modifiersparser, typerefparser 320 | }), 321 | 322 | -- parser without type 323 | gg.sequence({ 324 | builder = function (result) 325 | local tag = {} 326 | copykey(result[1],tag) 327 | tag.name = result[2].name 328 | return tag 329 | end, 330 | '@','field', modifiersparser, idparser 331 | }), 332 | 333 | -- parser without type and name 334 | gg.sequence({ 335 | builder = function (result) 336 | local tag = {} 337 | copykey(result[1],tag) 338 | return tag 339 | end, 340 | '@','field', modifiersparser 341 | }), 342 | 343 | -- parser without modifiers 344 | gg.sequence({ 345 | builder = function (result) 346 | return { name = result[2].name, type = result[1]} 347 | end, 348 | '@','field', typerefparser, idparser 349 | }), 350 | 351 | -- parser without modifiers and name 352 | gg.sequence({ 353 | builder = function (result) 354 | return {type = result[1]} 355 | end, 356 | '@','field', typerefparser 357 | }), 358 | 359 | -- reject the case where the type name is empty 360 | gg.sequence({ 361 | builder = function (result) 362 | return {tag = "Error"} 363 | end, 364 | '@','field', '#' 365 | }), 366 | 367 | -- parser without type and modifiers 368 | gg.sequence({ 369 | builder = function (result) 370 | return { name = result[1].name} 371 | end, 372 | '@','field', idparser 373 | }), 374 | 375 | -- parser with nothing 376 | gg.sequence({ 377 | builder = function (result) 378 | return {} 379 | end, 380 | '@','field' 381 | }) 382 | } 383 | 384 | -- ---------------------------------------------------- 385 | -- parse a function tag 386 | -- TODO use a more generic way to parse modifier ! 387 | -- ---------------------------------------------------- 388 | local functionparsers = { 389 | -- full parser 390 | gg.sequence({ 391 | builder = function (result) 392 | local tag = {} 393 | copykey(result[1],tag) 394 | tag.name = result[2].name 395 | return tag 396 | end, 397 | '@','function', modifiersparser, idparser 398 | }), 399 | 400 | -- parser without name 401 | gg.sequence({ 402 | builder = function (result) 403 | local tag = {} 404 | copykey(result[1],tag) 405 | return tag 406 | end, 407 | '@','function', modifiersparser 408 | }), 409 | 410 | -- parser without modifier 411 | gg.sequence({ 412 | builder = function (result) 413 | local tag = {} 414 | tag.name = result[1].name 415 | return tag 416 | end, 417 | '@','function', idparser 418 | }), 419 | 420 | -- empty parser 421 | gg.sequence({ 422 | builder = function (result) 423 | return {} 424 | end, 425 | '@','function' 426 | }) 427 | } 428 | 429 | -- ---------------------------------------------------- 430 | -- parse a type tag 431 | -- ---------------------------------------------------- 432 | local typeparsers = { 433 | -- full parser 434 | gg.sequence({ 435 | builder = function (result) 436 | return { name = result[1].name} 437 | end, 438 | '@','type',typenameparser 439 | }), 440 | -- parser without name 441 | gg.sequence({ 442 | builder = function (result) 443 | return {} 444 | end, 445 | '@','type' 446 | }) 447 | } 448 | 449 | -- ---------------------------------------------------- 450 | -- parse a module tag 451 | -- ---------------------------------------------------- 452 | local moduleparsers = { 453 | -- full parser 454 | gg.sequence({ 455 | builder = function (result) 456 | return { name = result[1].name } 457 | end, 458 | '@','module', modulenameparser 459 | }), 460 | -- parser without name 461 | gg.sequence({ 462 | builder = function (result) 463 | return {} 464 | end, 465 | '@','module' 466 | }) 467 | } 468 | 469 | -- ---------------------------------------------------- 470 | -- parse a third tag 471 | -- ---------------------------------------------------- 472 | local thirdtagsparser = gg.sequence({ 473 | builder = function (result) 474 | return { name = result[1][1] } 475 | end, 476 | '@', mlp.id 477 | }) 478 | -- ---------------------------------------------------- 479 | -- init parser 480 | -- ---------------------------------------------------- 481 | local function initparser() 482 | -- register parsers 483 | -- each tag name has several parsers 484 | registeredparsers = { 485 | ["module"] = moduleparsers, 486 | ["return"] = returnparsers, 487 | ["type"] = typeparsers, 488 | ["field"] = fieldparsers, 489 | ["function"] = functionparsers, 490 | ["param"] = paramparsers, 491 | ["extends"] = extendsparsers, 492 | ["list"] = listparsers, 493 | ["map"] = mapparsers, 494 | ["callof"] = callofparsers 495 | } 496 | 497 | -- create lexer used for parsing 498 | lx = lexer.lexer:clone() 499 | lx.extractors = { 500 | -- "extract_long_comment", 501 | -- "extract_short_comment", 502 | -- "extract_long_string", 503 | "extract_short_string", 504 | "extract_word", 505 | "extract_number", 506 | "extract_symbol" 507 | } 508 | 509 | -- Add dots as keyword 510 | local tagnames = { '...' } 511 | 512 | -- Add tag names as key word 513 | for tagname, _ in pairs(registeredparsers) do 514 | table.insert(tagnames,tagname) 515 | end 516 | lx:add(tagnames) 517 | 518 | return lx, parsers 519 | end 520 | 521 | initparser() 522 | 523 | -- ---------------------------------------------------- 524 | -- get the string pattern to remove for each line of description 525 | -- the goal is to fix the indentation problems 526 | -- ---------------------------------------------------- 527 | local function getstringtoremove (stringcomment,commentstart) 528 | local _,_,capture = string.find(stringcomment,"\n?([ \t]*)@[^{]+",commentstart) 529 | if not capture then 530 | _,_,capture = string.find(stringcomment,"^([ \t]*)",commentstart) 531 | end 532 | capture = string.gsub(capture,"(.)","%1?") 533 | return capture 534 | end 535 | 536 | -- ---------------------------------------------------- 537 | -- parse comment tag partition and return table structure 538 | -- ---------------------------------------------------- 539 | local function parsetag(part) 540 | if part.comment:find("^@") then 541 | -- check if the part start by a supported tag 542 | for tagname,parsers in pairs(registeredparsers) do 543 | if (part.comment:find("^@"..tagname)) then 544 | -- try the registered parsers for this tag 545 | local result 546 | for i, parser in ipairs(parsers) do 547 | local valid, tag = pcall(parser, lx:newstream(part.comment, tagname .. 'tag lexer')) 548 | if valid then 549 | -- add tagname 550 | tag.tagname = tagname 551 | 552 | -- add description 553 | local endoffset = tag.lineinfo.last.offset 554 | tag.description = part.comment:sub(endoffset+2,-1) 555 | return tag 556 | end 557 | end 558 | end 559 | end 560 | end 561 | return nil 562 | end 563 | 564 | -- ---------------------------------------------------- 565 | -- Parse third party tags. 566 | -- 567 | -- Enable to parse a tag not defined in language. 568 | -- So for, accepted format is: @sometagname adescription 569 | -- ---------------------------------------------------- 570 | local function parsethirdtag( part ) 571 | 572 | -- Check it there is someting to process 573 | if not part.comment:find("^@") then 574 | return nil, 'No tag to parse' 575 | end 576 | 577 | -- Apply parser 578 | local status, parsedtag = pcall(thirdtagsparser, lx:newstream(part.comment, 'Third party tag lexer')) 579 | if not status then 580 | return nil, "Unable to parse given string." 581 | end 582 | 583 | -- Retrieve description 584 | local endoffset = parsedtag.lineinfo.last.offset 585 | local tag = { 586 | description = part.comment:sub(endoffset+2,-1) 587 | } 588 | return parsedtag.name, tag 589 | end 590 | 591 | -- --------------------------------------------------------- 592 | -- split string comment in several part 593 | -- return list of {comment = string, offset = number} 594 | -- the first part is the part before the first tag 595 | -- the others are the part from a tag to the next one 596 | -- ---------------------------------------------------- 597 | local function split(stringcomment,commentstart) 598 | local partstart = commentstart 599 | local result = {} 600 | 601 | -- manage case where the comment start by @ 602 | -- (we must ignore the inline see tag @{..}) 603 | local at_startoffset, at_endoffset = stringcomment:find("^[ \t]*@[^{]",partstart) 604 | if at_endoffset then 605 | partstart = at_endoffset-1 -- we start before the @ and the non '{' character 606 | end 607 | 608 | -- split comment 609 | -- (we must ignore the inline see tag @{..}) 610 | repeat 611 | at_startoffset, at_endoffset = stringcomment:find("\n[ \t]*@[^{]",partstart) 612 | local partend 613 | if at_startoffset then 614 | partend= at_startoffset-1 -- the end is before the separator pattern (just before the \n) 615 | else 616 | partend = #stringcomment -- we don't find any pattern so the end is the end of the string 617 | end 618 | table.insert(result, { comment = stringcomment:sub (partstart,partend) , 619 | offset = partstart}) 620 | if at_endoffset then 621 | partstart = at_endoffset-1 -- the new start is befire the @ and the non { char 622 | end 623 | until not at_endoffset 624 | return result 625 | end 626 | 627 | 628 | -- ---------------------------------------------------- 629 | -- parse a comment block and return a table 630 | -- ---------------------------------------------------- 631 | function M.parse(stringcomment) 632 | 633 | local _comment = {description="", shortdescription=""} 634 | 635 | -- clean windows carriage return 636 | stringcomment = string.gsub(stringcomment,"\r\n","\n") 637 | 638 | -- check if it's a ld comment 639 | -- get the begin of the comment 640 | -- ============================ 641 | if not stringcomment:find("^-") then 642 | -- if this comment don't start by -, we will not handle it. 643 | return nil 644 | end 645 | 646 | -- retrieve the real start 647 | local commentstart = 2 --after the first hyphen 648 | -- if the first line is an empty comment line with at least 3 hyphens we ignore it 649 | local _ , endoffset = stringcomment:find("^-+[ \t]*\n") 650 | if endoffset then 651 | commentstart = endoffset+1 652 | end 653 | 654 | -- clean comments 655 | -- =================== 656 | -- remove line of "-" 657 | stringcomment = string.sub(stringcomment,commentstart) 658 | -- clean indentation 659 | local pattern = getstringtoremove (stringcomment,1) 660 | stringcomment = string.gsub(stringcomment,"^"..pattern,"") 661 | stringcomment = string.gsub(stringcomment,"\n"..pattern,"\n") 662 | 663 | -- split comment part 664 | -- ==================== 665 | local commentparts = split(stringcomment, 1) 666 | 667 | -- Extract descriptions 668 | -- ==================== 669 | local firstpart = commentparts[1].comment 670 | if firstpart:find("^[^@]") or firstpart:find("^@{") then 671 | -- if the comment part don't start by @ 672 | -- it's the part which contains descriptions 673 | -- (there are an exception for the in-line see tag @{..}) 674 | local shortdescription, description = string.match(firstpart,'^(.-[.?])(%s.+)') 675 | -- store description 676 | if shortdescription then 677 | _comment.shortdescription = shortdescription 678 | -- clean description 679 | -- remove always the first space character 680 | -- (this manage the case short and long description is on the same line) 681 | description = string.gsub(description, "^[ \t]","") 682 | -- if first line is only an empty string remove it 683 | description = string.gsub(description, "^[ \t]*\n","") 684 | _comment.description = description 685 | else 686 | _comment.shortdescription = firstpart 687 | _comment.description = "" 688 | end 689 | end 690 | 691 | -- Extract tags 692 | -- =================== 693 | -- Parse regular tags 694 | local tag 695 | for i, part in ipairs(commentparts) do 696 | tag = parsetag(part) 697 | --if it's a supported tag (so tag is not nil, it's a table) 698 | if tag then 699 | if not _comment.tags then _comment.tags = {} end 700 | if not _comment.tags[tag.tagname] then 701 | _comment.tags[tag.tagname] = {} 702 | end 703 | table.insert(_comment.tags[tag.tagname], tag) 704 | else 705 | 706 | -- Try user defined tags, so far they will look like 707 | -- @identifier description 708 | local tagname, thirdtag = parsethirdtag( part ) 709 | if tagname then 710 | -- 711 | -- Append found tag 712 | -- 713 | local reservedname = 'unknowntags' 714 | if not _comment.unknowntags then 715 | _comment.unknowntags = {} 716 | end 717 | 718 | -- Create specific section for parsed tag 719 | if not _comment.unknowntags[tagname] then 720 | _comment.unknowntags[tagname] = {} 721 | end 722 | -- Append to specific section 723 | table.insert(_comment.unknowntags[tagname], thirdtag) 724 | end 725 | end 726 | end 727 | return _comment 728 | end 729 | 730 | 731 | function M.parseinlinecomment(stringcomment) 732 | --TODO this code is use to activate typage only on --- comments. (deactivate for now) 733 | -- if not stringcomment or not stringcomment:find("^-") then 734 | -- -- if this comment don't start by -, we will not handle it. 735 | -- return nil 736 | -- end 737 | -- -- remove the first '-' 738 | -- stringcomment = string.sub(stringcomment,2) 739 | -- print (stringcomment) 740 | -- io.flush() 741 | local valid, parsedtag = pcall(typerefparser, lx:newstream(stringcomment, 'typeref parser')) 742 | if valid then 743 | local endoffset = parsedtag.lineinfo.last.offset 744 | parsedtag.description = stringcomment:sub(endoffset+2,-1) 745 | return parsedtag 746 | end 747 | end 748 | 749 | return M 750 | -------------------------------------------------------------------------------- /template/file.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return[[# 13 |
14 | # -- 15 | # -- Module name 16 | # -- 17 | # if _file.name then 18 | Module $(_file.name) 19 | # end 20 | # -- 21 | # -- Descriptions 22 | # -- 23 | # if _file.shortdescription then 24 | $( format(_file.shortdescription) ) 25 | # end 26 | # if _file.description and #_file.description > 0 then 27 | $( format(_file.description) ) 28 | # end 29 | # -- 30 | # -- Handle "@usage" special tag 31 | # -- 32 | #if _file.metadata and _file.metadata.usage then 33 | $( applytemplate(_file.metadata.usage, i+1) ) 34 | #end 35 | # -- 36 | # -- Show quick description of current type 37 | # -- 38 | # 39 | # -- show quick description for globals 40 | # if not isempty(_file.globalvars) then 41 | Global(s) 42 | 43 | # for _, item in sortedpairs(_file.globalvars) do 44 | 45 | 46 | 47 | 48 | # end 49 |
$( fulllinkto(item) )$( format(item.shortdescription) )
50 | # end 51 | # 52 | # -- get type corresponding to this file (module) 53 | # local currenttype 54 | # local typeref = _file:moduletyperef() 55 | # if typeref and typeref.tag == "internaltyperef" then 56 | # local typedef = _file.types[typeref.typename] 57 | # if typedef and typedef.tag == "recordtypedef" then 58 | # currenttype = typedef 59 | # end 60 | # end 61 | # 62 | # -- show quick description type exposed by module 63 | # if currenttype and (not isempty(currenttype.fields) or currenttype:getcalldef()) then 64 | Type $(currenttype.name) 65 | $( applytemplate(currenttype, i+2, 'index') ) 66 | # end 67 | # -- 68 | # -- Show quick description of other types 69 | # -- 70 | # if _file.types then 71 | # for name, type in sortedpairs( _file.types ) do 72 | # if type ~= currenttype and type.tag == 'recordtypedef' and (not isempty(type.fields) or type:getcalldef()) then 73 | Type $(name) 74 | $( applytemplate(type, i+2, 'index') ) 75 | # end 76 | # end 77 | # end 78 | # -- 79 | # -- Long description of globals 80 | # -- 81 | # if not isempty(_file.globalvars) then 82 | Global(s) 83 | # for name, item in sortedpairs(_file.globalvars) do 84 | $( applytemplate(item, i+2) ) 85 | # end 86 | # end 87 | # -- 88 | # -- Long description of current type 89 | # -- 90 | # if currenttype then 91 | Type $(currenttype.name) 92 | $( applytemplate(currenttype, i+2) ) 93 | # end 94 | # -- 95 | # -- Long description of other types 96 | # -- 97 | # if not isempty( _file.types ) then 98 | # for name, type in sortedpairs( _file.types ) do 99 | # if type ~= currenttype and type.tag == 'recordtypedef' then 100 | Type $(name) 101 | $( applytemplate(type, i+2) ) 102 | # end 103 | # end 104 | # end 105 |
106 | ]] 107 | -------------------------------------------------------------------------------- /template/functiontypedef.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return [[# 13 | # local fdef = _functiontypedef 14 | # local ignorefirstparam = templateparams[1] 15 | # local ignoredescription = templateparams[2] 16 | # 17 | #if not ignoredescription then 18 | # if fdef.shortdescription then 19 | $( format(fdef.shortdescription) ) 20 | # end 21 | # if fdef.description and #fdef.description > 0 then 22 | $( format(fdef.description) ) 23 | # end 24 | #end 25 | # -- 26 | # -- Describe parameters 27 | # -- 28 | # 29 | # 30 | # -- Adjust parameter count if first one is 'self' 31 | # local paramcount 32 | # if #fdef.params > 0 and ignorefirstparam then 33 | # paramcount = #fdef.params - 1 34 | # else 35 | # paramcount = #fdef.params 36 | # end 37 | # 38 | # -- List parameters 39 | # if paramcount > 0 then 40 | Parameter$( paramcount > 1 and 's' ) 41 |
    42 | # for position, param in ipairs( fdef.params ) do 43 | # if not (position == 1 and ignorefirstparam) then 44 |
  • 45 | # local paramline = "" 46 | # if param.type then 47 | # local link = linkto( param.type ) 48 | # local name = prettyname( param.type ) 49 | # if link then 50 | # paramline = paramline .. '' .. name .. "" 51 | # else 52 | # paramline = paramline .. name 53 | # end 54 | # end 55 | # 56 | # paramline = paramline .. " " .. param.name .. " " 57 | # 58 | # if param.optional then 59 | # paramline = paramline .. "optional" .. " " 60 | # end 61 | # if param.hidden then 62 | # paramline = paramline .. "hidden" 63 | # end 64 | # 65 | # paramline = paramline .. ": " 66 | # 67 | # if param.description and #param.description > 0 then 68 | # paramline = paramline .. "\n" .. param.description 69 | # end 70 | # 71 | $( format (paramline)) 72 |
  • 73 | # end 74 | # end 75 |
76 | # end 77 | # 78 | # -- 79 | # -- Describe returns types 80 | # -- 81 | # if fdef and #fdef.returns > 0 then 82 | Return value$(#fdef.returns > 1 and 's') 83 | # -- 84 | # -- Format nice type list 85 | # -- 86 | # local function niceparmlist( parlist ) 87 | # local typelist = {} 88 | # for position, type in ipairs(parlist) do 89 | # local link = linkto( type ) 90 | # local name = prettyname( type ) 91 | # if link then 92 | # typelist[#typelist + 1] = ''..name..'' 93 | # else 94 | # typelist[#typelist + 1] = name 95 | # end 96 | # -- Append end separator or separating comma 97 | # typelist[#typelist + 1] = position == #parlist and ':' or ', ' 98 | # end 99 | # return table.concat( typelist ) 100 | # end 101 | # -- 102 | # -- Generate a list if they are several return clauses 103 | # -- 104 | # if #fdef.returns > 1 then 105 |
    106 | # for position, ret in ipairs(fdef.returns) do 107 |
  1. 108 | # local returnline = ""; 109 | # 110 | # local paramlist = niceparmlist(ret.types) 111 | # if #ret.types > 0 and #paramlist > 0 then 112 | # returnline = "" .. paramlist .. "" 113 | # end 114 | # returnline = returnline .. "\n" .. ret.description 115 | $( format (returnline)) 116 |
  2. 117 | # end 118 |
119 | # else 120 | # local paramlist = niceparmlist(fdef.returns[1].types) 121 | # local isreturn = fdef.returns and #fdef.returns > 0 and #paramlist > 0 122 | # local isdescription = fdef.returns and fdef.returns[1].description and #format(fdef.returns[1].description) > 0 123 | # 124 | # local returnline = ""; 125 | # -- Show return type if provided 126 | # if isreturn then 127 | # returnline = ""..paramlist.."" 128 | # end 129 | # if isdescription then 130 | # returnline = returnline .. "\n" .. fdef.returns[1].description 131 | # end 132 | $( format(returnline)) 133 | # end 134 | # end 135 | # 136 | #-- 137 | #-- Show usage samples 138 | #-- 139 | #if fdef.metadata and fdef.metadata.usage then 140 | $( applytemplate(fdef.metadata.usage, i) ) 141 | #end 142 | ]] 143 | -------------------------------------------------------------------------------- /template/index.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return 13 | [[# 14 | #if _index.modules then 15 |
16 |

Module$( #_index.modules > 1 and 's' )

17 | 18 | # for _, module in sortedpairs( _index.modules ) do 19 | # if module.tag ~= 'index' then 20 | 21 | 22 | 23 | 24 | # end 25 | # end 26 |
$( fulllinkto(module) )$( module.description and format(module.shortdescription) )
27 |
28 | #end ]] 29 | -------------------------------------------------------------------------------- /template/index/recordtypedef.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return [[# 13 | # local calldef = _recordtypedef:getcalldef() 14 | # local hasfield = not isempty(_recordtypedef.fields) 15 | # if calldef or hasfield then 16 | 17 | # if calldef then 18 | 19 | 20 | 21 | 22 | # end 23 | # for _, item in sortedpairs( _recordtypedef.fields ) do 24 | 25 | 26 | 27 | 28 | # end 29 |
$( fulllinkto(calldef,_recordtypedef) )$( format(calldef.shortdescription) )
$( fulllinkto(item) )$( format(item.shortdescription) )
30 | # end 31 | # ]] 32 | -------------------------------------------------------------------------------- /template/item.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return 13 | [[
14 |
15 | # -- 16 | # -- Resolve item type definition 17 | # -- 18 | # local typedef = _item:resolvetype() 19 | 20 | # -- 21 | # -- Show item type for internal type 22 | # -- 23 | #if _item.type and (not typedef or typedef.tag ~= 'functiontypedef') then 24 | # --Show link only when available 25 | # local link = fulllinkto(_item.type) 26 | # if link then 27 | $( link ) 28 | # else 29 | $(prettyname(_item.type)) 30 | # end 31 | #end 32 | 33 | $( prettyname(_item) ) 34 | 35 |
36 |
37 | # local ignoredescription = false 38 | # if _item.shortdescription then 39 | $( format(_item.shortdescription) ) 40 | # ignoredescription = true 41 | # end 42 | # if _item.description and #_item.description > 0 then 43 | $( format(_item.description) ) 44 | # ignoredescription = true 45 | # end 46 | # 47 | # -- 48 | # -- For function definitions, describe parameters and return values 49 | # -- 50 | #if typedef and typedef.tag == 'functiontypedef' then 51 | # local fdef = typedef 52 | $( applytemplate(fdef, i,nil,isinvokable(_item),ignoredescription) ) 53 | #else 54 | #-- 55 | #-- Show usage samples for item which is not a function 56 | #-- 57 | # if _item.metadata and _item.metadata.usage then 58 | $( applytemplate(_item.metadata.usage, i) ) 59 | # end 60 | #end 61 | # 62 |
63 |
]] 64 | -------------------------------------------------------------------------------- /template/page.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return 13 | [[ 14 | 15 | #if _page.headers and #_page.headers > 0 then 16 | 17 | # for _, header in ipairs(_page.headers) do 18 | $(header) 19 | # end 20 | 21 | #end 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | # -- 31 | # -- Generating lateral menu 32 | # -- 33 | 64 | $( applytemplate(_page.currentmodule) ) 65 |
66 | 67 | 68 | ]] 69 | -------------------------------------------------------------------------------- /template/recordtypedef.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | return [[# 13 | # -- 14 | # -- Inheritance 15 | # -- 16 | #if _recordtypedef.supertype then 17 | Extends $( fulllinkto(_recordtypedef.supertype)) 18 | #end 19 | # -- 20 | # -- Descriptions 21 | # -- 22 | #if _recordtypedef.shortdescription and #_recordtypedef.shortdescription > 0 then 23 | $( format( _recordtypedef.shortdescription ) ) 24 | #end 25 | #if _recordtypedef.description and #_recordtypedef.description > 0 then 26 | $( format( _recordtypedef.description ) ) 27 | #end 28 | # -- 29 | # -- Structure 30 | # -- 31 | #if _recordtypedef.structurekind then 32 | # local structureLine 33 | # if _recordtypedef.structurekind == "map" then 34 | # structureLine = { 35 | # '', prettyname(_recordtypedef), '', 36 | # ' is a map of ', fulllinkto(_recordtypedef.defaultkeytyperef),'', 37 | # ' to ', fulllinkto(_recordtypedef.defaultvaluetyperef) , '. ', 38 | # _recordtypedef.structuredescription } 39 | # elseif _recordtypedef.structurekind == "list" then 40 | # structureLine = { 41 | # '', prettyname(_recordtypedef), '', 42 | # ' is a list of ', fulllinkto(_recordtypedef.defaultvaluetyperef),'. ', 43 | # _recordtypedef.structuredescription } 44 | # end 45 | # if structureLine then 46 | $(format(table.concat(structureLine))) 47 | # end 48 | #end 49 | #-- 50 | #-- Describe usage 51 | #-- 52 | #if _recordtypedef.metadata and _recordtypedef.metadata.usage then 53 | $( applytemplate(_recordtypedef.metadata.usage, i) ) 54 | #end 55 | # -- 56 | # -- Describe type fields 57 | # -- 58 | #local calldef = _recordtypedef:getcalldef() 59 | #local hasfield = not isempty(_recordtypedef.fields) 60 | #if calldef or hasfield then 61 | Field(s) 62 | # if calldef then 63 |
64 |
65 | 66 | $( prettyname(calldef,_recordtypedef) ) 67 | 68 |
69 |
70 | $( applytemplate(calldef, i, nil, true) ) 71 |
72 |
73 | # end 74 | # for name, item in sortedpairs( _recordtypedef.fields ) do 75 | $( applytemplate(item, i) ) 76 | # end 77 | #end ]] 78 | -------------------------------------------------------------------------------- /template/usage.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Marc AUBRY 10 | -- - initial API and implementation 11 | -------------------------------------------------------------------------------- 12 | return[[# 13 | #-- 14 | #-- Show usage samples 15 | #-- 16 | #if _usage then 17 | # if #_usage > 1 then 18 | # -- Show all usages 19 | Usages: 20 |
    21 | # -- Loop over several usage description 22 | # for _, usage in ipairs(_usage) do 23 |
  • $( securechevrons(usage.description) )
  • 24 | # end 25 |
26 | # elseif #_usage == 1 then 27 | # -- Show unique usage sample 28 | Usage: 29 | # local usage = _usage[1] 30 |
$( securechevrons(usage.description) )
31 | # end 32 | #end 33 | #]] 34 | -------------------------------------------------------------------------------- /template/utils.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | local apimodel = require 'models.apimodel' 13 | 14 | --- 15 | -- @module docutils 16 | -- Handles link generation, node quick description. 17 | -- 18 | -- Provides: 19 | -- * link generation 20 | -- * anchor generation 21 | -- * node quick description 22 | local M = {} 23 | 24 | function M.isempty(map) 25 | local f = pairs(map) 26 | return f(map) == nil 27 | end 28 | 29 | --- 30 | -- Provide a handling function for all supported anchor types 31 | -- recordtypedef => #(typename) 32 | -- item (field of recordtypedef) => #(typename).itemname 33 | -- item (global) => itemname 34 | --functiontypedef callof given type => ##(giventypename)__call 35 | 36 | M.anchortypes = { 37 | recordtypedef = function (o) return string.format('#(%s)', o.name) end, 38 | functiontypedef = function (o,t) return string.format('#(%s)__call', t.name) end, 39 | item = function(o) 40 | if not o.parent or o.parent.tag == 'file' then 41 | -- Handle items referencing globals 42 | return o.name 43 | elseif o.parent and o.parent.tag == 'recordtypedef' then 44 | -- Handle items included in recordtypedef 45 | local recordtypedef = o.parent 46 | local recordtypedefanchor = M.anchor(recordtypedef) 47 | if not recordtypedefanchor then 48 | return nil, 'Unable to generate anchor for `recordtypedef parent.' 49 | end 50 | return string.format('%s.%s', recordtypedefanchor, o.name) 51 | end 52 | return nil, 'Unable to generate anchor for `item' 53 | end 54 | } 55 | 56 | --- 57 | -- Provides anchor string for an object of API mode 58 | -- 59 | -- @function [parent = #docutils] anchor 60 | -- @param modelobject Object form API model 61 | -- @result #string Anchor for an API model object, this function __may rise an error__ 62 | -- @usage # -- In a template 63 | -- # local anchorname = anchor(someobject) 64 | -- 65 | function M.anchor( modelobject,... ) 66 | local tag = modelobject.tag 67 | if M.anchortypes[ tag ] then 68 | return M.anchortypes[ tag ](modelobject,...) 69 | end 70 | return nil, string.format('No anchor available for `%s', tag) 71 | end 72 | 73 | local function getexternalmodule( item ) 74 | -- Get file which contains this item 75 | local file 76 | if item.parent then 77 | if item.parent.tag =='recordtypedef' then 78 | local recordtypedefparent = item.parent.parent 79 | if recordtypedefparent and recordtypedefparent.tag =='file'then 80 | file = recordtypedefparent 81 | end 82 | elseif item.parent.tag =='file' then 83 | file = item.parent 84 | else 85 | return nil, 'Unable to fetch item parent' 86 | end 87 | end 88 | return file 89 | end 90 | 91 | --- 92 | -- Provide a handling function for all supported link types 93 | -- 94 | -- internaltyperef => ##(typename) 95 | -- => #anchor(recordtyperef) 96 | -- externaltyperef => modulename.html##(typename) 97 | -- => linkto(file)#anchor(recordtyperef) 98 | -- file(module) => modulename.html 99 | -- index => index.html 100 | -- functiontypedef callof given type => ##(giventypename)__call 101 | -- recordtypedef => ##(typename) 102 | -- => #anchor(recordtyperef) 103 | -- item (internal field of recordtypedef) => ##(typename).itemname 104 | -- => #anchor(item) 105 | -- item (internal global) => #itemname 106 | -- => #anchor(item) 107 | -- item (external field of recordtypedef) => modulename.html##(typename).itemname 108 | -- => linkto(file)#anchor(item) 109 | -- item (externalglobal) => modulename.html#itemname 110 | -- => linkto(file)#anchor(item) 111 | M.linktypes = { 112 | internaltyperef = function(o) return string.format('##(%s)', o.typename) end, 113 | externaltyperef = function(o) return string.format('%s.html##(%s)', o.modulename, o.typename) end, 114 | file = function(o) return string.format('%s.html', o.name) end, 115 | index = function() return 'index.html' end, 116 | recordtypedef = function(o) 117 | local anchor = M.anchor(o) 118 | if not anchor then 119 | return nil, 'Unable to generate anchor for `recordtypedef.' 120 | end 121 | return string.format('#%s', anchor) 122 | end, 123 | functiontypedef = function(o,...) 124 | local anchor = M.anchor(o,...) 125 | if not anchor then 126 | return nil, 'Unable to generate anchor for `functiontypedef.' 127 | end 128 | return string.format('#%s', anchor) 129 | end, 130 | item = function(o) 131 | 132 | -- For every item get anchor 133 | local anchor = M.anchor(o) 134 | if not anchor then 135 | return nil, 'Unable to generate anchor for `item.' 136 | end 137 | 138 | -- Built local link to item 139 | local linktoitem = string.format('#%s', anchor) 140 | 141 | -- 142 | -- For external item, prefix with the link to the module. 143 | -- 144 | -- The "external item" concept is used only here for short/embedded 145 | -- notation purposed. This concept and the `.external` field SHALL NOT 146 | -- be used elsewhere. 147 | -- 148 | if o.external then 149 | 150 | -- Get link to file which contains this item 151 | local file = getexternalmodule( o ) 152 | local linktofile = file and M.linkto( file ) 153 | if not linktofile then 154 | return nil, 'Unable to generate link for external `item.' 155 | end 156 | 157 | -- Built external link to item 158 | linktoitem = string.format("%s%s", linktofile, linktoitem) 159 | end 160 | 161 | return linktoitem 162 | end 163 | } 164 | 165 | --- 166 | -- Generates text for HTML links from API model element 167 | -- 168 | -- @function [parent = #docutils] 169 | -- @param modelobject Object form API model 170 | -- @result #string Links text for an API model element, this function __may rise an error__. 171 | -- @usage # -- In a template 172 | -- Some text 173 | function M.linkto( apiobject,...) 174 | local tag = apiobject.tag 175 | if M.linktypes[ tag ] then 176 | return M.linktypes[tag](apiobject,...) 177 | end 178 | if not tag then 179 | return nil, 'Link generation is impossible as no tag has been provided.' 180 | end 181 | return nil, string.format('No link generation available for `%s.', tag) 182 | end 183 | 184 | --- 185 | -- Provide a handling function for all supported pretty name types 186 | -- primitivetyperef => #typename 187 | -- internaltyperef => #typename 188 | -- inlinetyperef => #def.typename 189 | -- externaltyperef => modulename#typename 190 | -- file(module) => modulename 191 | -- index => index 192 | -- functiontypedef callof given type => giventypename(param1,param2, ...) 193 | -- recordtypedef => typename 194 | -- item (internal function of recordtypedef) => typename.itemname(param1, param2,...) 195 | -- item (internal func with self of recordtypedef) => typename:itemname(param2) 196 | -- item (internal non func field of recordtypedef) => typename.itemname 197 | -- item (internal func global) => functionname(param1, param2,...) 198 | -- item (internal non func global) => itemname 199 | -- item (external function of recordtypedef) => modulename#typename.itemname(param1, param2,...) 200 | -- item (external func with self of recordtypedef) => modulename#typename:itemname(param2) 201 | -- item (external non func field of recordtypedef) => modulename#typename.itemname 202 | -- item (external func global) => functionname(param1, param2,...) 203 | -- item (external non func global) => itemname 204 | M.prettynametypes = { 205 | primitivetyperef = function(o) return string.format('#%s', o.typename) end, 206 | externaltyperef = function(o) return string.format('%s#%s', o.modulename, o.typename) end, 207 | inlinetyperef = function(o) 208 | if not(o.def and o.def.tag == "recordtypedef" and o.def.name) then 209 | return nil 210 | end 211 | if o.def.name == "list" then 212 | local valuetypename = M.prettyname(o.def.defaultvaluetyperef) 213 | return valuetypename and string.format('#list<%s>', valuetypename) or nil 214 | elseif o.def.name == "map" then 215 | local keytypename = M.prettyname(o.def.defaultkeytyperef) 216 | local valuetypename = M.prettyname(o.def.defaultvaluetyperef) 217 | return keytypename and valuetypename and string.format('#map<%s,%s>', keytypename, valuetypename) or nil 218 | else 219 | return string.format('#%s',o.def.name) 220 | end 221 | end, 222 | index = function(o) return "index" end, 223 | file = function(o) return o.name end, 224 | recordtypedef = function(o) return o.name end, 225 | functiontypedef = function(o,t) 226 | if t and t.tag == 'recordtypedef' and t.name then 227 | local paramlist = {} 228 | for position, param in ipairs(o.params) do 229 | -- we ignore the first param 230 | if not (position == 1) then 231 | table.insert(paramlist, param.name) 232 | end 233 | end 234 | return string.format('%s(%s)',t.name, table.concat(paramlist, ", ")) 235 | end 236 | end, 237 | item = function( o ) 238 | 239 | -- Determine item name 240 | -- ---------------------- 241 | local itemname = o.name 242 | 243 | -- Determine scope 244 | -- ---------------------- 245 | local parent = o.parent 246 | local isglobal = parent and parent.tag == 'file' 247 | local isfield = parent and parent.tag == 'recordtypedef' 248 | 249 | -- Determine type name 250 | -- ---------------------- 251 | 252 | local typename = isfield and parent.name 253 | 254 | -- Fetch item definition 255 | -- ---------------------- 256 | -- Get file object 257 | local file 258 | if isglobal then 259 | file = parent 260 | elseif isfield then 261 | file = parent.parent 262 | end 263 | -- Get definition 264 | local definition = o:resolvetype (file) 265 | 266 | 267 | 268 | -- Build prettyname 269 | -- ---------------------- 270 | local prettyname 271 | if not definition or definition.tag ~= 'functiontypedef' then 272 | -- Fields 273 | if isglobal or not typename then 274 | prettyname = itemname 275 | else 276 | prettyname = string.format('%s.%s', typename, itemname) 277 | end 278 | else 279 | -- Functions 280 | -- Build parameter list 281 | local paramlist = {} 282 | local isinvokable = M.isinvokable(o) 283 | for position, param in ipairs(definition.params) do 284 | -- For non global function, when first parameter is 'self', 285 | -- it will not be part of listed parameters 286 | if not (position == 1 and isinvokable and isfield) then 287 | table.insert(paramlist, param.name) 288 | if position ~= #definition.params then 289 | table.insert(paramlist, ', ') 290 | end 291 | end 292 | end 293 | 294 | if isglobal or not typename then 295 | prettyname = string.format('%s(%s)',itemname, table.concat(paramlist)) 296 | else 297 | -- Determine function prefix operator, 298 | -- ':' if 'self' is first parameter, '.' else way 299 | local operator = isinvokable and ':' or '.' 300 | 301 | -- Append function parameters 302 | prettyname = string.format('%s%s%s(%s)',typename, operator, itemname, table.concat(paramlist)) 303 | end 304 | end 305 | 306 | -- Manage external Item prettyname 307 | -- ---------------------- 308 | local externalmodule = o.external and getexternalmodule( o ) 309 | local externalmodulename = externalmodule and externalmodule.name 310 | 311 | if externalmodulename then 312 | return string.format('%s#%s',externalmodulename,prettyname) 313 | else 314 | return prettyname 315 | end 316 | end 317 | } 318 | M.prettynametypes.internaltyperef = M.prettynametypes.primitivetyperef 319 | 320 | --- 321 | -- Check if the given item is a function that can be invoked 322 | function M.isinvokable(item) 323 | --test if the item is global 324 | if item.parent and item.parent.tag == 'file' then 325 | return false 326 | end 327 | -- check first param 328 | local definition = item:resolvetype() 329 | if definition and definition.tag == 'functiontypedef' then 330 | if (#definition.params > 0) then 331 | return definition.params[1].name == 'self' 332 | end 333 | end 334 | end 335 | 336 | --- 337 | -- Disable Markdown processing on a specific string 338 | -- @param #string s Content to browse 339 | -- @param #string escaped What Markdown should not process 340 | -- @return #string Original `s` #string with `escaped` #string backslashed 341 | function M.escape(s, escaped) 342 | return string.gsub(s, escaped, '\\'..escaped) 343 | end 344 | 345 | --- 346 | -- Provide human readable overview from an API model element 347 | -- 348 | -- Resolve all element needed to summurize nicely an element form API model. 349 | -- @usage $ print( prettyname(item) ) 350 | -- module:somefunction(secondparameter) 351 | -- @function [parent = #docutils] 352 | -- @param apiobject Object form API model 353 | -- @result #string Human readable description of given element. 354 | -- @result #nil, #string In case of error. 355 | function M.prettyname( apiobject, ... ) 356 | local tag = apiobject.tag 357 | if M.prettynametypes[tag] then 358 | local prettyname = M.prettynametypes[tag](apiobject,...) 359 | return M.escape(prettyname,'_') 360 | elseif not tag then 361 | return nil, 'No pretty name available as no tag has been provided.' 362 | end 363 | return nil, string.format('No pretty name for `%s.', tag) 364 | end 365 | 366 | --- 367 | -- Just make a string print table in HTML. 368 | -- @function [parent = #docutils] securechevrons 369 | -- @param #string String to convert. 370 | -- @usage securechevrons('') => '<markup>' 371 | -- @return #string Converted string. 372 | function M.securechevrons( str ) 373 | if not str then return nil, 'String expected.' end 374 | return string.gsub(str:gsub('<', '<'), '>', '>') 375 | end 376 | 377 | ------------------------------------------------------------------------------- 378 | -- Handling format of @{some#type} tag. 379 | -- Following functions enable to recognize several type of references between 380 | -- "{}". 381 | ------------------------------------------------------------------------------- 382 | 383 | --- 384 | -- Build a global var from string such as: 385 | -- * `global#foo` 386 | -- * `foo#global.bar` 387 | -- @param #string str 388 | local globals = function(str) 389 | -- Handling globals from modules 390 | local modulename, fieldname = str:gmatch('([%a%.%d_]+)#global%.([%a%.%d_]+)')() 391 | if modulename and fieldname then 392 | local item = apimodel._item(fieldname) 393 | local file = apimodel._file() 394 | file.name = modulename 395 | file:addglobalvar( item ) 396 | return item 397 | end 398 | -- Handling other globals 399 | local name = str:gmatch('global#([%a%.%d_]+)')() 400 | if name then 401 | return apimodel._externaltypref('global', name) 402 | end 403 | return nil 404 | end 405 | 406 | --- 407 | -- Build an external field from string like `module#(type).field` 408 | -- @param #string str 409 | local field = function( str ) 410 | 411 | -- Match `module#type.field` 412 | local mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#([%a%.%d_]+)%.([%a%.%d_]+)')() 413 | 414 | -- Try matching `module#(type).field` 415 | if not mod then 416 | mod, typename, fieldname = str:gmatch('([%a%.%d_]*)#%(([%a%.%d_]+)%)%.([%a%.%d_]+)')() 417 | if not mod then 418 | -- No match 419 | return nil 420 | end 421 | end 422 | 423 | -- Build according `item 424 | local modulefielditem = apimodel._item( fieldname ) 425 | local moduletype = apimodel._recordtypedef(typename) 426 | moduletype:addfield( modulefielditem ) 427 | local typeref 428 | if #mod > 0 then 429 | local modulefile = apimodel._file() 430 | modulefile.name = mod 431 | modulefile:addtype( moduletype ) 432 | typeref = apimodel._externaltypref(mod, typename) 433 | modulefielditem.external = true 434 | else 435 | typeref = apimodel._internaltyperef(typename) 436 | end 437 | modulefielditem.type = typeref 438 | return modulefielditem 439 | end 440 | 441 | --- 442 | -- Build an API internal reference from a string like: `#typeref` or #(type.ref) 443 | -- @param #string str 444 | local internal = function ( str ) 445 | local typename = str:gmatch('#([%a%.%d_]+)')() 446 | if not typename then 447 | typename = str:gmatch('#%(([%a%.%d_]+)%)')() 448 | if not typename then 449 | -- No match 450 | return nil 451 | end 452 | end 453 | 454 | -- Do not handle this name is it starts with reserved name "global" 455 | if typename:find("global.") == 1 then return nil end 456 | return apimodel._internaltyperef(typename) 457 | end 458 | 459 | --- 460 | -- Build an API external reference from a string like: `mod.ule#type` 461 | -- @param #string str 462 | local extern = function (str) 463 | 464 | -- Match `mod.ule#ty.pe` 465 | local modulename, typename = str:gmatch('([%a%.%d_]+)#([%a%.%d_]+)')() 466 | 467 | -- Trying `mod.ule#(ty.pe)` 468 | if not modulename then 469 | modulename, typename = str:gmatch('([%a%.%d_]+)#%(([%a%.%d_]+)%)')() 470 | 471 | -- No match at all 472 | if not modulename then 473 | return nil 474 | end 475 | end 476 | return apimodel._externaltypref(modulename, typename) 477 | end 478 | 479 | --- 480 | -- Build an API module(file) from a string like: `mod.ule` 481 | -- @param #string str 482 | local file = function (str) 483 | local modulename = str:gmatch('([%a%.%d_]+)')() 484 | if modulename then 485 | local file = apimodel._file() 486 | file.name = modulename 487 | return file 488 | end 489 | return nil 490 | end 491 | 492 | 493 | --- 494 | -- Provide API Model element from a string 495 | -- @usage local externaltyperef = getelement("somemodule#somefield") 496 | function M.getelement( str ) 497 | 498 | -- Order matters, more restrictive are at begin of table 499 | local extractors = { 500 | globals, 501 | field, 502 | extern, 503 | internal, 504 | file 505 | } 506 | -- Loop over extractors. 507 | -- First valid result is used 508 | for _, extractor in ipairs( extractors ) do 509 | local result = extractor( str ) 510 | if result then return result end 511 | end 512 | return nil 513 | end 514 | 515 | -------------------------------------------------------------------------------- 516 | -- Iterator that iterates on the table in key ascending order. 517 | -- 518 | -- @function [parent=#utils.table] sortedPairs 519 | -- @param t table to iterate. 520 | -- @return iterator function. 521 | function M.sortedpairs(t) 522 | local a = {} 523 | local insert = table.insert 524 | for n in pairs(t) do insert(a, n) end 525 | table.sort(a) 526 | local i = 0 527 | return function() 528 | i = i + 1 529 | return a[i], t[a[i]] 530 | end 531 | end 532 | return M 533 | -------------------------------------------------------------------------------- /templateengine.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Copyright (c) 2011-2012 Sierra Wireless. 3 | -- All rights reserved. This program and the accompanying materials 4 | -- are made available under the terms of the Eclipse Public License v1.0 5 | -- which accompanies this distribution, and is available at 6 | -- http://www.eclipse.org/legal/epl-v10.html 7 | -- 8 | -- Contributors: 9 | -- Kevin KIN-FOO 10 | -- - initial API and implementation and initial documentation 11 | -------------------------------------------------------------------------------- 12 | --- 13 | -- This library provide html description of elements from the externalapi 14 | local M = {} 15 | 16 | -- Load template engine 17 | local pltemplate = require 'pl.template' 18 | 19 | -- Markdown handling 20 | local markdown = require 'markdown' 21 | 22 | -- apply template to the given element 23 | function M.applytemplate(elem, ident, templatetype,...) 24 | -- define environment 25 | local env = M.getenv(elem, ident,...) 26 | 27 | -- load template 28 | local template = M.gettemplate(elem,templatetype) 29 | if not template then 30 | templatetype = templatetype and string.format(' "%s"', templatetype) or '' 31 | local elementname = string.format(' for %s', elem.tag or 'untagged element') 32 | error(string.format('Unable to load %s template %s', templatetype, elementname)) 33 | end 34 | 35 | -- apply template 36 | local str, err = pltemplate.substitute(template, env) 37 | 38 | --manage errors 39 | if not str then 40 | local templateerror = templatetype and string.format(' parsing "%s" template ', templatetype) or '' 41 | error(string.format('An error occured%s for "%s"\n%s',templateerror, elem.tag, err)) 42 | end 43 | return str 44 | end 45 | 46 | -- get the a new environment for this element 47 | function M.getenv(elem, ident,...) 48 | local currentenv ={} 49 | for k,v in pairs(M.env) do currentenv[k] = v end 50 | if elem and elem.tag then 51 | currentenv['_'..elem.tag]= elem 52 | end 53 | currentenv['i']= ident or 1 54 | currentenv['templateparams']= {...} 55 | return currentenv 56 | end 57 | 58 | -- get the template for this element 59 | function M.gettemplate(elem,templatetype) 60 | local tag = elem and elem.tag 61 | if tag then 62 | if templatetype then 63 | return require ("template." .. templatetype.. "." .. tag) 64 | else 65 | return require ("template." .. tag) 66 | end 67 | end 68 | end 69 | 70 | 71 | --- 72 | -- Allow user to format text in descriptions. 73 | -- Default implementation replaces @{---} tags with links and apply markdown. 74 | -- @return #string 75 | local function format(string) 76 | -- Allow to replace encountered tags with valid links 77 | local replace = function(found) 78 | local apiobj = M.env.getelement(found) 79 | if apiobj then 80 | return M.env.fulllinkto(apiobj) 81 | end 82 | return found 83 | end 84 | string = string:gsub('@{%s*(.-)%s*}', replace) 85 | return M.env.markdown( string ) 86 | end 87 | 88 | --- 89 | -- Provide a full link to an element using `prettyname` and `linkto`. 90 | -- Default implementation is for HTML. 91 | local function fulllinkto(o,...) 92 | local ref = M.env.linkto(o,...) 93 | local name = M.env.prettyname(o,...) 94 | if not ref then 95 | return name 96 | end 97 | return string.format('%s', ref, name) 98 | end 99 | -- 100 | -- Define default template environnement 101 | -- 102 | local defaultenv = { 103 | table = table, 104 | ipairs = ipairs, 105 | pairs = pairs, 106 | markdown = markdown, 107 | applytemplate = M.applytemplate, 108 | format = format, 109 | linkto = function(str) return str end, 110 | fulllinkto = fulllinkto, 111 | prettyname = function(s) return s end, 112 | getelement = function(s) return nil end 113 | } 114 | 115 | -- this is the global env accessible in the templates 116 | -- env should be redefine by docgenerator user to add functions or redefine it. 117 | M.env = defaultenv 118 | return M 119 | --------------------------------------------------------------------------------