nitlight: new tool nitlight (and new module highlight)
authorJean Privat <jean@pryen.org>
Wed, 21 Aug 2013 20:42:58 +0000 (16:42 -0400)
committerJean Privat <jean@pryen.org>
Thu, 22 Aug 2013 17:40:28 +0000 (13:40 -0400)
Signed-off-by: Jean Privat <jean@pryen.org>

src/highlight.nit [new file with mode: 0644]
src/nitlight.nit [new file with mode: 0644]

diff --git a/src/highlight.nit b/src/highlight.nit
new file mode 100644 (file)
index 0000000..447ef4d
--- /dev/null
@@ -0,0 +1,603 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Highliting of Nit AST
+module highlight
+
+import modelize_property
+import frontend
+import parser_util
+import html
+
+# Visitor used to produce a HTML tree based on a AST on a `Source`
+class HighlightVisitor
+       super Visitor
+
+       # The root of the HTML hierarchy
+       var html = new HTMLTag("span")
+
+       private var token_head: HTMLTag
+
+       private var prod_head: HTMLTag
+
+       private var prod_root: nullable HTMLTag
+
+       # Is the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
+       # Used to have a really huge and verbose HTML (mainly for debug)
+       var with_ast writable = false
+
+       # Enter in a new node
+       # Exit is automatic
+       fun enter(n: HTMLTag)
+       do
+               if prod_root != null then
+                       prod_head.add(n)
+                       prod_head = n
+               else
+                       prod_root = n
+                       prod_head = n
+               end
+       end
+
+       # The position in the source file.
+       # Used to print parts of the source betwen tokens of the AST
+       private var pos = 0
+
+       var modelbuilder: ModelBuilder
+
+       init(modelbuilder: ModelBuilder)
+       do
+               self.modelbuilder = modelbuilder
+               html.add_class("nitcode")
+               token_head = html
+               prod_head = html
+       end
+
+       # Used to remember the first node, thus knowing when the whole visit is over
+       private var first_node: nullable ANode
+
+       private var seen_token = new HashSet[Token]
+
+       private fun process_upto_token(node: Token)
+       do
+               # recursively process previous tokens
+               var prev = node.prev_token
+               if prev != null and not seen_token.has(prev) then
+                       process_upto_token(prev)
+                       prev.accept_highlight_visitor(self)
+               end
+
+               # Add text between `last_token` and `node`
+               var pstart = node.location.pstart
+               if pos < pstart then
+                       var text = node.location.file.string.substring(pos, pstart-pos)
+                       token_head.append(text)
+                       #node.debug("WRT: {token_head.classes} << '{text.escape_to_c}' ")
+               end
+               pos = node.location.pend + 1
+               if pos < pstart then
+                       node.debug("pos={pos}, pstart={pstart}, pend={node.location.pend}")
+               end
+
+               seen_token.add node
+       end
+
+       # Dubuging method
+       private fun where(node: ANode, tag: String)
+       do
+               var pr = prod_root
+               if pr == null then
+                       node.debug "{tag}-> {token_head.classes} : {prod_head.classes}"
+               else
+                       node.debug "{tag}-> {token_head.classes} : {pr.classes}..{prod_head.classes}"
+               end
+       end
+
+       redef fun visit(node)
+       do
+               if first_node == null then first_node = node
+
+               if node isa Token then
+                       process_upto_token(node)
+
+                       #where(node, "TOK")
+                       var pr = prod_root
+                       if pr != null then
+                               #node.debug("ADD: {token_head.classes} << {pr.classes} ")
+                               token_head.add(pr)
+                               token_head = prod_head
+                               prod_root = null
+                       end
+               end
+
+               var oldph = prod_head
+               #where(node, " IN")
+               node.accept_highlight_visitor(self)
+               #where(node, "OUT")
+               var pr = prod_root
+               if pr == null then
+                       assert token_head == prod_head
+               else
+                       assert token_head != prod_head
+                       token_head.add(pr)
+                       prod_root = null
+               end
+               prod_head = oldph
+               token_head = oldph
+               #where(node, " IS")
+
+               if node == first_node then
+                       html.append(node.location.file.string.substring_from(pos))
+               end
+       end
+
+       # Return a default CSS content related to CSS classes used in the `html` tree.
+       # Could be inlined in the `.html` file of saved as a specific `.css` file.
+       fun css_content: String
+       do
+               return """
+.nitcode a { color: inherit; text-decoration: inherit; } /* hide links */
+.nitcode a:hover { text-decoration: underline; } /* underline links */
+.nitcode span[title]:hover { text-decoration: underline; } /* underline titles */
+/* lexical raw tokens. independent of usage or semantic: */
+.nitcode .nc_c { color: gray; font-style: italic; } /* comment */
+.nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
+.nitcode .nc_k { font-weight: bold; } /* keyword */
+.nitcode .nc_o {} /* operator */
+.nitcode .nc_i {} /* standard identifier */
+.nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
+.nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
+.nitcode .nc_l { color: #009999; } /* char and number literal */
+.nitcode .nc_s { color: #8F1546; } /* string literal */
+/* syntactic token usage. added because of their position in the AST */
+.nitcode .nc_ast { color: blue; } /* assert label */
+.nitcode .nc_la { color: blue; } /* break/continue label */
+.nitcode .nc_m { color: #445588; } /* module name */
+/* syntactic groups */
+.nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
+  .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
+  .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
+.nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
+.nitcode .nc_cdef {} /* A whole class definition */
+.nitcode .nc_pdef {} /* A whole property definition */
+/* semantic token usage */
+.nitcode .nc_v { font-style: italic; } /* local variable or parameter */
+.nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
+
+.nitcode .nc_error { border: 1px red solid;} /* not used */
+"""
+       end
+end
+
+redef class ANode
+       private fun accept_highlight_visitor(v: HighlightVisitor)
+       do
+               if v.with_ast then
+                       var res = new HTMLTag("span")
+                       res.add_class(class_name)
+                       v.enter res
+               end
+               visit_all(v)
+       end
+       private fun decorate_tag(res: HTMLTag, token: Token)
+       do
+               #debug("no decoration for {token.inspect}")
+               #res.add_class("nc_error")
+       end
+end
+
+redef class AStdClassdef
+       redef fun accept_highlight_visitor(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_cdef")
+               var md = mclassdef
+               if md != null then
+                       var a = new HTMLTag("a")
+                       a.attr("id", md.to_s)
+                       res.add(a)
+               end
+               v.enter res
+               super
+       end
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_def")
+
+               var md = mclassdef
+               if md == null then return
+               var mc = md.mclass
+               res.attrs["title"] = mc.full_name
+               var mi = mc.intro
+               if md != mi then
+                       res.attrs["link"] = mi.mmodule.name + ".html#" + mi.to_s
+               end
+       end
+end
+redef class APropdef
+       redef fun accept_highlight_visitor(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_pdef")
+               var mpd
+               mpd = mpropdef
+               if mpd != null then res.add(tag(mpd))
+               if self isa AAttrPropdef then
+                       mpd = mreadpropdef
+                       if mpd != null then res.add(tag(mpd))
+                       mpd = mwritepropdef
+                       if mpd != null then res.add(tag(mpd))
+               end
+               v.enter res
+               super
+       end
+
+       private fun tag(mpd: MPropDef): HTMLTag
+       do
+               var a = new HTMLTag("a")
+               a.attr("id", mpd.to_s)
+               return a
+       end
+end
+
+redef class Token
+       # Produce an HTMLTag with the correct contents and CSS classes
+       # Subclasses can redefine it to decorate the tag
+       protected fun make_tag(v: HighlightVisitor): HTMLTag
+       do
+               var res = new HTMLTag("span")
+               res.text(text)
+               return res
+       end
+
+       # Use `empty_tag` to create the tag ; then fill it and add it to the html
+       redef fun accept_highlight_visitor(v)
+       do
+               var n = make_tag(v)
+               if n.attrs.is_empty and n.classes.is_empty then
+                       for c in n.children do
+                               v.token_head.add(c)
+                       end
+               else if n.attrs.has_key("link") then
+                       var a = new HTMLTag("a")
+                       a.attrs["href"] = n.attrs["link"]
+                       n.attrs.keys.remove("link")
+                       a.add(n)
+                       v.token_head.add(a)
+               else
+                       v.token_head.add(n)
+               end
+               #debug("WRT: {v.token_head.classes} << '{text.escape_to_c}' ")
+       end
+end
+redef class TokenKeyword
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_k")
+               return res
+       end
+end
+redef class TokenOperator
+       redef fun make_tag(v)
+       do
+               var res = super
+               var p = parent
+               if p != null then p.decorate_tag(res, self)
+               res.add_class("nc_o")
+               return res
+       end
+end
+
+redef class Variable
+       private fun decorate_tag(res: HTMLTag, token: Token)
+       do
+               if declared_type == null then return
+               res.attrs["title"] = name + ": " + declared_type.to_s
+       end
+end
+
+redef class AVarFormExpr
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_v")
+               var variable = self.variable
+               if variable == null then return
+               variable.decorate_tag(res, token)
+       end
+end
+
+redef class AVardeclExpr
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_v")
+               var variable = self.variable
+               if variable == null then return
+               variable.decorate_tag(res, token)
+       end
+end
+
+redef class AForExpr
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_v")
+               var vs = variables
+               if vs == null then return
+               var idx = n_ids.index_of(token.as(TId))
+               var variable = vs[idx]
+               variable.decorate_tag(res, token)
+       end
+end
+
+redef class AParam
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_v")
+               var mp = mparameter
+               if mp == null then return
+               res.attrs["title"] = mp.name + ": " + mp.mtype.to_s
+       end
+end
+
+redef class AAssertExpr
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_ast")
+       end
+end
+
+redef class ALabel
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_la")
+       end
+end
+
+redef class ASendExpr
+       redef fun decorate_tag(res, token)
+       do
+               if callsite == null then return
+               var mpropdef = callsite.mpropdef
+               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
+               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
+       end
+end
+
+redef class ANewExpr
+       redef fun decorate_tag(res, token)
+       do
+               if callsite == null then return
+               var mpropdef = callsite.mpropdef
+               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
+               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
+       end
+end
+
+redef class AAssignOp
+       redef fun decorate_tag(res, v)
+       do
+               var p = parent
+               assert p isa AReassignFormExpr
+
+               var callsite = p.reassign_callsite
+               if callsite == null then return
+               var mpropdef = callsite.mpropdef
+               res.attrs["title"] = mpropdef.to_s + callsite.msignature.to_s
+               res.attrs["link"] = mpropdef.mclassdef.mmodule.name + ".html#" + mpropdef.to_s
+       end
+end
+
+redef class AModuleName
+       redef fun decorate_tag(res, token)
+       do
+               parent.decorate_tag(res, token)
+       end
+end
+
+redef class AModuledecl
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_def")
+               res.add_class("nc_m")
+               var p = parent
+               assert p isa AModule
+               var mm = p.mmodule
+               if mm == null then return
+               res.attrs["title"] = mm.full_name
+       end
+end
+
+redef class AStdImport
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_m")
+               var mm = mmodule
+               if mm == null then return
+               res.attrs["title"] = mm.full_name
+               res.attrs["link"] = mm.name + ".html"
+       end
+end
+
+redef class AAttrPropdef
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_def")
+               var mpd: nullable MPropDef
+               mpd = mreadpropdef
+               if mpd == null then mpd = mpropdef
+               if mpd == null then return
+               var mp = mpd.mproperty
+               res.attrs["title"] = mp.full_name
+               if mp.intro != mpd then
+                       mpd = mp.intro
+                       res.attrs["link"] = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
+               end
+       end
+end
+
+redef class TId
+       redef fun make_tag(v)
+       do
+               var res = super
+               var p = parent
+               if p != null then p.decorate_tag(res, self)
+               res.add_class("nc_i")
+               return res
+       end
+end
+redef class AMethid
+       redef fun accept_highlight_visitor(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_def")
+               var p = parent
+               if p isa AMethPropdef then
+                       var mpd = p.mpropdef
+                       if mpd != null then
+                               var mp = mpd.mproperty
+                               res.attr("title", mp.full_name)
+                               if mp.intro != mpd then
+                                       mpd = mp.intro
+                                       var link = mpd.mclassdef.mmodule.name + ".html#" + mpd.to_s
+                                       var l = new HTMLTag("a")
+                                       l.attr("href", link)
+                                       v.enter l
+                               end
+                       end
+               end
+               v.enter res
+               super
+       end
+       redef fun decorate_tag(res, v)
+       do
+               # nothing to decorate
+       end
+end
+redef class TAttrid
+       redef fun make_tag(v)
+       do
+               var res = super
+               var p = parent
+               if p != null then p.decorate_tag(res, self)
+               res.add_class("nc_a")
+               return res
+       end
+end
+redef class AAttrFormExpr
+       redef fun decorate_tag(res, v)
+       do
+               var p = mproperty
+               if p == null then return
+               res.attrs["title"] = p.full_name
+               var pi = p.intro
+               res.attrs["link"] = pi.mclassdef.mmodule.name + ".html#" + pi.to_s
+       end
+end
+redef class TClassid
+       redef fun make_tag(v)
+       do
+               var res = super
+               var p = parent
+               if p != null then p.decorate_tag(res, self)
+               res.add_class("nc_t")
+               return res
+       end
+end
+redef class AType
+       redef fun decorate_tag(res, token)
+       do
+               var mt = mtype
+               if mt == null then return
+               var title = mt.to_s
+               if mt isa MNullableType then mt = mt.mtype
+               if mt isa MVirtualType or mt isa MParameterType then
+                       res.add_class("nc_vt")
+               else if mt isa MClassType then
+                       title = mt.mclass.full_name
+                       res.attrs["link"] = mt.mclass.intro.mmodule.name + ".html#" + mt.mclass.intro.to_s
+               end
+               res.attrs["title"] = title
+       end
+end
+redef class AFormaldef
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_vt")
+               if mtype == null then return
+               res.attrs["title"] = "{mtype.to_s}: {bound.to_s}"
+       end
+end
+redef class ATypePropdef
+       redef fun decorate_tag(res, token)
+       do
+               res.add_class("nc_def")
+               var md = mpropdef
+               if md == null then return
+               var mp = mpropdef.mproperty
+               res.attrs["title"] = mp.full_name
+               var mi = mp.intro
+               if md != mi then
+                       res.attrs["link"] = mi.mclassdef.mmodule.name + ".html#" + mi.to_s
+               end
+       end
+end
+redef class TComment
+       redef fun make_tag(v)
+       do
+               var res = super
+               if parent == null then
+                       res.add_class("nc_c")
+               else
+                       assert parent isa ADoc
+               end
+               return res
+       end
+end
+redef class ADoc
+       redef fun accept_highlight_visitor(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_d")
+               v.enter res
+               super
+       end
+end
+redef class TokenLiteral
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_l")
+               var p = parent
+               if p isa AStringFormExpr then p.decorate_tag(res, self)
+               return res
+       end
+end
+redef class ASuperstringExpr
+       redef fun accept_highlight_visitor(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_ss")
+               v.enter res
+               super
+       end
+end
+redef class AStringFormExpr
+       redef fun decorate_tag(res, v)
+       do
+               # Workarount to tag strings
+               res.classes.remove("nc_l")
+               res.add_class("nc_s")
+       end
+end
+
diff --git a/src/nitlight.nit b/src/nitlight.nit
new file mode 100644 (file)
index 0000000..ce3bf51
--- /dev/null
@@ -0,0 +1,114 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tool that produces highlighting for Nit programs
+module nitlight
+
+import highlight
+
+var toolcontext = new ToolContext
+
+var opt_fragment = new OptionBool("Omit document header and footer", "-f", "--fragment")
+var opt_dir = new OptionString("Output html files in a specific directory (required if more than one module)", "-d", "--dir")
+var opt_full = new OptionBool("Process also imported modules", "--full")
+var opt_ast = new OptionBool("Generate specific HTML elements for each Node of the AST", "--ast")
+toolcontext.option_context.add_option(opt_fragment, opt_dir, opt_full)
+
+var model = new Model
+var modelbuilder = new ModelBuilder(model, toolcontext)
+
+toolcontext.process_options
+var args = toolcontext.option_context.rest
+
+if args.is_empty then
+       print "usage: nitlight [options] files..."
+       toolcontext.option_context.usage
+       return
+end
+
+var mmodules = modelbuilder.parse(args)
+modelbuilder.run_phases
+
+if opt_full.value then mmodules = model.mmodules
+
+var dir = opt_dir.value
+if dir != null then
+       dir.mkdir
+else if mmodules.length > 1 then
+       print "More than one module to render, use option -d"
+       return
+end
+
+for mm in mmodules do
+       if dir != null then toolcontext.info("write {dir}/{mm.name}.html", 1)
+
+       var v = new HighlightVisitor(modelbuilder)
+       if opt_ast.value then v.with_ast = true
+       var page = null
+       if not opt_fragment.value then
+               page = new HTMLTag("html")
+               page.add_raw_html """<head>
+               <meta charset="utf-8">"""
+               if dir == null then
+                       page.add_raw_html """
+                       <style type="text/css">
+                       {{{v.css_content}}}
+                       </style>
+                       """
+               else
+                       page.add_raw_html """<link rel="stylesheet" type="text/css" href="style.css" />"""
+               end
+               page.add_raw_html "</head><body><pre>"
+       end
+       var m = modelbuilder.mmodule2nmodule[mm]
+       v.enter_visit(m)
+       if not opt_fragment.value then
+               page.add(v.html)
+               page.add_raw_html "</pre></body>"
+       else
+               page = v.html
+       end
+
+       if dir != null then
+               page.save("{dir}/{mm.name}.html")
+       else
+               print page.html
+       end
+end
+
+if dir != null then
+       toolcontext.info("write {dir}/index.html", 1)
+
+       var page = new HTMLTag("html")
+       page.add_raw_html """<head>
+       <meta charset="utf-8">
+       </head><body><ul>
+       """
+       for mm in mmodules do
+               var n = new HTMLTag("li")
+               var n2 = new HTMLTag("a")
+               page.add n
+               n.add n2
+               n2.attr("href", "{mm.name}.html")
+               n2.text(mm.name)
+       end
+       page.add_raw_html "</li></body>"
+       page.save("{dir}/index.html")
+
+       var v = new HighlightVisitor(modelbuilder)
+       toolcontext.info("write {dir}/style.css", 1)
+       var f = new OFStream.open("{dir}/style.css")
+       f.write v.css_content
+       f.close
+end