For long now we could express an `Int` in both decimal and hexadecimal form, however binary and octal forms were lacking.
This PR fixes it.
Pull-Request: #1378
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
module benitlux_controller
import nitcorn
-import json_serialization
+private import json::serialization
import benitlux_model
import benitlux_db
# List markdown source files from a directory.
fun list_md_files(dir: String): Array[String] do
var files = new Array[String]
- var pipe = new ProcessReader("find", dir, "-name", "*.md")
+ var pipe = new ProcessReader("find", dir, "-name", "*.{config.md_ext}")
while not pipe.eof do
var file = pipe.read_line
if file == "" then break # last line
- var name = file.basename(".md")
+ var name = file.basename(".{config.md_ext}")
if name == "header" or name == "footer" or name == "menu" then continue
files.add file
end
#
# REQUIRE: `has_template`
fun load_template(name: String): TemplateString do
- assert has_template(name)
+ if not has_template(name) then
+ message("Error: can't load template `{name}`", 0)
+ exit 1
+ end
var file = expand_path(config.root_dir, config.templates_dir, name)
var tpl = new TemplateString.from_file(file)
if tpl.has_macro("ROOT_URL") then
# Create a new article using a markdown source file.
init from_source(wiki: Nitiwiki, md_file: String) do
src_full_path = md_file
- init(wiki, md_file.basename(".md"))
+ init(wiki, md_file.basename(".{wiki.config.md_ext}"))
content = md
end
# * default: `http://localhost/`
var root_url: String is lazy do return value_or_default("wiki.root_url", "http://localhost/")
+ # Markdown extension recognized by this wiki.
+ #
+ # We allow only one kind of extension per wiki.
+ # Files with other markdown extensions will be treated as resources.
+ #
+ # * key: `wiki.md_ext`
+ # * default: `md`
+ var md_ext: String is lazy do return value_or_default("wiki.md_ext", "md")
# Root directory of the wiki.
#
module wiki_html
import wiki_links
+import markdown::decorators
redef class Nitiwiki
sitemap.is_dirty = true
return sitemap
end
+
+ # Markdown processor used for inline element such as titles in TOC.
+ private var inline_processor: MarkdownProcessor is lazy do
+ var proc = new MarkdownProcessor
+ proc.emitter.decorator = new InlineDecorator
+ return proc
+ end
+
+ # Inline markdown (remove h1, p, ... elements).
+ private fun inline_md(md: Writable): Writable do
+ return inline_processor.process(md.write_to_string)
+ end
end
redef class WikiEntry
while iter.is_ok do
var hl = iter.item
# parse title as markdown
- var title = hl.title.md_to_html.to_s
- title = title.substring(3, title.length - 8)
+ var title = wiki.inline_md(hl.title)
tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
iter.next
if iter.is_ok then
module wiki_links
import wiki_base
-intrude import markdown
+import markdown::wikilinks
redef class Nitiwiki
# Looks up a WikiEntry by its `name`.
emitter = new MarkdownEmitter(self)
emitter.decorator = new NitiwikiDecorator(wiki, context)
end
-
- redef fun token_at(text, pos) do
- var token = super
- if not token isa TokenLink then return token
- if pos + 1 < text.length then
- var c = text[pos + 1]
- if c == '[' then return new TokenWikiLink(pos, c)
- end
- return token
- end
end
private class NitiwikiDecorator
# Article used to contextualize links.
var context: WikiArticle
- fun add_wikilink(v: MarkdownEmitter, link: Text, name, comment: nullable Text) do
+ redef fun add_wikilink(v, link, name, comment) do
var wiki = v.processor.as(NitiwikiMdProcessor).wiki
var target: nullable WikiEntry = null
var anchor: nullable String = null
v.add "</a>"
end
end
-
-# A NitiWiki link token.
-#
-# Something of the form `[[foo]]`.
-#
-# Allowed formats:
-#
-# * `[[Wikilink]]`
-# * `[[Wikilink/Bar]]`
-# * `[[Wikilink#foo]]`
-# * `[[Wikilink/Bar#foo]]`
-# * `[[title|Wikilink]]`
-# * `[[title|Wikilink/Bar]]`
-# * `[[title|Wikilink/Bar#foo]]`
-class TokenWikiLink
- super TokenLink
-
- redef fun emit_hyper(v) do
- v.decorator.as(NitiwikiDecorator).add_wikilink(v, link.as(not null), name, comment)
- end
-
- redef fun check_link(v, out, start, token) do
- var md = v.current_text
- var pos = start + 2
- var tmp = new FlatBuffer
- pos = md.read_md_link_id(tmp, pos)
- if pos < start then return -1
- var name = tmp.write_to_string
- if name.has("|") then
- var parts = name.split_once_on("|")
- self.name = parts.first
- self.link = parts[1]
- else
- self.name = null
- self.link = name
- end
- pos += 1
- pos = md.skip_spaces(pos)
- if pos < start then return -1
- pos += 1
- return pos
- end
-end
--- /dev/null
+Render section out
--- /dev/null
+nitiWiki
+name: wiki3
+config: wiki3/config.ini
+url: http://localhost/
+
+There is modified files:
+ + pages
+ + /pages/contact.mdwn
+ + /pages/index.mdwn
+ + /pages/other_page.mdwn
+
+Use nitiwiki --render to render modified files
--- /dev/null
+wiki.name=wiki3
+wiki.root_dir=wiki3
+wiki.md_ext=mdwn
--- /dev/null
+# Other Page
--- /dev/null
+<div class="row footer">
+ <div class="container-fluid">
+ <div class="well well-sm">
+ <p><strong>%TITLE% © %YEAR%</strong></p>
+ <p class="text-muted"><em>last modification %GEN_TIME%</em></p>
+ <p class="text-muted">Proudly powered by
+ <a href="http://nitlanguage.org">nit</a>!</p>
+ </div>
+ </div>
+</div>
--- /dev/null
+<div class="container-fluid header">
+ <div class="container">
+ <div class="header">
+ <a href="http://uqam.ca"><img src="%ROOT_URL%/%LOGO%" alt="logo" /></a>
+ <h2>%SUBTITLE%</h2>
+ <h1>%TITLE%</h1>
+ </div>
+ </div>
+</div>
--- /dev/null
+<nav class="menu" role="navigation">
+ <div class="container">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="%ROOT_URL%index.html">%TITLE%</a>
+ </div>
+ <!-- Collect the nav links, forms, and other content for toggling -->
+ <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+ <ul class="nav navbar-nav">
+ %MENUS%
+ </ul>
+ </div><!-- /.navbar-collapse -->
+ </div>
+</nav>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>%TITLE%</title>
+
+ <link href="%ROOT_URL%/assets/vendors/bootstrap/bootstrap-3.2.0-dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+
+ <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+ <!--[if lt IE 9]>
+ <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+ <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+ %HEADER%
+ %TOP_MENU%
+ <div class="container">
+ <div class="row">
+ %BODY%
+ </div>
+ %FOOTER%
+ </div>
+
+ <script src="%ROOT_URL%/vendors/jquery/jquery-1.11.1.min.js"></script>
+ <script src="%ROOT_URL%/vendors/bootstrap/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script>
+ </body>
+</html>
--- /dev/null
+../bin/nitiwiki --config wiki3/config.ini --clean --render -v
--- /dev/null
+../bin/nitiwiki --config wiki3/config.ini --clean --status
self.path = path
var file = sys.files[path]
prepare_buffer(file.length)
- _buffer.append(file)
+ path.copy_to_native(_buffer, file.length, 0, 0)
end
redef fun close
redef fun fill_buffer
do
- _buffer.clear
+ buffer_reset
end_reached = true
end
redef fun on_save_state
do
- app.data_store["context"] = context.to_json
+ app.data_store["context"] = context
super
end
do
super
- var save = app.data_store["context"]
- if save == null then return
- assert save isa String
+ var context = app.data_store["context"]
+ if not context isa CalculatorContext then return
- self.context = new CalculatorContext.from_json(save)
+ self.context = context
display.text = context.display_text
end
end
# Business logic of a calculator
module calculator_logic
-import json::dynamic
+import serialization
# Hold the state of the calculator and its services
class CalculatorContext
+ auto_serializable
+
# Result of the last operation
var result: nullable Numeric = null
self.result = result
self.current = null
end
-
- # Serialize calculator state to Json
- fun to_json: String
- do
- # Do not save NaN nor inf
- var result = self.result
- if result != null and (result.to_f.is_nan or result.to_f.is_inf != 0) then result = null
-
- var self_last_op = self.last_op
- var last_op
- if self_last_op == null then
- last_op = "null"
- else last_op = "\"{self_last_op}\""
-
- var self_current = self.current
- var current
- if self_current == null then
- current = "null"
- else current = "\"{self_current}\""
-
- return """
-{
- "result": {{{result or else "null"}}},
- "last_op": {{{last_op}}},
- "current": {{{current}}}
-}"""
- end
-
- # Load calculator state from Json
- init from_json(json_string: String)
- do
- var json = json_string.to_json_value
- if json.is_error then
- print "Loading state failed: {json.to_error}"
- return
- end
-
- var result = json["result"]
- if result.is_numeric then self.result = result.to_numeric
-
- var last_op = json["last_op"]
- if last_op.is_string then self.last_op = last_op.to_s.chars.first
-
- var current = json["current"]
- if current.is_string then self.current = new FlatBuffer.from(current.to_s)
- end
end
redef universal Float
# ~~~
module a_star
+import serialization
+
# General graph node
class Node
+ super Serializable
+
# Type of the others nodes in the `graph`
type N: Node
end
end
end
+
+ # We customize the serialization process to avoid problems with recursive
+ # serialization engines. These engines, such as `JsonSerializer`,
+ # are at danger to serialize the graph as a very deep tree.
+ # With a large graph it can cause a stack overflow.
+ #
+ # Instead, we serialize the nodes first and then the links.
+ redef fun core_serialize_to(serializer: Serializer)
+ do
+ serializer.serialize_attribute("graph", graph)
+ end
+
+ redef init from_deserializer(deserializer)
+ do
+ deserializer.notify_of_creation self
+
+ var graph = deserializer.deserialize_attribute("graph")
+ assert graph isa Graph[N, Link]
+ self.graph = graph
+ end
end
# Link between two nodes and associated to a graph
class Link
+ auto_serializable
+
# Type of the nodes in `graph`
type N: Node
# General graph
class Graph[N: Node, L: Link]
+ super Serializable
+
# Nodes in this graph
var nodes: Set[N] = new HashSet[N]
# Used to check if nodes have been searched in one pathfinding
private var pathfinding_current_evocation: Int = 0
+
+ redef fun core_serialize_to(serializer: Serializer)
+ do
+ serializer.serialize_attribute("nodes", nodes)
+ serializer.serialize_attribute("links", links)
+ end
+
+ redef init from_deserializer(deserializer)
+ do
+ deserializer.notify_of_creation self
+
+ var nodes = deserializer.deserialize_attribute("nodes")
+ assert nodes isa HashSet[N]
+ self.nodes = nodes
+
+ var links = deserializer.deserialize_attribute("links")
+ assert links isa HashSet[L]
+ for link in links do add_link link
+ end
end
# Result from path finding and a walkable path
class AStarPath[N]
+ auto_serializable
- # The total cost of this path
+ # Total cost of this path
var total_cost: Int
- # The list of nodes composing this path
+ # Nodes composing this path
var nodes = new List[N]
private var at: Int = 0
# Context related to an evocation of pathfinding
class PathContext
+ auto_serializable
+
# Type of the nodes in `graph`
type N: Node
# Warning: A* is not optimize for such a case
class ConstantPathContext
super PathContext
+ auto_serializable
redef fun worst_cost do return 1
redef fun cost(l) do return 1
# A `PathContext` for graphs with `WeightedLink`
class WeightedPathContext
super PathContext
+ auto_serializable
redef type L: WeightedLink
# A `Link` with a `weight`
class WeightedLink
super Link
+ auto_serializable
# The `weight`, or cost, of this link
var weight: Int
# Advanced path conditions with customizable accept states
class TargetCondition[N: Node]
+ auto_serializable
+
# Should the pathfinding accept `node` as a goal?
fun accept(node: N): Bool is abstract
module bundle
import serialization
-import json_serialization
+import json::serialization
import platform
import activities
import dalvik
import android::bundle
import serialization
-private import json_serialization
+private import json::serialization
in "Java" `{
import android.content.Intent;
import dalvik
import serialization
-private import json_serialization
+private import json::serialization
in "Java" `{
import android.content.SharedPreferences;
- import android.content.Context;
+ import android.content.Context;
import android.app.Activity;
import java.util.Map;
import java.util.Iterator;
write_byte int
end
+ # Write `text` as a null terminated string
+ #
+ # To be used with `Reader::read_string`.
+ #
+ # Require: `text` has no null bytes.
+ fun write_string(text: Text)
+ do
+ write text
+ write_byte 0x00
+ end
+
+ # Write the length as a 64 bits integer, then the content of `text`
+ #
+ # To be used with `Reader::read_block`.
+ #
+ # Compared to `write_string`, this method supports null bytes in `text`.
+ fun write_block(text: Text)
+ do
+ write_int64 text.length
+ write text
+ end
+
# Write a floating point `value` on 32 bits
#
# Using this format may result in a loss of precision as it uses less bits
return [for b in 8.times do int.bin_and(2**b) > 0]
end
+ # Read a null terminated string
+ #
+ # To be used with `Writer::write_string`.
+ fun read_string: String
+ do
+ var buf = new FlatBuffer
+ loop
+ var byte = read_byte
+ if byte == 0x00 then return buf.to_s
+ buf.chars.add byte.ascii
+ end
+ end
+
+ # Read the length as a 64 bits integer, then the content of the block
+ #
+ # To be used with `Writer::write_block`.
+ fun read_block: String
+ do
+ var length = read_int64
+ if length == 0 then return ""
+ return read(length)
+ end
+
# Read a floating point on 32 bits and return it as a `Float`
#
# Using this format may result in a loss of precision as it uses less bits
# =============== Bitmap header ================
for x in [0..13] do
- bitmap_header[x] = fileReader.read(1)[0].ascii
+ var b = fileReader.read_byte
+ if b == null then
+ return
+ end
+ bitmap_header[x] = b
end
self.file_size = get_value(bitmap_header.subarray(2, 4))
self.data_offset = get_value(bitmap_header.subarray(10, 4))
# =============== DIB header ================
for x in [0..39] do
- dib_header[x] = fileReader.read(1)[0].ascii
+ var b = fileReader.read_byte
+ if b == null then return
+ dib_header[x] = b
end
var dib_size = get_value(dib_header.subarray(0, 4))
# only support BITMAPINFOHEADER
var row = new Array[Int].with_capacity(self.width)
for y in [0..self.width[
do
- var red = fileReader.read(1)[0].ascii * 256 * 256
- var green = fileReader.read(1)[0].ascii * 256
- var blue = fileReader.read(1)[0].ascii
+ var bts = fileReader.read_bytes(3)
+ if bts.length != 3 then return
+ var red = bts[0] << 16
+ var green = bts[1] << 8
+ var blue = bts[2]
row.add(red + green + blue)
end
self.data.add(row)
fileReader.close
end #end of load_from_file method
- # Reads in a series of bytes from the FileReader when loading a Bitmap from a file
- # FileReader.read(1) is used due to https://github.com/privat/nit/issues/1264
- private fun read_chars(fr: FileReader, howMany: Int): String
- do
- var s = ""
- for x in [1..howMany]
- do
- s += fr.read(1)
- end
- return s
- end
-
# Converts the value contained in two or four bytes into an Int. Since Nit
# does not have a byte type, Int is used
private fun get_value(array: Array[Int]): Int
# See the License for the specific language governing permissions and
# limitations under the License.
-# Handles serialization and deserialization of objects to/from Json.
-module json_serialization
+# Handles serialization and deserialization of objects to/from JSON
+#
+# ## Nity JSON
+#
+# `JsonSerializer` write Nit objects that subclass `Serializable` to JSON,
+# and `JsonDeserializer` can read them. They both use meta-data added to the
+# generated JSON to recreate the Nit instances with the exact original type.
+#
+# For more information on Nit serialization, see: ../serialization/README.md
+#
+# ## Plain JSON
+#
+# The attribute `JsonSerializer::plain_json` triggers generating plain and
+# clean JSON. This format is easier to read for an human and a non-Nit program,
+# but it cannot be fully deserialized. It can still be read by services from
+# `json::static` and `json::dynamic`.
+#
+# A shortcut to this service is provided by `Serializable::to_plain_json`.
+#
+# ### Usage Example
+#
+# ~~~nitish
+# import json::serialization
+#
+# class Person
+# auto_serializable
+#
+# var name: String
+# var year_of_birth: Int
+# var next_of_kin: nullable Person
+# end
+#
+# var bob = new Person("Bob", 1986)
+# var alice = new Person("Alice", 1978, bob)
+#
+# assert bob.to_plain_json == """
+# {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}"""
+#
+# assert alice.to_plain_json == """
+# {"name": "Alice", "year_of_birth": 1978, "next_of_kin": {"name": "Bob", "year_of_birth": 1986, "next_of_kin": null}}"""
+# ~~~
+module serialization
-import serialization
-import json::static
+import ::serialization
+private import ::serialization::engine_tools
+private import static
# Serializer of Nit objects to Json string.
class JsonSerializer
# Target writing stream
var stream: Writer
+ # Write plain JSON? easier to read but does not support Nit deserialization
+ #
+ # If `false`, the default, serialize to support deserialization:
+ #
+ # * Write meta-data, including the types of the serialized objects so they can
+ # be deserialized to their original form using `JsonDeserializer`.
+ # * Use references when an object has already been serialized so to not duplicate it.
+ # * Support cycles in references.
+ # * Preserve the Nit `Char` type as an object because it does not exist in JSON.
+ # * The generated JSON is standard and can be read by non-Nit programs.
+ # However, some Nit types are not represented by the simplest possible JSON representation.
+ # With the added meta-data, it can be complex to read.
+ #
+ # If `true`, serialize for other programs:
+ #
+ # * Nit objects are serialized to pure and standard JSON so they can
+ # be easily read by non-Nit programs and humans.
+ # * Nit objects are serialized for every references, so they can be duplicated.
+ # It is easier to read but it creates a larger output.
+ # * Does not support cycles, will replace the problematic references by `null`.
+ # * Does not serialize the meta-data needed to deserialize the objects
+ # back to regular Nit objects.
+ # * Keys of Nit `HashMap` are converted to their string reprensentation using `to_s`.
+ var plain_json = false is writable
+
+ # List of the current open objects, the first is the main target of the serialization
+ #
+ # Used only when `plain_json == true` to detect cycles in serialization.
+ private var open_objects = new Array[Object]
+
+ # Has the first attribute of the current object already been serialized?
+ #
+ # Used only when `plain_json == true`.
+ private var first_attribute = false
+
redef fun serialize(object)
do
if object == null then
stream.write "null"
- else object.serialize_to_json(self)
+ else
+ if plain_json then
+ for o in open_objects do
+ if object.is_same_serialized(o) then
+ # Cycle detected
+ stream.write "null"
+ return
+ end
+ end
+
+ open_objects.add object
+ end
+
+ first_attribute = true
+ object.serialize_to_json self
+ first_attribute = false
+
+ if plain_json then open_objects.pop
+ end
end
redef fun serialize_attribute(name, value)
do
- stream.write ", \"{name}\": "
+ if not plain_json or not first_attribute then
+ stream.write ", "
+ first_attribute = false
+ end
+
+ stream.write "\""
+ stream.write name
+ stream.write "\": "
super
end
redef fun serialize_reference(object)
do
- if refs_map.has_key(object) then
+ if not plain_json and refs_map.has_key(object) then
# if already serialized, add local reference
var id = ref_id_for(object)
- stream.write "\{\"__kind\": \"ref\", \"__id\": {id}\}"
+ stream.write "\{\"__kind\": \"ref\", \"__id\": "
+ stream.write id.to_s
+ stream.write "\}"
else
# serialize here
serialize object
private var text: Text
# Root json object parsed from input text.
- var root: nullable Jsonable is noinit
+ private var root: nullable Jsonable is noinit
# Depth-first path in the serialized object tree.
- var path = new Array[JsonObject]
+ private var path = new Array[JsonObject]
# Map of references to already deserialized objects.
private var id_to_object = new StrictHashMap[Int, Object]
private fun serialize_to_json(v: JsonSerializer)
do
var id = v.ref_id_for(self)
- v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\""
+ v.stream.write "\{"
+ if not v.plain_json then
+ v.stream.write "\"__kind\": \"obj\", \"__id\": "
+ v.stream.write id.to_s
+ v.stream.write ", \"__class\": \""
+ v.stream.write class_name
+ v.stream.write "\""
+ end
core_serialize_to(v)
v.stream.write "\}"
end
+
+ # Serialize this object to plain JSON
+ #
+ # This is a shortcut using `JsonSerializer::plain_json`,
+ # see its documentation for more information.
+ fun to_plain_json: String
+ do
+ var stream = new StringWriter
+ var serializer = new JsonSerializer(stream)
+ serializer.plain_json = true
+ serializer.serialize self
+ stream.close
+ return stream.to_s
+ end
end
redef class Int
end
redef class Char
- redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
+ redef fun serialize_to_json(v)
+ do
+ if v.plain_json then
+ v.stream.write to_s.to_json
+ else
+ v.stream.write "\{\"__kind\": \"char\", \"__val\": "
+ v.stream.write to_s.to_json
+ v.stream.write "\}"
+ end
+ end
end
redef class String
redef fun serialize_to_json(v)
do
# Register as pseudo object
- var id = v.ref_id_for(self)
- v.stream.write """{"__kind": "obj", "__id": """
- v.stream.write id.to_s
- v.stream.write """, "__class": """"
- v.stream.write class_name
- v.stream.write """", "__length": """
- v.stream.write length.to_s
- v.stream.write """, "__items": """
+ if not v.plain_json then
+ var id = v.ref_id_for(self)
+ v.stream.write """{"__kind": "obj", "__id": """
+ v.stream.write id.to_s
+ v.stream.write """, "__class": """"
+ v.stream.write class_name
+ v.stream.write """", "__length": """
+ v.stream.write length.to_s
+ v.stream.write """, "__items": """
+ end
+
serialize_to_pure_json v
- v.stream.write "\}"
+
+ if not v.plain_json then
+ v.stream.write "\}"
+ end
end
redef init from_deserializer(v: Deserializer)
redef class Array[E]
redef fun serialize_to_json(v)
do
- if class_name == "Array[nullable Serializable]" then
+ if v.plain_json or class_name == "Array[nullable Serializable]" then
# Using class_name to get the exact type,
# we do not want Array[Int] or anything else here.
# Register as pseudo object
var id = v.ref_id_for(self)
- v.stream.write """{"__kind": "obj", "__id": """
- v.stream.write id.to_s
- v.stream.write """, "__class": """"
- v.stream.write class_name
- v.stream.write """", "__length": """
- v.stream.write length.to_s
- v.stream.write """, "__keys": """
+ if v.plain_json then
+ v.stream.write "\{"
+ var first = true
+ for key, val in self do
+ if not first then
+ v.stream.write ", "
+ else first = false
+
+ if key == null then key = "null"
+
+ v.stream.write key.to_s.to_json
+ v.stream.write ": "
+ if not v.try_to_serialize(val) then
+ v.warn("element of type {val.class_name} is not serializable.")
+ v.stream.write "null"
+ end
+ end
+ v.stream.write "\}"
+ else
+ v.stream.write """{"__kind": "obj", "__id": """
+ v.stream.write id.to_s
+ v.stream.write """, "__class": """"
+ v.stream.write class_name
+ v.stream.write """", "__length": """
+ v.stream.write length.to_s
- keys.serialize_to_pure_json v
+ v.stream.write """, "__keys": """
+ keys.serialize_to_pure_json v
- v.stream.write """, "__values": """
- values.serialize_to_pure_json v
- v.stream.write "\}"
+ v.stream.write """, "__values": """
+ values.serialize_to_pure_json v
+
+ v.stream.write "\}"
+ end
end
# Instantiate a new `Array` from its serialized representation.
end
end
end
-
-# Maps instances to a value, uses `is_same_instance`
-#
-# Warning: This class does not implement all the services from `Map`.
-private class StrictHashMap[K, V]
- super Map[K, V]
-
- # private
- var map = new HashMap[K, Array[Couple[K, V]]]
-
- redef var length = 0
-
- redef fun is_empty do return length == 0
-
- # private
- fun node_at(key: K): nullable Couple[K, V]
- do
- if not map.keys.has(key) then return null
-
- var arr = map[key]
- for couple in arr do
- if couple.first.is_same_serialized(key) then
- return couple
- end
- end
-
- return null
- end
-
- redef fun [](key)
- do
- var node = node_at(key)
- assert node != null
- return node.second
- end
-
- redef fun []=(key, value)
- do
- var node = node_at(key)
- if node != null then
- node.second = value
- return
- end
-
- var arr
- if not map.keys.has(key) then
- arr = new Array[Couple[K, V]]
- map[key] = arr
- else arr = map[key]
-
- arr.add new Couple[K, V](key, value)
- self.length += 1
- end
-
- redef fun has_key(key) do return node_at(key) != null
-end
#
# TODO, use polls
class Connection
+ super Writer
+
# Closing this connection has been requested, but may not yet be `closed`
var close_requested = false
var native_buffer_event: NativeBufferEvent
# Close this connection if possible, otherwise mark it to be closed later
- fun close
+ redef fun close
do
var success = native_buffer_event.destroy
close_requested = true
fun event_callback(events: Int) do end
# Write a string to the connection
- fun write(str: String)
+ redef fun write(str)
do
native_buffer_event.write(str.to_cstring, str.length)
end
+ redef fun write_byte(byte) do native_buffer_event.write_byte(byte)
+
# Write a file to the connection
#
# require: `path.file_exists`
return bufferevent_write(recv, line, length);
`}
+ # Write the byte `value`
+ fun write_byte(value: Int): Int `{
+ unsigned char byt = (unsigned char)value;
+ return bufferevent_write(recv, &byt, 1);
+ `}
+
# Check if we have anything left in our buffers. If so, we set our connection to be closed
# on a callback. Otherwise we close it and free it right away.
fun destroy: Bool `{
import app::data_store
private import xdg_basedir
private import sqlite3
-private import json_serialization
+private import json::serialization
redef class App
redef var data_store = new LinuxStore
--- /dev/null
+# 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.
+
+# Decorators for `markdown` parsing.
+module decorators
+
+import markdown
+
+# `Decorator` that outputs markdown.
+class MdDecorator
+ super Decorator
+
+ redef var headlines = new ArrayMap[String, HeadLine]
+
+ redef fun add_ruler(v, block) do v.add "***\n"
+
+ redef fun add_headline(v, block) do
+ # save headline
+ var txt = block.block.first_line.value
+ var id = strip_id(txt)
+ var lvl = block.depth
+ headlines[id] = new HeadLine(id, txt, lvl)
+ v.add "{"#" * lvl} "
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_paragraph(v, block) do
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_code(v, block) do
+ if block isa BlockFence and block.meta != null then
+ v.add "~~~{block.meta.to_s}"
+ else
+ v.add "~~~"
+ end
+ v.addn
+ v.emit_in block
+ v.add "~~~"
+ v.addn
+ end
+
+ redef fun add_blockquote(v, block) do
+ v.add "> "
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_unorderedlist(v, block) do
+ in_unorderedlist = true
+ v.emit_in block
+ in_unorderedlist = false
+ end
+ private var in_unorderedlist = false
+
+ redef fun add_orderedlist(v, block) do
+ in_orderedlist = true
+ current_li = 0
+ v.emit_in block
+ in_unorderedlist = false
+ end
+ private var in_orderedlist = false
+ private var current_li = 0
+
+ redef fun add_listitem(v, block) do
+ if in_unorderedlist then
+ v.add "* "
+ else if in_orderedlist then
+ current_li += 1
+ v.add "{current_li} "
+ end
+ v.emit_in block
+ v.addn
+ end
+
+ redef fun add_em(v, text) do
+ v.add "*"
+ v.add text
+ v.add "*"
+ end
+
+ redef fun add_strong(v, text) do
+ v.add "**"
+ v.add text
+ v.add "**"
+ end
+
+ redef fun add_strike(v, text) do
+ v.add "~~"
+ v.add text
+ v.add "~~"
+ end
+
+ redef fun add_image(v, link, name, comment) do
+ v.add "!["
+ v.add name
+ v.add "]("
+ append_value(v, link)
+ if comment != null and not comment.is_empty then
+ v.add " "
+ append_value(v, comment)
+ end
+ v.add ")"
+ end
+
+ redef fun add_link(v, link, name, comment) do
+ v.add "["
+ v.add name
+ v.add "]("
+ append_value(v, link)
+ if comment != null and not comment.is_empty then
+ v.add " "
+ append_value(v, comment)
+ end
+ v.add ")"
+ end
+
+ redef fun add_abbr(v, name, comment) do
+ v.add "<abbr title=\""
+ append_value(v, comment)
+ v.add "\">"
+ v.emit_text(name)
+ v.add "</abbr>"
+ end
+
+ redef fun add_span_code(v, text, from, to) do
+ v.add "`"
+ append_code(v, text, from, to)
+ v.add "`"
+ end
+
+ redef fun add_line_break(v) do
+ v.add "\n"
+ end
+
+ redef fun append_value(v, text) do for c in text do escape_char(v, c)
+
+ redef fun escape_char(v, c) do v.addc(c)
+
+ redef fun append_code(v, buffer, from, to) do
+ for i in [from..to[ do
+ v.addc buffer[i]
+ end
+ end
+
+ redef fun strip_id(txt) do
+ # strip id
+ var b = new FlatBuffer
+ for c in txt do
+ if c == ' ' then
+ b.add '_'
+ else
+ if not c.is_letter and
+ not c.is_digit and
+ not allowed_id_chars.has(c) then continue
+ b.add c
+ end
+ end
+ var res = b.to_s
+ var key = res
+ # check for multiple id definitions
+ if headlines.has_key(key) then
+ var i = 1
+ key = "{res}_{i}"
+ while headlines.has_key(key) do
+ i += 1
+ key = "{res}_{i}"
+ end
+ end
+ return key
+ end
+
+ private var allowed_id_chars: Array[Char] = ['-', '_', ':', '.']
+end
+
+# Decorator for span elements.
+#
+# InlineDecorator does not decorate things like paragraphs or headers.
+class InlineDecorator
+ super HTMLDecorator
+
+ redef fun add_paragraph(v, block) do v.emit_in block
+ redef fun add_headline(v, block) do v.emit_in block
+
+ redef fun add_code(v, block) do
+ v.add "<code>"
+ v.emit_in block
+ v.add "</code>"
+ end
+end
# The emitter use a `Decorator` to select the output format.
class MarkdownEmitter
+ # Kind of processor used for parsing.
+ type PROCESSOR: MarkdownProcessor
+
# Processor containing link refs.
- var processor: MarkdownProcessor
+ var processor: PROCESSOR
+
+ # Kind of decorator used for decoration.
+ type DECORATOR: Decorator
# Decorator used for output.
# Default is `HTMLDecorator`
- var decorator: Decorator = new HTMLDecorator is writable
+ var decorator: DECORATOR is writable, lazy do
+ return new HTMLDecorator
+ end
# Create a new `MarkdownEmitter` using a custom `decorator`.
- init with_decorator(processor: MarkdownProcessor, decorator: Decorator) do
+ init with_decorator(processor: PROCESSOR, decorator: DECORATOR) do
init processor
self.decorator = decorator
end
fun emit_in(block: Block) do block.emit_in(self)
# Transform and emit mardown text
- fun emit_text(text: Text) do
- emit_text_until(text, 0, null)
- end
+ fun emit_text(text: Text) do emit_text_until(text, 0, null)
# Transform and emit mardown text starting at `from` and
# until a token with the same type as `token` is found.
end
# Append `c` to current buffer.
- fun addc(c: Char) do current_buffer.add c
+ fun addc(c: Char) do add c.to_s
# Append a "\n" line break.
- fun addn do current_buffer.add '\n'
+ fun addn do add "\n"
end
# A Link Reference.
# Default decorator used is `HTMLDecorator`.
interface Decorator
+ # Kind of emitter used for decoration.
+ type EMITTER: MarkdownEmitter
+
# Render a ruler block.
- fun add_ruler(v: MarkdownEmitter, block: BlockRuler) is abstract
+ fun add_ruler(v: EMITTER, block: BlockRuler) is abstract
# Render a headline block with corresponding level.
- fun add_headline(v: MarkdownEmitter, block: BlockHeadline) is abstract
+ fun add_headline(v: EMITTER, block: BlockHeadline) is abstract
# Render a paragraph block.
- fun add_paragraph(v: MarkdownEmitter, block: BlockParagraph) is abstract
+ fun add_paragraph(v: EMITTER, block: BlockParagraph) is abstract
# Render a code or fence block.
- fun add_code(v: MarkdownEmitter, block: BlockCode) is abstract
+ fun add_code(v: EMITTER, block: BlockCode) is abstract
# Render a blockquote.
- fun add_blockquote(v: MarkdownEmitter, block: BlockQuote) is abstract
+ fun add_blockquote(v: EMITTER, block: BlockQuote) is abstract
# Render an unordered list.
- fun add_unorderedlist(v: MarkdownEmitter, block: BlockUnorderedList) is abstract
+ fun add_unorderedlist(v: EMITTER, block: BlockUnorderedList) is abstract
# Render an ordered list.
- fun add_orderedlist(v: MarkdownEmitter, block: BlockOrderedList) is abstract
+ fun add_orderedlist(v: EMITTER, block: BlockOrderedList) is abstract
# Render a list item.
- fun add_listitem(v: MarkdownEmitter, block: BlockListItem) is abstract
+ fun add_listitem(v: EMITTER, block: BlockListItem) is abstract
# Render an emphasis text.
- fun add_em(v: MarkdownEmitter, text: Text) is abstract
+ fun add_em(v: EMITTER, text: Text) is abstract
# Render a strong text.
- fun add_strong(v: MarkdownEmitter, text: Text) is abstract
+ fun add_strong(v: EMITTER, text: Text) is abstract
# Render a strike text.
#
# Extended mode only (see `MarkdownProcessor::ext_mode`)
- fun add_strike(v: MarkdownEmitter, text: Text) is abstract
+ fun add_strike(v: EMITTER, text: Text) is abstract
# Render a link.
- fun add_link(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
+ fun add_link(v: EMITTER, link: Text, name: Text, comment: nullable Text) is abstract
# Render an image.
- fun add_image(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
+ fun add_image(v: EMITTER, link: Text, name: Text, comment: nullable Text) is abstract
# Render an abbreviation.
- fun add_abbr(v: MarkdownEmitter, name: Text, comment: Text) is abstract
+ fun add_abbr(v: EMITTER, name: Text, comment: Text) is abstract
# Render a code span reading from a buffer.
- fun add_span_code(v: MarkdownEmitter, buffer: Text, from, to: Int) is abstract
+ fun add_span_code(v: EMITTER, buffer: Text, from, to: Int) is abstract
# Render a text and escape it.
- fun append_value(v: MarkdownEmitter, value: Text) is abstract
+ fun append_value(v: EMITTER, value: Text) is abstract
# Render code text from buffer and escape it.
- fun append_code(v: MarkdownEmitter, buffer: Text, from, to: Int) is abstract
+ fun append_code(v: EMITTER, buffer: Text, from, to: Int) is abstract
# Render a character escape.
- fun escape_char(v: MarkdownEmitter, char: Char) is abstract
+ fun escape_char(v: EMITTER, char: Char) is abstract
# Render a line break
- fun add_line_break(v: MarkdownEmitter) is abstract
+ fun add_line_break(v: EMITTER) is abstract
# Generate a new html valid id from a `String`.
fun strip_id(txt: String): String is abstract
--- /dev/null
+# 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.
+
+# Wikilinks handling.
+#
+# Wikilinks are on the form `[[link]]`.
+# They can also contain a custom title with the syntax `[[title|link]]`.
+#
+# By importing this module, you enable the `MarkdownProcessor` to recognize
+# `TokenWikiLink` but nothing will happen until you define a
+# `Decorator::add_wikilink` customized to your applciation domain.
+module wikilinks
+
+intrude import markdown
+
+# `MarkdownProcessor` is now able to parse wikilinks.
+redef class MarkdownProcessor
+
+ redef fun token_at(text, pos) do
+ var token = super
+ if not token isa TokenLink then return token
+ if pos + 1 < text.length then
+ var c = text[pos + 1]
+ if c == '[' then return new TokenWikiLink(pos, c)
+ end
+ return token
+ end
+end
+
+redef class Decorator
+
+ # Renders a `[[wikilink]]` item.
+ fun add_wikilink(v: EMITTER, link: Text, name, comment: nullable Text) do
+ if name != null then
+ v.add "[[{name}|{link}]]"
+ else
+ v.add "[[{link}]]"
+ end
+ end
+end
+
+# A NitiWiki link token.
+#
+# Something of the form `[[foo]]`.
+#
+# Allowed formats:
+#
+# * `[[Wikilink]]`
+# * `[[Wikilink/Bar]]`
+# * `[[Wikilink#foo]]`
+# * `[[Wikilink/Bar#foo]]`
+# * `[[title|Wikilink]]`
+# * `[[title|Wikilink/Bar]]`
+# * `[[title|Wikilink/Bar#foo]]`
+class TokenWikiLink
+ super TokenLink
+
+ redef fun emit_hyper(v) do
+ v.decorator.add_wikilink(v, link.as(not null), name, comment)
+ end
+
+ redef fun check_link(v, out, start, token) do
+ var md = v.current_text
+ var pos = start + 2
+ var tmp = new FlatBuffer
+ pos = md.read_md_link_id(tmp, pos)
+ if pos < start then return -1
+ var name = tmp.write_to_string
+ if name.has("|") then
+ var parts = name.split_once_on("|")
+ self.name = parts.first
+ self.link = parts[1]
+ else
+ self.name = null
+ self.link = name
+ end
+ pos += 1
+ pos = md.skip_spaces(pos)
+ if pos < start then return -1
+ pos += 1
+ return pos
+ end
+end
# Highly specific, but useful, collections-related classes.
module more_collections
+import serialization
+
# Simple way to store an `HashMap[K, Array[V]]`
#
# Unlike standard HashMap, MultiHashMap provides a new
# assert m["four"] == ['i', 'i', 'i', 'i']
# assert m["zzz"] == new Array[Char]
class MultiHashMap[K, V]
+ auto_serializable
super HashMap[K, Array[V]]
# Add `v` to the array associated with `k`.
# assert hm2[2, "not-two"] == null
# ~~~~
class HashMap2[K1, K2, V]
+ auto_serializable
+
private var level1 = new HashMap[K1, HashMap[K2, V]]
# Return the value associated to the keys `k1` and `k2`.
# assert hm3[2, "not-two", 22] == null
# ~~~~
class HashMap3[K1, K2, K3, V]
+ auto_serializable
+
private var level1 = new HashMap[K1, HashMap2[K2, K3, V]]
# Return the value associated to the keys `k1`, `k2`, and `k3`.
# assert dma.default == [65]
# ~~~~
class DefaultMap[K, V]
+ auto_serializable
super HashMap[K, V]
# The default value.
import c
intrude import standard::string
import serialization
-private import json_serialization
+private import json::serialization
in "C Header" `{
#include <mpi.h>
# fill_buffer now checks for a message in the message queue which is filled by user inputs.
redef fun fill_buffer
do
- _buffer.clear
_buffer_pos = 0
- _buffer.append check_message.to_s
+ var nns = check_message
+ var nslen = nns.cstring_length
+ _buffer_length = nslen
+ nns.copy_to(buffer, nslen, 0, 0)
end
end
--- /dev/null
+# 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.
+
+# Advanced services for serialization engines
+module engine_tools
+
+import serialization
+
+# Maps instances to a value, uses `is_same_instance`
+#
+# Warning: This class does not implement all the services from `Map`.
+class StrictHashMap[K, V]
+ super Map[K, V]
+
+ # private
+ var map = new HashMap[K, Array[Couple[K, V]]]
+
+ redef var length = 0
+
+ redef fun is_empty do return length == 0
+
+ # private
+ fun node_at(key: K): nullable Couple[K, V]
+ do
+ if not map.keys.has(key) then return null
+
+ var arr = map[key]
+ for couple in arr do
+ if couple.first.is_same_serialized(key) then
+ return couple
+ end
+ end
+
+ return null
+ end
+
+ redef fun [](key)
+ do
+ var node = node_at(key)
+ assert node != null
+ return node.second
+ end
+
+ redef fun []=(key, value)
+ do
+ var node = node_at(key)
+ if node != null then
+ node.second = value
+ return
+ end
+
+ var arr
+ if not map.keys.has(key) then
+ arr = new Array[Couple[K, V]]
+ map[key] = arr
+ else arr = map[key]
+
+ arr.add new Couple[K, V](key, value)
+ self.length += 1
+ end
+
+ redef fun has_key(key) do return node_at(key) != null
+end
redef class String super DirectSerializable end
redef class SimpleCollection[E] super Serializable end
redef class Map[K, V] super Serializable end
+
+redef class Couple[F, S]
+ super Serializable
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+ var first = v.deserialize_attribute("first")
+ var second = v.deserialize_attribute("second")
+ init(first, second)
+ end
+
+ redef fun core_serialize_to(v)
+ do
+ v.serialize_attribute("first", first)
+ v.serialize_attribute("second", second)
+ end
+end
+
+redef class Container[E]
+ super Serializable
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+ var item = v.deserialize_attribute("item")
+ init item
+ end
+
+ redef fun core_serialize_to(v)
+ do
+ v.serialize_attribute("item", first)
+ end
+end
# Creates a socket connection to host `host` on port `port`
init connect(host: String, port: Int)
do
- _buffer = new FlatBuffer
+ _buffer = new NativeString(1024)
_buffer_pos = 0
socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
# Creates a client socket, this is meant to be used by accept only
private init server_side(h: SocketAcceptResult)
do
- _buffer = new FlatBuffer
+ _buffer = new NativeString(1024)
_buffer_pos = 0
socket = h.socket
addrin = h.addr_in
# timeout : Time in milliseconds before stopping to wait for events
fun ready_to_read(timeout: Int): Bool
do
- if _buffer_pos < _buffer.length then return true
+ if _buffer_pos < _buffer_length then return true
if end_reached then return false
var events = [new NativeSocketPollValues.pollin]
return pollin(events, timeout).length != 0
socket.write_byte value
end
+ redef fun write_bytes(s) do
+ if closed then return
+ socket.write(s.to_s)
+ end
+
fun write_ln(msg: Text)
do
if end_reached then return
redef fun fill_buffer
do
- _buffer.clear
+ _buffer_length = 0
_buffer_pos = 0
if not connected then return
var read = socket.read
close
end_reached = true
end
- _buffer.append(read)
+ enlarge(_buffer_capacity + read.length)
+ read.copy_to_native(_buffer, read.length, 0, 0)
+ _buffer_length = read.length
+ end
+
+ fun enlarge(len: Int) do
+ if _buffer_capacity >= len then return
+ while _buffer_capacity < len do _buffer_capacity *= 2
+ var ns = new NativeString(_buffer_capacity)
+ _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
+ _buffer = ns
end
redef fun close
# Write `value` as a single byte
fun write_byte(value: Int): Int `{
- return write(*recv, &value, 1);
+ unsigned char byt = (unsigned char)value;
+ return write(*recv, &byt, 1);
`}
- fun read: String import NativeString.to_s_with_length `{
+ fun read: String import NativeString.to_s_with_length, NativeString.to_s_with_copy `{
static char c[1024];
- int n = read(*recv, c, 1024);
+ int n = read(*recv, c, 1023);
if(n < 0) {
return NativeString_to_s_with_length("",0);
}
- char* ret = malloc(n + 1);
- memcpy(ret, c, n);
- ret[n] = '\0';
- return NativeString_to_s_with_length(ret, n);
+ c[n] = 0;
+ return NativeString_to_s_with_copy(c);
`}
# Sets an option for the socket
return
end
end_reached = false
- _buffer_pos = 0
- _buffer.clear
+ buffer_reset
end
redef fun close
do
super
- _buffer.clear
+ buffer_reset
end_reached = true
end
redef fun fill_buffer
do
- var nb = _file.io_read(_buffer.items, _buffer.capacity)
+ var nb = _file.io_read(_buffer, _buffer_capacity)
if nb <= 0 then
end_reached = true
nb = 0
end
- _buffer.length = nb
+ _buffer_length = nb
_buffer_pos = 0
end
super FileStream
super Writer
+ redef fun write_bytes(s) do
+ if last_error != null then return
+ if not _is_writable then
+ last_error = new IOError("cannot write to non-writable stream")
+ return
+ end
+ write_native(s.items, s.length)
+ end
+
redef fun write(s)
do
if last_error != null then return
if not _is_writable then
- last_error = new IOError("Cannot write to non-writable stream")
+ last_error = new IOError("cannot write to non-writable stream")
return
end
- if s isa FlatText then
- write_native(s.to_cstring, s.length)
- else
- for i in s.substrings do write_native(i.to_cstring, i.length)
- end
+ for i in s.substrings do write_native(i.to_cstring, i.length)
end
redef fun write_byte(value)
# ~~~
#
# See `Reader::read_all` for details.
- fun read_all: String
+ fun read_all: String do return read_all_bytes.to_s
+
+ fun read_all_bytes: Bytes
do
var s = open_ro
- var res = s.read_all
+ var res = s.read_all_bytes
s.close
return res
end
# assert files.is_empty
#
# TODO find a better way to handle errors and to give them back to the user.
- fun files: Array[String] is extern import Array[String], Array[String].add, NativeString.to_s, String.to_cstring `{
- char *dir_path;
- DIR *dir;
-
- dir_path = String_to_cstring( recv );
- if ((dir = opendir(dir_path)) == NULL)
- {
- //perror( dir_path );
- //exit( 1 );
- Array_of_String results;
- results = new_Array_of_String();
- return results;
- }
- else
- {
- Array_of_String results;
- String file_name;
- struct dirent *de;
-
- results = new_Array_of_String();
-
- while ( ( de = readdir( dir ) ) != NULL )
- if ( strcmp( de->d_name, ".." ) != 0 &&
- strcmp( de->d_name, "." ) != 0 )
- {
- file_name = NativeString_to_s( strdup( de->d_name ) );
- Array_of_String_add( results, file_name );
- }
+ fun files: Array[String]
+ do
+ var res = new Array[String]
+ var d = new NativeDir.opendir(to_cstring)
+ if d.address_is_null then return res
+
+ loop
+ var de = d.readdir
+ if de.address_is_null then break
+ var name = de.to_s_with_copy
+ if name == "." or name == ".." then continue
+ res.add name
+ end
+ d.closedir
- closedir( dir );
- return results;
- }
- `}
+ return res
+ end
end
redef class NativeString
new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
end
+# Standard `DIR*` pointer
+private extern class NativeDir `{ DIR* `}
+
+ # Open a directory
+ new opendir(path: NativeString) `{ return opendir(path); `}
+
+ # Close a directory
+ fun closedir `{ closedir(recv); `}
+
+ # Read the next directory entry
+ fun readdir: NativeString `{
+ struct dirent *de;
+ de = readdir(recv);
+ if (!de) return NULL;
+ return de->d_name;
+ `}
+end
+
redef class Sys
# Standard input
intrude import ropes
import error
+intrude import bytes
in "C" `{
#include <unistd.h>
# Reads a byte. Returns `null` on EOF or timeout
fun read_byte: nullable Int is abstract
+ # Reads a String of at most `i` length
+ fun read(i: Int): String do return read_bytes(i).to_s
+
# Read at most i bytes
- fun read(i: Int): String
+ fun read_bytes(i: Int): Bytes
do
- if last_error != null then return ""
- var s = new FlatBuffer.with_capacity(i)
+ if last_error != null then return new Bytes.empty
+ var s = new NativeString(i)
+ var buf = new Bytes(s, 0, 0)
while i > 0 and not eof do
- var c = read_char
+ var c = read_byte
if c != null then
- s.add(c)
+ buf.add c
i -= 1
end
end
- return s.to_s
+ return buf
end
# Read a string until the end of the line.
# Read all the stream until the eof.
#
- # The content of the file is returned verbatim.
+ # The content of the file is returned as a String.
#
# ~~~
# var txt = "Hello\n\nWorld\n"
# var i = new StringReader(txt)
# assert i.read_all == txt
# ~~~
- fun read_all: String
+ fun read_all: String do return read_all_bytes.to_s
+
+ # Read all the stream until the eof.
+ #
+ # The content of the file is returned verbatim.
+ fun read_all_bytes: Bytes
do
- if last_error != null then return ""
- var s = new FlatBuffer
+ if last_error != null then return new Bytes.empty
+ var s = new Bytes.empty
while not eof do
- var c = read_char
+ var c = read_byte
if c != null then s.add(c)
end
- return s.to_s
+ return s
end
# Read a string until the end of the line and append it to `s`.
# A `Stream` that can be written to
abstract class Writer
super Stream
+
+ # Writes bytes from `s`
+ fun write_bytes(s: Bytes) is abstract
+
# write a string
fun write(s: Text) is abstract
# Like `write_to` but return a new String (may be quite large)
#
- # This funtionnality is anectodical, since the point
+ # This funtionality is anectodical, since the point
# of streamable object to to be efficienlty written to a
# stream without having to allocate and concatenate strings
fun write_to_string: String
return c
end
- # Peeks up to `n` bytes in the buffer, returns an empty string on EOF
+ fun buffer_reset do
+ _buffer_length = 0
+ _buffer_pos = 0
+ end
+
+ # Peeks up to `n` bytes in the buffer
#
# The operation does not consume the buffer
#
# ~~~nitish
- # var x = new FileReader("File.txt")
- # assert x.peek(5) == x.read(5)
+ # var x = new FileReader.open("File.txt")
+ # assert x.peek(5) == x.read(5)
# ~~~
- fun peek(i: Int): String do
- if eof then return ""
- var b = new FlatBuffer.with_capacity(i)
- while i > 0 and not eof do
- b.add _buffer[_buffer_pos]
- _buffer_pos += 1
- i -= 1
+ fun peek(i: Int): Bytes do
+ if eof then return new Bytes.empty
+ var remsp = _buffer_length - _buffer_pos
+ if i <= remsp then
+ var bf = new Bytes.with_capacity(i)
+ bf.append_ns_from(_buffer, i, _buffer_pos)
+ return bf
end
- var nbuflen = b.length + (_buffer.length - _buffer_pos)
- var nbuf = new FlatBuffer.with_capacity(nbuflen)
- nbuf.append(b)
- while _buffer_pos < _buffer.length do
- nbuf.add(_buffer[_buffer_pos])
- _buffer_pos += 1
+ var bf = new Bytes.with_capacity(i)
+ bf.append_ns_from(_buffer, remsp, _buffer_pos)
+ _buffer_pos = _buffer_length
+ read_intern(i - bf.length, bf)
+ remsp = _buffer_length - _buffer_pos
+ var full_len = bf.length + remsp
+ if full_len > _buffer_capacity then
+ var c = _buffer_capacity
+ while c < full_len do c = c * 2 + 2
+ _buffer_capacity = c
end
+ var nns = new NativeString(_buffer_capacity)
+ bf.items.copy_to(nns, bf.length, 0, 0)
+ _buffer.copy_to(nns, remsp, _buffer_pos, bf.length)
+ _buffer = nns
_buffer_pos = 0
- _buffer = nbuf
- return b.to_s
+ _buffer_length = full_len
+ return bf
end
- redef fun read(i)
+ redef fun read_bytes(i)
do
- if last_error != null then return ""
- if eof then return ""
+ if last_error != null then return new Bytes.empty
+ var buf = new Bytes.with_capacity(i)
+ read_intern(i, buf)
+ return buf
+ end
+
+ # Fills `buf` with at most `i` bytes read from `self`
+ private fun read_intern(i: Int, buf: Bytes): Int do
+ if eof then return 0
var p = _buffer_pos
- var bufsp = _buffer.length - p
+ var bufsp = _buffer_length - p
if bufsp >= i then
_buffer_pos += i
- return _buffer.substring(p, i).to_s
+ buf.append_ns_from(_buffer, i, p)
+ return i
end
- _buffer_pos = _buffer.length
- var readln = _buffer.length - p
- var s = _buffer.substring(p, readln).to_s
- fill_buffer
- return s + read(i - readln)
+ _buffer_pos = _buffer_length
+ var readln = _buffer_length - p
+ buf.append_ns_from(_buffer, readln, p)
+ var rd = read_intern(i - readln, buf)
+ return rd + readln
end
- redef fun read_all
+ redef fun read_all_bytes
do
- if last_error != null then return ""
- var s = new FlatBuffer
+ if last_error != null then return new Bytes.empty
+ var s = new Bytes.with_capacity(10)
while not eof do
var j = _buffer_pos
- var k = _buffer.length
+ var k = _buffer_length
while j < k do
- s.add(_buffer[j])
+ s.add(_buffer[j].ascii)
j += 1
end
_buffer_pos = j
fill_buffer
end
- return s.to_s
+ return s
end
redef fun append_line_to(s)
loop
# First phase: look for a '\n'
var i = _buffer_pos
- while i < _buffer.length and _buffer[i] != '\n' do i += 1
+ while i < _buffer_length and _buffer[i] != '\n' do
+ i += 1
+ end
var eol
- if i < _buffer.length then
+ if i < _buffer_length then
assert _buffer[i] == '\n'
i += 1
eol = true
redef fun eof
do
- if _buffer_pos < _buffer.length then return false
+ if _buffer_pos < _buffer_length then return false
if end_reached then return true
fill_buffer
- return _buffer_pos >= _buffer.length and end_reached
+ return _buffer_pos >= _buffer_length and end_reached
end
# The buffer
- private var buffer: nullable FlatBuffer = null
+ private var buffer: NativeString = new NativeString(0)
# The current position in the buffer
- private var buffer_pos: Int = 0
+ private var buffer_pos = 0
+
+ # Length of the current buffer (i.e. nuber of bytes in the buffer)
+ private var buffer_length = 0
+
+ # Capacity of the buffer
+ private var buffer_capacity = 0
# Fill the buffer
protected fun fill_buffer is abstract
- # Is the last fill_buffer reach the end
+ # Has the last fill_buffer reached the end
protected fun end_reached: Bool is abstract
# Allocate a `_buffer` for a given `capacity`.
protected fun prepare_buffer(capacity: Int)
do
- _buffer = new FlatBuffer.with_capacity(capacity)
+ _buffer = new NativeString(capacity)
_buffer_pos = 0 # need to read
+ _buffer_length = 0
+ _buffer_capacity = capacity
end
end
private var content = new Array[String]
redef fun to_s do return content.to_s
redef fun is_writable do return not closed
+
+ redef fun write_bytes(b) do
+ content.add(b.to_s)
+ end
+
redef fun write(str)
do
assert not closed
source = ""
end
- redef fun read_all do
- var c = cursor
- cursor = source.length
- if c == 0 then return source
- return source.substring_from(c)
+ redef fun read_all_bytes do
+ var nslen = source.length - cursor
+ var nns = new NativeString(nslen)
+ source.copy_to_native(nns, nslen, cursor, 0)
+ return new Bytes(nns, nslen, nslen)
end
redef fun eof do return cursor >= source.length
import base64
intrude import standard::stream
+intrude import standard::bytes
# Websocket compatible listener
#
super TCPStream
init do
- _buffer = new FlatBuffer
+ _buffer = new NativeString(1024)
_buffer_pos = 0
+ _buffer_capacity = 1024
+ _buffer_length = 0
var headers = parse_handshake
var resp = handshake_response(headers)
end
# Frames a text message to be sent to a client
- private fun frame_message(msg: String): String
+ private fun frame_message(msg: String): Bytes
do
- var ans_buffer = new FlatBuffer
+ var ans_buffer = new Bytes.with_capacity(msg.length)
# Flag for final frame set to 1
# opcode set to 1 (for text)
- ans_buffer.add(129.ascii)
+ ans_buffer.add(129)
if msg.length < 126 then
- ans_buffer.add(msg.length.ascii)
+ ans_buffer.add(msg.length)
end
if msg.length >= 126 and msg.length <= 65535 then
- ans_buffer.add(126.ascii)
- ans_buffer.add(msg.length.rshift(8).ascii)
- ans_buffer.add(msg.length.ascii)
+ ans_buffer.add(126)
+ ans_buffer.add(msg.length.rshift(8))
+ ans_buffer.add(msg.length)
end
- ans_buffer.append(msg)
- return ans_buffer.to_s
+ if msg isa FlatString then
+ ans_buffer.append_ns_from(msg.items, msg.length, msg.index_from)
+ else
+ for i in msg.substrings do
+ ans_buffer.append_ns_from(i.as(FlatString).items, i.length, i.as(FlatString).index_from)
+ end
+ end
+ return ans_buffer
end
# Reads an HTTP frame
# Gets the message from the client, unpads it and reconstitutes the message
private fun unpad_message do
var fin = false
+ var bf = new Bytes.empty
while not fin do
var fst_byte = client.read_byte
var snd_byte = client.read_byte
if fin_flag != 0 then fin = true
var opcode = fst_byte.bin_and(15)
if opcode == 9 then
- _buffer.add(138.ascii)
- _buffer.add('\0')
- client.write(_buffer.to_s)
- _buffer_pos += 2
+ bf.add(138)
+ bf.add(0)
+ client.write(bf.to_s)
+ _buffer_pos = _buffer_length
return
end
if opcode == 8 then
var len = snd_byte.bin_and(127)
var payload_ext_len = 0
if len == 126 then
- var tmp = client.read(2)
+ var tmp = client.read_bytes(2)
if tmp.length != 2 then
last_error = new IOError("Error: received interrupted frame")
client.close
return
end
- payload_ext_len = tmp[1].ascii + tmp[0].ascii.lshift(8)
+ payload_ext_len = tmp[1] + tmp[0].lshift(8)
else if len == 127 then
# 64 bits for length are not supported,
# only the last 32 will be interpreted as a Nit Integer
- var tmp = client.read(8)
+ var tmp = client.read_bytes(8)
if tmp.length != 8 then
last_error = new IOError("Error: received interrupted frame")
client.close
return
end
for pos in [0 .. tmp.length[ do
- var i = tmp[pos].ascii
+ var i = tmp[pos]
payload_ext_len += i.lshift(8 * (7 - pos))
end
end
if mask_flag != 0 then
+ var mask = client.read_bytes(4).items
if payload_ext_len != 0 then
- var msg = client.read(payload_ext_len+4)
- var mask = msg.substring(0,4)
- _buffer.append(unmask_message(mask, msg.substring(4, payload_ext_len)))
- else
- if len == 0 then
- return
- end
- var msg = client.read(len+4)
- var mask = msg.substring(0,4)
- _buffer.append(unmask_message(mask, msg.substring(4, len)))
+ len = payload_ext_len
end
+ var msg = client.read_bytes(len).items
+ bf.append_ns(unmask_message(mask, msg, len), len)
end
end
+ _buffer = bf.items
+ _buffer_length = bf.length
end
# Unmasks a message sent by a client
- private fun unmask_message(key: String, message: String): String
+ private fun unmask_message(key: NativeString, message: NativeString, len: Int): NativeString
do
- var return_message = new FlatBuffer.with_capacity(message.length)
- var msg_iter = message.chars.iterator
+ var return_message = new NativeString(len)
- while msg_iter.is_ok do
- return_message.chars[msg_iter.index] = msg_iter.item.ascii.bin_xor(key.chars[msg_iter.index%4].ascii).ascii
- msg_iter.next
+ for i in [0 .. len[ do
+ return_message[i] = message[i].ascii.bin_xor(key[i%4].ascii).ascii
end
- return return_message.to_s
+ return return_message
end
# Checks if a connection to a client is available
redef fun connected do return client.connected
- redef fun write(msg)
- do
- client.write(frame_message(msg.to_s))
- end
+ redef fun write_bytes(s) do client.write_bytes(frame_message(s.to_s))
+
+ redef fun write(msg) do client.write(frame_message(msg.to_s).to_s)
redef fun is_writable do return client.connected
redef fun fill_buffer
do
- _buffer.clear
- _buffer_pos = 0
+ buffer_reset
unpad_message
end
- redef fun end_reached do return client._buffer_pos >= client._buffer.length and client.end_reached
+ redef fun end_reached do return client._buffer_pos >= client._buffer_length and client.end_reached
# Is there some data available to be read ?
fun can_read(timeout: Int): Bool do return client.ready_to_read(timeout)
# DESCRIPTION
-`nitx` in an interactive tool that display information about programs and libraries.
+`nitx` in an interactive tool that displays informations about programs and libraries.
-A command that query some information can be given as and argument.
+A command that query some information can be given as parameter.
This will immediately displays the information then terminates the programs.
If no command are given, the program starts an interactive session where commands are entered until `:q` is given.
`new: Type`
: lookup methods creating new instances of 'Type'.
+`call: Property`
+: lookup calls to 'Property'.
+
+`doc: name`
+: lookup documentation pages about 'name'.
+
+`code: name`
+: lookup source code related to 'name'.
+
`:h`
: display an help message about the commands.
# OPTIONS
-Only common options of the Nit tools are understood.
+`-q`
+: execute a query, display results in console then quit.
# SEE ALSO
do
var ret_type = mmodule.native_array_type(elttype)
ret_type = anchor(ret_type).as(MClassType)
+ length = autobox(length, compiler.mainmodule.int_type)
return self.new_expr("NEW_{ret_type.c_name}({length})", ret_type)
end
self.require_declaration("NEW_{mtype.mclass.c_name}")
assert mtype isa MGenericType
var compiler = self.compiler
+ length = autobox(length, compiler.mainmodule.int_type)
if mtype.need_anchor then
hardening_live_open_type(mtype)
link_unresolved_type(self.frame.mpropdef.mclassdef, mtype)
#
# See module `console`.
fun cs_visibility_color(string: String): String do return string.green
+
+ # Source code associated to this MEntity.
+ #
+ # Uses `cs_location` to locate the source code.
+ fun cs_source_code: String do
+ # FIXME up location to mentity
+ var loc = new Location.from_string(cs_location)
+ var fr = new FileReader.open(loc.file.filename)
+ var content = new FlatBuffer
+ var i = 0
+ while not fr.eof do
+ i += 1
+ var line = fr.read_line
+ if i < loc.line_start or i > loc.line_end then continue
+ # FIXME add nitlight for console
+ content.append "{line}\n"
+ end
+ fr.close
+ return content.write_to_string
+ end
end
redef class MProject
fun help do
print "\nCommands:"
print "\tname\t\tlookup module, class and property with the corresponding 'name'"
+ print "\tdoc: <name::space>\tdisplay the documentation page of 'namespace'"
+ print "\tparam: <Type>\tlookup methods using the corresponding 'Type' as parameter"
+ print "\treturn: <Type>\tlookup methods returning the corresponding 'Type'"
+ print "\tnew: <Type>\tlookup methods creating new instances of 'Type'"
+ print "\tcall: <name>\tlookup methods calling 'name'"
+ print "\tcode: <name>\tdisplay the source code associated to the 'name' entity"
print "\t:h\t\tdisplay this help message"
print "\t:q\t\tquit interactive mode"
print ""
return new NitxHelp
else if query_string.has_prefix("comment:") then
return new CommentQuery(query_string)
+ else if query_string.has_prefix("doc:") then
+ return new DocQuery(query_string)
+ else if query_string.has_prefix("param:") then
+ return new ParamQuery(query_string)
+ else if query_string.has_prefix("return:") then
+ return new ReturnQuery(query_string)
+ else if query_string.has_prefix("new:") then
+ return new NewQuery(query_string)
+ else if query_string.has_prefix("call:") then
+ return new CallQuery(query_string)
+ else if query_string.has_prefix("code:") then
+ return new CodeQuery(query_string)
+
end
return new CommentQuery("comment: {query_string}")
end
end
end
+# A query to search signatures using a specific `MType` as parameter.
+class ParamQuery
+ super MetaQuery
+
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var mtype_name = args.first
+ for mproperty in doc.mproperties do
+ if not mproperty isa MMethod then continue
+ var msignature = mproperty.intro.msignature
+ if msignature != null then
+ for mparam in msignature.mparameters do
+ if mparam.mtype.name == mtype_name then
+ res.add new MEntityMatch(self, mproperty)
+ end
+ end
+ end
+ end
+ return res
+ end
+end
+
+# A query to search signatures using a specific `MType` as return.
+class ReturnQuery
+ super MetaQuery
+
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var mtype_name = args.first
+ for mproperty in doc.mproperties do
+ if not mproperty isa MMethod then continue
+ var msignature = mproperty.intro.msignature
+ if msignature != null then
+ var mreturn = msignature.return_mtype
+ if mreturn != null and mreturn.name == mtype_name then
+ res.add new MEntityMatch(self, mproperty)
+ end
+ end
+ end
+ return res
+ end
+end
+
+# A query to search methods creating new instances of a specific `MType`.
+class NewQuery
+ super MetaQuery
+
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var mtype_name = args.first
+ for mpropdef in doc.mpropdefs do
+ var visitor = new TypeInitVisitor(mtype_name)
+ var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
+ if npropdef == null then continue
+ visitor.enter_visit(npropdef)
+ for i in visitor.inits do
+ res.add new MEntityMatch(self, mpropdef)
+ end
+ end
+ return res
+ end
+end
+
+# A query to search methods calling a specific `MProperty`.
+class CallQuery
+ super MetaQuery
+
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var mprop_name = args.first
+ for mpropdef in doc.mpropdefs do
+ var visitor = new MPropertyCallVisitor
+ var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
+ if npropdef == null then continue
+ visitor.enter_visit(npropdef)
+ for mprop in visitor.calls do
+ if mprop.name != mprop_name then continue
+ res.add new MEntityMatch(self, mpropdef)
+ end
+ end
+ return res
+ end
+end
+
+# A query to search a Nitdoc documentation page by its name.
+class DocQuery
+ super MetaQuery
+
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var name = args.first
+ for page in doc.pages do
+ if name == "*" then # FIXME dev only
+ res.add new PageMatch(self, page)
+ else if page.title == name then
+ res.add new PageMatch(self, page)
+ else if page isa MEntityPage and page.mentity.cs_namespace == name then
+ res.add new PageMatch(self, page)
+ end
+ end
+ return res
+ end
+
+ redef fun make_results(nitx, results) do
+ var len = results.length
+ # FIXME how to render the pager for one worded namespaces like "standard"?
+ if len == 1 then
+ var page = results.first.as(PageMatch).page
+ var pager = new Pager
+ pager.add page.write_to_string
+ pager.render
+ return page
+ else
+ return super
+ end
+ end
+end
+
+# A match between a `DocPage` and a `MEntity`.
+class PageMatch
+ super NitxMatch
+
+ # `DocPage` matched.
+ var page: DocPage
+
+ redef fun make_list_item do
+ var page = self.page
+ if page isa MEntityPage then
+ return page.mentity.cs_list_item
+ end
+ return " * {page.title}"
+ end
+end
+
+# A query to search source code from a file name.
+class CodeQuery
+ super MetaQuery
+
+ # FIXME refactor this!
+ redef fun perform(nitx, doc) do
+ var res = new Array[NitxMatch]
+ var name = args.first
+ # if name is an existing sourcefile, opens it
+ if name.file_exists then
+ var fr = new FileReader.open(name)
+ var content = fr.read_all
+ fr.close
+ res.add new CodeMatch(self, name, content)
+ return res
+ end
+ # else, lookup the model by name
+ for mentity in doc.search_mentities(name) do
+ if mentity isa MClass then continue
+ if mentity isa MProperty then continue
+ res.add new CodeMatch(self, mentity.cs_location, mentity.cs_source_code)
+ end
+ return res
+ end
+
+ redef fun make_results(nitx, results) do
+ var page = new DocPage("Code Results")
+ for res in results do
+ page.add new CodeQueryArticle(self, res.as(CodeMatch))
+ end
+ return page
+ end
+end
+
+# A match between a piece of code and a string.
+class CodeMatch
+ super NitxMatch
+
+ # Location of the code match.
+ var location: String
+
+ # Piece of code matched.
+ var content: String
+
+ redef fun make_list_item do return "* {location}"
+end
+
+
# A query that contains a nitx command.
#
# These commands are prefixed with `:` and are used to control the execution of
end
end
+# Visitor looking for initialized `MType` (new T).
+#
+# See `NewQuery`.
+private class TypeInitVisitor
+ super Visitor
+
+ # `MType` name to look for.
+ var mtype_name: String
+
+ var inits = new HashSet[MType]
+ redef fun visit(node)
+ do
+ node.visit_all(self)
+ # look for init
+ if not node isa ANewExpr then return
+ var mtype = node.n_type.mtype
+ if mtype != null and mtype.name == mtype_name then inits.add(mtype)
+ end
+end
+
+# Visitor looking for calls to a `MProperty` (new T).
+#
+# See `CallQuery`.
+private class MPropertyCallVisitor
+ super Visitor
+
+ var calls = new HashSet[MProperty]
+ redef fun visit(node)
+ do
+ node.visit_all(self)
+ if not node isa ASendExpr then return
+ calls.add node.callsite.mproperty
+ end
+end
+
# display
# A `DocArticle` that displays query results.
end
end
+# An article that displays a piece of code.
+private class CodeQueryArticle
+ super DocArticle
+
+ # The query linked to the result to display.
+ var query: NitxQuery
+
+ # The result to display.
+ var result: CodeMatch
+
+ redef fun render_body do
+ addn ""
+ addn "in {result.location}".gray.bold
+ addn ""
+ add result.content
+ end
+end
+
# A Pager is used to display data into a unix `less` container.
private class Pager
module debugger
intrude import naive_interpreter
-import nitx
intrude import semantize::local_var_init
intrude import semantize::scope
intrude import toolcontext
else if command == "help" then
help
return true
- # Opens a new NitIndex prompt on current model
- else if command == "nitx" then
- new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt
- return true
else if command == "bt" or command == "backtrack" then
print stack_trace
return true
var total_sends: Int = 0
var nullable_sends: Int = 0
+ var nullable_eq_sends: Int = 0
var buggy_sends: Int = 0
# Get a new visitor on a classef to add type count in `typecount`.
end
t = t.anchor_to(self.nclassdef.mclassdef.mmodule, self.nclassdef.mclassdef.bound_mtype)
if t isa MNullableType then
- self.nullable_sends += 1
+ var name = n.callsite.mproperty.name
+ if name == "==" or name == "!=" or name == "is_same_instance" then
+ self.nullable_eq_sends += 1
+ else
+ self.nullable_sends += 1
+ end
else if t isa MClassType then
# Nothing
else
print "--- Sends on Nullable Receiver ---"
var total_sends = 0
var nullable_sends = 0
+ var nullable_eq_sends = 0
var buggy_sends = 0
# Visit all the source code to collect data
visitor.enter_visit(nclassdef)
total_sends += visitor.total_sends
nullable_sends += visitor.nullable_sends
+ nullable_eq_sends += visitor.nullable_eq_sends
buggy_sends += visitor.buggy_sends
end
end
print "Total number of sends: {total_sends}"
- print "Number of sends on a nullable receiver: {nullable_sends} ({div(nullable_sends*100,total_sends)}%)"
+ print "Number of sends on a unsafe nullable receiver: {nullable_sends} ({div(nullable_sends*100,total_sends)}%)"
+ print "Number of sends on a safe nullable receiver: {nullable_eq_sends} ({div(nullable_eq_sends*100,total_sends)}%)"
print "Number of buggy sends (cannot determine the type of the receiver): {buggy_sends} ({div(buggy_sends*100,total_sends)}%)"
end
redef fun c_name do return "null"
redef fun as_nullable do return self
- # Aborts on `null`
- redef fun as_notnull do abort # sorry...
+ redef var as_notnull = new MBottomType(model) is lazy
+ redef fun need_anchor do return false
+ redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual) do return self
+ redef fun can_resolve_for(mtype, anchor, mmodule) do return true
+
+ redef fun collect_mclassdefs(mmodule) do return new HashSet[MClassDef]
+
+ redef fun collect_mclasses(mmodule) do return new HashSet[MClass]
+
+ redef fun collect_mtypes(mmodule) do return new HashSet[MClassType]
+end
+
+# The special universal most specific type.
+#
+# This type is intended to be only used internally for type computation or analysis and should not be exposed to the user.
+# The bottom type can de used to denote things that are absurd, dead, or the absence of knowledge.
+#
+# Semantically it is the singleton `null.as_notnull`.
+class MBottomType
+ super MType
+ redef var model: Model
+ redef fun to_s do return "bottom"
+ redef fun full_name do return "bottom"
+ redef fun c_name do return "bottom"
+ redef fun as_nullable do return model.null_type
+ redef fun as_notnull do return self
redef fun need_anchor do return false
redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual) do return self
redef fun can_resolve_for(mtype, anchor, mmodule) do return true
# See the License for the specific language governing permissions and
# limitations under the License.
-# nit index, is a command tool used to display documentation
+# `nitx`, is a command tool that displays useful informations about the code.
+#
+# Features:
+#
+# * Display comment from name/namespace
+# * Display documentation page from Nitdoc in console
+# * Find type usage in parameters, returns and news.
+# * Find usage of a specific property.
+# * Find source code related to class/property by its name.
module nitx
-import model_utils
-import modelize
-
-# Main class of the nit index tool
-# NitIndex build the model using the toolcontext argument
-# then wait for query on std in to display documentation
-class NitIndex
- private var toolcontext: ToolContext
- private var model: Model is noinit
- private var mbuilder: ModelBuilder is noinit
- private var mainmodule: MModule is noinit
- private var arguments: Array[String] is noinit
- private var renderer: PagerMatchesRenderer is noinit
-
- # New constructor to use the pre-calculated model when interpreting a module
- init with_infos(mbuilder: ModelBuilder, mmodule: MModule) do
-
- self.model = mbuilder.model
- self.mbuilder = mbuilder
-
- self.mainmodule = mmodule
- self.toolcontext = mbuilder.toolcontext
- self.arguments = toolcontext.option_context.rest
-
- renderer = new PagerMatchesRenderer(self)
- end
-
- init do
- # We need a model to collect stufs
- self.arguments = toolcontext.option_context.rest
-
- if arguments.length > 2 then
- print toolcontext.tooldescription
- exit(1)
- end
-
- model = new Model
- mbuilder = new ModelBuilder(model, toolcontext)
-
- var mmodules = mbuilder.parse([arguments.first])
- if mmodules.is_empty then return
- mbuilder.run_phases
- assert mmodules.length == 1
- self.mainmodule = mmodules.first
-
- renderer = new PagerMatchesRenderer(self)
- end
-
- fun start do
- if arguments.length == 1 then
- welcome
- prompt
- else
- search(arguments[1])
- end
- end
-
- fun welcome do
- print "Welcome in the Nit Index."
- print ""
- print "Loaded modules:"
- var mmodules = new Array[MModule]
- mmodules.add_all(model.mmodules)
- var sorter = new MEntityNameSorter
- sorter.sort(mmodules)
- for m in mmodules do
- print "\t{m.name}"
- end
- print ""
- help
- end
-
- fun help do
- print "\nCommands:"
- print "\tname\t\tlookup module, class and property with the corresponding 'name'"
- print "\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
- print "\treturn: Type\tlookup methods returning the corresponding 'Type'"
- print "\tnew: Type\tlookup methods creating new instances of 'Type'"
- print "\t:h\t\tdisplay this help message"
- print "\t:q\t\texit"
- print ""
- end
-
- fun prompt do
- printn ">> "
- search(sys.stdin.read_line)
- end
+import modelbuilder
+import doc::doc_phases::doc_console
- fun search(entry: String) do
- if entry.is_empty then
- prompt
- return
- end
- if entry == ":h" then
- help
- prompt
- return
- end
- if entry == ":q" then return
-
- # Parse query string
- var query = parse_query(entry)
-
- # search in index
- var matches = new HashSet[IndexMatch]
- if query isa IndexQueryPair then
- if query.category == "return" then
- # seek return types
- matches.add_all(search_returns(query))
- else if query.category == "param" then
- # seek param types
- matches.add_all(search_params(query))
- else if query.category == "new" then
- # seek type inits
- matches.add_all(search_inits(query))
- end
- else
- matches.add_all(search_modules(query))
- matches.add_all(search_classes(query))
- matches.add_all(search_properties(query))
- end
- # no matches
- if matches.is_empty then
- print "Nothing known about '{query.string}', type ':h' for help"
- else
- renderer.render_matches(query, matches)
- end
- if arguments.length == 1 then prompt
- end
-
- private fun parse_query(str: String): IndexQuery do
- var parts = str.split_with(":")
- if parts.length == 1 then
- return new IndexQuery(str, parts[0])
- else
- var category = parts[0]
- var keyword = parts[1]
- if keyword.chars.first == ' ' then keyword = keyword.substring_from(1)
- return new IndexQueryPair(str, keyword, category)
- end
- end
-
- # search for modules
- private fun search_modules(query: IndexQuery): Set[MModule] do
- var matches = new HashSet[MModule]
- for mmodule in model.mmodules do
- if mmodule.name == query.keyword then matches.add(mmodule)
- end
- return matches
- end
-
- # search for classes
- private fun search_classes(query: IndexQuery): Set[MClass] do
- var matches = new HashSet[MClass]
- for mclass in model.mclasses do
- if mclass.name == query.keyword then matches.add(mclass)
- end
- return matches
- end
-
- # search for properties
- private fun search_properties(query: IndexQuery): Set[MProperty] do
- var matches = new HashSet[MProperty]
- for mproperty in model.mproperties do
- if mproperty.name == query.keyword then matches.add(mproperty)
- end
- return matches
- end
-
- # search for mpropdef returning keyword
- private fun search_returns(query: IndexQuery): Set[MProperty] do
- var matches = new HashSet[MProperty]
- for mproperty in model.mproperties do
- var intro = mproperty.intro
- if intro isa MMethodDef then
- if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
- else if intro isa MAttributeDef then
- if intro.static_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
- end
- end
- return matches
- end
-
- # search for mpropdef taking keyword as parameter
- private fun search_params(query: IndexQuery): Set[MProperty] do
- var matches = new HashSet[MProperty]
- for mproperty in model.mproperties do
- var intro = mproperty.intro
- if intro isa MMethodDef then
- var mparameters = intro.msignature.mparameters
- for mparameter in mparameters do
- if mparameter.mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
- end
- else if intro isa MAttributeDef then
- if intro.static_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
- end
- end
- return matches
- end
-
- # search for mpropdef creating new instance of keyword
- private fun search_inits(query: IndexQuery): Set[MPropDef] do
- var mtype2mpropdefs = toolcontext.nitx_phase.mtype2mpropdefs
- var matches = new HashSet[MPropDef]
- for mtype in mtype2mpropdefs.keys do
- if mtype.to_console.has_prefix(query.keyword) then
- for mpropdef in mtype2mpropdefs[mtype] do
- matches.add(mpropdef)
- end
- end
- end
- return matches
- end
-end
-
-private class IndexQuery
- var string: String
- var keyword: String
-end
-
-private class IndexQueryPair
- super IndexQuery
- var category: String
-end
+redef class ToolContext
-# A match to a query in the nit index
-private interface IndexMatch
- # Short preview of the result for result list display
- fun preview(index: NitIndex, output: Pager) is abstract
- fun content(index: NitIndex, output: Pager) is abstract
-end
+ # Nittx generation phase.
+ var docx: Phase = new NitxPhase(self, null)
-# Code Analysis
+ # Used to shortcut the prompt and display directly the result in console.
+ var opt_query = new OptionString("Nitx query to perform", "-q", "--query")
-redef class ToolContext
- private var nitx_phase: NitxPhase = new NitxPhase(self, [modelize_property_phase])
+ init do option_context.add_option opt_query
end
-# Compiler phase for nitx
+# Nitx phase explores the model and prepares the console rendering.
private class NitxPhase
super Phase
-
- var mtype2mpropdefs = new HashMap[MType, Set[MPropDef]]
- redef fun process_npropdef(npropdef) do
- var visitor = new TypeInitVisitor
- visitor.enter_visit(npropdef)
- for mtype in visitor.inits do
- if not mtype2mpropdefs.has_key(mtype) then
- mtype2mpropdefs[mtype] = new HashSet[MPropDef]
- end
- mtype2mpropdefs[mtype].add(npropdef.mpropdef.as(not null))
- end
- end
-end
-
-# Visitor looking for initialized mtype (new T)
-private class TypeInitVisitor
- super Visitor
-
- var inits = new HashSet[MType]
- redef fun visit(node)
+ redef fun process_mainmodule(mainmodule, mmodules)
do
- node.visit_all(self)
- # look for init
- if not node isa ANewExpr then return
- var mtype = node.n_type.mtype
- if mtype != null then inits.add(mtype)
- end
-end
-
-# Pager output for console
-
-private class PagerMatchesRenderer
- var index: NitIndex
-
- fun render_matches(query: IndexQuery, matches: Collection[IndexMatch]) do
- var pager = new Pager
- if matches.length == 1 then
- pager.add("= result for '{query.string}'".bold)
- pager.add("")
- pager.indent = pager.indent + 1
- matches.first.content(index, pager)
- pager.indent = pager.indent - 1
- else
- pager.add("= multiple results for '{query.string}'".bold)
- pager.indent = pager.indent + 1
- for match in matches do
- pager.add("")
- match.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- pager.render
- end
-
- fun props_fulldoc(pager: Pager, raw_mprops: List[MProperty]) do
- # group by module
- var cats = new HashMap[MModule, Array[MProperty]]
- for mprop in raw_mprops do
- if mprop isa MAttribute then continue
- var key = mprop.intro.mclassdef.mmodule
- if not cats.has_key(key) then cats[key] = new Array[MProperty]
- cats[key].add(mprop)
- end
- #sort groups
- var sorter = new MEntityNameSorter
- var sorted = new Array[MModule]
- sorted.add_all(cats.keys)
- sorter.sort(sorted)
- # display
- for mmodule in sorted do
- var mprops = cats[mmodule]
- pager.add("# matches in module {mmodule.namespace.bold}")
- sorter.sort(mprops)
- for mprop in mprops do
-
- end
- pager.add_rule
- end
- end
-end
-
-private class Pager
- var content = new FlatBuffer
- var indent = 0
- fun add(text: String) do
- add_indent
- addn("{text}\n")
- end
- fun add_indent do addn(" " * indent)
- fun addn(text: String) do content.append(text.escape)
- fun add_rule do add("\n---\n")
- fun render do sys.system("echo \"{content}\" | less -r")
-end
-
-redef class MModule
- super IndexMatch
- # prototype of the module
- # module name
- private fun prototype: String do return "module {name.bold}"
-
- # namespace of the module
- # project::name
- private fun namespace: String do
- if mgroup == null or mgroup.mproject.name == self.name then
- return self.name
- else
- return "{mgroup.mproject}::{self.name}"
- end
- end
-
- redef fun preview(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- pager.add(mdoc.short_comment.green)
- end
- pager.add(prototype)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- end
-
- redef fun content(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- for comment in mdoc.content do pager.add(comment.green)
- end
- pager.add(prototype)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- pager.indent = pager.indent + 1
- var sorter = new MEntityNameSorter
- # imported modules
- var imports = new Array[MModule]
- for mmodule in in_importation.direct_greaters.to_a do
- imports.add(mmodule)
- end
- if not imports.is_empty then
- sorter.sort(imports)
- pager.add("")
- pager.add("== imported modules".bold)
- pager.indent = pager.indent + 1
- for mmodule in imports do
- pager.add("")
- mmodule.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- # mclassdefs
- var intros = new Array[MClassDef]
- var redefs = new Array[MClassDef]
- for mclassdef in mclassdefs do
- if mclassdef.is_intro then
- intros.add(mclassdef)
- else
- redefs.add(mclassdef)
- end
- end
- # introductions
- if not intros.is_empty then
- sorter.sort(intros)
- pager.add("")
- pager.add("== introduced classes".bold)
- pager.indent = pager.indent + 1
- for mclass in intros do
- pager.add("")
- mclass.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- # refinements
- if not redefs.is_empty then
- sorter.sort(redefs)
- pager.add("")
- pager.add("== refined classes".bold)
- pager.indent = pager.indent + 1
- for mclass in redefs do
- pager.add("")
- mclass.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- pager.indent = pager.indent - 1
- end
-end
+ var doc = new DocModel(mainmodule.model, mainmodule)
-redef class MClass
- super IndexMatch
- # return the generic signature of the class
- # [E, F]
- private fun signature: String do
- var res = new FlatBuffer
- if arity > 0 then
- res.append("[")
- for i in [0..mparameters.length[ do
- res.append(mparameters[i].name)
- if i < mparameters.length - 1 then res.append(", ")
- end
- res.append("]")
- end
- return res.to_s
- end
+ var phases = [
+ new ExtractionPhase(toolcontext, doc),
+ new MakePagePhase(toolcontext, doc),
+ new ConcernsPhase(toolcontext, doc),
+ new StructurePhase(toolcontext, doc): DocPhase]
- # return the prototype of the class
- # class name is displayed with colors depending on visibility
- # abstract interface Foo[E]
- private fun prototype: String do
- var res = new FlatBuffer
- res.append("{kind} ")
- if visibility.to_s == "public" then res.append("{name}{signature}".bold.green)
- if visibility.to_s == "private" then res.append("{name}{signature}".bold.red)
- if visibility.to_s == "protected" then res.append("{name}{signature}".bold.yellow)
- return res.to_s
- end
-
- private fun namespace: String do
- return "{intro_mmodule.namespace}::{name}"
- end
-
- redef fun preview(index, pager) do
- intro.preview(index, pager)
- end
-
- redef fun content(index, pager) do
- # intro comment
- var sorter = new MEntityNameSorter
- var mdoc = intro.mdoc
- if mdoc != null then
- for comment in mdoc.content do pager.add(comment.green)
- end
- pager.add(intro.to_console)
- pager.add("{intro.namespace}".bold.gray + " (lines {intro.location.lines})".gray)
- pager.indent = pager.indent + 1
- # parents
- var supers = self.in_hierarchy(index.mainmodule).direct_greaters.to_a
- if not supers.is_empty then
- sorter.sort(supers)
- pager.add("")
- pager.add("== supers".bold)
- pager.indent = pager.indent + 1
- for mclass in supers do
- pager.add("")
- mclass.preview(index, pager)
- end
- pager.indent = pager.indent - 1
+ for phase in phases do
+ toolcontext.info("# {phase.class_name}", 1)
+ phase.apply
end
- # formal types
- if not self.parameter_types.is_empty then
- pager.add("")
- pager.add("== formal types".bold)
- pager.indent = pager.indent + 1
- for ft, bound in self.parameter_types do
- pager.add("")
- pager.add("{ft.to_s.bold.green}: {bound.to_console}")
- end
- pager.indent = pager.indent - 1
- end
- # intro mproperties
- var mpropdefs = intro.mpropdefs
- index.mainmodule.linearize_mpropdefs(mpropdefs)
- for cat in intro.cats2mpropdefs.keys do
- var defs = intro.cats2mpropdefs[cat].to_a
- if defs.is_empty then continue
- sorter.sort(defs)
- pager.add("")
- pager.add("== {cat}".bold)
- pager.indent = pager.indent + 1
- for mpropdef in defs do
- pager.add("")
- mpropdef.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- # refinements
- if not self.mclassdefs.is_empty then
- pager.add("")
- pager.add("== refinements".bold)
- var mclassdefs = self.mclassdefs
- index.mainmodule.linearize_mclassdefs(mclassdefs)
- pager.indent = pager.indent + 1
- for mclassdef in mclassdefs do
- if not mclassdef.is_intro then
- pager.add("")
- mclassdef.content(index, pager)
- end
- end
- pager.indent = pager.indent - 1
- end
- pager.indent = pager.indent - 1
- end
-end
-
-redef class MClassDef
- super IndexMatch
- private fun namespace: String do
- return "{mmodule.full_name}::{mclass.name}"
- end
-
- fun to_console: String do
- var res = new FlatBuffer
- if not is_intro then res.append("redef ")
- res.append(mclass.prototype)
- return res.to_s
- end
-
- redef fun preview(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- pager.add(mdoc.short_comment.green)
- end
- pager.add(to_console)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- end
-
- redef fun content(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- for comment in mdoc.content do pager.add(comment.green)
- end
- pager.add(to_console)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- pager.indent = pager.indent + 1
- var mpropdefs = self.mpropdefs
- var sorter = new MEntityNameSorter
- index.mainmodule.linearize_mpropdefs(mpropdefs)
- for cat in cats2mpropdefs.keys do
- var defs = cats2mpropdefs[cat].to_a
- sorter.sort(defs)
- if defs.is_empty then continue
- pager.add("")
- pager.add("== {cat}".bold)
- pager.indent = pager.indent + 1
- for mpropdef in defs do
- pager.add("")
- mpropdef.preview(index, pager)
- end
- pager.indent = pager.indent - 1
- end
- pager.indent = pager.indent - 1
- end
-
- # get mpropdefs grouped by categories (vt, init, methods)
- fun cats2mpropdefs: Map[String, Set[MPropDef]] do
- var cats = new ArrayMap[String, Set[MPropDef]]
- cats["virtual types"] = new HashSet[MPropDef]
- cats["constructors"] = new HashSet[MPropDef]
- cats["methods"] = new HashSet[MPropDef]
-
- for mpropdef in mpropdefs do
- if mpropdef isa MAttributeDef then continue
- if mpropdef isa MVirtualTypeDef then cats["virtual types"].add(mpropdef)
- if mpropdef isa MMethodDef then
- if mpropdef.mproperty.is_init then
- cats["constructors"].add(mpropdef)
- else
- cats["methods"].add(mpropdef)
- end
- end
- end
- return cats
- end
-end
-
-redef class MProperty
- super IndexMatch
-
- fun to_console: String do
- if visibility.to_s == "public" then return name.green
- if visibility.to_s == "private" then return name.red
- if visibility.to_s == "protected" then return name.yellow
- return name.bold
- end
-
- redef fun preview(index, pager) do
- intro.preview(index, pager)
- end
-
- redef fun content(index, pager) do
- intro.content(index, pager)
- pager.indent = pager.indent + 1
- var mpropdefs = self.mpropdefs
- index.mainmodule.linearize_mpropdefs(mpropdefs)
- for mpropdef in mpropdefs do
- if mpropdef isa MAttributeDef then continue
- if not mpropdef.is_intro then
- pager.add("")
- mpropdef.preview(index, pager)
- end
- end
- pager.indent = pager.indent - 1
- end
-end
-
-redef class MPropDef
- super IndexMatch
-
- fun to_console: String is abstract
-
- private fun namespace: String do
- return "{mclassdef.namespace}::{mproperty.name}"
- end
-
- redef fun preview(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- pager.add(mdoc.short_comment.green)
- end
- pager.add(to_console)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- end
-
- redef fun content(index, pager) do
- var mdoc = self.mdoc
- if mdoc != null then
- for comment in mdoc.content do pager.add(comment.green)
- end
- pager.add(to_console)
- pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
- end
-end
-
-redef class MMethodDef
- redef fun to_console do
- var res = new FlatBuffer
- if not is_intro then res.append("redef ")
- if not mproperty.is_init then res.append("fun ")
- res.append(mproperty.to_console.bold)
- if msignature != null then res.append(msignature.to_console)
- if is_abstract then res.append " is abstract"
- if is_intern then res.append " is intern"
- if is_extern then res.append " is extern"
- return res.to_s
- end
-end
-
-redef class MVirtualTypeDef
- redef fun to_console do
- var res = new FlatBuffer
- res.append("type ")
- res.append(mproperty.to_console.bold)
- res.append(": {bound.to_console}")
- return res.to_s
- end
-end
-
-redef class MAttributeDef
- redef fun to_console do
- var res = new FlatBuffer
- res.append("var ")
- res.append(mproperty.to_console.bold)
- res.append(": {static_mtype.to_console}")
- return res.to_s
- end
-end
-
-redef class MSignature
- redef fun to_console do
- var res = new FlatBuffer
- if not mparameters.is_empty then
- res.append("(")
- for i in [0..mparameters.length[ do
- res.append(mparameters[i].to_console)
- if i < mparameters.length - 1 then res.append(", ")
- end
- res.append(")")
- end
- if return_mtype != null then
- res.append(": {return_mtype.to_console}")
- end
- return res.to_s
- end
-end
-
-redef class MParameter
- fun to_console: String do
- var res = new FlatBuffer
- res.append("{name}: {mtype.to_console}")
- if is_vararg then res.append("...")
- return res.to_s
- end
-end
-
-redef class MType
- fun to_console: String do return self.to_s
-end
-
-redef class MNullableType
- redef fun to_console do return "nullable {mtype.to_console}"
-end
-
-redef class MGenericType
- redef fun to_console do
- var res = new FlatBuffer
- res.append("{mclass.name}[")
- for i in [0..arguments.length[ do
- res.append(arguments[i].to_console)
- if i < arguments.length - 1 then res.append(", ")
- end
- res.append("]")
- return res.to_s
- end
-end
-
-redef class MParameterType
- redef fun to_console do return name
-end
-
-redef class MVirtualType
- redef fun to_console do return mproperty.name
-end
-
-redef class MDoc
- private fun short_comment: String do
- return content.first
- end
-end
-
-# Redef String class to add a function to color the string
-redef class String
-
- private fun add_escape_char(escapechar: String): String do
- return "{escapechar}{self}\\033[0m"
- end
-
- private fun esc: Char do return 27.ascii
- private fun gray: String do return add_escape_char("{esc}[30m")
- private fun red: String do return add_escape_char("{esc}[31m")
- private fun green: String do return add_escape_char("{esc}[32m")
- private fun yellow: String do return add_escape_char("{esc}[33m")
- private fun blue: String do return add_escape_char("{esc}[34m")
- private fun purple: String do return add_escape_char("{esc}[35m")
- private fun cyan: String do return add_escape_char("{esc}[36m")
- private fun light_gray: String do return add_escape_char("{esc}[37m")
- private fun bold: String do return add_escape_char("{esc}[1m")
- private fun underline: String do return add_escape_char("{esc}[4m")
-
- private fun escape: String
- do
- var b = new FlatBuffer
- for c in self.chars do
- if c == '\n' then
- b.append("\\n")
- else if c == '\0' then
- b.append("\\0")
- else if c == '"' then
- b.append("\\\"")
- else if c == '\\' then
- b.append("\\\\")
- else if c == '`' then
- b.append("'")
- else if c.ascii < 32 then
- b.append("\\{c.ascii.to_base(8, false)}")
- else
- b.add(c)
- end
+ # start nitx
+ var nitx = new Nitx(toolcontext, doc)
+ var q = toolcontext.opt_query.value
+ if q != null then # shortcut prompt
+ print ""
+ nitx.do_query(q)
+ return
end
- return b.to_s
- end
-end
-
-redef class Location
- fun lines: String do
- return "{line_start}-{line_end}"
+ nitx.start
end
end
-# Create a tool context to handle options and paths
+# build toolcontext
var toolcontext = new ToolContext
-toolcontext.tooldescription = "Usage: nitx [OPTION]... <file.nit> [query]\nDisplays specific pieces of API information from Nit source files."
+var tpl = new Template
+tpl.add "Usage: nitx [OPTION]... <file.nit>... [query]\n"
+tpl.add "Displays specific pieces of API information from Nit source files."
+toolcontext.tooldescription = tpl.write_to_string
+
+# process options
toolcontext.process_options(args)
+var arguments = toolcontext.option_context.rest
-# Here we launch the nit index
-var ni = new NitIndex(toolcontext)
-ni.start
+# build model
+var model = new Model
+var mbuilder = new ModelBuilder(model, toolcontext)
+var mmodules = mbuilder.parse_full(arguments)
-# TODO seek subclasses and super classes <.<class> >.<class>
-# TODO seek subclasses and super types <:<type> >:<type>
-# TODO seek with regexp
-# TODO standardize namespaces with private option
+# process
+if mmodules.is_empty then return
+mbuilder.run_phases
+toolcontext.run_global_phases(mmodules)
-base_simple3.nit A
-base_simple3.nit foo
-base_simple3.nit base_simple3
+base_simple3.nit -q A
+base_simple3.nit -q foo
+base_simple3.nit -q base_simple3
+++ /dev/null
-Runtime error: Aborted (../lib/serialization/serialization.nit:109)
-# Nit:
-<A: true a 0.123 1234 asdf false>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null}
-
-# Back in Nit:
-<A: true a 0.123 1234 asdf false>
-
-# Nit:
-<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}
-
-# Back in Nit:
-<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
-
-# Nit:
-<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null}, "b": {"__kind": "obj", "__id": 2, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
-
-# Back in Nit:
-<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
-
-# Nit:
-<D: <B: <A: false b 123.123 2345 new line ->
-<- false> 1111 f"\r\/> true>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "D", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "new line ->\n<-", "n": null, "ii": 1111, "ss": "\tf\"\r\\\/", "d": {"__kind": "ref", "__id": 0}}
-
-# Back in Nit:
-<D: <B: <A: false b 123.123 2345 new line ->
-<- false> 1111 f"\r\/> true>
-
-Error: doesn't know how to deserialize class "Array"
sum: 0
--- Sends on Nullable Receiver ---
Total number of sends: 19
-Number of sends on a nullable receiver: 0 (0.00%)
+Number of sends on a unsafe nullable receiver: 0 (0.00%)
+Number of sends on a safe nullable receiver: 0 (0.00%)
Number of buggy sends (cannot determine the type of the receiver): 0 (0.00%)
# RTA metrics
if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
if name == "Array[String]" then return new Array[String].from_deserializer(self)
- if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
if name == "HashMap[Serializable, Array[Couple[Serializable, Int]]]" then return new HashMap[Serializable, Array[Couple[Serializable, Int]]].from_deserializer(self)
if name == "Array[Couple[Serializable, Int]]" then return new Array[Couple[Serializable, Int]].from_deserializer(self)
+ if name == "Couple[Serializable, Int]" then return new Couple[Serializable, Int].from_deserializer(self)
return super
end
end
-Usage: nitx [OPTION]... <file.nit> [query]
+Usage: nitx [OPTION]... <file.nit>... [query]
Displays specific pieces of API information from Nit source files.
Use --help for help
-\e[1m= result for 'A'\e[0m
- class \e[32m\e[1mA\e[0m\e[0m
- \e[30m\e[1mbase_simple3::A\e[0m\e[0m\e[30m (lines 29-32)\e[0m
-
- \e[1m== supers\e[0m
-
- interface \e[32m\e[1mObject\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Object\e[0m\e[0m\e[30m (lines 19-20)\e[0m
-
- \e[1m== constructors\e[0m
-
- redef \e[1m\e[32minit\e[0m\e[0m
- \e[30m\e[1mbase_simple3::A::init\e[0m\e[0m\e[30m (lines 30-30)\e[0m
-
- \e[1m== methods\e[0m
-
- fun \e[1m\e[32mrun\e[0m\e[0m
- \e[30m\e[1mbase_simple3::A::run\e[0m\e[0m\e[30m (lines 31-31)\e[0m
-
- \e[1m== refinements\e[0m
+\e[1m\e[32m# 2 result(s) for 'comment: A'\e[m\e[m
+
+ \e[1m\e[32mC\e[m\e[m \e[1m\e[34mA\e[m\e[m
+ \e[1m\e[30mbase_simple3::A\e[m\e[m
+ class A
+ \e[30mbase_simple3.nit:29,1--32,3\e[m
+
+ \e[1m\e[32mC\e[m\e[m \e[1m\e[34mA\e[m\e[m
+ \e[1m\e[30mbase_simple3::base_simple3::A\e[m\e[m
+ class A
+ \e[30mbase_simple3.nit:29,1--32,3\e[m
-\e[1m= result for 'foo'\e[0m
- fun \e[1m\e[32mfoo\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Sys::foo\e[0m\e[0m\e[30m (lines 49-49)\e[0m
+\e[1m\e[32m# 2 result(s) for 'comment: foo'\e[m\e[m
+
+ \e[1m\e[32mF\e[m\e[m \e[1m\e[34mfoo\e[m\e[m
+ \e[1m\e[30mbase_simple3::Sys::foo\e[m\e[m
+ fun foo
+ \e[30mbase_simple3.nit:49,1--19\e[m
+
+ \e[1m\e[32mF\e[m\e[m \e[1m\e[34mfoo\e[m\e[m
+ \e[1m\e[30mbase_simple3::base_simple3::Sys::foo\e[m\e[m
+ fun foo
+ \e[30mbase_simple3.nit:49,1--19\e[m
-\e[1m= result for 'base_simple3'\e[0m
- module \e[1mbase_simple3\e[0m
- \e[30m\e[1mbase_simple3\e[0m\e[0m\e[30m (lines 17-66)\e[0m
-
- \e[1m== introduced classes\e[0m
-
- class \e[32m\e[1mA\e[0m\e[0m
- \e[30m\e[1mbase_simple3::A\e[0m\e[0m\e[30m (lines 29-32)\e[0m
-
- class \e[32m\e[1mB\e[0m\e[0m
- \e[30m\e[1mbase_simple3::B\e[0m\e[0m\e[30m (lines 34-42)\e[0m
-
- enum \e[32m\e[1mBool\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Bool\e[0m\e[0m\e[30m (lines 22-23)\e[0m
-
- class \e[32m\e[1mC\e[0m\e[0m
- \e[30m\e[1mbase_simple3::C\e[0m\e[0m\e[30m (lines 44-47)\e[0m
-
- enum \e[32m\e[1mInt\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Int\e[0m\e[0m\e[30m (lines 25-27)\e[0m
-
- interface \e[32m\e[1mObject\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Object\e[0m\e[0m\e[30m (lines 19-20)\e[0m
-
- class \e[32m\e[1mSys\e[0m\e[0m
- \e[30m\e[1mbase_simple3::Sys\e[0m\e[0m\e[30m (lines 49-49)\e[0m
+\e[1m\e[32m# 3 result(s) for 'comment: base_simple3'\e[m\e[m
+
+ \e[1m\e[32mP\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+ \e[1m\e[30mbase_simple3\e[m\e[m
+ project base_simple3
+ \e[30mbase_simple3.nit:17,1--66,13\e[m
+
+ \e[1m\e[32mG\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+ \e[1m\e[30mbase_simple3\e[m\e[m
+ group base_simple3
+ \e[30mbase_simple3.nit:17,1--66,13\e[m
+
+ \e[1m\e[32mM\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+ \e[1m\e[30mbase_simple3::base_simple3\e[m\e[m
+ module base_simple3
+ \e[30mbase_simple3.nit:17,1--66,13\e[m
+++ /dev/null
-Runtime error: Aborted (../lib/serialization/serialization.nit:109)
-# Nit:
-<A: true a 0.123 1234 asdf false>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null}
-
-# Back in Nit:
-<A: true a 0.123 1234 asdf false>
-
-# Nit:
-<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}
-
-# Back in Nit:
-<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
-
-# Nit:
-<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null}, "b": {"__kind": "obj", "__id": 2, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
-
-# Back in Nit:
-<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
-
-# Nit:
-<D: <B: <A: false b 123.123 2345 new line ->
-<- false> 1111 f"\r\/> true>
-
-# Json:
-{"__kind": "obj", "__id": 0, "__class": "D", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "new line ->\n<-", "n": null, "ii": 1111, "ss": "\tf\"\r\\\/", "d": {"__kind": "ref", "__id": 0}}
-
-# Back in Nit:
-<D: <B: <A: false b 123.123 2345 new line ->
-<- false> 1111 f"\r\/> true>
-
-Error: doesn't know how to deserialize class "Array[Object]"
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false>
+
+# Json:
+{"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null}
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
+
+# Json:
+{"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
+
+# Json:
+{"a": {"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null}, "b": {"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "ii": 1111, "ss": "qwer"}, "aa": {"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null}}
+
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false> 1111 f"\r\/> true>
+
+# Json:
+{"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "new line ->\n<-", "n": null, "ii": 1111, "ss": "\tf\"\r\\\/", "d": null}
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"a": ["hello", 1234, 123.4], "b": ["hella", 2345, 234.5]}
+
+# Nit:
+<E: 2222>
+
+# Json:
+{"n": 2222}
+
+# Nit:
+<E: 33.33>
+
+# Json:
+{"n": 33.33}
+
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{"hs": [-1, 0], "s": ["one", "two"], "hm": {"one": 1, "two": 2}, "am": {"three": "3", "four": "4"}}
+
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false>
+
+# Json:
+{"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": [88, "hello", null]}
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl false> 1111 qwer>
+
+# Json:
+{"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": [88, "hello", null], "ii": 1111, "ss": "qwer"}
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
+
+# Json:
+{"a": {"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": [88, "hello", null]}, "b": {"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": [88, "hello", null], "ii": 1111, "ss": "qwer"}, "aa": {"b": true, "c": "a", "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": [88, "hello", null]}}
+
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false> 1111 f"\r\/> true>
+
+# Json:
+{"b": false, "c": "b", "f": 123.123, "i": 2345, "s": "new line ->\n<-", "n": null, "array": [88, "hello", null], "ii": 1111, "ss": "\tf\"\r\\\/", "d": null}
+
# limitations under the License.
import serialization
-import json_serialization
# Simple class
class A
var a = new Array[Object].with_items("hello", 1234, 123.4)
var b = new Array[nullable Serializable].with_items("hella", 2345, 234.5)
- init do end
redef fun to_s do return "<E: a: {a.join(", ")}; b: {b.join(", ")}>"
end
auto_serializable
var n: N
- init(n: N) do self.n = n
redef fun to_s do return "<E: {n}>"
end
"hm: {hm.join(", ", ". ")}; am: {am.join(", ", ". ")}>"
end
-var a = new A(true, 'a', 0.1234, 1234, "asdf", null)
-var b = new B(false, 'b', 123.123, 2345, "hjkl", 12, 1111, "qwer")
-var c = new C(a, b)
-var d = new D(false, 'b', 123.123, 2345, "new line ->\n<-", null, 1111, "\t\f\"\r\\/")
-d.d = d
-var e = new E
-var fi = new F[Int](2222)
-var ff = new F[Float](33.33)
-var g = new G
-
-# Default works only with Nit serial
-var tests = [a, b, c, d, e, fi, ff, g: Serializable]
-
-# Alt1 should work without nitserial
-#alt1# tests = new Array[Serializable].with_items(a, b, c, d)
-
-for o in tests do
- var stream = new StringWriter
- var serializer = new JsonSerializer(stream)
- serializer.serialize(o)
-
- var deserializer = new JsonDeserializer(stream.to_s)
- var deserialized = deserializer.deserialize
-
- print "# Nit:\n{o}\n"
- print "# Json:\n{stream}\n"
- print "# Back in Nit:\n{deserialized or else "null"}\n"
+class TestEntities
+ var a = new A(true, 'a', 0.1234, 1234, "asdf", null)
+ var b = new B(false, 'b', 123.123, 2345, "hjkl", 12, 1111, "qwer")
+ var c = new C(a, b)
+ var d = new D(false, 'b', 123.123, 2345, "new line ->\n<-", null, 1111, "\t\f\"\r\\/")
+ init do d.d = d
+ var e = new E
+ var fi = new F[Int](2222)
+ var ff = new F[Float](33.33)
+ var g = new G
+
+ # should work without nitserial
+ var without_generics: Array[Serializable] = [a, b, c, d: Serializable]
+
+ # Default works only with Nit serial
+ var with_generics: Array[Serializable] = [a, b, c, d, e, fi, ff, g: Serializable]
end
+
+# We instanciate it here so that `nitserial` detects generic types as being alive
+var entities = new TestEntities
if name == "ArrayMap[String, String]" then return new ArrayMap[String, String].from_deserializer(self)
if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
if name == "Array[String]" then return new Array[String].from_deserializer(self)
- if name == "HashMap[Serializable, Int]" then return new HashMap[Serializable, Int].from_deserializer(self)
- if name == "Array[JsonObject]" then return new Array[JsonObject].from_deserializer(self)
- if name == "HashMap[Int, Object]" then return new HashMap[Int, Object].from_deserializer(self)
- if name == "Array[Node]" then return new Array[Node].from_deserializer(self)
- if name == "Array[LRState]" then return new Array[LRState].from_deserializer(self)
if name == "Array[Couple[String, String]]" then return new Array[Couple[String, String]].from_deserializer(self)
- if name == "Array[nullable Jsonable]" then return new Array[nullable Jsonable].from_deserializer(self)
return super
end
end
--- /dev/null
+# 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.
+
+import test_deserialization
+import json::serialization
+#alt1# import test_deserialization_serial
+
+var entities = new TestEntities
+
+var tests = entities.without_generics#alt1##alt2#
+#alt1#var tests = entities.with_generics
+#alt2#var tests = entities.with_generics
+
+for o in tests do
+ var stream = new StringWriter
+ var serializer = new JsonSerializer(stream)
+ #alt2#serializer.plain_json = true
+ serializer.serialize(o)
+
+ var deserializer = new JsonDeserializer(stream.to_s)#alt2#
+ var deserialized = deserializer.deserialize#alt2#
+
+ print "# Nit:\n{o}\n"
+ print "# Json:\n{stream}\n"
+ print "# Back in Nit:\n{deserialized or else "null"}\n"#alt2#
+end
print a[0]
print a.to_a.join(",")
+var i
+i = 3
+a = new NativeArray[Int](i)
+i = 1
+a[i] = i
+print a[i]
+print a[1]
# limitations under the License.
import serialization
-import json_serialization
+import json::serialization
# Simple class
class A
for o in new Array[Serializable].with_items(a, b, c, d) do
var stream = new StringWriter
var serializer = new JsonSerializer(stream)
+ #alt1#serializer.plain_json = true
serializer.serialize(o)
print "# Nit:\n{o}\n"