This PR replace the name heuristics used by nitunit by nice and tidy annotations.
Before:
~~~nit
module test_my_test is test_suite
import test_suite
class TestMyTest
super TestSuite
redef fun before do # something
fun test_my_test do
assert true
end
end
~~~
After:
~~~nit
module my_test is test
class MyTest
test
fun setup is before do # something
fun my_test is test do
assert true
end
end
~~~
Motivations:
* cleaner API / naming policy
* [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) friendly
* more flexibility as one can define a `after` method in the lib that will only be executed on children module tagged with `test`
* more extensible as one can improve nitunit to support `test(timeout = 150)` or `test(res= "res/test_file.res")`
Used annotations:
* `test` on modules, classes and properties to indicate that nitunit must run this module/class/method
* `before`, `after` on properties from all classes but `Sys` for the before/after each case hooks
* `before_all`, `after_all` on properties from `Sys` for the before/after all cases hooks
This also removes the need of the `lib/test_suite` module.
I also migrated the existing test suites to the new annotations system. Let's see what Jenkins has to say about it.
Should fix #2165
Pull-Request: #2474
Reviewed-by: Jean Privat <jean@pryen.org>
-c_src -diff
-parser.nit -diff
-parser_prod.nit -diff
-lexer.nit -diff
-tables_nit.c -diff
-c_src/** -diff
+/src/parser/parser.nit -diff
+/src/parser/parser_prod.nit -diff
+/src/parser/lexer.nit -diff
+/src/parser/tables_nit.c -diff
+/c_src/** -diff
tests/sav/**/*.res -whitespace
*.res -whitespace
super
var gamepad = new VirtualGamepad
- gamepad.add_dpad
- gamepad.controls.first.as(DPad).show_down = false
+
+ var dpad = gamepad.add_dpad
+ if dpad != null then dpad.show_down = false
+
gamepad.add_button("space", gamepad_spritesheet.fire)
gamepad.visible = true
self.gamepad = gamepad
import social_views
import user_views
-redef class App
- redef fun on_create
- do
- if debug then print "App::on_create"
-
- # Create the main window
- show_home
- super
- end
-
- # Show the home/main windows
- fun show_home
- do
- var window = new HomeWindow
- window.refresh
- push_window window
- end
+redef fun root_window do return new HomeWindow
+redef class App
redef fun on_log_in
do
super
# Write the model to file next to the Nit module
var model_path = file_name.strip_extension + ".jwrapper.bin"
var model_stream = model_path.to_path.open_wo
- var serializer = new BinarySerializer(model_stream)
+ var serializer = new MsgPackSerializer(model_stream)
serializer.serialize model
model_stream.close
end
import more_collections
import opts
import poset
-import binary::serialization
+import msgpack
import jtype_converter
end
# Short name of the class, mangled to remove `$` (e.g. `Set`)
- var id: String is lazy do return identifier.last.replace("$", "")
+ var id: String is lazy, noserialize do return identifier.last.replace("$", "")
# Full name of this class as used in java code (e.g. `java.lang.Set`)
- var java_full_name: String is lazy do return identifier.join(".").replace("$", ".")
+ var java_full_name: String is lazy, noserialize do return identifier.join(".").replace("$", ".")
# Full name of this class as used by jni (e.g. `android.graphics.BitmapFactory$Options`)
- var jni_full_name: String is lazy do return identifier.join(".")
+ var jni_full_name: String is lazy, noserialize do return identifier.join(".")
# Name of this class for the extern declaration in Nit (e.g. `java.lang.Set[]`)
- var extern_equivalent: String is lazy do return jni_full_name + "[]" * array_dimension
+ var extern_equivalent: String is lazy, noserialize do return jni_full_name + "[]" * array_dimension
# Full name of this class with arrays and generic values (e.g. `java.lang.Set<E>[]`)
redef fun to_s do
end
var file = model_path.to_path.open_ro
- var d = new BinaryDeserializer(file)
+ var d = new MsgPackDeserializer(file)
var model = d.deserialize
file.close
if d.errors.not_empty then
- print_error "Error: failed to deserialize model file '{model_path}' with: {d.errors.join(", ")}"
+ print_error "Error: failed to deserialize model file '{model_path}' with:\n* {d.errors.join("\n* ")}"
continue
end
end
# Does this model have access to the `java.lang.Object`?
- var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy
+ var knows_the_object_class: Bool = all_classes.keys.has("java.lang.Object") is lazy, noserialize
# Add a class in `classes`
fun add_class(jclass: JavaClass)
var path: String
# Name of the module
- var name: String is lazy do return path.basename(".nit")
+ var name: String is lazy, noserialize do return path.basename(".nit")
redef fun to_s do return self.name
redef fun ==(other) do return other isa NitModuleRef and self.path == other.path
var opt_extern_class_prefix = new OptionString("Prefix to extern classes (By default uses the full namespace)", "-p")
# Prefix used to name extern classes, if `null` use the full namespace
- var extern_class_prefix: nullable String is lazy do return opt_extern_class_prefix.value
+ var extern_class_prefix: nullable String is lazy, noserialize do return opt_extern_class_prefix.value
# Libraries to search for existing wrappers
var opt_libs = new OptionArray("Paths to libraries with wrappers of Java classes ('auto' to use the full Nit lib)", "-i")
init atmo do init(null, false, [0.0, 0.8*atmo_a, 1.0*atmo_a, atmo_a])
private var atmo_a = 0.05
- redef fun draw(actor, model)
+ redef fun draw(actor, model, camera)
do
var gl_error = glGetError
assert gl_error == gl_NO_ERROR else print gl_error
var program = app.globe_program
program.use
- # Set constant program values
- program.use
-
# Bind textures
glActiveTexture gl_TEXTURE0
glBindTexture(gl_TEXTURE_2D, app.texture_earth.gl_texture)
# Update camera view and light
var p = app.world_camera.position
program.camera.uniform(p.x, p.y, p.z)
- program.mvp.uniform app.world_camera.mvp_matrix
+ program.mvp.uniform camera.mvp_matrix
program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
# Set attributes
# The current entity.
protected fun entity: Entity is abstract
- redef fun start_dox_element(local_name: String, atts: Attributes) do
+ redef fun start_dox_element(local_name, atts) do
if ["briefdescription", "detaileddescription", "inbodydescription"].has(local_name) then
doc.doc = entity.doc
doc.listen_until(dox_uri, local_name)
end
# Parse the attributes of a `location` element.
- protected fun get_location(atts: Attributes): Location do
- var location = new Location
+ protected fun get_location(atts: Attributes): neo_doxygen::Location do
+ var location = new neo_doxygen::Location
location.path = atts.value_ns("", "bodyfile") or else atts.value_ns("", "file")
# Doxygen may indicate `[generated]`.
# Create a new parameter.
protected fun create_parameter: T is abstract
- redef fun start_dox_element(local_name: String, atts: Attributes) do
+ redef fun start_dox_element(local_name, atts) do
if "declname" == local_name then
text.listen_until(dox_uri, local_name)
else if "type" == local_name then
end
end
- redef fun end_dox_element(local_name: String) do
+ redef fun end_dox_element(local_name) do
if "declname" == local_name then
parameter.name = text.to_s
else if "type" == local_name then
# Return the number of type parameters.
fun arity: Int do return class_type.arity
- redef fun name=(name: String) do
+ redef fun name=(name) do
super
class_type.name = name
class_def.name = name
end
- redef fun location=(location: nullable Location) do
+ redef fun location=(location) do
super
class_def.location = location
end
class_def["mdoc"] = doc
end
- redef fun declare_super(id: String, full_name: String, prot: String, virt: String) do
+ redef fun declare_super(id, full_name, prot, virt) do
class_def.declare_super(id, full_name, prot, virt)
end
- redef fun declare_member(member: Member) do
+ redef fun declare_member(member) do
class_def.declare_member(member)
end
fun ns_separator: String do return "::"
# Set the location of the entity in the source code.
- fun location=(location: nullable Location) do
+ fun location=(location: nullable neo_doxygen::Location) do
self["location"] = location
end
# Get the location of the entity in the source code.
- fun location: nullable Location do
- return self["location"].as(nullable Location)
+ fun location: nullable neo_doxygen::Location do
+ return self["location"].as(nullable neo_doxygen::Location)
end
# Put the entity in the graph.
super Entity
init do
- self["location"] = new Location
+ self["location"] = new neo_doxygen::Location
end
- redef fun location=(location: nullable Location) do
+ redef fun location=(location) do
if location == null then
- super(new Location)
+ super(new neo_doxygen::Location)
else
super
end
# This module is used to model locations in source files.
module location
-import json::static
import json
# A location inside a source file.
super
end
- redef fun location=(location: nullable Location) do
+ redef fun location=(location) do
super
for m in inner_namespaces do m.location = location
end
- redef fun name=(name: String) do
+ redef fun name=(name) do
# Example: `MyClass.java`
super
var match = name.search_last(".")
for m in inner_namespaces do m.update_name
end
- redef fun declare_namespace(id: String, full_name: String) do
+ redef fun declare_namespace(id, full_name) do
var m: Module
assert not full_name.is_empty or id.is_empty else
var d_ns = new Namespace(graph)
var buffer = new Buffer
var root_ns = graph.by_id[""].as(Namespace)
-var location: Location
+var location
file.name = "Bar.java"
file.model_id = "_Bar_8java"
-location = new Location
+location = new neo_doxygen::Location
location.path = "a/b/Bar.java"
file.location = location
file.declare_class("classa_b_bar", "a::b::Bar", "package")
file_2.name = "Bar.java"
file_2.model_id = "_Bar_8java_2"
-location = new Location
+location = new neo_doxygen::Location
location.path = "Bar.java"
file_2.location = location
file_2.declare_namespace("namespacec", "c")
bar_class.model_id = "classa_b_bar"
bar_class.name = "Bar"
-location = new Location
+location = new neo_doxygen::Location
location.path = "a/b/Bar.class"
location.line_start = 5
location.column_start = 1
baz_class.model_id = "classbaz"
baz_class.name = "Baz"
-location = new Location
+location = new neo_doxygen::Location
location.path = "Baz.jar"
baz_class.location = location
baz_class.put_in_graph
# Delay in seconds before the next request after an error
fun request_delay_on_error: Float do return 60.0
-redef class App
- redef fun on_create
- do
- # Create the main window
- push_window new TnitterWindow
- super
- end
-end
-
-# Main window
-class TnitterWindow
- super Window
+redef class Window
private var layout = new VerticalLayout(parent=self)
private var list_posts = new ListLayout(parent=layout)
abstract class AsyncTnitterRequest
super AsyncHttpRequest
- private var window: TnitterWindow
+ private var window: Window
redef fun uri_root do return tnitter_server_uri
height="512"
id="svg2"
version="1.1"
- inkscape:version="0.48.5 r10040"
- sodipodi:docname="icon_sci.svg">
+ inkscape:version="0.92.1 r15371"
+ sodipodi:docname="icon-sci.svg">
<defs
id="defs4" />
<sodipodi:namedview
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
- inkscape:cx="164.13268"
+ inkscape:cx="165.56125"
inkscape:cy="278.8294"
inkscape:document-units="px"
inkscape:current-layer="layer1"
id="path2999"
style="" />
</g>
- <text
- xml:space="preserve"
- style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
- x="318.1875"
- y="744.69647"
- id="text2994"
- sodipodi:linespacing="125%"><tspan
- sodipodi:role="line"
- id="tspan2996"
- x="318.1875"
- y="744.69647">√</tspan></text>
- <text
- xml:space="preserve"
- style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
- x="72.978729"
- y="725.84735"
- id="text2998"
- sodipodi:linespacing="125%"><tspan
- sodipodi:role="line"
- id="tspan3000"
- x="72.978729"
- y="725.84735">π</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="path4504"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ d="M 376.61565,748.14397 H 364.2218 L 338.40905,675.6446 h -16.77364 v -13.32572 h 27.30375 l 21.52616,62.80794 47.71167,-136.14598 h 13.60528 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4507"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ d="m 181.35503,716.1555 q 4.93891,0 8.38682,-1.86373 v 13.13934 q -4.56616,2.42286 -12.58022,2.42286 -21.15342,0 -21.15342,-24.41495 V 639.6491 h -43.33188 v 88.34113 H 96.089068 V 639.6491 H 75.3084 v -7.26857 l 13.698467,-6.70945 H 192.63064 v 13.97802 h -20.03517 v 64.67168 q 0,11.83472 8.75956,11.83472 z" />
<flowRoot
xml:space="preserve"
id="flowRoot3002"
height="282.85715"
x="-225"
y="-62.285713" /></flowRegion><flowPara
- id="flowPara3008"></flowPara></flowRoot> <text
- xml:space="preserve"
- style="font-size:190.84666443px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
- x="303.01755"
- y="973.79059"
- id="text3014"
- sodipodi:linespacing="125%"><tspan
- sodipodi:role="line"
- id="tspan3016"
- x="303.01755"
- y="973.79059">x²</tspan></text>
+ id="flowPara3008" /></flowRoot> <path
+ inkscape:connector-curvature="0"
+ id="path4499"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ d="m 335.60349,924.36988 -35.31781,-50.04133 h 19.1033 l 26.37188,38.57935 26.09232,-38.57935 h 18.91693 l -35.31782,50.04133 37.27474,52.27783 h -19.1033 l -27.86287,-40.81584 -28.23562,40.81584 h -18.91693 z" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4501"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ d="m 454.6031,922.04021 h -53.862 v -10.25055 l 21.99209,-21.43298 q 11.83473,-11.46198 15.09627,-16.68044 3.35473,-5.31165 3.35473,-11.74154 0,-6.05715 -3.5411,-9.13232 -3.44791,-3.07516 -9.2255,-3.07516 -5.77758,0 -10.43693,2.23648 -4.65934,2.23649 -9.59824,5.96396 l -6.70945,-8.75956 q 12.39385,-10.53012 26.931,-10.53012 12.30066,0 19.19649,6.15034 6.98901,6.05714 6.98901,16.40088 0,4.2866 -1.21143,8.01407 -1.11824,3.63429 -3.5411,7.45495 -2.42286,3.72747 -6.33671,8.01407 -3.82066,4.28659 -26.37187,25.53319 h 37.27474 z" />
</g>
</svg>
# Show debug output?
fun debug: Bool do return false
-redef class App
- redef fun on_create
- do
- if debug then print "App::on_create"
-
- # Create the main window
- push_window new CalculatorWindow
- super
- end
-end
+redef fun root_window do return new CalculatorWindow
# The main (and only) window of this calculator
class CalculatorWindow
module data_store
import app::data_store
-private import shared_preferences
+import shared_preferences
-redef class App
- redef var data_store = new SharedPreferenceView
-end
-
-private class SharedPreferenceView
- super DataStore
+redef class DataStore
# The `SharedPreferences` used to implement the `DataStore`
var shared_preferences = new SharedPreferences.privately(app, "data_store") is lazy
if serialized_string == "" then return null
var deserializer = new JsonDeserializer(serialized_string)
- return deserializer.deserialize
+ var deserialized = deserializer.deserialize
+
+ var errors = deserializer.errors
+ if errors.not_empty then
+ # An update may have broken the versioning compatibility
+ print_error "{class_name} error at deserialization: {errors.join(", ")}"
+ return null # Let's be safe
+ end
+
+ return deserialized
end
end
# See the License for the specific language governing permissions and
# limitations under the License.
-# Simple data storage services
+# Key/value storage services
#
-# The implementation varies per platform.
+# The main services is `App::data_store`, a `DataStore` holding any
+# serializable Nit object.
module data_store
import app_base
import ios::data_store is conditional(ios)
redef class App
+
# Services to store and load data
- fun data_store: DataStore is abstract
+ #
+ # ~~~
+ # import app::ui
+ # import app::data_store
+ #
+ # class MyWindow
+ # super Window
+ #
+ # var state = "Simple string or any serializable class"
+ #
+ # redef fun on_save_state do app.data_store["state"] = state
+ #
+ # redef fun on_restore_state
+ # do
+ # var state = app.data_store["state"]
+ # if state isa String then self.state = state
+ # end
+ # end
+ # ~~~
+ var data_store = new DataStore is lazy
end
# Simple data storage facility
-interface DataStore
+#
+# Write values with `[]=` and read with `[]`.
+# ~~~
+# import linux::data_store # Needed for testing only
+#
+# class A
+# serialize
+#
+# var b = true
+# var f = 1.234
+# end
+#
+# var data_store = new DataStore
+# data_store["one"] = 1
+# data_store["str"] = "Some string"
+# data_store["a"] = new A
+#
+# assert data_store["one"] == 1
+# assert data_store["str"] == "Some string"
+# assert data_store["a"].as(A).b
+# assert data_store["a"].as(A).f == 1.234
+# assert data_store["other"] == null
+# ~~~
+#
+# Set to `null` to clear a value.
+# ~~~
+# data_store["one"] = null
+# assert data_store["one"] == null
+# ~~~
+class DataStore
# Get the object stored at `key`, or null if nothing is available
fun [](key: String): nullable Object is abstract
module ui_example is
app_name "app.nit UI"
app_namespace "org.nitlanguage.ui_example"
- android_api_target 15
+ android_api_min 21
+ android_api_target 21
+ android_manifest_activity "android:theme=\"@android:style/Theme.Material\""
end
import app::ui
var layout = new ListLayout(parent=self)
# Some label
- var some_label = new Label(parent=layout, text="This Window uses a ListLayout.")
+ var some_label = new Label(parent=layout, text="Sample Window using a ListLayout.")
# A checkbox
var checkbox = new CheckBox(parent=layout, text="A CheckBox")
var another_label = new Label(parent=layout, text="Close it by tapping the back button.")
end
-redef class App
- redef fun on_create
- do
- # Create the main window
- push_window new UiExampleWindow
- super
- end
-end
+redef fun root_window do return new UiExampleWindow
# limitations under the License.
# HTTP request services: `AsyncHttpRequest` and `Text::http_get`
+#
+# ~~~nitish
+# import app::http_request
+#
+# class MyHttpRequest
+# super AsyncHttpRequest
+#
+# redef fun uri do return "http://example.com/"
+#
+# redef fun on_load(data, status) do print "Received: {data or else "null"}"
+#
+# redef fun on_fail(error) do print "Connection error: {error}"
+# end
+#
+# var req = new MyHttpRequest
+# req.start
+# ~~~
module http_request
import app_base
fun after do end
end
-# Minimal implementation of `AsyncHttpRequest` where `uri` is an attribute
+# Simple `AsyncHttpRequest` where `uri` is an attribute
+#
+# Prints on communication errors and when the remote server returns an
+# HTTP status code not in the 200s.
#
-# Prints on communication errors and when the server returns an HTTP status code not in the 200s.
+# This class can be instantiated to execute a request where the response is
+# ignored by the application. Alternatively, it can be subclassed to implement
+# `on_load`.
#
# ~~~nitish
# var request = new SimpleAsyncHttpRequest("http://example.com")
# See the License for the specific language governing permissions and
# limitations under the License.
-# Portable UI API for the _app.nit_ framework
+# Portable UI controls for mobiles apps
+#
+# ~~~
+# import app::ui
+#
+# class MyWindow
+# super Window
+#
+# var layout = new ListLayout(parent=self)
+# var lbl = new Label(parent=layout, text="Hello world", align=0.5)
+# var but = new Button(parent=layout, text="Press here")
+#
+# redef fun on_event(event) do lbl.text = "Pressed!"
+# end
+#
+# redef fun root_window do return new MyWindow
+# ~~~
module ui
import app_base
# This attribute is set by `push_window`.
var window: Window is noinit
- # Make visible and push `window` on the top of `pop_window`
+ # Make `window` visible and push it on the top of the `window_stack`
#
- # This method must be called at least once within `App::on_create`.
- # It can be called at any times while the app is active.
+ # This method can be called at any times while the app is active.
fun push_window(window: Window)
do
window_stack.add window
self.window = window
+ window.on_create
end
# Pop the current `window` from the stack and show the previous one
# Stack of active windows
var window_stack = new Array[Window]
- redef fun on_create do window.on_create
+ redef fun on_create
+ do
+ var window = root_window
+ push_window window
+ end
redef fun on_resume do window.on_resume
redef fun on_save_state do window.on_save_state
end
+# Hook to create the first window shown to the user
+#
+# By default, a `Window` is created, which can be refined to customize it.
+# However, most apps should refine this method to return a different window,
+# this way the app can have more than one window.
+fun root_window: Window do return new Window
+
# An event created by an `AppComponent` and sent to `AppObserver`s
interface AppEvent
end
end
# A window, root of the `Control` tree
+#
+# Each window should hold a single control, usually a `CompositeControl`,
+# which in turn holds all the displayed controls.
class Window
super CompositeControl
fun on_back_button do app.pop_window
end
-# A viewable `Control`
+# A visible `Control`
abstract class View
super Control
var enabled: nullable Bool is writable, abstract, autoinit
end
-# A control with some `text`
+# A control displaying some `text`
+#
+# For a text-only control, see `Label`.
abstract class TextView
super View
var is_password: nullable Bool is writable
end
-# A pushable button, raises `ButtonPressEvent`
+# A pressable button, raises `ButtonPressEvent`
class Button
super TextView
end
-# A text label
+# A simple text label
class Label
super TextView
end
-# Toggle control with two states and a label
+# Toggle control between two states, also displays a label
class CheckBox
super TextView
return slice(st, ed - st + 1)
end
- # Returns a subset of the content of `self` starting at `from` and of length `count`
+ # Copy a subset of `self` starting at `from` and of `count` bytes
#
# var b = "abcd".to_bytes
# assert b.slice(1, 2).hexdigest == "6263"
return ret
end
- # Returns a copy of `self` starting at `from`
+ # Copy of `self` starting at `from`
#
# var b = "abcd".to_bytes
# assert b.slice_from(1).hexdigest == "626364"
return new FlatString.full(ns, elen, 0, elen)
end
- # Interprets `self` as a big-endian positive integer.
+ # Interprets `self` as a big-endian integer (unsigned by default)
#
# ~~~
# var b = "0102".hexdigest_to_bytes
# assert b.to_i == 258
+ #
+ # assert "01".hexdigest_to_bytes.to_i == 1
+ # assert "FF".hexdigest_to_bytes.to_i == 255
+ # assert "0000".hexdigest_to_bytes.to_i == 0
# ~~~
#
- # Nul bytes on the left are trimmed.
- # 0 is returned for an empty Bytes object.
+ # If `self.is_empty`, 0 is returned.
#
# ~~~
- # assert "01".hexdigest_to_bytes.to_i == 1
- # assert "0001".hexdigest_to_bytes.to_i == 1
- #
- # assert "0000".hexdigest_to_bytes.to_i == 0
- # assert "00".hexdigest_to_bytes.to_i == 0
# assert "".hexdigest_to_bytes.to_i == 0
# ~~~
#
+ # If `signed == true`, the bytes are read as a signed integer.
+ # As usual, the sign bit is the left most bit, no matter the
+ # `length` of `self`.
+ #
+ # ~~~
+ # assert "01".hexdigest_to_bytes.to_i(true) == 1
+ # assert "FF".hexdigest_to_bytes.to_i(true) == -1
+ # assert "00FF".hexdigest_to_bytes.to_i(true) == 255
+ # assert "E0".hexdigest_to_bytes.to_i(true) == -32
+ # assert "FE00".hexdigest_to_bytes.to_i(true) == -512
+ # assert "FEFEFE".hexdigest_to_bytes.to_i(true) == -65794
+ # ~~~
+ #
# `Int::to_bytes` is a loosely reverse method.
#
# ~~~
# assert b.to_i.to_bytes == b
# assert (b.to_i + 1).to_bytes.hexdigest == "0103"
# assert "0001".hexdigest_to_bytes.to_i.to_bytes.hexdigest == "01"
+ #
+ # assert (-32).to_bytes.to_i(true) == -32
# ~~~
#
# Warning: `Int` might overflow for bytes with more than 60 bits.
- fun to_i: Int do
+ fun to_i(signed: nullable Bool): Int do
var res = 0
var i = 0
while i < length do
res += self[i].to_i
i += 1
end
+
+ # Two's complement is `signed`
+ if signed == true and not_empty and first > 0x80u8 then
+ var ff = 0
+ for j in [0..length[ do
+ ff *= 0x100
+ ff += 0xFF
+ end
+
+ res = -((res ^ ff) + 1)
+ end
+
return res
end
length += ln
end
- # Appends the bytes of `s` to `selftextextt`
- fun append_text(s: Text) do
- for i in s.substrings do
- append_ns(i.fast_cstring, i.byte_length)
- end
- end
+ # Appends the bytes of `str` to `self`
+ fun append_text(str: Text) do str.append_to_bytes self
redef fun append_to(b) do b.append self
redef fun enlarge(sz) do
if capacity >= sz then return
persisted = false
+ if capacity < 16 then capacity = 16
while capacity < sz do capacity = capacity * 2 + 2
var ns = new CString(capacity)
items.copy_to(ns, length, 0, 0)
end
redef class Int
- # A big-endian representation of self.
+ # A signed big-endian representation of `self`
#
# ~~~
# assert 1.to_bytes.hexdigest == "01"
# assert 65536.to_bytes.hexdigest == "010000"
# ~~~
#
+ # Negative values are converted to their two's complement.
+ # Be careful as the result can be ambiguous.
+ #
+ # ~~~
+ # assert (-1).to_bytes.hexdigest == "FF"
+ # assert (-32).to_bytes.hexdigest == "E0"
+ # assert (-512).to_bytes.hexdigest == "FE00"
+ # assert (-65794).to_bytes.hexdigest == "FEFEFE"
+ # ~~~
+ #
+ # Optionally, set `n_bytes` to the desired number of bytes in the output.
+ # This setting can disambiguate the result between positive and negative
+ # integers. Be careful with this parameter as the result may overflow.
+ #
+ # ~~~
+ # assert 1.to_bytes(2).hexdigest == "0001"
+ # assert 65535.to_bytes(2).hexdigest == "FFFF"
+ # assert (-1).to_bytes(2).hexdigest == "FFFF"
+ # assert (-512).to_bytes(4).hexdigest == "FFFFFE00"
+ # assert 0x123456.to_bytes(2).hexdigest == "3456"
+ # ~~~
+ #
# For 0, a Bytes object with single nul byte is returned (instead of an empty Bytes object).
#
# ~~~
# assert 0.to_bytes.hexdigest == "00"
# ~~~
#
- # `Bytes::to_i` can be used to do the reverse operation.
+ # For positive integers, `Bytes::to_i` can reverse the operation.
#
# ~~~
# assert 1234.to_bytes.to_i == 1234
# ~~~
#
# Require self >= 0
- fun to_bytes: Bytes do
- if self == 0 then return "\0".to_bytes
- assert self > 0
+ fun to_bytes(n_bytes: nullable Int): Bytes do
+
+ # If 0, force using at least one byte
+ if self == 0 and n_bytes == null then n_bytes = 1
# Compute the len (log256)
var len = 1
var max = 256
- while self >= max do
+ var s = self.abs
+ while s >= max do
len += 1
max *= 256
end
+ # Two's complement
+ s = self
+ if self < 0 then
+ var ff = 0
+ for j in [0..len[ do
+ ff *= 0x100
+ ff += 0xFF
+ end
+
+ s = ((-self) ^ ff) + 1
+ end
+
+ # Cut long values
+ if n_bytes != null and len > n_bytes then len = n_bytes
+
# Allocate the buffer
- var res = new Bytes.with_capacity(len)
- for i in [0..len[ do res[i] = 0u8
+ var cap = n_bytes or else len
+ var res = new Bytes.with_capacity(cap)
+
+ var filler = if self < 0 then 0xFFu8 else 0u8
+ for i in [0..cap[ do res[i] = filler
# Fill it starting with the end
- var i = len
- var sum = self
- while i > 0 do
+ var i = cap
+ var sum = s
+ while i > cap - len do
i -= 1
res[i] = (sum % 256).to_b
sum /= 256
end
+
return res
end
end
redef class FlatText
redef fun append_to_bytes(b) do
var from = if self isa FlatString then first_byte else 0
- b.append_ns_from(items, byte_length, from)
+ if isset _items then b.append_ns_from(items, byte_length, from)
end
end
super Writer
end
-# `Stream` that can be used to write to a `String`
+# Write to `bytes` in memory
#
-# Mainly used for compatibility with Writer type and tests.
-class StringWriter
+# ~~~
+# var writer = new BytesWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "\\x53\\x74\\x72\\x69\\x6E\\x67\\x73\\x20\\x26\\x20\\x62\\x79\\x74\\x65\\x73"
+# assert writer.bytes.to_s == "Strings & bytes"
+# ~~~
+#
+# As with any binary data, UTF-8 code points encoded on two bytes or more
+# can be constructed byte by byte.
+#
+# ~~~
+# writer = new BytesWriter
+#
+# # Write just the character first half
+# writer.write_byte 0xC2u8
+# assert writer.to_s == "\\xC2"
+# assert writer.bytes.to_s == "�"
+#
+# # Complete the character
+# writer.write_byte 0xA2u8
+# assert writer.to_s == "\\xC2\\xA2"
+# assert writer.bytes.to_s == "¢"
+# ~~~
+class BytesWriter
super Writer
- private var content = new Buffer
- redef fun to_s do return content.to_s
- redef fun is_writable do return not closed
+ # Written memory
+ var bytes = new Bytes.empty
- redef fun write_bytes(b) do
- content.append(b.to_s)
- end
+ redef fun to_s do return bytes.chexdigest
redef fun write(str)
do
- assert not closed
- content.append(str)
+ if closed then return
+ str.append_to_bytes bytes
end
redef fun write_char(c)
do
- assert not closed
- content.add(c)
+ if closed then return
+ bytes.add_char c
+ end
+
+ redef fun write_byte(value)
+ do
+ if closed then return
+ bytes.add value
+ end
+
+ redef fun write_bytes(b)
+ do
+ if closed then return
+ bytes.append b
end
# Is the stream closed?
protected var closed = false
redef fun close do closed = true
+ redef fun is_writable do return not closed
end
-# `Stream` used to read from a `String`
+# `Stream` writing to a `String`
#
-# Mainly used for compatibility with Reader type and tests.
-class StringReader
+# This class has the same behavior as `BytesWriter`
+# except for `to_s` which decodes `bytes` to a string.
+#
+# ~~~
+# var writer = new StringWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "Strings & bytes"
+# ~~~
+class StringWriter
+ super BytesWriter
+
+ redef fun to_s do return bytes.to_s
+end
+
+# Read from `bytes` in memory
+#
+# ~~~
+# var reader = new BytesReader(b"a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all_bytes == b"b"
+# ~~~
+class BytesReader
super Reader
- # The string to read from.
- var source: String
+ # Source data to read
+ var bytes: Bytes
- # The current position in the string (bytewise).
- private var cursor: Int = 0
+ # The current position in `bytes`
+ private var cursor = 0
- redef fun read_char do
- if cursor < source.length then
- # Fix when supporting UTF-8
- var c = source[cursor]
- cursor += 1
- return c
- else
- return null
- end
- end
+ redef fun read_char
+ do
+ if cursor >= bytes.length then return null
- redef fun read_byte do
- if cursor < source.length then
- var c = source.bytes[cursor]
- cursor += 1
- return c
- else
- return null
- end
+ var len = bytes.items.length_of_char_at(cursor)
+ var char = bytes.items.char_at(cursor)
+ cursor += len
+ return char
end
- redef fun close do
- source = ""
+ redef fun read_byte
+ do
+ if cursor >= bytes.length then return null
+
+ var c = bytes[cursor]
+ cursor += 1
+ return c
end
- redef fun read_all_bytes do
- var nslen = source.length - cursor
- var nns = new CString(nslen)
- source.copy_to_native(nns, nslen, cursor, 0)
- return new Bytes(nns, nslen, nslen)
+ redef fun close do bytes = new Bytes.empty
+
+ redef fun read_all_bytes
+ do
+ var res = bytes.slice_from(cursor)
+ cursor = bytes.length
+ return res
end
- redef fun eof do return cursor >= source.byte_length
+ redef fun eof do return cursor >= bytes.length
+end
+
+# `Stream` reading from a `String` source
+#
+# This class has the same behavior as `BytesReader`
+# except for its constructor accepting a `String`.
+#
+# ~~~
+# var reader = new StringReader("a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all == "b"
+# ~~~
+class StringReader
+ super BytesReader
+
+ autoinit source
+
+ # Source data to read
+ var source: String
+
+ init do bytes = source.to_bytes
+
+ redef fun close
+ do
+ source = ""
+ super
+ end
end
var curr_pos: Int
- init do target_items = target._items
+ init do if isset target._items then target_items = target._items
redef fun index do return curr_pos
var c = src[pos]
if not c == '<' then return new XMLError(st_loc, "Expected start of tag, got `{c}`")
pos += 1
+ if pos >= src.length then return new XMLError(st_loc, "Malformed tag")
c = src[pos]
if c == '!' then
# Special tag
# Parses an xml tag name
private fun parse_tag_name(delims: Array[Char]): String do
var idst = pos
- var c = src[pos]
var srclen = src.length
- while pos < srclen and not c.is_whitespace and not delims.has(c) do
+ while pos < srclen do
+ var c = src[pos]
+ if c.is_whitespace or delims.has(c) then break
pos += 1
- c = src[pos]
end
return src.substring(idst, pos - idst).trim
end
var dy = 0.0
var text_width = 0.0
var line_sprites = new Array[Sprite]
+ var height = 0.0
+
+ # Has the current line height been added to `height`?
+ var line_height_counted = false
# TextSprite customization
var max_width = text_sprites.max_width
dy -= line_height
if max_height != null and max_height < -dy + line_height then break
dx = 0.0
+ if not line_height_counted then
+ # Force to account for empty lines
+ height += line_height
+ end
+ line_height_counted = false
prev_char = null
continue
else if c == pld then
dy -= partial_line_skip
+ height += partial_line_skip
word_break = true
continue
else if c == plu then
dy += partial_line_skip
+ height -= partial_line_skip # We could keep two heights and return the max
word_break = true
continue
else if c.is_whitespace then
var w = text[wi]
if w == '\n' or w == pld or w == plu or w.is_whitespace or (in_link and w == ']') then break
+
+ if not desc.chars.keys.has(w) then
+ var rc = replacement_char
+ if rc == null then continue
+ w = rc
+ end
+
word_len += advance(prev_w, w) * scale
prev_w = w
end
# Would the line be too long?
if dx + word_len > max_width then
- if text_sprites.wrap then
- # Wrap
- justify(line_sprites, text_sprites.align, dx)
- dy -= line_height
- if max_height != null and max_height < -dy + line_height then break
- dx = 0.0
- else
- # Cut short
- justify(line_sprites, text_sprites.align, dx)
- dy -= line_height
- if max_height != null and max_height < -dy + line_height then break
- dx = 0.0
+ justify(line_sprites, text_sprites.align, dx)
+ dy -= line_height
+ if max_height != null and max_height < -dy + line_height then break
+ dx = 0.0
+ line_height_counted = false
+
+ if not text_sprites.wrap then
+ # Cut short, skip everything until the next new line
while c != '\n' and i < text.length - 1 do
i += 1
c = text[i]
prev_char = c
text_width = text_width.max(dx)
+
+ if not line_height_counted then
+ # Increase `height` only once per line iff there's a caracter
+ line_height_counted = true
+ height += line_height
+ end
end
justify(line_sprites, text_sprites.align, dx)
end
text_sprites.width = text_width.max(dx)
- text_sprites.height = dy + line_height
+ text_sprites.height = height
end
# Character replacing other characters missing from the font
import model_dimensions
import particles
import selection
+import shadow
redef class App
world_camera.reset_height(10.0)
world_camera.near = 0.1
+ # Cull the invisible triangles in the back of the geometries
+ glCullFace gl_BACK
+
# Prepare programs
var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
for program in programs do
# Draw all elements of `actors` and then call `frame_core_flat`
protected fun frame_core_depth(display: GamnitDisplay)
do
- glViewport(0, 0, display.width, display.height)
- frame_core_dynamic_resolution_before display
+ frame_core_depth_clock.lapse
- # Update cameras on both our programs
- versatile_program.use
- versatile_program.mvp.uniform world_camera.mvp_matrix
+ # Compute shadows
+ if light isa LightCastingShadows then
+ frame_core_shadow_prep display
+ perfs["gamnit depth shadows"].add frame_core_depth_clock.lapse
+ end
- normals_program.use
- normals_program.mvp.uniform app.world_camera.mvp_matrix
+ glViewport(0, 0, display.width, display.height)
+ frame_core_dynamic_resolution_before display
+ perfs["gamnit depth dynres"].add frame_core_depth_clock.lapse
- frame_core_depth_clock.lapse
for actor in actors do
for leaf in actor.model.leaves do
- leaf.material.draw(actor, leaf)
+ leaf.material.draw(actor, leaf, app.world_camera)
end
end
perfs["gamnit depth actors"].add frame_core_depth_clock.lapse
perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse
frame_core_dynamic_resolution_after display
+
+ # Debug, show the light point of view
+ #frame_core_shadow_debug display
end
private var frame_core_depth_clock = new Clock
# Usually, there is one `LeafModel` per material.
# At each frame, each material is asked to draw all the live `LeafModel` instaces.
fun leaves: Array[LeafModel] is abstract
+
+ # Sub-models with names, usually declared in the asset file
+ var named_parts = new Map[Text, Model]
end
# Model composed of one or many other `LeafModel`
# This method is called on many materials for many `actor` and `model` at each frame.
# It is expected to use a `GLProgram` and call an equivalent to `glDrawArrays`.
# However, it should not call `glClear` nor `GamnitDisplay::flip`.
- fun draw(actor: Actor, model: LeafModel) do end
+ fun draw(actor: Actor, model: LeafModel, camera: Camera) do end
end
# Mesh with all geometry data
end
# Source of light
-#
-# Instances of this class define a light source position and type.
-class Light
-
- # TODO point light, spotlight, directional light, etc.
+abstract class Light
# Center of this light source in world coordinates
var position = new Point3d[Float](0.0, 1000.0, 0.0)
end
+# Basic light projected from a single point
+class PointLight
+ super Light
+end
+
+# Source of light casting shadows
+abstract class LightCastingShadows
+ super Light
+
+ # View from the camera, used for shadow mapping, implemented by sub-classes
+ fun camera: Camera is abstract
+end
+
redef class App
# Live actors to be drawn on screen
var actors = new Array[Actor]
# Single light of the scene
- var light = new Light
+ var light: Light = new PointLight is writable
# TODO move `actors & light` to a scene object
# TODO support more than 1 light
--- /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.
+
+# More implementations of `Light`
+module more_lights
+
+import depth_core
+intrude import cameras_cache
+
+# TODO
+#class PointLight
+#class Spotlight
+
+# Sun-like light projecting parallel rays
+class ParallelLight
+ super LightCastingShadows
+
+ # Angle to the light source, around the X axis
+ var pitch = 0.0 is writable
+
+ # Angle to the light source, around the Y axis
+ var yaw = 0.0 is writable
+
+ # Depth texture width, in world coordinates
+ var width = 100.0 is writable
+
+ # Depth texture height, in world coordinates
+ var height = 100.0 is writable
+
+ # Viewport depth, centered on `app.world_camera`
+ var depth = 500.0 is writable
+
+ redef var camera = new ParallelLightCamera(app.display.as(not null), self) is lazy
+end
+
+private class ParallelLightCamera
+ super Camera
+
+ var light: ParallelLight
+
+ # Rotation matrix produced by the current rotation of the camera
+ fun rotation_matrix: Matrix
+ do
+ var view = new Matrix.identity(4)
+ view.rotate(light.yaw, 0.0, 1.0, 0.0)
+ view.rotate(light.pitch, 1.0, 0.0, 0.0)
+ return view
+ end
+
+ private fun create_mvp_matrix: Matrix
+ do
+ var near = -light.depth/2.0
+ var far = light.depth/2.0
+
+ var view = new Matrix.identity(4)
+ view.translate(-position.x, -position.y, -position.z)
+ view = view * rotation_matrix
+ var projection = new Matrix.orthogonal(-light.width/2.0, light.width/2.0,
+ -light.height/2.0, light.height/2.0,
+ near, far)
+ return view * projection
+ end
+
+ redef fun mvp_matrix
+ do
+ var m = mvp_matrix_cache
+ if m == null or check_position_changed then
+ m = create_mvp_matrix
+ mvp_matrix_cache = m
+ end
+ return m
+ end
+
+ private var pitch_cache = 0.0
+ private var yaw_cache = 0.0
+ private var width_cache = 0.0
+ private var height_cache = 0.0
+ private var depth_cache = 0.0
+
+ redef fun check_position_changed
+ do
+ if super then return true
+
+ if light.pitch != pitch_cache or
+ light.yaw != yaw_cache or
+ light.width != width_cache or
+ light.height != height_cache or
+ light.depth != depth_cache then
+ pitch_cache = light.pitch
+ yaw_cache = light.yaw
+ width_cache = light.width
+ height_cache = light.height
+ depth_cache = light.depth
+ return true
+ end
+
+ return false
+ end
+end
intrude import depth_core
intrude import flat
+intrude import shadow
+import more_lights
redef class Material
# Get the default blueish material
# The RGB values should be premultiplied by the alpha value.
var specular_color: Array[Float] is writable
- redef fun draw(actor, model)
+ redef fun draw(actor, model, camera)
do
var program = app.versatile_program
program.use
+ program.mvp.uniform camera.mvp_matrix
var mesh = model.mesh
program.use_map_specular.uniform false
program.tex_coord.array_enabled = false
- # Lights
- program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
-
# Camera
- program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+ program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
# Colors from the material
var a = actor.alpha
program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
specular_color[2]*a, specular_color[3]*a)
+ setup_lights(actor, model, camera, program)
+
# Execute draw
if mesh.indices.is_empty then
glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
end
end
+
+ private fun setup_lights(actor: Actor, model: LeafModel, camera: Camera, program: BlinnPhongProgram)
+ do
+ # TODO use a list of lights
+
+ # Light, for Lambert and Blinn-Phong
+ var light = app.light
+ if light isa ParallelLight then
+ program.light_kind.uniform 1
+
+ # Vector parallel to the light source
+ program.light_center.uniform(
+ -light.pitch.sin * light.yaw.sin,
+ light.pitch.cos,
+ -light.yaw.cos)
+ else if light isa PointLight then
+ program.light_kind.uniform 2
+
+ # Position of the light source
+ program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
+ else
+ program.light_kind.uniform 0
+ end
+
+ # Draw projected shadows?
+ if not light isa LightCastingShadows or not app.shadow_depth_texture_available then
+ program.use_shadows.uniform false
+ return
+ else program.use_shadows.uniform true
+
+ # Light point of view
+ program.light_mvp.uniform light.camera.mvp_matrix
+
+ # Depth texture
+ glActiveTexture gl_TEXTURE4
+ glBindTexture(gl_TEXTURE_2D, app.shadow_context.depth_texture)
+ program.depth_texture.uniform 4
+ program.depth_texture_size.uniform app.shadow_resolution.to_f
+ program.depth_texture_taps.uniform 2 # TODO make configurable
+ end
end
# Material with potential `diffuse_texture` and `specular_texture`
# Bump map TODO
private var normals_texture: nullable Texture = null is writable
- redef fun draw(actor, model)
+ redef fun draw(actor, model, camera)
do
var mesh = model.mesh
program.use_map_bump.uniform false
end
+ program.mvp.uniform camera.mvp_matrix
program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
program.scale.uniform actor.scale
var xd = sample_used_texture.offset_right - xa
var ya = sample_used_texture.offset_top
var yd = sample_used_texture.offset_bottom - ya
- xd *= 0.999
- yd *= 0.999
var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
for i in [0..mesh.texture_coords.length/2[ do
program.normal.array_enabled = true
program.normal.array(mesh.normals, 3)
- program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
- program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+ # Light
+ setup_lights(actor, model, camera, program)
+
+ # Camera
+ program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
if mesh.indices.is_empty then
glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
class NormalsMaterial
super Material
- redef fun draw(actor, model)
+ redef fun draw(actor, model, camera)
do
var program = app.normals_program
program.use
- program.mvp.uniform app.world_camera.mvp_matrix
+ program.mvp.uniform camera.mvp_matrix
var mesh = model.mesh
// Vertex normal
attribute vec3 normal;
- // Model view projection matrix
+ // Camera model view projection matrix
uniform mat4 mvp;
uniform mat4 rotation;
// Lights config
+ uniform lowp int light_kind;
uniform vec3 light_center;
+ uniform mat4 light_mvp;
// Coordinates of the camera
uniform vec3 camera;
varying vec3 v_normal;
varying vec4 v_to_light;
varying vec4 v_to_camera;
+ varying vec4 v_depth_pos;
void main()
{
vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
gl_Position = pos * mvp;
+ v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
// Pass varyings to the fragment shader
v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
v_normal = normalize(vec4(normal, 0.0) * rotation).xyz;
- v_to_light = normalize(vec4(light_center, 1.0) - pos);
v_to_camera = normalize(vec4(camera, 1.0) - pos);
+
+ if (light_kind == 0) {
+ // No light
+ } else if (light_kind == 1) {
+ // Parallel
+ v_to_light = normalize(vec4(light_center, 1.0));
+ } else {
+ // Point light (and others?)
+ v_to_light = normalize(vec4(light_center, 1.0) - pos);
+ }
}
""" @ glsl_vertex_shader
varying vec3 v_normal;
varying vec4 v_to_light;
varying vec4 v_to_camera;
+ varying vec4 v_depth_pos;
// Colors
uniform vec4 ambient_color;
uniform bool use_map_normal;
uniform sampler2D map_normal;
+ // Shadow
+ uniform lowp int light_kind;
+ uniform bool use_shadows;
+ uniform sampler2D depth_texture;
+ uniform float depth_texture_size;
+ uniform int depth_texture_taps;
+
+ // Shadow effect on the diffuse colors of the fragment at offset `x, y`
+ float shadow_lookup(vec2 depth_coord, float x, float y) {
+ float tap_width = 1.0;
+ float pixel_size = tap_width/depth_texture_size;
+
+ vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
+ y * pixel_size * v_depth_pos.w);
+ depth_coord += offset;
+
+ float depth = v_depth_pos.z/v_depth_pos.w;
+ //vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+ if (depth_coord.x < 0.0 || depth_coord.x > 1.0 || depth_coord.y < 0.0 || depth_coord.y > 1.0) {
+ // Out of the shadow map texture
+ //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // debug, red out of the light view
+ return 1.0;
+ }
+
+ float shadow_depth = texture2D(depth_texture, depth_coord).r;
+ float bias = 0.0001;
+ if (shadow_depth == 1.0) {
+ // Too far to be in depth texture
+ return 1.0;
+ } else if (shadow_depth <= depth - bias) {
+ // In a shadow
+ //gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // debug, blue shadows
+ return 0.2; // TODO replace with a configurable ambient light
+ }
+
+ //gl_FragColor = vec4(0.0, 1.0-(shadow_depth-depth), 0.0, 1.0); // debug, green lit surfaces
+ return 1.0;
+ }
+
+ // Shadow effect on the diffuse colors of the fragment
+ float shadow() {
+ if (!use_shadows) return 1.0;
+
+ vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+
+ float taps = float(depth_texture_taps);
+ float tap_step = 2.00/taps;
+ float sum = 0.0;
+ for (float x = -1.0; x <= 0.99; x += tap_step)
+ for (float y = -1.0; y <= 0.99; y += tap_step)
+ sum += shadow_lookup(depth_coord, x, y);
+ return sum / taps / taps;
+ }
+
void main()
{
// Normal
vec4 ambient = ambient_color;
if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
- // Diffuse Lambert light
- vec3 to_light = v_to_light.xyz;
- float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
+ if (light_kind == 0) {
+ // No light, show diffuse and ambient
- vec4 diffuse = lambert * diffuse_color;
- if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+ vec4 diffuse = diffuse_color;
+ if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
- // Specular Phong light
- float s = 0.0;
- if (lambert > 0.0) {
- vec3 l = reflect(-to_light, normal);
- s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
- s = pow(s, 8.0); // TODO make this `shininess` a material attribute
- }
+ gl_FragColor = ambient + diffuse;
+ } else {
+ // Parallel light or point light (1 or 2)
+
+ // Diffuse Lambert light
+ vec3 to_light = v_to_light.xyz;
+ float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
+
+ vec4 diffuse = lambert * diffuse_color;
+ if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+
+ // Specular Phong light
+ float s = 0.0;
+ if (lambert > 0.0) {
+ // In light
+ vec3 l = reflect(-to_light, normal);
+ s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
+ s = pow(s, 8.0); // TODO make this `shininess` a material attribute
+
+ // Shadows
+ diffuse *= shadow();
+ }
- vec4 specular = s * specular_color;
- if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+ vec4 specular = s * specular_color;
+ if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+
+ gl_FragColor = ambient + diffuse + specular;
+ }
- gl_FragColor = ambient + diffuse + specular;
if (gl_FragColor.a < 0.01) discard;
- //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug
+ //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug normals
}
""" @ glsl_fragment_shader
# Should this program use the texture `map_diffuse`?
var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
- # Diffuser texture unit
+ # Diffuse texture unit
var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
# Should this program use the texture `map_specular`?
# Specular color
var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
- # Center position of the light
+ # Kind of lights: 0 -> no light, 1 -> parallel, 2 -> point
+ var light_kind = uniforms["light_kind"].as(UniformInt) is lazy
+
+ # Center position of the light *or* vector to parallel light source
var light_center = uniforms["light_center"].as(UniformVec3) is lazy
+ # Light model view projection matrix
+ var light_mvp = uniforms["light_mvp"].as(UniformMat4) is lazy
+
+ # Should shadow be drawn? Would use `depth_texture` and `light_mvp`.
+ var use_shadows = uniforms["use_shadows"].as(UniformBool) is lazy
+
+ # Diffuse texture unit
+ var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
+
+ # Size, in pixels, of `depth_texture`
+ var depth_texture_size = uniforms["depth_texture_size"].as(UniformFloat) is lazy
+
+ # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
+ var depth_texture_taps = uniforms["depth_texture_taps"].as(UniformInt) is lazy
+
# Camera position
var camera = uniforms["camera"].as(UniformVec3) is lazy
# Scaling per vertex
var scale = uniforms["scale"].as(UniformFloat) is lazy
- # Model view projection matrix
+ # Camera model view projection matrix
var mvp = uniforms["mvp"].as(UniformMat4) is lazy
end
redef fun load
do
+ if loaded then return
+
var ext = path.file_extension
if ext == "obj" then
load_obj_file
loaded = true
end
+ private fun lazy_load
+ do
+ if loaded then return
+
+ # Lazy load
+ load
+
+ # Print errors when lazy loading only
+ if errors.length == 1 then
+ print_error errors.first
+ else if errors.length > 1 then
+ print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
+ print_error errors.join("\n* ")
+ end
+ end
+
private fun load_obj_file
do
# Read .obj description from assets
if debug_gamnit then assert obj_def.is_coherent
# Build models
- var converter = new ModelFromObj(path, obj_def)
- converter.models leaves_cache
+ var converter = new BuildModelFromObj(path, obj_def)
+ converter.fill_leaves self
errors.add_all converter.errors
end
redef fun leaves
do
- if not loaded then
- # Lazy load
- load
-
- # Print errors when lazy loading only
- if errors.length == 1 then
- print_error errors.first
- else if errors.length > 1 then
- print_error "Loading model at '{path}' raised {errors.length} errors:\n* "
- print_error errors.join("\n* ")
- end
- end
-
+ lazy_load
return leaves_cache
end
private var leaves_cache = new Array[LeafModel]
+
+ redef fun named_parts
+ do
+ lazy_load
+ return named_leaves_cache
+ end
+
+ private var named_leaves_cache = new Map[String, Model]
end
-# Short-lived service to convert an `ObjDef` to `models`
+# Short-lived service to convert an `ObjDef` to `fill_leaves`
#
# Limitations: This service only support faces with 3 or 4 vertices.
# Faces with more vertices should be triangulated by the modeling tool.
-private class ModelFromObj
+private class BuildModelFromObj
# Path to the .obj file in the assets folder, used to find .mtl files
var path: String
# Parsed .obj definition
var obj_def: ObjDef
- # Errors raised by calls to `models`
+ # Errors raised by calls to `fill_leaves`
var errors = new Array[Error]
- # Fill `leaves` with models described in `obj_def`
- fun models(leaves: Array[LeafModel])
+ # Fill `leaves` with objects described in `obj_def`
+ fun fill_leaves(target_model: ModelAsset)
do
- # Sort faces by material
- var mtl_to_faces = new MultiHashMap[String, ObjFace]
- for face in obj_def.faces do
- var mtl_lib_name = face.material_lib
- var mtl_name = face.material_name
-
- var full_name = ""
- if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+ var leaves = target_model.leaves_cache
- mtl_to_faces[full_name].add face
+ # Sort faces by material
+ var obj_mtl_to_faces = new Map[ObjObj, MultiHashMap[String, ObjFace]]
+ for obj in obj_def.objects do
+ var mtl_to_faces = new MultiHashMap[String, ObjFace]
+ obj_mtl_to_faces[obj] = mtl_to_faces
+ for face in obj.faces do
+ var mtl_lib_name = face.material_lib
+ var mtl_name = face.material_name
+
+ var full_name = ""
+ if mtl_lib_name != null and mtl_name != null then full_name = mtl_lib_name / mtl_name
+
+ mtl_to_faces[full_name].add face
+ end
end
# Load material libs
mtl_libs[asset_path] = mtl_lib
end
- # Create 1 mesh per material, and prepare materials
+ # Create 1 mesh per material per object, and prepare materials
var mesh_to_mtl = new Map[Mesh, nullable MtlDef]
+ var mesh_to_name = new Map[Mesh, String]
var texture_names = new Set[String]
- for full_name, faces in mtl_to_faces do
-
- # Create mesh
- var mesh = new Mesh
- mesh.vertices = vertices(faces)
- mesh.normals = normals(faces)
- mesh.texture_coords = texture_coords(faces)
-
- # Material
- var mtl_def = null
-
- var mtl_lib_name = faces.first.material_lib
- var mtl_name = faces.first.material_name
- if mtl_lib_name != null and mtl_name != null then
- var asset_path = self.path.dirname / mtl_lib_name
- var mtl_lib = mtl_libs[asset_path]
- var mtl = mtl_lib.get_or_null(mtl_name)
- if mtl != null then
- mtl_def = mtl
-
- for e in mtl.maps do
- texture_names.add self.path.dirname / e
+ for obj in obj_def.objects do
+ var mtl_to_faces = obj_mtl_to_faces[obj]
+ for mtl_path, faces in mtl_to_faces do
+
+ # Create mesh
+ var mesh = new Mesh
+ mesh.vertices = vertices(faces)
+ mesh.normals = normals(faces)
+ mesh.texture_coords = texture_coords(faces)
+
+ # Material
+ var mtl_def = null
+
+ var mtl_lib_name = faces.first.material_lib
+ var mtl_name = faces.first.material_name
+ if mtl_lib_name != null and mtl_name != null then
+ var asset_path = self.path.dirname / mtl_lib_name
+ var mtl_lib = mtl_libs[asset_path]
+ var mtl = mtl_lib.get_or_null(mtl_name)
+ if mtl != null then
+ mtl_def = mtl
+
+ for e in mtl.maps do
+ texture_names.add self.path.dirname / e
+ end
+ else
+ errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
end
- else
- errors.add new Error("Error loading model at '{path}': mtl '{mtl_name}' not found in '{asset_path}'")
end
- end
- mesh_to_mtl[mesh] = mtl_def
+ mesh_to_mtl[mesh] = mtl_def
+ mesh_to_name[mesh] = obj.name
+ end
end
# Load textures need for these materials
end
# Create models and store them
+ var name_to_leaves = new MultiHashMap[String, LeafModel]
for mesh, mtl_def in mesh_to_mtl do
+
var material = materials.get_or_null(mtl_def)
if material == null then material = new Material
var model = new LeafModel(mesh, material)
leaves.add model
+
+ name_to_leaves[mesh_to_name[mesh]].add model
+ end
+
+ # Collect objects with a name
+ for name, models in name_to_leaves do
+ if models.length == 1 then
+ target_model.named_leaves_cache[name] = models.first
+ else
+ var named_model = new CompositeModel
+ named_model.leaves.add_all models
+ target_model.named_leaves_cache[name] = named_model
+ end
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.
+
+# Shadow mapping using a depth texture
+#
+# The default light does not cast any shadows. It can be changed to a
+# `ParallelLight` in client games to cast sun-like shadows:
+#
+# ~~~
+# import more_lights
+#
+# var sun = new ParallelLight
+# sun.pitch = 0.25*pi
+# sun.yaw = 0.25*pi
+# app.light = sun
+# ~~~
+module shadow
+
+intrude import gamnit::depth_core
+
+redef class App
+
+ # Resolution of the shadow texture, defaults to 4096 pixels
+ #
+ # TODO make configurable / ask the hardware for gl_MAX_TEXTURE_SIZE
+ var shadow_resolution = 4096
+
+ # Are shadows supported by the current hardware configuration?
+ #
+ # The implementation may change in the future, but it currently relies on
+ # the GL extension `GL_EOS_depth_texture`.
+ var supports_shadows: Bool is lazy do
+ return display.as(not null).gl_extensions.has("GL_OES_depth_texture")
+ end
+
+ # Is `shadow_context.depth_texture` ready to be used?
+ fun shadow_depth_texture_available: Bool
+ do return supports_shadows and shadow_context.depth_texture != -1
+
+ private var shadow_depth_program = new ShadowDepthProgram
+
+ private var perf_clock_shadow = new Clock is lazy
+
+ redef fun on_create
+ do
+ super
+
+ var program = shadow_depth_program
+ program.compile_and_link
+ var error = program.error
+ assert error == null else print_error error
+ end
+
+ private var shadow_context: ShadowContext = create_shadow_context is lazy
+
+ private fun create_shadow_context: ShadowContext
+ do
+ var display = display
+ assert display != null
+
+ var context = new ShadowContext
+ context.prepare_once(display, shadow_resolution)
+ return context
+ end
+
+ # Update the depth texture from the light point of view
+ #
+ # This method updates `shadow_context.depth_texture`.
+ protected fun frame_core_shadow_prep(display: GamnitDisplay)
+ do
+ if not supports_shadows then return
+
+ var light = app.light
+ if not light isa LightCastingShadows then return
+
+ perf_clock_shadow.lapse
+
+ # Make sure there's no errors pending
+ assert glGetError == gl_NO_ERROR
+
+ # Bind the framebuffer and make sure it is OK
+ glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.light_view_framebuffer)
+ assert glGetError == gl_NO_ERROR
+ assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+ # Draw to fill the depth texture and only the depth
+ glViewport(0, 0, shadow_resolution, shadow_resolution)
+ glColorMask(false, false, false, false)
+ glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
+ assert glGetError == gl_NO_ERROR
+
+ # Update light position
+ var camera = light.camera
+ camera.position.x = app.world_camera.position.x
+ camera.position.y = app.world_camera.position.y
+ camera.position.z = app.world_camera.position.z
+
+ # Draw all actors
+ for actor in actors do
+ for leaf in actor.model.leaves do
+ leaf.material.draw_depth(actor, leaf, camera)
+ end
+ end
+
+ # Take down, bring back default values
+ glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
+ glColorMask(true, true, true, true)
+
+ perfs["gamnit shadows prep"].add perf_clock_shadow.lapse
+ end
+
+ # ---
+ # Debug: show light view in the bottom left of the screen
+
+ # Lazy load the debugging program
+ private var shadow_debug_program: LightPointOfViewProgram is lazy do
+ var program = new LightPointOfViewProgram
+ program.compile_and_link
+ var error = program.error
+ assert error == null else print_error error
+ return program
+ end
+
+ # Draw the light view in the bottom left of the screen, for debugging only
+ #
+ # The shadow depth texture is a square that can be deformed by this projection.
+ protected fun frame_core_shadow_debug(display: GamnitDisplay)
+ do
+ if not supports_shadows then
+ print_error "Error: Shadows are not supported by the current hardware configuration"
+ return
+ end
+
+ perf_clock_shadow.lapse
+
+ var program = shadow_debug_program
+
+ glBindBuffer(gl_ARRAY_BUFFER, shadow_context.buffer_array)
+ glViewport(0, 0, display.width/3, display.height/3)
+ glClear gl_DEPTH_BUFFER_BIT
+ program.use
+
+ # Uniforms
+ glActiveTexture gl_TEXTURE0
+ glBindTexture(gl_TEXTURE_2D, shadow_context.depth_texture)
+ program.texture.uniform 0
+
+ # Attributes
+ var sizeof_gl_float = 4
+ var n_floats = 3
+ glEnableVertexAttribArray program.coord.location
+ glVertexAttribPointeri(program.coord.location, n_floats, gl_FLOAT, false, 0, 0)
+ var offset = 4 * n_floats * sizeof_gl_float
+
+ n_floats = 2
+ glEnableVertexAttribArray program.tex_coord.location
+ glVertexAttribPointeri(program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset)
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # Draw
+ glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # Take down
+ glBindBuffer(gl_ARRAY_BUFFER, 0)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ sys.perfs["gamnit shadow debug"].add app.perf_clock_shadow.lapse
+ end
+end
+
+# Handles to reused GL buffers and texture
+private class ShadowContext
+
+ # Real screen framebuffer
+ var screen_framebuffer: Int = -1
+
+ # Framebuffer for the light point of view
+ var light_view_framebuffer: Int = -1
+
+ # Depth attached to `light_view_framebuffer`
+ var depth_texture: Int = -1
+
+ # Buffer name for vertex data
+ var buffer_array: Int = -1
+
+ # Prepare all attributes once per resolution change
+ fun prepare_once(display: GamnitDisplay, shadow_resolution: Int)
+ do
+ assert display.gl_extensions.has("GL_OES_depth_texture")
+
+ # Set aside the real screen framebuffer name
+ var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
+ self.screen_framebuffer = screen_framebuffer
+
+ # Framebuffer
+ var framebuffer = glGenFramebuffers(1).first
+ glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
+ assert glIsFramebuffer(framebuffer)
+ self.light_view_framebuffer = framebuffer
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # Depth & texture/color
+ var textures = glGenTextures(1)
+ self.depth_texture = textures[0]
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ resize(display, shadow_resolution)
+ assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+ # Array buffer
+ buffer_array = glGenBuffers(1).first
+ glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
+ assert glIsBuffer(buffer_array)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ ## coord
+ var data = new Array[Float]
+ data.add_all([-1.0, -1.0, 0.0,
+ 1.0, -1.0, 0.0,
+ -1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0])
+ ## tex_coord
+ data.add_all([0.0, 0.0,
+ 1.0, 0.0,
+ 0.0, 1.0,
+ 1.0, 1.0])
+ var c_data = new GLfloatArray.from(data)
+ glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW)
+
+ glBindBuffer(gl_ARRAY_BUFFER, 0)
+
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+ end
+
+ # Init size or resize `depth_texture`
+ fun resize(display: GamnitDisplay, shadow_resolution: Int)
+ do
+ glBindFramebuffer(gl_FRAMEBUFFER, light_view_framebuffer)
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # Depth texture
+ var depth_texture = self.depth_texture
+ glActiveTexture gl_TEXTURE0
+ glBindTexture(gl_TEXTURE_2D, depth_texture)
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
+ glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers
+ #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...)
+
+ glTexImage2D(gl_TEXTURE_2D, 0, gl_DEPTH_COMPONENT,
+ shadow_resolution, shadow_resolution,
+ 0, gl_DEPTH_COMPONENT, gl_UNSIGNED_SHORT, new Pointer.nul)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ glFramebufferTexture2D(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_TEXTURE_2D, depth_texture, 0)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+
+ # Check if the framebuffer is complete and valid
+ assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
+
+ # Take down
+ glBindTexture(gl_TEXTURE_2D, 0)
+ glBindFramebuffer(gl_FRAMEBUFFER, 0)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+ end
+
+ var destroyed = false
+
+ fun destroy
+ do
+ if destroyed then return
+ destroyed = true
+
+ # Free the buffer
+ glDeleteBuffers([buffer_array])
+ var gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+ buffer_array = -1
+
+ # Free the array and framebuffer plus its attachments
+ glDeleteBuffers([buffer_array])
+ glDeleteFramebuffers([light_view_framebuffer])
+ glDeleteTextures([depth_texture])
+ end
+end
+
+redef class Material
+ # Optimized draw of `model`, a part of `actor`, from the view of `camera`
+ #
+ # This drawing should only produce usable depth data. The default behavior,
+ # uses `shadow_depth_program`.
+ protected fun draw_depth(actor: Actor, model: LeafModel, camera: Camera)
+ do
+ var program = app.shadow_depth_program
+ program.use
+ program.mvp.uniform camera.mvp_matrix
+
+ var mesh = model.mesh
+
+ program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
+ program.scale.uniform actor.scale
+ program.use_map_diffuse.uniform false
+
+ program.tex_coord.array_enabled = true
+ program.tex_coord.array(mesh.texture_coords, 2)
+
+ program.coord.array_enabled = true
+ program.coord.array(mesh.vertices, 3)
+
+ program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+
+ if mesh.indices.is_empty then
+ glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
+ else
+ glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+ end
+ end
+
+end
+
+# Efficiently draw actors from the light view
+class ShadowDepthProgram
+ super GamnitProgramFromSource
+
+ redef var vertex_shader_source = """
+ // Vertex coordinates
+ attribute vec4 coord;
+
+ // Vertex translation
+ uniform vec4 translation;
+
+ // Vertex scaling
+ uniform float scale;
+
+ // Vertex coordinates on textures
+ attribute vec2 tex_coord;
+
+ // Vertex normal
+ attribute vec3 normal;
+
+ // Model view projection matrix
+ uniform mat4 mvp;
+
+ // Rotation matrix
+ uniform mat4 rotation;
+
+ // Output for the fragment shader
+ varying vec2 v_tex_coord;
+
+ void main()
+ {
+ vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
+ gl_Position = pos * mvp;
+
+ // Pass varyings to the fragment shader
+ v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
+ }
+ """ @ glsl_vertex_shader
+
+ redef var fragment_shader_source = """
+ precision mediump float;
+
+ // Diffuse map
+ uniform bool use_map_diffuse;
+ uniform sampler2D map_diffuse;
+
+ varying vec2 v_tex_coord;
+
+ void main()
+ {
+ if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) {
+ discard;
+ }
+ }
+ """ @ glsl_fragment_shader
+
+ # Vertices coordinates
+ var coord = attributes["coord"].as(AttributeVec4) is lazy
+
+ # Should this program use the texture `map_diffuse`?
+ var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
+
+ # Diffuse texture unit
+ var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
+
+ # Coordinates on the textures, per vertex
+ var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+ # Diffuse color
+ var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
+
+ # Translation applied to each vertex
+ var translation = uniforms["translation"].as(UniformVec4) is lazy
+
+ # Rotation matrix
+ var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+
+ # Scaling per vertex
+ var scale = uniforms["scale"].as(UniformFloat) is lazy
+
+ # Model view projection matrix
+ var mvp = uniforms["mvp"].as(UniformMat4) is lazy
+end
+
+# Draw the camera point of view on screen
+private class LightPointOfViewProgram
+ super GamnitProgramFromSource
+
+ redef var vertex_shader_source = """
+ // Vertex coordinates
+ attribute vec3 coord;
+
+ // Vertex coordinates on textures
+ attribute vec2 tex_coord;
+
+ // Output to the fragment shader
+ varying vec2 v_coord;
+
+ void main()
+ {
+ gl_Position = vec4(coord, 1.0);
+ v_coord = tex_coord;
+ }
+ """ @ glsl_vertex_shader
+
+ redef var fragment_shader_source = """
+ precision mediump float;
+
+ // Virtual screen texture / color attachment
+ uniform sampler2D texture0;
+
+ // Input from the vertex shader
+ varying vec2 v_coord;
+
+ void main()
+ {
+ gl_FragColor = texture2D(texture0, v_coord);
+ }
+ """ @ glsl_fragment_shader
+
+ # Vertices coordinates
+ var coord = attributes["coord"].as(AttributeVec3) is lazy
+
+ # Coordinates on the textures, per vertex
+ var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
+
+ # Visible texture
+ var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
+end
#
# The implementation varies per platform.
fun feed_events do end
+
+ # Extensions to OpenGL ES 2.0 supported by the current configuration
+ var gl_extensions: Array[String] is lazy do return glGetString(gl_EXTENSIONS).split(' ')
end
jni_env.pop_local_frame
end
end
+
+redef class Pointer
+ # Disable out premultiply as we use only the one from Android
+ redef fun premultiply_alpha(width, height) do end
+end
# Audio support
var inited = mix.initialize(mix_init_flags)
- assert inited != mix_init_flags else
+ if inited != mix_init_flags then
print_error "Failed to load SDL2 mixer format supports: {mix.error}"
end
- var opened = mix.open_audio(44100, mix.default_format, 2, 1024)
- assert opened else
+ var open = mix.open_audio(44100, mix.default_format, 2, 1024)
+ if not open then
print_error "Failed to initialize SDL2 mixer: {mix.error}"
end
# SDL2 mixer initialization flags
#
- # Defaults to all available formats.
- var mix_init_flags: MixInitFlags = mix.flac | mix.mod | mix.mp3 | mix.ogg is lazy, writable
+ # Defaults to FLAC, MP3 and OGG.
+ var mix_init_flags: MixInitFlags = mix.flac | mix.mp3 | mix.ogg is lazy, writable
# Close the SDL display
fun close_sdl
# Draw
glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
+ gl_error = glGetError
+ assert gl_error == gl_NO_ERROR else print_error gl_error
+ # Take down
+ glBindBuffer(gl_ARRAY_BUFFER, 0)
gl_error = glGetError
assert gl_error == gl_NO_ERROR else print_error gl_error
end
end
-# Program drawing the dynamic screen to the real screen
+# Handles to reused GL buffers and texture
private class DynamicContext
# Real screen framebuffer
# Buffer name for vertex data
var buffer_array: Int = -1
- var float_per_vertex: Int is lazy do return 4 + 4 + 3
-
# Prepare all attributes once per resolution change
fun prepare_once(display: GamnitDisplay, max_dynamic_resolution_ratio: Float)
do
# Depth
glBindRenderbuffer(gl_RENDERBUFFER, depthbuffer)
assert glIsRenderbuffer(depthbuffer)
- glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPNENT16, width, height)
+ glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPONENT16, width, height)
glFramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_RENDERBUFFER, depthbuffer)
var gl_error = glGetError
assert gl_error == gl_NO_ERROR else print_error gl_error
# Asset font used to display text
var font = new BMFontAsset("Josefin_Sans/font.fnt")
- # Anchor texture used to identify the anchor coordinate of each `TextSprites`
+ # Anchor texture identifying the anchor coordinates of each `TextSprites`
var anchor = new Texture("anchor.png")
+ # Bottom right corner
+ var corner = new Texture("corner.png")
+
redef fun on_create
do
super
# Add the anchor effects to all TextSprites
for t in texts do ui_sprites.add new Sprite(anchor, t.anchor)
+
+ for t in texts do
+ # Bottom right
+ var br = t.anchor.offset(t.width*(1.0-t.align), -t.height*(1.0-t.valign), 1.0)
+ ui_sprites.add new Sprite(corner, br)
+ end
end
redef fun accept_event(event)
var display = new GamnitDisplay
display.setup
self.display = display
+
+ # Print the current GL configuration, for debugging
+ print "GL vendor: {glGetString(gl_VENDOR)}"
+ print "GL renderer: {glGetString(gl_RENDERER)}"
+ print "GL version: {glGetString(gl_VERSION)}"
+ print "GLSL version: {glGetString(gl_SHADING_LANGUAGE_VERSION)}"
+ print "GL extensions: {glGetString(gl_EXTENSIONS)}"
end
# Core of the frame logic, executed only when the display is visible
# var parser = new ObjFileParser(obj_src)
# var parsed_obj = parser.parse
# assert parsed_obj.is_coherent
+# assert parsed_obj.objects.first.name == "Cube"
# ~~~
class ObjFileParser
super StringProcessor
# Execute parsing of `src` to extract an `ObjDef`
fun parse: nullable ObjDef
do
+ var obj_obj = null
while not eof do
var token = read_token
if token.is_empty or token == "#" then
geometry.params.add vec
else if token == "f" then # Faces
var face = read_face
- geometry.faces.add face
+ if obj_obj != null then obj_obj.faces.add face
else if token == "mtllib" then
current_material_lib = read_until_eol_or_comment
else if token == "usemtl" then
# TODO other line type headers
else if token == "s" then
else if token == "o" then
+ obj_obj = new ObjObj(read_until_eol_or_comment)
+ geometry.objects.add obj_obj
else if token == "g" then
end
skip_eol
# Surface parameters
var params = new Array[Vec3]
- # Faces
- var faces = new Array[ObjFace]
+ # Sub-objects
+ var objects = new Array[ObjObj]
# Relative paths to referenced material libraries
fun material_libs: Set[String] do
var libs = new Set[String]
- for face in faces do
- var lib = face.material_lib
- if lib != null then libs.add lib
+ for obj in objects do
+ for face in obj.faces do
+ var lib = face.material_lib
+ if lib != null then libs.add lib
+ end
end
return libs
end
# be executed at each execution of a game.
fun is_coherent: Bool
do
- for f in faces do
- if f.vertices.length < 3 then return error("ObjFace with less than 3 vertices")
+ for obj in objects do
+ for f in obj.faces do
+ if f.vertices.length < 3 then return error("Face with less than 3 vertices")
+ end
- for v in f.vertices do
+ for f in obj.faces do for v in f.vertices do
var i = v.vertex_point_index
if i < 1 then return error("Vertex point index < 1")
if i > vertex_points.length then return error("Vertex point index > than length")
end
end
+# Sub-object within an `ObjDef`
+class ObjObj
+
+ # Sub-object name as declared in the source file
+ var name: String
+
+ # Sub-object faces
+ var faces = new Array[ObjFace]
+end
+
# Flat surface of an `ObjDef`
class ObjFace
- # Vertex composing this surface, thene should be 3 or more
+ # Vertex composing this surface, there should be 3 or more
var vertices = new Array[ObjVertex]
# Relative path to the .mtl material lib
var socket: nullable TCPStream = null
# Is this connection connected?
- fun connected: Bool do return socket != null and socket.connected
+ fun connected: Bool
+ do
+ var socket = socket
+ return socket != null and socket.connected
+ end
- # `BinarySerializer` used to send data to this client through `socket`
- var writer: BinarySerializer is noinit
+ # `MsgPackSerializer` used to send data to this client through `socket`
+ var writer: MsgPackSerializer is noinit
- # `BinaryDeserializer` used to receive data from this client through `socket`
- var reader: BinaryDeserializer is noinit
+ # `MsgPackDeserializer` used to receive data from this client through `socket`
+ var reader: MsgPackDeserializer is noinit
# Attempt connection with the remote server
fun connect: Bool
end
# Setup serialization
- writer = new BinarySerializer(socket)
+ writer = new MsgPackSerializer(socket)
writer.cache = new AsyncCache(false)
- reader = new BinaryDeserializer(socket)
+ reader = new MsgPackDeserializer(socket)
writer.link reader
return true
# App name
var app_name = sys.handshake_app_name
- socket.write_string app_name
+ socket.serialize_msgpack app_name
- var server_app = socket.read_string
+ var server_app = socket.deserialize_msgpack("String")
if server_app != app_name then
- print_error "Handshake Error: server app name is '{server_app}'"
+ print_error "Handshake Error: server app name is '{server_app or else "<invalid>"}'"
socket.close
return false
end
# App version
- socket.write_string sys.handshake_app_version
+ socket.serialize_msgpack sys.handshake_app_version
- var server_version = socket.read_string
+ var server_version = socket.deserialize_msgpack("String")
if server_version != sys.handshake_app_version then
- print_error "Handshake Error: server version is different '{server_version}'"
+ print_error "Handshake Error: server version is different '{server_version or else "<invalid>"}'"
socket.close
return false
end
module common
import socket
-import binary::serialization
+import msgpack
# Unique name of the application to use in the handshake
#
# Is this client connected?
fun connected: Bool do return socket.connected
- # `BinarySerializer` used to send data to this client through `socket`
- var writer: BinarySerializer is noinit
+ # `MsgPackSerializer` used to send data to this client through `socket`
+ var writer: MsgPackSerializer is noinit
- # `BinaryDeserializer` used to receive data from this client through `socket`
- var reader: BinaryDeserializer is noinit
+ # `MsgPackDeserializer` used to receive data from this client through `socket`
+ var reader: MsgPackDeserializer is noinit
init
do
# Setup serialization
- writer = new BinarySerializer(socket)
+ writer = new MsgPackSerializer(socket)
writer.cache = new AsyncCache(true)
- reader = new BinaryDeserializer(socket)
+ reader = new MsgPackDeserializer(socket)
writer.link reader
end
# Make sure it is the same app
var server_app = sys.handshake_app_name
- var client_app = socket.read_string
+ var client_app = socket.deserialize_msgpack
if server_app != client_app then
- print_error "Server Error: Client app name is '{client_app}'"
+ print_error "Server Error: Client app name is '{client_app or else "<invalid>"}'"
# Send an empty string so the client read it and give up
- socket.write_string ""
+ socket.serialize_msgpack ""
socket.close
return false
end
- socket.write_string server_app
+ socket.serialize_msgpack server_app
# App version
var app_version = sys.handshake_app_version
- var client_version = socket.read_string
+ var client_version = socket.deserialize_msgpack
if client_version != app_version then
- print_error "Handshake Error: client version is different '{client_version}'"
- socket.write_string ""
+ print_error "Handshake Error: client version is different '{client_version or else "<invalid>"}'"
+ socket.serialize_msgpack ""
socket.close
return false
end
- socket.write_string app_version
+ socket.serialize_msgpack app_version
return true
end
fun uniform(val: Bool) do uniform_1i(location, if val then 1 else 0)
end
+# Shader uniform of GLSL type `int`
+class UniformInt
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(val: Int) do uniform_1i(location, val)
+end
+
# Shader uniform of GLSL type `vec4`
class UniformFloat
super Uniform
class InactiveUniform
super InactiveVariable
super UniformBool
+ super UniformInt
super UniformFloat
super UniformSampler2D
super UniformVec2
var uniform
if typ == gl_BOOL then
uniform = new UniformBool(gl_program, name, location, size)
+ else if typ == gl_INT then
+ uniform = new UniformInt(gl_program, name, location, size)
else if typ == gl_SAMPLER_2D then
uniform = new UniformSampler2D(gl_program, name, location, size)
else if typ == gl_FLOAT then
for i in [0..4[ do cpixels[offset+i] = bytes[i]
end
- # Overwrite all pixels with `color`
+ # Overwrite all pixels with `color`, return `self`
#
# The argument `color` should be an array of up to 4 floats (RGBA).
# If `color` has less than 4 items, the missing items are replaced by 1.0.
#
# Require: `not loaded`
- fun fill(color: Array[Float])
+ fun fill(color: Array[Float]): SELF
do
assert not loaded else print_error "{class_name}::fill already loaded"
i += 4
end
end
+
+ return self
end
redef fun load(force)
private fun load_from_pixels(pixels: Pointer, width, height: Int, format: GLPixelFormat)
do
var max_texture_size = glGetIntegerv(gl_MAX_TEXTURE_SIZE, 0)
- if width > max_texture_size or height > max_texture_size then
- error = new Error("Texture {self} width or height is over the GL_MAX_TEXTURE_SIZE of {max_texture_size}")
+ if width > max_texture_size then
+ error = new Error("Texture width larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {width}")
+ return
+ else if height > max_texture_size then
+ error = new Error("Texture height larger than gl_MAX_TEXTURE_SIZE ({max_texture_size}) in {self} at {height}")
return
end
redef class App
# Current touch gamepad, still may be invisible
- var gamepad: nullable VirtualGamepad = null
+ var gamepad: nullable VirtualGamepad = null is writable
# Textures used for `DPad` and available to clients
var gamepad_spritesheet = new VirtualGamepadSpritesheet
# and `add_button`.
var controls = new Array[RoundControl]
- # Add a directional pad (`DPad`) to a default location
+ # Add and return a directional pad (`DPad`) to a default location
#
# The 4 buttons fire events with the corresponding name in `names`.
# Items in `names` should be in order of top, left, down and right.
# added by `add_button`.
#
# Require: `names == null or names.length == 4`
- fun add_dpad(names: nullable Array[String])
+ fun add_dpad(names: nullable Array[String]): nullable DPad
do
if names == null then names = ["w","a","s","d"]
assert names.length == 4
if n_dpads == 0 then
- controls.add new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+ var dpad = new DPad(app.ui_camera.bottom_left.offset(200.0, 100.0, 0.0), names)
+ controls.add dpad
+ return dpad
else if n_dpads == 1 then
- controls.add new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+ var dpad = new DPad(app.ui_camera.bottom_right.offset(-200.0, 100.0, 0.0), names)
+ controls.add dpad
+ return dpad
else
print_error "Too many DPad ({n_dpads}) in {self}"
+ return null
end
end
new Point[Float](-350.0, 350.0),
new Point[Float](-350.0, 550.0))
- # Add a round button to a default location
+ # Add and return a round button to a default location
#
# Fired events use `name`, it should usually correspond to a
# keyboard key like "space" or "a".
#
# A maximum of 6 buttons may be added using this method when
# there is less than 2 `DPad`. Otherwise, only 2 buttons can be added.
- fun add_button(name: String, texture: Texture)
+ fun add_button(name: String, texture: Texture): nullable RoundButton
do
if n_dpads == 2 and button_positions.length == 6 then
# Drop the bottom 4 buttons
assert button_positions.not_empty else print_error "Too many buttons in {self}"
var pos = button_positions.shift
- controls.add new RoundButton(
+ var but = new RoundButton(
app.ui_camera.bottom_right.offset(pos.x, pos.y, 0.0), name, texture)
+ controls.add but
+ return but
end
private fun prepare
# http://www.khronos.org/opengles/sdk/docs/man/
module glesv2 is
pkgconfig
+ no_warning "missing-doc"
new_annotation glsl_vertex_shader
new_annotation glsl_fragment_shader
ldflags("-lGLESv2")@android
fun gl_RGB5_A1: GLRenderbufferFormat `{ return GL_RGB5_A1; `}
# 16 depth bits format
-fun gl_DEPTH_COMPNENT16: GLRenderbufferFormat `{ return GL_DEPTH_COMPONENT16; `}
+fun gl_DEPTH_COMPONENT16: GLRenderbufferFormat `{ return GL_DEPTH_COMPONENT16; `}
# 8 stencil bits format
fun gl_STENCIL_INDEX8: GLRenderbufferFormat `{ return GL_STENCIL_INDEX8; `}
fun gl_ALPHA: GLPixelFormat `{ return GL_ALPHA; `}
fun gl_RGB: GLPixelFormat `{ return GL_RGB; `}
fun gl_RGBA: GLPixelFormat `{ return GL_RGBA; `}
+fun gl_DEPTH_COMPONENT: GLPixelFormat `{ return GL_DEPTH_COMPONENT; `}
# Set of buffers as a bitwise OR mask
extern class GLBuffer `{ GLbitfield `}
fun gl_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: GLGetParameterName `{ return GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING; `}
fun gl_FRAMEBUFFER_BINDING: GLGetParameterName `{ return GL_FRAMEBUFFER_BINDING; `}
fun gl_RENDERBUFFER_BINDING: GLGetParameterName `{ return GL_RENDERBUFFER_BINDING; `}
+
+# Return a string describing the current GL configuration
+fun glGetString(name: GLEnum): String do return glGetString_native(name).to_s
+private fun glGetString_native(name: GLEnum): CString `{ return (char*)glGetString(name); `}
+
+# Company responsible for this GL implementation
+fun gl_VENDOR: GLEnum `{ return GL_VENDOR; `}
+
+# Name of the renderer, typically specific to a particular configuration of the hardware platform
+fun gl_RENDERER: GLEnum `{ return GL_RENDERER; `}
+
+# Version or release number
+fun gl_VERSION: GLEnum `{ return GL_VERSION; `}
+
+# Version or release number for the shading language of the form
+fun gl_SHADING_LANGUAGE_VERSION: GLEnum `{ return GL_SHADING_LANGUAGE_VERSION; `}
+
+# Space-separated list of supported extensions to GL
+fun gl_EXTENSIONS: GLEnum `{ return GL_EXTENSIONS; `}
import cocoa::foundation
private import json
-redef class App
- redef var data_store = new UserDefaultView
-end
-
-private class UserDefaultView
- super DataStore
+redef class DataStore
# The `NSUserDefaults` used to implement `DataStore`
var user_defaults = new NSUserDefaults.standard_user_defaults is lazy
# TODO report errors
var deserializer = new JsonDeserializer(nsstr.to_s)
- return deserializer.deserialize
+ var deserialized = deserializer.deserialize
+
+ var errors = deserializer.errors
+ if errors.not_empty then
+ # An update may have broken the versioning compatibility
+ print_error "{class_name} error at deserialization: {errors.join(", ")}"
+ return null # Let's be safe
+ end
+
+ return deserialized
end
redef fun []=(key, value)
+++ /dev/null
-json_lexer.nit -diff
-json_parser.nit -diff
+++ /dev/null
-json.concrete_grammar.txt
-json.dfa.dot
-json.gram.dot
-json.lr.dot
-json.lr.txt
-json.nfa.dot
-json_test_parser.nit
+++ /dev/null
-NITCCDIR=../../contrib/nitcc/
-
-pre-build: $(NITCCDIR)src/nitcc
- $(NITCCDIR)src/nitcc $(NITCCDIR)examples/json.sablecc
-
-$(NITCCDIR)src/nitcc:
- make -C $(NITCCDIR)
# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# Errors related to JSON parsing.
-module json::error
+# Intro `JsonParseError` which is exposed by all JSON reading APIs
+module error
-import nitcc_runtime
+import parser_base
-# Ill-formed JSON.
+# JSON format error at parsing
class JsonParseError
super Error
serialize
- # The location of the error in the original text.
- var position: nullable Position
-
- redef fun to_s do
- var p = position
- if p isa Position then
- return "Error Parsing JSON: [{p}] {super}"
- else
- return super
- end
- end
+ # Location of the error in source
+ var location: nullable Location = null
end
+++ /dev/null
-# Lexer generated by nitcc for the grammar json
-module json_lexer is generated, no_warning "missing-doc"
-import nitcc_runtime
-import json_parser
-class Lexer_json
- super Lexer
- redef fun start_state do return dfastate_0
-end
-private fun dfastate_0: DFAState0 do return once new DFAState0
-private fun dfastate_1: DFAState1 do return once new DFAState1
-private fun dfastate_2: DFAState2 do return once new DFAState2
-private fun dfastate_3: DFAState3 do return once new DFAState3
-private fun dfastate_4: DFAState4 do return once new DFAState4
-private fun dfastate_5: DFAState5 do return once new DFAState5
-private fun dfastate_6: DFAState6 do return once new DFAState6
-private fun dfastate_7: DFAState7 do return once new DFAState7
-private fun dfastate_8: DFAState8 do return once new DFAState8
-private fun dfastate_9: DFAState9 do return once new DFAState9
-private fun dfastate_10: DFAState10 do return once new DFAState10
-private fun dfastate_11: DFAState11 do return once new DFAState11
-private fun dfastate_12: DFAState12 do return once new DFAState12
-private fun dfastate_13: DFAState13 do return once new DFAState13
-private fun dfastate_14: DFAState14 do return once new DFAState14
-private fun dfastate_15: DFAState15 do return once new DFAState15
-private fun dfastate_16: DFAState16 do return once new DFAState16
-private fun dfastate_17: DFAState17 do return once new DFAState17
-private fun dfastate_18: DFAState18 do return once new DFAState18
-private fun dfastate_19: DFAState19 do return once new DFAState19
-private fun dfastate_20: DFAState20 do return once new DFAState20
-private fun dfastate_21: DFAState21 do return once new DFAState21
-private fun dfastate_22: DFAState22 do return once new DFAState22
-private fun dfastate_23: DFAState23 do return once new DFAState23
-private fun dfastate_24: DFAState24 do return once new DFAState24
-private fun dfastate_25: DFAState25 do return once new DFAState25
-private fun dfastate_26: DFAState26 do return once new DFAState26
-private fun dfastate_27: DFAState27 do return once new DFAState27
-private fun dfastate_28: DFAState28 do return once new DFAState28
-private fun dfastate_29: DFAState29 do return once new DFAState29
-private fun dfastate_30: DFAState30 do return once new DFAState30
-private fun dfastate_31: DFAState31 do return once new DFAState31
-private fun dfastate_32: DFAState32 do return once new DFAState32
-private fun dfastate_33: DFAState33 do return once new DFAState33
-private fun dfastate_34: DFAState34 do return once new DFAState34
-class MyNToken
- super NToken
-end
-private class DFAState0
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 8 then return null
- if c <= 10 then return dfastate_1
- if c <= 31 then return null
- if c <= 32 then return dfastate_1
- if c <= 33 then return null
- if c <= 34 then return dfastate_2
- if c <= 43 then return null
- if c <= 44 then return dfastate_3
- if c <= 45 then return dfastate_4
- if c <= 47 then return null
- if c <= 57 then return dfastate_5
- if c <= 58 then return dfastate_6
- if c <= 90 then return null
- if c <= 91 then return dfastate_7
- if c <= 92 then return null
- if c <= 93 then return dfastate_8
- if c <= 101 then return null
- if c <= 102 then return dfastate_9
- if c <= 109 then return null
- if c <= 110 then return dfastate_10
- if c <= 115 then return null
- if c <= 116 then return dfastate_11
- if c <= 122 then return null
- if c <= 123 then return dfastate_12
- if c <= 124 then return null
- if c <= 125 then return dfastate_13
- return null
- end
-end
-private class DFAState1
- super DFAState
- redef fun is_accept do return true
- redef fun is_ignored do return true
- redef fun make_token(position, source) do
- return null
- end
- redef fun trans(char) do
- var c = char.code_point
- if c <= 8 then return null
- if c <= 10 then return dfastate_1
- if c <= 31 then return null
- if c <= 32 then return dfastate_1
- return null
- end
-end
-private class DFAState2
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c > 92 then return dfastate_2
- if c <= 33 then return dfastate_2
- if c <= 34 then return dfastate_29
- if c <= 91 then return dfastate_2
- return dfastate_30
- end
-end
-private class DFAState3
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_44d_39d
- t.text = ","
- t.position = position
- return t
- end
-end
-private class DFAState4
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_5
- return null
- end
-end
-private class DFAState5
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new Nnumber
- t.text = position.extract(source)
- t.position = position
- return t
- end
- redef fun trans(char) do
- var c = char.code_point
- if c <= 45 then return null
- if c <= 46 then return dfastate_24
- if c <= 47 then return null
- if c <= 57 then return dfastate_5
- if c <= 68 then return null
- if c <= 69 then return dfastate_25
- if c <= 100 then return null
- if c <= 101 then return dfastate_25
- return null
- end
-end
-private class DFAState6
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_58d_39d
- t.text = ":"
- t.position = position
- return t
- end
-end
-private class DFAState7
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_91d_39d
- t.text = "["
- t.position = position
- return t
- end
-end
-private class DFAState8
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_93d_39d
- t.text = "]"
- t.position = position
- return t
- end
-end
-private class DFAState9
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 96 then return null
- if c <= 97 then return dfastate_20
- return null
- end
-end
-private class DFAState10
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 116 then return null
- if c <= 117 then return dfastate_17
- return null
- end
-end
-private class DFAState11
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 113 then return null
- if c <= 114 then return dfastate_14
- return null
- end
-end
-private class DFAState12
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_123d_39d
- t.text = "\{"
- t.position = position
- return t
- end
-end
-private class DFAState13
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39d_125d_39d
- t.text = "\}"
- t.position = position
- return t
- end
-end
-private class DFAState14
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 116 then return null
- if c <= 117 then return dfastate_15
- return null
- end
-end
-private class DFAState15
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 100 then return null
- if c <= 101 then return dfastate_16
- return null
- end
-end
-private class DFAState16
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39dtrue_39d
- t.text = "true"
- t.position = position
- return t
- end
-end
-private class DFAState17
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 107 then return null
- if c <= 108 then return dfastate_18
- return null
- end
-end
-private class DFAState18
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 107 then return null
- if c <= 108 then return dfastate_19
- return null
- end
-end
-private class DFAState19
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39dnull_39d
- t.text = "null"
- t.position = position
- return t
- end
-end
-private class DFAState20
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 107 then return null
- if c <= 108 then return dfastate_21
- return null
- end
-end
-private class DFAState21
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 114 then return null
- if c <= 115 then return dfastate_22
- return null
- end
-end
-private class DFAState22
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 100 then return null
- if c <= 101 then return dfastate_23
- return null
- end
-end
-private class DFAState23
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new N_39dfalse_39d
- t.text = "false"
- t.position = position
- return t
- end
-end
-private class DFAState24
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_28
- return null
- end
-end
-private class DFAState25
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 42 then return null
- if c <= 43 then return dfastate_26
- if c <= 44 then return null
- if c <= 45 then return dfastate_26
- if c <= 47 then return null
- if c <= 57 then return dfastate_27
- return null
- end
-end
-private class DFAState26
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_27
- return null
- end
-end
-private class DFAState27
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new Nnumber
- t.text = position.extract(source)
- t.position = position
- return t
- end
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_27
- return null
- end
-end
-private class DFAState28
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new Nnumber
- t.text = position.extract(source)
- t.position = position
- return t
- end
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_28
- if c <= 68 then return null
- if c <= 69 then return dfastate_25
- if c <= 100 then return null
- if c <= 101 then return dfastate_25
- return null
- end
-end
-private class DFAState29
- super DFAState
- redef fun is_accept do return true
- redef fun make_token(position, source) do
- var t = new Nstring
- t.text = position.extract(source)
- t.position = position
- return t
- end
-end
-private class DFAState30
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 33 then return null
- if c <= 34 then return dfastate_2
- if c <= 46 then return null
- if c <= 47 then return dfastate_2
- if c <= 91 then return null
- if c <= 92 then return dfastate_2
- if c <= 97 then return null
- if c <= 98 then return dfastate_2
- if c <= 101 then return null
- if c <= 102 then return dfastate_2
- if c <= 109 then return null
- if c <= 110 then return dfastate_2
- if c <= 113 then return null
- if c <= 114 then return dfastate_2
- if c <= 115 then return null
- if c <= 116 then return dfastate_2
- if c <= 117 then return dfastate_31
- return null
- end
-end
-private class DFAState31
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_32
- if c <= 64 then return null
- if c <= 90 then return dfastate_32
- if c <= 96 then return null
- if c <= 122 then return dfastate_32
- return null
- end
-end
-private class DFAState32
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_33
- if c <= 64 then return null
- if c <= 90 then return dfastate_33
- if c <= 96 then return null
- if c <= 122 then return dfastate_33
- return null
- end
-end
-private class DFAState33
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_34
- if c <= 64 then return null
- if c <= 90 then return dfastate_34
- if c <= 96 then return null
- if c <= 122 then return dfastate_34
- return null
- end
-end
-private class DFAState34
- super DFAState
- redef fun trans(char) do
- var c = char.code_point
- if c <= 47 then return null
- if c <= 57 then return dfastate_2
- if c <= 64 then return null
- if c <= 90 then return dfastate_2
- if c <= 96 then return null
- if c <= 122 then return dfastate_2
- return null
- end
-end
+++ /dev/null
-# Parser generated by nitcc for the grammar json
-module json_parser is generated, no_warning("missing-doc","old-init")
-import nitcc_runtime
-class Parser_json
- super Parser
- redef fun start_state do return state_Start
-end
-private fun state_Start: LRStateStart do return once new LRStateStart
-private fun state_value: LRStatevalue do return once new LRStatevalue
-private fun state_number: LRStatenumber do return once new LRStatenumber
-private fun state_string: LRStatestring do return once new LRStatestring
-private fun state__39dtrue_39d: LRState_39dtrue_39d do return once new LRState_39dtrue_39d
-private fun state__39dfalse_39d: LRState_39dfalse_39d do return once new LRState_39dfalse_39d
-private fun state__39dnull_39d: LRState_39dnull_39d do return once new LRState_39dnull_39d
-private fun state__39d_123d_39d: LRState_39d_123d_39d do return once new LRState_39d_123d_39d
-private fun state__39d_91d_39d: LRState_39d_91d_39d do return once new LRState_39d_91d_39d
-private fun state_value_32dEof: LRStatevalue_32dEof do return once new LRStatevalue_32dEof
-private fun state__39d_123d_39d_32dmembers: LRState_39d_123d_39d_32dmembers do return once new LRState_39d_123d_39d_32dmembers
-private fun state__39d_123d_39d_32d_39d_125d_39d: LRState_39d_123d_39d_32d_39d_125d_39d do return once new LRState_39d_123d_39d_32d_39d_125d_39d
-private fun state__39d_123d_39d_32dpair: LRState_39d_123d_39d_32dpair do return once new LRState_39d_123d_39d_32dpair
-private fun state__39d_123d_39d_32dstring: LRState_39d_123d_39d_32dstring do return once new LRState_39d_123d_39d_32dstring
-private fun state__39d_91d_39d_32delements: LRState_39d_91d_39d_32delements do return once new LRState_39d_91d_39d_32delements
-private fun state__39d_91d_39d_32d_39d_93d_39d: LRState_39d_91d_39d_32d_39d_93d_39d do return once new LRState_39d_91d_39d_32d_39d_93d_39d
-private fun state__39d_91d_39d_32dvalue: LRState_39d_91d_39d_32dvalue do return once new LRState_39d_91d_39d_32dvalue
-private fun state__39d_123d_39d_32dmembers_32d_39d_125d_39d: LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d do return once new LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d
-private fun state__39d_123d_39d_32dmembers_32d_39d_44d_39d: LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d do return once new LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d
-private fun state__39d_123d_39d_32dstring_32d_39d_58d_39d: LRState_39d_123d_39d_32dstring_32d_39d_58d_39d do return once new LRState_39d_123d_39d_32dstring_32d_39d_58d_39d
-private fun state__39d_91d_39d_32delements_32d_39d_93d_39d: LRState_39d_91d_39d_32delements_32d_39d_93d_39d do return once new LRState_39d_91d_39d_32delements_32d_39d_93d_39d
-private fun state__39d_91d_39d_32delements_32d_39d_44d_39d: LRState_39d_91d_39d_32delements_32d_39d_44d_39d do return once new LRState_39d_91d_39d_32delements_32d_39d_44d_39d
-private fun state__39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair: LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair do return once new LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair
-private fun state__39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue: LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue do return once new LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue
-private fun state__39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue: LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue do return once new LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue
-private fun goto_Nvalue: Goto_Nvalue do return once new Goto_Nvalue
-private fun reduce_Nvalue_number(parser: Parser) do
- # REDUCE value::value_number=number
- var n0 = parser.pop.as(Nnumber)
- var p1 = new Nvalue_number(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_string(parser: Parser) do
- # REDUCE value::value_string=string
- var n0 = parser.pop.as(Nstring)
- var p1 = new Nvalue_string(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_true(parser: Parser) do
- # REDUCE value::value_true='true'
- var n0 = parser.pop.as(N_39dtrue_39d)
- var p1 = new Nvalue_true(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_false(parser: Parser) do
- # REDUCE value::value_false='false'
- var n0 = parser.pop.as(N_39dfalse_39d)
- var p1 = new Nvalue_false(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_null(parser: Parser) do
- # REDUCE value::value_null='null'
- var n0 = parser.pop.as(N_39dnull_39d)
- var p1 = new Nvalue_null(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_object_95d0(parser: Parser) do
- # REDUCE value::value_object_0='{' members '}'
- var n2 = parser.pop.as(N_39d_125d_39d)
- var n1 = parser.pop.as(Nmembers)
- var n0 = parser.pop.as(N_39d_123d_39d)
- var p1 = new Nvalue_object(n0, n1, n2)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_object_95d1(parser: Parser) do
- # REDUCE value::value_object_1='{' '}'
- var n1 = parser.pop.as(N_39d_125d_39d)
- var n0 = parser.pop.as(N_39d_123d_39d)
- var p1 = new Nvalue_object(n0, null, n1)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_array_95d0(parser: Parser) do
- # REDUCE value::value_array_0='[' elements ']'
- var n2 = parser.pop.as(N_39d_93d_39d)
- var n1 = parser.pop.as(Nelements)
- var n0 = parser.pop.as(N_39d_91d_39d)
- var p1 = new Nvalue_array(n0, n1, n2)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun reduce_Nvalue_array_95d1(parser: Parser) do
- # REDUCE value::value_array_1='[' ']'
- var n1 = parser.pop.as(N_39d_93d_39d)
- var n0 = parser.pop.as(N_39d_91d_39d)
- var p1 = new Nvalue_array(n0, null, n1)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nvalue)
-end
-private fun goto_Nmembers: Goto_Nmembers do return once new Goto_Nmembers
-private fun reduce_Nmembers_tail(parser: Parser) do
- # REDUCE members::members_tail=members ',' pair
- var n2 = parser.pop.as(Npair)
- var n1 = parser.pop.as(N_39d_44d_39d)
- var n0 = parser.pop.as(Nmembers)
- var p1 = new Nmembers_tail(n0, n1, n2)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nmembers)
-end
-private fun reduce_Nmembers_head(parser: Parser) do
- # REDUCE members::members_head=pair
- var n0 = parser.pop.as(Npair)
- var p1 = new Nmembers_head(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nmembers)
-end
-private fun goto_Npair: Goto_Npair do return once new Goto_Npair
-private fun reduce_Npair(parser: Parser) do
- # REDUCE pair::pair=string ':' value
- var n2 = parser.pop.as(Nvalue)
- var n1 = parser.pop.as(N_39d_58d_39d)
- var n0 = parser.pop.as(Nstring)
- var p1 = new Npair(n0, n1, n2)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Npair)
-end
-private fun goto_Nelements: Goto_Nelements do return once new Goto_Nelements
-private fun reduce_Nelements_tail(parser: Parser) do
- # REDUCE elements::elements_tail=elements ',' value
- var n2 = parser.pop.as(Nvalue)
- var n1 = parser.pop.as(N_39d_44d_39d)
- var n0 = parser.pop.as(Nelements)
- var p1 = new Nelements_tail(n0, n1, n2)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nelements)
-end
-private fun reduce_Nelements_head(parser: Parser) do
- # REDUCE elements::elements_head=value
- var n0 = parser.pop.as(Nvalue)
- var p1 = new Nelements_head(n0)
- var prod = p1
- parser.node_stack.push prod
- parser.goto(goto_Nelements)
-end
-private fun goto_N_start: Goto_N_start do return once new Goto_N_start
-private fun reduce_NStart(parser: Parser) do
- # REDUCE _start::Start=value Eof
- var n1 = parser.pop.as(NEof)
- var n0 = parser.pop.as(Nvalue)
- var p1 = new NStart(n0, n1)
- var prod = p1
- parser.node_stack.push prod
- parser.stop = true
-end
-redef class NToken
- # guarded action for state Start
- # 7 shift(s) and 0 reduce(s)
- private fun action_sStart(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state value
- # 1 shift(s) and 0 reduce(s)
- private fun action_svalue(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '{'
- # 2 shift(s) and 0 reduce(s)
- private fun action_s_39d_123d_39d(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '['
- # 8 shift(s) and 0 reduce(s)
- private fun action_s_39d_91d_39d(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '{' members
- # 2 shift(s) and 0 reduce(s)
- private fun action_s_39d_123d_39d_32dmembers(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '{' string
- # 1 shift(s) and 0 reduce(s)
- private fun action_s_39d_123d_39d_32dstring(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '[' elements
- # 2 shift(s) and 0 reduce(s)
- private fun action_s_39d_91d_39d_32delements(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '{' members ','
- # 1 shift(s) and 0 reduce(s)
- private fun action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '{' string ':'
- # 7 shift(s) and 0 reduce(s)
- private fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser: Parser) do
- parser.parse_error
- end
- # guarded action for state '[' elements ','
- # 7 shift(s) and 0 reduce(s)
- private fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser: Parser) do
- parser.parse_error
- end
-end
-class N_39d_123d_39d
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state__39d_123d_39d)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39d_123d_39d)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state__39d_123d_39d)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state__39d_123d_39d)
- end
- redef fun node_name do return "\'\{\'"
-end
-class N_39d_125d_39d
- super NToken
- redef fun action_s_39d_123d_39d(parser) do
- parser.shift(state__39d_123d_39d_32d_39d_125d_39d)
- end
- redef fun action_s_39d_123d_39d_32dmembers(parser) do
- parser.shift(state__39d_123d_39d_32dmembers_32d_39d_125d_39d)
- end
- redef fun node_name do return "\'\}\'"
-end
-class N_39d_91d_39d
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state__39d_91d_39d)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39d_91d_39d)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state__39d_91d_39d)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state__39d_91d_39d)
- end
- redef fun node_name do return "\'[\'"
-end
-class N_39d_93d_39d
- super NToken
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39d_91d_39d_32d_39d_93d_39d)
- end
- redef fun action_s_39d_91d_39d_32delements(parser) do
- parser.shift(state__39d_91d_39d_32delements_32d_39d_93d_39d)
- end
- redef fun node_name do return "\']\'"
-end
-class Nnumber
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state_number)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state_number)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state_number)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state_number)
- end
- redef fun node_name do return "number"
-end
-class Nstring
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state_string)
- end
- redef fun action_s_39d_123d_39d(parser) do
- parser.shift(state__39d_123d_39d_32dstring)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state_string)
- end
- redef fun action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser) do
- parser.shift(state__39d_123d_39d_32dstring)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state_string)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state_string)
- end
- redef fun node_name do return "string"
-end
-class N_39dtrue_39d
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state__39dtrue_39d)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39dtrue_39d)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state__39dtrue_39d)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state__39dtrue_39d)
- end
- redef fun node_name do return "\'true\'"
-end
-class N_39dfalse_39d
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state__39dfalse_39d)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39dfalse_39d)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state__39dfalse_39d)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state__39dfalse_39d)
- end
- redef fun node_name do return "\'false\'"
-end
-class N_39dnull_39d
- super NToken
- redef fun action_sStart(parser) do
- parser.shift(state__39dnull_39d)
- end
- redef fun action_s_39d_91d_39d(parser) do
- parser.shift(state__39dnull_39d)
- end
- redef fun action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser) do
- parser.shift(state__39dnull_39d)
- end
- redef fun action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser) do
- parser.shift(state__39dnull_39d)
- end
- redef fun node_name do return "\'null\'"
-end
-class N_39d_44d_39d
- super NToken
- redef fun action_s_39d_123d_39d_32dmembers(parser) do
- parser.shift(state__39d_123d_39d_32dmembers_32d_39d_44d_39d)
- end
- redef fun action_s_39d_91d_39d_32delements(parser) do
- parser.shift(state__39d_91d_39d_32delements_32d_39d_44d_39d)
- end
- redef fun node_name do return "\',\'"
-end
-class N_39d_58d_39d
- super NToken
- redef fun action_s_39d_123d_39d_32dstring(parser) do
- parser.shift(state__39d_123d_39d_32dstring_32d_39d_58d_39d)
- end
- redef fun node_name do return "\':\'"
-end
-redef class NEof
- super NToken
- redef fun action_svalue(parser) do
- parser.shift(state_value_32dEof)
- end
- redef fun node_name do return "Eof"
-end
-redef class LRGoto
- private fun goto_s_39d_123d_39d(parser: Parser) do abort
- private fun goto_s_39d_91d_39d(parser: Parser) do abort
-end
-class Goto_Nvalue
- super LRGoto
- redef fun goto_s_39d_91d_39d(parser) do
- parser.push(state__39d_91d_39d_32dvalue)
- end
-end
-class Goto_Nmembers
- super LRGoto
- redef fun goto_s_39d_123d_39d(parser) do
- parser.push(state__39d_123d_39d_32dmembers)
- end
-end
-class Goto_Npair
- super LRGoto
- redef fun goto_s_39d_123d_39d(parser) do
- parser.push(state__39d_123d_39d_32dpair)
- end
-end
-class Goto_Nelements
- super LRGoto
- redef fun goto_s_39d_91d_39d(parser) do
- parser.push(state__39d_91d_39d_32delements)
- end
-end
-class Goto_N_start
- super LRGoto
-end
-class Nvalue
- super NProd
- redef fun node_name do return "value"
-end
-class Nvalue_number
- super Nvalue
- redef fun node_name do return "value_number"
- var n_number: Nnumber
- init(n_number: Nnumber) do
- self.n_number = n_number
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_number
- abort
- end
-end
-class Nvalue_string
- super Nvalue
- redef fun node_name do return "value_string"
- var n_string: Nstring
- init(n_string: Nstring) do
- self.n_string = n_string
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_string
- abort
- end
-end
-class Nvalue_true
- super Nvalue
- redef fun node_name do return "value_true"
- var n_0: N_39dtrue_39d
- init(n_0: N_39dtrue_39d) do
- self.n_0 = n_0
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_0
- abort
- end
-end
-class Nvalue_false
- super Nvalue
- redef fun node_name do return "value_false"
- var n_0: N_39dfalse_39d
- init(n_0: N_39dfalse_39d) do
- self.n_0 = n_0
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_0
- abort
- end
-end
-class Nvalue_null
- super Nvalue
- redef fun node_name do return "value_null"
- var n_0: N_39dnull_39d
- init(n_0: N_39dnull_39d) do
- self.n_0 = n_0
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_0
- abort
- end
-end
-class Nvalue_object
- super Nvalue
- redef fun node_name do return "value_object"
- var n_0: N_39d_123d_39d
- var n_members: nullable Nmembers
- var n_2: N_39d_125d_39d
- init(n_0: N_39d_123d_39d, n_members: nullable Nmembers, n_2: N_39d_125d_39d) do
- self.n_0 = n_0
- self.n_members = n_members
- self.n_2 = n_2
- end
- redef fun number_of_children do return 3
- redef fun child(i) do
- if i == 0 then return n_0
- if i == 1 then return n_members
- if i == 2 then return n_2
- abort
- end
-end
-class Nvalue_array
- super Nvalue
- redef fun node_name do return "value_array"
- var n_0: N_39d_91d_39d
- var n_elements: nullable Nelements
- var n_2: N_39d_93d_39d
- init(n_0: N_39d_91d_39d, n_elements: nullable Nelements, n_2: N_39d_93d_39d) do
- self.n_0 = n_0
- self.n_elements = n_elements
- self.n_2 = n_2
- end
- redef fun number_of_children do return 3
- redef fun child(i) do
- if i == 0 then return n_0
- if i == 1 then return n_elements
- if i == 2 then return n_2
- abort
- end
-end
-class Nmembers
- super NProd
- redef fun node_name do return "members"
-end
-class Nmembers_tail
- super Nmembers
- redef fun node_name do return "members_tail"
- var n_members: Nmembers
- var n_1: N_39d_44d_39d
- var n_pair: Npair
- init(n_members: Nmembers, n_1: N_39d_44d_39d, n_pair: Npair) do
- self.n_members = n_members
- self.n_1 = n_1
- self.n_pair = n_pair
- end
- redef fun number_of_children do return 3
- redef fun child(i) do
- if i == 0 then return n_members
- if i == 1 then return n_1
- if i == 2 then return n_pair
- abort
- end
-end
-class Nmembers_head
- super Nmembers
- redef fun node_name do return "members_head"
- var n_pair: Npair
- init(n_pair: Npair) do
- self.n_pair = n_pair
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_pair
- abort
- end
-end
-class Npair
- super NProd
- redef fun node_name do return "pair"
- var n_string: Nstring
- var n_1: N_39d_58d_39d
- var n_value: Nvalue
- init(n_string: Nstring, n_1: N_39d_58d_39d, n_value: Nvalue) do
- self.n_string = n_string
- self.n_1 = n_1
- self.n_value = n_value
- end
- redef fun number_of_children do return 3
- redef fun child(i) do
- if i == 0 then return n_string
- if i == 1 then return n_1
- if i == 2 then return n_value
- abort
- end
-end
-class Nelements
- super NProd
- redef fun node_name do return "elements"
-end
-class Nelements_tail
- super Nelements
- redef fun node_name do return "elements_tail"
- var n_elements: Nelements
- var n_1: N_39d_44d_39d
- var n_value: Nvalue
- init(n_elements: Nelements, n_1: N_39d_44d_39d, n_value: Nvalue) do
- self.n_elements = n_elements
- self.n_1 = n_1
- self.n_value = n_value
- end
- redef fun number_of_children do return 3
- redef fun child(i) do
- if i == 0 then return n_elements
- if i == 1 then return n_1
- if i == 2 then return n_value
- abort
- end
-end
-class Nelements_head
- super Nelements
- redef fun node_name do return "elements_head"
- var n_value: Nvalue
- init(n_value: Nvalue) do
- self.n_value = n_value
- end
- redef fun number_of_children do return 1
- redef fun child(i) do
- if i == 0 then return n_value
- abort
- end
-end
-class N_start
- super NProd
- redef fun node_name do return "_start"
-end
-class NStart
- super N_start
- redef fun node_name do return "Start"
- var n_0: Nvalue
- var n_1: NEof
- init(n_0: Nvalue, n_1: NEof) do
- self.n_0 = n_0
- self.n_1 = n_1
- end
- redef fun number_of_children do return 2
- redef fun child(i) do
- if i == 0 then return n_0
- if i == 1 then return n_1
- abort
- end
-end
-# State Start
-private class LRStateStart
- super LRState
- redef fun to_s do return "Start"
- redef fun error_msg do return "value"
- redef fun action(parser) do
- parser.peek_token.action_sStart(parser)
- end
- redef fun goto(parser, goto) do
- parser.push(state_value)
- end
-end
-# State value
-private class LRStatevalue
- super LRState
- redef fun to_s do return "value"
- redef fun error_msg do return "Eof"
- redef fun action(parser) do
- parser.peek_token.action_svalue(parser)
- end
-end
-# State number
-private class LRStatenumber
- super LRState
- redef fun to_s do return "number"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_number(parser)
- end
-end
-# State string
-private class LRStatestring
- super LRState
- redef fun to_s do return "string"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_string(parser)
- end
-end
-# State 'true'
-private class LRState_39dtrue_39d
- super LRState
- redef fun to_s do return "\'true\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_true(parser)
- end
-end
-# State 'false'
-private class LRState_39dfalse_39d
- super LRState
- redef fun to_s do return "\'false\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_false(parser)
- end
-end
-# State 'null'
-private class LRState_39dnull_39d
- super LRState
- redef fun to_s do return "\'null\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_null(parser)
- end
-end
-# State '{'
-private class LRState_39d_123d_39d
- super LRState
- redef fun to_s do return "\'\{\'"
- redef fun error_msg do return "members, pair"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_123d_39d(parser)
- end
- redef fun goto(parser, goto) do
- goto.goto_s_39d_123d_39d(parser)
- end
-end
-# State '['
-private class LRState_39d_91d_39d
- super LRState
- redef fun to_s do return "\'[\'"
- redef fun error_msg do return "elements, value"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_91d_39d(parser)
- end
- redef fun goto(parser, goto) do
- goto.goto_s_39d_91d_39d(parser)
- end
-end
-# State value Eof
-private class LRStatevalue_32dEof
- super LRState
- redef fun to_s do return "value Eof"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_NStart(parser)
- end
-end
-# State '{' members
-private class LRState_39d_123d_39d_32dmembers
- super LRState
- redef fun to_s do return "\'\{\' members"
- redef fun error_msg do return "\'\}\', \',\'"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_123d_39d_32dmembers(parser)
- end
-end
-# State '{' '}'
-private class LRState_39d_123d_39d_32d_39d_125d_39d
- super LRState
- redef fun to_s do return "\'\{\' \'\}\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_object_95d1(parser)
- end
-end
-# State '{' pair
-private class LRState_39d_123d_39d_32dpair
- super LRState
- redef fun to_s do return "\'\{\' pair"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nmembers_head(parser)
- end
-end
-# State '{' string
-private class LRState_39d_123d_39d_32dstring
- super LRState
- redef fun to_s do return "\'\{\' string"
- redef fun error_msg do return "\':\'"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_123d_39d_32dstring(parser)
- end
-end
-# State '[' elements
-private class LRState_39d_91d_39d_32delements
- super LRState
- redef fun to_s do return "\'[\' elements"
- redef fun error_msg do return "\']\', \',\'"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_91d_39d_32delements(parser)
- end
-end
-# State '[' ']'
-private class LRState_39d_91d_39d_32d_39d_93d_39d
- super LRState
- redef fun to_s do return "\'[\' \']\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_array_95d1(parser)
- end
-end
-# State '[' value
-private class LRState_39d_91d_39d_32dvalue
- super LRState
- redef fun to_s do return "\'[\' value"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nelements_head(parser)
- end
-end
-# State '{' members '}'
-private class LRState_39d_123d_39d_32dmembers_32d_39d_125d_39d
- super LRState
- redef fun to_s do return "\'\{\' members \'\}\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_object_95d0(parser)
- end
-end
-# State '{' members ','
-private class LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d
- super LRState
- redef fun to_s do return "\'\{\' members \',\'"
- redef fun error_msg do return "pair"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_123d_39d_32dmembers_32d_39d_44d_39d(parser)
- end
- redef fun goto(parser, goto) do
- parser.push(state__39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair)
- end
-end
-# State '{' string ':'
-private class LRState_39d_123d_39d_32dstring_32d_39d_58d_39d
- super LRState
- redef fun to_s do return "\'\{\' string \':\'"
- redef fun error_msg do return "value"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_123d_39d_32dstring_32d_39d_58d_39d(parser)
- end
- redef fun goto(parser, goto) do
- parser.push(state__39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue)
- end
-end
-# State '[' elements ']'
-private class LRState_39d_91d_39d_32delements_32d_39d_93d_39d
- super LRState
- redef fun to_s do return "\'[\' elements \']\'"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nvalue_array_95d0(parser)
- end
-end
-# State '[' elements ','
-private class LRState_39d_91d_39d_32delements_32d_39d_44d_39d
- super LRState
- redef fun to_s do return "\'[\' elements \',\'"
- redef fun error_msg do return "value"
- redef fun action(parser) do
- parser.peek_token.action_s_39d_91d_39d_32delements_32d_39d_44d_39d(parser)
- end
- redef fun goto(parser, goto) do
- parser.push(state__39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue)
- end
-end
-# State '{' members ',' pair
-private class LRState_39d_123d_39d_32dmembers_32d_39d_44d_39d_32dpair
- super LRState
- redef fun to_s do return "\'\{\' members \',\' pair"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nmembers_tail(parser)
- end
-end
-# State '{' string ':' value
-private class LRState_39d_123d_39d_32dstring_32d_39d_58d_39d_32dvalue
- super LRState
- redef fun to_s do return "\'\{\' string \':\' value"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Npair(parser)
- end
-end
-# State '[' elements ',' value
-private class LRState_39d_91d_39d_32delements_32d_39d_44d_39d_32dvalue
- super LRState
- redef fun to_s do return "\'[\' elements \',\' value"
- redef fun error_msg do return ""
- redef fun action(parser) do
- reduce_Nelements_tail(parser)
- end
-end
# Services to read JSON: `from_json_string` and `JsonDeserializer`
module serialization_read
-import ::serialization::caching
-private import ::serialization::engine_tools
+import serialization::caching
+private import serialization::engine_tools
+import serialization::safe
+
private import static
-private import string_parser
import poset
# Deserializer from a Json string.
class JsonDeserializer
super CachingDeserializer
+ super SafeDeserializer
# Json text to deserialize from.
private var text: Text
- # Accepted parameterized classes to deserialize
- #
- # If `whitelist.empty`, all types are accepted.
- #
- # ~~~nitish
- # import json::serialization
- #
- # class MyClass
- # serialize
- # end
- #
- # var json_string = """
- # {"__class" = "MyClass"}
- # """
- #
- # var deserializer = new JsonDeserializer(json_string)
- # var obj = deserializer.deserialize
- # assert deserializer.errors.is_empty
- # assert obj isa MyClass
- #
- # deserializer = new JsonDeserializer(json_string)
- # deserializer.whitelist.add "Array[String]"
- # deserializer.whitelist.add "AnotherAcceptedClass"
- # obj = deserializer.deserialize
- # assert deserializer.errors.length == 1
- # assert obj == null
- # ~~~
- var whitelist = new Array[Text]
-
- # Should objects be checked if they a subtype of the static type before deserialization?
- #
- # Defaults to `true`, as it should always be activated.
- # It can be turned off to implement the subtype check itself.
- var check_subtypes = true is writable
-
# Root json object parsed from input text.
private var root: nullable Object is noinit
if class_name == null and static_type != null then
# Fallack to the static type, strip the `nullable` prefix
- var prefix = "nullable "
- if static_type.has_prefix(prefix) then
- class_name = static_type.substring_from(prefix.length)
- else class_name = static_type
+ class_name = static_type.strip_nullable
end
end
return object
end
- if whitelist.not_empty and not whitelist.has(class_name) then
- errors.add new Error("Deserialization Error: '{class_name}' not in whitelist")
- return null
- end
-
- if static_type != null and check_subtypes then
- var static_class = static_type.strip_nullable_and_params
- var dynamic_class = class_name.strip_nullable_and_params
- if not class_inheritance_metamodel.has_edge(dynamic_class, static_class) then
- errors.add new Error("Deserialization Error: `{class_name}` is not a subtype of the static type `{static_type}`")
- return null
- end
- end
+ if not accept(class_name, static_type) then return null
# advance on path
path.push object
return val.chars.first
end
+ # byte?
+ if kind == "byte" then
+ var val = object.get_or_null("__val")
+ if not val isa Int then
+ errors.add new Error("Serialization Error: JSON `byte` object does not declare an integer `__val`.")
+ return object
+ end
+
+ return val.to_b
+ end
+
errors.add new Error("Deserialization Error: JSON object has an unknown `__kind`.")
return object
end
if object isa Array[nullable Object] then
# Can we use the static type?
if static_type != null then
- var prefix = "nullable "
- var class_name = if static_type.has(prefix) then
- static_type.substring_from(prefix.length)
- else static_type
-
opened_array = object
- var value = deserialize_class(class_name)
+ var value = deserialize_class(static_type.strip_nullable)
opened_array = null
return value
end
return array
end
+ if object isa String and object.length == 1 and static_type == "Char" then
+ # Char serialized as a JSON string
+ return object.chars.first
+ end
+
+ if object isa Int and static_type == "Byte" then
+ # Byte serialized as an integer
+ return object.to_b
+ end
+
return object
end
end
return res
end
-
- # Strip the `nullable` prefix and the params from the class name `self`
- #
- # ~~~nitish
- # assert "String".strip_nullable_and_params == "String"
- # assert "Array[Int]".strip_nullable_and_params == "Array"
- # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
- # ~~~
- private fun strip_nullable_and_params: String
- do
- var class_name = to_s
-
- var prefix = "nullable "
- if class_name.has_prefix(prefix) then class_name = class_name.substring_from(prefix.length)
-
- var bracket_index = class_name.index_of('[')
- if bracket_index == -1 then return class_name
- return class_name.substring(0, bracket_index)
- end
end
redef class SimpleCollection[E]
private fun class_inheritance_metamodel_json: CString is intern
redef class Sys
- # Class inheritance graph
- #
- # ~~~
- # var hierarchy = class_inheritance_metamodel
- # assert hierarchy.has_edge("String", "Object")
- # assert not hierarchy.has_edge("Object", "String")
- # ~~~
- var class_inheritance_metamodel: POSet[String] is lazy do
+ redef var class_inheritance_metamodel is lazy do
var engine = new JsonDeserializer(class_inheritance_metamodel_json.to_s)
engine.check_subtypes = false
engine.whitelist.add_all(
- ["String", "POSet[String]", "POSetElement[String]", "HashSet[String]", "HashMap[String, POSetElement[String]]"])
+ ["String", "POSet[String]", "POSetElement[String]",
+ "HashSet[String]", "HashMap[String, POSetElement[String]]"])
+
var poset = engine.deserialize
if engine.errors.not_empty then
- print_error engine.errors.join("\n")
+ print_error "Deserialization errors in class_inheritance_metamodel:"
+ print_error engine.errors.join("\n* ")
return new POSet[String]
end
+
if poset isa POSet[String] then return poset
return new POSet[String]
end
end
-
-redef class Deserializer
- redef fun deserialize_class(name)
- do
- if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
- if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
- if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
- if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
-
- return super
- end
-end
# 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.
+ # * Preserve the Nit `Char` and `Byte` types as special objects.
# * 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 metadata, it can be complex to read.
# which is used by all the serialization engines, not just JSON.
protected fun accept_json_serializer(v: JsonSerializer)
do
- var id = v.cache.new_id_for(self)
v.stream.write "\{"
v.indent_level += 1
if not v.plain_json then
+ var id = v.cache.new_id_for(self)
v.new_line_and_indent
v.stream.write "\"__kind\": \"obj\", \"__id\": "
v.stream.write id.to_s
end
end
+redef class Byte
+ redef fun accept_json_serializer(v)
+ do
+ if v.plain_json then
+ to_i.accept_json_serializer v
+ else
+ v.stream.write "\{\"__kind\": \"byte\", \"__val\": "
+ to_i.accept_json_serializer v
+ v.stream.write "\}"
+ end
+ end
+end
+
redef class CString
redef fun accept_json_serializer(v) do to_s.accept_json_serializer(v)
end
redef class SimpleCollection[E]
redef fun accept_json_serializer(v)
do
- # Register as pseudo object
- if not v.plain_json then
+ if v.plain_json then
+ serialize_to_pure_json v
+ else
+ # Register as pseudo object
var id = v.cache.new_id_for(self)
v.stream.write """{"""
v.indent_level += 1
v.stream.write class_name
v.stream.write """","""
v.new_line_and_indent
+
v.stream.write """"__items": """
serialize_to_pure_json v
+
core_serialize_to v
- else
- serialize_to_pure_json v
- end
- if not v.plain_json then
v.indent_level -= 1
v.new_line_and_indent
v.stream.write "\}"
# This object can then be type checked as usual with `isa` and `as`.
module static
-import error
-private import json_parser
-private import json_lexer
+import parser_base
+intrude import error
redef class Text
#
# assert not "string".json_need_escape
# assert "\\\"string\\\"".json_need_escape
- protected fun json_need_escape: Bool do return has('\\')
+ private fun json_need_escape: Bool do return has('\\')
# Escapes `self` from a JSON string to a Nit string
#
# assert "\\\"string\\\"".json_to_nit_string == "\"string\""
# assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
# assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
- protected fun json_to_nit_string: String do
+ private fun json_to_nit_string: String do
var res = new FlatBuffer.with_capacity(byte_length)
var i = 0
var ln = self.length
# assert str isa String
# assert str == "foo, bar, baz"
#
- # Example of a syntaxic error:
+ # Example of a syntax error:
#
- # var bad = "\{foo: \"bar\"\}".parse_json
- # assert bad isa JsonParseError
- # assert bad.position.col_start == 2
- fun parse_json: nullable Serializable do
- var lexer = new Lexer_json(to_s)
- var parser = new Parser_json
- var tokens = lexer.lex
- parser.tokens.add_all(tokens)
- var root_node = parser.parse
- if root_node isa NStart then
- return root_node.n_0.to_nit_object
- else if root_node isa NError then
- return new JsonParseError(root_node.message, root_node.position)
- else abort
- end
+ # var error = "\{foo: \"bar\"\}".parse_json
+ # assert error isa JsonParseError
+ # assert error.to_s == "Bad key format Error: bad JSON entity"
+ fun parse_json: nullable Serializable do return (new JSONStringParser(self.to_s)).parse_entity
end
redef class FlatText
end
end
-# A map that can be translated into a JSON object.
-interface JsonMapRead[K: String, V: nullable Serializable]
- super MapRead[K, V]
- super Serializable
-end
-
-# A JSON Object.
-class JsonObject
- super JsonMapRead[String, nullable Serializable]
- super HashMap[String, nullable Serializable]
-end
-
-# A sequence that can be translated into a JSON array.
-class JsonSequenceRead[E: nullable Serializable]
- super Serializable
- super SequenceRead[E]
-end
-
-# A JSON array.
-class JsonArray
- super JsonSequenceRead[nullable Serializable]
- super Array[nullable Serializable]
-end
-
-################################################################################
-# Redef parser
-
-redef class Nvalue
- # The represented value.
- private fun to_nit_object: nullable Serializable is abstract
-end
-
-redef class Nvalue_number
- redef fun to_nit_object
- do
- var text = n_number.text
- if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
- return text.to_i
+redef class Char
+ # Is `self` a valid number start ?
+ private fun is_json_num_start: Bool do
+ if self == '-' then return true
+ if self.is_numeric then return true
+ return false
end
-end
-redef class Nvalue_string
- redef fun to_nit_object do return n_string.to_nit_string
-end
-
-redef class Nvalue_true
- redef fun to_nit_object do return true
+ # Is `self` a valid JSON separator ?
+ private fun is_json_separator: Bool do
+ if self == ':' then return true
+ if self == ',' then return true
+ if self == '{' then return true
+ if self == '}' then return true
+ if self == '[' then return true
+ if self == ']' then return true
+ if self == '"' then return true
+ if self.is_whitespace then return true
+ return false
+ end
end
-redef class Nvalue_false
- redef fun to_nit_object do return false
-end
+# A simple ad-hoc JSON parser
+#
+# To parse a simple JSON document, read it as a String and give it to `parse_entity`
+# NOTE: if your document contains several non-nested entities, use `parse_entity` for each
+# JSON entity to parse
+class JSONStringParser
+ super StringProcessor
-redef class Nvalue_null
- redef fun to_nit_object do return null
-end
+ # Parses a JSON Entity
+ #
+ # ~~~nit
+ # var p = new JSONStringParser("""{"numbers": [1,23,3], "string": "string"}""")
+ # assert p.parse_entity isa JsonObject
+ # ~~~
+ fun parse_entity: nullable Serializable do
+ var srclen = len
+ ignore_whitespaces
+ if pos >= srclen then return make_parse_error("Empty JSON")
+ var c = src[pos]
+ if c == '[' then
+ pos += 1
+ return parse_json_array
+ else if c == '"' then
+ var s = parse_json_string
+ return s
+ else if c == '{' then
+ pos += 1
+ return parse_json_object
+ else if c == 'f' then
+ if pos + 4 >= srclen then make_parse_error("Error: bad JSON entity")
+ if src[pos + 1] == 'a' and src[pos + 2] == 'l' and src[pos + 3] == 's' and src[pos + 4] == 'e' then
+ pos += 5
+ return false
+ end
+ return make_parse_error("Error: bad JSON entity")
+ else if c == 't' then
+ if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity")
+ if src[pos + 1] == 'r' and src[pos + 2] == 'u' and src[pos + 3] == 'e' then
+ pos += 4
+ return true
+ end
+ return make_parse_error("Error: bad JSON entity")
+ else if c == 'n' then
+ if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity")
+ if src[pos + 1] == 'u' and src[pos + 2] == 'l' and src[pos + 3] == 'l' then
+ pos += 4
+ return null
+ end
+ return make_parse_error("Error: bad JSON entity")
+ end
+ if not c.is_json_num_start then return make_parse_error("Bad JSON character")
+ return parse_json_number
+ end
-redef class Nstring
- # The represented string.
- private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
-end
+ # Parses a JSON Array
+ fun parse_json_array: Serializable do
+ var max = len
+ if pos >= max then return make_parse_error("Incomplete JSON array")
+ var arr = new JsonArray
+ var c = src[pos]
+ while not c == ']' do
+ ignore_whitespaces
+ if pos >= max then return make_parse_error("Incomplete JSON array")
+ if src[pos] == ']' then break
+ var ent = parse_entity
+ #print "Parsed an entity {ent} for a JSON array"
+ if ent isa JsonParseError then return ent
+ arr.add ent
+ ignore_whitespaces
+ if pos >= max then return make_parse_error("Incomplete JSON array")
+ c = src[pos]
+ if c == ']' then break
+ if c != ',' then return make_parse_error("Bad array separator {c}")
+ pos += 1
+ end
+ pos += 1
+ return arr
+ end
-redef class Nvalue_object
- redef fun to_nit_object do
+ # Parses a JSON Object
+ fun parse_json_object: Serializable do
+ var max = len
+ if pos >= max then return make_parse_error("Incomplete JSON object")
var obj = new JsonObject
- var members = n_members
- if members != null then
- var pairs = members.pairs
- for pair in pairs do obj[pair.name] = pair.value
+ var c = src[pos]
+ while not c == '}' do
+ ignore_whitespaces
+ if pos >= max then return make_parse_error("Malformed JSON object")
+ if src[pos] == '}' then break
+ var key = parse_entity
+ #print "Parsed key {key} for JSON object"
+ if not key isa String then return make_parse_error("Bad key format {key or else "null"}")
+ ignore_whitespaces
+ if pos >= max then return make_parse_error("Incomplete JSON object")
+ if not src[pos] == ':' then return make_parse_error("Bad key/value separator {src[pos]}")
+ pos += 1
+ ignore_whitespaces
+ var value = parse_entity
+ #print "Parsed value {value} for JSON object"
+ if value isa JsonParseError then return value
+ obj[key] = value
+ ignore_whitespaces
+ if pos >= max then return make_parse_error("Incomplete JSON object")
+ c = src[pos]
+ if c == '}' then break
+ if c != ',' then return make_parse_error("Bad object separator {src[pos]}")
+ pos += 1
end
+ pos += 1
return obj
end
-end
-
-redef class Nmembers
- # All the key-value pairs.
- private fun pairs: Array[Npair] is abstract
-end
-redef class Nmembers_tail
- redef fun pairs
- do
- var arr = n_members.pairs
- arr.add n_pair
- return arr
+ # Creates a `JsonParseError` with the right message and location
+ protected fun make_parse_error(message: String): JsonParseError do
+ var err = new JsonParseError(message)
+ err.location = hot_location
+ return err
end
-end
-redef class Nmembers_head
- redef fun pairs do return [n_pair]
-end
+ # Parses an Int or Float
+ fun parse_json_number: Serializable do
+ var max = len
+ var p = pos
+ var c = src[p]
+ var is_neg = false
+ if c == '-' then
+ is_neg = true
+ p += 1
+ if p >= max then return make_parse_error("Bad JSON number")
+ c = src[p]
+ end
+ var val = 0
+ while c.is_numeric do
+ val *= 10
+ val += c.to_i
+ p += 1
+ if p >= max then break
+ c = src[p]
+ end
+ if c == '.' then
+ p += 1
+ if p >= max then return make_parse_error("Bad JSON number")
+ c = src[p]
+ var fl = val.to_f
+ var frac = 0.1
+ while c.is_numeric do
+ fl += c.to_i.to_f * frac
+ frac /= 10.0
+ p += 1
+ if p >= max then break
+ c = src[p]
+ end
+ if c == 'e' or c == 'E' then
+ p += 1
+ var exp = 0
+ if p >= max then return make_parse_error("Malformed JSON number")
+ c = src[p]
+ while c.is_numeric do
+ exp *= 10
+ exp += c.to_i
+ p += 1
+ if p >= max then break
+ c = src[p]
+ end
+ fl *= (10 ** exp).to_f
+ end
+ if p < max and not c.is_json_separator then return make_parse_error("Malformed JSON number")
+ pos = p
+ if is_neg then return -fl
+ return fl
+ end
+ if c == 'e' or c == 'E' then
+ p += 1
+ if p >= max then return make_parse_error("Bad JSON number")
+ var exp = src[p].to_i
+ c = src[p]
+ while c.is_numeric do
+ exp *= 10
+ exp += c.to_i
+ p += 1
+ if p >= max then break
+ c = src[p]
+ end
+ val *= (10 ** exp)
+ end
+ if p < max and not src[p].is_json_separator then return make_parse_error("Malformed JSON number")
+ pos = p
+ if is_neg then return -val
+ return val
+ end
-redef class Npair
- # The represented key.
- private fun name: String do return n_string.to_nit_string
+ private var parse_str_buf = new FlatBuffer
- # The represented value.
- private fun value: nullable Serializable do return n_value.to_nit_object
-end
+ # Parses and returns a Nit string from a JSON String
+ fun parse_json_string: Serializable do
+ var src = src
+ var ln = src.length
+ var p = pos
+ p += 1
+ if p > ln then return make_parse_error("Malformed JSON String")
+ var c = src[p]
+ var ret = parse_str_buf
+ var chunk_st = p
+ while c != '"' do
+ if c != '\\' then
+ p += 1
+ if p >= ln then return make_parse_error("Malformed JSON string")
+ c = src[p]
+ continue
+ end
+ ret.append_substring_impl(src, chunk_st, p - chunk_st)
+ p += 1
+ if p >= ln then return make_parse_error("Malformed Escape sequence in JSON string")
+ c = src[p]
+ if c == 'r' then
+ ret.add '\r'
+ p += 1
+ else if c == 'n' then
+ ret.add '\n'
+ p += 1
+ else if c == 't' then
+ ret.add '\t'
+ p += 1
+ else if c == 'u' then
+ var cp = 0
+ p += 1
+ for i in [0 .. 4[ do
+ cp <<= 4
+ if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ c = src[p]
+ if c >= '0' and c <= '9' then
+ cp += c.code_point - '0'.code_point
+ else if c >= 'a' and c <= 'f' then
+ cp += c.code_point - 'a'.code_point + 10
+ else if c >= 'A' and c <= 'F' then
+ cp += c.code_point - 'A'.code_point + 10
+ else
+ make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ end
+ p += 1
+ end
+ c = cp.code_point
+ if cp >= 0xD800 and cp <= 0xDBFF then
+ if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ c = src[p]
+ if c != '\\' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ p += 1
+ c = src[p]
+ if c != 'u' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ var locp = 0
+ p += 1
+ for i in [0 .. 4[ do
+ locp <<= 4
+ if p > ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ c = src[p]
+ if c >= '0' and c <= '9' then
+ locp += c.code_point - '0'.code_point
+ else if c >= 'a' and c <= 'f' then
+ locp += c.code_point - 'a'.code_point + 10
+ else if c >= 'A' and c <= 'F' then
+ locp += c.code_point - 'A'.code_point + 10
+ else
+ make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
+ end
+ p += 1
+ end
+ c = (((locp & 0x3FF) | ((cp & 0x3FF) << 10)) + 0x10000).code_point
+ end
+ ret.add c
+ else if c == 'b' then
+ ret.add 8.code_point
+ p += 1
+ else if c == 'f' then
+ ret.add '\f'
+ p += 1
+ else
+ p += 1
+ ret.add c
+ end
+ chunk_st = p
+ c = src[p]
+ end
+ pos = p + 1
+ if ret.is_empty then return src.substring(chunk_st, p - chunk_st)
+ ret.append_substring_impl(src, chunk_st, p - chunk_st)
+ var rets = ret.to_s
+ ret.clear
+ return rets
+ end
-redef class Nvalue_array
- redef fun to_nit_object
- do
- var arr = new JsonArray
- var elements = n_elements
- if elements != null then
- var items = elements.items
- for item in items do arr.add(item.to_nit_object)
+ # Ignores any character until a JSON separator is encountered
+ fun ignore_until_separator do
+ var max = len
+ while pos < max do
+ if not src[pos].is_json_separator then return
end
- return arr
end
end
-redef class Nelements
- # All the items.
- private fun items: Array[Nvalue] is abstract
+# A map that can be translated into a JSON object.
+interface JsonMapRead[K: String, V: nullable Serializable]
+ super MapRead[K, V]
+ super Serializable
end
-redef class Nelements_tail
- redef fun items
- do
- var items = n_elements.items
- items.add(n_value)
- return items
- end
+# A JSON Object.
+class JsonObject
+ super JsonMapRead[String, nullable Serializable]
+ super HashMap[String, nullable Serializable]
end
-redef class Nelements_head
- redef fun items do return [n_value]
+# A sequence that can be translated into a JSON array.
+class JsonSequenceRead[E: nullable Serializable]
+ super Serializable
+ super SequenceRead[E]
+end
+
+# A JSON array.
+class JsonArray
+ super JsonSequenceRead[nullable Serializable]
+ super Array[nullable Serializable]
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.
-
-# Simple ad-hoc implementation of a JSON parser for String inputs
-module string_parser
-
-import parser_base
-import static
-
-redef class Char
- # Is `self` a valid number start ?
- private fun is_json_num_start: Bool do
- if self == '-' then return true
- if self.is_numeric then return true
- return false
- end
-
- # Is `self` a valid JSON separator ?
- private fun is_json_separator: Bool do
- if self == ':' then return true
- if self == ',' then return true
- if self == '{' then return true
- if self == '}' then return true
- if self == '[' then return true
- if self == ']' then return true
- if self == '"' then return true
- if self.is_whitespace then return true
- return false
- end
-end
-
-# A simple ad-hoc JSON parser
-#
-# To parse a simple JSON document, read it as a String and give it to `parse_entity`
-# NOTE: if your document contains several non-nested entities, use `parse_entity` for each
-# JSON entity to parse
-class JSONStringParser
- super StringProcessor
-
- # Parses a JSON Entity
- #
- # ~~~nit
- # var p = new JSONStringParser("""{"numbers": [1,23,3], "string": "string"}""")
- # assert p.parse_entity isa JsonObject
- # ~~~
- fun parse_entity: nullable Serializable do
- var srclen = len
- ignore_whitespaces
- if pos >= srclen then return make_parse_error("Empty JSON")
- var c = src[pos]
- if c == '[' then
- pos += 1
- return parse_json_array
- else if c == '"' then
- var s = parse_json_string
- return s
- else if c == '{' then
- pos += 1
- return parse_json_object
- else if c == 'f' then
- if pos + 4 >= srclen then make_parse_error("Error: bad JSON entity")
- if src[pos + 1] == 'a' and src[pos + 2] == 'l' and src[pos + 3] == 's' and src[pos + 4] == 'e' then
- pos += 5
- return false
- end
- return make_parse_error("Error: bad JSON entity")
- else if c == 't' then
- if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity")
- if src[pos + 1] == 'r' and src[pos + 2] == 'u' and src[pos + 3] == 'e' then
- pos += 4
- return true
- end
- return make_parse_error("Error: bad JSON entity")
- else if c == 'n' then
- if pos + 3 >= srclen then make_parse_error("Error: bad JSON entity")
- if src[pos + 1] == 'u' and src[pos + 2] == 'l' and src[pos + 3] == 'l' then
- pos += 4
- return null
- end
- return make_parse_error("Error: bad JSON entity")
- end
- if not c.is_json_num_start then return make_parse_error("Bad JSON character")
- return parse_json_number
- end
-
- # Parses a JSON Array
- fun parse_json_array: Serializable do
- var max = len
- if pos >= max then return make_parse_error("Incomplete JSON array")
- var arr = new JsonArray
- var c = src[pos]
- while not c == ']' do
- ignore_whitespaces
- if pos >= max then return make_parse_error("Incomplete JSON array")
- if src[pos] == ']' then break
- var ent = parse_entity
- #print "Parsed an entity {ent} for a JSON array"
- if ent isa JsonParseError then return ent
- arr.add ent
- ignore_whitespaces
- if pos >= max then return make_parse_error("Incomplete JSON array")
- c = src[pos]
- if c == ']' then break
- if c != ',' then return make_parse_error("Bad array separator {c}")
- pos += 1
- end
- pos += 1
- return arr
- end
-
- # Parses a JSON Object
- fun parse_json_object: Serializable do
- var max = len
- if pos >= max then return make_parse_error("Incomplete JSON object")
- var obj = new JsonObject
- var c = src[pos]
- while not c == '}' do
- ignore_whitespaces
- if pos >= max then return make_parse_error("Malformed JSON object")
- if src[pos] == '}' then break
- var key = parse_entity
- #print "Parsed key {key} for JSON object"
- if not key isa String then return make_parse_error("Bad key format {key or else "null"}")
- ignore_whitespaces
- if pos >= max then return make_parse_error("Incomplete JSON object")
- if not src[pos] == ':' then return make_parse_error("Bad key/value separator {src[pos]}")
- pos += 1
- ignore_whitespaces
- var value = parse_entity
- #print "Parsed value {value} for JSON object"
- if value isa JsonParseError then return value
- obj[key] = value
- ignore_whitespaces
- if pos >= max then return make_parse_error("Incomplete JSON object")
- c = src[pos]
- if c == '}' then break
- if c != ',' then return make_parse_error("Bad object separator {src[pos]}")
- pos += 1
- end
- pos += 1
- return obj
- end
-
- # Creates a `JsonParseError` with the right message and location
- protected fun make_parse_error(message: String): JsonParseError do
- var err = new JsonParseError(message)
- err.location = hot_location
- return err
- end
-
- # Parses an Int or Float
- fun parse_json_number: Serializable do
- var max = len
- var p = pos
- var c = src[p]
- var is_neg = false
- if c == '-' then
- is_neg = true
- p += 1
- if p >= max then return make_parse_error("Bad JSON number")
- c = src[p]
- end
- var val = 0
- while c.is_numeric do
- val *= 10
- val += c.to_i
- p += 1
- if p >= max then break
- c = src[p]
- end
- if c == '.' then
- p += 1
- if p >= max then return make_parse_error("Bad JSON number")
- c = src[p]
- var fl = val.to_f
- var frac = 0.1
- while c.is_numeric do
- fl += c.to_i.to_f * frac
- frac /= 10.0
- p += 1
- if p >= max then break
- c = src[p]
- end
- if c == 'e' or c == 'E' then
- p += 1
- var exp = 0
- if p >= max then return make_parse_error("Malformed JSON number")
- c = src[p]
- while c.is_numeric do
- exp *= 10
- exp += c.to_i
- p += 1
- if p >= max then break
- c = src[p]
- end
- fl *= (10 ** exp).to_f
- end
- if p < max and not c.is_json_separator then return make_parse_error("Malformed JSON number")
- pos = p
- if is_neg then return -fl
- return fl
- end
- if c == 'e' or c == 'E' then
- p += 1
- if p >= max then return make_parse_error("Bad JSON number")
- var exp = src[p].to_i
- c = src[p]
- while c.is_numeric do
- exp *= 10
- exp += c.to_i
- p += 1
- if p >= max then break
- c = src[p]
- end
- val *= (10 ** exp)
- end
- if p < max and not src[p].is_json_separator then return make_parse_error("Malformed JSON number")
- pos = p
- if is_neg then return -val
- return val
- end
-
- private var parse_str_buf = new FlatBuffer
-
- # Parses and returns a Nit string from a JSON String
- fun parse_json_string: Serializable do
- var src = src
- var ln = src.length
- var p = pos
- p += 1
- if p > ln then return make_parse_error("Malformed JSON String")
- var c = src[p]
- var ret = parse_str_buf
- var chunk_st = p
- while c != '"' do
- if c != '\\' then
- p += 1
- if p >= ln then return make_parse_error("Malformed JSON string")
- c = src[p]
- continue
- end
- ret.append_substring_impl(src, chunk_st, p - chunk_st)
- p += 1
- if p >= ln then return make_parse_error("Malformed Escape sequence in JSON string")
- c = src[p]
- if c == 'r' then
- ret.add '\r'
- p += 1
- else if c == 'n' then
- ret.add '\n'
- p += 1
- else if c == 't' then
- ret.add '\t'
- p += 1
- else if c == 'u' then
- var cp = 0
- p += 1
- for i in [0 .. 4[ do
- cp <<= 4
- if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- c = src[p]
- if c >= '0' and c <= '9' then
- cp += c.code_point - '0'.code_point
- else if c >= 'a' and c <= 'f' then
- cp += c.code_point - 'a'.code_point + 10
- else if c >= 'A' and c <= 'F' then
- cp += c.code_point - 'A'.code_point + 10
- else
- make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- end
- p += 1
- end
- c = cp.code_point
- if cp >= 0xD800 and cp <= 0xDBFF then
- if p >= ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- c = src[p]
- if c != '\\' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- p += 1
- c = src[p]
- if c != 'u' then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- var locp = 0
- p += 1
- for i in [0 .. 4[ do
- locp <<= 4
- if p > ln then make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- c = src[p]
- if c >= '0' and c <= '9' then
- locp += c.code_point - '0'.code_point
- else if c >= 'a' and c <= 'f' then
- locp += c.code_point - 'a'.code_point + 10
- else if c >= 'A' and c <= 'F' then
- locp += c.code_point - 'A'.code_point + 10
- else
- make_parse_error("Malformed \uXXXX Escape sequence in JSON string")
- end
- p += 1
- end
- c = (((locp & 0x3FF) | ((cp & 0x3FF) << 10)) + 0x10000).code_point
- end
- ret.add c
- else if c == 'b' then
- ret.add 8.code_point
- p += 1
- else if c == 'f' then
- ret.add '\f'
- p += 1
- else
- p += 1
- ret.add c
- end
- chunk_st = p
- c = src[p]
- end
- pos = p + 1
- if ret.is_empty then return src.substring(chunk_st, p - chunk_st)
- ret.append_substring_impl(src, chunk_st, p - chunk_st)
- var rets = ret.to_s
- ret.clear
- return rets
- end
-
- # Ignores any character until a JSON separator is encountered
- fun ignore_until_separator do
- var max = len
- while pos < max do
- if not src[pos].is_json_separator then return
- end
- end
-end
-
-redef class Text
- redef fun parse_json do return (new JSONStringParser(self.to_s)).parse_entity
-end
-
-redef class JsonParseError
- serialize
-
- # Location of the error in source
- var location: nullable Location = null
-end
self.native = native
end
- redef fun play
+ redef fun play do play_channel(-1, 0)
+
+ # Play this sound on `channel` (or any channel if -1) and return the channel
+ #
+ # Repeat the sound `loops` times, `loops == 0` plays it once,
+ # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+ fun play_channel(channel, loops: Int): Int
do
var native = native
end
# If there's an error, silently skip
- if error != null then return
+ if error != null then return -1
native = self.native
assert native != null
# Play on any available channel
- mix.play_channel(-1, native, 0)
+ return mix.play_channel(channel, native, loops)
end
end
import app::data_store
private import xdg_basedir
-private import sqlite3
+import sqlite3
private import json
-redef class App
- redef var data_store = new LinuxStore
-end
-
-private class LinuxStore
- super DataStore
+redef class DataStore
# File path of the Sqlite3 DB file
fun db_file: String do return "data_store.db"
# Sqlite3 table used
fun db_table: String do return "data_store"
- var db_cache: nullable Sqlite3DB = null
+ private var db_cache: nullable Sqlite3DB = null
# Database to use to implement the `DataStore`
fun db: nullable Sqlite3DB
# Prepare SELECT statement
var stmt = db.select("* FROM {db_table} WHERE key == {key.to_sql_string}")
+ if stmt == null then return null
# Execute statment
for row in stmt do
var deserializer = new JsonDeserializer(serialized)
var deserialized = deserializer.deserialize
+ var errors = deserializer.errors
+ if errors.not_empty then
+ # An update may have broken the versioning compatibility
+ print_error "{class_name} error at deserialization: {errors.join(", ")}"
+ return null # Let's be safe
+ end
+
return deserialized
end
if not line.is_empty and line.leading < 4 and line.value[line.leading] == '[' then
pos = line.leading + 1
pos = md.read_until(id, pos, ']')
- if not id.is_empty and pos + 2 < line.value.length then
+ if not id.is_empty and pos >= 0 and pos + 2 < line.value.length then
if line.value[pos + 1] == ':' then
pos += 2
pos = md.skip_spaces(pos)
- if line.value[pos] == '<' then
+ if pos >= 0 and line.value[pos] == '<' then
pos += 1
pos = md.read_until(link, pos, '>')
pos += 1
- else
+ else if pos >= 0 then
pos = md.read_until(link, pos, ' ', '\n')
end
if not link.is_empty then
# var db_name = "test_{db_suffix}"
# var db = client.database(db_name)
# db.collection("test").insert(new JsonObject)
- # assert client.database_names.has("test")
+ # assert client.database_names.has(db_name)
# ~~~
fun database_names: Array[String] do
var res = new Array[String]
--- /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.
+
+# Application specific MessagePack extension `MsgPackExt`
+module ext
+
+import serialization
+
+# Application specific MessagePack extension
+class MsgPackExt
+ serialize
+
+ # Custom type code, in [0..127]
+ var typ: Byte
+
+ # Data bytes
+ var data: Bytes
+
+ redef fun hash do return typ.hash + data.hash*8
+ redef fun ==(o) do return o isa MsgPackExt and o.typ == typ and o.data == data
+ redef fun to_s do return "<{class_name} typ: {typ}, data: {data.chexdigest}>"
+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.
+
+# MessagePack, an efficient binary serialization format
+#
+# This modules provides services at different levels:
+#
+# * Serialize Nit objects using either the quick and easy `Serializable::serialize_msgpack`
+# and `Write::serialize_msgpack`, or the extensible `MsgPackSerializer`.
+#
+# * Deserialize MessagePack to Nit objects using the quick and easy methods
+# `Reader|Bytes::deserialize_msgpack`, or inspect errors with the extensible
+# `MsgPackDeserializer`.
+#
+# * Read and write MessagePack data at a low-level (for engines and the likes)
+# by importing `msgpack::read` or `msgpack::write`. These services support
+# only which support only core Nit types with a corresponding MessagePack type.
+# See `Reader::read_msgpack` and services on `Writer` including `write_msgpack_ext`.
+#
+# Here we discuss the recommended serialization services supporting core
+# Nit and MessagePack types as well as full Nit objects.
+#
+# ## Primitive MessagePack types
+#
+# Most Nit core types are serialized to the smallest corresponding MessagePack type.
+#
+# ~~~
+# assert false.serialize_msgpack == b"\xC2"
+# assert b"\xC2".deserialize_msgpack == false
+#
+# assert true.serialize_msgpack == b"\xC3"
+# assert b"\xC3".deserialize_msgpack == true
+#
+# assert 1.234.serialize_msgpack == b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58"
+# assert b"\xCB\x3F\xF3\xBE\x76\xC8\xB4\x39\x58".deserialize_msgpack == 1.234
+#
+# assert "ABC".serialize_msgpack == b"\xA3ABC"
+# assert b"\xA3ABC".deserialize_msgpack == "ABC"
+#
+# assert [0x11, 0x22, 0x33].serialize_msgpack(plain=true) == b"\x93\x11\x22\x33"
+# assert b"\x93\x11\x22\x33".deserialize_msgpack == [0x11, 0x22, 0x33]
+#
+# var map = new Map[String, nullable Object]
+# map["i"] = 1
+# map["o"] = null
+# assert map.serialize_msgpack(plain=true) == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# ~~~
+#
+# Ints are serialized to the smallest MessagePack type, so a small integer fits
+# in a single byte and larger integers take more bytes as needed.
+#
+# ~~~
+# assert 1.serialize_msgpack == b"\x01"
+# assert (-32).serialize_msgpack == b"\xE0"
+# assert 0x7F.serialize_msgpack == b"\x7F"
+# assert 0x80.serialize_msgpack == b"\xCC\x80"
+# assert 0x1234.serialize_msgpack == b"\xCD\x12\x34"
+# assert (-0x1234).serialize_msgpack == b"\xD1\xED\xCC"
+# assert 0x12345678.serialize_msgpack == b"\xCE\x12\x34\x56\x78"
+# assert 0x0123456789.serialize_msgpack == b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89"
+#
+# assert b"\x01".deserialize_msgpack == 1
+# assert b"\xE0".deserialize_msgpack == -32
+# assert b"\x7F".deserialize_msgpack == 0x7F
+# assert b"\xCC\x80".deserialize_msgpack == 0x80
+# assert b"\xCD\x12\x34".deserialize_msgpack == 0x1234
+# assert b"\xD1\xED\xCC".deserialize_msgpack == -0x1234
+# assert b"\xCE\x12\x34\x56\x78".deserialize_msgpack == 0x12345678
+# assert b"\xCF\x00\x00\x00\x01\x23\x45\x67\x89".deserialize_msgpack == 0x0123456789
+# ~~~
+#
+# ## Primitive Nit type without a MessagePack equivalent
+#
+# Chars are serialized as a string in plain mode.
+#
+# ~~~
+# assert 'A'.serialize_msgpack(plain=true) == b"\xA1\x41"
+# assert b"\xA1\x41".deserialize_msgpack == "A" # Not a Char
+# ~~~
+#
+# Or, with metadata, chars are serialized to an ext with id 0x7C.
+#
+# ~~~
+# assert 'A'.serialize_msgpack == b"\xD4\x7C\x41"
+# assert b"\xD4\x7C\x41".deserialize_msgpack == 'A'
+# ~~~
+#
+# Byte instances are serialized as an integer in plain mode.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack(plain=true) == b"\x01"
+# assert b"\x01".deserialize_msgpack == 1 # Not a Byte
+# ~~~
+#
+# Or, with metadata, byte instances are serialized to an ext with id 0x7E.
+#
+# ~~~
+# assert 0x01u8.serialize_msgpack == b"\xD4\x7E\x01"
+# assert b"\xD4\x7E\x01".deserialize_msgpack == 0x01u8
+# ~~~
+#
+# ## Full objects
+#
+# Objects are serialized to a map in plain mode, replacing cycles by `null` values.
+# This creates plain MessagePack easy to read for other non-Nit programs,
+# but cycles and the dynamic type information are lost.
+#
+# ~~~
+# class A
+# serialize
+#
+# var i = 1
+# var o: nullable A = self
+#
+# redef fun ==(o) do return o isa A and o.i == i # Skip the cyclic `o`
+# end
+#
+# var a = new A
+# var bytes = a.serialize_msgpack(plain=true)
+# assert bytes == b"\x82\xA1\x69\x01\xA1\x6F\xC0"
+# assert bytes.deserialize_msgpack isa Map[nullable Serializable, nullable Serializable] # Not an A
+# ~~~
+#
+# Or, with metadata, the same object is serialized with information on object
+# uniqueness (with an id and references) and its dynamic type.
+# The whole object is contained in a MessagePack array:
+#
+# * The array holds the metadata and attributes or each object,
+# here it is a fixarray of 3 items: 0x93
+# * Define an object (ext type 0x7B) with the id 0, here a fixext1: 0xD47B00
+# * The dynamic type name, here a fixstr with the letter 'A': 0xA141
+# * The attributes as a map, here a fixmap of 2 items: 0x82
+# * First attribute name, here a fixstr for "i": 0xA169
+# * First attribute value, here a fixint for 1: 0x01
+# * Second attribute name, here a fixstr for "o": 0xA16F
+# * Second attribute value, a reference (ext type 0x7D) to object id 0,
+# here a fixext1: 0xD47D00
+#
+# ~~~
+# bytes = a.serialize_msgpack
+# assert bytes == b"\x93\xD4\x7B\x00\xA1\x41\x82\xA1\x69\x01\xA1\x6F\xD4\x7D\x00"
+# assert bytes.deserialize_msgpack == a
+# ~~~
+#
+# ## References
+#
+# Format description and other implementations: http://msgpack.org/
+#
+# Format specification: https://github.com/msgpack/msgpack/blob/master/spec.md
+module msgpack
+
+import serialization_write
+import serialization_read
--- /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.
+
+# Convert MessagePack format to JSON
+module msgpack_to_json
+
+import msgpack::read
+import json
+
+if args.has("-h") then
+ print "Usage: nit msgpack::msgpack_to_json [source_file.msgpack]"
+ print "Convert MessagePack format to JSON. Read from stdin if no source_file is given."
+ exit 0
+end
+
+var reader = if args.length >= 1 then
+ new FileReader.open(args.first)
+ else stdin
+
+while reader.last_error == null and not reader.eof do
+ var deserialized = reader.read_msgpack
+
+ if deserialized != null then
+ print deserialized.serialize_to_json(plain=true, pretty=true)
+ else
+ print "null"
+ end
+end
--- /dev/null
+[package]
+name=msgpack
+tags=format,lib
+maintainer=Alexis Laferrière <alexis.laf@xymus.net>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/msgpack/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/msgpack/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
--- /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.
+
+# Low-level read MessagePack format from `Reader` streams
+module read
+
+import serialization
+private import binary
+
+import ext
+
+redef class Reader
+
+ # Read the next MessagePack object and return it as a simple Nit object
+ #
+ # The return value is composed of:
+ # * the simple types `null`, `Bool`, `Int`, `Float`, `String` and `Bytes`,
+ # * collections of simple Nit objects `Array[nullable Serializable]`
+ # and `Map[nullable Serializable, nullable Serializable]`,
+ # * and `MsgPackExt` for custom MessagePack *ext* data.
+ #
+ # This method reads plain MessagePack data, as written by `MsgPackSerializer`
+ # when `plain_msgpack == true`. To deserialize full Nit objects from
+ # MessagePack with metadata use `Reader::deserialize_msgpack`.
+ fun read_msgpack: nullable Serializable
+ do
+ if last_error != null then return 0
+
+ var typ = read_byte
+ if typ == null then
+ # Error, return default `null`
+ return null
+
+ else if typ & 0b1000_0000u8 == 0u8 or typ & 0b1110_0000u8 == 0b1110_0000u8 then
+ # fixint
+ var bytes = new Bytes.with_capacity(1)
+ bytes.add typ
+ return bytes.to_i(signed=true)
+
+ else if typ & 0b1111_0000u8 == 0b1000_0000u8 then
+ # fixmap
+ var len = typ & 0b0000_1111u8
+ return read_msgpack_map_data(len.to_i)
+
+ else if typ & 0b1111_0000u8 == 0b1001_0000u8 then
+ # fixarray
+ var len = typ & 0b0000_1111u8
+ return read_msgpack_array_data(len.to_i)
+
+ else if typ & 0b1110_0000u8 == 0b1010_0000u8 then
+ # fixstr
+ var len = typ & 0b0001_1111u8
+ return read_bytes(len.to_i).to_s
+
+ else if typ == 0xC0u8 then
+ return null
+ else if typ == 0xC2u8 then
+ return false
+ else if typ == 0xC3u8 then
+ return true
+
+ else if typ == 0xCCu8 then
+ # uint8
+ return (read_byte or else 0u8).to_i
+ else if typ == 0xCDu8 then
+ # uint16
+ return read_bytes(2).to_i
+ else if typ == 0xCEu8 then
+ # uint32
+ return read_bytes(4).to_i
+ else if typ == 0xCFu8 then
+ # uint64
+ return read_bytes(8).to_i
+ else if typ == 0xD0u8 then
+ # int8
+ return read_bytes(1).to_i(true)
+ else if typ == 0xD1u8 then
+ # int16
+ return read_bytes(2).to_i(true)
+ else if typ == 0xD2u8 then
+ # int32
+ return read_bytes(4).to_i(true)
+ else if typ == 0xD3u8 then
+ # int64
+ return read_int64
+
+ else if typ == 0xCAu8 then
+ return read_float
+ else if typ == 0xCBu8 then
+ return read_double
+
+ else if typ == 0xD9u8 then
+ # str8
+ var len = read_byte
+ if len == null then return null
+ return read_bytes(len.to_i).to_s
+ else if typ == 0xDAu8 then
+ # str16
+ var len = read_bytes(2)
+ return read_bytes(len.to_i).to_s
+ else if typ == 0xDBu8 then
+ # str32
+ var len = read_bytes(4)
+ return read_bytes(len.to_i).to_s
+
+ else if typ == 0xC4u8 then
+ # bin8
+ var len = read_byte
+ if len == null then return null
+ return read_bytes(len.to_i)
+ else if typ == 0xC5u8 then
+ # bin16
+ var len = read_bytes(2)
+ return read_bytes(len.to_i)
+ else if typ == 0xC6u8 then
+ # bin32
+ var len = read_bytes(4)
+ return read_bytes(len.to_i)
+
+ else if typ == 0xDCu8 then
+ # array16
+ var len = read_bytes(2)
+ return read_msgpack_array_data(len.to_i)
+ else if typ == 0xDDu8 then
+ # array32
+ var len = read_bytes(4)
+ return read_msgpack_array_data(len.to_i)
+
+ else if typ == 0xDEu8 then
+ # map16
+ var len = read_bytes(2)
+ return read_msgpack_map_data(len.to_i)
+ else if typ == 0xDFu8 then
+ # map32
+ var len = read_bytes(4)
+ return read_msgpack_map_data(len.to_i)
+
+ else if typ == 0xD4u8 then
+ # fixext1
+ return read_msgpack_fixext_data(1)
+ else if typ == 0xD5u8 then
+ # fixext2
+ return read_msgpack_fixext_data(2)
+ else if typ == 0xD6u8 then
+ # fixext4
+ return read_msgpack_fixext_data(4)
+ else if typ == 0xD7u8 then
+ # fixext8
+ return read_msgpack_fixext_data(8)
+ else if typ == 0xD8u8 then
+ # fixext16
+ return read_msgpack_fixext_data(16)
+
+ else if typ == 0xC7u8 then
+ # ext1
+ return read_msgpack_ext_data(1)
+ else if typ == 0xC8u8 then
+ # ext2
+ return read_msgpack_ext_data(2)
+ else if typ == 0xC9u8 then
+ # ext4
+ return read_msgpack_ext_data(4)
+ end
+
+ print_error "MessagePack Warning: Found no match for typ {typ} / 0b{typ.to_i.to_base(2)}"
+ return null
+ end
+
+ # Read the content of a map, `len` keys and values
+ private fun read_msgpack_map_data(len: Int): Map[nullable Serializable, nullable Serializable]
+ do
+ var map = new Map[nullable Serializable, nullable Serializable]
+ for i in [0..len.to_i[ do map[read_msgpack] = read_msgpack
+ return map
+ end
+
+ # Read the content of an array of `len` items
+ private fun read_msgpack_array_data(len: Int): Array[nullable Serializable]
+ do
+ return [for i in [0..len[ do read_msgpack]
+ end
+
+ # Read the content of a *fixext* of `len` bytes
+ #
+ # ~~~
+ # var reader = new BytesReader(b"\xC7\x03\x0A\x0B\x0C\x0D")
+ # var ext = reader.read_msgpack
+ # assert ext isa MsgPackExt
+ # assert ext.typ == 0x0Au8
+ # assert ext.data == b"\x0B\x0C\x0D"
+ # ~~~
+ private fun read_msgpack_fixext_data(len: Int): MsgPackExt
+ do
+ var exttyp = read_byte or else 0u8
+ var data = read_bytes(len)
+ return new MsgPackExt(exttyp, data)
+ end
+
+ # Read the content of a dynamic *ext* including the length on `len_len` bytes
+ private fun read_msgpack_ext_data(len_len: Int): MsgPackExt
+ do
+ var len = read_bytes(len_len).to_i
+ return read_msgpack_fixext_data(len)
+ 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.
+
+# Serialization services for `serialization_write` and `serialization_read`
+module serialization_common
+
+# MessagePack serialization or deserialization engine
+abstract class MsgPackEngine
+
+ # *ext type* byte for object definitions, defaults to 0x7Bu8 or '{'
+ var ext_typ_obj: Byte = 0x7Bu8 is writable
+
+ # *ext type* byte for object references, defaults to 0x7Du8 or '}'
+ var ext_typ_ref: Byte = 0x7Du8 is writable
+
+ # *ext type* byte to identify a char, defaults to 0x7Cu8 or '~'
+ var ext_typ_char: Byte = 0x7Cu8 is writable
+
+ # *ext type* byte to identify a byte, defaults to 0x7Eu8 or '|'
+ var ext_typ_byte: Byte = 0x7Eu8 is writable
+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.
+
+# Deserialize full Nit objects from MessagePack format
+#
+# See the package `msgpack` for more details on the serialization
+# of Nit objects.
+module serialization_read
+
+import serialization::caching
+import serialization::safe
+private import json # for class_inheritance_metamodel
+private import serialization::engine_tools
+
+import serialization_common
+private import read
+import ext
+
+# ---
+# Easy services
+
+redef class Bytes
+
+ # Deserialize full Nit `nullable Object` from MessagePack formated data
+ #
+ # The dynamic type of the deserialized object can be limited to `static_type`.
+ #
+ # Warning: Deserialization errors are reported with `print_error`,
+ # the returned object may be partial or fall back on `null`.
+ # To handle the errors programmatically, use a `MsgPackDeserializer`.
+ fun deserialize_msgpack(static_type: nullable String): nullable Object
+ do
+ var stream = new BytesReader(self)
+ var res = stream.deserialize_msgpack(static_type)
+ stream.close
+ return res
+ end
+end
+
+redef class Reader
+
+ # Deserialize full Nit `nullable Object` from MessagePack formated data
+ #
+ # This method use metadata in the MessagePack source to recreate full
+ # Nit objects serialized by `Writer::serialize_msgpack` or
+ # `MsgPackSerializer`.
+ #
+ # The dynamic type of the deserialized object can be limited to `static_type`.
+ #
+ # Warning: Deserialization errors are reported with `print_error`,
+ # the returned object may be partial or fall back on `null`.
+ # To handle the errors programmatically, use a `MsgPackDeserializer`.
+ fun deserialize_msgpack(static_type: nullable String): nullable Object
+ do
+ var deserializer = new MsgPackDeserializer(self)
+ var res = deserializer.deserialize(static_type)
+
+ if deserializer.errors.length == 1 then
+ print_error deserializer.errors.join("")
+ else if deserializer.errors.not_empty then
+ print_error "Deserialization Errors:\n* {deserializer.errors.join("\n* ")}"
+ end
+
+ return res
+ end
+end
+
+# ---
+# Engine
+
+# Deserialize MessagePack format to full Nit objects
+class MsgPackDeserializer
+ super CachingDeserializer
+ super MsgPackEngine
+ super SafeDeserializer
+
+ # Source stream
+ var stream: Reader
+
+ # Map of attributes from the root deserialized object to the current object
+ private var path = new Array[Map[nullable Serializable, nullable Serializable]]
+
+ # Metadata arrays with from the root deserialized object to the current object
+ var path_arrays = new Array[nullable Array[nullable Object]]
+
+ # Names of the attributes from the root to the object currently being deserialized
+ var attributes_path = new Array[String]
+
+ # Last encountered object reference id.
+ #
+ # See `id_to_object`.
+ private var just_opened_id: nullable Int = null
+
+ redef fun deserialize_attribute(name, static_type)
+ do
+ if path.is_empty then
+ # The was a parsing error or the root is not an object
+ deserialize_attribute_missing = false
+ return null
+ end
+
+ var current = path.last
+
+ var serialized_value = null
+ var serialized_value_found = false
+ if current.keys.has(name) then
+ # Non-cached string
+ serialized_value = current[name]
+ serialized_value_found = true
+ else
+ # It may be cached, deserialize all keys until we find it
+ for key in current.keys.to_a do
+ if key isa Array[nullable Serializable] or key isa MsgPackExt then
+ var str = convert_object(key, "String")
+ if str isa String then
+ var value = current[key]
+ current.keys.remove key
+ current[str] = value
+
+ if str == name then
+ serialized_value = value
+ serialized_value_found = true
+ break
+ end
+ end
+ end
+ end
+ end
+
+ if not serialized_value_found then
+ # Let the generated code / caller of `deserialize_attribute` raise the missing attribute error
+ deserialize_attribute_missing = true
+ return null
+ end
+
+ attributes_path.add name
+ var res = convert_object(serialized_value, static_type)
+ attributes_path.pop
+
+ deserialize_attribute_missing = false
+ return res
+ end
+
+ # This may be called multiple times by the same object from defs of a same constructor
+ redef fun notify_of_creation(new_object)
+ do
+ var id = just_opened_id
+ if id == null then return
+ cache[id] = new_object
+ end
+
+ # Convert the simple JSON `object` to a Nit object
+ private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
+ do
+ #print "convert_object {if object != null then object.class_name else "null"}"
+ if object isa Array[nullable Object] and object.length >= 1 then
+ # Serialized object?
+ var first = object.first
+ if first isa MsgPackExt then
+ if first.typ == ext_typ_obj then
+ # An array starts with a *ext*, it must be a serialized object
+
+ # New object declaration
+ var id = first.data.to_i
+
+ if cache.has_id(id) then
+ # FIXME use Warning
+ errors.add new Error("Deserialization Error: object with id {id} is deserialized twice.")
+ # Keep going
+ end
+
+ var type_name = null
+ var i = 1
+
+ # Read dynamic type
+ if object.length >= 2 then
+
+ # Try to get the type name as a string
+ var o = object[i]
+ if o isa String and static_type == "String" and object.length == 2 then
+ cache[id] = o
+ return o
+ else
+ var typ = convert_object(object[i], "String")
+ if typ isa String then
+ type_name = typ
+ i += 1
+ end
+ end
+ end
+
+ if type_name == null then
+ # There was no dynamic type
+
+ # We could use a `class_name_heuristic` here...
+
+ # Fallback to the static type
+ if static_type != null then
+ type_name = static_type.strip_nullable
+ end
+
+ if type_name == null then
+ errors.add new Error("Deserialization Error: could not determine dynamic type of `{object}`.")
+ return null
+ end
+ end
+
+ if not accept(type_name, static_type) then return null
+
+ var attributes = null
+ if object.length > i then attributes = object[i]
+ if not attributes isa Map[nullable Serializable, nullable Serializable] then
+ # Some other type (could be an error), or there's no attributes
+ attributes = new Map[nullable Serializable, nullable Serializable]
+ end
+
+ # advance on path
+ path.push attributes
+ path_arrays.push object
+
+ just_opened_id = id
+ var value = deserialize_class(type_name)
+ just_opened_id = null
+
+ # revert on path
+ path.pop
+ path_arrays.pop
+
+ return value
+ else
+ errors.add new Error("Deserialization Error: unknown MessagePack ext '{first.typ}'.")
+ end
+ end
+
+ # Plain array? Try to convert it to the desired static_type
+ if static_type != null then
+ return deserialize_class(static_type.strip_nullable)
+ end
+ return object
+ end
+
+ if object isa Map[nullable Serializable, nullable Serializable] then
+ # Plain map
+ # TODO parse it as an instance of `static_type`
+
+ if static_type != null then
+ path.push object
+ path_arrays.push null
+
+ just_opened_id = null
+ var value = deserialize_class(static_type.strip_nullable)
+
+ path.pop
+ path_arrays.pop
+
+ return value
+ end
+
+ return object
+ end
+
+ if object isa MsgPackExt then
+
+ # First try the custom extensions
+ var custom = deserialize_ext(object, static_type)
+ if custom == null then
+
+ # No custom, go for deser standard references
+ if object.typ == ext_typ_ref then
+ # Reference to an object
+ var id = object.data.to_i
+ if not cache.has_id(id) then
+ errors.add new Error("Deserialization Error: object reference id unknown.")
+ return object
+ end
+ return cache.object_for(id)
+
+ else if object.typ == ext_typ_char then
+ # Char
+ return object.data.to_s.first
+
+ else if object.typ == ext_typ_byte then
+ # Byte
+ return object.data.first
+ end
+ end
+ end
+
+ if object isa String and object.length == 1 and static_type == "Char" then
+ # Char serialized as a string
+ return object.chars.first
+ end
+
+ if object isa Int and static_type == "Byte" then
+ # Byte serialized as an integer
+ return object.to_b
+ end
+
+ return object
+ end
+
+ redef fun deserialize(static_type)
+ do
+ errors.clear
+
+ var root = stream.read_msgpack
+ return convert_object(root, static_type)
+ end
+
+ # Hook to customize the deserialization of MessagePack extensions
+ #
+ # Redefine this method in subclasses to return custom Nit objects from
+ # an application specific extension.
+ #
+ # This method is invoked before dealing with the extensions used by the
+ # Nit serialization metadata [0x40..0x43]. In general, you should ignore
+ # them by returning `null`, but they can also be intercepted to comply to
+ # a format from a remote server.
+ protected fun deserialize_ext(ext: MsgPackExt, static_type: nullable String): nullable Object
+ do
+ return null
+ end
+end
+
+redef class SimpleCollection[E]
+ redef init from_deserializer(v)
+ do
+ super
+ if v isa MsgPackDeserializer then
+ v.notify_of_creation self
+ init
+
+ var open_array = v.path_arrays.last
+ var msgpack_items = null
+ if open_array != null then msgpack_items = open_array.last
+
+ if not msgpack_items isa Array[nullable Serializable] then
+ v.errors.add new Error("Deserialization Error: no items in source of `{class_name}`")
+ return
+ end
+
+ # Name of the dynamic name of E
+ var items_type_name = (new GetName[E]).to_s
+
+ # Fill array
+ for o in msgpack_items do
+ var obj = v.convert_object(o, items_type_name)
+ if obj isa E then
+ add obj
+ else v.errors.add new AttributeTypeError(self, "items", obj, items_type_name)
+ end
+ end
+ end
+end
+
+redef class Map[K, V]
+ redef init from_deserializer(v)
+ do
+ super
+
+ if v isa MsgPackDeserializer then
+ v.notify_of_creation self
+ init
+
+ var open_object = v.path_arrays.last
+ var msgpack_items
+ if open_object != null then
+ # Metadata available
+ msgpack_items = open_object.last
+ else
+ msgpack_items = v.path.last
+ end
+
+ if not msgpack_items isa Map[nullable Object, nullable Object] then
+ v.errors.add new Error("Deserialization Error: no key/values in source of `{class_name}`")
+ return
+ end
+
+ var keys_type_name = (new GetName[K]).to_s
+ var values_type_name = (new GetName[V]).to_s
+
+ for key_src, value_src in msgpack_items do
+ var key = v.convert_object(key_src, keys_type_name)
+ if not key isa K then
+ v.errors.add new AttributeTypeError(self, "keys", key, keys_type_name)
+ continue
+ end
+
+ var value = v.convert_object(value_src, values_type_name)
+ if not value isa V then
+ v.errors.add new AttributeTypeError(self, "values", value, values_type_name)
+ continue
+ end
+
+ self[key] = value
+ end
+ end
+ 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.
+
+# Serialize full Nit objects to MessagePack format
+#
+# There are 3 main entrypoint services:
+# * `Writer::serialize_msgpack` adds an object to any stream writer.
+# * `Serializable::serialize_msgpack` serializes the object to bytes.
+# * `MsgPackSerializer` gives full control over the serialization of
+# Nit objets to the MessagePack format.
+module serialization_write
+
+import serialization::caching
+private import serialization::engine_tools
+
+import serialization_common
+private import write
+import ext
+
+# MessagePack deserialization engine
+class MsgPackSerializer
+ super CachingSerializer
+ super MsgPackEngine
+
+ # Target writing stream
+ var stream: Writer
+
+ # Write plain MessagePack without metadata for deserialization?
+ #
+ # If `false`, the default, serialize to support deserialization:
+ #
+ # * Each object is encapsulated in an array that contains metadata and
+ # the actual object attributes in a map. The metadata includes the type
+ # name and references to already serialized object. This information
+ # supports deserializing the message, including cycles.
+ # * Preserve the Nit `Char` and `Byte` types as an object.
+ # * The generated MessagePack is standard and can be read by non-Nit programs.
+ # However, it contains some complexity that may make it harder to use.
+ #
+ # If `true`, serialize only the real data or non-Nit programs:
+ #
+ # * Nit objects are serialized to pure and standard MessagePack so they can
+ # be easily read by non-Nit programs.
+ # * Nit objects are serialized at every reference, so they may be duplicated.
+ # It is easier to read but it creates a larger output and it does not support
+ # cycles. Cyclic references are replaced by `null`.
+ # * The serialized data can only be deserialized to their expected static
+ # types, losing the knowledge of their dynamic type.
+ var plain_msgpack = false is writable
+
+ # Should strings declaring the objects type and attributes name be cached?
+ #
+ # If `true` metadata strings are cached using `cache`.
+ # The first occurrence is written as an object declaration,
+ # successive occurrences are written as an object reference.
+ #
+ # If `false`, the default, metadata strings are written as pure MessagePack
+ # strings, without their own metadata.
+ #
+ # Using the cache may save some space by avoiding the repetition of
+ # names used by many types or attributes.
+ # However, it adds complexity to the generated message and may be less
+ # safe for versioning.
+ var cache_metadata_strings = false is writable
+
+ # List of the current open objects, the first is the main target of the serialization
+ #
+ # Used only when `plain_msgpack == true` to detect cycles in serialization.
+ private var open_objects = new Array[Object]
+
+ redef var current_object = null
+
+ redef fun serialize(object)
+ do
+ if object == null then
+ stream.write_msgpack_null
+ else
+ if plain_msgpack then
+ for o in open_objects do
+ if object.is_same_serialized(o) then
+ # Cycle, can't be managed in plain_msgpack mode
+ warn "Cycle detected in serialized object, replacing reference with 'null'."
+ stream.write_msgpack_null
+ return
+ end
+ end
+
+ open_objects.add object
+ end
+
+ var last_object = current_object
+ current_object = object
+ object.accept_msgpack_serializer self
+ current_object = last_object
+
+ if plain_msgpack then open_objects.pop
+ end
+ end
+
+ redef fun serialize_attribute(name, value)
+ do
+ serialize_meta_string name
+ super
+ end
+
+ redef fun serialize_reference(object)
+ do
+ if not plain_msgpack and cache.has_object(object) then
+ # if already serialized, add local reference
+ var id = cache.id_for(object)
+ stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+ else
+ # serialize
+ serialize object
+ end
+ end
+
+ private fun serialize_meta_string(type_name: String)
+ do
+ if plain_msgpack or not cache_metadata_strings then
+ # String only version
+ stream.write_msgpack_str type_name
+ return
+ end
+
+ if cache.has_object(type_name) then
+ # if already serialized, add reference
+ var id = cache.id_for(type_name)
+ stream.write_msgpack_ext(ext_typ_ref, id.to_bytes)
+ else
+ # serialize
+ var id = cache.new_id_for(type_name)
+ stream.write_msgpack_array 2 # obj+id, type_name
+ stream.write_msgpack_ext(ext_typ_obj, id.to_bytes)
+ stream.write_msgpack_str type_name
+ end
+ end
+end
+
+# Serialization visitor to count attribute in `Serializable` objects
+class AttributeCounter
+ super Serializer
+
+ # Number of attributes counted
+ var count = 0
+
+ redef fun serialize_attribute(name, object) do count += 1
+end
+
+# ---
+# Services and serializables
+
+redef class Writer
+ # Serialize `value` in MessagePack format
+ fun serialize_msgpack(value: nullable Serializable, plain: nullable Bool)
+ do
+ var serializer = new MsgPackSerializer(self)
+ serializer.plain_msgpack = plain or else false
+ serializer.serialize value
+ end
+end
+
+redef class Serializable
+
+ # Serialize `self` to MessagePack bytes
+ #
+ # Set `plain = true` to generate standard MessagePack, without deserialization metadata.
+ # Use this option if the generated MessagePack will be read by non-Nit programs.
+ # Use the default, `plain = false`, if the MessagePack bytes are to be deserialized by a Nit program.
+ fun serialize_msgpack(plain: nullable Bool): Bytes
+ do
+ var stream = new BytesWriter
+ stream.serialize_msgpack(self, plain)
+ stream.close
+ return stream.bytes
+ end
+
+ # Hook to customize the serialization of this class to MessagePack
+ #
+ # This method can be refined to customize the serialization by either
+ # writing pure JSON directly on the stream `v.stream` or
+ # by using other services of `MsgPackSerializer`.
+ #
+ # Most of the time, it is better to refine the method `core_serialize_to`
+ # which is used by all the serialization engines, not just MessagePack.
+ protected fun accept_msgpack_serializer(v: MsgPackSerializer)
+ do
+
+ # Count the number of attributes
+ var attribute_counter = new AttributeCounter
+ accept_msgpack_attribute_counter attribute_counter
+ var n_attributes = attribute_counter.count
+
+ if not v.plain_msgpack then
+
+ var n_meta_items = 2
+ if n_attributes > 0 then n_meta_items += 1
+ n_meta_items += msgpack_extra_array_items # obj+id, class_name, attributes
+
+ # Metadata
+ var id = v.cache.new_id_for(self)
+ v.stream.write_msgpack_array n_meta_items
+ v.stream.write_msgpack_ext(v.ext_typ_obj, id.to_bytes)
+ v.serialize_meta_string class_name
+
+ if n_attributes > 0 then v.stream.write_msgpack_map n_attributes
+ else
+ v.stream.write_msgpack_map n_attributes
+ end
+
+ v.serialize_core self
+ end
+
+ # Hook to customize the behavior of the `AttributeCounter`
+ #
+ # By default, this method makes `v` visits all serializable attributes.
+ protected fun accept_msgpack_attribute_counter(v: AttributeCounter)
+ do
+ v.serialize_core self
+ end
+
+ # Hook to request a larger than usual metadata array
+ #
+ # Use by `SimpleCollection` and `Map` to append the items after
+ # the metadata and attributes.
+ protected fun msgpack_extra_array_items: Int do return 0
+end
+
+redef class MsgPackExt
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_ext(typ, data)
+end
+
+redef class Text
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_str self
+end
+
+redef class Int
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_int self
+end
+
+redef class Float
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_double self
+end
+
+redef class Bool
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bool self
+end
+
+redef class Byte
+ redef fun accept_msgpack_serializer(v)
+ do
+ if v.plain_msgpack then
+ # Write as a string
+ v.stream.write_msgpack_int to_i
+ else
+ # Write as ext
+ var bytes = new Bytes.with_capacity(1)
+ bytes.add self
+ v.stream.write_msgpack_ext(v.ext_typ_byte, bytes)
+ end
+ end
+end
+
+redef class Char
+ redef fun accept_msgpack_serializer(v)
+ do
+ if v.plain_msgpack then
+ # Write as a string
+ v.stream.write_msgpack_fixstr to_s
+ else
+ # Write as ext
+ var bytes = to_s.to_bytes
+ v.stream.write_msgpack_ext(v.ext_typ_char, bytes)
+ end
+ end
+end
+
+redef class Bytes
+ redef fun accept_msgpack_serializer(v) do v.stream.write_msgpack_bin self
+end
+
+redef class CString
+ redef fun accept_msgpack_serializer(v) do to_s.accept_msgpack_serializer(v)
+end
+
+redef class SimpleCollection[E]
+ redef fun accept_msgpack_serializer(v)
+ do
+ if not v.plain_msgpack then
+ # Add metadata and other attributes
+ super
+ end
+
+ # Header
+ v.stream.write_msgpack_array length
+
+ # Items
+ for e in self do
+ if not v.try_to_serialize(e) then
+ assert e != null # null would have been serialized
+ v.warn "element of type {e.class_name} is not serializable."
+ v.stream.write_msgpack_null
+ end
+ end
+ end
+
+ redef fun msgpack_extra_array_items do return 1
+end
+
+redef class Map[K, V]
+ redef fun accept_msgpack_serializer(v)
+ do
+ if not v.plain_msgpack then
+ # Add metadata and other attributes
+ super
+ end
+
+ # Header
+ v.stream.write_msgpack_map keys.length
+
+ # Key / values, alternating
+ for key, val in self do
+ if not v.try_to_serialize(key) then
+ assert val != null # null would have been serialized
+ v.warn "element of type {val.class_name} is not serializable."
+ v.stream.write_msgpack_null
+ end
+
+ if not v.try_to_serialize(val) then
+ assert val != null # null would have been serialized
+ v.warn "element of type {val.class_name} is not serializable."
+ v.stream.write_msgpack_null
+ end
+ end
+ end
+
+ redef fun msgpack_extra_array_items do return 1
+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.
+
+# Low-level write in MessagePack format to `Writer` streams
+module write
+
+import binary
+
+redef class Writer
+
+ # Write `null`, or nil, in MessagePack format
+ fun write_msgpack_null do write_byte 0xC0u8
+
+ # Write `bool` in MessagePack format
+ fun write_msgpack_bool(bool: Bool)
+ do write_byte(if bool then 0xC3u8 else 0xC2u8)
+
+ # ---
+ # Integers
+
+ # Write the integer `value` either as the shortest possible MessagePack _int_
+ fun write_msgpack_int(value: Int)
+ do
+ if value >= -0x20 and value <= 0x7F then
+ write_msgpack_fixint value
+ else if value >= 0 then
+ if value <= 0xFF then
+ write_msgpack_uint8 value
+ else if value <= 0xFFFF then
+ write_msgpack_uint16 value
+ else if value <= 0xFFFF_FFFF then
+ write_msgpack_uint32 value
+ else #if value <= 0xFFFF_FFFF_FFFF_FFFF then
+ write_msgpack_uint64 value
+ end
+ else if value >= -128 then
+ write_msgpack_int8 value
+ else if value >= -32768 then
+ write_msgpack_int16 value
+ else if value >= -2147483648 then
+ write_msgpack_int32 value
+ else
+ write_msgpack_int64 value
+ end
+ end
+
+ # Write `value` as a single byte with metadata
+ #
+ # Require: `value >= -0x20 and value <= 0x7F`
+ fun write_msgpack_fixint(value: Int)
+ do
+ assert value >= -0x20 and value <= 0x7F
+ write_byte value.to_b
+ end
+
+ # Write `value` over one unsigned byte, following 1 metadata byte
+ #
+ # Require: `value >= 0x00 and value <= 0xFF`
+ fun write_msgpack_uint8(value: Int)
+ do
+ write_byte 0xCCu8
+ write_bytes value.to_bytes(n_bytes=1)
+ end
+
+ # Write `value` over two unsigned bytes, following 1 metadata byte
+ #
+ # Require: `value >= 0x00 and value <= 0xFFFF`
+ fun write_msgpack_uint16(value: Int)
+ do
+ write_byte 0xCDu8
+ write_bytes value.to_bytes(n_bytes=2)
+ end
+
+ # Write `value` over 4 unsigned bytes, following 1 metadata byte
+ #
+ # Require: `value >= 0x00 and value <= 0xFFFF_FFFF`
+ fun write_msgpack_uint32(value: Int)
+ do
+ write_byte 0xCEu8
+ write_bytes value.to_bytes(n_bytes=4)
+ end
+
+ # Write `value` over 8 unsigned bytes, following 1 metadata byte
+ #
+ # Require: `value >= 0x00 and value <= 0xFFFF_FFFF_FFFF_FFFF`
+ fun write_msgpack_uint64(value: Int)
+ do
+ write_byte 0xCFu8
+ write_bytes value.to_bytes(n_bytes=8)
+ end
+
+ # Write `value` over one signed byte, following 1 metadata byte
+ #
+ # Require: `value >= -128 and value <= 127`
+ fun write_msgpack_int8(value: Int)
+ do
+ write_byte 0xD0u8
+ write_bytes value.to_bytes(n_bytes=1)
+ end
+
+ # Write `value` over two signed bytes, following 1 metadata byte
+ fun write_msgpack_int16(value: Int)
+ do
+ write_byte 0xD1u8
+ write_bytes value.to_bytes(n_bytes=2)
+ end
+
+ # Write `value` over 4 signed bytes, following 1 metadata byte
+ fun write_msgpack_int32(value: Int)
+ do
+ write_byte 0xD2u8
+ write_bytes value.to_bytes(n_bytes=4)
+ end
+
+ # Write `value` over 8 signed bytes, following 1 metadata byte
+ fun write_msgpack_int64(value: Int)
+ do
+ write_byte 0xD3u8
+ write_int64 value
+ end
+
+ # ---
+ # Floats
+
+ # Write `value` as a MessagePack float (losing precision)
+ fun write_msgpack_float(value: Float)
+ do
+ write_byte 0xCAu8
+ write_float value
+ end
+
+ # Write `value` as a MessagePack double
+ fun write_msgpack_double(value: Float)
+ do
+ write_byte 0xCBu8
+ write_double value
+ end
+
+ # ---
+ # Strings
+
+ # Write `text` in the shortest possible MessagePack format
+ #
+ # Require: `text.byte_length <= 0xFFFF_FFFF`
+ fun write_msgpack_str(text: Text)
+ do
+ var len = text.byte_length
+ if len <= 0x1F then
+ write_msgpack_fixstr text
+ else if len <= 0xFF then
+ write_msgpack_str8 text
+ else if len <= 0xFFFF then
+ write_msgpack_str16 text
+ else if len <= 0xFFFF_FFFF then
+ write_msgpack_str32 text
+ else
+ abort
+ end
+ end
+
+ # Write `text` in _fixstr_ format, max of 0x1F bytes
+ #
+ # Require: `text.byte_length <= 0x1F`
+ fun write_msgpack_fixstr(text: Text)
+ do
+ var len = text.byte_length
+ assert len <= 0x1F
+
+ var b = 0b1010_0000u8 | len.to_b
+ write_byte b
+
+ write text
+ end
+
+ # Write `text` in _str8_ format, max of 0xFF bytes
+ #
+ # Require: `text.byte_length <= 0xFF`
+ fun write_msgpack_str8(text: Text)
+ do
+ var len = text.byte_length
+ assert len <= 0xFF
+
+ write_byte 0xD9u8
+ write_byte len.to_b
+ write text
+ end
+
+ # Write `text` in _str16_ format, max of 0xFFFF bytes
+ #
+ # Require: `text.byte_length <= 0xFFFF`
+ fun write_msgpack_str16(text: Text)
+ do
+ var len = text.byte_length
+ assert len <= 0xFFFF
+
+ write_byte 0xDAu8
+ var len_bytes = len.to_bytes
+ write_byte len_bytes[0]
+ write_byte if len_bytes.length > 1 then len_bytes[1] else 0u8
+ write text
+ end
+
+ # Write `text` in _str32_ format, max of 0xFFFF_FFFF bytes
+ #
+ # Require: `text.byte_length <= 0xFFFF_FFFF`
+ fun write_msgpack_str32(text: Text)
+ do
+ var len = text.byte_length
+ assert len <= 0xFFFF_FFFF
+
+ write_byte 0xDBu8
+ var len_bytes = len.to_bytes
+ write_byte len_bytes[0]
+ for i in [1..4[ do
+ write_byte if len_bytes.length > i then len_bytes[i] else 0u8
+ end
+ write text
+ end
+
+ # ---
+ # Binary data
+
+ # Write `data` in the shortest possible MessagePack _bin_ format
+ #
+ # Require: `data.length <= 0xFFFF_FFFF`
+ fun write_msgpack_bin(data: Bytes)
+ do
+ var len = data.length
+ if len <= 0xFF then
+ write_msgpack_bin8 data
+ else if len <= 0xFFFF then
+ write_msgpack_bin16 data
+ else if len <= 0xFFFF_FFFF then
+ write_msgpack_bin32 data
+ else abort
+ end
+
+ # Write `data` in _bin8_ format, max of 0xFF bytes
+ #
+ # Require: `data.length <= 0xFF`
+ fun write_msgpack_bin8(data: Bytes)
+ do
+ var len = data.length
+ assert len <= 0xFF
+
+ write_byte 0xC4u8
+ write_byte len.to_b
+ write_bytes data
+ end
+
+ # Write `data` in _bin16_ format, max of 0xFFFF bytes
+ #
+ # Require: `data.length <= 0xFFFF`
+ fun write_msgpack_bin16(data: Bytes)
+ do
+ var len = data.length
+ assert len <= 0xFFFF
+
+ write_byte 0xC5u8
+ write_bytes len.to_bytes(n_bytes=2)
+ write_bytes data
+ end
+
+ # Write `data` in _bin32_ format, max of 0xFFFF_FFFF bytes
+ #
+ # Require: `data.length <= 0xFFFF_FFFF`
+ fun write_msgpack_bin32(data: Bytes)
+ do
+ var len = data.length
+ assert len <= 0xFFFF_FFFF
+
+ write_byte 0xC6u8
+ write_bytes len.to_bytes(n_bytes=4)
+ write_bytes data
+ end
+
+ # ---
+ # Arrays
+
+ # Write an array header for `len` items in the shortest possible MessagePack _array_ format
+ #
+ # After writing the header, clients should write the array items.
+ #
+ # Require: `len <= 0xFFFF_FFFF`
+ fun write_msgpack_array(len: Int)
+ do
+ if len <= 0x0F then
+ write_msgpack_fixarray len
+ else if len <= 0xFFFF then
+ write_msgpack_array16 len
+ else if len <= 0xFFFF_FFFF then
+ write_msgpack_array32 len
+ else
+ abort
+ end
+ end
+
+ # Write an array header for `len` items, max of 0x0F items
+ #
+ # After writing the header, clients should write the array items.
+ #
+ # Require: `len <= 0x0F`
+ fun write_msgpack_fixarray(len: Int)
+ do
+ assert len <= 0x0F
+ write_byte 0b1001_0000u8 | len.to_b
+ end
+
+ # Write an array header for `len` items, max of 0xFFFF items
+ #
+ # After writing the header, clients should write the array items.
+ #
+ # Require: `len <= 0xFFFF`
+ fun write_msgpack_array16(len: Int)
+ do
+ assert len <= 0xFFFF
+ write_byte 0xDCu8
+ write_bytes len.to_bytes(n_bytes=2)
+ end
+
+ # Write an array header for `len` items, max of 0xFFFF_FFFF items
+ #
+ # After writing the header, clients should write the array items.
+ #
+ # Require: `len <= 0xFFFF_FFFF`
+ fun write_msgpack_array32(len: Int)
+ do
+ assert len <= 0xFFFF_FFFF
+ write_byte 0xDDu8
+ write_bytes len.to_bytes(n_bytes=4)
+ end
+
+ # ---
+ # Map
+
+ # Write a map header for `len` keys/value pairs in the shortest possible MessagePack _map_ format
+ #
+ # After writing the header, clients should write the map data, alternating
+ # between keys and values.
+ #
+ # Require: `len <= 0xFFFF_FFFF`
+ fun write_msgpack_map(len: Int)
+ do
+ if len <= 0x0F then
+ write_msgpack_fixmap len
+ else if len <= 0xFFFF then
+ write_msgpack_map16 len
+ else if len <= 0xFFFF_FFFF then
+ write_msgpack_map32 len
+ else
+ abort
+ end
+ end
+
+ # Write a map header for `len` key/value pairs, max of 0x0F pairs
+ #
+ # After writing the header, clients should write the map data, alternating
+ # between keys and values.
+ #
+ # Require: `len <= 0x0F`
+ fun write_msgpack_fixmap(len: Int)
+ do
+ assert len <= 0x0F
+ write_byte 0b1000_0000u8 | len.to_b
+ end
+
+ # Write a map header for `len` key/value pairs, max of 0xFFFF pairs
+ #
+ # After writing the header, clients should write the map data, alternating
+ # between keys and values.
+ #
+ # Require: `len <= 0xFFFF`
+ fun write_msgpack_map16(len: Int)
+ do
+ assert len <= 0xFFFF
+ write_byte 0xDEu8
+ write_bytes len.to_bytes(n_bytes=2)
+ end
+
+ # Write a map header for `len` key/value pairs, max of 0xFFFF_FFFF pairs
+ #
+ # After writing the header, clients should write the map data, alternating
+ # between keys and values.
+ #
+ # Require: `len <= 0xFFFF_FFFF`
+ fun write_msgpack_map32(len: Int)
+ do
+ assert len <= 0xFFFF_FFFF
+ write_byte 0xDFu8
+ write_bytes len.to_bytes(n_bytes=4)
+ end
+
+ # ---
+ # Ext
+
+ # Write an application-specific extension for `typ` and `bytes` in the shortest possible MessagePack _ext_ format
+ #
+ # Require: `bytes.length <= 0xFFFF_FFFF`
+ #
+ # ~~~
+ # var writer = new BytesWriter
+ # writer.write_msgpack_ext(0x0Au8, b"\x0B\x0C\x0D")
+ # assert writer.bytes == b"\xC7\x03\x0A\x0B\x0C\x0D"
+ # ~~~
+ fun write_msgpack_ext(typ: Byte, bytes: Bytes)
+ do
+ var len = bytes.length
+ if len == 1 then
+ write_msgpack_fixext1 typ
+ write_byte bytes.first
+ else if len == 2 then
+ write_msgpack_fixext2 typ
+ write_bytes bytes
+ else if len == 4 then
+ write_msgpack_fixext4 typ
+ write_bytes bytes
+ else if len == 8 then
+ write_msgpack_fixext8 typ
+ write_bytes bytes
+ else if len == 16 then
+ write_msgpack_fixext16 typ
+ write_bytes bytes
+ else if len <= 0xFF then
+ write_msgpack_ext8(typ, len)
+ write_bytes bytes
+ else if len <= 0xFFFF then
+ write_msgpack_ext16(typ, len)
+ write_bytes bytes
+ else if len <= 0xFFFF_FFFF then
+ write_msgpack_ext32(typ, len)
+ write_bytes bytes
+ else
+ abort
+ end
+ end
+
+ # Write the header for an application-specific extension of one data byte
+ #
+ # After writing the header, clients should write the data byte.
+ fun write_msgpack_fixext1(typ: Byte)
+ do
+ write_byte 0xD4u8
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of two data bytes
+ #
+ # After writing the header, clients should write the two data bytes.
+ fun write_msgpack_fixext2(typ: Byte)
+ do
+ write_byte 0xD5u8
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of 4 data bytes
+ #
+ # After writing the header, clients should write the 4 data bytes.
+ fun write_msgpack_fixext4(typ: Byte)
+ do
+ write_byte 0xD6u8
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of 8 data bytes
+ #
+ # After writing the header, clients should write the 8 data bytes.
+ fun write_msgpack_fixext8(typ: Byte)
+ do
+ write_byte 0xD7u8
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of 16 data bytes
+ #
+ # After writing the header, clients should write the 16 data bytes.
+ fun write_msgpack_fixext16(typ: Byte)
+ do
+ write_byte 0xD8u8
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of `len` data bytes
+ #
+ # After writing the header, clients should write the data bytes.
+ #
+ # Require: `len >= 0 and <= 0xFF`
+ fun write_msgpack_ext8(typ: Byte, len: Int)
+ do
+ assert len >= 0 and len <= 0xFF
+ write_byte 0xC7u8
+ write_byte len.to_b
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of `len` data bytes
+ #
+ # After writing the header, clients should write the data bytes.
+ #
+ # Require: `len >= 0 and <= 0xFFFF`
+ fun write_msgpack_ext16(typ: Byte, len: Int)
+ do
+ assert len >= 0 and len <= 0xFFFF
+ write_byte 0xC8u8
+ write_bytes len.to_bytes(n_bytes=2)
+ write_byte typ
+ end
+
+ # Write the header for an application-specific extension of `len` data bytes
+ #
+ # After writing the header, clients should write the data bytes.
+ #
+ # Require: `len >= 0 and <= 0xFFFF_FFFF`
+ fun write_msgpack_ext32(typ: Byte, len: Int)
+ do
+ assert len >= 0 and len <= 0xFFFF_FFFF
+ write_byte 0xC9u8
+ write_bytes len.to_bytes(n_bytes=4)
+ write_byte typ
+ end
+
+ # TODO timestamps
+end
# Pre order sets and partial order set (ie hierarchies)
module poset
-import serialization
+import serialization::serialization_core
# Pre-order set graph.
# This class models an incremental pre-order graph where new nodes and edges can be added (but not removed).
# Play `chunk` on `channel`
#
# If `channel == -1` the first unreserved channel is used.
- # The sound is repeated `loops` times, `loops == 0` plays it once and
- # `loops == -1` loops infinitely.
- fun play_channel(channel: Int, chunk: MixChunk, loops: Int): Bool `{
- return Mix_PlayChannel(channel, chunk, loops) == 0;
+ # The sound is repeated `loops` times, `loops == 0` plays it once,
+ # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+ #
+ # Returns the channel used, or `-1` on error.
+ fun play_channel(channel: Int, chunk: MixChunk, loops: Int): Int `{
+ return Mix_PlayChannel(channel, chunk, loops);
`}
- # Set the chunk volume out of `mix.max_volume` and return the previous value
+ # Play `chunk` on `channel`
#
- # Use `volume = -1` to only read the previous value.
+ # If `channel == -1` the first unreserved channel is used.
+ # The sound is repeated `loops` times, `loops == 0` plays it once,
+ # `loops == 1` plays it twice and `loops == -1` loops infinitely.
+ # If `ticks != -1`, the sample plays for at most `ticks` milliseconds.
+ fun play_channel_timed(channel: Int, chunk: MixChunk, loops, ticks: Int): Int `{
+ return Mix_PlayChannelTimed(channel, chunk, loops, ticks);
+ `}
+
+ # Halt/stop `channel` playback
+ #
+ # If `channel == -1`, halt all channels.
+ fun halt_channel(channel: Int) `{
+ Mix_HaltChannel(channel);
+ `}
+
+ # Halt `channel` in `ticks` milliseconds and return the number of channels set to expire
+ #
+ # If `channel == -1`, halt all channels.
+ fun expire_channel(channel, ticks: Int): Int `{
+ return Mix_ExpireChannel(channel, ticks);
+ `}
+
+ # Reserve `num` channels from being used by `play_channel(-1...)`
+ #
+ # Returns the number of of channels reserved.
+ fun reserve_channels(num: Int): Int `{
+ return Mix_ReserveChannels(num);
+ `}
+
+ # Set the `volume` of `channel`, out of `mix.max_volume`
+ #
+ # If `channel == -1`, set the volume of all channels.
+ #
+ # Returns the current volume of the channel, or if `channel == -1` the average volume.
+ fun volume(channel, volume: Int): Int `{
+ return Mix_Volume(channel, volume);
+ `}
+
+ # Set the `volume` for `chunk`, out of `mix.max_volume`
+ #
+ # If `volume == -1`, only read the previous value.
+ #
+ # Returns the previous volume value.
fun volume_chunk(chunk: MixChunk, volume: Int) `{
Mix_VolumeChunk(chunk, volume);
`}
+ # Pause `channel`, or all playing channels if -1
+ fun pause(channel: Int) `{
+ Mix_Pause(channel);
+ `}
+
+ # Unpause `channel`, or all paused channels if -1
+ fun resume(channel: Int) `{
+ Mix_Resume(channel);
+ `}
+
# ---
# Music
# Services for caching serialization engines
module caching
-import serialization
+import serialization_core
private import engine_tools
# A `Serializer` with a `cache`
# Advanced services for serialization engines
module engine_tools
-import serialization
+import serialization_core
intrude import core::collection::hash_collection
# Maps instances to a value, uses `is_same_serialized` and `serialization_hash`.
var c = _array[i]
while c != null do
var ck = c._key
+ assert ck != null
if ck.is_same_serialized(k) then
break
end
return c
end
end
+
+redef interface Object
+ # Is `self` the same as `other` in a serialization context?
+ #
+ # Used to determine if an object has already been serialized.
+ fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
+
+ # Hash value use for serialization
+ #
+ # Used in combination with `is_same_serialized`. If two objects are the same
+ # in a serialization context, they must have the same `serialization_hash`.
+ fun serialization_hash: Int do return object_id
+end
+
+redef class String
+ redef fun serialization_hash do return hash
+ redef fun is_same_serialized(o) do return self == o
+end
+
+redef class Text
+
+ # Strip the `nullable` prefix from the type name `self`
+ #
+ # ~~~
+ # assert "String".strip_nullable == "String"
+ # assert "nullable Array[Int]".strip_nullable == "Array[Int]"
+ # assert "Map[Set[String], Set[Int]]".strip_nullable == "Map[Set[String], Set[Int]]"
+ # ~~~
+ fun strip_nullable: Text
+ do
+ var prefix = "nullable "
+ return if has_prefix(prefix) then substring_from(prefix.length) else self
+ end
+
+ # Strip the `nullable` prefix and the params from the type name `self`
+ #
+ # ~~~
+ # assert "String".strip_nullable_and_params == "String"
+ # assert "nullable Array[Int]".strip_nullable_and_params == "Array"
+ # assert "Map[Set[String], Set[Int]]".strip_nullable_and_params == "Map"
+ # ~~~
+ fun strip_nullable_and_params: Text
+ do
+ var class_name = strip_nullable
+
+ var bracket_index = class_name.index_of('[')
+ if bracket_index == -1 then return class_name
+ return class_name.substring(0, bracket_index)
+ 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.
+
+# Refine `Serializable::inspect` to show more useful information
+module inspect
+
+import serialization_core
+private import caching
+
+private fun inspect_testing: Bool do return "NIT_TESTING".environ == "true"
+
+# Serialization engine writing the object attributes to strings
+private class InspectSerializer
+ super CachingSerializer
+
+ # Target writing stream
+ var stream: Writer
+
+ redef var current_object = null
+
+ var first_object: nullable Object = null
+
+ redef fun serialize(object)
+ do
+ if object == null then
+ stream.write "null"
+ else
+ if current_object == null then
+ first_object = object
+ end
+
+ var last_object = current_object
+ current_object = object
+ object.accept_inspect_serializer self
+ current_object = last_object
+ end
+ end
+
+ var first_attribute_serialized = false
+
+ redef fun serialize_attribute(name, value)
+ do
+ if first_attribute_serialized then
+ stream.write ", "
+ else
+ stream.write " "
+ first_attribute_serialized = true
+ end
+
+ stream.write name
+ stream.write ":"
+
+ super
+ end
+
+ redef fun serialize_reference(object)
+ do
+ if cache.has_object(object) then
+ # Cycle
+ var id = object.object_id
+ if inspect_testing then id = cache.id_for(object)
+
+ stream.write "<"
+ stream.write object.class_name
+ stream.write "#"
+ stream.write id.to_s
+ stream.write ">"
+ else if object != first_object and (not object isa DirectSerializable) then
+ # Another object, print class and id only
+ var id = object.object_id
+ if inspect_testing then id = cache.new_id_for(object)
+
+ stream.write "<"
+ stream.write object.class_name
+ stream.write "#"
+ stream.write id.to_s
+ stream.write ">"
+ else
+ # Main object
+ serialize object
+ end
+ end
+end
+
+redef class Serializable
+
+ # Improve the default inspection reading serializable attributes
+ #
+ # Simple immutable data are inspected as they would be written in Nit code.
+ #
+ # ~~~
+ # assert 123.inspect == "123"
+ # assert 1.5.inspect == "1.5"
+ # assert 0xa1u8.inspect == "0xa1u8"
+ # assert 'c'.inspect == "'c'"
+ # assert "asdf\n".inspect == "\"asdf\\n\""
+ # ~~~
+ #
+ # Inspections of mutable serializable objects show their dynamic type,
+ # their `object_id` and their first level attributes. When testing,
+ # the `object_id` is replaced by an id unique to each call to `inspect`.
+ #
+ # ~~~
+ # class MyClass
+ # serialize
+ #
+ # var i: Int
+ # var o: nullable Object
+ # end
+ #
+ # var class_with_null = new MyClass(123)
+ # assert class_with_null.to_s == class_with_null.inspect
+ # assert class_with_null.to_s == "<MyClass#0 i:123, o:null>"
+ #
+ # var class_with_other = new MyClass(456, class_with_null)
+ # assert class_with_other.to_s == "<MyClass#0 i:456, o:<MyClass#1>>"
+ #
+ # var class_with_cycle = new MyClass(789)
+ # class_with_cycle.o = class_with_cycle
+ # assert class_with_cycle.to_s == "<MyClass#0 i:789, o:<MyClass#0>>"
+ # ~~~
+ #
+ # Items of collections are flattened and appended to the output.
+ #
+ # ~~~
+ # assert [1, 2, 3].inspect == "<Array[Int]#0 [1, 2, 3]>"
+ #
+ # var set = new HashSet[Object].from([1, 1.5, "two": Object])
+ # assert set.inspect == """<HashSet[Object]#0 [1, 1.5, "two"]>"""
+ #
+ # var map = new Map[Int, String]
+ # map[1] = "one"
+ # map[2] = "two"
+ # assert map.inspect == """<HashMap[Int, String]#0 {1:"one", 2:"two"}>"""
+ # ~~~
+ #
+ # Inspections producing over 80 characters are cut short.
+ #
+ # ~~~
+ # var long_class = new MyClass(123456789, "Some " + "very "*8 + "long string")
+ # assert long_class.to_s == "<MyClass#0 i:123456789, o:\"Some very very very very very very very very long s…>"
+ # ~~~
+ redef fun inspect
+ do
+ var stream = new StringWriter
+ var serializer = new InspectSerializer(stream)
+ serializer.serialize self
+ stream.close
+ var str = stream.to_s
+
+ # Cut long inspects
+ var max_length = 80
+ if str.length > max_length then
+ str = str.substring(0, max_length-2) + "…>"
+ end
+
+ return str
+ end
+
+ private fun accept_inspect_serializer(v: InspectSerializer)
+ do
+ v.stream.write "<"
+
+ v.stream.write class_name
+ v.stream.write "#"
+
+ var id = object_id
+ if inspect_testing then id = v.cache.new_id_for(self)
+ v.stream.write id.to_s
+
+ accept_inspect_serializer_core v
+
+ v.stream.write ">"
+ end
+
+ private fun accept_inspect_serializer_core(v: InspectSerializer)
+ do v.serialize_core(self)
+end
+
+redef class Int
+ redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Float
+ redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Bool
+ redef fun accept_inspect_serializer(v) do v.stream.write to_s
+end
+
+redef class Char
+ redef fun accept_inspect_serializer(v)
+ do
+ v.stream.write "'"
+ v.stream.write to_s.escape_to_nit
+ v.stream.write "'"
+ end
+end
+
+redef class Byte
+ redef fun accept_inspect_serializer(v)
+ do
+ v.stream.write to_s
+ v.stream.write "u8"
+ end
+end
+
+redef class CString
+ redef fun accept_inspect_serializer_core(v)
+ do
+ v.stream.write " \""
+ v.stream.write to_s.escape_to_nit
+ v.stream.write_char '"'
+ end
+end
+
+redef class Text
+
+ redef fun accept_inspect_serializer(v)
+ do
+ v.stream.write "\""
+ v.stream.write escape_to_nit
+ v.stream.write "\""
+ end
+end
+
+redef class Collection[E]
+ private fun serialize_as_inspect(v: InspectSerializer)
+ do
+ v.stream.write "["
+ var is_first = true
+ for e in self do
+ if is_first then
+ is_first = false
+ else
+ v.stream.write ", "
+ end
+
+ if not v.try_to_serialize(e) then
+ assert e != null
+ v.stream.write e.inspect
+ end
+ end
+ v.stream.write "]"
+ end
+end
+
+redef class SimpleCollection[E]
+ redef fun accept_inspect_serializer_core(v)
+ do
+ v.stream.write " "
+ serialize_as_inspect v
+ end
+end
+
+redef class Map[K, V]
+ redef fun accept_inspect_serializer_core(v)
+ do
+ v.stream.write " \{"
+
+ var first = true
+ for key, val in self do
+ if not first then
+ v.stream.write ", "
+ else first = false
+
+ if not v.try_to_serialize(key) then
+ assert key != null
+ v.stream.write key.inspect
+ end
+
+ v.stream.write ":"
+
+ if not v.try_to_serialize(val) then
+ assert val != null
+ v.stream.write val.inspect
+ end
+ end
+
+ v.stream.write "\}"
+ 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.
+
+# Services for safer deserialization engines
+module safe
+
+import poset
+
+import serialization
+private import engine_tools
+
+# Deserialization engine limiting which types can be deserialized
+class SafeDeserializer
+ super Deserializer
+
+ # Accepted parameterized classes to deserialize
+ #
+ # If `whitelist.empty`, all types are accepted.
+ #
+ # ~~~
+ # import json
+ #
+ # class MyClass
+ # serialize
+ # end
+ #
+ # var json_string = """
+ # {"__class": "MyClass"}
+ # """
+ #
+ # var deserializer = new JsonDeserializer(json_string)
+ # var obj = deserializer.deserialize
+ # assert deserializer.errors.is_empty
+ # assert obj isa MyClass
+ #
+ # deserializer = new JsonDeserializer(json_string)
+ # deserializer.whitelist.add "Array[String]"
+ # deserializer.whitelist.add "AnotherAcceptedClass"
+ # obj = deserializer.deserialize
+ # assert deserializer.errors.length == 1
+ # assert obj == null
+ # ~~~
+ var whitelist = new Array[Text]
+
+ # Should objects be checked if they a subtype of the static type before deserialization?
+ #
+ # Defaults to `true`, as it should always be activated.
+ # It can be turned off to implement the subtype check itself.
+ var check_subtypes = true is writable
+
+ # Should `self` accept to deserialize an instance of `dynamic_type` for an attribute wuth `static_type`?
+ #
+ # Uses `whitelist` if not empty...
+ # Check correct inheritance if `check_subtypes`...
+ fun accept(dynamic_type: Text, static_type: nullable Text): Bool
+ do
+ if whitelist.not_empty and not whitelist.has(dynamic_type) then
+ errors.add new Error("Deserialization Error: '{dynamic_type}' not in whitelist")
+ return false
+ end
+
+ if static_type != null and check_subtypes then
+ var static_class = static_type.strip_nullable_and_params.to_s
+ var dynamic_class = dynamic_type.strip_nullable_and_params.to_s
+ if not class_inheritance_metamodel.has_edge(dynamic_class, static_class) then
+ errors.add new Error("Deserialization Error: `{dynamic_type}` is not a subtype of the static type `{static_type}`")
+ return false
+ end
+ end
+
+ return true
+ end
+end
+
+redef class Sys
+ # Class inheritance graph, implemented by the `json` package
+ #
+ # ~~~
+ # import json
+ #
+ # var hierarchy = class_inheritance_metamodel
+ # assert hierarchy.has_edge("String", "Object")
+ # assert not hierarchy.has_edge("Object", "String")
+ # ~~~
+ fun class_inheritance_metamodel: POSet[String] is abstract
+end
+
+redef class Deserializer
+ redef fun deserialize_class(name)
+ do
+ if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
+ if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
+ if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
+ if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
+
+ return super
+ end
+end
# See the License for the specific language governing permissions and
# limitations under the License.
-# Abstract services to serialize Nit objects to different formats
-#
-# This module declares the `serialize` annotation to mark Nit classes as serializable.
-# For an introduction to this service, refer to the documentation of the `serialization` group.
-# This documentation provides more technical information on interesting entitie of this module.
-#
-# Interesting entities for end users of serializable classes:
-#
-# * Serialize an instance subclass of `Serializable` with either
-# `Serializer::serializable` and `Serializable::serialize`.
-# * Deserialize an object using `Deserializer::deserialize`.
-# The object type must the be checked with an `assert` or otherwise.
-#
-# Interesting entities to create custom serializable classes:
-#
-# * Subclass `Serializable` to declare a class as serializable and to customize
-# the serialization and deserialization behavior.
-# * Redefine `Serializable::core_serialize_to` to customize the serialization
-# of the receiver class.
-# * Redefine `Deserializer::deserialize_class` to customize the deserialization
-# of a specific class by name.
-#
-# Interesting entities for serialization format:
-#
-# * Subclass `Serializer` and `Deserializer` with custom serices.
-# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
-# * In `Deserializer`; `deserialize`, `deserialize_attribute and
-# `notify_of_creation` must be redefined.
-module serialization is
- new_annotation auto_serializable
- new_annotation serialize
- new_annotation noserialize
- new_annotation serialize_as
-end
-
-intrude import core::queue
-import meta
-
-# Abstract serialization service to be sub-classed by specialized services.
-interface Serializer
- # Entry point method of this service, serialize the `object`
- #
- # This method, and refinements, should handle `null` and probably
- # use double dispatch to customize the bahavior per serializable objects.
- fun serialize(object: nullable Serializable) is abstract
-
- # The object currently serialized by `serialized`
- #
- # Can be used by a custom serializer to add domain-specific serialization behavior.
- protected fun current_object: nullable Object is abstract
-
- # Serialize an object, with full serialization or a simple reference
- protected fun serialize_reference(object: Serializable) is abstract
-
- # Serialize an attribute to compose a serializable object
- #
- # This method should be called from `Serializable::core_serialize_to`.
- fun serialize_attribute(name: String, value: nullable Object)
- do
- if not try_to_serialize(value) then
- warn("argument {name} of type {value.class_name} is not serializable.")
- end
- end
-
- # Serialize `value` is possie, i.e. it is `Serializable` or `null`
- fun try_to_serialize(value: nullable Object): Bool
- do
- if value isa Serializable then
- value.serialize_to_or_delay(self)
- else if value == null then
- serialize value
- else return false
- return true
- end
-
- # The method is called when a standard `value` is serialized
- #
- # The default behavior is to call `value.core_serialize_to(self)` but it
- # can be redefined by a custom serializer to add domain-specific serialization behavior.
- fun serialize_core(value: Serializable)
- do
- value.core_serialize_to(self)
- end
-
- # Warn of problems and potential errors (such as if an attribute
- # is not serializable)
- fun warn(msg: String) do print "Serialization warning: {msg}"
-end
-
-# Abstract deserialization service
-#
-# The main service is `deserialize`.
-abstract class Deserializer
- # Deserialize and return an object, storing errors in the attribute `errors`
- #
- # If a `static_type` is given, only subtypes of the `static_type` are accepted.
- #
- # This method behavior varies according to the implementation engines.
- fun deserialize(static_type: nullable String): nullable Object is abstract
-
- # Deserialize the attribute with `name` from the object open for deserialization
- #
- # The `static_type` restricts what kind of object can be deserialized.
- #
- # Return the deserialized value or null on error, and set
- # `deserialize_attribute_missing` to whether the attribute was missing.
- #
- # Internal method to be implemented by the engines.
- fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
-
- # Was the attribute queried by the last call to `deserialize_attribute` missing?
- var deserialize_attribute_missing = false
-
- # Register a newly allocated object (even if not completely built)
- #
- # Internal method called by objects in creation, to be implemented by the engines.
- fun notify_of_creation(new_object: Object) is abstract
-
- # Deserialize the next available object as an instance of `class_name`
- #
- # Return the deserialized object on success and
- # record in `errors` if `class_name` is unknown.
- #
- # This method should be redefined for each custom subclass of `Serializable`.
- # All refinement should look for a precise `class_name` and call super
- # on unsupported classes.
- protected fun deserialize_class(class_name: String): nullable Object do
- if class_name == "Error" then return new Error.from_deserializer(self)
- return deserialize_class_intern(class_name)
- end
-
- # Generated service to deserialize the next available object as an instance of `class_name`
- #
- # Refinements to this method will be generated by the serialization phase.
- # To avoid conflicts, there should not be any other refinements to this method.
- # You can instead use `deserialize_class`.
- protected fun deserialize_class_intern(class_name: String): nullable Object do
- errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
- return null
- end
-
- # Should `self` keep trying to deserialize an object after an error?
- #
- # This behavior takes effect after each attribute deserialization with
- # errors such as a missing attribute or the value is of the wrong type.
- # If `keep_going`, the attribute will be skipped but the engine will
- # deserialize the next attribute.
- # If `not keep_going`, the engine stops deserializing right away.
- #
- # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
- #
- # Default at `true`.
- var keep_going: nullable Bool = null is writable
-
- # Errors encountered in the last call to `deserialize`
- var errors = new Array[Error]
-end
-
-# Deserialization error related to an attribute of `receiver`
-abstract class AttributeError
- super Error
-
- # Parent object of the problematic attribute
- var receiver: Object
-
- # Name of the problematic attribute in `receiver`
- var attribute_name: String
-end
-
-# Invalid dynamic type for a deserialized attribute
-class AttributeTypeError
- super AttributeError
-
- autoinit receiver, attribute_name, attribute, expected_type
-
- # Deserialized object that isn't of the `expected_type`
- var attribute: nullable Object
-
- # Name of the type expected for `attribute`
- var expected_type: String
-
- redef var message is lazy do
- var attribute = attribute
- var found_type = if attribute != null then attribute.class_name else "null"
-
- return "Deserialization Error: {
- }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
- end
-end
-
-# Missing attribute at deserialization
-class AttributeMissingError
- super AttributeError
-
- autoinit receiver, attribute_name
-
- redef var message is lazy do
- return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
- end
-end
-
-# Instances of this class can be passed to `Serializer::serialize`
-interface Serializable
- # Serialize `self` to `serializer`
- #
- # This is a shortcut to `Serializer::serialize`.
- fun serialize_to(serializer: Serializer) do serializer.serialize(self)
-
- # Actual serialization of `self` to `serializer`
- #
- # This writes the full data of `self` to `serializer`.
- #
- # This method can be redefined in sub classes and refinements.
- # It should use `Serializer::serialize_attribute` to to register real or
- # logical attributes.
- #
- # Any refinement should have its equivalent refinement of
- # `Deserializer::deserialize_class` to support this custom deserialization.
- fun core_serialize_to(serializer: Serializer) do end
-
- # Accept references or force direct serialization (using `serialize_to`)
- #
- # The subclass change the default behavior, which will accept references,
- # to force to always serialize copies of `self`.
- private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
-
- # Create an instance of this class from the `deserializer`
- #
- # This constructor is refined by subclasses to correctly build their instances.
- init from_deserializer(deserializer: Deserializer) is nosuper do end
-end
-
-redef interface Object
- # Is `self` the same as `other` in a serialization context?
- #
- # Used to determine if an object has already been serialized.
- fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
-
- # Hash value use for serialization
- #
- # Used in combination with `is_same_serialized`. If two objects are the same
- # in a serialization context, they must have the same `serialization_hash`.
- fun serialization_hash: Int do return object_id
-end
-
-# Instances of this class are not delayed and instead serialized immediately
-# This applies mainly to `universal` types
-interface DirectSerializable
- super Serializable
-
- redef fun serialize_to_or_delay(v) do serialize_to(v)
-end
-
-redef class Bool super DirectSerializable end
-redef class Char super DirectSerializable end
-redef class Int super DirectSerializable end
-redef class Float super DirectSerializable end
-redef class CString super DirectSerializable end
-redef class Text 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 Ref[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
-
-redef class Error
- super Serializable
-
- redef init from_deserializer(v)
- do
- v.notify_of_creation self
-
- var message = v.deserialize_attribute("message")
- if not message isa String then message = ""
- init message
-
- var cause = v.deserialize_attribute("cause")
- if cause isa nullable Error then self.cause = cause
- end
-
- redef fun core_serialize_to(v)
- do
- v.serialize_attribute("message", message)
- v.serialize_attribute("cause", cause)
- end
-end
-
-# ---
-# core::queue classes
-
-redef abstract class ProxyQueue[E]
-
- redef init from_deserializer(v)
- do
- v.notify_of_creation self
-
- var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
- if not seq isa Sequence[E] then seq = new Array[E]
- if v.deserialize_attribute_missing then
- v.errors.add new AttributeMissingError(self, "seq")
- end
-
- init seq
- end
-
- redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class RandQueue[E]
-
- redef init from_deserializer(v)
- do
- v.notify_of_creation self
-
- var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
- if not seq isa SimpleCollection[E] then seq = new Array[E]
- if v.deserialize_attribute_missing then
- v.errors.add new AttributeMissingError(self, "seq")
- end
-
- init seq
- end
-
- redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
-end
-
-redef class MinHeap[E]
-
- redef init from_deserializer(v)
- do
- v.notify_of_creation self
-
- var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
- if not items isa Array[E] then items = new Array[E]
- if v.deserialize_attribute_missing then
- v.errors.add new AttributeMissingError(self, "items")
- end
-
- var comparator = v.deserialize_attribute("comparator", "Comparator")
- if not comparator isa Comparator then comparator = default_comparator
- if v.deserialize_attribute_missing then
- v.errors.add new AttributeMissingError(self, "comparator")
- end
-
- init comparator
- self.items.add_all items
- end
+# General serialization services
+module serialization
- redef fun core_serialize_to(v)
- do
- v.serialize_attribute("items", items)
- v.serialize_attribute("comparator", comparator)
- end
-end
+import serialization_core
+import inspect
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Abstract services to serialize Nit objects to different formats
+#
+# This module declares the `serialize` annotation to mark Nit classes as serializable.
+# For an introduction to this service, refer to the documentation of the `serialization` group.
+# This documentation provides more technical information on interesting entitie of this module.
+#
+# Interesting entities for end users of serializable classes:
+#
+# * Serialize an instance subclass of `Serializable` with either
+# `Serializer::serializable` and `Serializable::serialize`.
+# * Deserialize an object using `Deserializer::deserialize`.
+# The object type must the be checked with an `assert` or otherwise.
+#
+# Interesting entities to create custom serializable classes:
+#
+# * Subclass `Serializable` to declare a class as serializable and to customize
+# the serialization and deserialization behavior.
+# * Redefine `Serializable::core_serialize_to` to customize the serialization
+# of the receiver class.
+# * Redefine `Deserializer::deserialize_class` to customize the deserialization
+# of a specific class by name.
+#
+# Interesting entities for serialization format:
+#
+# * Subclass `Serializer` and `Deserializer` with custom serices.
+# * In `Serializer`, `serialize` and `serialize_reference` must be redefined.
+# * In `Deserializer`; `deserialize`, `deserialize_attribute and
+# `notify_of_creation` must be redefined.
+module serialization_core is
+ new_annotation auto_serializable
+ new_annotation serialize
+ new_annotation noserialize
+ new_annotation serialize_as
+end
+
+intrude import core::queue
+import meta
+
+# Abstract serialization service to be sub-classed by specialized services.
+interface Serializer
+ # Entry point method of this service, serialize the `object`
+ #
+ # This method, and refinements, should handle `null` and probably
+ # use double dispatch to customize the bahavior per serializable objects.
+ fun serialize(object: nullable Serializable) is abstract
+
+ # The object currently serialized by `serialized`
+ #
+ # Can be used by a custom serializer to add domain-specific serialization behavior.
+ protected fun current_object: nullable Object is abstract
+
+ # Serialize an object, with full serialization or a simple reference
+ protected fun serialize_reference(object: Serializable) is abstract
+
+ # Serialize an attribute to compose a serializable object
+ #
+ # This method should be called from `Serializable::core_serialize_to`.
+ fun serialize_attribute(name: String, value: nullable Object)
+ do
+ if not try_to_serialize(value) then
+ assert value != null # null would have been serialized
+ warn("argument {name} of type {value.class_name} is not serializable.")
+ end
+ end
+
+ # Serialize `value` is possible, i.e. it is `Serializable` or `null`
+ fun try_to_serialize(value: nullable Object): Bool
+ do
+ if value isa Serializable then
+ value.serialize_to_or_delay(self)
+ else if value == null then
+ serialize value
+ else return false
+ return true
+ end
+
+ # The method is called when a standard `value` is serialized
+ #
+ # The default behavior is to call `value.core_serialize_to(self)` but it
+ # can be redefined by a custom serializer to add domain-specific serialization behavior.
+ fun serialize_core(value: Serializable)
+ do
+ value.core_serialize_to(self)
+ end
+
+ # Warn of problems and potential errors (such as if an attribute
+ # is not serializable)
+ fun warn(msg: String) do print "Serialization warning: {msg}"
+end
+
+# Abstract deserialization service
+#
+# The main service is `deserialize`.
+abstract class Deserializer
+ # Deserialize and return an object, storing errors in the attribute `errors`
+ #
+ # If a `static_type` is given, only subtypes of the `static_type` are accepted.
+ #
+ # This method behavior varies according to the implementation engines.
+ fun deserialize(static_type: nullable String): nullable Object is abstract
+
+ # Deserialize the attribute with `name` from the object open for deserialization
+ #
+ # The `static_type` restricts what kind of object can be deserialized.
+ #
+ # Return the deserialized value or null on error, and set
+ # `deserialize_attribute_missing` to whether the attribute was missing.
+ #
+ # Internal method to be implemented by the engines.
+ fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
+
+ # Was the attribute queried by the last call to `deserialize_attribute` missing?
+ var deserialize_attribute_missing = false
+
+ # Register a newly allocated object (even if not completely built)
+ #
+ # Internal method called by objects in creation, to be implemented by the engines.
+ fun notify_of_creation(new_object: Object) is abstract
+
+ # Deserialize the next available object as an instance of `class_name`
+ #
+ # Return the deserialized object on success and
+ # record in `errors` if `class_name` is unknown.
+ #
+ # This method should be redefined for each custom subclass of `Serializable`.
+ # All refinement should look for a precise `class_name` and call super
+ # on unsupported classes.
+ protected fun deserialize_class(class_name: Text): nullable Object do
+ if class_name == "Error" then return new Error.from_deserializer(self)
+ return deserialize_class_intern(class_name)
+ end
+
+ # Generated service to deserialize the next available object as an instance of `class_name`
+ #
+ # Refinements to this method will be generated by the serialization phase.
+ # To avoid conflicts, there should not be any other refinements to this method.
+ # You can instead use `deserialize_class`.
+ protected fun deserialize_class_intern(class_name: Text): nullable Object do
+ errors.add new Error("Deserialization Error: Doesn't know how to deserialize class \"{class_name}\"")
+ return null
+ end
+
+ # Should `self` keep trying to deserialize an object after an error?
+ #
+ # This behavior takes effect after each attribute deserialization with
+ # errors such as a missing attribute or the value is of the wrong type.
+ # If `keep_going`, the attribute will be skipped but the engine will
+ # deserialize the next attribute.
+ # If `not keep_going`, the engine stops deserializing right away.
+ #
+ # When at `true`, this may cause the accumulation of a lot of entries in `errors`.
+ #
+ # Default at `true`.
+ var keep_going: nullable Bool = null is writable
+
+ # Errors encountered in the last call to `deserialize`
+ var errors = new Array[Error]
+end
+
+# Deserialization error related to an attribute of `receiver`
+abstract class AttributeError
+ super Error
+
+ # Parent object of the problematic attribute
+ var receiver: Object
+
+ # Name of the problematic attribute in `receiver`
+ var attribute_name: String
+end
+
+# Invalid dynamic type for a deserialized attribute
+class AttributeTypeError
+ super AttributeError
+
+ autoinit receiver, attribute_name, attribute, expected_type
+
+ # Deserialized object that isn't of the `expected_type`
+ var attribute: nullable Object
+
+ # Name of the type expected for `attribute`
+ var expected_type: String
+
+ redef var message is lazy do
+ var attribute = attribute
+ var found_type = if attribute != null then attribute.class_name else "null"
+
+ return "Deserialization Error: {
+ }Wrong type on `{receiver.class_name}::{attribute_name}` expected `{expected_type}`, got `{found_type}`"
+ end
+end
+
+# Missing attribute at deserialization
+class AttributeMissingError
+ super AttributeError
+
+ autoinit receiver, attribute_name
+
+ redef var message is lazy do
+ return "Deserialization Error: Missing attribute `{receiver.class_name}::{attribute_name}`"
+ end
+end
+
+# Instances of this class can be passed to `Serializer::serialize`
+interface Serializable
+ # Serialize `self` to `serializer`
+ #
+ # This is a shortcut to `Serializer::serialize`.
+ fun serialize_to(serializer: Serializer) do serializer.serialize(self)
+
+ # Actual serialization of `self` to `serializer`
+ #
+ # This writes the full data of `self` to `serializer`.
+ #
+ # This method can be redefined in sub classes and refinements.
+ # It should use `Serializer::serialize_attribute` to to register real or
+ # logical attributes.
+ #
+ # Any refinement should have its equivalent refinement of
+ # `Deserializer::deserialize_class` to support this custom deserialization.
+ fun core_serialize_to(serializer: Serializer) do end
+
+ # Accept references or force direct serialization (using `serialize_to`)
+ #
+ # The subclass change the default behavior, which will accept references,
+ # to force to always serialize copies of `self`.
+ private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
+
+ # Create an instance of this class from the `deserializer`
+ #
+ # This constructor is refined by subclasses to correctly build their instances.
+ init from_deserializer(deserializer: Deserializer) is nosuper do end
+end
+
+# Instances of this class are not delayed and instead serialized immediately
+# This applies mainly to `universal` types
+interface DirectSerializable
+ super Serializable
+
+ redef fun serialize_to_or_delay(v) do serialize_to(v)
+end
+
+redef class Bool super DirectSerializable end
+redef class Char super DirectSerializable end
+redef class Byte super DirectSerializable end
+redef class Int super DirectSerializable end
+redef class Float super DirectSerializable end
+redef class CString super DirectSerializable end
+redef class Text 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 Ref[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
+
+redef class Error
+ super Serializable
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var message = v.deserialize_attribute("message")
+ if not message isa String then message = ""
+ init message
+
+ var cause = v.deserialize_attribute("cause")
+ if cause isa nullable Error then self.cause = cause
+ end
+
+ redef fun core_serialize_to(v)
+ do
+ v.serialize_attribute("message", message)
+ v.serialize_attribute("cause", cause)
+ end
+end
+
+# ---
+# core::queue classes
+
+redef abstract class ProxyQueue[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var seq = v.deserialize_attribute("seq", (new GetName[Sequence[E]]).to_s)
+ if not seq isa Sequence[E] then seq = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "seq")
+ end
+
+ init seq
+ end
+
+ redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class RandQueue[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var seq = v.deserialize_attribute("seq", (new GetName[SimpleCollection[E]]).to_s)
+ if not seq isa SimpleCollection[E] then seq = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "seq")
+ end
+
+ init seq
+ end
+
+ redef fun core_serialize_to(v) do v.serialize_attribute("seq", seq)
+end
+
+redef class MinHeap[E]
+
+ redef init from_deserializer(v)
+ do
+ v.notify_of_creation self
+
+ var items = v.deserialize_attribute("items", (new GetName[SimpleCollection[E]]).to_s)
+ if not items isa Array[E] then items = new Array[E]
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "items")
+ end
+
+ var comparator = v.deserialize_attribute("comparator", "Comparator")
+ if not comparator isa Comparator then comparator = default_comparator
+ if v.deserialize_attribute_missing then
+ v.errors.add new AttributeMissingError(self, "comparator")
+ end
+
+ init comparator
+ self.items.add_all items
+ end
+
+ redef fun core_serialize_to(v)
+ do
+ v.serialize_attribute("items", items)
+ v.serialize_attribute("comparator", comparator)
+ end
+end
ng-class='currentTab == "signature" ? "active" : ""'>
<span class='synopsis' ng-bind-html='mentity.mdoc.html_synopsis' />
</div>
- <div id='{{mentity.html_id}}-doc' class='tab-pane'
- ng-class='currentTab == "doc" ? "active" : ""'>
- <entity-doc mentity='mentity' />
- </div>
<div id='{{mentity.html_id}}-grade' class='tab-pane'
ng-class='currentTab == "grade" ? "active" : ""'>
<entity-rating mentity='mentity' ratings='ratings'>
<li ng-class='currentTab == "signature" ? "active" : ""' ng-if='!noSynopsis'>
<a ng-click='currentTab = "signature"'>Signature</a>
</li>
- <li ng-class='currentTab == "doc" ? "active" : ""'>
- <a ng-click='currentTab = "doc"'>Doc</a>
- </li>
<li ng-class='currentTab == "grade" ? "active" : ""'>
<a ng-click='loadEntityStars(); currentTab = "grade"'>Grade</a>
</li>
-<span class="glyphicon glyphicon-tag" ng-class='{
+<span>
+ <!-- package -->
+ <span ng-if='mentity.class_name == "MPackage"'>
+ <span class='glyphicon glyphicon-book text-muted' style='top: 3px' />
+ </span>
+
+ <!-- groups -->
+ <span ng-if='mentity.class_name == "MGroup"'>
+ <span class='glyphicon glyphicon-folder-close text-muted' />
+ </span>
+
+ <!-- modules -->
+ <span ng-if='mentity.class_name == "MModule"'>
+ <span class='glyphicon glyphicon-file text-muted' style='top: 2px' />
+ </span>
+
+ <!-- classes -->
+ <span ng-if='mentity.class_name == "MClass"'>
+ <span class='glyphicon glyphicon-stop' ng-class='{
+ "text-success": mentity.visibility == "public",
+ "text-warning": mentity.visibility == "protected",
+ "text-danger": mentity.visibility == "private",
+ }' />
+ </span>
+
+ <!-- classdefs -->
+ <span ng-if='mentity.class_name == "MClassDef"' ng-class='{
"text-success": mentity.visibility == "public",
"text-warning": mentity.visibility == "protected",
"text-danger": mentity.visibility == "private",
-}' />
+ }'>
+ <span class='glyphicon' style='top: 2px; left:2px' ng-class='{
+ "glyphicon-plus": mentity.is_intro,
+ "glyphicon-asterisk": !mentity.is_intro
+ }' />
+ </span>
+
+ <!-- props -->
+ <span ng-if='mentity.class_name == "MAttribute" ||
+ mentity.class_name == "MMethod" ||
+ mentity.class_name == "MVirtualTypeProp"
+ '>
+ <span class='glyphicon glyphicon-tag' ng-class='{
+ "text-success": mentity.visibility == "public",
+ "text-warning": mentity.visibility == "protected",
+ "text-danger": mentity.visibility == "private",
+ }' />
+ </span>
+
+ <!-- propdefs -->
+ <span ng-if='mentity.class_name == "MAttributeDef" ||
+ mentity.class_name == "MMethodDef" ||
+ mentity.class_name == "MVirtualTypePropDef"'
+ ng-class='{
+ "text-success": mentity.visibility == "public",
+ "text-warning": mentity.visibility == "protected",
+ "text-danger": mentity.visibility == "private"
+ }'>
+ <span class='glyphicon' style='top: 2px; left:2px' ng-class='{
+ "glyphicon-plus": mentity.is_intro,
+ "glyphicon-asterisk": !mentity.is_intro
+ }' />
+ </span>
+</span>
.state('doc.entity.doc', {
url: '',
templateUrl: 'views/doc/doc.html',
- controller: function(mentity) {
+ resolve: {
+ doc: function(Model, $q, $stateParams, $state) {
+ var d = $q.defer();
+ Model.loadEntityDoc($stateParams.id, d.resolve,
+ function() {
+ $state.go('404', null, { location: false })
+ });
+ return d.promise;
+ }
+ },
+ controller: function(mentity, doc) {
this.mentity = mentity;
+ this.doc = doc;
},
controllerAs: 'vm',
})
$state.go('404', null, { location: false })
});
return d.promise;
+ },
+ inh: function(Model, $q, $stateParams, $state) {
+ var d = $q.defer();
+ Model.loadEntityInh($stateParams.id, d.resolve,
+ function() {
+ $state.go('404', null, { location: false })
+ });
+ return d.promise;
}
},
- controller: function(graph, $sce) {
+ controller: function(inh, graph, $sce) {
this.graph = $sce.trustAsHtml(graph);
+ this.inh = inh;
},
controllerAs: 'vm',
})
.error(cbErr);
},
+ loadEntityDoc: function(id, cb, cbErr) {
+ $http.get('/api/entity/' + id + '/doc')
+ .success(cb)
+ .error(cbErr);
+ },
+
loadEntityLinearization: function(id, cb, cbErr) {
$http.get('/api/linearization/' + id)
.success(cb)
.error(cbErr);
},
+ loadEntityInh: function(id, cb, cbErr) {
+ $http.get('/api/inheritance/' + id)
+ .success(cb)
+ .error(cbErr);
+ },
+
search: function(q, n, cb, cbErr) {
$http.get('/api/search?q=' + q + '&n=' + n)
.success(cb)
<div>
- <entity-list list-title='Class definitions'
+ <entity-list list-title='Groups' ng-if='vm.mentity.class_name == "MPackage"'
+ list-entities='vm.defs' list-object-filter='{}' />
+
+ <entity-list list-title='Groups & Modules' ng-if='vm.mentity.class_name == "MGroup"'
+ list-entities='vm.defs' list-object-filter='{}' />
+
+ <entity-list list-title='Class definitions' ng-if='vm.mentity.class_name == "MModule"'
list-entities='vm.defs' list-object-filter='{}' />
</div>
<ui-summary target='#summary-content' />
</div>
<div class='col-xs-9' id='summary-content'>
- <entity-card mentity='vm.mentity' default-tab='doc' no-synopsis='true' />
-
- <entity-list list-title='Groups' list-entities='vm.mentity.mgroups'
- list-object-filter='{}' />
-
- <entity-list list-title='Parent group' list-entities='[vm.mentity.parent]'
- list-object-filter='{}' ng-if='vm.mentity.parent' />
-
- <entity-list list-title='Subgroups' list-entities='vm.mentity.mgroups'
- list-object-filter='{}' />
-
- <entity-list list-title='Modules' list-entities='vm.mentity.mmodules'
- list-object-filter='{}' />
-
- <entity-list list-title='Imported modules' list-entities='vm.mentity.imports'
- list-object-filter='{}' />
+ <div class='card'>
+ <div class='card-body'>
+ <div ng-if='vm.doc'>
+ <div ng-bind-html='vm.doc.documentation'></div>
+ </div>
+ <div ng-if='!vm.doc'>
+ <i class='text-muted'>No documentation for this entity.</i>
+ </div>
+ </div>
+ </div>
<entity-list list-title='Introduced classes' list-entities='vm.mentity.intro_mclasses'
list-object-filter='{}' />
<entity-list list-title='Class redefinitions' list-entities='vm.mentity.redef_mclassdefs'
list-object-filter='{}' />
- <entity-list list-title='Parents'
- list-entities='vm.mentity.parents'
- list-object-filter='{}' />
-
<entity-list list-title='Constructors'
list-entities='vm.mentity.all_mproperties'
list-object-filter='{is_init: true}' />
</a>
</li>
+ <!-- definitions -->
+ <li role='presentation' ui-sref-active='active' ng-if='
+ vm.mentity.class_name == "MPackage" ||
+ vm.mentity.class_name == "MGroup" ||
+ vm.mentity.class_name == "MModule"'>
+ <a ui-sref='.defs'>
+ <span class='glyphicon glyphicon-list'/>
+ <span ng-if='vm.mentity.class_name == "MPackage"'>Groups</span>
+ <span ng-if='vm.mentity.class_name == "MGroup"'>Content</span>
+ <span ng-if='vm.mentity.class_name == "MModule"'>Classes</span>
+ </a>
+ </li>
+
<!-- graph -->
<li role='presentation' ui-sref-active='active' ng-if='
vm.mentity.class_name == "MPackage" ||
</a>
</li>
- <!-- definitions -->
- <li role='presentation' ui-sref-active='active' ng-if='
- vm.mentity.class_name == "MModule"'>
- <a ui-sref='.defs'>
- <span class='glyphicon glyphicon-asterisk'/> Class definitions
- </a>
- </li>
-
<!-- all -->
<li role='presentation' ui-sref-active='active' ng-if='
vm.mentity.class_name == "MClass"'>
-<div class='card'>
- <div class='card-body text-center'>
- <entity-graph mentity='mentity' graph='vm.graph' />
+<div>
+ <div class='card'>
+ <div class='card-body text-center'>
+ <entity-graph mentity='mentity' graph='vm.graph' />
+ </div>
</div>
+
+ <!-- Parents -->
+ <entity-list list-title='Parents'
+ list-entities='vm.inh.direct_greaters' list-object-filter='{}' />
+
+ <!-- Children -->
+ <entity-list list-title='Children'
+ list-entities='vm.inh.direct_smallers' list-object-filter='{}' />
</div>
if deserializer_npropdef == null then return
# Collect local types expected to be deserialized
- var types_to_deserialize = new Set[String]
+ #
+ # Map types' `name` to their `full_name`.
+ #
+ # FIXME use only the full name when there's a `class_full_name`
+ var types_to_deserialize = new Map[String, String]
## Local serializable standard class without parameters
for nclassdef in nclassdefs do
if mclass == null then continue
if mclass.arity == 0 and mclass.kind == concrete_kind then
- types_to_deserialize.add mclass.name
+ types_to_deserialize[mclass.name] = mclass.full_name
end
end
break
end
- if is_serializable then types_to_deserialize.add mtype.to_s
+ if is_serializable then types_to_deserialize[mtype.name] = mtype.full_name
end
end
end
code.add "redef fun deserialize_class_intern(name)"
code.add "do"
- for name in types_to_deserialize do
- code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
+ for name, full_name in types_to_deserialize do
+
+ if full_name.has('-') then
+ # Invalid module name, it is either artificial or a script
+ # without module declaration (like those generated by nitunit)
+ full_name = name
+ end
+
+ code.add " if name == \"{name}\" then return new {full_name}.from_deserializer(self)"
end
code.add " return super"
end
var code = """
-redef init from_deserializer(v: Deserializer) do abort"""
+redef init from_deserializer(v) do abort"""
var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
npropdefs.add npropdef
import astutil
import serialization
-# Fully process a content as a nit source file.
-fun hightlightcode(hl: HighlightVisitor, content: String): HLCode
+# Fully process `content` as a Nit source file.
+#
+# Set `print_errors = true` to print errors in the code to the console.
+fun hightlightcode(hl: HighlightVisitor, content: String, print_errors: nullable Bool): HLCode
do
# Prepare a stand-alone tool context
var tc = new ToolContext
tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
tc.keep_going = true # no exit, obviously
- tc.opt_warn.value = -1 # no output, obviously
+ if print_errors != true then tc.opt_warn.value = -1 # no output
# Prepare an stand-alone model and model builder.
# Unfortunately, models are enclosing and append-only.
alpha_comparator.sort(fs)
# Try each entry as a group or a module
for f in fs do
+ if f.first == '.' then continue
var af = a/f
mgroup = identify_group(af)
if mgroup != null then
var files = p.files
alpha_comparator.sort(files)
for f in files do
+ if f.first == '.' then continue
var fp = p/f
var g = identify_group(fp)
# Recursively scan for groups of the same package
end
#print "4.is {sub} a {sup}? <- no more resolution"
- assert sub isa MClassType # It is the only remaining type
-
- # A unfixed formal type can only accept itself
- if sup isa MFormalType then
- return false
+ if sub isa MBottomType or sub isa MErrorType then
+ return true
end
- if sup isa MNullType then
- # `sup` accepts only null
+ assert sub isa MClassType else print_error "{sub} <? {sup}" # It is the only remaining type
+
+ # Handle sup-type when the sub-type is class-based (other cases must have be identified before).
+ if sup isa MFormalType or sup isa MNullType or sup isa MBottomType or sup isa MErrorType then
+ # These types are not super-types of Class-based types.
return false
end
- assert sup isa MClassType # It is the only remaining type
+ assert sup isa MClassType else print_error "got {sup} {sub.inspect}" # It is the only remaining type
# Now both are MClassType, we need to dig
do
if n isa AAnnotation then return
- if n isa AType then
- var mclassdef = self.nclassdef.mclassdef.as(not null)
+ if n isa AType then do
+ var mclassdef = self.nclassdef.mclassdef
+ if mclassdef == null then break
var mtype = modelbuilder.resolve_mtype(mclassdef, n)
if mtype != null then
self.typecount.inc(mtype)
end
end
-redef class Location
+redef class nitc::Location
serialize
redef fun core_serialize_to(v) do
end
end
-redef class Location
+redef class nitc::Location
serialize
# Avoid diff on location absolute path
end
# Get a `Location` from its string representation.
- private fun to_location(loc: String): Location do
- return new Location.from_string(loc)
+ private fun to_location(loc: String): nitc::Location do
+ return new nitc::Location.from_string(loc)
end
# Get a `MVisibility` from its string representation.
use("/search", new APISearch(config))
use("/random", new APIRandom(config))
use("/entity/:id", new APIEntity(config))
+ use("/entity/:id/doc", new APIEntityDoc(config))
use("/code/:id", new APIEntityCode(config))
use("/uml/:id", new APIEntityUML(config))
use("/linearization/:id", new APIEntityLinearization(config))
end
end
+# Return the full MDoc of a MEntity.
+#
+# Example: `GET /entity/core::Array/doc`
+class APIEntityDoc
+ super APIHandler
+
+ redef fun get(req, res) do
+ var mentity = mentity_from_uri(req, res)
+ if mentity == null then return
+
+ var obj = new JsonObject
+ var mdoc = mentity.mdoc_or_fallback
+ if mdoc != null then
+ obj["documentation"] = mdoc.html_documentation.write_to_string
+ obj["location"] = mdoc.location
+ end
+ res.json obj
+ end
+end
+
# List ancestors, parents, child and descendants of MEntity
#
# Example: `GET /entity/core::Array/inheritance`
var mentity = mentity_from_uri(req, res)
if mentity == null then return
var mentities: Array[MEntity]
- if mentity isa MModule then
+ if mentity isa MPackage then
+ mentities = mentity.mgroups.to_a
+ else if mentity isa MGroup then
+ mentities = new Array[MEntity]
+ mentities.add_all mentity.in_nesting.direct_smallers
+ mentities.add_all mentity.mmodules
+ else if mentity isa MModule then
mentities = mentity.mclassdefs
else if mentity isa MClass then
mentities = mentity.mclassdefs
v.serialize_attribute("mdoc", mentity.mdoc_or_fallback)
v.serialize_attribute("visibility", mentity.visibility.to_s)
v.serialize_attribute("modifiers", mentity.collect_modifiers)
+ v.serialize_attribute("class_name", mentity.class_name)
var mentity = self.mentity
if mentity isa MMethod then
v.serialize_attribute("msignature", mentity.intro.msignature)
# Add doc down processing
redef fun core_serialize_to(v) do
v.serialize_attribute("html_synopsis", html_synopsis.write_to_string)
- v.serialize_attribute("html_documentation", html_documentation.write_to_string)
end
end
redef class POSetElement[E]
super Serializable
- redef fun serialize_to(v) do
+ redef fun core_serialize_to(v) do
assert self isa POSetElement[MEntity]
- v.serialize_attribute("greaters", to_mentity_refs(greaters))
v.serialize_attribute("direct_greaters", to_mentity_refs(direct_greaters))
v.serialize_attribute("direct_smallers", to_mentity_refs(direct_smallers))
- v.serialize_attribute("smallers", to_mentity_refs(smallers))
end
end
--- /dev/null
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line …>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array#1>, b:<Array#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet#1>, s:<ArraySet#2>, hm:<HashMap#3>, am:<ArrayMap#4>>
+
Deserialization Error: Doesn't know how to deserialize class "Array"
Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{"__kind": "obj", "__id": 0, "__class": "F","n":2222}
Deserialization Error: Doesn't know how to deserialize class "F"
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{"__kind": "obj", "__id": 0, "__class": "F","n":33.33}
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{"n":2222}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<F: 33.33>
+
+# Json:
+{"n":33.33}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# 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"}}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
Deserialization Error: Doesn't know how to deserialize class "Array"
Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{
Deserialization Error: Doesn't know how to deserialize class "F"
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Json:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{
+ "a": ["hello", 1234, 123.4],
+ "b": ["hella", 2345, 234.5]
+}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{
+ "n": 2222
+}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# Nit:
+<F: 33.33>
+
+# Json:
+{
+ "n": 33.33
+}
+
+# Back in Nit:
+null
+
+Deserialization Error: Doesn't know how to deserialize class "F"
+# 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"
+ }
+}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+ "a": ["hello", 1234, 123.4],
+ "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+ "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+ "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+ "hs": [-1, 0],
+ "s": ["one", "two"],
+ "hm": {
+ "one": 1,
+ "two": 2
+ },
+ "am": {
+ "three": "3",
+ "four": "4"
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "C", {
+ "a": [{
+ "typ": 123,
+ "data": [1]
+ }, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }],
+ "b": [{
+ "typ": 123,
+ "data": [2]
+ }, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ }],
+ "aa": {
+ "typ": 125,
+ "data": [1]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "D", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": {
+ "typ": 125,
+ "data": [0]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "E", {
+ "a": [{
+ "typ": 123,
+ "data": [1]
+ }, "Array", ["hello", 1234, 123.4]],
+ "b": [{
+ "typ": 123,
+ "data": [2]
+ }, "Array", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "F", {
+ "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x46\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "F", {
+ "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "G", {
+ "hs": [{
+ "typ": 123,
+ "data": [1]
+ }, "HashSet", [-1, 0]],
+ "s": [{
+ "typ": 123,
+ "data": [2]
+ }, "ArraySet", ["one", "two"]],
+ "hm": [{
+ "typ": 123,
+ "data": [3]
+ }, "HashMap", {
+ "one": 1,
+ "two": 2
+ }],
+ "am": [{
+ "typ": 123,
+ "data": [4]
+ }, "ArrayMap", {
+ "three": "3",
+ "four": "4"
+ }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "A"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [97]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "B"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [98]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "C"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "A"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+ "typ": 124,
+ "data": [97]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+ "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+ }],
+ "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+ "typ": 123,
+ "data": [11]
+ }, [{
+ "typ": 123,
+ "data": [12]
+ }, "B"], {
+ "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+ "<MsgPackExt typ: 0x7d, data: \\x06>": {
+ "typ": 124,
+ "data": [98]
+ },
+ "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+ "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+ "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+ "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+ "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+ }],
+ "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+ "typ": 125,
+ "data": [3]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "D"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [98]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+ "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+ "typ": 125,
+ "data": [0]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA5\x41\x72\x72\x61\x79\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\xD4\x7D\x04\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "E"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "Array"], ["hello", 1234, 123.4]],
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+ "typ": 123,
+ "data": [6]
+ }, {
+ "typ": 125,
+ "data": [4]
+ }, ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "F"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x46\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "F"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+null
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA7\x48\x61\x73\x68\x53\x65\x74\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xA8\x41\x72\x72\x61\x79\x53\x65\x74\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xA7\x48\x61\x73\x68\x4D\x61\x70\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xA8\x41\x72\x72\x61\x79\x4D\x61\x70\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "G"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "HashSet"], [-1, 0]],
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+ "typ": 123,
+ "data": [6]
+ }, [{
+ "typ": 123,
+ "data": [7]
+ }, "ArraySet"], ["one", "two"]],
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+ "typ": 123,
+ "data": [9]
+ }, [{
+ "typ": 123,
+ "data": [10]
+ }, "HashMap"], {
+ "one": 1,
+ "two": 2
+ }],
+ "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+ "typ": 123,
+ "data": [12]
+ }, [{
+ "typ": 123,
+ "data": [13]
+ }, "ArrayMap"], {
+ "three": "3",
+ "four": "4"
+ }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: ; s: ; hm: ; am: >
+
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Json:
+{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{"n":2222}
+
+# Back in Nit:
+<F: 2222>
+
+# Nit:
+<F: 33.33>
+
+# Json:
+{"n":33.33}
+
+# Back in Nit:
+<F: 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"}}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: ; am: >
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `Bool`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `Bool`
--- /dev/null
+# Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Json:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Json:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Json:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Json:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Json:
+{
+ "a": ["hello", 1234, 123.4],
+ "b": ["hella", 2345, 234.5]
+}
+
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Nit:
+<F: 2222>
+
+# Json:
+{
+ "n": 2222
+}
+
+# Back in Nit:
+<F: 2222>
+
+# Nit:
+<F: 33.33>
+
+# Json:
+{
+ "n": 33.33
+}
+
+# Back in Nit:
+<F: 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"
+ }
+}
+
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: ; am: >
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `Bool`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `Bool`
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+ "a": ["hello", 1234, 123.4],
+ "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+ "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+ "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+ "hs": [-1, 0],
+ "s": ["one", "two"],
+ "hm": {
+ "one": 1,
+ "two": 2
+ },
+ "am": {
+ "three": "3",
+ "four": "4"
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
redef fun deserialize_class(name)
do
# Module: test_serialization
- if name == "Array[Text]" then return new Array[Text].from_deserializer(self)
if name == "Array[Map[String, nullable Object]]" then return new Array[Map[String, nullable Object]].from_deserializer(self)
if name == "Array[String]" then return new Array[String].from_deserializer(self)
- if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
+ if name == "Array[Text]" then return new Array[Text].from_deserializer(self)
if name == "Array[Error]" then return new Array[Error].from_deserializer(self)
+ if name == "StrictHashMap[Int, Object]" then return new StrictHashMap[Int, Object].from_deserializer(self)
if name == "POSet[String]" then return new POSet[String].from_deserializer(self)
+ if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
if name == "Array[Int]" then return new Array[Int].from_deserializer(self)
if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
if name == "HashSet[String]" then return new HashSet[String].from_deserializer(self)
if name == "Array[Float]" then return new Array[Float].from_deserializer(self)
if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
- if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
if name == "POSetElement[String]" then return new POSetElement[String].from_deserializer(self)
if name == "HashMap[String, POSetElement[String]]" then return new HashMap[String, POSetElement[String]].from_deserializer(self)
if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
process http://localhost:3000/api/entity/core. 1+0/1
Error with http://localhost:3000/api/entity/core
-http://localhost:3000/api/entity/core: Unexpected Eof; is acceptable instead: value
+http://localhost:3000/api/entity/core: Empty JSON
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
# Src:
-<E: 2222>
+<F: 2222>
# Dst:
-<E: 2222>
+<F: 2222>
# Src:
-<E: 33.33>
+<F: 33.33>
# Dst:
-<E: 33.33>
+<F: 33.33>
# Src:
<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
--- /dev/null
+# Custom:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# Inspect:
+<A#0 b:true, c:'a', f:0.123, i:1234, serialization_specific_name:"asdf", n:null>
+
+# Custom:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# Inspect:
+<B#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"hjkl", n:…>
+
+# Custom:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# Inspect:
+<C#0 a:<A#1>, b:<B#2>, aa:<A#1>>
+
+# Custom:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# Inspect:
+<D#0 b:false, c:'b', f:123.123, i:2345, serialization_specific_name:"new line …>
+
+# Custom:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# Inspect:
+<E#0 a:<Array[Object]#1>, b:<Array[nullable Serializable]#2>>
+
+# Custom:
+<F: 2222>
+
+# Inspect:
+<F[Int]#0 n:2222>
+
+# Custom:
+<F: 33.33>
+
+# Inspect:
+<F[Float]#0 n:33.33>
+
+# Custom:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Inspect:
+<G#0 hs:<HashSet[Int]#1>, s:<ArraySet[String]#2>, hm:<HashMap[String, Int]#3>,…>
+
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{"__kind": "obj", "__id": 0, "__class": "F[Int]","n":2222}
# Back in Nit:
-<E: 2222>
+<F: 2222>
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{"__kind": "obj", "__id": 0, "__class": "F[Float]","n":33.33}
# Back in Nit:
-<E: 33.33>
+<F: 33.33>
# Nit:
<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
# Json:
{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
# Nit:
<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
# Json:
{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"}
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
# Nit:
<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
# Json:
{"a":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null},"b":{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"hjkl","n":12,"ii":1111,"ss":"qwer"},"aa":{"b":true,"c":"a","f":0.123,"i":1234,"serialization_specific_name":"asdf","n":null}}
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
# Nit:
<D: <B: <A: false b 123.123 2345 new line ->
# Json:
{"b":false,"c":"b","f":123.123,"i":2345,"serialization_specific_name":"new line ->\n<-","n":null,"ii":1111,"ss":"\tf\"\r\\/","d":null}
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
# Nit:
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
# Json:
{"a":["hello",1234,123.4],"b":["hella",2345,234.5]}
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{"n":2222}
+# Back in Nit:
+<F: 2222>
+
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{"n":33.33}
+# Back in Nit:
+<F: 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"}}
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{
}
# Back in Nit:
-<E: 2222>
+<F: 2222>
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{
}
# Back in Nit:
-<E: 33.33>
+<F: 33.33>
# Nit:
<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
"n": null
}
+# Back in Nit:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
# Nit:
<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
"ss": "qwer"
}
+# Back in Nit:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
# Nit:
<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
}
}
+# Back in Nit:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
# Nit:
<D: <B: <A: false b 123.123 2345 new line ->
"d": null
}
+# Back in Nit:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
# Nit:
<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
"b": ["hella", 2345, 234.5]
}
+# Back in Nit:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
# Nit:
-<E: 2222>
+<F: 2222>
# Json:
{
"n": 2222
}
+# Back in Nit:
+<F: 2222>
+
# Nit:
-<E: 33.33>
+<F: 33.33>
# Json:
{
"n": 33.33
}
+# Back in Nit:
+<F: 33.33>
+
# Nit:
<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
}
}
+# Back in Nit:
+<G: hs: -1, 0; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+Deserialization Error: Doesn't know how to deserialize class "Set[String]"
+Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "C", {
+ "a": [{
+ "typ": 123,
+ "data": [1]
+ }, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }],
+ "b": [{
+ "typ": 123,
+ "data": [2]
+ }, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ }],
+ "aa": {
+ "typ": 125,
+ "data": [1]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "D", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": {
+ "typ": 125,
+ "data": [0]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}
+
+# 4. Back in Nit (no metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}
+
+# 4. Back in Nit (no metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x83\xA1\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x88\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\x86\xA1\x62\xC3\xA1\x63\xA1\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+{
+ "a": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ },
+ "b": {
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ },
+ "aa": {
+ "b": true,
+ "c": "a",
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+Serialization warning: Cycle detected in serialized object, replacing reference with 'null'.
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x89\xA1\x62\xC2\xA1\x63\xA1\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xC0
+
+# 3. JSON:
+{
+ "b": false,
+ "c": "b",
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": null
+}
+
+# 4. Back in Nit (no metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> false>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x82\xA1\x61\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+{
+ "a": ["hello", 1234, 123.4],
+ "b": ["hella", 2345, 234.5]
+}
+
+# 4. Back in Nit (no metadata):
+<E: a: ; b: >
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+{
+ "n": 2222
+}
+
+# 4. Back in Nit (no metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+{
+ "n": 33.33
+}
+
+# 4. Back in Nit (no metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x84\xA2\x68\x73\x92\xFF\x00\xA1\x73\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+{
+ "hs": [-1, 0],
+ "s": ["one", "two"],
+ "hm": {
+ "one": 1,
+ "two": 2
+ },
+ "am": {
+ "three": "3",
+ "four": "4"
+ }
+}
+
+# 4. Back in Nit (no metadata):
+<G: hs: ; s: ; hm: one. 1, two. 2; am: three. 3, four. 4>
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x43\x83\xA1\x61\x93\xD4\x7B\x01\xA1\x41\x86\xA1\x62\xC3\xA1\x63\xD4\x7C\x61\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\xA1\x69\xCD\x04\xD2\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\xA1\x6E\xC0\xA1\x62\x93\xD4\x7B\x02\xA1\x42\x88\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\xA1\x6E\x0C\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA4\x71\x77\x65\x72\xA2\x61\x61\xD4\x7D\x01
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "C", {
+ "a": [{
+ "typ": 123,
+ "data": [1]
+ }, "A", {
+ "b": true,
+ "c": {
+ "typ": 124,
+ "data": [97]
+ },
+ "f": 0.123,
+ "i": 1234,
+ "serialization_specific_name": "asdf",
+ "n": null
+ }],
+ "b": [{
+ "typ": 123,
+ "data": [2]
+ }, "B", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "hjkl",
+ "n": 12,
+ "ii": 1111,
+ "ss": "qwer"
+ }],
+ "aa": {
+ "typ": 125,
+ "data": [1]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x44\x89\xA1\x62\xC2\xA1\x63\xD4\x7C\x62\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xA1\x69\xCD\x09\x29\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\xA1\x6E\xC0\xA2\x69\x69\xCD\x04\x57\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "D", {
+ "b": false,
+ "c": {
+ "typ": 124,
+ "data": [98]
+ },
+ "f": 123.123,
+ "i": 2345,
+ "serialization_specific_name": "new line ->\n<-",
+ "n": null,
+ "ii": 1111,
+ "ss": "\tf\"\r\\/",
+ "d": {
+ "typ": 125,
+ "data": [0]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x45\x82\xA1\x61\x93\xD4\x7B\x01\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\xA1\x62\x93\xD4\x7B\x02\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "E", {
+ "a": [{
+ "typ": 123,
+ "data": [1]
+ }, "Array[Object]", ["hello", 1234, 123.4]],
+ "b": [{
+ "typ": 123,
+ "data": [2]
+ }, "Array[nullable Serializable]", ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA6\x46\x5B\x49\x6E\x74\x5D\x81\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "F[Int]", {
+ "n": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "F[Float]", {
+ "n": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\xA1\x47\x84\xA2\x68\x73\x93\xD4\x7B\x01\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\xA1\x73\x93\xD4\x7B\x02\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\xA2\x68\x6D\x93\xD4\x7B\x03\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\xA2\x61\x6D\x93\xD4\x7B\x04\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, "G", {
+ "hs": [{
+ "typ": 123,
+ "data": [1]
+ }, "HashSet[Int]", [-1, 0]],
+ "s": [{
+ "typ": 123,
+ "data": [2]
+ }, "ArraySet[String]", ["one", "two"]],
+ "hm": [{
+ "typ": 123,
+ "data": [3]
+ }, "HashMap[String, Int]", {
+ "one": 1,
+ "two": 2
+ }],
+ "am": [{
+ "typ": 123,
+ "data": [4]
+ }, "ArrayMap[String, String]", {
+ "three": "3",
+ "four": "4"
+ }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
--- /dev/null
+# 1. Nit source:
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x41\x86\x92\xD4\x7B\x02\xA1\x62\xC3\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x04\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x05\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x07\xA1\x6E\xC0
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "A"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": true,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [97]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 0.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 1234,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "asdf",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null
+}]
+
+# 4. Back in Nit (with metadata):
+<A: true a 0.123 1234 asdf false p4ssw0rd>
+
+# 1. Nit source:
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x42\x88\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x68\x6A\x6B\x6C\x92\xD4\x7B\x07\xA1\x6E\x0C\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA4\x71\x77\x65\x72
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "B"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [98]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "hjkl",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": 12,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "qwer"
+}]
+
+# 4. Back in Nit (with metadata):
+<B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>
+
+# 1. Nit source:
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x43\x83\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xA1\x41\x86\x92\xD4\x7B\x05\xA1\x62\xC3\x92\xD4\x7B\x06\xA1\x63\xD4\x7C\x61\x92\xD4\x7B\x07\xA1\x66\xCB\x3F\xBF\x97\x24\x74\x53\x8E\xF3\x92\xD4\x7B\x08\xA1\x69\xCD\x04\xD2\x92\xD4\x7B\x09\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xA4\x61\x73\x64\x66\x92\xD4\x7B\x0A\xA1\x6E\xC0\xD4\x7D\x05\x93\xD4\x7B\x0B\x92\xD4\x7B\x0C\xA1\x42\x88\xD4\x7D\x05\xC2\xD4\x7D\x06\xD4\x7C\x62\xD4\x7D\x07\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\xD4\x7D\x08\xCD\x09\x29\xD4\x7D\x09\xA4\x68\x6A\x6B\x6C\xD4\x7D\x0A\x0C\x92\xD4\x7B\x0D\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x0E\xA2\x73\x73\xA4\x71\x77\x65\x72\x92\xD4\x7B\x0F\xA2\x61\x61\xD4\x7D\x03
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "C"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "A"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": true,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,c]": {
+ "typ": 124,
+ "data": [97]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,f]": 0.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,i]": 1234,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,serialization_specific_name]": "asdf",
+ "[<MsgPackExt typ: 0x7b, data: \\x0A>,n]": null
+ }],
+ "<MsgPackExt typ: 0x7d, data: \\x05>": [{
+ "typ": 123,
+ "data": [11]
+ }, [{
+ "typ": 123,
+ "data": [12]
+ }, "B"], {
+ "<MsgPackExt typ: 0x7d, data: \\x05>": false,
+ "<MsgPackExt typ: 0x7d, data: \\x06>": {
+ "typ": 124,
+ "data": [98]
+ },
+ "<MsgPackExt typ: 0x7d, data: \\x07>": 123.123,
+ "<MsgPackExt typ: 0x7d, data: \\x08>": 2345,
+ "<MsgPackExt typ: 0x7d, data: \\x09>": "hjkl",
+ "<MsgPackExt typ: 0x7d, data: \\x0A>": 12,
+ "[<MsgPackExt typ: 0x7b, data: \\x0D>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x0E>,ss]": "qwer"
+ }],
+ "[<MsgPackExt typ: 0x7b, data: \\x0F>,aa]": {
+ "typ": 125,
+ "data": [3]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<C: <A: true a 0.123 1234 asdf false p4ssw0rd> <B: <A: false b 123.123 2345 hjkl true p4ssw0rd> 1111 qwer>>
+
+# 1. Nit source:
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x44\x89\x92\xD4\x7B\x02\xA1\x62\xC2\x92\xD4\x7B\x03\xA1\x63\xD4\x7C\x62\x92\xD4\x7B\x04\xA1\x66\xCB\x40\x5E\xC7\xDF\x3B\x64\x5A\x1D\x92\xD4\x7B\x05\xA1\x69\xCD\x09\x29\x92\xD4\x7B\x06\xBB\x73\x65\x72\x69\x61\x6C\x69\x7A\x61\x74\x69\x6F\x6E\x5F\x73\x70\x65\x63\x69\x66\x69\x63\x5F\x6E\x61\x6D\x65\xAE\x6E\x65\x77\x20\x6C\x69\x6E\x65\x20\x2D\x3E\x0A\x3C\x2D\x92\xD4\x7B\x07\xA1\x6E\xC0\x92\xD4\x7B\x08\xA2\x69\x69\xCD\x04\x57\x92\xD4\x7B\x09\xA2\x73\x73\xA6\x09\x66\x22\x0D\x5C\x2F\x92\xD4\x7B\x0A\xA1\x64\xD4\x7D\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "D"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,b]": false,
+ "[<MsgPackExt typ: 0x7b, data: \\x03>,c]": {
+ "typ": 124,
+ "data": [98]
+ },
+ "[<MsgPackExt typ: 0x7b, data: \\x04>,f]": 123.123,
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,i]": 2345,
+ "[<MsgPackExt typ: 0x7b, data: \\x06>,serialization_specific_name]": "new line ->\n<-",
+ "[<MsgPackExt typ: 0x7b, data: \\x07>,n]": null,
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,ii]": 1111,
+ "[<MsgPackExt typ: 0x7b, data: \\x09>,ss]": "\tf\"\r\\/",
+ "[<MsgPackExt typ: 0x7b, data: \\x0A>,d]": {
+ "typ": 125,
+ "data": [0]
+ }
+}]
+
+# 4. Back in Nit (with metadata):
+<D: <B: <A: false b 123.123 2345 new line ->
+<- false p4ssw0rd> 1111 f"\r\/> true>
+
+# 1. Nit source:
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x45\x82\x92\xD4\x7B\x02\xA1\x61\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAD\x41\x72\x72\x61\x79\x5B\x4F\x62\x6A\x65\x63\x74\x5D\x93\xA5\x68\x65\x6C\x6C\x6F\xCD\x04\xD2\xCB\x40\x5E\xD9\x99\x99\x99\x99\x9A\x92\xD4\x7B\x05\xA1\x62\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xBC\x41\x72\x72\x61\x79\x5B\x6E\x75\x6C\x6C\x61\x62\x6C\x65\x20\x53\x65\x72\x69\x61\x6C\x69\x7A\x61\x62\x6C\x65\x5D\x93\xA5\x68\x65\x6C\x6C\x61\xCD\x09\x29\xCB\x40\x6D\x50\x00\x00\x00\x00\x00
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "E"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,a]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "Array[Object]"], ["hello", 1234, 123.4]],
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,b]": [{
+ "typ": 123,
+ "data": [6]
+ }, [{
+ "typ": 123,
+ "data": [7]
+ }, "Array[nullable Serializable]"], ["hella", 2345, 234.5]]
+}]
+
+# 4. Back in Nit (with metadata):
+<E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
+
+# 1. Nit source:
+<F: 2222>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA6\x46\x5B\x49\x6E\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCD\x08\xAE
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "F[Int]"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 2222
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 2222>
+
+# 1. Nit source:
+<F: 33.33>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA8\x46\x5B\x46\x6C\x6F\x61\x74\x5D\x81\x92\xD4\x7B\x02\xA1\x6E\xCB\x40\x40\xAA\x3D\x70\xA3\xD7\x0A
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "F[Float]"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,n]": 33.33
+}]
+
+# 4. Back in Nit (with metadata):
+<F: 33.33>
+
+# 1. Nit source:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# 2. MsgPack:
+\x93\xD4\x7B\x00\x92\xD4\x7B\x01\xA1\x47\x84\x92\xD4\x7B\x02\xA2\x68\x73\x93\xD4\x7B\x03\x92\xD4\x7B\x04\xAC\x48\x61\x73\x68\x53\x65\x74\x5B\x49\x6E\x74\x5D\x92\xFF\x00\x92\xD4\x7B\x05\xA1\x73\x93\xD4\x7B\x06\x92\xD4\x7B\x07\xB0\x41\x72\x72\x61\x79\x53\x65\x74\x5B\x53\x74\x72\x69\x6E\x67\x5D\x92\xA3\x6F\x6E\x65\xA3\x74\x77\x6F\x92\xD4\x7B\x08\xA2\x68\x6D\x93\xD4\x7B\x09\x92\xD4\x7B\x0A\xB4\x48\x61\x73\x68\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x49\x6E\x74\x5D\x82\xA3\x6F\x6E\x65\x01\xA3\x74\x77\x6F\x02\x92\xD4\x7B\x0B\xA2\x61\x6D\x93\xD4\x7B\x0C\x92\xD4\x7B\x0D\xB8\x41\x72\x72\x61\x79\x4D\x61\x70\x5B\x53\x74\x72\x69\x6E\x67\x2C\x20\x53\x74\x72\x69\x6E\x67\x5D\x82\xA5\x74\x68\x72\x65\x65\xA1\x33\xA4\x66\x6F\x75\x72\xA1\x34
+
+# 3. JSON:
+[{
+ "typ": 123,
+ "data": [0]
+}, [{
+ "typ": 123,
+ "data": [1]
+}, "G"], {
+ "[<MsgPackExt typ: 0x7b, data: \\x02>,hs]": [{
+ "typ": 123,
+ "data": [3]
+ }, [{
+ "typ": 123,
+ "data": [4]
+ }, "HashSet[Int]"], [-1, 0]],
+ "[<MsgPackExt typ: 0x7b, data: \\x05>,s]": [{
+ "typ": 123,
+ "data": [6]
+ }, [{
+ "typ": 123,
+ "data": [7]
+ }, "ArraySet[String]"], ["one", "two"]],
+ "[<MsgPackExt typ: 0x7b, data: \\x08>,hm]": [{
+ "typ": 123,
+ "data": [9]
+ }, [{
+ "typ": 123,
+ "data": [10]
+ }, "HashMap[String, Int]"], {
+ "one": 1,
+ "two": 2
+ }],
+ "[<MsgPackExt typ: 0x7b, data: \\x0B>,am]": [{
+ "typ": 123,
+ "data": [12]
+ }, [{
+ "typ": 123,
+ "data": [13]
+ }, "ArrayMap[String, String]"], {
+ "three": "3",
+ "four": "4"
+ }]
+}]
+
+# 4. Back in Nit (with metadata):
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
# See the License for the specific language governing permissions and
# limitations under the License.
-import json::string_parser
+import json::static
import json
if args.length < 1 then
var n: N
- redef fun to_s do return "<E: {n}>"
+ redef fun to_s do return "<F: {n}>"
end
# Other collections
--- /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
+
+var entities = new TestEntities
+var tests = entities.with_generics
+
+for o in tests do
+ print "# Custom:\n{o}\n"
+ print "# Inspect:\n{o.inspect}\n"
+end
# See the License for the specific language governing permissions and
# limitations under the License.
-import test_deserialization
import json
-#alt1# import test_deserialization_serial
-#alt3# import test_deserialization_serial
+
+import test_deserialization
+import test_deserialization_serial
var entities = new TestEntities
#alt4#serializer.pretty_json = true
serializer.serialize(o)
- var deserializer = new JsonDeserializer(stream.to_s)#alt2##alt4#
- var deserialized = deserializer.deserialize#alt2##alt4#
+ var type_name: nullable String = o.class_name
+ type_name = null #alt2##alt4#
+ var deserializer = new JsonDeserializer(stream.to_s)
+ var deserialized = deserializer.deserialize(type_name)
print "# Nit:\n{o}\n"
print "# Json:\n{stream}\n"
- print "# Back in Nit:\n{deserialized or else "null"}\n"#alt2##alt4#
- if deserializer.errors.not_empty then print deserializer.errors.join("\n")#alt2##alt4#
+ print "# Back in Nit:\n{deserialized or else "null"}\n"
+ if deserializer.errors.not_empty then print deserializer.errors.join("\n")
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.
+
+# alt0 non-generics plain
+# alt1 non-generics metadata
+# alt2 generics plain
+# alt3 generics metadata
+# alt4 generics metadata cache_metadata_strings
+
+import test_deserialization
+import msgpack
+import msgpack::read
+import json
+#alt2# import test_deserialization_serial
+#alt3# import test_deserialization_serial
+#alt4# import test_deserialization_serial
+
+var entities = new TestEntities
+
+var tests = entities.without_generics
+#alt2#tests = entities.with_generics
+#alt3#tests = entities.with_generics
+#alt4#tests = entities.with_generics
+
+for o in tests do
+ var plain = true
+ #alt1#plain = false
+ #alt3#plain = false
+ #alt4#plain = false
+
+ var writer = new BytesWriter
+ var serializer = new MsgPackSerializer(writer)
+ serializer.plain_msgpack = plain
+ #alt4#serializer.cache_metadata_strings = true
+ serializer.serialize o
+ var bytes = writer.bytes
+
+ # Nit source
+ print "# 1. Nit source:\n{o}\n"
+
+ # Generated MessagePack
+ print "# 2. MsgPack:\n{bytes.chexdigest}\n"
+
+ # Python deserialization line to ensure compliance and otherwise debug
+ #print "msgpack.unpackb(b'{bytes.chexdigest}')"
+
+ # Show readable structure of the generated MessagePack as JSON
+ var msg = (new BytesReader(bytes)).read_msgpack
+ var json = if msg != null then
+ msg.serialize_to_json(plain=true, pretty=true)
+ else "null"
+ print "# 3. JSON:\n{json}\n"
+
+ # Deserialize
+ var deserializer = new MsgPackDeserializer(new BytesReader(bytes))
+ if serializer.plain_msgpack then
+ var deserialized = deserializer.deserialize(o.class_name)
+ print "# 4. Back in Nit (no metadata):\n{deserialized or else "null"}\n"
+ else
+ var deserialized = deserializer.deserialize
+ print "# 4. Back in Nit (with metadata):\n{deserialized or else "null"}\n"
+ end
+end