Browse Source

Source code, makefile and Xcode project.

Marco Bambini 8 years ago
parent
commit
e8999a84ef
100 changed files with 21423 additions and 1 deletions
  1. 29 0
      Makefile
  2. 1 1
      docs/internals/1_files.html
  3. 537 0
      gravity.xcodeproj/project.pbxproj
  4. 7 0
      gravity.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  5. BIN
      gravity.xcodeproj/project.xcworkspace/xcuserdata/marco.xcuserdatad/UserInterfaceState.xcuserstate
  6. 91 0
      gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/gravity.xcscheme
  7. 91 0
      gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/unittest.xcscheme
  8. 32 0
      gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist
  9. 189 0
      src/cli/gravity.c
  10. 190 0
      src/cli/unittest.c
  11. 119 0
      src/compiler/debug_macros.h
  12. 730 0
      src/compiler/gravity_ast.c
  13. 318 0
      src/compiler/gravity_ast.h
  14. 1611 0
      src/compiler/gravity_codegen.c
  15. 18 0
      src/compiler/gravity_codegen.h
  16. 206 0
      src/compiler/gravity_compiler.c
  17. 29 0
      src/compiler/gravity_compiler.h
  18. 498 0
      src/compiler/gravity_ircode.c
  19. 101 0
      src/compiler/gravity_ircode.h
  20. 620 0
      src/compiler/gravity_lexer.c
  21. 58 0
      src/compiler/gravity_lexer.h
  22. 512 0
      src/compiler/gravity_optimizer.c
  23. 17 0
      src/compiler/gravity_optimizer.h
  24. 2158 0
      src/compiler/gravity_parser.c
  25. 39 0
      src/compiler/gravity_parser.h
  26. 180 0
      src/compiler/gravity_semacheck1.c
  27. 62 0
      src/compiler/gravity_semacheck1.h
  28. 1100 0
      src/compiler/gravity_semacheck2.c
  29. 88 0
      src/compiler/gravity_semacheck2.h
  30. 166 0
      src/compiler/gravity_symboltable.c
  31. 29 0
      src/compiler/gravity_symboltable.h
  32. 348 0
      src/compiler/gravity_token.c
  33. 143 0
      src/compiler/gravity_token.h
  34. 59 0
      src/compiler/gravity_visitor.c
  35. 52 0
      src/compiler/gravity_visitor.h
  36. 1881 0
      src/runtime/gravity_core.c
  37. 26 0
      src/runtime/gravity_core.h
  38. 2044 0
      src/runtime/gravity_vm.c
  39. 68 0
      src/runtime/gravity_vm.h
  40. 273 0
      src/runtime/gravity_vmmacros.h
  41. 45 0
      src/shared/gravity_array.h
  42. 76 0
      src/shared/gravity_delegate.h
  43. 399 0
      src/shared/gravity_hash.c
  44. 52 0
      src/shared/gravity_hash.h
  45. 129 0
      src/shared/gravity_macros.h
  46. 348 0
      src/shared/gravity_memory.c
  47. 57 0
      src/shared/gravity_memory.h
  48. 225 0
      src/shared/gravity_opcodes.h
  49. 1897 0
      src/shared/gravity_value.c
  50. 480 0
      src/shared/gravity_value.h
  51. 294 0
      src/utils/gravity_debug.c
  52. 18 0
      src/utils/gravity_debug.h
  53. 1292 0
      src/utils/gravity_json.c
  54. 314 0
      src/utils/gravity_json.h
  55. 445 0
      src/utils/gravity_utils.c
  56. 62 0
      src/utils/gravity_utils.h
  57. 44 0
      test/01-syntax/class_declaration.gravity
  58. 4 0
      test/01-syntax/empty.gravity
  59. 9 0
      test/01-syntax/empty_enum.gravity
  60. 9 0
      test/01-syntax/empty_return.gravity
  61. 10 0
      test/01-syntax/func_error_params.gravity
  62. 14 0
      test/01-syntax/module_declaration.gravity
  63. 10 0
      test/01-syntax/var_in_if.gravity
  64. 11 0
      test/01-syntax/var_in_repeat.gravity
  65. 11 0
      test/01-syntax/var_in_switch.gravity
  66. 11 0
      test/01-syntax/var_in_while.gravity
  67. 12 0
      test/02-semantic_step1/class1_redeclared.gravity
  68. 11 0
      test/02-semantic_step1/class2_internal_redeclared.gravity
  69. 14 0
      test/02-semantic_step1/enum1_redeclared.gravity
  70. 11 0
      test/02-semantic_step1/enum2_internal_redeclared.gravity
  71. 9 0
      test/02-semantic_step1/function_redeclared.gravity
  72. 28 0
      test/02-semantic_step1/local_variables_number.gravity
  73. 12 0
      test/02-semantic_step1/module1_redeclared.gravity
  74. 11 0
      test/02-semantic_step1/module2_internal_redeclared.gravity
  75. 9 0
      test/02-semantic_step1/var_redeclared.gravity
  76. 10 0
      test/03-semantic_step2/class1_access_specifier.gravity
  77. 11 0
      test/03-semantic_step2/class2_access_specifier.gravity
  78. 14 0
      test/03-semantic_step2/class_container.gravity
  79. 10 0
      test/03-semantic_step2/enum1_access_specifier.gravity
  80. 12 0
      test/03-semantic_step2/enum2_access_specifier.gravity
  81. 12 0
      test/03-semantic_step2/for1_identifier_not_found.gravity
  82. 10 0
      test/03-semantic_step2/func1_access_specifier.gravity
  83. 12 0
      test/03-semantic_step2/func2_access_specifier.gravity
  84. 11 0
      test/03-semantic_step2/func3_identifier_redeclared.gravity
  85. 11 0
      test/03-semantic_step2/func4_identifier_redeclared.gravity
  86. 14 0
      test/03-semantic_step2/func_container.gravity
  87. 17 0
      test/03-semantic_step2/invalid_condition_if.gravity
  88. 18 0
      test/03-semantic_step2/invalid_condition_while.gravity
  89. 9 0
      test/03-semantic_step2/module_access_specifier.gravity
  90. 14 0
      test/03-semantic_step2/module_container.gravity
  91. 16 0
      test/03-semantic_step2/override_property.gravity
  92. 8 0
      test/03-semantic_step2/var1_access_specifier.gravity
  93. 10 0
      test/03-semantic_step2/var2_access_specifier.gravity
  94. 8 0
      test/03-semantic_step2/var_container.gravity
  95. 11 0
      test/04-codegen/assignment1.gravity
  96. 13 0
      test/04-codegen/assignment2.gravity
  97. 12 0
      test/04-codegen/assignment3.gravity
  98. 13 0
      test/04-codegen/assignment4.gravity
  99. 25 0
      test/04-codegen/class/1.gravity
  100. 19 0
      test/04-codegen/class/12.gravity

+ 29 - 0
Makefile

@@ -0,0 +1,29 @@
+COMPILER_DIR = src/compiler/
+RUNTIME_DIR = src/runtime/
+SHARED_DIR = src/shared/
+UTILS_DIR = src/utils/
+UNITTEST_SRC = src/cli/unittest.c
+GRAVITY_SRC = src/cli/gravity.c
+
+SRC = $(wildcard $(COMPILER_DIR)*.c) \
+      $(wildcard $(RUNTIME_DIR)/*.c) \
+      $(wildcard $(SHARED_DIR)/*.c) \
+      $(wildcard $(UTILS_DIR)/*.c)
+
+INCLUDE = -I$(COMPILER_DIR) -I$(RUNTIME_DIR) -I$(SHARED_DIR) -I$(UTILS_DIR) 
+CFLAGS = $(INCLUDE) -O2
+OBJ = $(SRC:.c=.o)
+LDFLAGS = 
+
+all: unittest gravity
+
+unittest:	$(OBJ) $(UNITTEST_SRC)
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+gravity:	$(OBJ) $(GRAVITY_SRC)
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+.PHONY: all clean unittest gravity
+
+clean:	
+	rm -f $(OBJ) unittest gravity

+ 1 - 1
docs/internals/1_files.html

@@ -113,7 +113,7 @@
 				<li style="color: #7E267E; margin: 8px 0;"><i class="fa fa-file-text-o" aria-hidden="true"></i> gravity_utils: <span style="color: #797979;">Collects useful functions extensively used by other files. Time, IO, strings, UTF-8 and many other functions can be found in this file.</span></li>
 				<li style="color: #7E267E; margin: 8px 0;"><i class="fa fa-file-text-o" aria-hidden="true"></i> gravity_json: <span style="color: #797979;">Very efficient JSON parser from <a href="https://github.com/udp/json-parser">https://github.com/udp/json-parser</a>.</span></li>
 				<li style="color: #7E267E; margin: 8px 0;"><i class="fa fa-file-text-o" aria-hidden="true"></i> gravity_debug: <span style="color: #797979;">Contains the gravity_disassemble function used for debugging purpose.</span></li>
-				<li style="color: #7E267E; margin: 8px 0;"><i class="fa fa-file-text-o" aria-hidden="true"></i> gravity_objc: <span style="color: #797979;">Official objc bridge used in the <a href="http://creolabs.com">Creo</a> project (not yet committed).</span></li>
+				<!--<li style="color: #7E267E; margin: 8px 0;"><i class="fa fa-file-text-o" aria-hidden="true"></i> gravity_objc: <span style="color: #797979;">Official objc bridge used in the <a href="http://creolabs.com">Creo</a> project (not yet committed).</span></li>-->
 			</ul>
 			<p style="color: #7E267E;"><i class="fa fa-folder-o" aria-hidden="true"></i> Compiler</p>
 			<ul style="list-style: none;">

+ 537 - 0
gravity.xcodeproj/project.pbxproj

@@ -0,0 +1,537 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		A9506D381E69AB66009A0045 /* gravity.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506CFC1E69AB1E009A0045 /* gravity.c */; };
+		A9506D391E69AB6A009A0045 /* unittest.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506CFD1E69AB1E009A0045 /* unittest.c */; };
+		A9506D3A1E69AB78009A0045 /* gravity_ast.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D001E69AB1E009A0045 /* gravity_ast.c */; };
+		A9506D3B1E69AB78009A0045 /* gravity_codegen.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D021E69AB1E009A0045 /* gravity_codegen.c */; };
+		A9506D3C1E69AB78009A0045 /* gravity_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D041E69AB1E009A0045 /* gravity_compiler.c */; };
+		A9506D3D1E69AB78009A0045 /* gravity_ircode.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D061E69AB1E009A0045 /* gravity_ircode.c */; };
+		A9506D3E1E69AB78009A0045 /* gravity_lexer.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D081E69AB1E009A0045 /* gravity_lexer.c */; };
+		A9506D3F1E69AB78009A0045 /* gravity_optimizer.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0A1E69AB1E009A0045 /* gravity_optimizer.c */; };
+		A9506D401E69AB78009A0045 /* gravity_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0C1E69AB1E009A0045 /* gravity_parser.c */; };
+		A9506D411E69AB78009A0045 /* gravity_semacheck1.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0E1E69AB1E009A0045 /* gravity_semacheck1.c */; };
+		A9506D421E69AB78009A0045 /* gravity_semacheck2.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D101E69AB1E009A0045 /* gravity_semacheck2.c */; };
+		A9506D431E69AB78009A0045 /* gravity_symboltable.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D121E69AB1E009A0045 /* gravity_symboltable.c */; };
+		A9506D441E69AB78009A0045 /* gravity_token.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D141E69AB1E009A0045 /* gravity_token.c */; };
+		A9506D451E69AB78009A0045 /* gravity_visitor.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D161E69AB1E009A0045 /* gravity_visitor.c */; };
+		A9506D461E69AB78009A0045 /* gravity_ast.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D001E69AB1E009A0045 /* gravity_ast.c */; };
+		A9506D471E69AB78009A0045 /* gravity_codegen.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D021E69AB1E009A0045 /* gravity_codegen.c */; };
+		A9506D481E69AB78009A0045 /* gravity_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D041E69AB1E009A0045 /* gravity_compiler.c */; };
+		A9506D491E69AB78009A0045 /* gravity_ircode.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D061E69AB1E009A0045 /* gravity_ircode.c */; };
+		A9506D4A1E69AB78009A0045 /* gravity_lexer.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D081E69AB1E009A0045 /* gravity_lexer.c */; };
+		A9506D4B1E69AB78009A0045 /* gravity_optimizer.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0A1E69AB1E009A0045 /* gravity_optimizer.c */; };
+		A9506D4C1E69AB78009A0045 /* gravity_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0C1E69AB1E009A0045 /* gravity_parser.c */; };
+		A9506D4D1E69AB78009A0045 /* gravity_semacheck1.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D0E1E69AB1E009A0045 /* gravity_semacheck1.c */; };
+		A9506D4E1E69AB78009A0045 /* gravity_semacheck2.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D101E69AB1E009A0045 /* gravity_semacheck2.c */; };
+		A9506D4F1E69AB78009A0045 /* gravity_symboltable.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D121E69AB1E009A0045 /* gravity_symboltable.c */; };
+		A9506D501E69AB78009A0045 /* gravity_token.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D141E69AB1E009A0045 /* gravity_token.c */; };
+		A9506D511E69AB78009A0045 /* gravity_visitor.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D161E69AB1E009A0045 /* gravity_visitor.c */; };
+		A9506D521E69AB7E009A0045 /* gravity_core.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D191E69AB1E009A0045 /* gravity_core.c */; };
+		A9506D531E69AB7E009A0045 /* gravity_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D1B1E69AB1E009A0045 /* gravity_vm.c */; };
+		A9506D541E69AB7E009A0045 /* gravity_core.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D191E69AB1E009A0045 /* gravity_core.c */; };
+		A9506D551E69AB7E009A0045 /* gravity_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D1B1E69AB1E009A0045 /* gravity_vm.c */; };
+		A9506D561E69AB86009A0045 /* gravity_hash.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D211E69AB1E009A0045 /* gravity_hash.c */; };
+		A9506D571E69AB86009A0045 /* gravity_memory.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D241E69AB1E009A0045 /* gravity_memory.c */; };
+		A9506D581E69AB86009A0045 /* gravity_value.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D271E69AB1E009A0045 /* gravity_value.c */; };
+		A9506D591E69AB86009A0045 /* gravity_hash.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D211E69AB1E009A0045 /* gravity_hash.c */; };
+		A9506D5A1E69AB86009A0045 /* gravity_memory.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D241E69AB1E009A0045 /* gravity_memory.c */; };
+		A9506D5B1E69AB86009A0045 /* gravity_value.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D271E69AB1E009A0045 /* gravity_value.c */; };
+		A9506D5C1E69AB8D009A0045 /* gravity_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2A1E69AB1E009A0045 /* gravity_debug.c */; };
+		A9506D5D1E69AB8D009A0045 /* gravity_json.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2C1E69AB1E009A0045 /* gravity_json.c */; };
+		A9506D5E1E69AB8D009A0045 /* gravity_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2E1E69AB1E009A0045 /* gravity_utils.c */; };
+		A9506D5F1E69AB8E009A0045 /* gravity_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2A1E69AB1E009A0045 /* gravity_debug.c */; };
+		A9506D601E69AB8E009A0045 /* gravity_json.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2C1E69AB1E009A0045 /* gravity_json.c */; };
+		A9506D611E69AB8E009A0045 /* gravity_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = A9506D2E1E69AB1E009A0045 /* gravity_utils.c */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		A9506CEE1E69AAEB009A0045 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+		A9506D331E69AB33009A0045 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		A9506CF01E69AAEB009A0045 /* gravity */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = gravity; sourceTree = BUILT_PRODUCTS_DIR; };
+		A9506CFC1E69AB1E009A0045 /* gravity.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity.c; sourceTree = "<group>"; };
+		A9506CFD1E69AB1E009A0045 /* unittest.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = unittest.c; sourceTree = "<group>"; };
+		A9506CFF1E69AB1E009A0045 /* debug_macros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debug_macros.h; sourceTree = "<group>"; };
+		A9506D001E69AB1E009A0045 /* gravity_ast.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_ast.c; sourceTree = "<group>"; };
+		A9506D011E69AB1E009A0045 /* gravity_ast.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_ast.h; sourceTree = "<group>"; };
+		A9506D021E69AB1E009A0045 /* gravity_codegen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_codegen.c; sourceTree = "<group>"; };
+		A9506D031E69AB1E009A0045 /* gravity_codegen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_codegen.h; sourceTree = "<group>"; };
+		A9506D041E69AB1E009A0045 /* gravity_compiler.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_compiler.c; sourceTree = "<group>"; };
+		A9506D051E69AB1E009A0045 /* gravity_compiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_compiler.h; sourceTree = "<group>"; };
+		A9506D061E69AB1E009A0045 /* gravity_ircode.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_ircode.c; sourceTree = "<group>"; };
+		A9506D071E69AB1E009A0045 /* gravity_ircode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_ircode.h; sourceTree = "<group>"; };
+		A9506D081E69AB1E009A0045 /* gravity_lexer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_lexer.c; sourceTree = "<group>"; };
+		A9506D091E69AB1E009A0045 /* gravity_lexer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_lexer.h; sourceTree = "<group>"; };
+		A9506D0A1E69AB1E009A0045 /* gravity_optimizer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_optimizer.c; sourceTree = "<group>"; };
+		A9506D0B1E69AB1E009A0045 /* gravity_optimizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_optimizer.h; sourceTree = "<group>"; };
+		A9506D0C1E69AB1E009A0045 /* gravity_parser.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_parser.c; sourceTree = "<group>"; };
+		A9506D0D1E69AB1E009A0045 /* gravity_parser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_parser.h; sourceTree = "<group>"; };
+		A9506D0E1E69AB1E009A0045 /* gravity_semacheck1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_semacheck1.c; sourceTree = "<group>"; };
+		A9506D0F1E69AB1E009A0045 /* gravity_semacheck1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_semacheck1.h; sourceTree = "<group>"; };
+		A9506D101E69AB1E009A0045 /* gravity_semacheck2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_semacheck2.c; sourceTree = "<group>"; };
+		A9506D111E69AB1E009A0045 /* gravity_semacheck2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_semacheck2.h; sourceTree = "<group>"; };
+		A9506D121E69AB1E009A0045 /* gravity_symboltable.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_symboltable.c; sourceTree = "<group>"; };
+		A9506D131E69AB1E009A0045 /* gravity_symboltable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_symboltable.h; sourceTree = "<group>"; };
+		A9506D141E69AB1E009A0045 /* gravity_token.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_token.c; sourceTree = "<group>"; };
+		A9506D151E69AB1E009A0045 /* gravity_token.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_token.h; sourceTree = "<group>"; };
+		A9506D161E69AB1E009A0045 /* gravity_visitor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_visitor.c; sourceTree = "<group>"; };
+		A9506D171E69AB1E009A0045 /* gravity_visitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_visitor.h; sourceTree = "<group>"; };
+		A9506D191E69AB1E009A0045 /* gravity_core.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_core.c; sourceTree = "<group>"; };
+		A9506D1A1E69AB1E009A0045 /* gravity_core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_core.h; sourceTree = "<group>"; };
+		A9506D1B1E69AB1E009A0045 /* gravity_vm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_vm.c; sourceTree = "<group>"; };
+		A9506D1C1E69AB1E009A0045 /* gravity_vm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_vm.h; sourceTree = "<group>"; };
+		A9506D1D1E69AB1E009A0045 /* gravity_vmmacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_vmmacros.h; sourceTree = "<group>"; };
+		A9506D1F1E69AB1E009A0045 /* gravity_array.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_array.h; sourceTree = "<group>"; };
+		A9506D201E69AB1E009A0045 /* gravity_delegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_delegate.h; sourceTree = "<group>"; };
+		A9506D211E69AB1E009A0045 /* gravity_hash.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_hash.c; sourceTree = "<group>"; };
+		A9506D221E69AB1E009A0045 /* gravity_hash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_hash.h; sourceTree = "<group>"; };
+		A9506D231E69AB1E009A0045 /* gravity_macros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_macros.h; sourceTree = "<group>"; };
+		A9506D241E69AB1E009A0045 /* gravity_memory.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_memory.c; sourceTree = "<group>"; };
+		A9506D251E69AB1E009A0045 /* gravity_memory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_memory.h; sourceTree = "<group>"; };
+		A9506D261E69AB1E009A0045 /* gravity_opcodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_opcodes.h; sourceTree = "<group>"; };
+		A9506D271E69AB1E009A0045 /* gravity_value.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_value.c; sourceTree = "<group>"; };
+		A9506D281E69AB1E009A0045 /* gravity_value.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_value.h; sourceTree = "<group>"; };
+		A9506D2A1E69AB1E009A0045 /* gravity_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_debug.c; sourceTree = "<group>"; };
+		A9506D2B1E69AB1E009A0045 /* gravity_debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_debug.h; sourceTree = "<group>"; };
+		A9506D2C1E69AB1E009A0045 /* gravity_json.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_json.c; sourceTree = "<group>"; };
+		A9506D2D1E69AB1E009A0045 /* gravity_json.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_json.h; sourceTree = "<group>"; };
+		A9506D2E1E69AB1E009A0045 /* gravity_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gravity_utils.c; sourceTree = "<group>"; };
+		A9506D2F1E69AB1E009A0045 /* gravity_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gravity_utils.h; sourceTree = "<group>"; };
+		A9506D371E69AB33009A0045 /* unittest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unittest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		A9506CED1E69AAEB009A0045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A9506D321E69AB33009A0045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		A9506CE71E69AAEB009A0045 = {
+			isa = PBXGroup;
+			children = (
+				A9506CFA1E69AB1E009A0045 /* gravity */,
+				A9506CF11E69AAEB009A0045 /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		A9506CF11E69AAEB009A0045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				A9506CF01E69AAEB009A0045 /* gravity */,
+				A9506D371E69AB33009A0045 /* unittest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		A9506CFA1E69AB1E009A0045 /* gravity */ = {
+			isa = PBXGroup;
+			children = (
+				A9506CFB1E69AB1E009A0045 /* cli */,
+				A9506CFE1E69AB1E009A0045 /* compiler */,
+				A9506D181E69AB1E009A0045 /* runtime */,
+				A9506D1E1E69AB1E009A0045 /* shared */,
+				A9506D291E69AB1E009A0045 /* utils */,
+			);
+			name = gravity;
+			path = src;
+			sourceTree = "<group>";
+		};
+		A9506CFB1E69AB1E009A0045 /* cli */ = {
+			isa = PBXGroup;
+			children = (
+				A9506CFC1E69AB1E009A0045 /* gravity.c */,
+				A9506CFD1E69AB1E009A0045 /* unittest.c */,
+			);
+			path = cli;
+			sourceTree = "<group>";
+		};
+		A9506CFE1E69AB1E009A0045 /* compiler */ = {
+			isa = PBXGroup;
+			children = (
+				A9506CFF1E69AB1E009A0045 /* debug_macros.h */,
+				A9506D001E69AB1E009A0045 /* gravity_ast.c */,
+				A9506D011E69AB1E009A0045 /* gravity_ast.h */,
+				A9506D021E69AB1E009A0045 /* gravity_codegen.c */,
+				A9506D031E69AB1E009A0045 /* gravity_codegen.h */,
+				A9506D041E69AB1E009A0045 /* gravity_compiler.c */,
+				A9506D051E69AB1E009A0045 /* gravity_compiler.h */,
+				A9506D061E69AB1E009A0045 /* gravity_ircode.c */,
+				A9506D071E69AB1E009A0045 /* gravity_ircode.h */,
+				A9506D081E69AB1E009A0045 /* gravity_lexer.c */,
+				A9506D091E69AB1E009A0045 /* gravity_lexer.h */,
+				A9506D0A1E69AB1E009A0045 /* gravity_optimizer.c */,
+				A9506D0B1E69AB1E009A0045 /* gravity_optimizer.h */,
+				A9506D0C1E69AB1E009A0045 /* gravity_parser.c */,
+				A9506D0D1E69AB1E009A0045 /* gravity_parser.h */,
+				A9506D0E1E69AB1E009A0045 /* gravity_semacheck1.c */,
+				A9506D0F1E69AB1E009A0045 /* gravity_semacheck1.h */,
+				A9506D101E69AB1E009A0045 /* gravity_semacheck2.c */,
+				A9506D111E69AB1E009A0045 /* gravity_semacheck2.h */,
+				A9506D121E69AB1E009A0045 /* gravity_symboltable.c */,
+				A9506D131E69AB1E009A0045 /* gravity_symboltable.h */,
+				A9506D141E69AB1E009A0045 /* gravity_token.c */,
+				A9506D151E69AB1E009A0045 /* gravity_token.h */,
+				A9506D161E69AB1E009A0045 /* gravity_visitor.c */,
+				A9506D171E69AB1E009A0045 /* gravity_visitor.h */,
+			);
+			path = compiler;
+			sourceTree = "<group>";
+		};
+		A9506D181E69AB1E009A0045 /* runtime */ = {
+			isa = PBXGroup;
+			children = (
+				A9506D191E69AB1E009A0045 /* gravity_core.c */,
+				A9506D1A1E69AB1E009A0045 /* gravity_core.h */,
+				A9506D1B1E69AB1E009A0045 /* gravity_vm.c */,
+				A9506D1C1E69AB1E009A0045 /* gravity_vm.h */,
+				A9506D1D1E69AB1E009A0045 /* gravity_vmmacros.h */,
+			);
+			path = runtime;
+			sourceTree = "<group>";
+		};
+		A9506D1E1E69AB1E009A0045 /* shared */ = {
+			isa = PBXGroup;
+			children = (
+				A9506D1F1E69AB1E009A0045 /* gravity_array.h */,
+				A9506D201E69AB1E009A0045 /* gravity_delegate.h */,
+				A9506D211E69AB1E009A0045 /* gravity_hash.c */,
+				A9506D221E69AB1E009A0045 /* gravity_hash.h */,
+				A9506D231E69AB1E009A0045 /* gravity_macros.h */,
+				A9506D241E69AB1E009A0045 /* gravity_memory.c */,
+				A9506D251E69AB1E009A0045 /* gravity_memory.h */,
+				A9506D261E69AB1E009A0045 /* gravity_opcodes.h */,
+				A9506D271E69AB1E009A0045 /* gravity_value.c */,
+				A9506D281E69AB1E009A0045 /* gravity_value.h */,
+			);
+			path = shared;
+			sourceTree = "<group>";
+		};
+		A9506D291E69AB1E009A0045 /* utils */ = {
+			isa = PBXGroup;
+			children = (
+				A9506D2A1E69AB1E009A0045 /* gravity_debug.c */,
+				A9506D2B1E69AB1E009A0045 /* gravity_debug.h */,
+				A9506D2C1E69AB1E009A0045 /* gravity_json.c */,
+				A9506D2D1E69AB1E009A0045 /* gravity_json.h */,
+				A9506D2E1E69AB1E009A0045 /* gravity_utils.c */,
+				A9506D2F1E69AB1E009A0045 /* gravity_utils.h */,
+			);
+			path = utils;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		A9506CEF1E69AAEB009A0045 /* gravity */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = A9506CF71E69AAEB009A0045 /* Build configuration list for PBXNativeTarget "gravity" */;
+			buildPhases = (
+				A9506CEC1E69AAEB009A0045 /* Sources */,
+				A9506CED1E69AAEB009A0045 /* Frameworks */,
+				A9506CEE1E69AAEB009A0045 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = gravity;
+			productName = gravity;
+			productReference = A9506CF01E69AAEB009A0045 /* gravity */;
+			productType = "com.apple.product-type.tool";
+		};
+		A9506D301E69AB33009A0045 /* unittest */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = A9506D341E69AB33009A0045 /* Build configuration list for PBXNativeTarget "unittest" */;
+			buildPhases = (
+				A9506D311E69AB33009A0045 /* Sources */,
+				A9506D321E69AB33009A0045 /* Frameworks */,
+				A9506D331E69AB33009A0045 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = unittest;
+			productName = gravity;
+			productReference = A9506D371E69AB33009A0045 /* unittest */;
+			productType = "com.apple.product-type.tool";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		A9506CE81E69AAEB009A0045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0820;
+				ORGANIZATIONNAME = Creolabs;
+				TargetAttributes = {
+					A9506CEF1E69AAEB009A0045 = {
+						CreatedOnToolsVersion = 8.2.1;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = A9506CEB1E69AAEB009A0045 /* Build configuration list for PBXProject "gravity" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = A9506CE71E69AAEB009A0045;
+			productRefGroup = A9506CF11E69AAEB009A0045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				A9506CEF1E69AAEB009A0045 /* gravity */,
+				A9506D301E69AB33009A0045 /* unittest */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+		A9506CEC1E69AAEB009A0045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9506D3D1E69AB78009A0045 /* gravity_ircode.c in Sources */,
+				A9506D3F1E69AB78009A0045 /* gravity_optimizer.c in Sources */,
+				A9506D441E69AB78009A0045 /* gravity_token.c in Sources */,
+				A9506D571E69AB86009A0045 /* gravity_memory.c in Sources */,
+				A9506D581E69AB86009A0045 /* gravity_value.c in Sources */,
+				A9506D3B1E69AB78009A0045 /* gravity_codegen.c in Sources */,
+				A9506D5D1E69AB8D009A0045 /* gravity_json.c in Sources */,
+				A9506D531E69AB7E009A0045 /* gravity_vm.c in Sources */,
+				A9506D411E69AB78009A0045 /* gravity_semacheck1.c in Sources */,
+				A9506D401E69AB78009A0045 /* gravity_parser.c in Sources */,
+				A9506D5C1E69AB8D009A0045 /* gravity_debug.c in Sources */,
+				A9506D381E69AB66009A0045 /* gravity.c in Sources */,
+				A9506D561E69AB86009A0045 /* gravity_hash.c in Sources */,
+				A9506D421E69AB78009A0045 /* gravity_semacheck2.c in Sources */,
+				A9506D3E1E69AB78009A0045 /* gravity_lexer.c in Sources */,
+				A9506D431E69AB78009A0045 /* gravity_symboltable.c in Sources */,
+				A9506D3A1E69AB78009A0045 /* gravity_ast.c in Sources */,
+				A9506D5E1E69AB8D009A0045 /* gravity_utils.c in Sources */,
+				A9506D451E69AB78009A0045 /* gravity_visitor.c in Sources */,
+				A9506D3C1E69AB78009A0045 /* gravity_compiler.c in Sources */,
+				A9506D521E69AB7E009A0045 /* gravity_core.c in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A9506D311E69AB33009A0045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9506D491E69AB78009A0045 /* gravity_ircode.c in Sources */,
+				A9506D4B1E69AB78009A0045 /* gravity_optimizer.c in Sources */,
+				A9506D501E69AB78009A0045 /* gravity_token.c in Sources */,
+				A9506D5A1E69AB86009A0045 /* gravity_memory.c in Sources */,
+				A9506D5B1E69AB86009A0045 /* gravity_value.c in Sources */,
+				A9506D471E69AB78009A0045 /* gravity_codegen.c in Sources */,
+				A9506D601E69AB8E009A0045 /* gravity_json.c in Sources */,
+				A9506D551E69AB7E009A0045 /* gravity_vm.c in Sources */,
+				A9506D4D1E69AB78009A0045 /* gravity_semacheck1.c in Sources */,
+				A9506D4C1E69AB78009A0045 /* gravity_parser.c in Sources */,
+				A9506D5F1E69AB8E009A0045 /* gravity_debug.c in Sources */,
+				A9506D391E69AB6A009A0045 /* unittest.c in Sources */,
+				A9506D591E69AB86009A0045 /* gravity_hash.c in Sources */,
+				A9506D4E1E69AB78009A0045 /* gravity_semacheck2.c in Sources */,
+				A9506D4A1E69AB78009A0045 /* gravity_lexer.c in Sources */,
+				A9506D4F1E69AB78009A0045 /* gravity_symboltable.c in Sources */,
+				A9506D461E69AB78009A0045 /* gravity_ast.c in Sources */,
+				A9506D611E69AB8E009A0045 /* gravity_utils.c in Sources */,
+				A9506D511E69AB78009A0045 /* gravity_visitor.c in Sources */,
+				A9506D481E69AB78009A0045 /* gravity_compiler.c in Sources */,
+				A9506D541E69AB7E009A0045 /* gravity_core.c in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		A9506CF51E69AAEB009A0045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		A9506CF61E69AAEB009A0045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		A9506CF81E69AAEB009A0045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		A9506CF91E69AAEB009A0045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+		A9506D351E69AB33009A0045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		A9506D361E69AB33009A0045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		A9506CEB1E69AAEB009A0045 /* Build configuration list for PBXProject "gravity" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A9506CF51E69AAEB009A0045 /* Debug */,
+				A9506CF61E69AAEB009A0045 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		A9506CF71E69AAEB009A0045 /* Build configuration list for PBXNativeTarget "gravity" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A9506CF81E69AAEB009A0045 /* Debug */,
+				A9506CF91E69AAEB009A0045 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		A9506D341E69AB33009A0045 /* Build configuration list for PBXNativeTarget "unittest" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A9506D351E69AB33009A0045 /* Debug */,
+				A9506D361E69AB33009A0045 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = A9506CE81E69AAEB009A0045 /* Project object */;
+}

+ 7 - 0
gravity.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:gravity.xcodeproj">
+   </FileRef>
+</Workspace>

BIN
gravity.xcodeproj/project.xcworkspace/xcuserdata/marco.xcuserdatad/UserInterfaceState.xcuserstate


+ 91 - 0
gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/gravity.xcscheme

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0820"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "A9506CEF1E69AAEB009A0045"
+               BuildableName = "gravity"
+               BlueprintName = "gravity"
+               ReferencedContainer = "container:gravity.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506CEF1E69AAEB009A0045"
+            BuildableName = "gravity"
+            BlueprintName = "gravity"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506CEF1E69AAEB009A0045"
+            BuildableName = "gravity"
+            BlueprintName = "gravity"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506CEF1E69AAEB009A0045"
+            BuildableName = "gravity"
+            BlueprintName = "gravity"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 91 - 0
gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/unittest.xcscheme

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0820"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "A9506D301E69AB33009A0045"
+               BuildableName = "unittest"
+               BlueprintName = "unittest"
+               ReferencedContainer = "container:gravity.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506D301E69AB33009A0045"
+            BuildableName = "unittest"
+            BlueprintName = "unittest"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506D301E69AB33009A0045"
+            BuildableName = "unittest"
+            BlueprintName = "unittest"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A9506D301E69AB33009A0045"
+            BuildableName = "unittest"
+            BlueprintName = "unittest"
+            ReferencedContainer = "container:gravity.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 32 - 0
gravity.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SchemeUserState</key>
+	<dict>
+		<key>gravity.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+		<key>unittest.xcscheme</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>1</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>A9506CEF1E69AAEB009A0045</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>A9506D301E69AB33009A0045</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 189 - 0
src/cli/gravity.c

@@ -0,0 +1,189 @@
+//
+//  compiler_test.c
+//  gravity
+//
+//  Created by Marco Bambini on 24/03/16.
+//  Copyright © 2016 CreoLabs. All rights reserved.
+//
+
+#include "gravity_compiler.h"
+#include "gravity_core.h"
+#include "gravity_vm.h"
+
+#define DEFAULT_OUTPUT "gravity.json"
+typedef enum  {
+	OP_COMPILE,			// just compile source code and exit
+	OP_RUN,				// just run an already compiled file
+	OP_COMPILE_RUN,		// compile source code and run it
+	OP_REPL				// run a read eval print loop
+} op_type;
+
+static const char *input_file = NULL;
+static const char *output_file = DEFAULT_OUTPUT;
+
+static void report_error (error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) {
+	#pragma unused(xdata)
+	const char *type = "N/A";
+	switch (error_type) {
+		case GRAVITY_ERROR_NONE: type = "NONE"; break;
+		case GRAVITY_ERROR_SYNTAX: type = "SYNTAX"; break;
+		case GRAVITY_ERROR_SEMANTIC: type = "SEMANTIC"; break;
+		case GRAVITY_ERROR_RUNTIME: type = "RUNTIME"; break;
+		case GRAVITY_WARNING: type = "WARNING"; break;
+		case GRAVITY_ERROR_IO: type = "I/O"; break;
+	}
+	
+	if (error_type == GRAVITY_ERROR_RUNTIME) printf("RUNTIME ERROR: ");
+	else printf("%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno);
+	printf("%s\n", message);
+}
+
+static void print_version (void) {
+	printf("Gravity version %s (%s)\n", GRAVITY_VERSION, GRAVITY_BUILD_DATE);
+}
+
+static void print_help (void) {
+	printf("usage: gravity [options]\n");
+	printf("no options means enter interactive mode (not yet supported)\n");
+	printf("Available options are:\n");
+	printf("--version          show version information and exit\n");
+	printf("--help             show command line usage and exit\n");
+	printf("-c input_file      compile input_file (default to gravity.json)\n");
+	printf("-o output_file     specify output file name\n");
+	printf("file_name          compile file_name and executes it\n");
+}
+
+static void gravity_repl (void) {
+	printf("REPL not yet implemented.\n");
+}
+
+static op_type parse_args (int argc, const char* argv[]) {
+	if (argc == 1) return OP_REPL;
+	
+	if (argc == 2 && strcmp(argv[1], "--version") == 0) {
+		print_version();
+		exit(0);
+	}
+	
+	if (argc == 2 && strcmp(argv[1], "--help") == 0) {
+		print_help();
+		exit(0);
+	}
+	
+	if (argc == 2) {
+		input_file = argv[2];
+		return OP_COMPILE_RUN;
+	}
+	
+	op_type type = OP_RUN;
+	for (int i=2; i<argc; ++i) {
+		if ((strcmp(argv[i], "-c") == 0) && (i+1 < argc)) {
+			input_file = argv[++i];
+			type = OP_COMPILE;
+		}
+		if ((strcmp(argv[i], "-o") == 0) && (i+1 < argc)) {
+			output_file = argv[++i];
+		}
+	}
+	
+	if (input_file == NULL) input_file = argv[2];
+	return type;
+}
+
+// MARK: -
+
+int main (int argc, const char* argv[]) {
+	// parse arguments and return operation type
+	op_type type = parse_args(argc, argv);
+	
+	// special repl case
+	if (type == OP_REPL) {
+		gravity_repl();
+		exit(0);
+	}
+	
+	// initialize memory debugger (if activated)
+	mem_init();
+	
+	// closure to execute/serialize
+	gravity_closure_t *closure = NULL;
+	
+	// optional compiler
+	gravity_compiler_t *compiler = NULL;
+	
+	// setup compiler/VM delegate
+	gravity_delegate_t delegate = {.error_callback = report_error};
+	
+	// create VM
+	gravity_vm *vm = gravity_vm_new(&delegate);
+	
+	// check if input file is source code that needs to be compiled
+	if ((type == OP_COMPILE) || (type == OP_COMPILE_RUN)) {
+		size_t size = 0;
+		
+		// load source code
+		const char *source_code = file_read(input_file, &size);
+		if (!source_code) {
+			printf("Error loading file %s", input_file);
+			goto cleanup;
+		}
+		
+		// create compiler
+		compiler = gravity_compiler_create(&delegate);
+		
+		// compile source code into a closure
+		closure = gravity_compiler_run(compiler, source_code, size, 0, false);
+		
+		// check if closure needs to be serialized
+		if (type == OP_COMPILE) {
+			bool result = gravity_compiler_serialize_infile(compiler, closure, output_file);
+			if (!result) {
+				printf("Error serializing file %s\n", output_file);
+				goto cleanup;
+			}
+		}
+		
+		// op is OP_COMPILE_RUN so transfer memory from compiler to VM
+		gravity_compiler_transfer(compiler, vm);
+		
+		// cleanup compiler
+		gravity_compiler_free(compiler);
+		
+	} else if (type == OP_RUN) {
+		// unserialize file
+		closure = gravity_vm_loadfile(vm, input_file);
+		if (!closure) {
+			printf("Error while loading compile file %s\n", input_file);
+			goto cleanup;
+		}
+	}
+	
+	// sanity check
+	assert(closure);
+	
+	if (gravity_vm_run(vm, closure)) {
+		gravity_value_t result = gravity_vm_result(vm);
+		double t = gravity_vm_time(vm);
+		
+		char buffer[512];
+		gravity_value_dump(result, buffer, sizeof(buffer));
+		printf("RESULT: %s (in %.4f ms)\n\n", buffer, t);
+	}
+	
+cleanup:
+	if (compiler) gravity_compiler_free(compiler);
+	if (vm) gravity_vm_free(vm);
+	gravity_core_free();
+	
+	#if GRAVITY_MEMORY_DEBUG
+	size_t current_memory = mem_leaks();
+	if (current_memory != 0) {
+		printf("--> VM leaks: %zu bytes\n", current_memory);
+		mem_stat();
+	} else {
+		printf("\tNo VM leaks found!\n");
+	}
+	#endif
+	
+	return 0;
+}

+ 190 - 0
src/cli/unittest.c

@@ -0,0 +1,190 @@
+//
+//  unittest.c
+//  gravity
+//
+//  Created by Marco Bambini on 23/03/16.
+//  Copyright © 2016 CreoLabs. All rights reserved.
+//
+
+#include "gravity_compiler.h"
+#include "gravity_utils.h"
+#include "gravity_core.h"
+#include "gravity_vm.h"
+
+typedef struct {
+	bool			processed;
+	
+	uint32_t		ncount;
+	uint32_t		nsuccess;
+	uint32_t		nfailure;
+	
+	error_type_t	expected_error;
+	gravity_value_t expected_value;
+	int32_t			expected_row;
+	int32_t			expected_col;
+} test_data;
+
+static void unittest_init (const char *target_file, test_data *data) {
+	#pragma unused(target_file)
+	++data->ncount;
+	data->processed = false;
+}
+
+static void unittest_cleanup (const char *target_file, test_data *data) {
+	#pragma unused(target_file,data)
+}
+
+static void	unittest_callback (error_type_t error_type, const char *description, const char *notes, gravity_value_t value, int32_t row, int32_t col, void *xdata) {
+	test_data *data = (test_data *)xdata;
+	data->expected_error = error_type;
+	data->expected_value = value;
+	data->expected_row = row;
+	data->expected_col = col;
+	
+	if (notes) printf("\tNOTE: %s\n", notes);
+	printf("\t%s\n", description);
+}
+
+// MARK: -
+
+static void callback_error (error_type_t error_type, const char *message, error_desc_t error_desc, void *xdata) {
+	test_data *data = (test_data *)xdata;
+	
+	if (data->processed == true) return; // ignore 2nd error
+	data->processed = true;
+	
+	const char *type = "NONE";
+	if (error_type == GRAVITY_ERROR_SYNTAX) type = "SYNTAX";
+	else if (error_type == GRAVITY_ERROR_SEMANTIC) type = "SEMANTIC";
+	else if (error_type == GRAVITY_ERROR_RUNTIME) type = "RUNTIME";
+	else if (error_type == GRAVITY_WARNING) type = "WARNING";
+	
+	if (error_type == GRAVITY_ERROR_RUNTIME) printf("\tRUNTIME ERROR: ");
+	else printf("\t%s ERROR on %d (%d,%d): ", type, error_desc.fileid, error_desc.lineno, error_desc.colno);
+	printf("%s\n", message);
+	
+	bool same_error = (data->expected_error == error_type);
+	bool same_row = (data->expected_row != -1) ? (data->expected_row == error_desc.lineno) : true;
+	bool same_col = (data->expected_col != -1) ? (data->expected_col == error_desc.colno) : true;
+	
+	if (same_error && same_row && same_col) {
+		++data->nsuccess;
+		printf("\tSUCCESS\n");
+	} else {
+		++data->nfailure;
+		printf("\tFAILURE\n");
+	}
+}
+
+static const char *callback_read (const char *path, size_t *size, uint32_t *fileid, void *xdata) {
+	#pragma unused(fileid,xdata)
+	return file_read(path, size);
+}
+
+static void test_folder (const char *folder_path, test_data *data) {
+	DIRREF dir = directory_init(folder_path);
+	if (!dir) return;
+	
+	const char *target_file;
+	while ((target_file = directory_read(dir))) {
+		// if file is a folder then start recursion
+		const char *full_path = file_buildpath(target_file, folder_path);
+		if (is_directory(full_path)) {
+			// skip disabled folder
+			if (strcmp(target_file, "disabled") == 0) continue;
+			
+			test_folder(full_path, data);
+			continue;
+		}
+		
+		// load source code
+		size_t size = 0;
+		const char *source_code = file_read(full_path, &size);
+		assert(source_code);
+		
+		// start unit test
+		unittest_init(target_file, data);
+		
+		// compile and run source code
+		printf("\n%d\tTest file: %s\n", data->ncount, target_file);
+		printf("\tTest path: %s\n", full_path);
+		mem_free(full_path);
+		
+		// initialize compiler and delegates
+		gravity_delegate_t delegate = {
+			.xdata = (void *)data,
+			.error_callback = callback_error,
+			.unittest_callback = unittest_callback,
+			.loadfile_callback = callback_read
+		};
+		
+		gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
+		gravity_closure_t *closure = gravity_compiler_run(compiler, source_code, size, 0, false);
+		gravity_vm *vm = gravity_vm_new(&delegate);
+		gravity_compiler_transfer(compiler, vm);
+		gravity_compiler_free(compiler);
+		
+		if (closure) {
+			if (gravity_vm_run(vm, closure)) {
+				data->processed = true;
+				gravity_value_t result = gravity_vm_result(vm);
+				if (gravity_value_equals(result, data->expected_value)) {
+					++data->nsuccess;
+					printf("\tSUCCESS\n");
+				} else {
+					++data->nfailure;
+					printf("\tFAILURE\n");
+				}
+				gravity_value_free(NULL, data->expected_value);
+			}
+		}
+		gravity_vm_free(vm);
+		
+		// case for empty files or simple declarations test
+		if (!data->processed) {
+			++data->nsuccess;
+			printf("\tSUCCESS\n");
+		}
+		
+		// cleanup unitest
+		unittest_cleanup(target_file, data);
+	}
+}
+
+int main (int argc, const char* argv[]) {
+	test_data data = {
+		.ncount = 0,
+		.nsuccess = 0,
+		.nfailure = 0
+	};
+	
+	if (argc != 2) {
+		printf("Usage: unittest /path/to/unitest/\n");
+		return 0;
+	}
+	
+	// print console header
+	printf("==============================================\n");
+	printf("Gravity UnitTest\n");
+	printf("Gravity version %s\n", GRAVITY_VERSION);
+	printf("Build date: %s\n", GRAVITY_BUILD_DATE);
+	printf("==============================================\n");
+	
+	mem_init();
+	nanotime_t tstart = nanotime();
+	test_folder(argv[1], &data);
+	nanotime_t tend = nanotime();
+	
+	double result = ((double)((data.nsuccess * 100)) / (double)data.ncount);
+	printf("\n\n");
+	printf("==============================================\n");
+	printf("Total Tests: %d\n", data.ncount);
+	printf("Total Successes: %d\n", data.nsuccess);
+	printf("Total Failures: %d\n", data.nfailure);
+	printf("Result: %.2f %%\n", result);
+	printf("Time: %.4f ms\n", millitime(tstart, tend));
+	printf("==============================================\n");
+	printf("\n");
+	
+	return 0;
+}

+ 119 - 0
src/compiler/debug_macros.h

@@ -0,0 +1,119 @@
+//
+//  debug_macros.h
+//  gravity
+//
+//  Created by Marco Bambini on 30/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_CMACROS__
+#define __GRAVITY_CMACROS__
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h> 
+#include <assert.h>
+#include <unistd.h>
+#include <limits.h>
+#include <float.h>
+#include <math.h>
+#include "gravity_memory.h"
+
+#define GRAVITY_LEXEM_DEBUG			0
+#define GRAVITY_LEXER_DEGUB			0
+#define GRAVITY_PARSER_DEBUG		0
+#define GRAVITY_SEMANTIC_DEBUG		0
+#define GRAVITY_AST_DEBUG			0
+#define GRAVITY_LOOKUP_DEBUG		0
+#define GRAVITY_SYMTABLE_DEBUG		0
+#define GRAVITY_CODEGEN_DEBUG		0
+#define GRAVITY_OPCODE_DEBUG		0
+#define GRAVITY_BYTECODE_DEBUG		0
+#define GRAVITY_REGISTER_DEBUG		0
+#define GRAVITY_FREE_DEBUG			0
+#define GRAVITY_DESERIALIZE_DEBUG	0
+
+#define PRINT_LINE(...)				printf(__VA_ARGS__);printf("\n");fflush(stdout)
+
+#if GRAVITY_LEXER_DEGUB
+#define DEBUG_LEXER(l)				gravity_lexer_debug(l)
+#else
+#define DEBUG_LEXER(...)
+#endif
+
+#if GRAVITY_LEXEM_DEBUG
+#define DEBUG_LEXEM(...)			do { if (!lexer->peeking) { \
+										printf("(%03d, %03d, %02d) ", lexer->token.lineno, lexer->token.colno, lexer->token.position); \
+										PRINT_LINE(__VA_ARGS__);} \
+									} while(0)
+#else
+#define DEBUG_LEXEM(...)
+#endif
+
+#if GRAVITY_PARSER_DEBUG
+#define DEBUG_PARSER(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define gravity_parser_debug(p)
+#define DEBUG_PARSER(...)
+#endif
+
+#if GRAVITY_SEMANTIC_DEBUG
+#define DEBUG_SEMANTIC(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_SEMANTIC(...)
+#endif
+
+#if GRAVITY_LOOKUP_DEBUG
+#define DEBUG_LOOKUP(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_LOOKUP(...)
+#endif
+
+#if GRAVITY_SYMTABLE_DEBUG
+#define DEBUG_SYMTABLE(...)			printf("%*s",ident*4," ");PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_SYMTABLE(...)
+#endif
+
+#if GRAVITY_CODEGEN_DEBUG
+#define DEBUG_CODEGEN(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_CODEGEN(...)
+#endif
+
+#if GRAVITY_OPCODE_DEBUG
+#define DEBUG_OPCODE(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_OPCODE(...)
+#endif
+
+#if GRAVITY_BYTECODE_DEBUG
+#define DEBUG_BYTECODE(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_BYTECODE(...)
+#endif
+
+#if GRAVITY_REGISTER_DEBUG
+#define DEBUG_REGISTER(...)			PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_REGISTER(...)
+#endif
+
+#if GRAVITY_FREE_DEBUG
+#define DEBUG_FREE(...)				PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_FREE(...)
+#endif
+
+#if GRAVITY_DESERIALIZE_DEBUG
+#define DEBUG_DESERIALIZE(...)		PRINT_LINE(__VA_ARGS__)
+#else
+#define DEBUG_DESERIALIZE(...)
+#endif
+
+#define DEBUG_ALWAYS(...)			PRINT_LINE(__VA_ARGS__)
+
+#endif

+ 730 - 0
src/compiler/gravity_ast.c

@@ -0,0 +1,730 @@
+//
+//  gravity_ast.c
+//  gravity
+//
+//  Created by Marco Bambini on 02/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_ast.h"
+#include "gravity_utils.h"
+#include "gravity_visitor.h"
+#include "gravity_symboltable.h"
+
+#define SETBASE(node, tagv, _tok)		node->base.tag = tagv;			\
+										node->base.token = _tok;
+#define CHECK_REFCOUNT(_node)			if (_node->base.refcount > 0) {--_node->base.refcount; return;}
+
+// MARK: -
+
+void_r *void_array_create (void) {
+	void_r *r = mem_alloc(sizeof(void_r));
+	marray_init(*r);
+	return r;
+}
+
+cstring_r *cstring_array_create (void) {
+	cstring_r *r = mem_alloc(sizeof(cstring_r));
+	gnode_array_init(r);
+	return r;
+}
+
+gnode_r *gnode_array_create (void) {
+	gnode_r *r = mem_alloc(sizeof(gnode_r));
+	gnode_array_init(r);
+	return r;
+}
+
+void gnode_array_sethead(gnode_r *list, gnode_t *node) {
+	// get old size
+	size_t list_size = gnode_array_size(list);
+	
+	// push node at the end to trigger memory allocation (if needed)
+	gnode_array_push(list, node);
+	
+	// shift elements in array
+	for (size_t i=list_size; i>0; --i) {
+		list->p[i] = list->p[i-1];
+	}
+	
+	// set new array head
+	list->p[0] = node;
+}
+
+gnode_r *gnode_array_remove_byindex(gnode_r *old_list, size_t index) {
+	// get old size
+	size_t list_size = gnode_array_size(old_list);
+	if (index >= list_size) return NULL;
+	
+	gnode_r *new_list = gnode_array_create();
+	for (size_t i=0; i<list_size; ++i) {
+		if (i == index) continue;
+		gnode_t *node = gnode_array_get(old_list, i);
+		gnode_array_push(new_list, node);
+	}
+	gnode_array_free(old_list);
+	return new_list;
+}
+
+gupvalue_t *gnode_function_add_upvalue(gnode_function_decl_t *f, gnode_var_t *symbol, uint16_t n) {
+	// create uplist if necessary
+	if (!f->uplist) {
+		f->uplist = mem_alloc(sizeof(gupvalue_r));
+		gnode_array_init(f->uplist);
+	}
+	
+	// lookup symbol in uplist (if any)
+	gtype_array_each(f->uplist, {
+		// symbol already found in uplist so return its index
+		gnode_var_t *node = (gnode_var_t *)val->node;
+		if (strcmp(node->identifier, symbol->identifier) == 0) return val;
+	}, gupvalue_t *);
+	
+	// symbol not found in uplist so add it
+	gupvalue_t *upvalue = mem_alloc(sizeof(gupvalue_t));
+	upvalue->node = (gnode_t *)symbol;
+	upvalue->index = (n == 1) ? symbol->index : (uint32_t)gnode_array_size(f->uplist);
+	upvalue->selfindex = (uint32_t)gnode_array_size(f->uplist);
+	upvalue->is_direct = (n == 1);
+	marray_push(gupvalue_t*, *f->uplist, upvalue);
+	
+	// return symbol position in uplist
+	return upvalue;
+}
+
+// MARK: - Statements initializers -
+
+gnode_t *gnode_jump_stat_create (gtoken_s token, gnode_t *expr) {
+	gnode_jump_stmt_t *node = (gnode_jump_stmt_t *)mem_alloc(sizeof(gnode_jump_stmt_t));
+	
+	SETBASE(node, NODE_JUMP_STAT, token);
+	node->expr = expr;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_label_stat_create (gtoken_s token, gnode_t *expr, gnode_t *stmt) {
+	gnode_label_stmt_t *node = (gnode_label_stmt_t *)mem_alloc(sizeof(gnode_label_stmt_t));
+	
+	SETBASE(node, NODE_LABEL_STAT, token);
+	node->expr = expr;
+	node->stmt = stmt;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_flow_stat_create (gtoken_s token, gnode_t *cond, gnode_t *stmt1, gnode_t *stmt2) {
+	gnode_flow_stmt_t *node = (gnode_flow_stmt_t *)mem_alloc(sizeof(gnode_flow_stmt_t));
+	
+	SETBASE(node, NODE_FLOW_STAT, token);
+	node->cond = cond;
+	node->stmt = stmt1;
+	node->elsestmt = stmt2;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_loop_stat_create (gtoken_s token, gnode_t *cond, gnode_t *stmt, gnode_t *expr) {
+	gnode_loop_stmt_t *node = (gnode_loop_stmt_t *)mem_alloc(sizeof(gnode_loop_stmt_t));
+	
+	SETBASE(node, NODE_LOOP_STAT, token);
+	node->cond = cond;
+	node->stmt = stmt;
+	node->expr = expr;
+	node->nclose = UINT32_MAX;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_block_stat_create (gnode_n type, gtoken_s token, gnode_r *stmts) {
+	gnode_compound_stmt_t *node = (gnode_compound_stmt_t *)mem_alloc(sizeof(gnode_compound_stmt_t));
+	
+	SETBASE(node, type, token);
+	node->stmts = stmts;
+	node->nclose = UINT32_MAX;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_empty_stat_create (gtoken_s token) {
+	gnode_empty_stmt_t *node = (gnode_empty_stmt_t *)mem_alloc(sizeof(gnode_empty_stmt_t));
+	
+	SETBASE(node, NODE_EMPTY_STAT, token);
+	return (gnode_t *)node;
+}
+
+// MARK: - Declarations initializers -
+
+gnode_t *gnode_class_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_t *superclass, gnode_r *protocols, gnode_r *declarations, bool is_struct) {
+	gnode_class_decl_t *node = (gnode_class_decl_t *)mem_alloc(sizeof(gnode_class_decl_t));
+	node->is_struct = is_struct;
+	
+	// before class creation, iterate declarations and set proper access specifiers
+	// default is PUBLIC but if IDENTIFIER begins with _ then set it to PRIVATE
+	gnode_array_each(declarations, {
+		if (val->tag == NODE_VARIABLE_DECL) {
+			// default access specifier for variables is TOK_KEY_PUBLIC
+			gnode_variable_decl_t *vdec_node = (gnode_variable_decl_t *)val;
+			bool is_private = ((gnode_array_size(vdec_node->decls) > 0) && (((gnode_var_t *)gnode_array_get(vdec_node->decls, 0))->identifier[0] == '_'));
+			if (vdec_node->access == 0) vdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
+		} else if (val->tag == NODE_FUNCTION_DECL) {
+			// default access specifier for functions is PUBLIC
+			gnode_function_decl_t *fdec_node = (gnode_function_decl_t *)val;
+			if (!fdec_node->identifier) continue;
+			bool is_private = (fdec_node->identifier[0] == '_');
+			if (fdec_node->access == 0) fdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
+		} else if (val->tag == NODE_CLASS_DECL) {
+			// default access specifier for inner class declarations is PUBLIC
+			gnode_class_decl_t *cdec_node = (gnode_class_decl_t *)val;
+			if (!cdec_node->identifier) continue;
+			bool is_private = (cdec_node->identifier[0] == '_');
+			if (cdec_node->access == 0) cdec_node->access = (is_private) ? TOK_KEY_PRIVATE : TOK_KEY_PUBLIC;
+		}
+	});
+	
+	
+	SETBASE(node, NODE_CLASS_DECL, token);
+	node->bridge = false;
+	node->identifier = identifier;
+	node->access = access_specifier;
+	node->storage = storage_specifier;
+	node->superclass = superclass;
+	node->protocols = protocols;
+	node->decls = declarations;
+	node->nivar = 0;
+	node->nsvar = 0;
+	
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_module_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *declarations) {
+	gnode_module_decl_t *node = (gnode_module_decl_t *)mem_alloc(sizeof(gnode_module_decl_t));
+	
+	SETBASE(node, NODE_MODULE_DECL, token);
+	node->identifier = identifier;
+	node->access = access_specifier;
+	node->storage = storage_specifier;
+	node->decls = declarations;
+	
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_enum_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, symboltable_t *symtable) {
+	gnode_enum_decl_t *node = (gnode_enum_decl_t *)mem_alloc(sizeof(gnode_enum_decl_t));
+	
+	SETBASE(node, NODE_ENUM_DECL, token);
+	node->identifier = identifier;
+	node->access = access_specifier;
+	node->storage = storage_specifier;
+	node->symtable= symtable;
+	
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_function_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *params, gnode_compound_stmt_t *block) {
+	gnode_function_decl_t *node = (gnode_function_decl_t *)mem_alloc(sizeof(gnode_function_decl_t));
+	
+	SETBASE(node, NODE_FUNCTION_DECL, token);
+	node->identifier = identifier;
+	node->access = access_specifier;
+	node->storage = storage_specifier;
+	node->params = params;
+	node->block = block;
+	node->nlocals = 0;
+	node->uplist = NULL;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_variable_decl_create (gtoken_s token, gtoken_t type, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *declarations) {
+	gnode_variable_decl_t *node = (gnode_variable_decl_t *)mem_alloc(sizeof(gnode_variable_decl_t));
+	
+	SETBASE(node, NODE_VARIABLE_DECL, token);
+	node->type = type;
+	node->access = access_specifier;
+	node->storage = storage_specifier;
+	node->decls = declarations;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_variable_create (gtoken_s token, const char *identifier, const char *annotation_type, gtoken_t access_specifier, gnode_t *expr) {
+	gnode_var_t *node = (gnode_var_t *)mem_alloc(sizeof(gnode_var_t));
+	
+	SETBASE(node, NODE_VARIABLE, token);
+	node->identifier = identifier;
+	node->annotation_type = annotation_type;
+	node->expr = expr;
+	node->access = access_specifier;
+	return (gnode_t *)node;
+}
+
+// MARK: - Expressions initializers -
+
+bool gnode_is_equal (gnode_t *node1, gnode_t *node2) {
+	// very simple gnode verification for map key uniqueness
+	gnode_base_t *_node1 = (gnode_base_t *)node1;
+	gnode_base_t *_node2 = (gnode_base_t *)node2;
+	if (_node1->base.tag != _node2->base.tag) return false;
+	if (gnode_is_literal(node1)) {
+		gnode_literal_expr_t *e1 = (gnode_literal_expr_t *)node1;
+		gnode_literal_expr_t *e2 = (gnode_literal_expr_t *)node2;
+		if (e1->type != e2->type) return false;
+		// LITERAL_STRING, LITERAL_FLOAT, LITERAL_INT, LITERAL_BOOL
+		if (e1->type == LITERAL_BOOL) return (e1->value.n64 == e1->value.n64);
+		if (e1->type == LITERAL_INT) return (e1->value.n64 == e1->value.n64);
+		if (e1->type == LITERAL_FLOAT) return (e1->value.d == e1->value.d);
+		if (e1->type == LITERAL_STRING) return (strcmp(e1->value.str, e2->value.str)==0);
+	}
+	return false;
+}
+
+bool gnode_is_expression (gnode_t *node) {
+	gnode_base_t *_node = (gnode_base_t *)node;
+	return ((_node->base.tag >= NODE_BINARY_EXPR) && (_node->base.tag <= NODE_KEYWORD_EXPR));
+}
+
+bool gnode_is_literal (gnode_t *node) {
+	gnode_base_t *_node = (gnode_base_t *)node;
+	return (_node->base.tag == NODE_LITERAL_EXPR);
+}
+
+bool gnode_is_literal_int (gnode_t *node) {
+	if (gnode_is_literal(node) == false) return false;
+	gnode_literal_expr_t *_node = (gnode_literal_expr_t *)node;
+	return (_node->type == LITERAL_INT);
+}
+
+bool gnode_is_literal_string (gnode_t *node) {
+	if (gnode_is_literal(node) == false) return false;
+	gnode_literal_expr_t *_node = (gnode_literal_expr_t *)node;
+	return (_node->type == LITERAL_STRING);
+}
+
+bool gnode_is_literal_number (gnode_t *node) {
+	if (gnode_is_literal(node) == false) return false;
+	gnode_literal_expr_t *_node = (gnode_literal_expr_t *)node;
+	return (_node->type != LITERAL_STRING);
+}
+
+gnode_t *gnode_binary_expr_create (gtoken_t op, gnode_t *left, gnode_t *right) {
+	if (!left || !right) return NULL;
+	
+	gnode_binary_expr_t	*node = (gnode_binary_expr_t *)mem_alloc(sizeof(gnode_binary_expr_t));
+	SETBASE(node, NODE_BINARY_EXPR, left->token);
+	node->op = op;
+	node->left = left;
+	node->right = right;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_unary_expr_create (gtoken_t op, gnode_t *expr) {
+	if (!expr) return NULL;
+	
+	gnode_unary_expr_t *node = (gnode_unary_expr_t *)mem_alloc(sizeof(gnode_unary_expr_t));
+	SETBASE(node, NODE_UNARY_EXPR, expr->token);
+	node->op = op;
+	node->expr = expr;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_file_expr_create (gtoken_s token, cstring_r *list) {
+	if (!list) return NULL;
+	
+	gnode_file_expr_t *node = (gnode_file_expr_t *)mem_alloc(sizeof(gnode_file_expr_t));
+	SETBASE(node, NODE_FILE_EXPR, token);
+	node->identifiers = list;
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_identifier_expr_create (gtoken_s token, const char *identifier, const char *identifier2) {
+	if (!identifier) return NULL;
+	
+	gnode_identifier_expr_t *node = (gnode_identifier_expr_t *)mem_alloc(sizeof(gnode_identifier_expr_t));
+	SETBASE(node, NODE_IDENTIFIER_EXPR, token);
+	node->value = identifier;
+	node->value2 = identifier2;
+	return (gnode_t *)node;
+}
+
+void gnode_literal_dump (gnode_literal_expr_t *node, char *buffer, int buffersize) {
+	switch (node->type) {
+		case LITERAL_STRING: snprintf(buffer, buffersize, "STRING: %.*s", node->len, node->value.str); break;
+		case LITERAL_FLOAT: snprintf(buffer, buffersize, "FLOAT: %.2f", node->value.d); break;
+		case LITERAL_INT: snprintf(buffer, buffersize, "INT: %lld", (int64_t)node->value.n64); break;
+		case LITERAL_BOOL: snprintf(buffer, buffersize, "BOOL: %d", (int32_t)node->value.n64); break;
+		default: assert(0); // should never reach this point
+	}
+}
+
+static gnode_t *gnode_literal_value_expr_create (gtoken_s token, gliteral_t type, const char *s, double d, int64_t n64) {
+	gnode_literal_expr_t *node = (gnode_literal_expr_t *)mem_alloc(sizeof(gnode_literal_expr_t));
+	
+	SETBASE(node, NODE_LITERAL_EXPR, token);
+	node->type = type;
+	node->len = 0;
+	
+	switch (type) {
+		case LITERAL_STRING: node->value.str = (char *)s; break;
+		case LITERAL_FLOAT: node->value.d = d; node->len = (d < FLT_MAX) ? 32 : 64; break;
+		case LITERAL_INT: node->value.n64 = n64; node->len = (n64 < 2147483647) ? 32 : 64; break;
+		case LITERAL_BOOL: node->value.n64 = n64; node->len = 32; break;
+		default: assert(0); // should never reach this point
+	}
+	
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_literal_string_expr_create (gtoken_s token, const char *s, uint32_t len) {
+	gnode_literal_expr_t *node = (gnode_literal_expr_t *)gnode_literal_value_expr_create(token, LITERAL_STRING, NULL, 0, 0);
+	
+	node->len = len;
+	node->value.str = (char *)mem_alloc(len+1);
+	
+	if (token.escaped) {
+		node->value.str = string_unescape(s, &len, node->value.str);
+		node->len = len;
+	} else {
+		memcpy((void *)node->value.str, (const void *)s, len);
+	}
+	
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_literal_float_expr_create (gtoken_s token, double d) {
+	return gnode_literal_value_expr_create(token, LITERAL_FLOAT, NULL, d, 0);
+}
+
+gnode_t *gnode_literal_int_expr_create (gtoken_s token, int64_t n) {
+	return gnode_literal_value_expr_create(token, LITERAL_INT, NULL, 0, n);
+}
+
+gnode_t *gnode_literal_bool_expr_create (gtoken_s token, int32_t n) {
+	return gnode_literal_value_expr_create(token, LITERAL_BOOL, NULL, 0, n);
+}
+
+gnode_t *gnode_keyword_expr_create (gtoken_s token) {
+	gnode_keyword_expr_t *node = (gnode_keyword_expr_t *)mem_alloc(sizeof(gnode_keyword_expr_t));
+	
+	SETBASE(node, NODE_KEYWORD_EXPR, token);
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_postfix_subexpr_create (gtoken_s token, gnode_n type, gnode_t *expr, gnode_r *list) {
+	gnode_postfix_subexpr_t *node = (gnode_postfix_subexpr_t *)mem_alloc(sizeof(gnode_postfix_subexpr_t));
+	
+	if (type == NODE_CALL_EXPR)
+		node->args = list;
+	else
+		node->expr = expr;
+	
+	SETBASE(node, type, token);
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_postfix_expr_create (gtoken_s token, gnode_t *id, gnode_r *list) {
+	gnode_postfix_expr_t *node = (gnode_postfix_expr_t *)mem_alloc(sizeof(gnode_postfix_expr_t));
+	
+	node->id = id;
+	node->list = list;
+	
+	SETBASE(node, NODE_POSTFIX_EXPR, token);
+	return (gnode_t *)node;
+}
+
+gnode_t *gnode_list_expr_create (gtoken_s token, gnode_r *list1, gnode_r *list2, bool ismap) {
+	gnode_list_expr_t *node = (gnode_list_expr_t *)mem_alloc(sizeof(gnode_list_expr_t));
+	
+	SETBASE(node, NODE_LIST_EXPR, token);
+	node->ismap = ismap;
+	node->list1 = list1;
+	node->list2 = list2;
+	return (gnode_t *)node;
+}
+
+// MARK: -
+
+gnode_t *gnode_duplicate (gnode_t *node, bool deep) {
+	if (deep == true) {
+		// deep is true so I need to examine node and perform a real duplication (only of the outer nodes)
+		// deep is true ONLY when node can also be part of an assignment and its assignment flag can be
+		// true is node is on the left and false when node is on the right
+		// true flag is used only by adjust_assignment_expression in parser.c
+		
+		// node can be: identifier, file or postfix
+		if (NODE_ISA(node, NODE_IDENTIFIER_EXPR)) {
+			gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)node;
+			return gnode_identifier_expr_create(expr->base.token, string_dup(expr->value), (expr->value2) ? string_dup(expr->value2) : NULL);
+		} else if (NODE_ISA(node, NODE_FILE_EXPR)) {
+			gnode_file_expr_t *expr = (gnode_file_expr_t *)node;
+			cstring_r *list = cstring_array_create();
+			size_t count = gnode_array_size(expr->identifiers);
+			for (size_t i=0; i<count; ++i) {
+				const char *identifier = gnode_array_get(expr->identifiers, i);
+				cstring_array_push(list, string_dup(identifier));
+			}
+			return gnode_file_expr_create(expr->base.token, list);
+		} else if (NODE_ISA(node, NODE_POSTFIX_EXPR)) {
+			gnode_postfix_expr_t *expr = (gnode_postfix_expr_t *)node;
+			gnode_t *id = gnode_duplicate(expr->id, false);
+			gnode_r *list = gnode_array_create();
+			gnode_array_each(expr->list, {gnode_array_push(list, gnode_duplicate(val, false));});
+			return gnode_postfix_expr_create(expr->base.token, id, list);
+		} else {
+			printf("gnode_duplicate UNHANDLED case\n");
+			assert(0); // should never reach this point
+		}
+		// just return the original node and since it is invalid for an assignment a semantic error will be generated
+	}
+
+	// it means that I can perform a light duplication where
+	// duplicating a node means increase its refcount so it isn't freed more than once
+	++node->refcount;
+	return node;
+}
+
+// MARK: - AST deallocator -
+
+// STATEMENTS
+static void free_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	gnode_array_each(node->stmts, {visit(val);});
+	gnode_array_free(node->stmts);
+	
+	if (node->symtable) symboltable_free(node->symtable);
+	mem_free((gnode_t*)node);
+}
+
+static void free_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	gnode_array_each(node->stmts, {visit(val);});
+	gnode_array_free(node->stmts);
+	
+	if (node->symtable) symboltable_free(node->symtable);
+	mem_free((gnode_t*)node);
+}
+
+static void free_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->expr) visit(node->expr);
+	if (node->stmt) visit(node->stmt);
+	mem_free((gnode_t*)node);
+}
+
+static void free_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->cond) visit(node->cond);
+	if (node->stmt) visit(node->stmt);
+	if (node->elsestmt) visit(node->elsestmt);
+	mem_free((gnode_t*)node);
+}
+
+static void free_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->stmt) visit(node->stmt);
+	if (node->cond) visit(node->cond);
+	if (node->expr) visit(node->expr);
+	mem_free((gnode_t*)node);
+}
+
+static void free_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->expr) visit(node->expr);
+	mem_free((gnode_t*)node);
+}
+
+static void free_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
+	#pragma unused(self)
+	CHECK_REFCOUNT(node);
+	mem_free((gnode_t*)node);
+}
+
+static void free_variable (gvisitor_t *self, gnode_var_t *p) {
+	CHECK_REFCOUNT(p);
+	if (p->identifier) mem_free((void *)p->identifier);
+	if (p->annotation_type) mem_free((void *)p->annotation_type);
+	if (p->expr) visit(p->expr);
+	mem_free((void *)p);
+}
+
+static void free_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->symtable) symboltable_free(node->symtable);
+	if (node->identifier) mem_free((void *)node->identifier);
+	if (node->params) {
+		gnode_array_each(node->params, {free_variable(self, (gnode_var_t *)val);});
+		gnode_array_free(node->params);
+	}
+	
+	if (node->block) visit((gnode_t *)node->block);
+	if (node->uplist) {
+		gtype_array_each(node->uplist, {mem_free(val);}, gupvalue_t*);
+		gnode_array_free(node->uplist);
+	}
+	mem_free((gnode_t*)node);
+}
+
+static void free_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->decls) {
+		gnode_array_each(node->decls, {free_variable(self, (gnode_var_t *)val);});
+		gnode_array_free(node->decls);
+	}
+	mem_free((gnode_t*)node);
+}
+
+static void free_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
+	#pragma unused(self)
+	CHECK_REFCOUNT(node);
+	if (node->identifier) mem_free((void *)node->identifier);
+	if (node->symtable) symboltable_free(node->symtable);
+	mem_free((gnode_t*)node);
+}
+
+static void free_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->identifier) mem_free((void *)node->identifier);
+	if (node->decls) {
+		gnode_array_each(node->decls, {visit(val);});
+		gnode_array_free(node->decls);
+	}
+	
+	if (node->symtable) symboltable_free(node->symtable);
+	mem_free((gnode_t*)node);
+}
+
+static void free_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->identifier) mem_free((void *)node->identifier);
+	if (node->decls) {
+		gnode_array_each(node->decls, {visit(val);});
+		gnode_array_free(node->decls);
+	}
+	
+	if (node->symtable) symboltable_free(node->symtable);
+	mem_free((gnode_t*)node);
+}
+
+static void free_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->left) visit(node->left);
+	if (node->right) visit(node->right);
+	mem_free((gnode_t*)node);
+}
+
+static void free_unary_expr (gvisitor_t *self, gnode_unary_expr_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->expr) visit(node->expr);
+	mem_free((gnode_t*)node);
+}
+
+static void free_postfix_subexpr (gvisitor_t *self, gnode_postfix_subexpr_t *subnode) {
+	CHECK_REFCOUNT(subnode);
+	
+	gnode_n tag = subnode->base.tag;
+	if (tag == NODE_CALL_EXPR) {
+		if (subnode->args) {
+			gnode_array_each(subnode->args, visit(val););
+			gnode_array_free(subnode->args);
+		}
+	} else {
+		visit(subnode->expr);
+	}
+	
+	mem_free((gnode_t*)subnode);
+}
+
+static void free_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
+	CHECK_REFCOUNT(node);
+	
+	visit(node->id);
+	
+	// node->list can be NULL due to enum static conversion
+	size_t count = gnode_array_size(node->list);
+	for (size_t i=0; i<count; ++i) {
+		gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *) gnode_array_get(node->list, i);
+		free_postfix_subexpr(self, subnode);
+	}
+	if (node->list) gnode_array_free(node->list);
+	mem_free((gnode_t*)node);
+}
+
+static void free_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
+	#pragma unused(self)
+	CHECK_REFCOUNT(node);
+	cstring_array_each(node->identifiers, {
+		mem_free((void *)val);
+	});
+	
+	gnode_array_free(node->identifiers);
+	mem_free((void *)node);
+}
+
+static void free_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {
+	#pragma unused(self)
+	CHECK_REFCOUNT(node);
+	if (node->type == LITERAL_STRING) mem_free((void *)node->value.str);
+	mem_free((void *)node);
+}
+
+static void free_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *node) {
+	#pragma unused(self, node)
+	CHECK_REFCOUNT(node);
+	if (node->value) mem_free((void *)node->value);
+	if (node->value2) mem_free((void *)node->value2);
+	mem_free((void *)node);
+}
+
+static void free_keyword_expr (gvisitor_t *self, gnode_keyword_expr_t *node) {
+	#pragma unused(self)
+	CHECK_REFCOUNT(node);
+	mem_free((void *)node);
+}
+
+static void free_list_expr (gvisitor_t *self, gnode_list_expr_t *node) {
+	CHECK_REFCOUNT(node);
+	if (node->list1) {
+		gnode_array_each(node->list1, {visit(val);});
+		gnode_array_free(node->list1);
+	}
+	if (node->list2) {
+		gnode_array_each(node->list2, {visit(val);});
+		gnode_array_free(node->list2);
+	}
+	mem_free((gnode_t*)node);
+}
+
+static void gravity_astfree (void *node) {
+	gvisitor_t visitor = {
+		.nerr = 0,
+		.data = NULL,
+		.delegate = NULL,
+		
+		// STATEMENTS: 7
+		.visit_list_stmt = free_list_stmt,
+		.visit_compound_stmt = free_compound_stmt,
+		.visit_label_stmt = free_label_stmt,
+		.visit_flow_stmt = free_flow_stmt,
+		.visit_loop_stmt = free_loop_stmt,
+		.visit_jump_stmt = free_jump_stmt,
+		.visit_empty_stmt = free_empty_stmt,
+		
+		// DECLARATIONS: 5
+		.visit_function_decl = free_function_decl,
+		.visit_variable_decl = free_variable_decl,
+		.visit_enum_decl = free_enum_decl,
+		.visit_class_decl = free_class_decl,
+		.visit_module_decl = free_module_decl,
+		
+		// EXPRESSIONS: 7+1
+		.visit_binary_expr = free_binary_expr,
+		.visit_unary_expr = free_unary_expr,
+		.visit_file_expr = free_file_expr,
+		.visit_literal_expr = free_literal_expr,
+		.visit_identifier_expr = free_identifier_expr,
+		.visit_keyword_expr = free_keyword_expr,
+		.visit_list_expr = free_list_expr,
+		.visit_postfix_expr = free_postfix_expr
+	};
+	
+	gvisit(&visitor, (gnode_t*)node);
+}
+
+// MARK: -
+
+void gnode_free (gnode_t *ast) {
+	gravity_astfree(ast);
+}

+ 318 - 0
src/compiler/gravity_ast.h

@@ -0,0 +1,318 @@
+//
+//  gravity_ast.h
+//  gravity
+//
+//  Created by Marco Bambini on 02/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_AST__
+#define __GRAVITY_AST__
+
+#include "debug_macros.h"
+#include "gravity_array.h"
+#include "gravity_token.h"
+
+/*
+	AST can be uniform (the same data struct is used for all expressions/statements/declarations) or
+	non-uniform. I choosed a non-uniform AST node implementation with a common base struct.
+	It requires more work but design and usage is much more cleaner and we benefit from static check.
+ */
+
+typedef enum {
+	// statements: 7
+	NODE_LIST_STAT, NODE_COMPOUND_STAT, NODE_LABEL_STAT, NODE_FLOW_STAT, NODE_JUMP_STAT, NODE_LOOP_STAT, NODE_EMPTY_STAT,
+
+	// declarations: 6
+	NODE_ENUM_DECL, NODE_FUNCTION_DECL, NODE_VARIABLE_DECL, NODE_CLASS_DECL, NODE_MODULE_DECL, NODE_VARIABLE,
+	
+	// expressions: 8
+	NODE_BINARY_EXPR, NODE_UNARY_EXPR, NODE_FILE_EXPR, NODE_LIST_EXPR, NODE_LITERAL_EXPR, NODE_IDENTIFIER_EXPR,
+	NODE_POSTFIX_EXPR, NODE_KEYWORD_EXPR,
+	
+	// postfix subexpression type
+	NODE_CALL_EXPR, NODE_SUBSCRIPT_EXPR, NODE_ACCESS_EXPR
+} gnode_n;
+
+typedef enum {
+	LOCATION_LOCAL,
+	LOCATION_GLOBAL,
+	LOCATION_UPVALUE,
+	LOCATION_CLASS_IVAR_SAME,
+	LOCATION_CLASS_IVAR_OUTER
+} gnode_location_type;
+
+// BASE NODE
+typedef struct {
+	gnode_n		tag;						// node type from gnode_n enum
+	uint32_t	refcount;					// reference count to manage duplicated nodes
+	gtoken_s	token;						// token type and location
+	bool		is_assignment;				// flag to check if it is an assignment node
+} gnode_t;
+
+// UPVALUE STRUCT
+typedef struct {
+	gnode_t		*node;						// reference to the original var node
+	uint32_t	index;						// can be an index in the stack or in the upvalue list (depending on the is_direct flag)
+	uint32_t	selfindex;					// always index inside uplist
+	bool		is_direct;					// flag to check if var is local to the direct enclosing func
+} gupvalue_t;
+
+// shortcut for array of common structs
+typedef marray_t(gnode_t *)					gnode_r;
+typedef marray_t(gupvalue_t *)				gupvalue_r;
+typedef struct symboltable_t				symboltable_t;
+
+// LOCATION
+typedef struct {
+	gnode_location_type	type;				// location type
+	uint16_t			index;				// symbol index
+	uint16_t			nup;				// upvalue index or outer index
+} gnode_location_t;
+
+// STATEMENTS
+typedef struct {
+	gnode_t				base;				// NODE_LIST_STAT | NODE_COMPOUND_STAT
+	symboltable_t		*symtable;			// node internal symbol table
+	gnode_r				*stmts;				// array of statements node
+	uint32_t			nclose;				// initialized to UINT32_MAX
+} gnode_compound_stmt_t;
+typedef gnode_compound_stmt_t gnode_list_stmt_t;
+
+typedef struct {
+	gnode_t				base;				// CASE or DEFAULT
+	gnode_t				*expr;				// expression in case of CASE
+	gnode_t				*stmt;				// common statement
+} gnode_label_stmt_t;
+
+typedef struct {
+	gnode_t				base;				// IF, SWITCH, TOK_OP_TERNARY
+	gnode_t				*cond;				// common condition (it's an expression)
+	gnode_t				*stmt;				// common statement
+	gnode_t				*elsestmt;			// optional else statement in case of IF
+} gnode_flow_stmt_t;
+
+typedef struct {
+	gnode_t				base;				// WHILE, REPEAT or FOR
+	gnode_t				*cond;				// used in WHILE and FOR
+	gnode_t				*stmt;				// common statement
+	gnode_t				*expr;				// used in REPEAT and FOR
+	uint32_t			nclose;				// initialized to UINT32_MAX
+} gnode_loop_stmt_t;
+
+typedef struct {
+	gnode_t				base;				// BREAK, CONTINUE or RETURN
+	gnode_t				*expr;				// optional expression in case of RETURN
+} gnode_jump_stmt_t;
+
+// DECLARATIONS
+typedef struct {
+	gnode_t				base;				// FUNCTION_DECL or FUNCTION_EXPR
+	gnode_t				*env;				// shortcut to node where function is declared
+	gtoken_t			access;				// TOK_KEY_PRIVATE | TOK_KEY_INTERNAL | TOK_KEY_PUBLIC
+	gtoken_t			storage;			// TOK_KEY_STATIC | TOK_KEY_EXTERN
+	symboltable_t		*symtable;			// function internal symbol table
+	const char			*identifier;		// function name
+	gnode_r				*params;			// function params
+	gnode_compound_stmt_t *block;			// internal function statements
+	uint16_t			nlocals;			// locals counter
+	uint16_t			nparams;			// formal parameters counter
+	gupvalue_r			*uplist;			// list of upvalues used in function (can be empty)
+} gnode_function_decl_t;
+typedef gnode_function_decl_t gnode_function_expr_t;
+
+typedef struct {
+	gnode_t				base;				// VARIABLE
+	gnode_t				*env;				// shortcut to node where variable is declared
+	const char			*identifier;		// variable name
+	const char			*annotation_type;	// optional annotation type
+	gnode_t				*expr;				// optional assignment expression/declaration
+	gtoken_t			access;				// optional access token (duplicated value from its gnode_variable_decl_t)
+	uint16_t			index;				// local variable index (if local)
+	bool				upvalue;			// flag set if this variable is used as an upvalue
+} gnode_var_t;
+
+typedef struct {
+	gnode_t				base;				// VARIABLE_DECL
+	gtoken_t			type;				// TOK_KEY_VAR | TOK_KEY_CONST
+	gtoken_t			access;				// TOK_KEY_PRIVATE | TOK_KEY_INTERNAL | TOK_KEY_PUBLIC
+	gtoken_t			storage;			// TOK_KEY_STATIC | TOK_KEY_EXTERN
+	gnode_r				*decls;				// variable declarations list (gnode_var_t)
+} gnode_variable_decl_t;
+
+typedef struct {
+	gnode_t				base;				// ENUM_DECL
+	gnode_t				*env;				// shortcut to node where enum is declared
+	gtoken_t			access;				// TOK_KEY_PRIVATE | TOK_KEY_INTERNAL | TOK_KEY_PUBLIC
+	gtoken_t			storage;			// TOK_KEY_STATIC | TOK_KEY_EXTERN
+	symboltable_t		*symtable;			// enum internal hash table
+	const char			*identifier;		// enum name
+} gnode_enum_decl_t;
+
+typedef struct {
+	gnode_t				base;				// CLASS_DECL
+	bool				bridge;				// flag to check of a bridged class
+	bool				is_struct;			// flag to mark the class as a struct
+	gnode_t				*env;				// shortcut to node where class is declared
+	gtoken_t			access;				// TOK_KEY_PRIVATE | TOK_KEY_INTERNAL | TOK_KEY_PUBLIC
+	gtoken_t			storage;			// TOK_KEY_STATIC | TOK_KEY_EXTERN
+	const char			*identifier;		// class name
+	gnode_t				*superclass;		// super class ptr
+	gnode_r				*protocols;			// array of protocols (currently unused)
+	gnode_r				*decls;				// class declarations list
+	symboltable_t		*symtable;			// class internal symbol table
+	void				*data;				// used to keep track of super classes
+	uint32_t			nivar;				// instance variables counter
+	uint32_t			nsvar;				// static variables counter
+} gnode_class_decl_t;
+
+typedef struct {
+	gnode_t				base;				// MODULE_DECL
+	gnode_t				*env;				// shortcut to node where module is declared
+	gtoken_t			access;				// TOK_KEY_PRIVATE | TOK_KEY_INTERNAL | TOK_KEY_PUBLIC
+	gtoken_t			storage;			// TOK_KEY_STATIC | TOK_KEY_EXTERN
+	const char			*identifier;		// module name
+	gnode_r				*decls;				// module declarations list
+	symboltable_t		*symtable;			// module internal symbol table
+} gnode_module_decl_t;
+
+// EXPRESSIONS
+typedef struct {
+	gnode_t				base;				// BINARY_EXPR
+	gtoken_t			op;					// operation
+	gnode_t				*left;				// left node
+	gnode_t				*right;				// right node
+} gnode_binary_expr_t;
+
+typedef struct {
+	gnode_t				base;				// UNARY_EXPR
+	gtoken_t			op;					// operation
+	gnode_t				*expr;				// node
+} gnode_unary_expr_t;
+
+typedef struct {
+	gnode_t				base;				// FILE
+	cstring_r			*identifiers;		// identifier name
+	gnode_location_t	location;			// identifier location
+} gnode_file_expr_t;
+
+typedef struct {
+	gnode_t				base;				// LITERAL
+	gliteral_t			type;				// LITERAL_STRING, LITERAL_FLOAT, LITERAL_INT, LITERAL_BOOL
+	uint32_t			len;				// used only for TYPE_STRING
+	union {
+		char			*str;				// LITERAL_STRING
+		double			d;					// LITERAL_FLOAT
+		int64_t			n64;				// LITERAL_INT or LITERAL_BOOL
+	} value;
+} gnode_literal_expr_t;
+
+typedef struct {
+	gnode_t				base;				// IDENTIFIER or ID
+	const char			*value;				// identifier name
+	const char			*value2;			// NULL for IDENTIFIER (check if just one value or an array)
+	gnode_t				*symbol;			// pointer to identifier declaration (if any)
+	gnode_location_t	location;			// location coordinates
+	gupvalue_t			*upvalue;			// upvalue location reference
+} gnode_identifier_expr_t;
+
+typedef struct {
+	gnode_t				base;				// KEYWORD token
+} gnode_keyword_expr_t;
+
+typedef gnode_keyword_expr_t gnode_empty_stmt_t;
+typedef gnode_keyword_expr_t gnode_base_t;
+
+typedef struct {
+	gnode_t				base;				// NODE_CALLFUNC_EXPR, NODE_SUBSCRIPT_EXPR, NODE_ACCESS_EXPR
+	gnode_t				*id;				// id(...) or id[...] or id.
+	gnode_r				*list;				// list of postfix_subexpr
+} gnode_postfix_expr_t;
+
+typedef struct {
+	gnode_t				base;				// NODE_CALLFUNC_EXPR, NODE_SUBSCRIPT_EXPR, NODE_ACCESS_EXPR
+	union {
+		gnode_t			*expr;				// used in case of NODE_SUBSCRIPT_EXPR or NODE_ACCESS_EXPR
+		gnode_r			*args;				// used in case of NODE_CALLFUNC_EXPR
+	};
+} gnode_postfix_subexpr_t;
+
+typedef struct {
+	gnode_t				base;				// LIST_EXPR
+	bool				ismap;				// flag to check if the node represents a map (otehrwise it is a list)
+	gnode_r				*list1;				// node items (cannot use a symtable here because order is mandatory in array)
+	gnode_r				*list2;				// used only in case of map
+} gnode_list_expr_t;
+
+gnode_t *gnode_jump_stat_create (gtoken_s token, gnode_t *expr);
+gnode_t *gnode_label_stat_create (gtoken_s token, gnode_t *expr, gnode_t *stmt);
+gnode_t *gnode_flow_stat_create (gtoken_s token, gnode_t *cond, gnode_t *stmt1, gnode_t *stmt2);
+gnode_t *gnode_loop_stat_create (gtoken_s token, gnode_t *cond, gnode_t *stmt, gnode_t *expr);
+gnode_t *gnode_block_stat_create (gnode_n type, gtoken_s token, gnode_r *stmts);
+gnode_t *gnode_empty_stat_create (gtoken_s token);
+
+gnode_t *gnode_enum_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, symboltable_t *symtable);
+gnode_t *gnode_class_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_t *superclass, gnode_r *protocols, gnode_r *declarations, bool is_struct);
+gnode_t *gnode_module_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *declarations);
+gnode_t *gnode_variable_decl_create (gtoken_s token, gtoken_t type, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *declarations);
+gnode_t *gnode_variable_create (gtoken_s token, const char *identifier, const char *annotation_type, gtoken_t storage_specifier, gnode_t *expr);
+
+gnode_t *gnode_function_decl_create (gtoken_s token, const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_r *params, gnode_compound_stmt_t *block);
+
+gnode_t *gnode_binary_expr_create (gtoken_t op, gnode_t *left, gnode_t *right);
+gnode_t *gnode_unary_expr_create (gtoken_t op, gnode_t *expr);
+gnode_t *gnode_file_expr_create (gtoken_s token, cstring_r *list);
+gnode_t *gnode_identifier_expr_create (gtoken_s token, const char *identifier, const char *identifier2);
+gnode_t *gnode_literal_string_expr_create (gtoken_s token, const char *s, uint32_t len);
+gnode_t *gnode_literal_float_expr_create (gtoken_s token, double f);
+gnode_t *gnode_literal_int_expr_create (gtoken_s token, int64_t n);
+gnode_t *gnode_literal_bool_expr_create (gtoken_s token, int32_t n);
+gnode_t *gnode_keyword_expr_create (gtoken_s token);
+gnode_t *gnode_postfix_subexpr_create (gtoken_s token, gnode_n type, gnode_t *expr, gnode_r *list);
+gnode_t *gnode_postfix_expr_create (gtoken_s token, gnode_t *id, gnode_r *list);
+gnode_t *gnode_list_expr_create (gtoken_s token, gnode_r *list1, gnode_r *list2, bool ismap);
+
+gnode_t *gnode_duplicate (gnode_t *node, bool deep);
+gnode_r	*gnode_array_create (void);
+gnode_r *gnode_array_remove_byindex(gnode_r *list, size_t index);
+gupvalue_t *gnode_function_add_upvalue(gnode_function_decl_t *f, gnode_var_t *symbol, uint16_t n);
+cstring_r  *cstring_array_create (void);
+void_r	*void_array_create (void);
+void	gnode_array_sethead(gnode_r *list, gnode_t *node);
+
+bool	gnode_is_equal (gnode_t *node1, gnode_t *node2);
+bool	gnode_is_expression (gnode_t *node);
+bool	gnode_is_literal (gnode_t *node);
+bool	gnode_is_literal_int (gnode_t *node);
+bool	gnode_is_literal_number (gnode_t *node);
+bool	gnode_is_literal_string (gnode_t *node);
+void	gnode_literal_dump (gnode_literal_expr_t *node, char *buffer, int buffersize);
+void	gnode_free (gnode_t *node);
+
+// MARK: -
+
+#define gnode_array_init(r)					marray_init(*r)
+#define gnode_array_size(r)					((r) ? marray_size(*r) : 0)
+#define gnode_array_push(r, node)			marray_push(gnode_t*,*r,node)
+#define gnode_array_pop(r)					(marray_size(*r) ? marray_pop(*r) : NULL)
+#define gnode_array_get(r, i)				(((i) >= 0 && (i) < marray_size(*r)) ? marray_get(*r, (i)) : NULL)
+#define gnode_array_free(r)					do {marray_destroy(*r); mem_free((void*)r);} while (0)
+#define gtype_array_each(r, block, type)	{	size_t _len = gnode_array_size(r);				\
+												for (size_t _i=0; _i<_len; ++_i) {				\
+													type val = (type)gnode_array_get(r, _i);	\
+													block;} \
+											}
+#define gnode_array_each(r, block)			gtype_array_each(r, block, gnode_t*)
+#define gnode_array_eachbase(r, block)		gtype_array_each(r, block, gnode_base_t*)
+
+#define cstring_array_free(r)				marray_destroy(*r)
+#define cstring_array_push(r, s)			marray_push(const char*,*r,s)
+#define cstring_array_each(r, block)		gtype_array_each(r, block, const char*)
+
+#define NODE_TOKEN_TYPE(_node)				_node->base.token.type
+#define NODE_TAG(_node)						((gnode_base_t *)_node)->base.tag
+#define NODE_ISA(_node,_tag)				(NODE_TAG(_node) == _tag)
+#define NODE_ISA_FUNCTION(_node)			(NODE_ISA(_node, NODE_FUNCTION_DECL))
+#define NODE_ISA_CLASS(_node)				(NODE_ISA(_node, NODE_CLASS_DECL))
+
+#endif

+ 1611 - 0
src/compiler/gravity_codegen.c

@@ -0,0 +1,1611 @@
+//
+//  gravity_codegen.c
+//  gravity
+//
+//  Created by Marco Bambini on 09/10/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_codegen.h"
+#include "gravity_symboltable.h"
+#include "gravity_optimizer.h"
+#include "gravity_visitor.h"
+#include "gravity_ircode.h"
+#include "gravity_utils.h"
+#include "gravity_array.h"
+#include "gravity_hash.h"
+
+typedef marray_t(gnode_class_decl_t *)		gnode_class_r;
+struct codegen_t {
+	gravity_object_r	context;
+	gnode_class_r		superfix;
+	gravity_vm			*vm;
+};
+typedef struct codegen_t codegen_t;
+
+#define CONTEXT_PUSH(x)					marray_push(gravity_object_t*, ((codegen_t *)self->data)->context, (gravity_object_t*)x)
+#define CONTEXT_POP()					marray_pop(((codegen_t *)self->data)->context)
+#define CONTEXT_GET()					marray_last(((codegen_t *)self->data)->context)
+#define CONTEXT_IS_MODULE(x)			((OBJECT_ISA_FUNCTION(x)) && (string_cmp(((gravity_function_t *)x)->identifier, INITMODULE_NAME) == 0))
+
+#define DECLARE_CONTEXT()				gravity_object_t *context_object = CONTEXT_GET();
+#define DECLARE_FUNCTION_CONTEXT()		DECLARE_CONTEXT();																	\
+										assert(OBJECT_ISA_FUNCTION(context_object));										\
+										gravity_function_t *context_function = (gravity_function_t *)context_object;
+#define DECLARE_CLASS_CONTEXT()			DECLARE_CONTEXT();																	\
+										assert(OBJECT_ISA_CLASS(context_object));											\
+										gravity_class_t *context_class = (gravity_class_t *)context_object;
+#define DECLARE_CODE()					DECLARE_FUNCTION_CONTEXT();															\
+										ircode_t *code = (ircode_t *)context_function->bytecode;
+
+#define IS_IMPLICIT_SELF(_expr)			(NODE_ISA(_expr, NODE_IDENTIFIER_EXPR) &&											\
+										(((gnode_identifier_expr_t *)_expr)->location.type == LOCATION_CLASS_IVAR_SAME) &&	\
+										(((gnode_identifier_expr_t *)_expr)->location.nup == 0) &&							\
+										(((gnode_identifier_expr_t *)_expr)->location.index == UINT16_MAX))
+
+#define IS_SUPER(_expr)					(NODE_ISA(_expr, NODE_KEYWORD_EXPR) && (((gnode_keyword_expr_t *)_expr)->base.token.type == TOK_KEY_SUPER))
+
+#define GET_VM()						((codegen_t *)self->data)->vm
+
+#define IS_LAST_LOOP(n1,n2)				(n1+1==n2)
+
+#if 1
+#define CODEGEN_COUNT_REGISTERS(_n)						uint32_t _n = ircode_register_count(code)
+#define CODEGEN_ASSERT_REGISTERS(_n1,_n2,_v)			assert(_n2 -_n1 == (_v))
+#else
+#define CODEGEN_COUNT_REGISTERS(_n)
+#define CODEGEN_ASSERT_REGISTERS(_n1,_n2,_v)
+#endif
+
+// MARK: -
+
+static void report_error (gvisitor_t *self, gnode_t *node, const char *format, ...) {
+	// increment internal error counter
+	++self->nerr;
+	
+	// get error callback (if any)
+	void *data = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->xdata : NULL;
+	gravity_error_callback error_fn = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->error_callback : NULL;
+	
+	// build error message
+	char		buffer[1024];
+	va_list		arg;
+	if (format) {
+		va_start (arg, format);
+		vsnprintf(buffer, sizeof(buffer), format, arg);
+		va_end (arg);
+	}
+	
+	// setup error struct
+	error_desc_t error_desc = {
+		.code = 0,
+		.lineno = node->token.lineno,
+		.colno = node->token.colno,
+		.fileid = node->token.fileid,
+		.offset = node->token.position
+	};
+	
+	// finally call error callback
+	if (error_fn) error_fn(GRAVITY_ERROR_SEMANTIC, buffer, error_desc, data);
+	else printf("%s\n", buffer);
+}
+
+// MARK: -
+static opcode_t token2opcode(gtoken_t op) {
+	switch (op) {
+		// BIT
+		case TOK_OP_SHIFT_LEFT: return LSHIFT;
+		case TOK_OP_SHIFT_RIGHT: return RSHIFT;
+		case TOK_OP_BIT_NOT: return BNOT;
+		case TOK_OP_BIT_AND: return BAND;
+		case TOK_OP_BIT_OR: return BOR;
+		case TOK_OP_BIT_XOR: return BXOR;
+			
+		// MATH
+		case TOK_OP_ADD: return ADD;
+		case TOK_OP_SUB: return SUB;
+		case TOK_OP_DIV: return DIV;
+		case TOK_OP_MUL: return MUL;
+		case TOK_OP_REM: return REM;
+		// NEG not handled here
+			
+		// COMPARISON
+		case TOK_KEY_ISA: return ISA;
+		case TOK_OP_LESS: return LT;
+		case TOK_OP_GREATER: return GT;
+		case TOK_OP_LESS_EQUAL: return LEQ;
+		case TOK_OP_GREATER_EQUAL: return GEQ;
+		case TOK_OP_ISEQUAL: return EQ;
+		case TOK_OP_ISNOTEQUAL: return NEQ;
+		case TOK_OP_ISIDENTICAL: return EQQ;
+		case TOK_OP_ISNOTIDENTICAL: return NEQQ;
+		case TOK_OP_PATTERN_MATCH: return MATCH;
+		
+		// LOGICAL
+		case TOK_OP_AND: return AND;
+		case TOK_OP_NOT: return NOT;
+		case TOK_OP_OR: return OR;
+		
+		default: assert(0); break;  // should never reach this point
+	}
+}
+
+#if 0
+static gravity_value_t literal2value (gnode_literal_expr_t *node) {
+	if (node->type == LITERAL_STRING) return VALUE_FROM_STRING(NULL, node->value.str, node->len);
+	if (node->type == LITERAL_FLOAT) return VALUE_FROM_FLOAT(node->value.d);
+	if (node->type == LITERAL_INT) return VALUE_FROM_INT(node->value.n64);
+	return VALUE_FROM_INT(node->value.n64); // default BOOLEAN case
+}
+
+static gravity_list_t *literals2list (gvisitor_t *self, gnode_r *r, uint32_t start, uint32_t stop) {
+	gravity_list_t *list = gravity_list_new(GET_VM(), stop-start);
+	
+	for (uint32_t i=start; i<stop; ++i) {
+		gnode_literal_expr_t *node = (gnode_literal_expr_t *)marray_get(*r, i);
+		gravity_value_t value = literal2value(node);
+		marray_push(gravity_value_t, list->array, value);
+	}
+	
+	return list;
+}
+
+static gravity_map_t *literals2map (gvisitor_t *self, gnode_r *r1, gnode_r *r2, uint32_t start, uint32_t stop) {
+	gravity_map_t *map = gravity_map_new(GET_VM(), stop-start);
+	
+	for (uint32_t i=start; i<stop; ++i) {
+		gnode_literal_expr_t *_key = (gnode_literal_expr_t *)marray_get(*r1, i);
+		gnode_literal_expr_t *_value = (gnode_literal_expr_t *)marray_get(*r2, i);
+		
+		// when here I am sure that both key and value are literals
+		// so they can be LITERAL_STRING, LITERAL_FLOAT, LITERAL_INT, LITERAL_BOOL
+		gravity_value_t key = literal2value(_key);
+		gravity_value_t value = literal2value(_value);
+		gravity_map_insert(NULL, map, key, value);
+	}
+	
+	return map;
+}
+
+static gravity_map_t *enum2map (gvisitor_t *self, gnode_enum_decl_t *node) {
+	uint32_t count = symboltable_count(node->symtable, 0);
+	gravity_map_t *map = gravity_map_new(GET_VM(), count);
+	
+	// FixMe
+	
+	return map;
+}
+
+static bool check_literals_list (gvisitor_t *self, gnode_list_expr_t *node, bool ismap, size_t idxstart, size_t idxend, uint32_t dest) {
+	DEBUG_CODEGEN("check_literal_list_expr");
+	DECLARE_CODE();
+	
+	// first check if all nodes inside this chuck are all literals
+	// and in this case apply a more efficient SETLIST variant
+	for (size_t j=idxstart; j < idxend; ++j) {
+		gnode_t *e = gnode_array_get(node->list1, j);
+		if (!gnode_is_literal(e)) return false;
+		if (ismap) {
+			// additional check on key that must be a string literal in case of a map
+			if (!gnode_is_literal_string(e)) return false;
+			e = gnode_array_get(node->list2, j);
+			if (!gnode_is_literal(e)) return false;
+		}
+	}
+	
+	// emit optimized (cpoll based) version
+	gravity_value_t v;
+	
+	if (ismap) {
+		gravity_map_t *map = literals2map(self, node->list1, node->list2, (uint32_t)idxstart, (uint32_t)idxend);
+		v = VALUE_FROM_OBJECT(map);
+	} else {
+		gravity_list_t *list = literals2list(self, node->list1, (uint32_t)idxstart, (uint32_t)idxend);
+		v = VALUE_FROM_OBJECT(list);
+	}
+	uint16_t index = gravity_function_cpool_add(GET_VM(), context_function, v);
+	ircode_add(code, SETLIST, dest, 0, index);
+	
+	return true;
+}
+#endif
+
+static uint32_t node2index(gnode_t * node) {
+	// node can be a VARIABLE declaration or a local IDENTIFIER
+	
+	if (NODE_ISA(node, NODE_VARIABLE_DECL)) {
+		gnode_variable_decl_t *expr = (gnode_variable_decl_t *)node;
+		assert(gnode_array_size(expr->decls) == 1);
+		
+		gnode_var_t *var = (gnode_var_t *)gnode_array_get(expr->decls, 0);
+		return var->index;
+	}
+	
+	if (NODE_ISA(node, NODE_IDENTIFIER_EXPR)) {
+		gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)node;
+		assert(expr->location.type == LOCATION_LOCAL);
+		return expr->location.index;
+	}
+	
+	// should never reach this point because semacheck2 should take care of the check
+	assert(0);
+	return UINT32_MAX;
+}
+
+static void fix_superclasses(gvisitor_t *self) {
+	// this function cannot fail because superclasses was already checked in samecheck2 so I am sure that they exist somewhere
+	codegen_t		*data = (codegen_t *)self->data;
+	gnode_class_r	*superfix = &data->superfix;
+	
+	size_t count = gnode_array_size(superfix);
+	for (size_t i=0; i<count; ++i) {
+		gnode_class_decl_t *node = (gnode_class_decl_t *)gnode_array_get(superfix, i);
+		gnode_class_decl_t *super = (gnode_class_decl_t *)node->superclass;
+		
+		gravity_class_t	*c = (gravity_class_t *)node->data;
+		gravity_class_setsuper(c, (gravity_class_t *)super->data);
+	}
+}
+
+// MARK: - Statements -
+
+static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	DEBUG_CODEGEN("visit_list_stmt");
+	gnode_array_each(node->stmts, {visit(val);});
+}
+
+static void visit_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	DEBUG_CODEGEN("visit_compound_stmt");
+	gnode_array_each(node->stmts, {visit(val);});
+	if (node->nclose != UINT32_MAX) {
+		DECLARE_CODE();
+		ircode_add(code, CLOSE, node->nclose, 0, 0);
+	}
+}
+
+static void visit_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
+	DEBUG_CODEGEN("visit_label_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	assert((type == TOK_KEY_DEFAULT) || (type == TOK_KEY_CASE));
+	
+	if (type == TOK_KEY_DEFAULT) {visit(node->stmt);}
+	else if (type == TOK_KEY_CASE) {visit(node->expr); visit(node->stmt);}
+}
+
+static void visit_flow_if_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
+	DEBUG_CODEGEN("visit_flow_if_stmt");
+	DECLARE_CODE();
+	
+	/*
+		 <condition>
+		 if-false: goto $end
+		 <then-part>
+		 goto $true
+		 $end:
+		 <else-part>
+		 $true:
+		 
+		 TRUE  := getLabel
+		 FALSE := getLabel
+		 compile condition
+		 emit ifeq FALSE
+		 compile stm1
+		 emit goto TRUE
+		 emit FALSE
+		 compile stm2
+		 emit TRUE
+	 */
+	
+	uint32_t labelTrue  = ircode_newlabel(code);
+	uint32_t labelFalse = ircode_newlabel(code);
+	
+	visit(node->cond);
+	ircode_add(code, JUMPF, ircode_register_pop(code), labelFalse, 0);
+	
+	visit(node->stmt);
+	if (node->elsestmt) ircode_add(code, JUMP, labelTrue, 0, 0);
+	
+	ircode_marklabel(code, labelFalse);
+	if (node->elsestmt) {
+		visit(node->elsestmt);
+		ircode_marklabel(code, labelTrue);
+	}
+}
+
+static void visit_flow_switch_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
+	DEBUG_CODEGEN("visit_flow_switch_stmt");
+	
+	visit(node->cond);
+	visit(node->stmt);
+}
+
+static void visit_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
+	DEBUG_CODEGEN("visit_flow_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	assert((type == TOK_KEY_IF) || (type == TOK_KEY_SWITCH));
+	
+	if (type == TOK_KEY_IF) {
+		visit_flow_if_stmt(self, node);
+	} else if (type == TOK_KEY_SWITCH) {
+		visit_flow_switch_stmt(self, node);
+	}
+}
+
+static void visit_loop_while_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	DEBUG_CODEGEN("visit_loop_while_stmt");
+	DECLARE_CODE();
+	
+	/*
+		 $start:	<condition>
+		 if-false: goto $end
+		 <body>
+		 goto $start
+		 $end:
+		 
+		 START := getLabel
+		 END  := getLabel
+		 emit START
+		 compile exp
+		 emit ifeq END
+		 compile stm
+		 emit goto START
+		 emit END
+	 */
+	
+	uint32_t labelTrue  = ircode_newlabel(code);
+	uint32_t labelFalse = ircode_newlabel(code);
+	ircode_setlabel_true(code, labelTrue);
+	ircode_setlabel_false(code, labelFalse);
+	
+	ircode_marklabel(code, labelTrue);
+	visit(node->cond);
+	ircode_add(code, JUMPF, ircode_register_pop(code), labelFalse, 0);
+	
+	visit(node->stmt);
+	ircode_add(code, JUMP, labelTrue, 0, 0);
+	
+	ircode_marklabel(code, labelFalse);
+	
+	ircode_unsetlabel_true(code);
+	ircode_unsetlabel_false(code);
+}
+
+static void visit_loop_repeat_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	DEBUG_CODEGEN("visit_loop_repeat_stmt");
+	DECLARE_CODE();
+	
+	/*
+		 $start:	<body>
+		 <expression>
+		 if-false: goto $start
+		 $end:
+	 */
+	
+	uint32_t labelTrue  = ircode_newlabel(code);
+	uint32_t labelFalse = ircode_newlabel(code);	// end label is necessary to handle optional break statement
+	ircode_setlabel_true(code, labelTrue);
+	ircode_setlabel_false(code, labelFalse);
+	
+	ircode_marklabel(code, labelTrue);
+	visit(node->stmt);
+	visit(node->expr);
+	ircode_add(code, JUMPF, ircode_register_pop(code), labelFalse, 0);
+	ircode_add(code, JUMP, labelTrue, 0, 0);
+	
+	ircode_marklabel(code, labelFalse);
+	
+	ircode_unsetlabel_true(code);
+	ircode_unsetlabel_false(code);
+}
+
+static void visit_loop_for_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	// https://www.natashatherobot.com/swift-alternatives-to-c-style-for-loops/
+	
+	DEBUG_CODEGEN("visit_loop_for_stmt");
+	DECLARE_CODE();
+	
+	// FOR loop is transformed to a WHILE loop
+	//
+	// from:
+	// for (cond in expr) {
+	//    stmp;
+	// }
+	//
+	// to:
+	// {
+	//    var $expr = expr;
+	//    var $value = $expr.iterate(null);
+	//    while ($value) {
+	//		cond = $expr.next($value);
+	//		stmp;
+	//		$value = $expr.iterate($value);
+	//    }
+	// }
+	
+	uint32_t $expr = ircode_register_push_temp(code);			// ++TEMP => 1
+	uint32_t $value = ircode_register_push_temp(code);			// ++TEMP => 2
+	
+	uint16_t iterate_idx = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, ITERATOR_INIT_FUNCTION));
+	uint16_t next_idx = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, ITERATOR_NEXT_FUNCTION));
+	uint32_t cond_idx = node2index(node->cond);
+	
+	// generate code for $expr = expr (so expr is only evaluated once)
+	visit(node->expr);
+	uint32_t once_expr = ircode_register_pop(code);
+	ircode_add(code, MOVE, $expr, once_expr, 0);
+	
+	// generate code for $value = $expr.iterate(null);
+	uint32_t iterate_fn = ircode_register_push_temp(code);		// ++TEMP => 3
+	ircode_add(code, LOADK, iterate_fn, iterate_idx, 0);
+	ircode_add(code, LOAD, iterate_fn, $expr, iterate_fn);
+	
+	uint32_t next_fn = ircode_register_push_temp(code);			// ++TEMP => 4
+	ircode_add(code, LOADK, next_fn, next_idx, 0);
+	ircode_add(code, LOAD, next_fn, $expr, next_fn);
+		
+	uint32_t temp1 = ircode_register_push_temp(code);			// ++TEMP => 5
+	ircode_add(code, MOVE, temp1, iterate_fn, 0);
+	uint32_t temp2 = ircode_register_push_temp(code);			// ++TEMP => 6
+	ircode_add(code, MOVE, temp2, $expr, 0);
+	temp2 = ircode_register_push_temp(code);					// ++TEMP => 7
+	ircode_add(code, LOADK, temp2, CPOOL_VALUE_NULL, 0);
+	ircode_add(code, CALL, $value, temp1, 2);
+	ircode_register_pop(code);									// --TEMP => 6
+	ircode_register_pop(code);									// --TEMP => 5
+	ircode_register_pop(code);									// --TEMP => 4
+	
+	// while code
+	uint32_t labelTrue  = ircode_newlabel(code);
+	uint32_t labelFalse = ircode_newlabel(code);
+	ircode_setlabel_true(code, labelTrue);
+	ircode_setlabel_false(code, labelFalse);
+	
+	ircode_marklabel(code, labelTrue);
+	ircode_add(code, JUMPF, $value, labelFalse, 1);				// flag JUMPF instruction to check ONLY BOOL values
+	
+	// cond = $expr.next($value);
+	// cond is a local variable
+	temp1 = ircode_register_push_temp(code);					// ++TEMP => 5
+	ircode_add(code, MOVE, temp1, next_fn, 0);
+	temp2 = ircode_register_push_temp(code);					// ++TEMP => 6
+	ircode_add(code, MOVE, temp2, $expr, 0);
+	temp2 = ircode_register_push_temp(code);					// ++TEMP => 7
+	ircode_add(code, MOVE, temp2, $value, 0);
+	ircode_add(code, CALL, cond_idx, temp1, 2);
+	
+	// process statement
+	visit(node->stmt);
+	
+	// pop next_fn temp register AFTER user code because function ptr must be protected inside loop
+	ircode_register_pop(code);									// --TEMP => 6
+	ircode_register_pop(code);									// --TEMP => 5
+	ircode_register_pop(code);									// --TEMP => 4
+	
+	// update $value for the next check
+	// $value = $expr.iterate($value);
+	temp1 = ircode_register_push_temp(code);					// ++TEMP => 5
+	ircode_add(code, MOVE, temp1, iterate_fn, 0);
+	temp2 = ircode_register_push_temp(code);					// ++TEMP => 6
+	ircode_add(code, MOVE, temp2, $expr, 0);
+	temp2 = ircode_register_push_temp(code);					// ++TEMP => 7
+	ircode_add(code, MOVE, temp2, $value, 0);
+	ircode_add(code, CALL, $value, temp1, 2);
+	ircode_register_pop(code);									// --TEMP => 6
+	ircode_register_pop(code);									// --TEMP => 5
+	ircode_register_pop(code);									// --TEMP => 4
+	
+	ircode_add(code, JUMP, labelTrue, 0, 0);
+	
+	ircode_marklabel(code, labelFalse);
+	
+	ircode_unsetlabel_true(code);
+	ircode_unsetlabel_false(code);
+	
+	ircode_register_pop(code);									// --TEMP => 3
+	ircode_register_pop(code);									// --TEMP => 2
+	ircode_register_pop(code);									// --TEMP => 1
+	ircode_register_pop(code);									// --TEMP => 0
+	
+	if (node->nclose != UINT32_MAX) {
+		ircode_add(code, CLOSE, node->nclose, 0, 0);
+	}
+}
+
+static void visit_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	DEBUG_CODEGEN("visit_loop_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	assert((type == TOK_KEY_WHILE) || (type == TOK_KEY_REPEAT) || (type == TOK_KEY_FOR));
+	
+	if (type == TOK_KEY_WHILE) {
+		visit_loop_while_stmt(self, node);
+	} else if (type == TOK_KEY_REPEAT) {
+		visit_loop_repeat_stmt(self, node);
+	} else if (type == TOK_KEY_FOR) {
+		visit_loop_for_stmt(self, node);
+	}
+}
+
+static void visit_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
+	DEBUG_CODEGEN("visit_jump_stmt");
+	DECLARE_CODE();
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	assert((type == TOK_KEY_BREAK) || (type == TOK_KEY_CONTINUE) || (type == TOK_KEY_RETURN));
+	
+	if (type == TOK_KEY_BREAK) {
+		uint32_t label = ircode_getlabel_false(code);
+		ircode_add(code, JUMP, label, 0, 0); // goto $end;
+	} else if (type == TOK_KEY_CONTINUE) {
+		uint32_t label = ircode_getlabel_true(code);
+		ircode_add(code, JUMP, label, 0, 0); // goto $start;
+	} else if (type == TOK_KEY_RETURN) {
+		if (node->expr) {
+			visit(node->expr);
+			ircode_add(code, RET, ircode_register_pop(code), 0, 0);
+		} else {
+			ircode_add(code, RET0, 0, 0, 0);
+		}
+	}
+}
+
+static void visit_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
+	#pragma unused(self, node)
+	DEBUG_CODEGEN("visit_empty_stmt");
+	
+	DECLARE_CODE();
+	ircode_add(code, NOP, 0, 0, 0);
+}
+
+// MARK: - Declarations -
+
+static void store_declaration (gvisitor_t *self, gravity_object_t *obj, bool is_static, gnode_function_decl_t *node) {
+	DEBUG_CODEGEN("store_object_declaration");
+	assert(obj);
+	
+	DECLARE_CONTEXT();
+	bool is_module = CONTEXT_IS_MODULE(context_object);
+	bool is_class = OBJECT_ISA_CLASS(context_object);
+	bool is_local = ((is_module == false) && (is_class == false));
+	if (is_static) assert(is_class); // static makes sense only for class objects
+	
+	if (is_local || is_module) {
+		gravity_function_t *context_function = (gravity_function_t *)context_object;
+		ircode_t *code = (ircode_t *)context_function->bytecode;
+		
+		// add object to cpool and get its index
+		uint16_t index = gravity_function_cpool_add(NULL, context_function, VALUE_FROM_OBJECT(obj));
+		
+		// if it is a function then generate a CLOSURE opcode instead of LOADK
+		if (OBJECT_ISA_FUNCTION(obj)) {
+			gravity_function_t *f = (gravity_function_t *)obj;
+			uint32_t regnum = ircode_register_push_temp(code);
+			ircode_add(code, CLOSURE, regnum, index, 0);
+			uint32_t upindex = 0;
+			for (uint16_t i=0; i<f->nupvalues; ++i) {
+				gupvalue_t *upvalue = (gupvalue_t *)gnode_array_get(node->uplist, i);
+				uint32_t opindex = (upvalue->is_direct) ? upvalue->index : upindex++;
+				ircode_add(code, MOVE, opindex, (upvalue->is_direct) ? 1 : 0, 0);
+			}
+		} else {
+			ircode_add_constant(code, index);
+		}
+			
+		if (is_module && obj->identifier) {
+			index = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, obj->identifier));
+			ircode_add(code, STOREG, ircode_register_pop(code), index, 0);
+		}
+			
+		return;
+	}
+	
+	if (is_class) {
+		gravity_class_t *context_class = (gravity_class_t *)context_object;
+		context_class = (is_static) ? context_class->objclass : context_class;
+		gravity_class_bind(context_class, obj->identifier, VALUE_FROM_OBJECT(obj));
+		return;
+	}
+	
+	// should never reach this point
+	assert(0);
+}
+
+// used ONLY by CODEGEN
+static gravity_function_t *class_lookup_nosuper (gravity_class_t *c, const char *name) {
+	STATICVALUE_FROM_STRING(key, name, strlen(name));
+	gravity_value_t *v = gravity_hash_lookup(c->htable, key);
+	return (v) ? (gravity_function_t*)v->p : NULL;
+}
+
+static void process_constructor (gvisitor_t *self, gravity_class_t *c) {
+	DEBUG_CODEGEN("process_constructor");
+	
+	// $init is an internal function used to initialize instance variables to a default value
+	// in case of subclasses USER is RESPONSIBLE to call super.init();
+	// in case of subclasses COMPILER is RESPONSIBLE to create the appropriate $init call chain
+	
+	// check internal $init function
+	gravity_function_t *internal_init_function = class_lookup_nosuper(c, CLASS_INTERNAL_INIT_NAME);
+	
+	// check for user constructor function
+	gravity_function_t *constructor_function = class_lookup_nosuper(c, CLASS_CONSTRUCTOR_NAME);
+	
+	// build appropriate $init function
+	gravity_class_t *super = c->superclass;
+	uint32_t ninit = 2;
+	while (super) {
+		gravity_function_t *super_init = class_lookup_nosuper(super, CLASS_INTERNAL_INIT_NAME);
+		if (super_init) {
+			// copy super init code to internal_init code
+			if (!internal_init_function) internal_init_function = gravity_function_new(NULL, CLASS_INTERNAL_INIT_NAME, 1, 0, 0, ircode_create(1));
+			
+			// build unique internal init name ($init2, $init3 and so on)
+			char name[256];
+			snprintf(name, sizeof(name), "%s%d", CLASS_INTERNAL_INIT_NAME, ninit++);
+			
+			// add new internal init to class and call it from main $init function
+			// super_init should not be duplicated here because class hash table values are not freed (only keys are freed)
+			gravity_class_bind(c, name, VALUE_FROM_OBJECT(super_init));
+			uint16_t index = gravity_function_cpool_add(NULL, internal_init_function, VALUE_FROM_CSTRING(GET_VM(), name));
+			ircode_patch_init((ircode_t *)internal_init_function->bytecode, index);
+		}
+		super = super->superclass;
+	}
+	
+	// 4 cases to handle:
+	
+	// 1. internal_init and constuctor are not present, so nothing to do here
+	if ((!internal_init_function) && (!constructor_function)) goto check_meta;
+	
+//	// 2. internal init is present and constructor is not used
+//	if ((internal_init_function) && (!constructor_function)) {
+//		// add a RET0 command
+//		ircode_t *code = (ircode_t *)internal_init_function->bytecode;
+//		ircode_add(code, RET0, 0, 0, 0);
+//		
+//		// bind internal init as constructor
+//		gravity_class_bind(c, CLASS_CONSTRUCTOR_NAME, VALUE_FROM_OBJECT(internal_init_function));
+//		gravity_object_setenclosing((gravity_object_t *)internal_init_function, (gravity_object_t *)c);
+//		goto process_funcs;
+//	}
+	
+	// 3. internal init is present so constructor is mandatory
+	if (internal_init_function) {
+		// convert ircode to bytecode for $init special function and add a RET0 command
+		ircode_t *code = (ircode_t *)internal_init_function->bytecode;
+		ircode_add(code, RET0, 0, 0, 0);
+		
+		if (constructor_function == NULL) {
+			constructor_function = gravity_function_new(NULL, CLASS_CONSTRUCTOR_NAME, 1, 0, 2, ircode_create(1));
+			ircode_t *code2 = (ircode_t *)constructor_function->bytecode;
+			ircode_add_skip(code2);	// LOADK
+			ircode_add_skip(code2);	// LOAD
+			ircode_add_skip(code2);	// MOVE
+			ircode_add_skip(code2);	// CALL
+			gravity_class_bind(c, CLASS_CONSTRUCTOR_NAME, VALUE_FROM_OBJECT(constructor_function));
+		}
+	}
+	
+	// 4. constructor is present so internal init is optional
+	if (constructor_function) {
+		// add an implicit RET 0 (RET self) to the end of the constructor
+		ircode_t *code = (ircode_t *)constructor_function->bytecode;
+		ircode_add(code, RET, 0, 0, 0);
+		
+		if (internal_init_function) {
+			// if an internal init function is present ($init) then add a call to it as a first instruction
+			uint16_t index = gravity_function_cpool_add(GET_VM(), constructor_function, VALUE_FROM_CSTRING(NULL, CLASS_INTERNAL_INIT_NAME));
+			
+			// load constant
+			uint32_t dest = ircode_register_push_temp(code);
+			ircode_set_index(0, code, LOADK, dest, index, 0);
+			
+			// load from lookup
+			ircode_set_index(1, code, LOAD, dest, 0, dest);
+			
+			// prepare parameters
+			uint32_t dest2 = ircode_register_push_temp(code);
+			ircode_set_index(2, code, MOVE, dest2, 0, 0);
+			ircode_register_pop(code);
+			
+			// execute call
+			ircode_set_index(3, code, CALL, dest, dest, 1);
+		}
+	}
+	
+//process_funcs:
+	if (internal_init_function) gravity_optimizer(internal_init_function);
+	if (constructor_function) gravity_optimizer(constructor_function);
+	
+check_meta:
+	// recursively process constructor but stop when object or class class is found, otherwise an infinite loop is triggered
+	if ((c->objclass) && (c->objclass->isa) && (c->objclass->isa != c->objclass->objclass)) process_constructor(self, c->objclass);
+}
+
+static void process_getter_setter (gvisitor_t *self, gnode_var_t *p, gravity_class_t *c) {
+	gnode_compound_stmt_t	*expr = (gnode_compound_stmt_t *)p->expr;
+	gnode_function_decl_t	*getter = (gnode_function_decl_t *)gnode_array_get(expr->stmts, 0);
+	gnode_function_decl_t	*setter = (gnode_function_decl_t *)gnode_array_get(expr->stmts, 1);
+	
+	gnode_function_decl_t	*f1[2] = {getter, setter};
+	gravity_function_t		*f2[2] = {NULL, NULL};
+	
+	for (uint16_t i=0; i<2; ++i) {
+		gnode_function_decl_t *node = f1[i];
+		if (!node) continue;
+		
+		// create gravity function
+		uint16_t nparams = (uint16_t)(node->params) ? marray_size(*node->params) : 0;
+		f2[i] = gravity_function_new(NULL, NULL, nparams, node->nlocals, 0, ircode_create(node->nlocals+nparams));
+		
+		// process inner block
+		CONTEXT_PUSH(f2[i]);
+		gnode_compound_stmt_t *block = node->block;
+		if (block) {gnode_array_each(block->stmts, {visit(val);});}
+		CONTEXT_POP();
+		
+		gravity_optimizer(f2[i]);
+	}
+	
+	// getter and setter NULL means default
+	// since getter and setter are methods and not simple functions, do not transfer to VM
+	gravity_function_t *f = gravity_function_new_special(NULL, NULL, GRAVITY_COMPUTED_INDEX, f2[0], f2[1]);
+	gravity_class_bind(c, p->identifier, VALUE_FROM_OBJECT(f));
+}
+
+static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
+	DEBUG_CODEGEN("visit_function_decl %s", node->identifier);
+	
+	// extern means it will be provided at runtime by the delegate
+	if (node->storage == TOK_KEY_EXTERN) return;
+	
+	DECLARE_CONTEXT();
+	bool is_class_ctx = OBJECT_ISA_CLASS(context_object);
+	
+	// create new function object
+	uint16_t nparams = (uint16_t)(node->params) ? marray_size(*node->params) : 0;
+	gravity_function_t *f = gravity_function_new((is_class_ctx) ? NULL : GET_VM(), node->identifier,
+												 nparams, node->nlocals, 0, (void *)ircode_create(node->nlocals+nparams));
+	
+	// check if f is a special constructor function (init)
+	// name must be CLASS_CONSTRUCTOR_NAME and context_object must be a class
+	bool is_constructor = (string_cmp(node->identifier, CLASS_CONSTRUCTOR_NAME) == 0) && (is_class_ctx);
+	
+	CONTEXT_PUSH(f);
+	
+	if (is_constructor) {
+		// reserve first four instructions that could be later filled with a CALL to $init
+		// see process_constructor for more information
+		ircode_t *code = (ircode_t *)f->bytecode;
+		ircode_add_skip(code);
+		ircode_add_skip(code);
+		ircode_add_skip(code);
+		ircode_add_skip(code);
+	}
+	
+	// process inner block
+	if (node->block) {gnode_array_each(node->block->stmts, {visit(val);});}
+	
+	// check for upvalues
+	if (node->uplist) f->nupvalues = gnode_array_size(node->uplist);
+	
+	// remove current function
+	CONTEXT_POP();
+	
+	// check for ircode errors
+	if (ircode_iserror((ircode_t *)f->bytecode))
+		report_error(self, (gnode_t *)node, "Maximum number of available registers used in function %s.", f->identifier);
+	
+	// store function in current context
+	store_declaration(self, (gravity_object_t *)f, (node->storage == TOK_KEY_STATIC), node);
+	
+	// convert ircode to bytecode (postpone optimization of the constructor)
+	if (!is_constructor) gravity_optimizer(f);
+}
+
+static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
+	DEBUG_CODEGEN("visit_variable_decl");
+	DECLARE_CONTEXT();
+	
+	// no initialization for extern variables since the real value will be provided at runtime
+	if (node->storage == TOK_KEY_EXTERN) return;
+	
+	bool is_module = CONTEXT_IS_MODULE(context_object);
+	bool is_class = OBJECT_ISA_CLASS(context_object);
+	bool is_local = ((is_module == false) && (is_class == false));
+	
+	// loop through declarations
+	size_t count = gnode_array_size(node->decls);
+	for (size_t i=0; i<count; ++i) {
+		gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
+		DEBUG_CODEGEN("visit_variable_decl %s", p->identifier);
+		
+		// variable declarations can be specified in:
+		// FUNCTION (local variable)
+		// MODULE (module variable)
+		// CLASS (property)
+		
+		if (is_local) {
+			// it is a local variable declaration (default initialized to NULL)
+			// code is generate ONLY if an init expression is specified
+			//
+			//	example:
+			//
+			//	func foo () {
+			//		var a = 10;
+			//	}
+			//
+			//	LOADI	1 10	; move 10 into register 1
+			//	MOVE	0 1		; move register 1 into register 0
+			//
+			
+			// generate expression code
+			if (p->expr) visit(p->expr); // context is a function
+				
+			gravity_function_t *context_function = (gravity_function_t *)context_object;
+			ircode_t *code = (ircode_t *)context_function->bytecode;
+			if (p->expr) {
+				// assign to variable result of the expression
+				ircode_add(code, MOVE, p->index, ircode_register_pop(code), 0);
+			} else {
+				// no default assignment expression found so initialize to NULL
+				ircode_add(code, LOADK, p->index, CPOOL_VALUE_NULL, 0);
+			}
+			continue;
+		}
+		
+		if (is_module) {
+			// it is a module variable (default initialized to null)
+			// code must ALWAYS be generated
+			//
+			// example 1:
+			//	var a;
+			//
+			//	LOADK	0 NULL	; move null into register 0
+			//	STOREG	0 0		; move register 0 into hash(constant_pool(0))
+			//
+			// example 2:
+			//	var a = 10;
+			//
+			//	LOADI	0 10	; move 10 into register 0
+			//	STOREG	0 0		; move register 0 into hash(constant_pool(0))
+			//
+			
+			gravity_function_t *context_function = (gravity_function_t *)context_object;
+			ircode_t *code = (ircode_t *)context_function->bytecode;
+			uint16_t index = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, p->identifier));
+			
+			if (p->expr) {
+				visit(p->expr); // context is a function
+			} else {
+				ircode_add_constant(code, CPOOL_VALUE_NULL);
+			}
+			ircode_add(code, STOREG, ircode_register_pop(code), index, 0);
+			continue;
+		}
+		
+		if (is_class) {
+			bool is_static = (node->storage == TOK_KEY_STATIC);
+			gravity_class_t *context_class = (is_static) ? ((gravity_class_t *)context_object)->objclass : (gravity_class_t *)context_object;
+			
+			// check computed property case first
+			if ((p->expr) && (p->expr->tag == NODE_COMPOUND_STAT)) {
+				process_getter_setter(self, p, context_class);
+				continue;
+			}
+			
+			// create ivar first
+			uint16_t ivar_index = (p->index >= 0) ? p->index : gravity_class_add_ivar(context_class, NULL);
+			
+			// add default getter and setter ONLY if property is public
+			if (node->access == TOK_KEY_PUBLIC) {
+				// since getter and setter are methods and not simple functions, do not transfer to VM
+				gravity_function_t *f = gravity_function_new_special(NULL, NULL, ivar_index, NULL, NULL); // getter and setter NULL means default
+				gravity_class_bind(context_class, p->identifier, VALUE_FROM_OBJECT(f));
+			}
+			DEBUG_CODEGEN("Class: %s (static: %d) property: %s index: %d", context_class->identifier, is_static, p->identifier, ivar_index);
+			
+			// it is a property (default initialized to null)
+			// code must be generated ONLY if an init expression is specified
+			
+			//
+			//  example:
+			// 	class foo {
+			// 		var a = 10;
+			// 	}
+			//
+			//  get $init function
+			//  depending if variable has been created static
+			//  and push it as current declaration then:
+			//
+			// 	LOADI	0 10	; move 10 into register 0
+			// 	STOREF	0 0		; move register 0 into property 0
+			
+			if (p->expr) {
+				// was gravity_class_lookup but that means than $init or init will be recursively searched also in super classes
+				gravity_function_t *init_function = class_lookup_nosuper(context_class, CLASS_INTERNAL_INIT_NAME);
+				if (init_function == NULL) {
+					// no $init method found so create a new one
+					init_function = gravity_function_new (NULL, CLASS_INTERNAL_INIT_NAME, 1, 0, 0, ircode_create(1));
+					gravity_class_bind(context_class, CLASS_INTERNAL_INIT_NAME, VALUE_FROM_OBJECT(init_function));
+				}
+				
+				CONTEXT_PUSH(init_function);
+				ircode_t *code = (ircode_t *)init_function->bytecode;
+				visit(p->expr);
+				uint32_t dest = ircode_register_pop(code);
+				ircode_add(code, STORE, dest, 0, p->index + MAX_REGISTERS);
+				CONTEXT_POP();
+			}
+			
+			continue;
+		}
+		
+		// should never reach this point
+		assert(0);
+	}
+}
+
+static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
+	#pragma unused(self,node)
+	DEBUG_CODEGEN("visit_enum_decl %s", node->identifier);
+	
+	// enum is a map at runtime
+	// enum foo {a=1,b=2,...}
+	// is translated to
+	// var foo = [a:1,b:2,...]
+}
+
+static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
+	DEBUG_CODEGEN("visit_class_decl %s", node->identifier);
+	
+	// extern means it will be provided at runtime by the delegate
+	if (node->storage == TOK_KEY_EXTERN) return;
+	
+	// create a new pair of classes (class itself and its meta)
+	gravity_class_t *c = gravity_class_new_pair(GET_VM(), node->identifier, NULL, node->nivar, node->nsvar);
+	
+	// mark the class as a struct
+	c->is_struct = node->is_struct;
+	
+	// check if class has a declared superclass
+	if (node->superclass) {
+		// node->superclass should be a gnode_class_decl_t at this point
+		assert(NODE_ISA_CLASS(node->superclass));
+		
+		gnode_class_decl_t *super = (gnode_class_decl_t *)node->superclass;
+		if (super->data) {
+			// means that superclass has already been processed and its runtime representation is available
+			gravity_class_setsuper(c, (gravity_class_t *)super->data);
+		} else {
+			// superclass has not yet processed so we need recheck the node at the end of the visit
+			// add node to superfix for later processing
+			codegen_t *data = (codegen_t *)self->data;
+			marray_push(gnode_class_decl_t *, data->superfix, node);
+		}
+	}
+	
+	CONTEXT_PUSH(c);
+	
+	// process inner declarations
+	gnode_array_each(node->decls, {visit(val);});
+	
+	// adjust declaration stack
+	CONTEXT_POP();
+	
+	// fix constructor chain
+	process_constructor(self, c);
+	
+	// store class declaration in current context
+	store_declaration(self, (gravity_object_t *)c, (node->storage == TOK_KEY_STATIC), NULL);
+	
+	// save runtime representation
+	// since this class could be a superclass of another class
+	// then save opaque gravity_class_t pointer to node->data
+	node->data = (void *)c;
+}
+
+static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
+	#pragma unused(self, node)
+	DEBUG_CODEGEN("visit_module_decl %s", node->identifier);
+
+	// a module should be like a class with static entries
+	// instantiated with import
+	
+//	gravity_module_t *module = gravity_module_new(GET_VM(), node->identifier);
+//	CONTEXT_PUSH(module);
+//	
+//	// process inner declarations
+//	gnode_array_each(node->decls, {visit(val);});
+//	
+//	// adjust declaration stack
+//	CONTEXT_POP();
+}
+
+// MARK: - Expressions -
+
+//static void process_special_enum (gvisitor_t *self, gnode_enum_decl_t *node, gnode_identifier_expr_t *identifier) {
+//	symboltable_t *symtable = node->symtable;
+//	gnode_t *v = symboltable_lookup(symtable, identifier->value);
+//	assert(v);
+//	assert(NODE_ISA(v, NODE_LITERAL_EXPR));
+//	visit(v);
+//}
+
+static void visit_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
+	DEBUG_CODEGEN("visit_binary_expr %s", token_name(node->op));
+	DECLARE_CODE();
+	
+	// assignment is right associative
+	if (node->op == TOK_OP_ASSIGN) {
+		CODEGEN_COUNT_REGISTERS(n1);
+		visit(node->right);
+		visit(node->left);	// left expression can be: IDENTIFIER, FILE, POSTIFIX (not a call)
+		CODEGEN_COUNT_REGISTERS(n2);
+		CODEGEN_ASSERT_REGISTERS(n1, n2, 0);
+		return;
+	}
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	// visiting binary operation from left to right
+	visit(node->left);
+	visit(node->right);
+	
+	uint32_t r3 = ircode_register_pop(code);
+	uint32_t r2 = ircode_register_pop(code);
+	uint32_t r1 = ircode_register_push_temp(code);
+	
+	// a special instruction needs to be generated for a binary expression of type RANGE
+	if ((node->op == TOK_OP_RANGE_INCLUDED) || (node->op == TOK_OP_RANGE_EXCLUDED)) {
+		ircode_add_tag(code, RANGENEW, r1, r2, r3, (node->op == TOK_OP_RANGE_INCLUDED) ? RANGE_INCLUDE_TAG : RANGE_EXCLUDE_TAG);
+		return;
+	}
+	
+	// generate code for binary OP
+	opcode_t op = token2opcode(node->op);
+	ircode_add(code, op, r1, r2, r3);
+		
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, 1);
+}
+
+static void visit_unary_expr (gvisitor_t *self, gnode_unary_expr_t *node) {
+	DEBUG_CODEGEN("visit_unary_expr %s", token_name(node->op));
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	// unary expression can be:
+	//	+	Unary PLUS
+	//	-	Unary MINUS
+	//	!	Logical NOT
+	//	~	Bitwise NOT
+	
+	visit(node->expr);
+	if (node->op == TOK_OP_ADD) {
+		// +1 is just 1 and more generally +expr is just expr so ignore + and proceed
+		return;
+	}
+	
+	uint32_t r2 = ircode_register_pop(code);
+	uint32_t r1 = ircode_register_push_temp(code);
+	
+	opcode_t op = (node->op == TOK_OP_SUB) ? NEG : token2opcode(node->op);
+	ircode_add(code, op, r1, r2, 0);
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, 1);
+}
+
+static void visit_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
+	DEBUG_CODEGEN("visit_call_expr");
+	
+	// node->list usually cannot be NULL, it is NULL only as a result of a static enum transformation (see semacheck2.c)
+	if (node->list == NULL) {
+		visit(node->id);
+		return;
+	}
+	
+	DECLARE_CODE();
+	CODEGEN_COUNT_REGISTERS(n1);
+	ircode_push_context(code);
+	
+	// generate code for the common id node
+	visit(node->id);
+	
+	bool is_super = IS_SUPER(node->id);
+	
+	// register that contains callable object
+	uint32_t target_register = ircode_register_pop_protect(code, true);
+	
+	// register where to store final result
+	uint32_t dest_register = target_register;
+	
+	// mandatory self register (initialized to 0 in case of implicit self or explicit super)
+	uint32_r self_list; marray_init(self_list);
+	marray_push(uint32_t, self_list, ((IS_IMPLICIT_SELF(node->id)) || (is_super)) ? 0 : target_register);
+	
+	// process each subnode and set is_assignment flag
+	bool is_assignment = node->base.is_assignment;
+	size_t count = gnode_array_size(node->list);
+	
+	for (size_t i=0; i<count; ++i) {
+		// a subnode can be a CALL_EXPR		=> id.()
+		// a NODE_ACCESS_EXPR				=> id.id2
+		// a NODE_SUBSCRIPT_EXPR			=> id[]
+		// or ANY combination of the them!	=> id.id2.id3()[24]().id5()
+		gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, i);
+		
+		// identify postfix type: NODE_CALL_EXPR, NODE_ACCESS_EXPR, NODE_SUBSCRIPT_EXPR
+		gnode_n tag = subnode->base.tag;
+		
+		// check assignment flag
+		bool is_real_assigment = (is_assignment && IS_LAST_LOOP(i, count));
+		
+		if (tag == NODE_CALL_EXPR) {
+			// a CALL instruction needs to properly prepare stack before execution
+			// format is CALL A B C
+			
+			// where A is the destination register
+			// B is the callable object
+			// and C is the number of arguments passed to the CALL
+			// arguments must be on the stack (so gravity VM can use a register window in order to speed up instruction)
+			// and are expected to be starting from B+1
+			
+			// check dest register
+			bool dest_is_temp = ircode_register_istemp(code, dest_register);
+			if (!dest_is_temp) dest_register = ircode_register_push_temp(code);
+			
+			// add target register (must be temp)
+			uint32_t temp_target_register = ircode_register_push_temp(code);
+			ircode_add(code, MOVE, temp_target_register, target_register, 0);
+			ircode_register_pop_protect(code, true);
+			
+			// always add SELF parameter (must be temp+1)
+			uint32_t self_register = marray_pop(self_list);
+			uint32_t temp_self_register = ircode_register_push_temp(code);
+			ircode_add(code, MOVE, temp_self_register, self_register, 0);
+			ircode_register_pop_protect(code, true);
+			
+			// process each parameter (each must be temp+2 ... temp+n)
+			marray_decl_init(uint32_r, args);
+			size_t n = gnode_array_size(subnode->args);
+			for (size_t j=0; j<n; ++j) {
+				// process each argument
+				gnode_t *arg = (gnode_t *)gnode_array_get(subnode->args, j);
+				visit(arg);
+				uint32_t nreg = ircode_register_pop_protect(code, true);
+				// make sure args are in consecutive register locations (from temp_target_register + 1 to temp_target_register + n)
+				if (nreg != temp_target_register + j + 2) {
+					uint32_t temp = ircode_register_push_temp(code);
+					if (temp == 0) return; // temp value == 0 means codegen error (error will be automatically reported later in visit_function_decl
+					ircode_add(code, MOVE, temp, nreg, 0);
+					nreg = ircode_register_pop_protect(code, true);
+				}
+				assert(nreg == temp_target_register + j + 2);
+				marray_push(uint32_t, args, nreg);
+			}
+			
+			// generate instruction CALL with count parameters (taking in account self)
+			ircode_add(code, CALL, dest_register, temp_target_register, (uint32_t)n+1);
+			
+			// cleanup temp registers
+			ircode_register_clean(code, temp_target_register);
+			ircode_register_clean(code, temp_self_register);
+			n = gnode_array_size(subnode->args);
+			for (size_t j=0; j<n; ++j) {
+				uint32_t reg = marray_get(args, j);
+				ircode_register_clean(code, reg);
+			}
+			
+			// update self list
+			marray_push(uint32_t, self_list, dest_register);
+			
+			// a call returns a value
+			if (IS_LAST_LOOP(i, count)) {
+				if (ircode_register_count(code)) {
+					// code added in order to protect the extra register pushed in case
+					// of code like: f(20)(30)
+					uint32_t last_register = ircode_register_last(code);
+					if (dest_is_temp && last_register == dest_register) dest_is_temp = false;
+				}
+				if (dest_is_temp) ircode_register_push(code, dest_register);
+				ircode_register_protect(code, dest_register);
+			}
+			
+			// free temp args array
+			marray_destroy(args);
+			
+		} else if (tag == NODE_ACCESS_EXPR) {
+			// process identifier node (semantic check assures that each node is an identifier)
+			gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)subnode->expr;
+			uint32_t index = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, expr->value));
+			uint32_t index_register = ircode_register_push_temp(code);
+			ircode_add(code, LOADK, index_register, index, 0);
+			ircode_register_pop(code);
+			
+			// generate LOAD/STORE instruction
+			dest_register = (is_real_assigment) ? ircode_register_pop(code) : ircode_register_push_temp(code);
+			if (is_super) ircode_add(code, LOADS, dest_register, target_register, index_register);
+			else ircode_add(code, (is_real_assigment) ? STORE : LOAD, dest_register, target_register, index_register);
+			if ((!is_real_assigment) && (i+1<count)) ircode_register_pop_protect(code, true);
+			
+			// update self list (if latest instruction)
+			// this was added in order to properly emit instructions for nested_class.gravity unit test
+			if (!IS_LAST_LOOP(i, count)) {
+				gnode_postfix_subexpr_t *nextnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, i+1);
+				if (nextnode->base.tag != NODE_CALL_EXPR) marray_push(uint32_t, self_list, dest_register);
+			}
+			
+		} else if (tag == NODE_SUBSCRIPT_EXPR) {
+			// process index
+			visit(subnode->expr);
+			uint32_t index = ircode_register_pop(code);
+			
+			// generate LOADAT/STOREAT instruction
+			dest_register = (is_real_assigment) ? ircode_register_pop(code) : ircode_register_push_temp(code);
+			ircode_add(code, (is_assignment) ? STOREAT : LOADAT, dest_register, target_register, index);
+			if ((!is_real_assigment) && (i+1<count)) ircode_register_pop_protect(code, true);
+		}
+		
+		// reset is_super flag
+		is_super = false;
+		
+		// update target
+		target_register = dest_register;
+	}
+	
+	marray_destroy(self_list);
+	ircode_pop_context(code);
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, (is_assignment) ? -1 : 1);
+}
+
+static void visit_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
+	DEBUG_CODEGEN("visit_file_expr");
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	// check if the node is a left expression of an assignment
+	bool is_assignment = node->base.is_assignment;
+	
+	size_t count = gnode_array_size(node->identifiers);
+	for (size_t i=0; i<count; ++i) {
+		const char *identifier = gnode_array_get(node->identifiers, i);
+		uint16_t kindex = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, identifier));
+		
+		if ((is_assignment) && (IS_LAST_LOOP(i, count)))
+			ircode_add(code, STOREG, ircode_register_pop(code), kindex, 0);
+		else
+			ircode_add(code, LOADG, ircode_register_push_temp(code), kindex, 0);
+	}
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, (is_assignment) ? -1 : 1);
+}
+
+static void visit_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {
+	/*
+	 
+	 NOTE:
+	 
+	 doubles and int64 should be added to the constant pool but I avoid
+	 adding them here so the optimizer has a way to perform better constant folding:
+	 http://en.wikipedia.org/wiki/Constant_folding
+	 http://www.compileroptimizations.com/category/constant_folding.htm
+	 
+	 */
+	
+	DEBUG_CODEGEN("visit_literal_expr");
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	switch (node->type) {
+		case LITERAL_STRING: {
+			// LOADK temp, s
+			uint16_t index = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_STRING(NULL, node->value.str, node->len));
+			ircode_add_constant(code, index);
+			DEBUG_CODEGEN("visit_literal_expr (string) %s", node->value.str);
+		} break;
+			
+		case LITERAL_FLOAT:
+			// LOADI temp, d
+			ircode_add_double(code, node->value.d);
+			DEBUG_CODEGEN("visit_literal_expr (float) %.2f", node->value.d);
+			break;
+		
+		case LITERAL_INT:
+			// LOADI temp, n
+			ircode_add_int(code, node->value.n64);
+			DEBUG_CODEGEN("visit_literal_expr (int) %lld", node->value.n64);
+			break;
+			
+		case LITERAL_BOOL: {
+			uint32_t value = (node->value.n64 == 0) ? CPOOL_VALUE_FALSE : CPOOL_VALUE_TRUE;
+			ircode_add_constant(code, value);
+			DEBUG_CODEGEN("visit_literal_expr (bool) %lld", node->value.n64);
+		} break;
+			
+		default: assert(0);
+	}
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, 1);
+}
+
+static void visit_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *node) {
+	DEBUG_CODEGEN("visit_identifier_expr %s", node->value);
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	// check if the node is a left expression of an assignment
+	bool is_assignment = node->base.is_assignment;
+	
+	const char			*identifier = node->value;		// identifier as c string
+	gnode_location_type	type = node->location.type;		// location type
+	uint16_t			index = node->location.index;	// symbol index
+	uint16_t			nup = node->location.nup;		// upvalue index or outer index
+	
+	switch (type) {
+			
+		// local variable
+		case LOCATION_LOCAL: {
+			if (is_assignment)
+				ircode_add(code, MOVE, index, ircode_register_pop(code), 0);
+			else
+				ircode_register_push(code, index);
+		} break;
+		
+		// module (global) variable
+		case LOCATION_GLOBAL: {
+			uint16_t kindex = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, identifier));
+			if (is_assignment)
+				ircode_add(code, STOREG, ircode_register_pop(code), kindex, 0);
+			else
+				ircode_add(code, LOADG, ircode_register_push_temp(code), kindex, 0);
+		} break;
+		
+		// upvalue access
+		case LOCATION_UPVALUE: {
+			gupvalue_t *upvalue = node->upvalue;
+			if (is_assignment)
+				ircode_add(code, STOREU, ircode_register_pop(code), upvalue->selfindex, 0);
+			else
+				ircode_add(code, LOADU, ircode_register_push_temp(code), upvalue->selfindex, 0);
+		} break;
+			
+		// class ivar case (outer case just need a loop lookup before)
+		case LOCATION_CLASS_IVAR_OUTER:
+		case LOCATION_CLASS_IVAR_SAME: {
+			// needs to differentiate ivar (indexed by an integer) from other cases (indexed by a string)
+			bool		is_ivar = (index != UINT16_MAX);
+			uint32_t	dest = 0;
+			uint32_t	target = 0;
+			
+			if (type == LOCATION_CLASS_IVAR_OUTER) {
+				dest = ircode_register_push_temp(code);
+				for (uint16_t i=0; i<nup; ++i) {
+					ircode_add(code, LOAD, dest, target, 0 + MAX_REGISTERS);
+					target = dest;
+				}
+				if (is_assignment) ircode_register_pop(code);
+			}
+			
+			uint32_t index_register;
+			if (is_ivar) {
+				// ivar case, use an index to load/retrieve it
+				index_register = index + MAX_REGISTERS;
+			} else {
+				// not an ivar so it could be another class declaration like a func, a class or an enum
+				// use lookup in order to retrieve it (assignment is handled here so you can change a
+				// first class citizen at runtime too)
+				uint16_t kindex = gravity_function_cpool_add(GET_VM(), context_function, VALUE_FROM_CSTRING(NULL, identifier));
+				index_register = ircode_register_push_temp(code);
+				ircode_add(code, LOADK, index_register, kindex, 0);
+				ircode_register_pop(code);
+			}
+			
+			if (is_assignment) {
+				// should be prohibited by semantic to store something into a non ivar slot?
+				dest = ircode_register_pop(code); // consume temp register
+				ircode_add(code, STORE, dest, target, index_register);
+			} else {
+				dest = (type == LOCATION_CLASS_IVAR_OUTER) ? target : ircode_register_push_temp(code);
+				ircode_add(code, LOAD, dest , target, index_register);
+			}
+		} break;
+	}
+		
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, (is_assignment) ? -1 : 1);
+}
+
+static void visit_keyword_expr (gvisitor_t *self, gnode_keyword_expr_t *node) {
+	DEBUG_CODEGEN("visit_keyword_expr %s", token_name(node->base.token.type));
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	switch (type) {
+		case TOK_KEY_CURRFUNC:
+			ircode_add_constant(code, CPOOL_VALUE_FUNC);
+			break;
+			
+		case TOK_KEY_NULL:
+			ircode_add_constant(code, CPOOL_VALUE_NULL);
+			break;
+			
+		case TOK_KEY_SUPER:
+			ircode_add_constant(code, CPOOL_VALUE_SUPER);
+			break;
+			
+		case TOK_KEY_CURRARGS:
+			// compiler can know in advance if arguments special array is used
+			context_function->useargs = true;
+			ircode_add_constant(code, CPOOL_VALUE_ARGUMENTS);
+			break;
+			
+		case TOK_KEY_UNDEFINED:
+			ircode_add_constant(code, CPOOL_VALUE_UNDEFINED);
+			break;
+			
+		case TOK_KEY_TRUE:
+			ircode_add_constant(code, CPOOL_VALUE_TRUE);
+			break;
+			
+		case TOK_KEY_FALSE:
+			ircode_add_constant(code, CPOOL_VALUE_FALSE);
+			break;
+			
+		default:
+			// should never reach this point
+			assert(0);
+			break;
+	}
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, 1);
+}
+
+static void visit_list_expr (gvisitor_t *self, gnode_list_expr_t *node) {
+	DEBUG_CODEGEN("visit_list_expr");
+	DECLARE_CODE();
+	
+	CODEGEN_COUNT_REGISTERS(n1);
+	
+	bool	 ismap = node->ismap;
+	uint32_t n = (uint32_t)gnode_array_size(node->list1);
+	
+	// a map requires twice registers than a list
+	uint32_t max_fields = (ismap) ? MAX_FIELDSxFLUSH : MAX_FIELDSxFLUSH*2;
+	
+	// destination register of a new instruction is ALWAYS a temp register
+	// then the optimizer could decide to optimize and merge the step
+	uint32_t dest = ircode_register_push_temp(code);
+	ircode_add(code, (ismap) ? MAPNEW : LISTNEW, dest, n, 0);
+	if (n == 0) return;
+	
+	// this is just like Lua "fields per flush"
+	// basically nodes are processed in a finite chunk
+	// and then added to the list/map
+	uint32_t nloops = n / max_fields;
+	if (n % max_fields != 0) ++nloops;
+	uint32_t nprocessed = 0;
+	
+	while (nprocessed < n) {
+		size_t k = (n - nprocessed > max_fields) ? max_fields : (n - nprocessed);
+		size_t idxstart = nprocessed;
+		size_t idxend = nprocessed + k;
+		nprocessed += k;
+		
+		// check if this chunk can be optimized
+		// if (check_literals_list(self, node, ismap, idxstart, idxend, dest)) continue;
+		
+		// save register context
+		ircode_push_context(code);
+			
+		// process each node
+		for (size_t j=idxstart; j<idxend; ++j) {
+			gnode_t *e = gnode_array_get(node->list1, j);
+			visit(e);
+			uint32_t nreg = ircode_register_pop_protect(code, true);
+			if (!ircode_register_istemp(code, nreg)) {
+				uint32_t temp_register = ircode_register_push_temp(code);
+				ircode_add(code, MOVE, temp_register, nreg, 0);
+				ircode_register_pop_protect(code, true);
+			}
+			if (ismap) {
+				e = gnode_array_get(node->list2, j);
+				visit(e);
+				nreg = ircode_register_pop_protect(code, true);
+				if (!ircode_register_istemp(code, nreg)) {
+					uint32_t temp_register = ircode_register_push_temp(code);
+					ircode_add(code, MOVE, temp_register, nreg, 0);
+					ircode_register_pop_protect(code, true);
+				}
+			}
+		}
+		
+		// emit proper SETLIST instruction
+		// since in a map registers are always used in pairs (key, value) it is
+		// extremely easier to just set reg1 to be always 0 and use r in a loop
+		ircode_add(code, SETLIST, dest, (uint32_t)(idxend-idxstart), 0);
+		
+		// restore register context
+		ircode_pop_context(code);
+	}
+	
+	CODEGEN_COUNT_REGISTERS(n2);
+	CODEGEN_ASSERT_REGISTERS(n1, n2, 1);
+}
+
+// MARK: -
+
+gravity_function_t *gravity_codegen(gnode_t *node, gravity_delegate_t *delegate, gravity_vm *vm) {
+	codegen_t data;
+	data.vm = vm;
+	marray_init(data.context);
+	marray_init(data.superfix);
+	
+	ircode_t *code = ircode_create(0);
+	gravity_function_t *f = gravity_function_new(vm, INITMODULE_NAME, 0, 0, 0, code);
+	marray_push(gravity_object_t*, data.context, (gravity_object_t *)f);
+	
+	gvisitor_t visitor = {
+		.nerr = 0,						// used for internal codegen errors
+		.data = &data,					// used to store a pointer to codegen struct
+		.delegate = (void *)delegate,	// compiler delegate to report errors
+		
+		// STATEMENTS: 7
+		.visit_list_stmt = visit_list_stmt,
+		.visit_compound_stmt = visit_compound_stmt,
+		.visit_label_stmt = visit_label_stmt,
+		.visit_flow_stmt = visit_flow_stmt,
+		.visit_loop_stmt = visit_loop_stmt,
+		.visit_jump_stmt = visit_jump_stmt,
+		.visit_empty_stmt = visit_empty_stmt,
+		
+		// DECLARATIONS: 5
+		.visit_function_decl = visit_function_decl,
+		.visit_variable_decl = visit_variable_decl,
+		.visit_enum_decl = visit_enum_decl,
+		.visit_class_decl = visit_class_decl,
+		.visit_module_decl = visit_module_decl,
+		
+		// EXPRESSIONS: 8
+		.visit_binary_expr = visit_binary_expr,
+		.visit_unary_expr = visit_unary_expr,
+		.visit_file_expr = visit_file_expr,
+		.visit_literal_expr = visit_literal_expr,
+		.visit_identifier_expr = visit_identifier_expr,
+		.visit_keyword_expr = visit_keyword_expr,
+		.visit_list_expr = visit_list_expr,
+		.visit_postfix_expr = visit_postfix_expr,
+	};
+	
+	DEBUG_CODEGEN("=== BEGIN CODEGEN ===");
+	gvisit(&visitor, node);
+	DEBUG_CODEGEN("\n");
+	
+	// check for special superfix
+	if (marray_size(data.superfix)) {
+		fix_superclasses(&visitor);
+	}
+	
+	// pop globals instance init special function
+	marray_pop(data.context);	
+	assert(marray_size(data.context) == 0);
+	marray_destroy(data.context);
+	
+	// in case of codegen errors explicity free code and return NULL
+	if (visitor.nerr != 0) {ircode_free(code); f->bytecode = NULL;}
+	return (visitor.nerr == 0) ? f : NULL;
+}

+ 18 - 0
src/compiler/gravity_codegen.h

@@ -0,0 +1,18 @@
+//
+//  gravity_codegen.h
+//  gravity
+//
+//  Created by Marco Bambini on 09/10/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_CODEGEN__
+#define __GRAVITY_CODEGEN__
+
+#include "gravity_ast.h"
+#include "gravity_value.h"
+#include "gravity_delegate.h"
+
+gravity_function_t *gravity_codegen(gnode_t *node, gravity_delegate_t *delegate, gravity_vm *vm);
+
+#endif

+ 206 - 0
src/compiler/gravity_compiler.c

@@ -0,0 +1,206 @@
+//
+//  gravity_compiler.c
+//  gravity
+//
+//  Created by Marco Bambini on 29/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_compiler.h"
+#include "gravity_parser.h"
+#include "gravity_token.h"
+#include "gravity_utils.h"
+#include "gravity_semacheck1.h"
+#include "gravity_semacheck2.h"
+#include "gravity_optimizer.h"
+#include "gravity_codegen.h"
+#include "gravity_array.h"
+#include "gravity_hash.h"
+#include "gravity_core.h"
+
+struct gravity_compiler_t {
+	gravity_parser_t		*parser;
+	gravity_delegate_t		*delegate;
+	cstring_r				*storage;
+	gravity_vm				*vm;
+	gnode_t					*ast;
+	void_r					*objects;
+};
+
+static void internal_vm_transfer (gravity_vm *vm, gravity_object_t *obj) {
+	gravity_compiler_t *compiler = (gravity_compiler_t *)gravity_vm_getdata(vm);
+	marray_push(void*, *compiler->objects, obj);
+}
+
+static void internal_free_class (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused (hashtable, data)
+	
+	// sanity checks
+	if (!VALUE_ISA_FUNCTION(value)) return;
+	if (!VALUE_ISA_STRING(key)) return;
+	
+	// check for special function
+	gravity_function_t *f = VALUE_AS_FUNCTION(value);
+	if (f->tag == EXEC_TYPE_SPECIAL) {
+		if (f->special[0]) gravity_function_free(NULL, (gravity_function_t *)f->special[0]);
+		if (f->special[1]) gravity_function_free(NULL, (gravity_function_t *)f->special[1]);
+	}
+	
+	// a super special init constructor is a string that begins with $init AND it is longer than strlen($init)
+	gravity_string_t *s = VALUE_AS_STRING(key);
+	bool is_super_function = ((s->len > 5) && (string_casencmp(s->s, CLASS_INTERNAL_INIT_NAME, 5) == 0));
+	if (!is_super_function) gravity_function_free(NULL, VALUE_AS_FUNCTION(value));
+}
+
+static void internal_vm_cleanup (gravity_vm *vm) {
+	gravity_compiler_t *compiler = (gravity_compiler_t *)gravity_vm_getdata(vm);
+	size_t count = marray_size(*compiler->objects);
+	for (size_t i=0; i<count; ++i) {
+		gravity_object_t *obj = marray_pop(*compiler->objects);
+		if (OBJECT_ISA_CLASS(obj)) {
+			gravity_class_t *c = (gravity_class_t *)obj;
+			gravity_hash_iterate(c->htable, internal_free_class, NULL);
+		}
+		gravity_object_free(vm, obj);
+	}
+}
+
+// MARK: -
+
+gravity_compiler_t *gravity_compiler_create (gravity_delegate_t *delegate) {
+	gravity_compiler_t *compiler = mem_alloc(sizeof(gravity_compiler_t));
+	if (!compiler) return NULL;
+	
+	compiler->ast = NULL;
+	compiler->objects = void_array_create();
+	compiler->delegate = delegate;
+	return compiler;
+}
+
+static void gravity_compiler_reset (gravity_compiler_t *compiler, bool free_core) {
+	// free memory for array of strings storage
+	if (compiler->storage) {
+		cstring_array_each(compiler->storage, {mem_free((void *)val);});
+		gnode_array_free(compiler->storage);
+	}
+	
+	// first ast then parser, don't change the release order
+	if (compiler->ast) gnode_free(compiler->ast);
+	if (compiler->parser) gravity_parser_free(compiler->parser);
+	
+	// at the end free mini VM and objects array
+	if (compiler->vm) gravity_vm_free(compiler->vm);
+	if (compiler->objects) {
+		marray_destroy(*compiler->objects);
+		mem_free((void*)compiler->objects);
+	}
+	
+	// feel free to free core if someone requires it
+	if (free_core) gravity_core_free();
+	
+	// reset internal pointers
+	compiler->vm = NULL;
+	compiler->ast = NULL;
+	compiler->parser = NULL;
+	compiler->objects = NULL;
+	compiler->storage = NULL;
+}
+
+void gravity_compiler_free (gravity_compiler_t *compiler) {
+	gravity_compiler_reset(compiler, true);
+	mem_free(compiler);
+}
+
+gnode_t *gravity_compiler_ast (gravity_compiler_t *compiler) {
+	return compiler->ast;
+}
+
+void gravity_compiler_transfer(gravity_compiler_t *compiler, gravity_vm *vm) {
+	if (!compiler->objects) return;
+	
+	// transfer each object from compiler mini VM to exec VM
+	gravity_gc_setenabled(vm, false);
+	size_t count = marray_size(*compiler->objects);
+	for (size_t i=0; i<count; ++i) {
+		gravity_object_t *obj = marray_pop(*compiler->objects);
+		gravity_vm_transfer(vm, obj);
+		if (!OBJECT_ISA_CLOSURE(obj)) continue;
+		
+		// $moduleinit closure needs to be explicitly initialized
+		gravity_closure_t *closure = (gravity_closure_t *)obj;
+		if ((closure->f->identifier) && strcmp(closure->f->identifier, INITMODULE_NAME) == 0) {
+			// code is here because it does not make sense to add this overhead (that needs to be executed only once)
+			// inside the gravity_vm_transfer callback which is called for each allocated object inside the VM
+			gravity_vm_initmodule(vm, closure->f);
+		}
+	}
+	
+	gravity_gc_setenabled(vm, true);
+}
+
+// MARK: -
+
+gravity_closure_t *gravity_compiler_run (gravity_compiler_t *compiler, const char *source, size_t len, uint32_t fileid, bool is_static) {
+	if ((source == NULL) || (len == 0)) return NULL;
+	
+	// CHECK cleanup first
+	if (compiler->ast) gnode_free(compiler->ast);
+	if (!compiler->objects) compiler->objects = void_array_create();
+	
+	// CODEGEN requires a mini vm in order to be able to handle garbage collector
+	compiler->vm = gravity_vm_newmini();
+	gravity_vm_setdata(compiler->vm, (void *)compiler);
+	gravity_vm_set_callbacks(compiler->vm, internal_vm_transfer, internal_vm_cleanup);
+	gravity_core_register(compiler->vm);
+	
+	// STEP 0: CREATE PARSER
+	compiler->parser = gravity_parser_create(source, len, fileid, is_static);
+	if (!compiler->parser) return NULL;
+	
+	// STEP 1: SYNTAX CHECK
+	compiler->ast = gravity_parser_run(compiler->parser, compiler->delegate);
+	if (!compiler->ast) goto abort_compilation;
+	gravity_parser_free(compiler->parser);
+	compiler->parser = NULL;
+	
+	// STEP 2a: SEMANTIC CHECK (NON-LOCAL DECLARATIONS)
+	bool b1 = gravity_semacheck1(compiler->ast, compiler->delegate);
+	if (!b1) goto abort_compilation;
+	
+	// STEP 2b: SEMANTIC CHECK (LOCAL DECLARATIONS)
+	bool b2 = gravity_semacheck2(compiler->ast, compiler->delegate);
+	if (!b2) goto abort_compilation;
+	
+	// STEP 3: INTERMEDIATE CODE GENERATION (stack based VM)
+	gravity_function_t *f = gravity_codegen(compiler->ast, compiler->delegate, compiler->vm);
+	if (!f) goto abort_compilation;
+	
+	// STEP 4: CODE GENERATION (register based VM)
+	f = gravity_optimizer(f);
+	if (f) return gravity_closure_new(compiler->vm, f);
+	
+abort_compilation:
+	gravity_compiler_reset(compiler, false);
+	return NULL;
+}
+
+json_t *gravity_compiler_serialize (gravity_compiler_t *compiler, gravity_closure_t *closure) {
+	#pragma unused(compiler)
+	
+	json_t *json = json_new();
+	json_begin_object(json, NULL);
+	
+	gravity_function_serialize(closure->f, json);
+	
+	json_end_object(json);
+	return json;
+}
+
+bool gravity_compiler_serialize_infile (gravity_compiler_t *compiler, gravity_closure_t *closure, const char *path) {
+	if (!closure) return false;
+	json_t *json = gravity_compiler_serialize(compiler, closure);
+	if (!json) return false;
+	json_write_file(json, path);
+	json_free(json);
+	return true;
+}

+ 29 - 0
src/compiler/gravity_compiler.h

@@ -0,0 +1,29 @@
+//
+//  gravity_compiler.h
+//  gravity
+//
+//  Created by Marco Bambini on 29/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_COMPILER__
+#define __GRAVITY_COMPILER__
+
+#include "gravity_delegate.h"
+#include "debug_macros.h"
+#include "gravity_utils.h"
+#include "gravity_value.h"
+#include "gravity_ast.h"
+
+// opaque compiler data type
+typedef struct gravity_compiler_t	gravity_compiler_t;
+
+gravity_compiler_t	*gravity_compiler_create (gravity_delegate_t *delegate);
+gravity_closure_t	*gravity_compiler_run (gravity_compiler_t *compiler, const char *source, size_t len, uint32_t fileid, bool is_static);
+json_t				*gravity_compiler_serialize (gravity_compiler_t *compiler, gravity_closure_t *closure);
+bool				gravity_compiler_serialize_infile (gravity_compiler_t *compiler, gravity_closure_t *closure, const char *path);
+void				gravity_compiler_transfer (gravity_compiler_t *compiler, gravity_vm *vm);
+gnode_t				*gravity_compiler_ast (gravity_compiler_t *compiler);
+void				gravity_compiler_free (gravity_compiler_t *compiler);
+
+#endif

+ 498 - 0
src/compiler/gravity_ircode.c

@@ -0,0 +1,498 @@
+//
+//  gravity_ircode.c
+//  gravity
+//
+//  Created by Marco Bambini on 06/11/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_ircode.h"
+#include "gravity_value.h"
+#include "gravity_debug.h"
+
+typedef marray_t(inst_t *)		code_r;
+typedef marray_t(bool *)		context_r;
+
+struct ircode_t {
+	code_r		*list;					// array of ircode instructions
+	
+	uint32_r	label_true;				// labels used in loops
+	uint32_r	label_false;
+	uint32_t	label_counter;
+	
+	uint32_t	maxtemp;				// maximum number of temp registers used in this ircode
+	uint32_t	ntemps;					// current number of temp registers in use
+	uint16_t	nlocals;				// number of local registers (params + local variables)
+	bool		error;					// error flag set when no more registers are availables
+	
+	bool		state[MAX_REGISTERS];	// registers mask
+	uint32_r	registers;				// registers stack
+	context_r	context;				// context array
+};
+
+ircode_t *ircode_create (uint16_t nlocals) {
+	ircode_t *code = (ircode_t *)mem_alloc(sizeof(ircode_t));
+	code->label_counter = 0;
+	code->nlocals = nlocals;
+	code->ntemps = 0;
+	code->maxtemp = 0;
+	code->error = false;
+	
+	code->list = mem_alloc(sizeof(code_r));
+	marray_init(*code->list);
+	marray_init(code->label_true);
+	marray_init(code->label_false);
+	marray_init(code->registers);
+	marray_init(code->context);
+	
+	// init state array (register 0 is reserved)
+	bzero(code->state, MAX_REGISTERS * sizeof(bool));
+	code->state[0] = true;
+	for (uint32_t i=0; i<nlocals; ++i) {
+		code->state[i] = true;
+	}
+	return code;
+}
+
+#if 0
+static void dump_context(bool *context) {
+	for (uint32_t i=0; i<MAX_REGISTERS; ++i) {
+		printf("%d ", context[i]);
+	}
+	printf("\n");
+}
+#endif
+
+void ircode_push_context (ircode_t *code) {
+	bool *context = mem_alloc(sizeof(bool) * MAX_REGISTERS);
+	marray_push(bool *, code->context, context);
+}
+
+void ircode_pop_context (ircode_t *code) {
+	bool *context = marray_pop(code->context);
+	// apply context mask
+	for (uint32_t i=0; i<MAX_REGISTERS; ++i) {
+		if (context[i]) code->state[i] = false;
+	}
+	mem_free(context);
+}
+
+static void ircode_add_context (ircode_t *code, uint32_t n) {
+	bool *context = marray_last(code->context);
+	context[n] = true;
+}
+
+void ircode_free (ircode_t *code) {
+	uint32_t count = ircode_count(code);
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = marray_get(*code->list, i);
+		mem_free(inst);
+	}
+	
+	marray_destroy(*code->list);
+	marray_destroy(code->registers);
+	marray_destroy(code->label_true);
+	marray_destroy(code->label_false);
+	mem_free(code->list);
+	mem_free(code);
+}
+
+uint32_t ircode_ntemps (ircode_t *code) {
+	return code->ntemps;
+}
+
+uint32_t ircode_count (ircode_t *code) {
+	return (uint32_t)marray_size(*code->list);
+}
+
+inst_t *ircode_get (ircode_t *code, uint32_t index) {
+	uint32_t n = (uint32_t)marray_size(*code->list);
+	if (index == IRCODE_LATEST) index = n-1;
+	return (index >= n) ? NULL : marray_get(*code->list, index);
+}
+
+bool ircode_iserror (ircode_t *code) {
+	return code->error;
+}
+// MARK: -
+
+static inst_t *inst_new (opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3, optag_t tag, int64_t n, double d) {
+	
+	// debug code
+	#if GRAVITY_OPCODE_DEBUG
+	if (tag == LABEL_TAG) {
+		DEBUG_OPCODE("LABEL %d", p1);
+	} else {
+		const char *op_name = opcode_name(op);
+		
+		if (op == LOADI) {
+			if (tag == DOUBLE_TAG)
+				printf("%s %d %.2f\n", op_name, p1, d);
+			else
+				printf("%s %d %lld\n", op_name, p1, n);
+		} else {
+			int nop = opcode_numop(op);
+			if (nop == 0) {
+				DEBUG_OPCODE("%s", op_name);
+			} else if (nop == 1) {
+				DEBUG_OPCODE("%s %d", op_name, p1);
+			} else if (nop == 2) {
+				DEBUG_OPCODE("%s %d %d", op_name, p1, p2);
+			} else if (nop == 3) {
+				DEBUG_OPCODE("%s %d %d %d", opcode_name(op), p1, p2, p3);
+			}
+		}
+	}
+	#endif
+	
+	inst_t *inst = (inst_t *)mem_alloc(sizeof(inst_t));
+	inst->op = op;
+	inst->tag = tag;
+	inst->p1 = p1;
+	inst->p2 = p2;
+	inst->p3 = p3;
+	
+	if (tag == DOUBLE_TAG) inst->d = d;
+	else if (tag == INT_TAG) inst->n = n;
+	
+	assert(inst);
+	return inst;
+}
+
+void inst_setskip (inst_t *inst) {
+	inst->tag = SKIP_TAG;
+}
+
+void ircode_patch_init (ircode_t *code, uint16_t index) {
+	// prepend call instructions to code
+	// LOADK temp index
+	// LOAD  temp 0 temp
+	// MOVE  temp+1 0
+	// CALL  temp temp 1
+	
+	// load constant
+	uint32_t dest = ircode_register_push_temp(code);
+	inst_t *inst1 = inst_new(LOADK, dest, index, 0, NO_TAG, 0, 0.0);
+	
+	// load from lookup
+	inst_t *inst2 = inst_new(LOAD, dest, 0, dest, NO_TAG, 0, 0.0);
+	
+	// prepare parameter
+	uint32_t dest2 = ircode_register_push_temp(code);
+	inst_t *inst3 = inst_new(MOVE, dest2, 0, 0, NO_TAG, 0, 0.0);
+	ircode_register_pop(code);
+	
+	// execute call
+	inst_t *inst4 = inst_new(CALL, dest, dest, 1, NO_TAG, 0, 0.0);
+	
+	// pop temps used
+	ircode_register_pop(code);
+	
+	// create new instruction list
+	code_r		*list = mem_alloc(sizeof(code_r));
+	marray_init(*list);
+	
+	// add newly create instructions
+	marray_push(inst_t*, *list, inst1);
+	marray_push(inst_t*, *list, inst2);
+	marray_push(inst_t*, *list, inst3);
+	marray_push(inst_t*, *list, inst4);
+	
+	// then copy original instructions
+	code_r *orig_list = code->list;
+	uint32_t count = ircode_count(code);
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = marray_get(*orig_list, i);
+		marray_push(inst_t*, *list, inst);
+	}
+	
+	// free dest list
+	marray_destroy(*orig_list);
+	mem_free(code->list);
+	
+	// replace dest list with the newly created list
+	code->list = list;
+}
+
+uint8_t opcode_numop (opcode_t op) {
+	switch (op) {
+		case HALT: return 0;
+		case NOP: return 0;
+		case RET0: return 0;
+		case RET: return 1;
+		case CALL: return 3;
+		case SETLIST: return 3;
+		case LOADK: return 2;
+		case LOADG: return 2;
+		case LOADI: return 2;
+		case LOADAT: return 3;
+		case LOADS: return 3;
+		case LOAD: return 3;
+		case LOADU: return 2;
+		case MOVE: return 2;
+		case STOREG: return 2;
+		case STOREAT: return 3;
+		case STORE: return 3;
+		case STOREU: return 2;
+		case JUMP: return 1;
+		case JUMPF: return 2;
+		case SWITCH: return 1;
+		case ADD: return 3;
+		case SUB: return 3;
+		case DIV: return 3;
+		case MUL: return 3;
+		case REM: return 3;
+		case AND: return 3;
+		case OR: return 3;
+		case LT: return 3;
+		case GT: return 3;
+		case EQ: return 3;
+		case ISA: return 3;
+		case MATCH: return 3;
+		case EQQ: return 3;
+		case LEQ: return 3;
+		case GEQ: return 3;
+		case NEQ: return 3;
+		case NEQQ: return 3;
+		case NEG: return 2;
+		case NOT: return 2;
+		case LSHIFT: return 3;
+		case RSHIFT: return 3;
+		case BAND: return 3;
+		case BOR: return 3;
+		case BXOR: return 3;
+		case BNOT: return 2;
+		case MAPNEW: return 2;
+		case LISTNEW: return 2;
+		case RANGENEW: return 3;
+		case CLOSURE: return 2;
+		case CLOSE: return 1;
+		case RESERVED1:
+		case RESERVED2:
+		case RESERVED3:
+		case RESERVED4:
+		case RESERVED5:
+		case RESERVED6: return 0;
+	}
+	
+	assert(0);
+	return 0;
+}
+
+void ircode_dump  (void *_code) {
+	ircode_t	*code = (ircode_t *)_code;
+	code_r		*list = code->list;
+	uint32_t	count = ircode_count(code);
+	
+	if (count == 0) {
+		printf("NONE\n");
+		return;
+	}
+	
+	for (uint32_t i=0, line=0; i<count; ++i) {
+		inst_t *inst = marray_get(*list, i);
+		opcode_t	op = inst->op;
+		int32_t		p1 = inst->p1;
+		int32_t		p2 = inst->p2;
+		int32_t		p3 = inst->p3;
+		if (inst->tag == SKIP_TAG) continue;
+		if (inst->tag == LABEL_TAG) {printf("LABEL %d:\n", p1); continue;}
+		
+		uint8_t n = opcode_numop(op);
+		if ((op == SETLIST) && (p2 == 0)) n = 2;
+		
+		switch (n) {
+			case 0: {
+				printf("%05d\t%s\n", line, opcode_name(op));
+			}
+			
+			case 1: {
+				printf("%05d\t%s %d\n", line, opcode_name(op), p1);
+			} break;
+				
+			case 2: {
+				if (op == LOADI) {
+					if (inst->tag == DOUBLE_TAG) printf("%05d\t%s %d %.2f\n", line, opcode_name(op), p1, inst->d);
+					else printf("%05d\t%s %d %lld\n", line, opcode_name(op), p1, inst->n);
+				} else if (op == LOADK) {
+					if (p2 < CPOOL_INDEX_MAX) printf("%05d\t%s %d %d\n", line, opcode_name(op), p1, p2);
+					else printf("%05d\t%s %d %s\n", line, opcode_name(op), p1, opcode_constname(p2));
+				} else {
+					printf("%05d\t%s %d %d\n", line, opcode_name(op), p1, p2);
+				}
+			} break;
+				
+			case 3: {
+				printf("%05d\t%s %d %d %d\n", line, opcode_name(op), p1, p2, p3);
+			} break;
+				
+			default: assert(0);
+		}
+		++line;
+	}
+}
+
+// MARK: -
+
+uint32_t ircode_newlabel (ircode_t *code) {
+	return ++code->label_counter;
+}
+
+void ircode_setlabel_true (ircode_t *code, uint32_t nlabel) {
+	marray_push(uint32_t, code->label_true, nlabel);
+}
+
+void ircode_setlabel_false (ircode_t *code, uint32_t nlabel) {
+	marray_push(uint32_t, code->label_false, nlabel);
+}
+
+void ircode_unsetlabel_true (ircode_t *code) {
+	marray_pop(code->label_true);
+}
+
+void ircode_unsetlabel_false (ircode_t *code) {
+	marray_pop(code->label_false);
+}
+
+uint32_t ircode_getlabel_true (ircode_t *code) {
+	size_t n = marray_size(code->label_true);
+	uint32_t v = marray_get(code->label_true, n-1);
+	return v;
+}
+
+uint32_t ircode_getlabel_false (ircode_t *code) {
+	size_t n = marray_size(code->label_false);
+	uint32_t v = marray_get(code->label_false, n-1);
+	return v;
+}
+
+void ircode_marklabel (ircode_t *code, uint32_t nlabel) {
+	inst_t *inst = inst_new(0, nlabel, 0, 0, LABEL_TAG, 0, 0.0);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+// MARK: -
+
+void ircode_set_index (uint32_t index, ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3) {
+	inst_t *inst = marray_get(*code->list, index);
+	inst->op = op;
+	inst->p1 = p1;
+	inst->p2 = p2;
+	inst->p3 = p3;
+	inst->tag = NO_TAG;
+}
+
+void ircode_add (ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3) {
+	ircode_add_tag(code, op, p1, p2, p3, 0);
+}
+
+void ircode_add_tag (ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3, optag_t tag) {
+	inst_t *inst = inst_new(op, p1, p2, p3, tag, 0, 0.0);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+void ircode_add_double (ircode_t *code, double d) {
+	uint32_t regnum = ircode_register_push_temp(code);
+	inst_t *inst = inst_new(LOADI, regnum, 0, 0, DOUBLE_TAG, 0, d);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+void ircode_add_constant (ircode_t *code, uint32_t index) {
+	uint32_t regnum = ircode_register_push_temp(code);
+	inst_t *inst = inst_new(LOADK, regnum, index, 0, NO_TAG, 0, 0);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+void ircode_add_int (ircode_t *code, int64_t n) {
+	uint32_t regnum = ircode_register_push_temp(code);
+	inst_t *inst = inst_new(LOADI, regnum, 0, 0, INT_TAG, n, 0);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+void ircode_add_skip (ircode_t *code) {
+	inst_t *inst = inst_new(0, 0, 0, 0, NO_TAG, 0, 0);
+	inst_setskip(inst);
+	marray_push(inst_t*, *code->list, inst);
+}
+
+// MARK: -
+
+static uint32_t ircode_register_new (ircode_t *code) {
+	for (uint32_t i=0; i<MAX_REGISTERS; ++i) {
+		if (code->state[i] == false) {
+			code->state[i] = true;
+			return i;
+		}
+	}
+	// 0 means no registers available
+	code->error = true;
+	return 0;
+}
+
+uint32_t ircode_register_push (ircode_t *code, uint32_t nreg) {
+	marray_push(uint32_t, code->registers, nreg);
+	if (nreg >= code->nlocals) ++code->ntemps;
+	
+	DEBUG_REGISTER("PUSH REGISTER %d", nreg);
+	return nreg;
+}
+
+uint32_t ircode_register_push_temp (ircode_t *code) {
+	uint32_t value = ircode_register_new(code);
+	marray_push(uint32_t, code->registers, value);
+	if (value > code->maxtemp) {code->maxtemp = value; ++code->ntemps;}
+	
+	DEBUG_REGISTER("PUSH REGISTER %d", value);
+	return value;
+}
+
+uint32_t ircode_register_pop (ircode_t *code) {
+	return ircode_register_pop_protect(code, false);
+}
+
+uint32_t ircode_register_pop_protect (ircode_t *code, bool protect) {
+	assert(marray_size(code->registers) != 0);
+	uint32_t value = (uint32_t)marray_pop(code->registers);
+	if (protect) code->state[value] = true;
+	else if (value >= code->nlocals) code->state[value] = false;
+	if (protect && value >= code->nlocals) ircode_add_context(code, value);
+	
+	DEBUG_REGISTER("POP REGISTER %d", value);
+	return value;
+}
+
+void ircode_register_protect (ircode_t *code, uint32_t nreg) {
+	if (nreg < code->nlocals) return;
+	bool *context = marray_last(code->context);
+	context[nreg] = false;
+}
+
+void ircode_register_clean (ircode_t *code, uint32_t nreg) {
+	// cleanup busy mask only if it is a temp register
+	if (nreg >= code->nlocals) code->state[nreg] = false;
+}
+
+uint32_t ircode_register_last (ircode_t *code) {
+	assert(marray_size(code->registers) != 0);
+	uint32_t value = (uint32_t)marray_last(code->registers);
+	
+	return value;
+}
+
+bool ircode_register_istemp (ircode_t *code, uint32_t n) {
+	uint32_t n1 = (uint32_t)code->nlocals;
+	return (n>=n1);
+}
+
+void ircode_register_dump (ircode_t *code) {
+	uint32_t n = (uint32_t)marray_size(code->registers);
+	if (n == 0) printf("EMPTY\n");
+	for (uint32_t i=0; i<n; ++i) {
+		uint32_t value = marray_get(code->registers, i);
+		printf("[%d]\t%d\n", i, value);
+	}
+}
+
+uint32_t ircode_register_count (ircode_t *code) {
+	return (uint32_t)marray_size(code->registers);
+}

+ 101 - 0
src/compiler/gravity_ircode.h

@@ -0,0 +1,101 @@
+//
+//  gravity_ircode.h
+//  gravity
+//
+//  Created by Marco Bambini on 06/11/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_IRCODE__
+#define __GRAVITY_IRCODE__
+
+// References:
+// https://www.usenix.org/legacy/events/vee05/full_papers/p153-yunhe.pdf
+// http://www.lua.org/doc/jucs05.pdf
+//
+// In a stack-based VM, a local variable is accessed using an index, and the operand stack is accessed via the stack pointer.
+// In a register-based VM both the local variables and operand stack can be considered as virtual registers for the method.
+// There is a simple mapping from stack locations to register numbers, because the height and contents of the VM operand stack
+// are known at any point in a program.
+//
+// All values on the operand stack can be considered as temporary variables (registers) for a method and therefore are short-lived.
+// Their scope of life is between the instructions that push them onto the operand stack and the instruction that consumes
+// the value on the operand stack. On the other hand, local variables (also registers) are long-lived and their life scope is
+// the time of method execution.
+
+#include "debug_macros.h"
+#include "gravity_opcodes.h"
+#include "gravity_array.h"
+
+#define IRCODE_LATEST	UINT32_MAX
+
+typedef enum {
+		NO_TAG = 0,
+		INT_TAG,
+		DOUBLE_TAG,
+		LABEL_TAG,
+		SKIP_TAG,
+		RANGE_INCLUDE_TAG,
+		RANGE_EXCLUDE_TAG,
+		PRAGMA_OPTIMIZATION
+} optag_t;
+
+typedef struct {
+	opcode_t	op;
+	optag_t		tag;
+	int32_t		p1;
+	int32_t		p2;
+	int32_t		p3;
+	union {
+		double		d;		//	tag is DOUBLE_TAG
+		int64_t		n;		//	tag is INT_TAG
+	};
+} inst_t;
+
+typedef struct ircode_t ircode_t;
+
+ircode_t	*ircode_create (uint16_t nlocals);
+void		ircode_free (ircode_t *code);
+uint32_t	ircode_count (ircode_t *code);
+uint32_t	ircode_ntemps (ircode_t *code);
+inst_t		*ircode_get (ircode_t *code, uint32_t index);
+void		ircode_dump (void *code);
+void		ircode_push_context (ircode_t *code);
+void		ircode_pop_context (ircode_t *code);
+bool		ircode_iserror (ircode_t *code);
+void		ircode_patch_init (ircode_t *code, uint16_t index);
+
+uint32_t	ircode_newlabel (ircode_t *code);
+void		ircode_setlabel_true (ircode_t *code, uint32_t nlabel);
+void		ircode_setlabel_false (ircode_t *code, uint32_t nlabel);
+void		ircode_unsetlabel_true (ircode_t *code);
+void		ircode_unsetlabel_false (ircode_t *code);
+uint32_t	ircode_getlabel_true (ircode_t *code);
+uint32_t	ircode_getlabel_false (ircode_t *code);
+void		ircode_marklabel (ircode_t *code, uint32_t nlabel);
+
+void		inst_setskip (inst_t *inst);
+uint8_t		opcode_numop (opcode_t op);
+
+void		ircode_add (ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3);
+void		ircode_add_tag (ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3, optag_t tag);
+void		ircode_add_array (ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3, uint32_r r);
+void		ircode_add_double (ircode_t *code, double d);
+void		ircode_add_int (ircode_t *code, int64_t n);
+void		ircode_add_constant (ircode_t *code, uint32_t index);
+void		ircode_add_skip (ircode_t *code);
+void		ircode_set_index (uint32_t index, ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3);
+void		ircode_setarray_index (uint32_t index, ircode_t *code, opcode_t op, uint32_t p1, uint32_t p2, uint32_t p3, uint32_r r);
+
+bool		ircode_register_istemp (ircode_t *code, uint32_t n);
+uint32_t	ircode_register_push_temp (ircode_t *code);
+uint32_t	ircode_register_push (ircode_t *code, uint32_t nreg);
+uint32_t	ircode_register_pop (ircode_t *code);
+uint32_t	ircode_register_pop_protect (ircode_t *code, bool protect);
+void		ircode_register_protect (ircode_t *code, uint32_t nreg);
+uint32_t	ircode_register_last (ircode_t *code);
+uint32_t	ircode_register_count (ircode_t *code);
+void		ircode_register_clean (ircode_t *code, uint32_t nreg);
+void		ircode_register_dump (ircode_t *code);
+
+#endif

+ 620 - 0
src/compiler/gravity_lexer.c

@@ -0,0 +1,620 @@
+//
+//  gravity_lexer.c
+//  gravity
+//
+//  Created by Marco Bambini on 30/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+// ASCII Table: http://www.theasciicode.com.ar
+
+#include "gravity_lexer.h"
+#include "gravity_token.h"
+#include "gravity_utils.h"
+
+struct gravity_lexer_t {
+	const char					*buffer;		// buffer
+	uint32_t					offset;			// current buffer offset (in bytes)
+	uint32_t					position;		// current buffer position (in characters)
+	uint32_t					length;			// buffer length (in bytes)
+	uint32_t					lineno;			// line counter
+	uint32_t					colno;			// column counter
+	uint32_t					fileid;			// current file id
+	
+	gtoken_s					token;			// current token
+	bool						peeking;		// flag to check if a peek operation is in progress
+	bool						is_static;		// flag to check if buffer is static and must not be freed
+	gravity_delegate_t			*delegate;		// delegate if any
+};
+
+typedef enum {
+	NUMBER_INTEGER,
+	NUMBER_HEX,
+	NUMBER_BIN,
+	NUMBER_OCT
+} gravity_number_type;
+
+
+// LEXER macros
+#define NEXT					lexer->buffer[lexer->offset++]; ++lexer->position; INC_COL
+#define PEEK_CURRENT			lexer->buffer[lexer->offset]
+#define PEEK_NEXT				((lexer->offset < lexer->length) ? lexer->buffer[lexer->offset+1] : 0)
+#define PEEK_NEXT2				((lexer->offset+1 < lexer->length) ? lexer->buffer[lexer->offset+2] : 0)
+#define INC_LINE				++lexer->lineno; RESET_COL
+#define INC_COL					++lexer->colno
+#define DEC_COL					--lexer->colno
+#define RESET_COL				lexer->colno = 1
+#define IS_EOF					(lexer->offset >= lexer->length)
+#define DEC_OFFSET				--lexer->offset; DEC_COL
+#define DEC_POSITION			--lexer->position
+#define DEC_OFFSET_POSITION		DEC_OFFSET; DEC_POSITION
+#define INC_OFFSET				++lexer->offset; INC_COL
+#define INC_POSITION			++lexer->position
+#define INC_OFFSET_POSITION		INC_OFFSET; INC_POSITION
+
+// TOKEN macros
+#define TOKEN_RESET				lexer->token = NO_TOKEN; lexer->token.position = lexer->position; lexer->token.value = lexer->buffer + lexer->offset;	\
+								lexer->token.lineno = lexer->lineno; lexer->token.colno = lexer->colno
+#define TOKEN_FINALIZE(t)		lexer->token.type = t; lexer->token.fileid = lexer->fileid
+#define INC_TOKBYTES			++lexer->token.bytes
+#define INC_TOKUTF8LEN			++lexer->token.length
+#define INC_TOKLEN				INC_TOKBYTES; INC_TOKUTF8LEN
+#define DEC_TOKLEN				--lexer->token.bytes; --lexer->token.length
+#define SET_TOKESCAPED(value)	lexer->token.escaped = value
+#define SET_TOKTYPE(t)			lexer->token.type = t
+
+#define LEXER_CALL_CALLBACK()	if ((lexer->peeking == false) && (lexer->delegate) && (lexer->delegate->parser_callback)) {	\
+									lexer->delegate->parser_callback(&lexer->token, lexer->delegate->xdata); }
+
+// MARK: -
+
+static inline bool is_whitespace (int c) {
+	return ((c == ' ') || (c == '\t') || (c == '\v') || (c == '\f'));
+}
+
+static inline bool is_newline (gravity_lexer_t *lexer, int c) {
+	// CR: Carriage Return, U+000D (UTF-8 in hex: 0D)
+	// LF: Line Feed, U+000A (UTF-8 in hex: 0A)
+	// CR+LF: CR (U+000D) followed by LF (U+000A) (UTF-8 in hex: 0D0A)
+	
+	// LF
+	if (c == 0x0A) return true;
+	
+	// CR+LF or CR
+	if (c == 0x0D) {
+		if (PEEK_NEXT == 0x0A) {NEXT; return true;}
+		return true;
+	}
+	
+	// UTF-8 cases https://en.wikipedia.org/wiki/Newline#Unicode
+	
+	// NEL: Next Line, U+0085 (UTF-8 in hex: C285)
+	if ((c == 0xC2) && (PEEK_NEXT == 0x85)) {
+		NEXT;
+		return true;
+	}
+	
+	// LS: Line Separator, U+2028 (UTF-8 in hex: E280A8)
+	if ((c == 0xE2) && (PEEK_NEXT == 0x80) && (PEEK_NEXT2 == 0xA8)) {
+		NEXT; NEXT;
+		return true;
+	}
+		
+	// and probably more not handled here
+	return false;
+}
+
+static inline bool is_comment (int c1, int c2) {
+	return (c1 == '/') && ((c2 == '*') || (c2 == '/'));
+}
+
+static inline bool is_semicolon (int c) {
+	return (c == ';');
+}
+
+static inline bool is_alpha (int c) {
+	if (c == '_') return true;
+	return isalpha(c);
+}
+
+static inline bool is_digit (int c, gravity_number_type ntype) {
+	if (ntype == NUMBER_BIN) return (c == '0' || (c == '1'));
+	if (ntype == NUMBER_OCT) return (c >= '0' && (c <= '7'));
+	if ((ntype == NUMBER_HEX) && ((toupper(c) >= 'A' && toupper(c) <= 'F'))) return true;
+	return isdigit(c);
+}
+
+static inline bool is_string (int c) {
+	return ((c == '"') || (c == '\''));
+}
+
+static inline bool is_special (int c) {
+	return (c == '@');
+}
+
+static inline bool is_builtin_operator (int c) {
+	// PARENTHESIS
+	// { } [ ] ( )
+	// PUNCTUATION
+	// . ; : ? ,
+	// OPERATORS
+	// + - * / < > ! = | & ^ % ~
+	
+	return ((c == '+') || (c == '-') || (c == '*') || (c == '/') ||
+			(c == '<') || (c == '>') || (c == '!') || (c == '=') ||
+			(c == '|') || (c == '&') || (c == '^') || (c == '%') ||
+			(c == '~') || (c == '.') || (c == ';') || (c == ':') ||
+			(c == '?') || (c == ',') || (c == '{') || (c == '}') ||
+			(c == '[') || (c == ']') || (c == '(') || (c == ')') );
+}
+
+static inline bool is_preprocessor (int c) {
+	return (c == '#');
+}
+
+static inline bool is_identifier (int c) {
+	// when called I am already sure first character is alpha so next valid characters are alpha, digit and _
+	return ((isalpha(c)) || (isdigit(c)) || (c == '_'));
+}
+
+// MARK: -
+
+static gtoken_t lexer_error(gravity_lexer_t *lexer, const char *message) {
+	if (!IS_EOF) {
+		INC_TOKLEN;
+		INC_OFFSET_POSITION;
+	}
+	TOKEN_FINALIZE(TOK_ERROR);
+
+	lexer->token.value = (char *)message;
+	lexer->token.bytes = (uint32_t)strlen(message);
+	return TOK_ERROR;
+}
+
+static inline int next_utf8(gravity_lexer_t *lexer) {
+	int c = NEXT;
+	INC_TOKLEN;
+	
+	uint32_t len = utf8_charbytes((const char *)&c, 0);
+	if (len == 1) return c;
+	
+	switch(len) {
+		case 0: lexer_error(lexer, "Unknown character inside a string literal"); return 0;
+		case 2: INC_OFFSET; INC_TOKBYTES; break;
+		case 3: INC_OFFSET; INC_OFFSET; INC_TOKBYTES; INC_TOKBYTES; break;
+		case 4: INC_OFFSET; INC_OFFSET; INC_OFFSET; INC_TOKBYTES; INC_TOKBYTES; INC_TOKBYTES; INC_POSITION; INC_TOKUTF8LEN; break;
+	}
+	
+	return c;
+}
+
+static gtoken_t lexer_scan_comment(gravity_lexer_t *lexer) {
+	bool isLineComment = (PEEK_NEXT == '/');
+	
+	TOKEN_RESET;
+	INC_OFFSET_POSITION;
+	INC_OFFSET_POSITION;
+	
+	// because I already scanned /* or //
+	lexer->token.bytes = lexer->token.length = 2;
+	
+	// count necessary only to support nested comments
+	int count = 1;
+	while (!IS_EOF) {
+		int c = next_utf8(lexer);
+		
+		if (isLineComment){
+			if (is_newline(lexer, c)) {INC_LINE; break;}
+		} else {
+			int c2 = PEEK_CURRENT;
+			if ((c == '/') && (c2 == '*')) ++count;
+			if ((c == '*') && (c2 == '/')) {--count; NEXT; INC_TOKLEN; if (count == 0) break;}
+			if (is_newline(lexer, c)) {INC_LINE;}
+		}
+	}
+	
+	// comment is from buffer->[nseek] and it is nlen length
+	TOKEN_FINALIZE(TOK_COMMENT);
+	
+	// comments callback is called directly from the scan function and not from the main scan loop
+	if ((lexer->delegate) && (lexer->delegate->parser_callback)) {
+		lexer->delegate->parser_callback(&lexer->token, lexer->delegate->xdata);
+	}
+	
+	DEBUG_LEXEM("Found comment");
+	return TOK_COMMENT;
+}
+
+static gtoken_t lexer_scan_semicolon(gravity_lexer_t *lexer) {
+	TOKEN_RESET;
+	INC_TOKLEN;
+	INC_OFFSET_POSITION;
+	TOKEN_FINALIZE(TOK_OP_SEMICOLON);
+	
+	return TOK_OP_SEMICOLON;
+}
+
+static gtoken_t lexer_scan_identifier(gravity_lexer_t *lexer) {	
+	TOKEN_RESET;
+	while (is_identifier(PEEK_CURRENT)) {
+		INC_OFFSET_POSITION;
+		INC_TOKLEN;
+	}
+	
+	TOKEN_FINALIZE(TOK_IDENTIFIER);
+	
+	// first check if it is a reserved word, otherwise reports it as an identifier
+	gtoken_t type = token_keyword(lexer->token.value, lexer->token.bytes);
+	SET_TOKTYPE(type);
+	
+	#if GRAVITY_LEXEM_DEBUG
+	if (type == TOK_IDENTIFIER) DEBUG_LEXEM("Found identifier: %.*s", TOKEN_BYTES(lexer->token), TOKEN_VALUE(lexer->token));
+	else DEBUG_LEXEM("Found keyword: %s", token_name(type));
+	#endif
+	
+	return type;
+}
+
+static gtoken_t lexer_scan_number(gravity_lexer_t *lexer) {
+	bool		floatAllowed = true;
+	bool		expAllowed = true;
+	bool		signAllowed = false;
+	bool		dotFound = false;
+	bool		expFound = false;
+	int			c, expChar = 'e', floatChar = '.';
+	int			plusSign = '+', minusSign = '-';
+	
+	gravity_number_type	ntype = NUMBER_INTEGER;
+	if (PEEK_CURRENT == '0') {
+		if (toupper(PEEK_NEXT) == 'X') {ntype = NUMBER_HEX; floatAllowed = false; expAllowed = false;}
+		else if (toupper(PEEK_NEXT) == 'B') {ntype = NUMBER_BIN; floatAllowed = false; expAllowed = false;}
+		else if (toupper(PEEK_NEXT) == 'O') {ntype = NUMBER_OCT; floatAllowed = false; expAllowed = false;}
+	}
+	
+	TOKEN_RESET;
+	if (ntype != NUMBER_INTEGER) {
+		// skip first 0* number marker
+		INC_TOKLEN;
+		INC_TOKLEN;
+		INC_OFFSET_POSITION;
+		INC_OFFSET_POSITION;
+	}
+	
+	// supported exp formats:
+	// 12345	// decimal
+	// 3.1415	// float
+	// 1.25e2 = 1.25 * 10^2 = 125.0		// scientific notation
+	// 1.25e-2 = 1.25 * 10^-2 = 0.0125	// scientific notation
+	// 0xFFFF	// hex
+	// 0B0101	// binary
+	// 0O7777	// octal
+	
+	if (ntype == NUMBER_HEX) {
+		
+	}
+	
+loop:
+	c = PEEK_CURRENT;
+	
+	// explicitly list all accepted cases
+	if (IS_EOF) goto report_token;
+	if (is_digit(c, ntype)) goto accept_char;
+	if (is_whitespace(c)) goto report_token;
+	if (is_newline(lexer, c)) goto report_token;
+	
+	if (expAllowed) {
+		if ((c == expChar) && (!expFound)) {expFound = true; signAllowed = true; goto accept_char;}
+	}
+	if (floatAllowed) {
+		if ((c == floatChar) && (!is_digit(PEEK_NEXT, ntype))) goto report_token;
+		if ((c == floatChar) && (!dotFound))  {dotFound = true; goto accept_char;}
+	}
+	if (signAllowed) {
+		if ((c == plusSign) || (c == minusSign)) {signAllowed = false; goto accept_char;}
+	}
+	if (is_builtin_operator(c)) goto report_token;
+	if (is_semicolon(c)) goto report_token;
+	
+	// any other case is an error
+	goto report_error;
+	
+accept_char:
+	INC_TOKLEN;
+	INC_OFFSET_POSITION;
+	goto loop;
+
+report_token:
+	// number is from buffer->[nseek] and it is bytes length
+	TOKEN_FINALIZE(TOK_NUMBER);
+	
+	DEBUG_LEXEM("Found number: %.*s", TOKEN_BYTES(lexer->token), TOKEN_VALUE(lexer->token));
+	return TOK_NUMBER;
+	
+report_error:
+	return lexer_error(lexer, "Malformed number expression.");
+}
+
+static gtoken_t lexer_scan_string(gravity_lexer_t *lexer) {
+	int c, c2;
+	
+	// no memory allocation here
+	c = NEXT;					// save escaped character
+	TOKEN_RESET;				// save offset
+	SET_TOKESCAPED(false);		// set escaped flag to false
+	
+	while ((c2 = (unsigned char)PEEK_CURRENT) != c) {
+		if (IS_EOF) {return lexer_error(lexer, "Unexpected EOF inside a string literal");}
+		if (is_newline(lexer, c2)) INC_LINE;
+		
+		// handle escaped characters
+		if (c2 == '\\') {
+			SET_TOKESCAPED(true);
+			INC_OFFSET_POSITION;
+			INC_OFFSET_POSITION;
+			INC_TOKLEN;
+			INC_TOKLEN;
+			continue;
+		}
+		
+		// scan next
+		next_utf8(lexer);
+	}
+	
+	// skip last escape character
+	INC_OFFSET_POSITION;
+	
+	// string is from buffer->[nseek] and it is nlen length
+	TOKEN_FINALIZE(TOK_STRING);
+	
+	DEBUG_LEXEM("Found string: %.*s", TOKEN_BYTES(lexer->token), TOKEN_VALUE(lexer->token));
+	return TOK_STRING;
+}
+
+static gtoken_t lexer_scan_operator(gravity_lexer_t *lexer) {
+	TOKEN_RESET;
+	INC_TOKLEN;
+	
+	int c = NEXT;
+	int c2 = PEEK_CURRENT;
+	int tok = 0;
+	
+	switch (c) {
+		case '=':
+			if (c2 == '=') {
+				INC_OFFSET_POSITION; INC_TOKLEN; c2 = PEEK_CURRENT;
+				if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_ISIDENTICAL;}
+				else tok = TOK_OP_ISEQUAL;
+			}
+			else tok = TOK_OP_ASSIGN;
+			break;
+		case '+':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_ADD_ASSIGN;}
+			else tok = TOK_OP_ADD;
+			break;
+		case '-':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_SUB_ASSIGN;}
+			else tok = TOK_OP_SUB;
+			break;
+		case '*':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_MUL_ASSIGN;}
+			else tok = TOK_OP_MUL;
+			break;
+		case '/':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_DIV_ASSIGN;}
+			else tok = TOK_OP_DIV;
+			break;
+		case '%':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_REM_ASSIGN;}
+			else tok = TOK_OP_REM;
+			break;
+		case '<':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_LESS_EQUAL;}
+			else if (c2 == '<') {
+				INC_OFFSET_POSITION; INC_TOKLEN; c2 = PEEK_CURRENT;
+				if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_SHIFT_LEFT_ASSIGN;}
+				else tok = TOK_OP_SHIFT_LEFT;
+			}
+			else tok = TOK_OP_LESS;
+			break;
+		case '>':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_GREATER_EQUAL;}
+			else if (c2 == '>') {
+				INC_OFFSET_POSITION; INC_TOKLEN; c2 = PEEK_CURRENT;
+				if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_SHIFT_RIGHT_ASSIGN;}
+				else tok = TOK_OP_SHIFT_RIGHT;
+			}
+			else tok = TOK_OP_GREATER;
+			break;
+		case '&':
+			if (c2 == '&') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_AND;}
+			else if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_BIT_AND_ASSIGN;}
+			else tok = TOK_OP_BIT_AND;
+			break;
+		case '|':
+			if (c2 == '|') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_OR;}
+			else if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_BIT_OR_ASSIGN;}
+			else tok = TOK_OP_BIT_OR;
+			break;
+		case '.': // check for special .digit case
+			if (is_digit(c2, false)) {DEC_OFFSET_POSITION; DEC_TOKLEN; tok = lexer_scan_number(lexer);}
+			else if (c2 == '.') {
+				// seems a range, now peek c2 again and decide range type
+				INC_OFFSET_POSITION; INC_TOKLEN; c2 = PEEK_CURRENT;
+				if ((c2 == '<') || (c2 == '.')) {
+					INC_OFFSET_POSITION; INC_TOKLEN;
+					tok = (c2 == '<') ? TOK_OP_RANGE_EXCLUDED : TOK_OP_RANGE_INCLUDED;
+				} else {
+					return lexer_error(lexer, "Unrecognized Range operator");
+				}
+			}
+			else tok = TOK_OP_DOT;
+			break;
+		case ',':
+			tok = TOK_OP_COMMA;
+			break;
+		case '!':
+			if (c2 == '=') {
+				INC_OFFSET_POSITION; INC_TOKLEN; c2 = PEEK_CURRENT;
+				if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_ISNOTIDENTICAL;}
+				else tok = TOK_OP_ISNOTEQUAL;
+			}
+			else tok = TOK_OP_NOT;
+			break;
+		case '^':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_BIT_XOR_ASSIGN;}
+			else tok = TOK_OP_BIT_XOR;
+			break;
+		case '~':
+			if (c2 == '=') {INC_OFFSET_POSITION; INC_TOKLEN; tok = TOK_OP_PATTERN_MATCH;}
+			else tok = TOK_OP_BIT_NOT;
+			break;
+		case ':':
+			tok = TOK_OP_COLON;
+			break;
+		case '{':
+			tok = TOK_OP_OPEN_CURLYBRACE;
+			break;
+		case '}':
+			tok = TOK_OP_CLOSED_CURLYBRACE;
+			break;
+		case '[':
+			tok = TOK_OP_OPEN_SQUAREBRACKET;
+			break;
+		case ']':
+			tok = TOK_OP_CLOSED_SQUAREBRACKET;
+			break;
+		case '(':
+			tok = TOK_OP_OPEN_PARENTHESIS;
+			break;
+		case ')':
+			tok = TOK_OP_CLOSED_PARENTHESIS;
+			break;
+		case '?':
+			tok = TOK_OP_TERNARY;
+			break;
+		default:
+			return lexer_error(lexer, "Unrecognized Operator");
+			
+	}
+	
+	TOKEN_FINALIZE(tok);
+	
+	DEBUG_LEXEM("Found operator: %s", token_name(tok));
+	return tok;	
+}
+
+static gtoken_t lexer_scan_special(gravity_lexer_t *lexer) {
+	TOKEN_RESET;
+	INC_TOKLEN;
+	INC_OFFSET_POSITION;
+	TOKEN_FINALIZE(TOK_SPECIAL);
+	
+	return TOK_SPECIAL;
+}
+
+static gtoken_t lexer_scan_preprocessor(gravity_lexer_t *lexer) {
+	TOKEN_RESET;
+	INC_TOKLEN;
+	INC_OFFSET_POSITION;
+	TOKEN_FINALIZE(TOK_MACRO);
+	
+	return TOK_MACRO;
+}
+
+// MARK: -
+
+gravity_lexer_t *gravity_lexer_create (const char *source, size_t len, uint32_t fileid, bool is_static) {
+	gravity_lexer_t *lexer = mem_alloc(sizeof(gravity_lexer_t));
+	if (!lexer) return NULL;
+	bzero(lexer, sizeof(gravity_lexer_t));
+	
+	lexer->is_static = is_static;
+	lexer->lineno = 1;
+	lexer->buffer = source;
+	lexer->length = (uint32_t)len;
+	lexer->fileid = fileid;
+	lexer->peeking = false;
+	return lexer;
+}
+
+void gravity_lexer_setdelegate (gravity_lexer_t *lexer, gravity_delegate_t *delegate) {
+	lexer->delegate = delegate;
+}
+
+gtoken_t gravity_lexer_peek (gravity_lexer_t *lexer) {
+	lexer->peeking = true;
+	gravity_lexer_t saved = *lexer;
+	
+	gtoken_t result = gravity_lexer_next(lexer);
+	
+	*lexer = saved;
+	lexer->peeking = false;
+	
+	return result;
+}
+
+gtoken_t gravity_lexer_next (gravity_lexer_t *lexer) {
+	int			c;
+	gtoken_t	token;
+	
+loop:
+	if (IS_EOF) return TOK_EOF;
+	c = PEEK_CURRENT;
+	
+	if (is_whitespace(c)) {INC_OFFSET_POSITION; goto loop;}
+	if (is_newline(lexer, c)) {INC_OFFSET_POSITION; INC_LINE; goto loop;}
+	if (is_comment(c, PEEK_NEXT)) {lexer_scan_comment(lexer); goto loop;}
+	
+	if (is_semicolon(c)) {token = lexer_scan_semicolon(lexer); goto return_result;}
+	if (is_alpha(c)) {token = lexer_scan_identifier(lexer); goto return_result;}
+	if (is_digit(c, false)) {token = lexer_scan_number(lexer); goto return_result;}
+	if (is_string(c)) {token = lexer_scan_string(lexer); goto return_result;}
+	if (is_builtin_operator(c)) {token = lexer_scan_operator(lexer); goto return_result;}
+	if (is_special(c)) {token = lexer_scan_special(lexer); goto return_result;}
+	if (is_preprocessor(c)) {token = lexer_scan_preprocessor(lexer); goto return_result;}
+	
+	return lexer_error(lexer, "Unrecognized token");
+	
+return_result:
+	LEXER_CALL_CALLBACK();
+	return token;
+}
+
+void gravity_lexer_free (gravity_lexer_t *lexer) {
+	if ((!lexer->is_static) && (lexer->buffer)) mem_free(lexer->buffer);
+	mem_free(lexer);
+}
+
+gtoken_s gravity_lexer_token (gravity_lexer_t *lexer) {
+	return lexer->token;
+}
+
+gtoken_s gravity_lexer_token_next (gravity_lexer_t *lexer) {
+	gtoken_s token = lexer->token;
+	token.lineno = lexer->lineno;
+	token.colno = lexer->colno;
+	token.position = lexer->position;
+	return token;
+}
+
+gtoken_t gravity_lexer_token_type (gravity_lexer_t *lexer) {
+	return lexer->token.type;
+}
+
+void gravity_lexer_token_dump (gtoken_s token) {
+	printf("(%02d, %02d) %s: ", token.lineno, token.colno, token_name(token.type));
+	printf("%.*s\t(offset: %d len:%d)\n", token.bytes, token.value, token.position, token.bytes);
+}
+
+#if GRAVITY_LEXER_DEGUB
+void gravity_lexer_debug (gravity_lexer_t *lexer) {
+	//static int lineno = 0;
+	if (lexer->peeking) return;
+	//if (lineno > 0) printf("\n");
+	gtoken_s token = lexer->token;
+	if ((token.lineno == 0) && (token.colno == 0)) return;
+	printf("(%02d, %02d) %s: ", token.lineno, token.colno, token_name(token.type));
+	printf("%.*s\t(offset: %d)\n", token.bytes, token.value, token.position);
+	//++lineno;
+}
+#endif

+ 58 - 0
src/compiler/gravity_lexer.h

@@ -0,0 +1,58 @@
+//
+//  gravity_lexer.h
+//  gravity
+//
+//  Created by Marco Bambini on 30/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_LEXER__
+#define __GRAVITY_LEXER__
+
+#include "gravity_delegate.h"
+#include "gravity_token.h"
+#include "debug_macros.h"
+
+/*
+	Lexer is built in such a way that no memory allocations are necessary during usage
+	(except for the gravity_lexer_t opaque datatype allocated within gravity_lexer_create).
+ 
+	Example:
+	gravity_lexer *lexer = gravity_lexer_create(...);
+	while (gravity_lexer_next(lexer)) {
+		// do something here
+	}
+	gravity_lexer_free(lexer);
+	
+	gravity_lexer_next (and gravity_lexer_peek) returns an int token (gtoken_t)
+	which represents what has been currently scanned. When EOF is reached TOK_EOF is
+	returned (with value 0) and the while loop exits.
+	
+	In order to have token details, gravity_lexer_token must be called.
+	In case of a scan error TOK_ERROR is returned and error details can be extracted
+	from the token itself. In order to be able to not allocate any memory during
+	tokenization STRINGs and NUMBERs are just sanity checked but not converted.
+	It is parser responsability to perform the right conversion.
+ 
+ */
+
+// opaque datatype
+typedef struct gravity_lexer_t gravity_lexer_t;
+
+// public functions
+gravity_lexer_t		*gravity_lexer_create (const char *source, size_t len, uint32_t fileid, bool is_static);
+void				gravity_lexer_setdelegate (gravity_lexer_t *lexer, gravity_delegate_t *delegate);
+void				gravity_lexer_free (gravity_lexer_t *lexer);
+
+gtoken_t			gravity_lexer_peek (gravity_lexer_t *lexer);
+gtoken_t			gravity_lexer_next (gravity_lexer_t *lexer);
+gtoken_s			gravity_lexer_token (gravity_lexer_t *lexer);
+gtoken_s			gravity_lexer_token_next (gravity_lexer_t *lexer);
+gtoken_t			gravity_lexer_token_type (gravity_lexer_t *lexer);
+void				gravity_lexer_token_dump (gtoken_s token);
+
+#if GRAVITY_LEXER_DEGUB
+void				gravity_lexer_debug (gravity_lexer_t *lexer);
+#endif
+
+#endif

+ 512 - 0
src/compiler/gravity_optimizer.c

@@ -0,0 +1,512 @@
+//
+//  gravity_optimizer.c
+//  gravity
+//
+//  Created by Marco Bambini on 24/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+// Some optimizations taken from: http://www.compileroptimizations.com/
+
+#include "gravity_hash.h"
+#include "gravity_optimizer.h"
+#include "gravity_opcodes.h"
+#include "gravity_ircode.h"
+#include "gravity_utils.h"
+#include "gravity_value.h"
+
+#define IS_MOVE(inst)					((inst) && (inst->op == MOVE))
+#define IS_RET(inst)					((inst) && (inst->op == RET))
+#define IS_NEG(inst)					((inst) && (inst->op == NEG))
+#define IS_NUM(inst)					((inst) && (inst->op == LOADI))
+#define IS_MATH(inst)					((inst) && (inst->op >= ADD) && (inst->op <= REM))
+#define IS_SKIP(inst)					(inst->tag == SKIP_TAG)
+#define IS_LABEL(inst)					(inst->tag == LABEL_TAG)
+#define IS_NOTNULL(inst)				(inst)
+
+// http://www.mathsisfun.com/binary-decimal-hexadecimal-converter.html
+#define OPCODE_SET(op,code)								op = (code & 0x3F) << 26
+#define OPCODE_SET_TWO8bit_ONE10bit(op,code,a,b,c)		op = (code & 0x3F) << 26; op += (a & 0xFF) << 18; op += (b & 0xFF) << 10; op += (c & 0x3FF)
+#define OPCODE_SET_FOUR8bit(op,a,b,c,d)					op = (a & 0xFF) << 24; op += (b & 0xFF) << 16; op += (c & 0xFF) << 8; op += (d & 0xFF)
+#define OPCODE_SET_ONE8bit_SIGN_ONE17bit(op,code,a,s,n)	op = (code & 0x3F) << 26; op += (a & 0xFF) << 18; op += (s & 0x01) << 17; op += (n & 0x1FFFF)
+#define OPCODE_SET_SIGN_ONE25bit(op,code,s,a)			op = (code & 0x3F) << 26; op += (s & 0x01) << 25; op += (a & 0x1FFFFFF)
+#define OPCODE_SET_ONE8bit_ONE18bit(op,code,a,n)		op = (code & 0x3F) << 26; op += (a & 0xFF) << 18; op += (n & 0x3FFFF)
+#define OPCODE_SET_ONE26bit(op,code,a)					op = (code & 0x3F) << 26; op += (a & 0x3FFFFFF)
+#define OPCODE_SET_THREE8bit(op,code,a,b,c)				OPCODE_SET_TWO8bit_ONE10bit(op,code,a,b,c)
+#define OPCODE_SET_ONE8bit_ONE10bit(op,code,a,b)		OPCODE_SET_TWO8bit_ONE10bit(op,code,a,0,b)
+#define OPCODE_SET_ONE8bit(op,code,a)					OPCODE_SET_TWO8bit_ONE10bit(op,code,a,0,0)
+#define OPCODE_SET_THREE8bit_ONE2bit(op,code,a,b,c,f)	op =(code & 0x3F)<<26; op+=(a & 0xFF)<<18; op+=(b & 0xFF)<<10; op+=(c & 0xFF)<<2; op+=(f & 0x03)
+
+// MARK: -
+
+static bool hash_isequal (gravity_value_t v1, gravity_value_t v2) {
+	return (v1.n == v2.n);
+}
+
+static uint32_t hash_compute (gravity_value_t v) {
+	return gravity_hash_compute_int(v.n);
+}
+
+static void finalize_function (gravity_function_t *f) {
+	ircode_t		*code = (ircode_t *)f->bytecode;
+	uint32_t		ninst = 0, count = ircode_count(code);
+	uint32_t		notpure = 0;
+	uint32_t		*bytecode = NULL;
+	gravity_hash_t	*labels = gravity_hash_create(0, hash_compute, hash_isequal, NULL, NULL);
+	
+	// determine how big bytecode buffer must be
+	// and collect all LABEL instructions
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = ircode_get(code, i);
+		if (IS_SKIP(inst)) continue;
+		if (IS_LABEL(inst)) {
+			// insert key inst->p1 into hash table labels with value ninst (next instruction)
+			gravity_hash_insert(labels, VALUE_FROM_INT(inst->p1), VALUE_FROM_INT(ninst));
+			continue;
+		}
+		++ninst;
+	}
+	
+	// +1 is just a trick so the VM switch loop terminates with an implicit RET0 instruction (RET0 has opcode 0)
+	f->ninsts = ninst;
+	bytecode = (uint32_t *)mem_alloc((ninst+1) * sizeof(uint32_t));
+	assert(bytecode);
+	
+	uint32_t j=0;
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = ircode_get(code, i);
+		if (IS_SKIP(inst)) continue;
+		if (IS_LABEL(inst)) continue;
+		
+		uint32_t op = 0x0;
+		switch (inst->op) {
+			case HALT:
+			case RET0:
+			case NOP:
+				OPCODE_SET(op, inst->op);
+				break;
+			
+			case LOAD:
+			case STORE:
+				++notpure;	// not sure here
+			case LOADS:
+			case LOADAT:
+			case STOREAT:
+			case EQQ:
+			case NEQQ:
+			case ISA:
+			case MATCH:
+			case LSHIFT:
+			case RSHIFT:
+			case BOR:
+			case BAND:
+			case BNOT:
+			case BXOR:
+			case ADD:
+			case SUB:
+			case DIV:
+			case MUL:
+			case REM:
+			case AND:
+			case OR:
+			case LT:
+			case GT:
+			case EQ:
+			case LEQ:
+			case GEQ:
+			case NEQ:
+			case NEG:
+			case NOT:
+				OPCODE_SET_TWO8bit_ONE10bit(op, inst->op, inst->p1, inst->p2, inst->p3);
+				break;
+			
+			case LOADI:
+				OPCODE_SET_ONE8bit_SIGN_ONE17bit(op, inst->op, inst->p1, (inst->n < 0) ? 1 : 0, inst->n);
+				break;
+				
+			case JUMPF: {
+				gravity_value_t *v = gravity_hash_lookup(labels, VALUE_FROM_INT(inst->p2));
+				assert(v); // key MUST exists!
+				uint32_t njump = (uint32_t)v->n;
+				uint32_t bflag = inst->p3;
+				OPCODE_SET_ONE8bit_SIGN_ONE17bit(op, inst->op, inst->p1, bflag, njump);
+				//OPCODE_SET_ONE8bit_ONE18bit(op, inst->op, inst->p1, njump);
+				break;
+			}
+			
+			case RET:
+				OPCODE_SET_ONE8bit(op, inst->op, inst->p1);
+				break;
+				
+			case JUMP: {
+				gravity_value_t *v = gravity_hash_lookup(labels, VALUE_FROM_INT(inst->p1));
+				assert(v); // key MUST exists!
+				uint32_t njump = (uint32_t)v->n;
+				OPCODE_SET_ONE26bit(op, inst->op, njump);
+				break;
+			}
+			
+			case LOADG:
+			case STOREG:
+				++notpure;
+			case MOVE:
+			case LOADK:
+				OPCODE_SET_ONE8bit_ONE18bit(op, inst->op, inst->p1, inst->p2);
+				break;
+				
+			case CALL:
+				OPCODE_SET_TWO8bit_ONE10bit(op, inst->op, inst->p1, inst->p2, inst->p3);
+				break;
+			
+			case SETLIST:
+				OPCODE_SET_TWO8bit_ONE10bit(op, inst->op, inst->p1, inst->p2, inst->p3);
+				break;
+				
+			case LOADU:
+			case STOREU:
+				++notpure;
+				OPCODE_SET_ONE8bit_ONE18bit(op, inst->op, inst->p1, inst->p2);
+				break;
+			
+			case RANGENEW: {
+				uint8_t flag = (inst->tag == RANGE_INCLUDE_TAG) ? 0 : 1;
+				OPCODE_SET_THREE8bit_ONE2bit(op, inst->op, inst->p1, inst->p2, inst->p3, flag);
+				break;
+			}
+			case MAPNEW:
+			case LISTNEW:
+				OPCODE_SET_ONE8bit_ONE18bit(op, inst->op, inst->p1, inst->p2);
+				break;
+				
+			case SWITCH:
+				assert(0);
+				break;
+				
+			case CLOSURE:
+			case CLOSE:
+				OPCODE_SET_ONE8bit_ONE18bit(op, inst->op, inst->p1, inst->p2);
+				break;
+				
+			case RESERVED1:
+			case RESERVED2:
+			case RESERVED3:
+			case RESERVED4:
+			case RESERVED5:
+			case RESERVED6:
+				assert(0);
+				break;
+		}
+		
+		// store encoded instruction
+		bytecode[j++] = op;
+	}
+	
+	ircode_free(code);
+	gravity_hash_free(labels);
+	
+	f->bytecode = bytecode;
+	f->purity = (notpure == 0) ? 1.0 : ((float)(notpure * 100) / (float)ninst) / 100.0f;
+}
+
+// MARK: -
+
+inline static bool pop1_instruction (ircode_t *code, uint32_t index, inst_t **inst1) {
+	*inst1 = NULL;
+	
+	for (int32_t i=index-1; i>=0; --i) {
+		inst_t *inst = ircode_get(code, i);
+		if ((inst != NULL) && (inst->tag != SKIP_TAG)) {
+			*inst1 = inst;
+			return true;
+		}
+	}
+	
+	return false;
+}
+
+inline static bool pop2_instructions (ircode_t *code, uint32_t index, inst_t **inst1, inst_t **inst2) {
+	*inst1 = NULL;
+	*inst2 = NULL;
+	
+	for (int32_t i=index-1; i>=0; --i) {
+		inst_t *inst = ircode_get(code, i);
+		if ((inst != NULL) && (inst->tag != SKIP_TAG)) {
+			if (*inst1 == NULL) *inst1 = inst;
+			else if (*inst2 == NULL) {
+				*inst2 = inst;
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}
+
+inline static inst_t *current_instruction (ircode_t *code, uint32_t i) {
+	while (1) {
+		inst_t *inst = ircode_get(code, i);
+		if (inst == NULL) return NULL;
+		if (inst->tag != SKIP_TAG) return inst;
+		++i;
+	}
+	
+	return NULL;
+}
+
+// MARK: -
+
+static bool optimize_const_instruction (inst_t *inst, inst_t *inst1, inst_t *inst2) {
+	// select type algorithm:
+	// two numeric types are supported here, int64 or double
+	// if types are equals then set the first one
+	// if types are not equals then set to double
+	optag_t	type;
+	double	d = 0.0, d1 = 0.0, d2 = 0.0;
+	int64_t	n = 0, n1 = 0, n2 = 0;
+	
+	// compute types
+	if (inst1->tag == inst2->tag) type = inst1->tag;
+	else type = DOUBLE_TAG;
+	
+	// compute operands
+	if (type == DOUBLE_TAG) {
+		d1 = (inst1->tag == INT_TAG) ? (double)inst1->n : inst1->d;
+		d2 = (inst2->tag == INT_TAG) ? (double)inst2->n : inst2->d;
+	} else {
+		n1 = (inst1->tag == INT_TAG) ? inst1->n : (int64_t)inst1->d;
+		n2 = (inst2->tag == INT_TAG) ? inst2->n : (int64_t)inst2->d;
+	}
+	
+	// perform operation
+	switch (inst->op) {
+		case ADD:
+			if (type == DOUBLE_TAG) d = d1 + d2;
+			else n = n1 + n2;
+			break;
+			
+		case SUB:
+			if (type == DOUBLE_TAG) d = d1 - d2;
+			else n = n1 - n2;
+			break;
+			
+		case MUL:
+			if (type == DOUBLE_TAG) d = d1 * d2;
+			else n = n1 * n2;
+			break;
+			
+		case DIV:
+			// don't optimize in case of division by 0
+			if (d2 == 0) return false;
+			if (type == DOUBLE_TAG) d = d1 / d2;
+			else n = n1 / n2;
+			break;
+			
+		case REM:
+			if (type == DOUBLE_TAG) d = (int64_t)d1 % (int64_t)d2;
+			else n = n1 % n2;
+			break;
+			
+		default:
+			assert(0);
+	}
+	
+	// adjust IRCODE
+	inst_setskip(inst1);
+	inst_setskip(inst2);
+	
+	// convert an ADD instruction to a LOADI instruction
+	// ADD A B C	=> R(A) = R(B) + R(C)
+	// LOADI A B	=> R(A) = N
+	inst->op = LOADI;
+	inst->tag = type;
+	inst->p2 = inst->p3 = 0;
+	if (type == DOUBLE_TAG) inst->d = d;
+	else inst->n = n;
+	
+	return true;
+}
+
+static bool optimize_neg_instruction (ircode_t *code, inst_t *inst, uint32_t i) {
+	inst_t *inst1 = NULL;
+	pop1_instruction(code, i, &inst1);
+	if (inst1 == NULL) return false;
+	if (inst1->op != LOADI) return false;
+	if (inst1->p1 != inst->p2) return false;
+	if (!ircode_register_istemp(code, inst1->p1)) return false;
+	
+	uint64_t n = inst1->n;
+	if (n>131072) return false;
+	inst1->p1 = inst->p2;
+	inst1->n = -n;
+	inst_setskip(inst);
+	return true;
+}
+
+static bool optimize_math_instruction (ircode_t *code, inst_t *inst, uint32_t i) {
+	uint8_t count = opcode_numop(inst->op) - 1;
+	inst_t *inst1 = NULL, *inst2 = NULL;
+	bool	flag = false;
+	
+	if (count == 2) {
+		pop2_instructions(code, i, &inst2, &inst1);
+		if (IS_NUM(inst1) && IS_NUM(inst2)) flag = optimize_const_instruction(inst, inst1, inst2);
+		
+		// process inst2
+		if (IS_MOVE(inst2)) {
+			bool b1 = ircode_register_istemp(code, inst->p3);
+			bool b2 = ((inst2) && ircode_register_istemp(code, inst2->p1));
+			if ((b1) && (b2) && (inst->p3 == inst2->p1)) {
+				inst->p3 = inst2->p2;
+				inst_setskip(inst2);
+				flag = true;
+			}
+		}
+		
+		// process inst1
+		if (IS_MOVE(inst1)) {
+			bool b1 = ircode_register_istemp(code, inst->p2);
+			bool b2 = ((inst1) && ircode_register_istemp(code, inst1->p1));
+			if ((b1) && (b2) && (inst->p2 == inst1->p1)) {
+				inst->p2 = inst1->p2;
+				inst_setskip(inst1);
+				flag = true;
+			}
+		}
+		
+	}
+	else {
+		pop1_instruction(code, i, &inst1);
+		if (IS_NUM(inst1)) flag = optimize_const_instruction(inst, inst1, NULL);
+	}
+
+	return flag;
+}
+
+static bool optimize_move_instruction (ircode_t *code, inst_t *inst, uint32_t i) {
+	return false;
+	inst_t *inst1 = NULL;
+	pop1_instruction(code, i, &inst1);
+	if (inst1 == NULL) return false;
+	if ((inst1->op != LOADI) && (inst1->op != LOADG) && (inst1->op != LOADK)) return false;
+	
+	bool b1 = ircode_register_istemp(code, inst->p2);
+	bool b2 = ((inst1) && ircode_register_istemp(code, inst1->p1));
+	
+	if ((b1) && (b2) && (inst->p2 == inst1->p1)) {
+		inst1->p1 = inst->p1;
+		inst_setskip(inst);
+		return true;
+	}
+	
+	return false;
+}
+
+static bool optimize_return_instruction (ircode_t *code, inst_t *inst, uint32_t i) {
+	inst_t *inst1 = NULL;
+	pop1_instruction(code, i, &inst1);
+	
+	if (!ircode_register_istemp(code, inst->p1)) return false;
+	if ((IS_MOVE(inst1)) && (inst->p1 == inst1->p1)) {
+		inst->p1 = inst1->p2;
+		inst_setskip(inst1);
+		return true;
+	}
+	
+	return false;
+}
+
+static bool optimize_num_instruction (inst_t *inst, gravity_function_t *f) {
+	
+	// double values always added to constant pool
+	bool add_cpool = (inst->tag == DOUBLE_TAG);
+	
+	// LOADI is a 32bit instruction
+	// 32 - 6 (OPCODE) - 8 (register) - 1 bit sign = 17
+	// range is from MAX_INLINE_INT-1 to MAX_INLINE_INT
+	// so max/min values are in the range -(2^17)-1/+2^17
+	// 2^17 = 131072 (MAX_INLINE_INT)
+	if (inst->tag == INT_TAG) {
+		int64_t n = inst->n;
+		add_cpool = ((n < -MAX_INLINE_INT + 1) || (n > MAX_INLINE_INT));
+	}
+	
+	if (add_cpool) {
+		uint16_t index = 0;
+		if (inst->tag == INT_TAG) {
+			int64_t n = inst->n;
+			index = gravity_function_cpool_add(NULL, f, VALUE_FROM_INT(n));
+		} else {
+			// always add floating point values as double in constant pool (then VM will be configured to interpret it as float or double)
+			index = gravity_function_cpool_add(NULL, f, VALUE_FROM_FLOAT(inst->d));
+		}
+		
+		// replace LOADI with a LOADK instruction
+		inst->op = LOADK;
+		inst->p2 = index;
+		inst->tag = NO_TAG;
+	}
+	
+	return true;
+}
+
+// MARK: -
+
+gravity_function_t *gravity_optimizer(gravity_function_t *f) {
+	if (f->bytecode == NULL) return f;
+	
+	ircode_t	*code = (ircode_t *)f->bytecode;
+	uint32_t	count = ircode_count(code);
+	
+	f->ntemps = ircode_ntemps(code);
+	
+	loop_neg:
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = current_instruction(code, i);
+		if (IS_NEG(inst)) {
+			bool b = optimize_neg_instruction (code, inst, i);
+			if (b) goto loop_neg;
+		}
+	}
+	
+	loop_math:
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = current_instruction(code, i);
+		if (IS_MATH(inst)) {
+			bool b = optimize_math_instruction (code, inst, i);
+			if (b) goto loop_math;
+		}
+	}
+	
+	loop_move:
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = current_instruction(code, i);
+		if (IS_MOVE(inst)) {
+			bool b = optimize_move_instruction (code, inst, i);
+			if (b) goto loop_move;
+		}
+	}
+	
+	loop_ret:
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = current_instruction(code, i);
+		if (IS_RET(inst)) {
+			bool b = optimize_return_instruction (code, inst, i);
+			if (b) goto loop_ret;
+		}
+	}
+	
+	for (uint32_t i=0; i<count; ++i) {
+		inst_t *inst = current_instruction(code, i);
+		if (IS_NUM(inst)) optimize_num_instruction (inst, f);
+	}
+	
+	// dump optimized version
+	#if GRAVITY_BYTECODE_DEBUG
+	gravity_function_dump(f, ircode_dump);
+	#endif
+	
+	// finalize function
+	finalize_function(f);
+	
+	return f;
+}

+ 17 - 0
src/compiler/gravity_optimizer.h

@@ -0,0 +1,17 @@
+//
+//  gravity_optimizer.h
+//  gravity
+//
+//  Created by Marco Bambini on 24/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_OPTIMIZER__
+#define __GRAVITY_OPTIMIZER__
+
+#include "gravity_macros.h"
+#include "gravity_value.h"
+
+gravity_function_t *gravity_optimizer(gravity_function_t *f);
+
+#endif

+ 2158 - 0
src/compiler/gravity_parser.c

@@ -0,0 +1,2158 @@
+//
+//  gravity_parser.c
+//  gravity
+//
+//  Created by Marco Bambini on 01/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_symboltable.h"
+#include "gravity_parser.h"
+#include "gravity_macros.h"
+#include "gravity_lexer.h"
+#include "gravity_token.h"
+#include "gravity_utils.h"
+#include "gravity_array.h"
+#include "gravity_core.h"
+#include "gravity_ast.h"
+
+typedef marray_t(gravity_lexer_t*)		lexer_r;
+
+struct gravity_parser_t {
+	lexer_r								*lexer;
+	gnode_r								*statements;
+	uint16_r							declarations;
+	gravity_delegate_t					*delegate;
+	
+	double								time;
+	uint32_t							nerrors;
+	uint32_t							unique_id;
+	uint32_t							last_error_lineno;
+	
+	// state ivars used by Pratt parser
+	gtoken_t							current_token;
+	gnode_t								*current_node;
+};
+
+// MARK: - PRATT parser specs -
+// http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
+// http://javascript.crockford.com/tdop/tdop.html
+
+// Precedence table as defined in Swift
+// http://nshipster.com/swift-operators/
+typedef enum {
+	PREC_LOWEST,
+	PREC_ASSIGN      = 90,	// = *= /= %= += -= <<= >>= &= ^= |=	(11 cases)
+	PREC_TERNARY     = 100,	// ?:									  (1 case)
+	PREC_LOGICAL_OR  = 110,	// ||									  (1 case)
+	PREC_LOGICAL_AND = 120,	// &&									  (1 case)
+	PREC_COMPARISON  = 130,	// < <= > >= == != === !== ~=			 (9 cases)
+	PREC_ISA         = 132,	// isa									  (1 case)
+	PREC_RANGE       = 135,	// ..< ...								 (2 cases)
+	PREC_TERM        = 140,	// + - | ^								 (4 cases)
+	PREC_FACTOR      = 150,	// * / % &								 (4 cases)
+	PREC_SHIFT       = 160,	// << >>								 (2 cases)
+	PREC_UNARY       = 170,	// + - ! ~								 (4 cases)
+	PREC_CALL        = 200  // . ( [								 (3 cases)
+} prec_level;
+
+typedef gnode_t* (*parse_func) (gravity_parser_t *parser);
+
+typedef struct {
+	parse_func			prefix;
+	parse_func			infix;
+	prec_level			precedence;
+	const char			*name;
+	bool				right;
+} grammar_rule;
+
+// This table defines all of the parsing rules for the prefix and infix expressions in the grammar.
+#define RULE(prec, fn1, fn2)			(grammar_rule){ fn1, fn2, prec, NULL, false}
+#define PREFIX(prec, fn)				(grammar_rule){ fn, NULL, prec, NULL, false}
+#define INFIX(prec, fn)					(grammar_rule){ NULL, fn, prec, NULL, false}
+#define INFIX_OPERATOR(prec, name)		(grammar_rule){ NULL, parse_infix, prec, name, false}
+#define INFIX_OPERATOR_RIGHT(prec,name) (grammar_rule){ NULL, parse_infix, prec, name, true}
+#define PREFIX_OPERATOR(name)			(grammar_rule){ parse_unary, NULL, PREC_LOWEST, name, false}
+#define OPERATOR(prec, name)			(grammar_rule){ parse_unary, parse_infix, prec, name, false}
+
+// Global singleton grammar rule table
+static grammar_rule rules[TOK_END];
+
+// MARK: - Internal macros -
+#define SEMICOLON_IS_OPTIONAL					1
+
+#define REPORT_ERROR(_tok,...)					report_error(parser, GRAVITY_ERROR_SYNTAX, _tok, __VA_ARGS__)
+#define REPORT_WARNING(_tok,...)				report_error(parser, GRAVITY_WARNING, _tok, __VA_ARGS__)
+
+#define PUSH_DECLARATION(_decl)					marray_push(uint16_t, parser->declarations, (uint16_t)_decl)
+#define PUSH_FUNCTION_DECLARATION()				PUSH_DECLARATION(NODE_FUNCTION_DECL)
+#define PUSH_CLASS_DECLARATION()				PUSH_DECLARATION(NODE_CLASS_DECL)
+#define POP_DECLARATION()						marray_pop(parser->declarations)
+#define LAST_DECLARATION()						(marray_size(parser->declarations) ? marray_last(parser->declarations) : 0)
+#define IS_FUNCTION_ENCLOSED()					(LAST_DECLARATION() == NODE_FUNCTION_DECL)
+#define IS_CLASS_ENCLOSED()						(LAST_DECLARATION() == NODE_CLASS_DECL)
+#define CHECK_NODE(_n)							if (!_n) return NULL
+
+#define POP_LEXER								(gravity_lexer_t *)marray_pop(*parser->lexer)
+#define CURRENT_LEXER							(gravity_lexer_t *)marray_last(*parser->lexer)
+#define DECLARE_LEXER							gravity_lexer_t *lexer = CURRENT_LEXER; DEBUG_LEXER(lexer)
+
+#define STATIC_TOKEN_CSTRING(_s,_n,_l,_b,_t)	char _s[_n] = {0}; uint32_t _l = 0;			\
+												const char *_b = token_string(_t, &_l);		\
+												if (_l) memcpy(_s, _b, MINNUM(_n, _l))
+
+// MARK: - Prototypes -
+static const char *parse_identifier (gravity_parser_t *parser);
+static gnode_t *parse_statement (gravity_parser_t *parser);
+static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser);
+static gnode_t *parse_compound_statement (gravity_parser_t *parser);
+static gnode_t *parse_expression (gravity_parser_t *parser);
+static gnode_t *parse_declaration_statement (gravity_parser_t *parser);
+static gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t access_specifier, gtoken_t storage_specifier);
+static gnode_t *adjust_assignment_expression (gtoken_t tok, gnode_t *lnode, gnode_t *rnode);
+
+// MARK: - Utils functions -
+
+static void report_error (gravity_parser_t *parser, error_type_t error_type, gtoken_s token, const char *format, ...) {
+	// consider just one error for each line;
+	if (parser->last_error_lineno == token.lineno) return;
+	parser->last_error_lineno = token.lineno;
+	
+	// increment internal error counter
+	++parser->nerrors;
+	
+	// get error callback (if any)
+	void *data = (parser->delegate) ? parser->delegate->xdata : NULL;
+	gravity_error_callback error_fn = (parser->delegate) ? ((gravity_delegate_t *)parser->delegate)->error_callback : NULL;
+	
+	// build error message
+	char		buffer[1024];
+	va_list		arg;
+	if (format) {
+		va_start (arg, format);
+		vsnprintf(buffer, sizeof(buffer), format, arg);
+		va_end (arg);
+	}
+	
+	// setup error struct
+	error_desc_t error_desc = {
+		.code = 0,
+		.lineno = token.lineno,
+		.colno = token.colno,
+		.fileid = token.fileid,
+		.offset = token.position
+	};
+	
+	// finally call error callback
+	if (error_fn) error_fn(error_type, buffer, error_desc, data);
+	else printf("%s\n", buffer);
+}
+
+static gnode_t *parse_error (gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	REPORT_ERROR(token, "%s", token.value);
+	return NULL;
+}
+
+// RETURN:
+//	- true if next token is equal to token passed as parameter (token is also consumed)
+//	- false if next token is not equal (and no error is reported)
+//
+static bool parse_optional (gravity_parser_t *parser, gtoken_t token) {
+	DECLARE_LEXER;
+	
+	gtoken_t peek = gravity_lexer_peek(lexer);
+	if (token_iserror(peek)) {
+		parse_error(parser);
+		peek = gravity_lexer_peek(lexer);
+	}
+	
+	if (peek == token) {
+		gravity_lexer_next(lexer); // consume expected token
+		return true;
+	}
+	
+	// do not report any error in this case
+	return false;
+}
+
+static bool parse_required (gravity_parser_t *parser, gtoken_t token) {
+	if (parse_optional(parser, token)) return true;
+	
+	// token not found (and not consumed) so an error strategy must be implemented here
+	
+	// simple (but not simpler) error recovery
+	// parser should keep track of what I am parsing
+	// so based on tok I could have a token list of terminal symbols
+	// call next until first sync symbol (or EOF or start of another terminal symbol is found)
+	
+	// simple error recovery, just consume next and report error
+	DECLARE_LEXER;
+	gtoken_t next = gravity_lexer_next(lexer);
+	gtoken_s unexpected_token = gravity_lexer_token(lexer);
+	REPORT_ERROR(unexpected_token, "Expected %s but found %s.", token_name(token), token_name(next));
+	return false;
+}
+
+static bool parse_semicolon (gravity_parser_t *parser) {
+	#if SEMICOLON_IS_OPTIONAL
+	DECLARE_LEXER;
+	if (gravity_lexer_peek(lexer) == TOK_OP_SEMICOLON) {gravity_lexer_next(lexer); return true;}
+	return false;
+	#else
+	return parse_required(parser, TOK_OP_SEMICOLON);
+	#endif
+}
+
+gnode_t *parse_function (gravity_parser_t *parser, bool is_declaration, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	DECLARE_LEXER;
+	
+	// access_specifier? storage_specifier? already parsed
+	// 'function' IDENTIFIER '(' parameter_declaration_clause? ')' compound_statement
+	
+	// consume FUNC keyword (or peek for OPEN_CURLYBRACE)
+	bool is_implicit = (gravity_lexer_peek(lexer) == TOK_OP_OPEN_CURLYBRACE);
+	gtoken_s token = gravity_lexer_token(lexer);
+	if (!is_implicit) {
+		gtoken_t type = gravity_lexer_next(lexer);
+		token = gravity_lexer_token(lexer);
+		assert(type == TOK_KEY_FUNC);
+	}
+	
+	// parse IDENTIFIER
+	const char *identifier = NULL;
+	if (is_declaration) {
+		gtoken_t peek = gravity_lexer_peek(lexer);
+		if (token_isoperator(peek)) {
+			gravity_lexer_next(lexer);
+			identifier = string_dup(token_name(peek));
+		} else {
+			identifier = parse_identifier(parser);
+		}
+		DEBUG_PARSER("parse_function_declaration %s", identifier);
+	}
+	
+	// check and consume TOK_OP_OPEN_PARENTHESIS
+	if (!is_implicit) parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
+	
+	// parse optional parameter declaration clause
+	gnode_r *params = (!is_implicit) ? parse_optional_parameter_declaration(parser) : NULL;
+	
+	// check and consume TOK_OP_CLOSED_PARENTHESIS
+	if (!is_implicit) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+	
+	// parse compound statement
+	PUSH_FUNCTION_DECLARATION();
+	gnode_compound_stmt_t *compound = (gnode_compound_stmt_t*)parse_compound_statement(parser);
+	POP_DECLARATION();
+	
+	// parse optional semicolon
+	parse_semicolon(parser);
+	
+	return gnode_function_decl_create(token, identifier, access_specifier, storage_specifier, params, compound);
+}
+
+static char *cstring_from_token (gravity_parser_t *parser, gtoken_s token) {
+	#pragma unused(parser)
+	uint32_t len = 0;
+	const char *buffer = token_string(token, &len);
+	
+	char *str = (char *)mem_alloc(len+1);
+	memcpy(str, buffer, len);
+	return str;
+}
+
+static gnode_t *local_store_declaration (const char *identifier, gtoken_t access_specifier, gtoken_t storage_specifier, gnode_t *declaration) {
+	gnode_r *decls = gnode_array_create();
+	gnode_t *decl = gnode_variable_create(declaration->token, identifier ? string_dup(identifier) : NULL, NULL, access_specifier, declaration);
+	gnode_array_push(decls, decl);
+	return gnode_variable_decl_create(declaration->token, TOK_KEY_VAR, access_specifier, storage_specifier, decls);
+}
+
+static gliteral_t decode_number_binary (gtoken_s token, int64_t *n) {
+	// from 2 in order to skip 0b
+	*n = number_from_bin(&token.value[2], token.bytes-2);
+	return LITERAL_INT;
+}
+
+static gliteral_t decode_number_octal (gtoken_s token, int64_t *n) {
+	STATIC_TOKEN_CSTRING(str, 512, len, buffer, token);
+	if (len) *n = (int64_t) number_from_oct(&str[2], len-2);
+	return LITERAL_INT;
+}
+
+static gliteral_t decode_number_hex (gtoken_s token, int64_t *n, double *d) {
+	#pragma unused(d)
+	STATIC_TOKEN_CSTRING(str, 512, len, buffer, token);
+	if (len) *n = (int64_t) number_from_hex(str, token.bytes);
+	return LITERAL_INT;
+}
+
+// MARK: - Expressions -
+
+static gnode_t *parse_ternary_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_ternary_expression");
+	DECLARE_LEXER;
+	
+	// conditional expression already parsed
+	gnode_t *cond = parser->current_node;
+	if (!cond) return NULL;
+	
+	// '?' expression ':' expression
+	
+	// '?' already consumed
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	// parse expression 1
+	gnode_t *expr1 = parse_expression(parser);
+	CHECK_NODE(expr1);
+	
+	parse_required(parser, TOK_OP_COLON);
+	
+	// parse expression 2
+	gnode_t *expr2 = parse_expression(parser);
+	CHECK_NODE(expr2);
+	
+	return gnode_flow_stat_create(token, cond, expr1, expr2);
+}
+
+static gnode_t *parse_file_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_file_expression");
+	DECLARE_LEXER;
+	
+	// at least one identifier is mandatory
+	// 'file' ('.' IDENTIFIER)+
+	
+	gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	if (gravity_lexer_peek(lexer) != TOK_OP_DOT) {
+		REPORT_ERROR(token, "A .identifier list is expected here.");
+		return NULL;
+	}
+	
+	cstring_r *list = cstring_array_create();
+	while (gravity_lexer_peek(lexer) == TOK_OP_DOT) {
+		gravity_lexer_next(lexer); // consume TOK_OP_DOT
+		const char *identifier = parse_identifier(parser);
+		if (!identifier) return NULL;
+		cstring_array_push(list, identifier);
+	}
+	
+	return gnode_file_expr_create(token, list);
+}
+
+static const char *parse_identifier (gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	
+	// parse IDENTIFIER is always mandatory
+	gtoken_t type = gravity_lexer_peek(lexer);
+	if (type != TOK_IDENTIFIER) {
+		if (type == TOK_ERROR) parse_error(parser);
+		else REPORT_ERROR(gravity_lexer_token(lexer), "Expected identifier but found %s", token_name(type));
+		return NULL;
+	}
+	
+	gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	const char *identifier = cstring_from_token(parser, token);
+	return identifier;
+}
+
+static const char *parse_optional_type_annotation (gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	const char	*type_annotation = NULL;
+	gtoken_t	peek = gravity_lexer_peek(lexer);
+	
+	// type annotation
+	// function foo (a: string, b: number)
+	
+	// check for optional type_annotation
+	if (peek == TOK_OP_COLON) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COLON
+		
+		// parse identifier
+		type_annotation = parse_identifier(parser);
+		if (!type_annotation) return NULL;
+	}
+	
+	return type_annotation;
+}
+
+static gnode_t *parse_parentheses_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_parentheses_expression");
+	
+	// check and consume TOK_OP_OPEN_PARENTHESIS
+	parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
+	
+	// parse expression
+	gnode_t *expr = parse_expression(parser);
+	CHECK_NODE(expr);
+	
+	// check and consume TOK_OP_CLOSED_PARENTHESIS
+	parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+	
+	return expr;
+}
+
+static gnode_t *parse_list_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_list_expression");
+	DECLARE_LEXER;
+	
+	/*
+	 list_expression
+	 :	'[' ((expression) (',' expression)*)? ']'		// array or empty array
+	 |	'[' ((map_entry (',' map_entry)*) | ':') ']'	// map or empty map
+	 ;
+	 
+	 map_entry
+	 :	STRING ':' expression
+	 ;
+	 */
+	
+	// consume first '['
+	parse_required(parser, TOK_OP_OPEN_SQUAREBRACKET);
+	
+	// this saved token is necessary to save start of the list/map
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	// check for special empty list
+	if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_SQUAREBRACKET) {
+		gravity_lexer_next(lexer); // consume TOK_OP_CLOSED_SQUAREBRACKET
+		return gnode_list_expr_create(token, NULL, NULL, false);
+	}
+	
+	// check for special empty map
+	if (gravity_lexer_peek(lexer) == TOK_OP_COLON) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COLON
+		parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
+		return gnode_list_expr_create(token, NULL, NULL, true);
+	}
+	
+	// parse first expression (if any) outside of the list/map loop
+	// in order to check if it is a list or map expression
+	gnode_t *expr1 = parse_expression(parser);
+	
+	// if next token is a colon then assume a map
+	bool ismap = (gravity_lexer_peek(lexer) == TOK_OP_COLON);
+	
+	// a list expression can be an array [expr1, expr2] or a map [string1: expr1, string2: expr2]
+	// cannot be mixed so be very restrictive here
+	
+	gnode_r *list1 = gnode_array_create();
+	gnode_r *list2 = (ismap) ? gnode_array_create() : NULL;
+	if (expr1) gnode_array_push(list1, expr1);
+	
+	if (ismap) {
+		parse_required(parser, TOK_OP_COLON);
+		gnode_t *expr2 = parse_expression(parser);
+		if (expr2) gnode_array_push(list2, expr2);
+	}
+	
+	while (gravity_lexer_peek(lexer) == TOK_OP_COMMA) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COMMA
+		
+		// parse first expression
+		expr1 = parse_expression(parser);
+		if (expr1) gnode_array_push(list1, expr1);
+		
+		if (ismap) {
+			parse_required(parser, TOK_OP_COLON);
+			gnode_t *expr2 = parse_expression(parser);
+			if (expr2) gnode_array_push(list2, expr2);
+		}
+	}
+	
+	parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
+	return gnode_list_expr_create(token, list1, list2, ismap);
+}
+
+static gnode_t *parse_function_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_function_expression");
+	
+	// 'func' '(' parameter_declaration_clause? ')' compound_statement
+	// or
+	// compound_statement (implicit func and implicit parameters)
+	/*
+		example:
+		func foo () {
+	 var bar = func(x) {return x*2;}
+	 return bar(3);
+		}
+	 
+		it is equivalent to:
+		
+		func foo () {
+	 func bar(x) {return x*2;}
+	 return bar(3);
+		}
+		
+	 */
+	
+	// check if func is a function expression or
+	// if it is a func keyword used to refers to
+	// the current executing function
+	
+	return parse_function(parser, false, 0, 0);
+}
+
+static gnode_t *parse_identifier_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_identifier_expression");
+	DECLARE_LEXER;
+	
+	const char	*identifier = parse_identifier(parser);
+	if (!identifier) return NULL;
+	DEBUG_PARSER("IDENTIFIER: %s", identifier);
+	
+	gtoken_s token = gravity_lexer_token(lexer);
+	return gnode_identifier_expr_create(token, identifier, NULL);
+}
+
+static gnode_t *parse_number_expression (gtoken_s token) {
+	DEBUG_PARSER("parse_number_expression");
+	
+	// what I know here is that token is a well formed NUMBER
+	// so I just need to properly decode it
+	
+	const char	*value = token.value;
+	gliteral_t	type;
+	int64_t		n = 0;
+	double		d = 0;
+	
+	if (value[0] == '0') {
+		int c = toupper(value[1]);
+		if (c == 'B') {type = decode_number_binary(token, &n); goto report_node;}
+		else if (c == 'O') {type = decode_number_octal(token, &n); goto report_node;}
+		else if (c == 'X') {type = decode_number_hex(token, &n, &d); goto report_node;}
+	}
+	
+	// number is decimal (check if is float)
+	bool isfloat = false;
+	for (uint32_t i=0; i<token.bytes; ++i) {
+		if (value[i] == '.') {isfloat = true; break;}
+	}
+	
+	STATIC_TOKEN_CSTRING(str, 512, len, buffer, token);
+	if (isfloat) {
+		d = strtod(str, NULL);
+		type = LITERAL_FLOAT;
+		DEBUG_PARSER("FLOAT: %.2f", d);
+	}
+	else {
+		n = (int64_t) strtoll(str, NULL, 0);
+		type = LITERAL_INT;
+		DEBUG_PARSER("INT: %lld", n);
+	}
+	
+report_node:
+	if (type == LITERAL_FLOAT) return gnode_literal_float_expr_create(token, (double)d);
+	else if (type == LITERAL_INT) return gnode_literal_int_expr_create(token, n);
+	else assert(0);
+	
+	return NULL;
+}
+
+static gnode_t *parse_literal_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_literal_expression");
+	DECLARE_LEXER;
+	
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	if (type == TOK_STRING) {
+		uint32_t len = 0;
+		const char *value = token_string(token, &len);
+		DEBUG_PARSER("STRING: %.*s", len, value);
+		return gnode_literal_string_expr_create(token, value, len);
+	}
+	
+	if (type == TOK_KEY_TRUE || type == TOK_KEY_FALSE) {
+		return gnode_literal_bool_expr_create(token, (int32_t)(type == TOK_KEY_TRUE) ? 1 : 0);
+	}
+	
+	if (type != TOK_NUMBER) {
+		REPORT_ERROR(token, "Expected literal expression but found %s.", token_name(type));
+		return NULL;
+	}
+	
+	return parse_number_expression(token);
+}
+
+static gnode_t *parse_keyword_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_keyword_expression");
+	DECLARE_LEXER;
+	
+	gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	return gnode_keyword_expr_create(token);
+}
+
+static gnode_r *parse_arguments_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_call_expression_list");
+	DECLARE_LEXER;
+	
+	// it's OK for a call_expression_list to be empty
+	if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS) return NULL;
+	
+	gnode_r *list = gnode_array_create();
+	while (1) {
+		gtoken_t peek = gravity_lexer_peek(lexer);
+		
+		if (peek == TOK_OP_COMMA) {
+			// added the ability to convert ,, to ,undefined,
+			gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN));
+			
+			// consume next TOK_OP_COMMA and check for special ,) case
+			gravity_lexer_next(lexer);
+			if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS) gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN));
+		} else {
+			// check exit condition
+			if ((peek == TOK_EOF) || (peek == TOK_OP_CLOSED_PARENTHESIS)) break;
+			
+			// parse expression
+			gnode_t *expr = parse_expression(parser);
+			if (expr) gnode_array_push(list, expr);
+			
+			// consume next TOK_OP_COMMA and check for special ,) case
+			if (gravity_lexer_peek(lexer) == TOK_OP_COMMA) {
+				gravity_lexer_next(lexer);
+				if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_PARENTHESIS) gnode_array_push(list, gnode_keyword_expr_create(UNDEF_TOKEN));
+			}
+		}
+	}
+	
+	return list;
+}
+
+static gnode_t *parse_postfix_expression (gravity_parser_t *parser, gtoken_t tok) {
+	DEBUG_PARSER("parse_postfix_expression");
+	DECLARE_LEXER;
+	
+	// '[' assignment_expression ']' => Subscript operator
+	// '(' expression_list? ')' => Function call operator
+	// '.' IDENTIFIER => Member access operator
+	
+	// tok already consumed and used to identify postfix sub-expression
+	gnode_t *lnode = parser->current_node;
+	gtoken_s token = gravity_lexer_token(lexer);
+	
+	// a postfix expression is an expression followed by a list of other expressions (separated by specific tokens)
+	gnode_r *list = gnode_array_create();
+	while (1) {
+		gnode_t *node = NULL;
+		
+		if (tok == TOK_OP_OPEN_SQUAREBRACKET) {
+			gnode_t *expr = parse_expression(parser);
+			gtoken_s subtoken = gravity_lexer_token(lexer);
+			parse_required(parser, TOK_OP_CLOSED_SQUAREBRACKET);
+			node = gnode_postfix_subexpr_create(subtoken, NODE_SUBSCRIPT_EXPR, expr, NULL);
+		} else if (tok == TOK_OP_OPEN_PARENTHESIS) {
+			gnode_r *args = parse_arguments_expression(parser);	// can be NULL and it's OK
+			gtoken_s subtoken = gravity_lexer_token(lexer);
+			parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+			node = gnode_postfix_subexpr_create(subtoken, NODE_CALL_EXPR, NULL, args);
+		} else if (tok == TOK_OP_DOT) {
+			gnode_t *expr = parse_identifier_expression(parser);
+			gtoken_s subtoken = gravity_lexer_token(lexer);
+			node = gnode_postfix_subexpr_create(subtoken, NODE_ACCESS_EXPR, expr, NULL);
+		} else {
+			// should never reach this point
+			assert(0);
+		}
+		
+		// add subnode to list
+		gnode_array_push(list, node);
+		
+		// check if postifx expression has more sub-nodes
+		gtoken_t peek = gravity_lexer_peek(lexer);
+		if ((peek != TOK_OP_OPEN_SQUAREBRACKET) && (peek != TOK_OP_OPEN_PARENTHESIS) && (peek != TOK_OP_DOT)) break;
+		tok = gravity_lexer_next(lexer);
+	}
+	
+	return gnode_postfix_expr_create(token, lnode, list);
+}
+
+static gnode_t *parse_postfix_subscript (gravity_parser_t *parser) {
+	// NOTE:
+	// Gravity does not support a syntax like m[1,2] for matrix access (not m[1,2,3])
+	// but it supports a syntax like m[1][2] (or m[1][2][3])
+	
+	DEBUG_PARSER("parse_postfix_subscript");
+	return parse_postfix_expression(parser, TOK_OP_OPEN_SQUAREBRACKET);
+}
+
+static gnode_t *parse_postfix_access (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_postfix_access");
+	return parse_postfix_expression(parser, TOK_OP_DOT);
+}
+
+static gnode_t *parse_postfix_call (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_postfix_call");
+	return parse_postfix_expression(parser, TOK_OP_OPEN_PARENTHESIS);
+}
+
+static gnode_t *parse_precedence(gravity_parser_t *parser, prec_level precedence) {
+	DEBUG_PARSER("parse_precedence (level %d)", precedence);
+	DECLARE_LEXER;
+	
+	// peek next
+	gtoken_t type = gravity_lexer_peek(lexer);
+	if (type == TOK_EOF) return NULL;
+	
+	parse_func prefix = rules[type].prefix;
+	if (prefix == NULL) {
+		// gravity_lexer_token reports the latest succesfully scanned token but since we need to report
+		// an error for a peeked token then we force reporting "next" token
+		REPORT_ERROR(gravity_lexer_token_next(lexer), "Expected expression but found %s.", token_name(type));
+		return NULL;
+	}
+	gnode_t *node = prefix(parser);
+	
+	gtoken_t peek = gravity_lexer_peek(lexer);
+	if (type == TOK_EOF) return NULL;
+	
+	while (precedence < rules[peek].precedence) {
+		gtoken_t tok = gravity_lexer_next(lexer);
+		grammar_rule *rule = &rules[tok];
+		
+		parser->current_token = tok;
+		parser->current_node = node;
+		node = rule->infix(parser);
+		
+		peek = gravity_lexer_peek(lexer);
+		if (type == TOK_EOF) return NULL;
+	}
+	
+	return node;
+}
+
+static gnode_t *parse_expression (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_expression");
+	DECLARE_LEXER;
+	
+	// parse_expression is the default case called when no other case in parse_statament can be resolved
+	// due to some syntax errors an infinte loop condition can be verified
+	gtoken_s tok1 = gravity_lexer_token(lexer);
+	
+	gnode_t *expr = parse_precedence(parser, PREC_LOWEST);
+	
+	// expr is NULL means than an error condition has been encountered (and a potential infite loop)
+	if (expr == NULL) {
+		gtoken_s tok2 = gravity_lexer_token(lexer);
+		// if current token is equal to the token saved before the recursion than skip token in order to avoid infinite loop
+		if (token_identical(&tok1, &tok2)) gravity_lexer_next(lexer);
+	}
+	
+	return expr;
+}
+
+static gnode_t *parse_unary (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_unary");
+	DECLARE_LEXER;
+	
+	gtoken_t tok = gravity_lexer_next(lexer);
+	gnode_t *node = parse_precedence(parser, PREC_UNARY);
+	return gnode_unary_expr_create(tok, node);
+}
+
+static gnode_t *parse_infix (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_infix");
+	
+	gtoken_t tok = parser->current_token;
+	gnode_t *lnode = parser->current_node;
+	
+	// we can make right associative operators by reducing the right binding power
+	grammar_rule *rule = &rules[tok];
+	prec_level precedence = (rule->right) ? rule->precedence-1 : rule->precedence;
+	
+	gnode_t *rnode = parse_precedence(parser, precedence);
+	if ((tok != TOK_OP_ASSIGN) && token_isassignment(tok)) return adjust_assignment_expression(tok, lnode, rnode);
+	return gnode_binary_expr_create(tok, lnode, rnode);
+}
+
+// MARK: -
+
+static gnode_t *adjust_assignment_expression (gtoken_t tok, gnode_t *lnode, gnode_t *rnode) {
+	DEBUG_PARSER("adjust_assignment_expression");
+	
+	// called when tok is an assignment != TOK_OP_ASSIGN
+	// convert expressions:
+	// a += 1	=> a = a + 1
+	// a -= 1	=> a = a - 1
+	// a *= 1	=> a = a * 1
+	// a /= 1	=> a = a / 1
+	// a %= 1	=> a = a % 1
+	// a <<=1	=> a = a << 1
+	// a >>=1	=> a = a >> 1
+	// a &= 1	=> a = a & 1
+	// a |= 1	=> a = a | 1
+	// a ^= 1	=> a = a ^ 1
+	
+	gtoken_t t;
+	switch (tok) {
+		case TOK_OP_MUL_ASSIGN: t = TOK_OP_MUL; break;
+		case TOK_OP_DIV_ASSIGN: t = TOK_OP_DIV; break;
+		case TOK_OP_REM_ASSIGN: t = TOK_OP_REM; break;
+		case TOK_OP_ADD_ASSIGN: t = TOK_OP_ADD; break;
+		case TOK_OP_SUB_ASSIGN: t = TOK_OP_SUB; break;
+		case TOK_OP_SHIFT_LEFT_ASSIGN: t = TOK_OP_SHIFT_LEFT; break;
+		case TOK_OP_SHIFT_RIGHT_ASSIGN: t = TOK_OP_SHIFT_RIGHT; break;
+		case TOK_OP_BIT_AND_ASSIGN: t = TOK_OP_BIT_AND; break;
+		case TOK_OP_BIT_OR_ASSIGN: t = TOK_OP_BIT_OR; break;
+		case TOK_OP_BIT_XOR_ASSIGN: t = TOK_OP_BIT_XOR; break;
+		
+		// should never reach this point
+		default: assert(0); break;
+	}
+	
+	// duplicate node is mandatory here, otherwise the deallocator will try to free memory occopied by the same node twice
+	rnode = gnode_binary_expr_create(t, gnode_duplicate(lnode, true), rnode);
+	tok = TOK_OP_ASSIGN;
+	
+	// its an assignment expression so switch the order
+	return gnode_binary_expr_create(tok, lnode, rnode);
+}
+
+static void init_grammer_rules (void) {
+	static bool created = false;
+	if (created) return;
+	created = true;
+	
+	// rules is a static variable initialized to 0
+	// so we automatically have all members initialized to UNUSED
+	
+	rules[TOK_OP_OPEN_PARENTHESIS] = RULE(PREC_CALL, parse_parentheses_expression, parse_postfix_call);
+	rules[TOK_OP_OPEN_SQUAREBRACKET] = RULE(PREC_CALL, parse_list_expression, parse_postfix_subscript);
+	rules[TOK_OP_DOT] = RULE(PREC_CALL, parse_literal_expression, parse_postfix_access);
+	
+	rules[TOK_OP_OPEN_CURLYBRACE] = PREFIX(PREC_LOWEST, parse_function_expression);
+	rules[TOK_KEY_FUNC] = PREFIX(PREC_LOWEST, parse_function_expression);
+	
+	rules[TOK_IDENTIFIER] = PREFIX(PREC_LOWEST, parse_identifier_expression);
+	rules[TOK_STRING] = PREFIX(PREC_LOWEST, parse_literal_expression);
+	rules[TOK_NUMBER] = PREFIX(PREC_LOWEST, parse_literal_expression);
+	
+	rules[TOK_KEY_UNDEFINED] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_CURRARGS] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_CURRFUNC] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_SUPER] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_FILE] = PREFIX(PREC_LOWEST, parse_file_expression);
+	rules[TOK_KEY_NULL] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_TRUE] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	rules[TOK_KEY_FALSE] = PREFIX(PREC_LOWEST, parse_keyword_expression);
+	
+	rules[TOK_OP_SHIFT_LEFT] = INFIX_OPERATOR(PREC_SHIFT, "<<");
+	rules[TOK_OP_SHIFT_RIGHT] = INFIX_OPERATOR(PREC_SHIFT, ">>");
+	
+	rules[TOK_OP_MUL] = INFIX_OPERATOR(PREC_FACTOR, "*");
+	rules[TOK_OP_DIV] = INFIX_OPERATOR(PREC_FACTOR, "/");
+	rules[TOK_OP_REM] = INFIX_OPERATOR(PREC_FACTOR, "%");
+	rules[TOK_OP_BIT_AND] = INFIX_OPERATOR(PREC_FACTOR, "&");
+	rules[TOK_OP_ADD] = OPERATOR(PREC_TERM, "+");
+	rules[TOK_OP_SUB] = OPERATOR(PREC_TERM, "-");
+	rules[TOK_OP_BIT_OR] = INFIX_OPERATOR(PREC_TERM, "|");
+	rules[TOK_OP_BIT_XOR] = INFIX_OPERATOR(PREC_TERM, "^");
+	rules[TOK_OP_BIT_NOT] = PREFIX_OPERATOR("~");
+	
+	rules[TOK_OP_RANGE_EXCLUDED] = INFIX_OPERATOR(PREC_RANGE, "..<");
+	rules[TOK_OP_RANGE_INCLUDED] = INFIX_OPERATOR(PREC_RANGE, "...");
+	
+	rules[TOK_KEY_ISA] = INFIX_OPERATOR(PREC_ISA, "isa");
+	rules[TOK_OP_LESS] = INFIX_OPERATOR(PREC_COMPARISON, "<");
+	rules[TOK_OP_LESS_EQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "<=");
+	rules[TOK_OP_GREATER] = INFIX_OPERATOR(PREC_COMPARISON, ">");
+	rules[TOK_OP_GREATER_EQUAL] = INFIX_OPERATOR(PREC_COMPARISON, ">=");
+	rules[TOK_OP_ISEQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "==");
+	rules[TOK_OP_ISNOTEQUAL] = INFIX_OPERATOR(PREC_COMPARISON, "!=");
+	rules[TOK_OP_ISIDENTICAL] = INFIX_OPERATOR(PREC_COMPARISON, "===");
+	rules[TOK_OP_ISNOTIDENTICAL] = INFIX_OPERATOR(PREC_COMPARISON, "!==");
+	rules[TOK_OP_PATTERN_MATCH] = INFIX_OPERATOR(PREC_COMPARISON, "~=");
+	
+	rules[TOK_OP_AND] = INFIX_OPERATOR_RIGHT(PREC_LOGICAL_AND, "&&");
+	rules[TOK_OP_OR] = INFIX_OPERATOR_RIGHT(PREC_LOGICAL_OR, "||");
+	rules[TOK_OP_TERNARY] = INFIX(PREC_TERNARY, parse_ternary_expression);
+	
+	rules[TOK_OP_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "=");
+	rules[TOK_OP_MUL_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "*=");
+	rules[TOK_OP_DIV_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "/=");
+	rules[TOK_OP_REM_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "%=");
+	rules[TOK_OP_ADD_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "+=");
+	rules[TOK_OP_SUB_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "-=");
+	rules[TOK_OP_SHIFT_LEFT_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "<<=");
+	rules[TOK_OP_SHIFT_RIGHT_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, ">>=");
+	rules[TOK_OP_BIT_AND_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "=&");
+	rules[TOK_OP_BIT_OR_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "|=");
+	rules[TOK_OP_BIT_XOR_ASSIGN] = INFIX_OPERATOR(PREC_ASSIGN, "^=");
+	
+	rules[TOK_OP_NOT] = PREFIX_OPERATOR("!");
+}
+
+// MARK: - Declarations -
+
+static gnode_t *parse_getter_setter (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_getter_setter");
+	DECLARE_LEXER;
+	
+	gnode_t *getter = NULL;
+	gnode_t *setter = NULL;
+	gtoken_s token_block = gravity_lexer_token(lexer);
+	
+	while (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE) {
+		const char *identifier = parse_identifier(parser);
+		if (!identifier) goto parse_error;
+		
+		bool		is_getter = false;
+		gtoken_s	token = gravity_lexer_token(lexer);
+		gnode_r		*params = NULL;
+		
+		// getter case: does not have explicit parameters (only implicit self)
+		if (strcmp(identifier, GETTER_FUNCTION_NAME) == 0) {
+			is_getter = true;
+			params = gnode_array_create();	// add implicit SELF param
+			gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SELF_PARAMETER_NAME), NULL, 0, NULL));
+		}
+		
+		// setter case: could have explicit parameters (otherwise value is implicit)
+		if (strcmp(identifier, SETTER_FUNCTION_NAME) == 0) {
+			is_getter = false;
+			// check if parameters are explicit
+			if (gravity_lexer_peek(lexer) == TOK_OP_OPEN_PARENTHESIS) {
+				parse_required(parser, TOK_OP_OPEN_PARENTHESIS);
+				params = parse_optional_parameter_declaration(parser);	// add implicit SELF
+				parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+			} else {
+				params = gnode_array_create();	// add implicit SELF and VALUE params
+				gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SELF_PARAMETER_NAME), NULL, 0, NULL));
+				gnode_array_push(params, gnode_variable_create(NO_TOKEN, string_dup(SETTER_PARAMETER_NAME), NULL, 0, NULL));
+			}
+		}
+		mem_free(identifier);
+		
+		// parse compound statement
+		PUSH_FUNCTION_DECLARATION();
+		gnode_compound_stmt_t *compound = (gnode_compound_stmt_t*)parse_compound_statement(parser);
+		POP_DECLARATION();
+		gnode_t *f = gnode_function_decl_create(token, NULL, 0, 0, params, compound);
+		
+		// assign f to the right function
+		if (is_getter) getter = f; else setter = f;
+	}
+	
+	gnode_r *functions = gnode_array_create();
+	gnode_array_push(functions, (getter) ? getter : NULL);	// getter is at index 0
+	gnode_array_push(functions, (setter) ? setter : NULL);	// setter is at index 1
+	
+	// a compound node is used to capture getter and setter
+	return gnode_block_stat_create(NODE_COMPOUND_STAT, token_block, functions);
+	
+parse_error:
+	return NULL;
+}
+
+static gnode_t *parse_variable_declaration (gravity_parser_t *parser, bool isstatement, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	DEBUG_PARSER("parse_variable_declaration");
+	DECLARE_LEXER;
+	
+	gnode_r		*decls = NULL;
+	gnode_t		*decl = NULL;
+	gnode_t		*expr = NULL;
+	const char	*identifier = NULL;
+	const char	*type_annotation = NULL;
+	gtoken_t	type;
+	gtoken_t	peek;
+	gtoken_s	token, token2;
+	
+	// access_specifier? storage_specifier? variable_declaration ';'
+	// variable_declaration: variable_declarator decl_item
+	// variable_declarator: 'const' | 'var'
+	// decl_item: (IDENTIFIER assignment?) (',' IDENTIFIER assignment?)*
+	// assignment
+	
+	// sanity check on variable type
+	type = gravity_lexer_next(lexer);
+	if (!token_isvariable_declaration(type)) {
+		REPORT_ERROR(gravity_lexer_token(lexer), "VAR or CONST expected here but found %s.", token_name(type));
+		return NULL;
+	}
+	token = gravity_lexer_token(lexer);
+	
+	// initialize node array
+	decls = gnode_array_create();
+	
+loop:
+	identifier = parse_identifier(parser);
+	if (!identifier) return NULL;
+	token2 = gravity_lexer_token(lexer);
+	
+	// type annotation is optional so it can be NULL
+	type_annotation = parse_optional_type_annotation(parser);
+	DEBUG_PARSER("IDENTIFIER: %s %s", identifier, (type_annotation) ? type_annotation : "");
+	
+	// check for optional assignment or getter/setter declaration (ONLY = is ALLOWED here!)
+	expr = NULL;
+	peek = gravity_lexer_peek(lexer);
+	if (token_isvariable_assignment(peek)) {
+		gravity_lexer_next(lexer); // consume ASSIGNMENT
+		expr = parse_expression(parser);
+	} else if (peek == TOK_OP_OPEN_CURLYBRACE) {
+		gravity_lexer_next(lexer); // consume TOK_OP_OPEN_CURLYBRACE
+		expr = parse_getter_setter(parser);
+		parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	}
+	
+	// sanity checks
+	// 1. CONST must be followed by an assignment expression ?
+	// 2. check if identifier is unique inside variable declarations
+	
+	decl = gnode_variable_create(token2, identifier, type_annotation, access_specifier, expr);
+	if (decl) gnode_array_push(decls, decl);
+	
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_COMMA) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COMMA
+		goto loop;
+	}
+	
+	// check and consume TOK_OP_SEMICOLON (ALWAYS required for assignments)
+	// Aaron: I would keep it consistent, even if it's not strictly required.
+	// Otherwise we end up with all of JavaScript's terrible ideas. ;-)
+	// So I would require the semicolon at the end of any assignment statement.
+	if (isstatement) parse_semicolon(parser);
+	
+	return gnode_variable_decl_create(token, type, access_specifier, storage_specifier, decls);
+}
+
+static gnode_t *parse_enum_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	DEBUG_PARSER("parse_enum_declaration");
+	DECLARE_LEXER;
+	
+	// enum is a bit different than the traditional C like enum statements
+	// in Gravity enum can contains String, Integer, Boolean and Float BUT cannot be mixed
+	// Integer case can also skip values and autoincrement will be applied
+	// String and Float must have a default value
+	// in any case default value must be unique (as identifiers)
+	// this code will take care of parsing and syntax check for the above restrictions
+	
+	// in order to simplify node struct all the sematic checks are performed here
+	// even if it is not a best practice, I find a lot easier to perform all the checks
+	// directly into the parser
+	// parse_enum_declaration is the only reason why gravity_symboltable.h is included here
+	
+	// checks are:
+	// 1: unique internal identifiers
+	// 2: unique internal values
+	// 3: if not INT then a default value is mandatory
+	// 4: all values must be literals
+	
+	// 'enum' IDENTIFIER '{' enum_list '}' ';'
+	// enum_list: enum_list_item (',' enum_list_item)*
+	// enum_list_item: IDENTIFIER ('=' LITERAL)?
+	
+	// NODE_ENUM_DECL
+	
+	// optional scope already consumed
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert(type == TOK_KEY_ENUM);
+	
+	// parse IDENTIFIER
+	const char *identifier = parse_identifier(parser);
+	DEBUG_PARSER("parse_enum_declaration %s", identifier);
+	
+	// check and consume TOK_OP_OPEN_CURLYBRACE
+	parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
+	
+	symboltable_t	*symtable = symboltable_create(true);	// enum symbol table (symtable is OK because order is not important inside an enum)
+	int64_t			enum_autoint = 0;						// autoincrement value (in case of INT enum)
+	uint32_t		enum_counter = 0;						// enum internal counter (first value (if any) determines enum type)
+	gliteral_t		enum_type = LITERAL_INT;				// enum type (default to int)
+	
+	while (1) {
+		// check for empty enum
+		if (gravity_lexer_peek(lexer) == TOK_OP_CLOSED_CURLYBRACE) break;
+		
+		// identifier is mandatory here
+		const char *enum_id = NULL;
+		gtoken_t peek = gravity_lexer_peek(lexer);
+		gtoken_s enumid_token = NO_TOKEN;
+		if (peek == TOK_IDENTIFIER) {
+			enum_id = parse_identifier(parser);
+			enumid_token = gravity_lexer_token(lexer);
+		}
+		if (!enum_id) {
+			REPORT_ERROR(enumid_token, "Identifier expected here (found %s).", token_name(peek));
+		}
+		
+		// peek next that can be only = or , or }
+		peek = gravity_lexer_peek(lexer);
+		gtoken_s enum_token = gravity_lexer_token(lexer);
+		if (!token_isvariable_assignment(peek) && (peek != TOK_OP_COMMA) && (peek != TOK_OP_CLOSED_CURLYBRACE)) {
+			REPORT_ERROR(enum_token, "Token %s not allowed here.", token_name(peek));
+		}
+		
+		// check for assignment (ONLY = is ALLOWED here!)
+		// assignment is optional ONLY for LITERAL_TYPE_INT case
+		if ((!token_isvariable_assignment(peek)) && (enum_type != LITERAL_INT)) {
+			REPORT_ERROR(enum_token, "A default value is expected here (found %s).", token_name(peek));
+		}
+		
+		// check for optional default value (optional only in LITERAL_INT case)
+		gnode_base_t *enum_value = NULL;
+		if (token_isvariable_assignment(peek)) {
+			gravity_lexer_next(lexer); // consume ASSIGNMENT
+			enum_value = (gnode_base_t *)parse_expression(parser);
+		}
+		
+		if (enum_value) {
+			// make sure that value is a literal (or a unary expression like +num or -num)
+			gnode_literal_expr_t *enum_literal = NULL;
+			if (enum_value->base.tag == NODE_LITERAL_EXPR) {
+				enum_literal = (gnode_literal_expr_t *)enum_value;
+			} else if (enum_value->base.tag == NODE_UNARY_EXPR) {
+				gnode_unary_expr_t *unary = (gnode_unary_expr_t *)enum_value;
+				gnode_base_t *expr = (gnode_base_t *)unary->expr;
+				
+				// sanity check on unary expression
+				if (expr->base.tag != NODE_LITERAL_EXPR) {
+					REPORT_ERROR(enum_token, "%s", "Literal value expected here.");
+					continue;
+				}
+				
+				if ((unary->op != TOK_OP_SUB) && (unary->op != TOK_OP_ADD)) {
+					REPORT_ERROR(enum_token, "%s", "Only + or - allowed in enum value definition.");
+					continue;
+				}
+				
+				enum_literal = (gnode_literal_expr_t *)expr;
+				if ((enum_literal->type != LITERAL_FLOAT) && (enum_literal->type != LITERAL_INT)) {
+					REPORT_ERROR(enum_token, "%s", "A number is expected after a + or - unary expression in an enum definition.");
+					continue;
+				}
+				
+				if (unary->op == TOK_OP_SUB) {
+					if (enum_literal->type == LITERAL_FLOAT) enum_literal->value.d = -enum_literal->value.d;
+					else if (enum_literal->type == LITERAL_INT) enum_literal->value.n64 = -enum_literal->value.n64;
+					else assert(0); // should never reach this point
+				}
+				
+			} else {
+				REPORT_ERROR(enum_token, "%s", "Literal value expected here.");
+				continue;
+			}
+			
+			// first assignment (if any) determines enum type, otherwise default INT case is assumed
+			if (enum_counter == 0) {
+				if (enum_literal->type == LITERAL_STRING) enum_type = LITERAL_STRING;
+				else if (enum_literal->type == LITERAL_FLOAT) enum_type = LITERAL_FLOAT;
+				else if (enum_literal->type == LITERAL_BOOL) enum_type = LITERAL_BOOL;
+			}
+			
+			// check if literal value conforms to enum type
+			if (enum_literal->type != enum_type) {
+				REPORT_ERROR(enum_token, "%s", "Literal value of type %s expected here.", token_literal_name(enum_literal->type));
+			}
+			
+			// update enum_autoint value to next value
+			if (enum_literal->type == LITERAL_INT) {
+				enum_autoint = enum_literal->value.n64 + 1;
+			}
+			
+		} else {
+			enum_value = (gnode_base_t *)gnode_literal_int_expr_create((gtoken_s)NO_TOKEN, enum_autoint);
+			++enum_autoint;
+		}
+		
+		// update internal enum counter
+		++enum_counter;
+		
+		// enum identifier could be NULL due to an already reported error
+		if (enum_id) {
+			if (!symboltable_insert(symtable, enum_id, (void *)enum_value)) {
+				REPORT_ERROR(enumid_token, "Identifier %s redeclared.", enum_id);
+				gnode_free((gnode_t *)enum_value); // free value here because it has not beed saved into symbol table
+			}
+			mem_free(enum_id); // because key is duplicated inside internal hash table
+		}
+		
+		peek = gravity_lexer_peek(lexer);
+		if (peek != TOK_OP_COMMA) break;
+		
+		// consume TOK_OP_COMMA and continue loop
+		gravity_lexer_next(lexer);
+	}
+	
+	// check and consume TOK_OP_CLOSED_CURLYBRACE
+	parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	
+	// consume semicolon
+	parse_semicolon(parser);
+	
+	// check for empty enum (not allowed)
+	if (enum_counter == 0) {
+		REPORT_ERROR(token, "Empty enum %s not allowed.", identifier);
+	}
+	
+	gnode_t *node = gnode_enum_decl_create(token, identifier, access_specifier, storage_specifier, symtable);
+	if (IS_FUNCTION_ENCLOSED()) return local_store_declaration(identifier, access_specifier, storage_specifier, node);
+	return node;
+}
+
+static gnode_t *parse_module_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	DEBUG_PARSER("parse_module_declaration");
+	
+	// 'module' IDENTIFIER '{' declaration_statement* '}' ';'
+	
+	// optional scope already consumed
+	DECLARE_LEXER;
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert(type == TOK_KEY_MODULE);
+	
+	// parse IDENTIFIER
+	const char *identifier = parse_identifier(parser);
+	DEBUG_PARSER("parse_module_declaration %s", identifier);
+	
+	// parse optional curly brace
+	bool curly_brace = parse_optional(parser, TOK_OP_OPEN_CURLYBRACE);
+	
+	gnode_r *declarations = gnode_array_create();
+	while (token_isdeclaration_statement(gravity_lexer_peek(lexer))) {
+		gnode_t *decl = parse_declaration_statement(parser);
+		if (decl) gnode_array_push(declarations, decl);
+	}
+	
+	// check and consume TOK_OP_CLOSED_CURLYBRACE
+	if (curly_brace) parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	
+	parse_semicolon(parser);
+	
+	return gnode_module_decl_create(token, identifier, access_specifier, storage_specifier, declarations);
+}
+
+static gnode_t *parse_event_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	#pragma unused(parser, access_specifier, storage_specifier)
+	
+	// 'event' IDENTIFIER '(' parameter_declaration_clause? ')' ';'
+	
+	// NODE_EVENT_DECL
+	assert(0);
+	return NULL;
+}
+
+static gnode_t *parse_function_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	gnode_t *node = parse_function(parser, true, access_specifier, storage_specifier);
+	
+	// convert a function declaration within another function to a local variable assignment
+	// for example:
+	//
+	// func foo() {
+	//	func bar() {...}
+	// }
+	//
+	// is converter to:
+	//
+	// func foo() {
+	//	var bar = func() {...}
+	// }
+	//
+	// conversion is performed inside the parser
+	// so next semantic checks can perform
+	// identifier uniqueness checks
+	
+	if (IS_FUNCTION_ENCLOSED()) return local_store_declaration(((gnode_function_decl_t *)node)->identifier, access_specifier, storage_specifier, node);
+	return node;
+}
+
+static gnode_t *parse_id (gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	const char	*identifier1 = NULL;
+	const char	*identifier2 = NULL;
+	gtoken_t	peek;
+	gtoken_s	token;
+	
+	// IDENTIFIER |	(IDENTIFIER)('.' IDENTIFIER)
+	
+	DEBUG_PARSER("parse_id");
+	
+	identifier1 = parse_identifier(parser);
+	
+	token = gravity_lexer_token(lexer);
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_DOT) {
+		gravity_lexer_next(lexer); // consume TOK_OP_DOT
+		identifier2 = parse_identifier(parser);
+	}
+	
+	DEBUG_PARSER("ID: %s %s", identifier1, (identifier2) ? identifier2 : "");
+	return gnode_identifier_expr_create(token, identifier1, identifier2);
+}
+
+static gnode_r *parse_protocols (gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	gtoken_t peek;
+	gnode_t	 *node = NULL;
+	gnode_r	 *list = NULL;
+	
+	// (id) (',' id)*
+	
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_GREATER) return NULL; // just an empty protocols implementation statement
+	
+	list = gnode_array_create();
+	
+loop:
+	if (!token_isidentifier(peek)) goto abort;
+	node = parse_id(parser);
+	if (node) gnode_array_push(list, node);
+	
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_COMMA) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COMMA
+		goto loop;
+	}
+	
+	return list;
+	
+abort:
+	if (list) gnode_array_free(list);
+	return NULL;
+}
+
+static gnode_t *parse_class_declaration (gravity_parser_t *parser, gtoken_t access_specifier, gtoken_t storage_specifier) {
+	DEBUG_PARSER("parse_class_declaration");
+	DECLARE_LEXER;
+	
+	// access_specifier? storage_specifier? 'class' IDENTIFIER class_superclass? class_protocols? '{' declaration_statement* '}' ';'
+	// class_superclass: (':') id
+	// class_protocols: '<' (id) (',' id)* '>'
+	
+	// optional scope already consumed (when here I am sure type is TOK_KEY_CLASS or TOK_KEY_STRUCT)
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	bool is_struct = (type == TOK_KEY_STRUCT);
+	
+	// parse IDENTIFIER
+	const char *identifier = parse_identifier(parser);
+	
+	// check for optional superclass
+	gnode_t *super = NULL;
+	gnode_r *protocols = NULL;
+	gtoken_t peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_COLON) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COLON
+		super = parse_id(parser);
+	}
+	
+	// check for optional protocols (not supported in this version)
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_LESS) {
+		gravity_lexer_next(lexer); // consume '<'
+		protocols = parse_protocols(parser);
+		parse_required(parser, TOK_OP_GREATER);  // consume '>'
+	}
+	
+	// check and consume TOK_OP_OPEN_CURLYBRACE
+	parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
+	gnode_r *declarations = gnode_array_create();
+	
+	// if class is declared inside another class then a hidden implicit privare "outer" instance var at index 0
+	// is automatically added
+	if (IS_CLASS_ENCLOSED()) {
+		gnode_r *decls = gnode_array_create();
+		gnode_t *outer_var = gnode_variable_create((gtoken_s)NO_TOKEN, string_dup(OUTER_IVAR_NAME), NULL, 0, NULL);
+		gnode_array_push(decls, outer_var);
+		
+		gnode_t *outer_decl = gnode_variable_decl_create((gtoken_s)NO_TOKEN, TOK_KEY_VAR, TOK_KEY_PRIVATE, 0, decls);
+		gnode_array_push(declarations, outer_decl);
+	}
+	
+	PUSH_CLASS_DECLARATION();
+	while (token_isdeclaration_statement(gravity_lexer_peek(lexer))) {
+		gnode_t *decl = parse_declaration_statement(parser);
+		if (decl) gnode_array_push(declarations, decl);
+	}
+	POP_DECLARATION();
+	
+	// check and consume TOK_OP_CLOSED_CURLYBRACE
+	parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	
+	// to check
+	parse_semicolon(parser);
+	
+	gnode_t *node = gnode_class_decl_create(token, identifier, access_specifier, storage_specifier, super, protocols, declarations, is_struct);
+	if (IS_FUNCTION_ENCLOSED()) return local_store_declaration(identifier, access_specifier, storage_specifier, node);
+	return node;
+}
+
+static gnode_r *parse_optional_parameter_declaration (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_parameter_declaration");
+	DECLARE_LEXER;
+	
+	gtoken_s	token = NO_TOKEN;
+	gnode_t		*node = NULL;
+	const char	*identifier = NULL;
+	const char	*type_annotation = NULL;
+	
+	// (IDENTIFIER type_annotation?) (',' type_annotation)*
+	// type_annotation: ':' identifier
+	
+	gnode_r *params = gnode_array_create();
+	assert(params);
+	
+	// check if implicit self parameter must be added
+	// was if (IS_CLASS_ENCLOSED()*/) { ... add SELF PARAMETER ...}
+	// but we decided to ALWAYS pass SELF because it simplified cases
+	// like c1().p1.p1.p1(1234);
+	
+	// ALWAYS add an implicit SELF parameter
+	// string_dup mandatory here because when the node will be freed
+	// memory for the identifier will be deallocated
+	node = gnode_variable_create(token, string_dup(SELF_PARAMETER_NAME), type_annotation, 0, NULL);
+	if (node) gnode_array_push(params, node);
+		
+	// parameter declaration clause is ALWAYS optional
+	gtoken_t peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_CLOSED_PARENTHESIS) return params;
+	
+	// so there is at leat one explicit parameter
+loop:
+	// initialize variables
+	type_annotation = NULL;
+	
+	// parse identifier
+	identifier = parse_identifier(parser);
+	token = gravity_lexer_token(lexer);
+	
+	// parse optional type annotation
+	type_annotation = parse_optional_type_annotation(parser);
+		
+	// fill parameters array with the new node
+	node = gnode_variable_create(token, identifier, type_annotation, 0, NULL);
+	if (node) gnode_array_push(params, node);
+	
+	// check for optional comma in order to decide
+	// if the loop should continue or not
+	peek = gravity_lexer_peek(lexer);
+	if (peek == TOK_OP_COMMA) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COMMA
+		goto loop;
+	}
+	
+	return params;
+}
+
+// MARK: - UnitTest -
+
+typedef enum {
+	UNITTEST_NONE,
+	UNITTEST_NAME,
+	UNITTEST_ERROR,
+	UNITTEST_RESULT,
+	UNITTEST_ERROR_ROW,
+	UNITTEST_ERROR_COL,
+	UNITTEST_NOTE
+} unittest_t;
+
+static unittest_t parse_unittest_identifier(const char *identifier) {
+	if (string_cmp(identifier, "name") == 0) return UNITTEST_NAME;
+	if (string_cmp(identifier, "note") == 0) return UNITTEST_NOTE;
+	if (string_cmp(identifier, "error") == 0) return UNITTEST_ERROR;
+	if (string_cmp(identifier, "error_row") == 0) return UNITTEST_ERROR_ROW;
+	if (string_cmp(identifier, "error_col") == 0) return UNITTEST_ERROR_COL;
+	if (string_cmp(identifier, "result") == 0) return UNITTEST_RESULT;
+	
+	return UNITTEST_NONE;
+}
+
+static gnode_t *parse_unittest_declaration(gravity_parser_t *parser) {
+	DECLARE_LEXER;
+	DEBUG_PARSER("parse_unittest_declaration");
+	
+	// @unittest {
+	//		name: "Unit test name";
+	//		note: "Some notes here";
+	//		error: NONE, SYNTAX, RUNTIME, WARNING;
+	//		error_row: number;
+	//		error_col: number;
+	//		result: LITERAL;
+	// '}' ';'?
+	
+	gnode_literal_expr_t	*name_node = NULL;
+	gnode_literal_expr_t	*note_node = NULL;
+	gnode_identifier_expr_t	*err_node = NULL;
+	gnode_literal_expr_t	*row_node = NULL;
+	gnode_literal_expr_t	*col_node = NULL;
+	gnode_literal_expr_t	*value_node = NULL;
+	
+	parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
+	while (gravity_lexer_peek(lexer) != TOK_OP_CLOSED_CURLYBRACE) {
+		const char	*id = parse_identifier(parser);
+		if (id == NULL) goto handle_error;
+		parse_required(parser, TOK_OP_COLON);
+		
+		unittest_t type = parse_unittest_identifier(id);
+		mem_free(id);
+		
+		if (type == UNITTEST_NAME) {
+			name_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
+			if (name_node == NULL) goto handle_error;
+		}
+		else if (type == UNITTEST_NOTE) {
+			note_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
+			if (note_node == NULL) goto handle_error;
+		}
+		else if (type == UNITTEST_ERROR) {
+			err_node = (gnode_identifier_expr_t *)parse_identifier_expression(parser);
+			if (err_node == NULL) goto handle_error;
+		}
+		else if (type == UNITTEST_ERROR_ROW) {
+			row_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
+			if (row_node == NULL) goto handle_error;
+		}
+		else if (type == UNITTEST_ERROR_COL) {
+			col_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
+			if (col_node == NULL) goto handle_error;
+		}
+		else if (type == UNITTEST_RESULT) {
+			gtoken_t op = TOK_EOF;
+			gtoken_t peek = gravity_lexer_peek(lexer);
+			
+			// check if peek is a + or - sign
+			if ((peek == TOK_OP_SUB) || (peek == TOK_OP_ADD))
+				op = gravity_lexer_next(lexer);
+			else if (peek == TOK_KEY_NULL) {
+				// an expected return value can now be keyword NULL
+				gravity_lexer_next(lexer); // consume NULL keyword
+				value_node = NULL;
+				goto handle_continue;
+			}
+			
+			value_node = (gnode_literal_expr_t *)parse_literal_expression(parser);
+			if (value_node == NULL) goto handle_error;
+			
+			// if a negative sign has been parsed then manually fix the literal expression (if it is a number)
+			if (op == TOK_OP_SUB) {
+				if (value_node->type == LITERAL_INT) value_node->value.n64 = -value_node->value.n64;
+				else if (value_node->type == LITERAL_FLOAT) value_node->value.d = -value_node->value.d;
+			}
+		}
+		else {
+			REPORT_ERROR(gravity_lexer_token(lexer), "Unknown token found in @unittest declaration.");
+			goto handle_error;
+		}
+		
+	handle_continue:
+		parse_semicolon(parser);
+	}
+	
+	parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	parse_semicolon(parser);
+	
+	// decode unit array and report error/unittest
+	// unit test name max length is 1024
+	const char			*description = NULL;
+	const char			*note = NULL;
+	char				buffer[1024];
+	char				buffer2[1024];
+	error_type_t		expected_error = GRAVITY_ERROR_NONE;
+	gravity_value_t		expected_value = VALUE_FROM_NULL;
+	int32_t				expected_nrow = -1;
+	int32_t				expected_ncol = -1;
+	
+	// unittest name should be a literal string
+	if ((name_node) && (name_node->type == LITERAL_STRING)) {
+		// no more C strings in AST so we need a static buffer
+		snprintf(buffer, sizeof(buffer), "%.*s", name_node->len, name_node->value.str);
+		description = buffer;
+	}
+	
+	// note (optional) should be a literal string
+	if ((note_node) && (note_node->type == LITERAL_STRING)) {
+		// no more C strings in AST so we need a static buffer
+		snprintf(buffer2, sizeof(buffer2), "%.*s", note_node->len, note_node->value.str);
+		note = buffer2;
+	}
+	
+	// decode expected error: NONE, SYNTAX, SEMANTIC, RUNTIME, WARNING
+	if (err_node) {
+		if (string_cmp(err_node->value, "NONE") == 0)
+			expected_error = GRAVITY_ERROR_NONE;
+		else if (string_cmp(err_node->value, "SYNTAX") == 0)
+			expected_error = GRAVITY_ERROR_SYNTAX;
+		else if (string_cmp(err_node->value, "SEMANTIC") == 0)
+			expected_error = GRAVITY_ERROR_SEMANTIC;
+		else if (string_cmp(err_node->value, "RUNTIME") == 0)
+			expected_error = GRAVITY_ERROR_RUNTIME;
+		else if (string_cmp(err_node->value, "WARNING") == 0)
+			expected_error = GRAVITY_WARNING;
+	}
+	
+	// decode error line/col
+	if ((row_node) && (row_node->type == LITERAL_INT)) {
+		expected_nrow = (int32_t)row_node->value.n64;
+	}
+	if ((col_node) && (col_node->type == LITERAL_INT)) {
+		expected_ncol = (int32_t)col_node->value.n64;
+	}
+	
+	// decode unittest expected result
+	if (value_node) {
+		if (value_node->type == LITERAL_STRING)
+			expected_value = VALUE_FROM_CSTRING(NULL, value_node->value.str);
+		else if (value_node->type == LITERAL_INT)
+			expected_value = VALUE_FROM_INT((gravity_int_t)value_node->value.n64);
+		else if (value_node->type == LITERAL_FLOAT)
+			expected_value = VALUE_FROM_FLOAT((gravity_float_t)value_node->value.d);
+		else if (value_node->type == LITERAL_BOOL)
+			expected_value = (value_node->value.n64) ? VALUE_FROM_TRUE : VALUE_FROM_FALSE;
+	}
+	
+	// report unittest to delegate
+	if ((parser->delegate) && (parser->delegate->unittest_callback)) {
+		gravity_unittest_callback unittest_cb = parser->delegate->unittest_callback;
+		unittest_cb(expected_error, description, note, expected_value, expected_nrow, expected_ncol, parser->delegate->xdata);
+	} else {
+		// it was unit test responsability to free expected_value but if no unit test delegate is set I should take care of it
+		gravity_value_free(NULL, expected_value);
+	}
+	
+	// free temp nodes
+	if (name_node) gnode_free((gnode_t*)name_node);
+	if (note_node) gnode_free((gnode_t*)note_node);
+	if (err_node) gnode_free((gnode_t*)err_node);
+	if (row_node) gnode_free((gnode_t*)row_node);
+	if (col_node) gnode_free((gnode_t*)col_node);
+	if (value_node) gnode_free((gnode_t*)value_node);
+	
+	// always return NULL
+handle_error:
+	return NULL;
+}
+
+// MARK: - Statements -
+
+static gnode_t *parse_label_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_label_statement");
+	
+	// 'case' expression ':' statement
+	// 'default' ':' statement
+	
+	DECLARE_LEXER;
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert((type == TOK_KEY_CASE) || (type == TOK_KEY_DEFAULT));
+	
+	// case specific expression
+	gnode_t *expr = NULL;
+	if (type == TOK_KEY_CASE) {
+		expr = parse_expression(parser);
+	}
+	
+	// common part
+	parse_required(parser, TOK_OP_COLON);
+	gnode_t *stmt = parse_statement(parser);
+	
+	return gnode_label_stat_create(token, expr, stmt);
+}
+
+static gnode_t *parse_flow_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_flow_statement");
+	
+	// 'if' '(' expression ')' statement ('else' statement)?
+	// 'switch' '(' expression ')' statement
+	
+	DECLARE_LEXER;
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert((type == TOK_KEY_IF) || (type == TOK_KEY_SWITCH));
+	
+	// check optional TOK_OP_OPEN_PARENTHESIS
+	bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
+	
+	// parse common expression
+	gnode_t *cond = parse_expression(parser);
+	
+	// check and consume TOK_OP_CLOSED_PARENTHESIS
+	if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+	
+	// parse common statement
+	gnode_t *stmt1 = parse_statement(parser);
+	gnode_t *stmt2 = NULL;
+	if ((type == TOK_KEY_IF) && (gravity_lexer_peek(lexer) == TOK_KEY_ELSE)) {
+		gravity_lexer_next(lexer); // consume TOK_KEY_ELSE
+		stmt2 = parse_statement(parser);
+	}
+	
+	return gnode_flow_stat_create(token, cond, stmt1, stmt2);
+}
+
+static gnode_t *parse_loop_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_loop_statement");
+	
+	// 'while' '(' expression ')' statement
+	// 'repeat' statement 'while' '(' expression ')' ';'
+	// 'for' '(' condition 'in' expression ')' statement
+	
+	DECLARE_LEXER;
+	gnode_t		*cond = NULL;
+	gnode_t		*stmt = NULL;
+	gnode_t		*expr = NULL;
+	
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert((type == TOK_KEY_WHILE) || (type == TOK_KEY_REPEAT)  || (type == TOK_KEY_FOR));
+	
+	// 'while' '(' expression ')' statement
+	if (type == TOK_KEY_WHILE) {
+		// check optional TOK_OP_OPEN_PARENTHESIS
+		bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
+		
+		// parse while condition
+		cond = parse_expression(parser);
+		
+		// check and consume TOK_OP_CLOSED_PARENTHESIS
+		if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+		
+		// parse while statement
+		stmt = parse_statement(parser);
+		
+		goto return_node;
+	}
+	
+	// 'repeat' statement 'while' '(' expression ')' ';'
+	if (type == TOK_KEY_REPEAT) {
+		// parse repeat statement
+		stmt = parse_statement(parser);
+		
+		// check and consume TOK_KEY_WHILE
+		parse_required(parser, TOK_KEY_WHILE);
+		
+		// check optional TOK_OP_OPEN_PARENTHESIS
+		bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
+		
+		// parse while expression
+		expr = parse_expression(parser);
+		
+		// check and consume TOK_OP_CLOSED_PARENTHESIS
+		if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+		
+		// semicolon
+		parse_semicolon(parser);
+		
+		goto return_node;
+	}
+	
+	// 'for' '(' condition 'in' expression ')' statement
+	if (type == TOK_KEY_FOR) {
+		// check optional TOK_OP_OPEN_PARENTHESIS
+		bool is_parenthesize = parse_optional(parser, TOK_OP_OPEN_PARENTHESIS);
+		
+		// parse condition (means parse variable declaration or expression)
+		if (token_isvariable_declaration(gravity_lexer_peek(lexer))) {
+			cond = parse_variable_declaration(parser, false, 0, 0);
+		} else {
+			cond = parse_expression(parser);
+		}
+			
+		// check and consume TOK_KEY_IN
+		parse_required(parser, TOK_KEY_IN);
+		
+		// parse expression
+		expr = parse_expression(parser);
+		
+		// check and consume TOK_OP_CLOSED_PARENTHESIS
+		if (is_parenthesize) parse_required(parser, TOK_OP_CLOSED_PARENTHESIS);
+		
+		// parse for statement
+		stmt = parse_statement(parser);
+	}
+		
+return_node:
+	return gnode_loop_stat_create(token, cond, stmt, expr);
+}
+
+static gnode_t *parse_jump_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_jump_statement");
+	
+	// 'break' ';'
+	// 'continue' ';'
+	// 'return' expression? ';'
+	
+	DECLARE_LEXER;
+	gtoken_t type = gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	assert((type == TOK_KEY_BREAK) || (type == TOK_KEY_CONTINUE) || (type == TOK_KEY_RETURN));
+	
+	gnode_t	*expr = NULL;
+	if ((type == TOK_KEY_RETURN) && (gravity_lexer_peek(lexer) != TOK_OP_SEMICOLON)) {
+		expr = parse_expression(parser);
+	}
+	
+	parse_semicolon(parser);
+	return gnode_jump_stat_create(token, expr);
+}
+
+static gnode_t *parse_compound_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_compound_statement");
+	
+	// '{' (statement+)? '}'
+	
+	// check and consume TOK_OP_OPEN_CURLYBRACE
+	parse_required(parser, TOK_OP_OPEN_CURLYBRACE);
+	
+	DECLARE_LEXER;
+	gtoken_s token = gravity_lexer_token(lexer);
+	gnode_r *stmts = gnode_array_create();
+	while (token_isstatement(gravity_lexer_peek(lexer))) {
+		gnode_t *node = parse_statement(parser);
+		if (node) gnode_array_push(stmts, node);
+	}
+	
+	// check and consume TOK_OP_CLOSED_CURLYBRACE
+	parse_required(parser, TOK_OP_CLOSED_CURLYBRACE);
+	
+	return gnode_block_stat_create(NODE_COMPOUND_STAT, token, stmts);
+}
+
+static gnode_t *parse_empty_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_empty_statement");
+	
+	// ;
+	
+	DECLARE_LEXER;
+	gravity_lexer_next(lexer);
+	gtoken_s token = gravity_lexer_token(lexer);
+	return gnode_empty_stat_create(token);
+}
+
+static gnode_t *parse_declaration_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_declaration_statement");
+	
+	DECLARE_LEXER;
+	gtoken_t peek = gravity_lexer_peek(lexer);
+	gtoken_t access_specifier = 0;	// 0 means no access specifier set
+	gtoken_t storage_specifier = 0;	// 0 means no storage specifier set
+	
+	// check if an access specifier is set
+	if (token_isaccess_specifier(peek)) {
+		access_specifier = gravity_lexer_next(lexer);
+		peek = gravity_lexer_peek(lexer);
+	}
+	
+	// check if a storage specifier is set
+	if (token_isstorage_specifier(peek)) {
+		storage_specifier = gravity_lexer_next(lexer);
+		peek = gravity_lexer_peek(lexer);
+	}
+	
+	// it is a syntax error to specify an access or storage specifier followed by an empty declaration
+	if ((peek == TOK_OP_SEMICOLON) && ((access_specifier) || (storage_specifier))) {
+		REPORT_ERROR(gravity_lexer_token(lexer), "%s", "Access or storage specifier cannot be used here.");
+	}
+	
+	switch (peek) {
+		case TOK_KEY_FUNC: return parse_function_declaration(parser, access_specifier, storage_specifier);
+		case TOK_KEY_ENUM: return parse_enum_declaration(parser, access_specifier, storage_specifier);
+		case TOK_KEY_MODULE: return parse_module_declaration(parser, access_specifier, storage_specifier);
+		case TOK_KEY_EVENT: return parse_event_declaration(parser, access_specifier, storage_specifier);
+		case TOK_KEY_CLASS:
+		case TOK_KEY_STRUCT: return parse_class_declaration(parser, access_specifier, storage_specifier);
+		case TOK_OP_SEMICOLON: return parse_empty_statement(parser);
+		case TOK_KEY_VAR:
+		case TOK_KEY_CONST: return parse_variable_declaration(parser, true, access_specifier, storage_specifier);
+		default: REPORT_ERROR(gravity_lexer_token(lexer), "Unrecognized token %s.", token_name(peek)); return NULL;
+	}
+	
+	// should never reach this point
+	assert(0);
+	return NULL;
+}
+
+static gnode_t *parse_import_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_import_statement");
+	
+	DECLARE_LEXER;
+
+	// parse import keyword
+	gravity_lexer_next(lexer);
+	
+	// process filename (can be an identifier or a literal string)
+	// identifier to import modules ?
+	// only literals are supported in this version
+	gtoken_t		type;
+	gtoken_s		token;
+	const char		*module_name;
+	gravity_lexer_t	*newlexer;
+	
+loop:
+	newlexer = NULL;
+	type = gravity_lexer_next(lexer);
+	token = gravity_lexer_token(lexer);
+	
+	// check if it is a string token
+	if (type != TOK_STRING) {
+		REPORT_ERROR(token, "Expected file name but found %s.", token_name(type));
+		return NULL;
+	}
+	
+	// check pre-requisites
+	if ((!parser->delegate) || (!parser->delegate->loadfile_callback)) {
+		REPORT_ERROR(gravity_lexer_token(lexer), "%s", "Unable to load file because no loadfile callback registered in delegate.");
+		return NULL;
+	}
+	
+	// parse string
+	module_name = cstring_from_token(parser, token);
+	size_t size = 0;
+	uint32_t fileid = 0;
+	
+	// module_name is a filename and it is used by lexer to store filename into tokens
+	// tokens are then stored inside AST nodes in order to locate errors into source code
+	// AST can live a lot longer than both lexer and parser so we need a way to persistent
+	// store these chuncks of memory
+	const char *source = parser->delegate->loadfile_callback(module_name, &size, &fileid, parser->delegate->xdata);
+	if (source) newlexer = gravity_lexer_create(source, size, fileid, false);
+
+	if (newlexer) {
+		// push new lexer into lexer stack
+		marray_push(gravity_lexer_t*, *parser->lexer, newlexer);
+	} else {
+		REPORT_ERROR(token, "Unable to load file %s.", module_name);
+	}
+	
+	// check for optional comma
+	if (gravity_lexer_peek(lexer) == TOK_OP_COMMA) {
+		gravity_lexer_next(lexer); // consume TOK_OP_COMMA
+		goto loop;
+	}
+
+	// parse semicolon
+	parse_semicolon(parser);
+	
+	return NULL;
+}
+
+static gnode_t *parse_macro_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_macro_statement");
+	DECLARE_LEXER;
+	
+	// consume special # symbol
+	gtoken_t type = gravity_lexer_next(lexer);
+	assert(type == TOK_MACRO);
+	
+	// macro has its own parser because I don't want to mess standard syntax
+	const char *macroid = parse_identifier(parser);
+	if (macroid == NULL) goto handle_error;
+	
+	// check #unittest macro
+	bool is_unittest = (string_cmp(macroid, "unittest") == 0);
+	mem_free(macroid);
+	
+	if (is_unittest) {
+		return parse_unittest_declaration(parser);
+	}
+	
+handle_error:
+	REPORT_WARNING(gravity_lexer_token(lexer), "%s", "Unknown macro token. Declaration will be ignored.");
+	return NULL;
+}
+
+static gnode_t *parse_special_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_special_statement");
+	DECLARE_LEXER;
+	
+	// consume special @ symbol
+	gtoken_t type = gravity_lexer_next(lexer);
+	assert(type == TOK_SPECIAL);
+	
+	// special is really special so it has its own parser
+	// because I don't want to mess standard syntax
+	const char *specialid = parse_identifier(parser);
+	if (specialid == NULL) goto handle_error;
+	
+handle_error:
+	REPORT_WARNING(gravity_lexer_token(lexer), "%s", "Unknown special token. Declaration will be ignored.");
+	return NULL;
+}
+
+static gnode_t *parse_expression_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_expression_statement");
+	
+	gnode_t *expr = parse_expression(parser);
+	parse_semicolon(parser);
+	return expr;
+}
+
+static gnode_t *parse_statement (gravity_parser_t *parser) {
+	DEBUG_PARSER("parse_statement");
+	
+	// label_statement
+	// flow_statement
+	// loop_statement
+	// jump_statement
+	// compound_statement
+	// declaration_statement
+	// empty_statement
+	// import_statement
+	// expression_statement (default)
+	
+	DECLARE_LEXER;
+	gtoken_t token = gravity_lexer_peek(lexer);
+	if (token_iserror(token)) return parse_error(parser);
+	
+	if (token_islabel_statement(token)) return parse_label_statement(parser);
+	else if (token_isflow_statement(token)) return parse_flow_statement(parser);
+	else if (token_isloop_statement(token)) return parse_loop_statement(parser);
+	else if (token_isjump_statement(token)) return parse_jump_statement(parser);
+	else if (token_iscompound_statement(token)) return parse_compound_statement(parser);
+	else if (token_isdeclaration_statement(token)) return parse_declaration_statement(parser);
+	else if (token_isempty_statement(token)) return parse_empty_statement(parser);
+	else if (token_isimport_statement(token)) return parse_import_statement(parser);
+	else if (token_isspecial_statement(token)) return parse_special_statement(parser);
+	else if (token_ismacro(token)) return parse_macro_statement(parser);
+	
+	return parse_expression_statement(parser); // DEFAULT
+}
+
+// MARK: - Internal functions -
+
+static void parser_register_core_classes (gravity_parser_t *parser) {
+	const char **list = NULL;
+	uint32_t n = gravity_core_identifiers(&list);
+	if (!n) return;
+	
+	// for each core identifier create a dummy extern variable node
+	gnode_r	*decls = gnode_array_create();
+	for (uint32_t i=0; i<n; ++i) {
+		const char *identifier = list[i];
+		gnode_t *node = gnode_variable_create(NO_TOKEN, string_dup(identifier), NULL, 0, NULL);
+		gnode_array_push(decls, node);
+	}
+	
+	// register a variable declaration node in global statements
+	gnode_t *node = gnode_variable_decl_create(NO_TOKEN, TOK_KEY_VAR, 0, TOK_KEY_EXTERN, decls);;
+	gnode_array_push(parser->statements, (gnode_t *)node);
+}
+
+static uint32_t parser_run (gravity_parser_t *parser) {
+	DEBUG_PARSER("=== BEGIN PARSING ===");
+	
+	// register core classes as extern globals
+	parser_register_core_classes(parser);
+	
+	nanotime_t t1 = nanotime();
+	do {
+		while (gravity_lexer_peek(CURRENT_LEXER)) {
+			gnode_t *node = parse_statement(parser);
+			if (node) gnode_array_push(parser->statements, node);
+		}
+		
+		// since it is a stack of lexers then check if it is a real EOF
+		gravity_lexer_t *lexer = CURRENT_LEXER;
+		gravity_lexer_free(lexer);
+		marray_pop(*parser->lexer);
+		
+	} while (marray_size(*parser->lexer));
+	nanotime_t t2 = nanotime();
+	parser->time = millitime(t1, t2);
+	
+	DEBUG_PARSER("===  END PARSING  ===\n");
+	
+	return parser->nerrors;
+}
+
+static void parser_cleanup (gravity_parser_t *parser) {
+	// in case of error (so AST is not returned)
+	// then cleanup internal nodes
+	gnode_t *node= gnode_block_stat_create(NODE_LIST_STAT, (gtoken_s)NO_TOKEN, parser->statements);
+	gnode_free(node);
+}
+
+static void parser_appendcode (const char *source, gravity_parser_t *parser) {
+	if (source == NULL) return;
+	
+	size_t len = strlen(source);
+	if (len <= 0) return;
+	
+	// build a new lexer based on source code to prepend
+	gravity_lexer_t *lexer1 = gravity_lexer_create(source, len, 0, true);
+	if (!lexer1) return;
+	
+	// pop current lexer
+	gravity_lexer_t *lexer2 = POP_LEXER;
+	
+	// swap lexer2 with lexer1
+	marray_push(gravity_lexer_t*, *parser->lexer, lexer1);
+	marray_push(gravity_lexer_t*, *parser->lexer, lexer2);
+}
+
+// MARK: - Public functions -
+
+gravity_parser_t *gravity_parser_create (const char *source, size_t len, uint32_t fileid, bool is_static) {
+	init_grammer_rules();
+	
+	gravity_parser_t *parser = mem_alloc(sizeof(gravity_parser_t));
+	if (!parser) return NULL;
+	
+	gravity_lexer_t *lexer = gravity_lexer_create(source, len, fileid, is_static);
+	if (!lexer) goto abort_init;
+	
+	parser->lexer = mem_alloc(sizeof(lexer_r));
+	marray_init(*parser->lexer);
+	marray_push(gravity_lexer_t*, *parser->lexer, lexer);
+	
+	parser->statements = gnode_array_create();
+	if (!parser->statements) goto abort_init;
+	
+	marray_init(parser->declarations);
+	
+	parser->last_error_lineno = UINT32_MAX;
+	return parser;
+	
+abort_init:
+	gravity_parser_free(parser);
+	return NULL;
+}
+
+gnode_t *gravity_parser_run (gravity_parser_t *parser, gravity_delegate_t *delegate) {
+	parser->delegate = delegate;
+	gravity_lexer_setdelegate(CURRENT_LEXER, delegate);
+	
+	// check if some user code needs to be prepended
+	if ((delegate) && (delegate->precode_callback))
+		parser_appendcode(delegate->precode_callback(delegate->xdata), parser);
+	
+	// if there are syntax errors then just returns
+	if (parser_run(parser) > 0) {
+		parser_cleanup (parser);
+		return NULL;
+	}
+	
+	// if there are some open declarations then there should be an error somewhere
+	if (marray_size(parser->declarations) > 0) return NULL;
+	
+	// return ast
+	return gnode_block_stat_create(NODE_LIST_STAT, (gtoken_s)NO_TOKEN, parser->statements);
+}
+
+void gravity_parser_free (gravity_parser_t *parser) {
+	// free memory for stack of lexers
+	if (parser->lexer) {
+		size_t _len = marray_size(*parser->lexer);
+		for (size_t i=0; i<_len; ++i) {
+			gravity_lexer_t *lexer = (gravity_lexer_t *)marray_get(*parser->lexer, i);
+			gravity_lexer_free(lexer);
+		}
+		marray_destroy(*parser->lexer);
+		mem_free(parser->lexer);
+	}
+	
+	marray_destroy(parser->declarations);
+	// parser->statements is returned from gravity_parser_run
+	// and must be deallocated using gnode_free
+	
+	mem_free(parser);
+}

+ 39 - 0
src/compiler/gravity_parser.h

@@ -0,0 +1,39 @@
+//
+//  gravity_parser.h
+//  gravity
+//
+//  Created by Marco Bambini on 01/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_PARSER__
+#define __GRAVITY_PARSER__
+
+#include "gravity_compiler.h"
+#include "debug_macros.h"
+#include "gravity_ast.h"
+
+/*
+	Parser is responsible to build the AST, convert strings and number from tokens and
+	implement syntax error recovery strategy.
+	
+	Notes about error recovery:
+	Each parse* function can return NULL in case of error but each function is RESPONSIBLE
+	to make appropriate actions in order to handle/recover errors. 
+	
+	Error recovery techniques can be:
+	Shallow Error Recovery
+	Deep Error Recovery
+	https://javacc.java.net/doc/errorrecovery.html
+ 
+ */
+
+// opaque datatype
+typedef struct gravity_parser_t	gravity_parser_t;
+
+// public functions
+gravity_parser_t	*gravity_parser_create (const char *source, size_t len, uint32_t fileid, bool is_static);
+gnode_t				*gravity_parser_run (gravity_parser_t *parser, gravity_delegate_t *delegate);
+void				gravity_parser_free (gravity_parser_t *parser);
+
+#endif

+ 180 - 0
src/compiler/gravity_semacheck1.c

@@ -0,0 +1,180 @@
+//
+//  gravity_semacheck1.c
+//  gravity
+//
+//  Created by Marco Bambini on 08/10/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_symboltable.h"
+#include "gravity_semacheck1.h"
+#include "gravity_compiler.h"
+#include "gravity_visitor.h"
+
+#define REPORT_ERROR(node,...)			{report_error(self, (gnode_t *)node, __VA_ARGS__); return;}
+
+#define DECLARE_SYMTABLE				symboltable_t *symtable = (symboltable_t *)self->data
+#define SAVE_SYMTABLE					symboltable_t *saved = symtable
+#define CREATE_SYMTABLE					INC_IDENT; SAVE_SYMTABLE; self->data = (void *)symboltable_create(false);
+#define RESTORE_SYMTABLE				DEC_IDENT; node->symtable = ((symboltable_t *)self->data); self->data = (void *)saved
+
+#if GRAVITY_SYMTABLE_DEBUG
+static int ident =0;
+#define INC_IDENT		++ident
+#define DEC_IDENT		--ident
+#else
+#define INC_IDENT
+#define DEC_IDENT
+#endif
+
+// MARK: -
+
+static void report_error (gvisitor_t *self, gnode_t *node, const char *format, ...) {
+	// increment internal error counter
+	++self->nerr;
+	
+	// get error callback (if any)
+	void *data = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->xdata : NULL;
+	gravity_error_callback error_fn = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->error_callback : NULL;
+	
+	// build error message
+	char		buffer[1024];
+	va_list		arg;
+	if (format) {
+		va_start (arg, format);
+		vsnprintf(buffer, sizeof(buffer), format, arg);
+		va_end (arg);
+	}
+	
+	// setup error struct
+	error_desc_t error_desc = {
+		.code = 0,
+		.lineno = node->token.lineno,
+		.colno = node->token.colno,
+		.fileid = node->token.fileid,
+		.offset = node->token.position
+	};
+	
+	// finally call error callback
+	if (error_fn) error_fn(GRAVITY_ERROR_SEMANTIC, buffer, error_desc, data);
+	else printf("%s\n", buffer);
+}
+
+// MARK: - Declarations -
+
+static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	DECLARE_SYMTABLE;
+	
+	node->symtable = symtable;	// GLOBALS
+	gnode_array_each(node->stmts, {visit(val);});
+}
+
+static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
+	DECLARE_SYMTABLE;
+	
+	DEBUG_SYMTABLE("function: %s", node->identifier);
+	
+	// function identifier
+	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+	
+	// we are just interested in non-local declarations so don't further scan function node
+	// node->symtable is NULL here and it will be created in semacheck2
+}
+
+static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
+	DECLARE_SYMTABLE;
+	
+	gnode_array_each(node->decls, {
+		gnode_var_t *p = (gnode_var_t *)val;
+		DEBUG_SYMTABLE("variable: %s", p->identifier);
+		if (!symboltable_insert(symtable, p->identifier, (void *)p))
+			REPORT_ERROR(p, "Identifier %s redeclared.", p->identifier);
+	});
+}
+
+static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
+	DECLARE_SYMTABLE;
+	
+	DEBUG_SYMTABLE("enum: %s", node->identifier);
+	
+	// check enum identifier uniqueness in current symbol table
+	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+}
+
+static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
+	DECLARE_SYMTABLE;
+	
+	DEBUG_SYMTABLE("class: %s", node->identifier);
+	
+	// class identifier
+	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+	
+	CREATE_SYMTABLE;
+	gnode_array_each(node->decls, {
+		visit(val);
+	});
+	RESTORE_SYMTABLE;
+}
+
+static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
+	DECLARE_SYMTABLE;
+	
+	DEBUG_SYMTABLE("module: %s", node->identifier);
+	
+	// module identifier
+	if (!symboltable_insert(symtable, node->identifier, (void *)node))
+		REPORT_ERROR(node, "Identifier %s redeclared.", node->identifier);
+	
+	CREATE_SYMTABLE;
+	gnode_array_each(node->decls, {
+		visit(val);
+	});
+	RESTORE_SYMTABLE;
+}
+
+// MARK: -
+
+bool gravity_semacheck1 (gnode_t *node, gravity_delegate_t *delegate) {
+	symboltable_t *context = symboltable_create(false);
+	
+	gvisitor_t visitor = {
+		.nerr = 0,							// used to store number of found errors
+		.data = (void *)context,			// used to store a pointer to the symbol table
+		.delegate = (void *)delegate,		// compiler delegate to report errors
+		
+		// STATEMENTS: 7
+		.visit_list_stmt = visit_list_stmt,
+		.visit_compound_stmt = NULL,
+		.visit_label_stmt = NULL,
+		.visit_flow_stmt = NULL,
+		.visit_loop_stmt = NULL,
+		.visit_jump_stmt = NULL,
+		.visit_empty_stmt = NULL,
+		
+		// DECLARATIONS: 5
+		.visit_function_decl = visit_function_decl,
+		.visit_variable_decl = visit_variable_decl,
+		.visit_enum_decl = visit_enum_decl,
+		.visit_class_decl = visit_class_decl,
+		.visit_module_decl = visit_module_decl,
+		
+		// EXPRESSIONS: 8
+		.visit_binary_expr = NULL,
+		.visit_unary_expr = NULL,
+		.visit_file_expr = NULL,
+		.visit_literal_expr = NULL,
+		.visit_identifier_expr = NULL,
+		.visit_keyword_expr = NULL,
+		.visit_list_expr = NULL,
+		.visit_postfix_expr = NULL,
+	};
+	
+	DEBUG_SYMTABLE("=== SYMBOL TABLE ===");
+	gvisit(&visitor, node);
+	DEBUG_SYMTABLE("====================\n");
+	
+	return (visitor.nerr == 0);
+}

+ 62 - 0
src/compiler/gravity_semacheck1.h

@@ -0,0 +1,62 @@
+//
+//  semacheck1.h
+//  gravity
+//
+//  Created by Marco Bambini on 08/10/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_SEMACHECK1__
+#define __GRAVITY_SEMACHECK1__
+
+// Semantic check step 1 does not have the notion of context and scope.
+// It just gathers non-local names into a symbol table and check for uniqueness.
+//
+// Only declarations (non-locals) are visited and a symbol table is created.
+//
+//
+// In order to debug symbol table just enable the GRAVITY_SYMTABLE_DEBUG macro
+// in debug_macros.h
+
+// Semantic Check Step 1 enables to resolve cases like:
+/*
+		function foo() {
+			return bar();
+		}
+	
+		function bar() {
+			...
+		}
+ 
+		or
+ 
+		class foo:bar {
+			...
+		}
+ 
+		class bar {
+			...
+		}
+ 
+		and
+ 
+		class foo {
+			var a;
+ 
+			function bar() {
+				return a + b;
+			}
+ 
+			var b;
+		}
+ */
+
+// It's a mandatory step in order to account for forward references
+// allowed in any non-local declaration
+
+#include "gravity_ast.h"
+#include "gravity_delegate.h"
+
+bool gravity_semacheck1(gnode_t *node, gravity_delegate_t *delegate);
+
+#endif

+ 1100 - 0
src/compiler/gravity_semacheck2.c

@@ -0,0 +1,1100 @@
+//
+//  gravity_semacheck2.c
+//  gravity
+//
+//  Created by Marco Bambini on 12/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_symboltable.h"
+#include "gravity_semacheck2.h"
+#include "gravity_compiler.h"
+#include "gravity_visitor.h"
+
+struct semacheck_t {
+	gnode_r			*declarations;		// declarations stack
+	uint16_r		statements;			// statements stack
+	uint32_t		lasterror;			// last error line number to prevent reporting more than one error per line
+};
+typedef struct semacheck_t semacheck_t;
+
+#define REPORT_ERROR(node,...)			report_error(self, GRAVITY_ERROR_SEMANTIC, (gnode_t *)node, __VA_ARGS__);
+#define REPORT_WARNING(node,...)		report_error(self, GRAVITY_WARNING, (gnode_t *)node, __VA_ARGS__)
+
+#define STATEMENTS						((semacheck_t *)self->data)->statements
+#define PUSH_STATEMENT(stat)			marray_push(uint16_t, STATEMENTS, (uint16_t)stat)
+#define POP_STATEMENT()					marray_pop(STATEMENTS)
+#define TOP_STATEMENT()					(marray_size(STATEMENTS) ? marray_last(STATEMENTS) : 0)
+#define TOP_STATEMENT_ISA(stat)			(TOP_STATEMENT() == stat)
+#define TOP_STATEMENT_ISA_SWITCH()		(TOP_STATEMENT_ISA(TOK_KEY_SWITCH))
+#define TOP_STATEMENT_ISA_LOOP()		((TOP_STATEMENT_ISA(TOK_KEY_WHILE))		||		\
+										(TOP_STATEMENT_ISA(TOK_KEY_REPEAT))		||		\
+										(TOP_STATEMENT_ISA(TOK_KEY_FOR)))
+
+#define PUSH_DECLARATION(node)			gnode_array_push(((semacheck_t *)self->data)->declarations, (gnode_t*)node)
+#define POP_DECLARATION()				gnode_array_pop(((semacheck_t *)self->data)->declarations)
+#define TOP_DECLARATION()				gnode_array_get(((semacheck_t *)self->data)->declarations, gnode_array_size(((semacheck_t *)self->data)->declarations)-1)
+#define ISA(n1,_tag)					((n1) ? (((gnode_t *)n1)->tag == _tag) : 0)
+#define ISA_CLASS(n1)					(((gnode_t *)n1)->tag == NODE_CLASS_DECL)
+#define ISA_VAR_DECLARATION(n1)			(((gnode_t *)n1)->tag == NODE_VARIABLE_DECL)
+#define ISA_VARIABLE(n1)				(((gnode_t *)n1)->tag == NODE_VARIABLE)
+#define ISA_LITERAL(n1)					(((gnode_t *)n1)->tag == NODE_LITERAL_EXPR)
+#define ISA_IDENTIFIER(n1)				(((gnode_t *)n1)->tag == NODE_IDENTIFIER_EXPR)
+#define ISA_ID(n1)						(((gnode_t *)n1)->tag == NODE_ID_EXPR)
+
+#define SET_LOCAL_INDEX(var, symtable)	var->index = (uint16_t)symboltable_local_index(symtable)
+#define SET_NODE_LOCATION(_node, _type, _idx, _nup) _node->location.type = _type; _node->location.index = _idx; _node->location.nup = _nup;
+
+// MARK: -
+
+static void report_error (gvisitor_t *self, error_type_t error_type, gnode_t *node, const char *format, ...) {
+	// check last error line in order to prevent to emit multiple errors for the same row
+	semacheck_t *current = (semacheck_t *)self->data;
+	if (node->token.lineno == current->lasterror) return;
+	current->lasterror = node->token.lineno;
+	
+	// increment internal error counter
+	++self->nerr;
+	
+	// get error callback (if any)
+	void *data = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->xdata : NULL;
+	gravity_error_callback error_fn = (self->delegate) ? ((gravity_delegate_t *)self->delegate)->error_callback : NULL;
+	
+	// build error message
+	char		buffer[1024];
+	va_list		arg;
+	if (format) {
+		va_start (arg, format);
+		vsnprintf(buffer, sizeof(buffer), format, arg);
+		va_end (arg);
+	}
+	
+	// setup error struct
+	error_desc_t error_desc = {
+		.code = 0,
+		.lineno = node->token.lineno,
+		.colno = node->token.colno,
+		.fileid = node->token.fileid,
+		.offset = node->token.position
+	};
+	
+	// finally call error callback
+	if (error_fn) error_fn(error_type, buffer, error_desc, data);
+	else printf("%s\n", buffer);
+}
+
+static symboltable_t *symtable_from_node (gnode_t *node) {
+	// globals
+	if (ISA(node, NODE_LIST_STAT)) return ((gnode_compound_stmt_t *)node)->symtable;
+	
+	// class symtable
+	if (ISA(node, NODE_CLASS_DECL)) return ((gnode_class_decl_t *)node)->symtable;
+	
+	// enum symtable
+	if (ISA(node, NODE_ENUM_DECL)) return ((gnode_enum_decl_t *)node)->symtable;
+	
+	// module symtable
+	if (ISA(node, NODE_MODULE_DECL)) return ((gnode_module_decl_t *)node)->symtable;
+	
+	// function symtable
+	if (ISA(node, NODE_FUNCTION_DECL)) return ((gnode_function_decl_t *)node)->symtable;
+	
+	// should never reach this point
+	assert(0);
+	return NULL;
+}
+
+// lookup identifier into node
+static gnode_t *lookup_node (gnode_t *node, const char *identifier) {
+	symboltable_t *symtable = symtable_from_node(node);
+	return symboltable_lookup(symtable, identifier);
+}
+
+// lookup an identifier into a stack of symbol tables
+// location inside node is updated with the result
+// and node found is returned
+static gnode_t *lookup_identifier (gvisitor_t *self, const char *identifier, gnode_identifier_expr_t *node) {
+	gnode_r *decls = ((semacheck_t *)self->data)->declarations;
+	size_t len = gnode_array_size(decls);
+	if (len == 0) return NULL;
+	
+	uint16_t nf = 0; // number of functions traversed
+	uint16_t nc = 0; // number of classes traversed
+	
+	// get first node (the latest in the decls stack)
+	gnode_t	*base_node = gnode_array_get(decls, len-1);
+	bool base_is_class = ISA(base_node, NODE_CLASS_DECL);
+	
+	for (int i=(int)len-1; i>=0; --i) {
+		gnode_t	*target = gnode_array_get(decls, i);
+		
+		// identify target type
+		bool target_is_global = ISA(target, NODE_LIST_STAT);
+		bool target_is_function = ISA(target, NODE_FUNCTION_DECL);
+		bool target_is_class = ISA(target, NODE_CLASS_DECL);
+		bool target_is_module = ISA(target, NODE_MODULE_DECL);
+		
+		if (target_is_function) ++nf;
+		else if (target_is_class) ++nc;
+		
+		// lookup identifier is current target (obtained traversing the declaration stack)
+		gnode_t	*symbol = lookup_node(target, identifier);
+		
+		// sanity check: if base_node is a class and symbol was found inside a func then report an error
+		if (symbol && target_is_function && base_is_class) {
+			// added to explicitly prevent cases like:
+			/*
+				func foo() {
+					var a;
+					class b {
+						func bar() {return a;}
+					}
+				}
+			 */
+			REPORT_ERROR(node, "Unable to access local func var %s from within a class.", identifier);
+		}
+		
+		// if target is class and symbol is not found then lookup also its superclass hierarchy
+		if (!symbol && target_is_class) {
+			// lookup identifier in super (if not found target class)
+			gnode_class_decl_t *c = (gnode_class_decl_t *)target;
+			gnode_class_decl_t *super = (gnode_class_decl_t *)c->superclass;
+			while (super) {
+				symbol = lookup_node((gnode_t *)super, identifier);
+				if (symbol) {
+					if (NODE_ISA(symbol, NODE_VARIABLE)) {
+						gnode_var_t *p = (gnode_var_t *)symbol;
+						if (p->access == TOK_KEY_PRIVATE) {
+							REPORT_ERROR(node, "Forbidden access to private ivar %s from a subclass.", p->identifier);
+						}
+					}
+					break;
+				}
+				super = (gnode_class_decl_t *)super->superclass;
+			}
+		}
+		
+		// continue lookup in declaration stack is symbol is not found
+		if (!symbol) continue;
+		
+		// symbol found so process it bases on target type
+		if (target_is_global) {
+			// identifier found in global no other information is needed
+			SET_NODE_LOCATION(node, LOCATION_GLOBAL, 0, 0);
+			DEBUG_LOOKUP("Identifier %s found in GLOBALS", identifier);
+			
+			node->symbol = symbol;
+			return symbol;
+		}
+		
+		// if symbol is a variable then copy its index
+		uint16_t index = UINT16_MAX;
+		if (NODE_ISA(symbol, NODE_VARIABLE)) {
+			gnode_var_t *p = (gnode_var_t *)symbol;
+			index = p->index;
+		}
+		
+		if (target_is_function) {
+			// Symbol found in a function
+			if (nf > 1) {
+				assert(ISA(base_node, NODE_FUNCTION_DECL));
+				
+				// symbol is upvalue and its index represents an index inside uplist
+				gnode_var_t *var = (gnode_var_t *)symbol;
+				gnode_function_decl_t *f = ((gnode_function_decl_t *)base_node);
+				uint16_t n = nf - 1;
+				gupvalue_t *upvalue = gnode_function_add_upvalue(f, var, n);
+				
+				// add upvalue to all enclosing functions
+				// base_node has index = len - 1 so from (len - 2) up to n-1 levels
+				uint16_t idx = len - 2;
+				while (n > 1) {
+					f = (gnode_function_decl_t *) gnode_array_get(decls, idx);
+					gnode_function_add_upvalue(f, var, --n);
+					--idx;
+				}
+				
+				var->upvalue = true;
+				node->upvalue = upvalue;
+				SET_NODE_LOCATION(node, LOCATION_UPVALUE, index, nf);
+			} else {
+				// symbol is local
+				SET_NODE_LOCATION(node, LOCATION_LOCAL, index, nf);
+			}
+			DEBUG_LOOKUP("Identifier %s found in FUNCTION %s (nf: %lu index: %d)", identifier, ((gnode_function_decl_t *)target)->identifier, nf-1, index);
+		}
+		else if (target_is_class) {
+			// Symbol found in a class
+			SET_NODE_LOCATION(node, (nc == 1) ? LOCATION_CLASS_IVAR_SAME : LOCATION_CLASS_IVAR_OUTER, index, nc-1);
+			DEBUG_LOOKUP("Identifier %s found in CLASS %s (up to %zu outer levels)", identifier, ((gnode_class_decl_t *)target)->identifier, nc-1);
+		}
+		else if (target_is_module) {
+			// Symbol found in a module
+			// Module support not yet ready
+		}
+		else {
+			// Should never reach this point
+			assert(0);
+		}
+		
+		node->symbol = symbol;
+		return symbol;
+	}
+	
+	DEBUG_LOOKUP("Identifier %s NOT FOUND\n", identifier);
+	return NULL;
+}
+
+static gnode_t *lookup_symtable_id (gvisitor_t *self, gnode_identifier_expr_t *id, bool isclass) {
+	gnode_t *target = NULL;
+	
+	gnode_t *target1 = lookup_identifier(self, id->value, id);
+	if (!target1) {REPORT_ERROR((gnode_t *)id, "%s %s not found.", (isclass) ? "Class" : "Protocol", id->value); return NULL;}
+	target = target1;
+	
+	if (id->value2) {
+		gnode_t *target2 = lookup_node(target1, id->value2);
+		if (!target2) {REPORT_ERROR((gnode_t *)id, "%s %s not found in %s.", (isclass) ? "Class" : "Protocol", id->value2, id->value); return NULL;}
+		target = target2;
+	}
+	
+	return target;
+}
+
+// MARK: -
+
+static bool is_expression (gnode_t *node) {
+	gnode_n tag = NODE_TAG(node);
+	return ((tag >= NODE_BINARY_EXPR) && (tag <= NODE_ACCESS_EXPR));
+}
+
+static bool is_expression_assignment (gnode_t *node) {
+	gnode_n tag = NODE_TAG(node);
+	if (tag == NODE_BINARY_EXPR) {
+		gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
+		return (expr->op == TOK_OP_ASSIGN);
+	}
+	return false;
+}
+
+static bool is_expression_range (gnode_t *node) {
+	gnode_n tag = NODE_TAG(node);
+	if (tag == NODE_BINARY_EXPR) {
+		gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
+		return ((expr->op == TOK_OP_RANGE_INCLUDED) || (expr->op == TOK_OP_RANGE_EXCLUDED));
+	}
+	return false;
+}
+
+
+static bool is_expression_valid (gnode_t *node) {
+	if (!node) return false;
+	
+	/*
+		From: http://c2.com/cgi/wiki?FirstClass
+	 
+	 |      Class of value
+		Manipulation				   | First    Second    Third
+		===============================+================================
+		Pass value as a parameter      | yes      yes       no
+		Return value from a procedure  | yes      no        no
+		Assign value into a variable   | yes      no        no
+	 
+	 */
+	
+	/*
+	 
+	 NODE_LIST_STAT, NODE_COMPOUND_STAT, NODE_LABEL_STAT, NODE_FLOW_STAT, NODE_JUMP_STAT,
+	 NODE_LOOP_STAT, NODE_EMPTY_STAT,
+	 
+	 // declarations: 6
+	 NODE_ENUM_DECL, NODE_FUNCTION_DECL, NODE_VARIABLE_DECL, NODE_CLASS_DECL, NODE_MODULE_DECL,
+	 NODE_VARIABLE,
+	 
+	 // expressions: 8
+	 NODE_BINARY_EXPR, NODE_UNARY_EXPR, NODE_FILE_EXPR,
+	 NODE_LIST_EXPR, NODE_LITERAL_EXPR, NODE_IDENTIFIER_EXPR, NODE_KEYWORD_EXPR,
+	 NODE_FUNCTION_EXPR,
+	 
+	 // postfix expression type: 2 + NODE_IDENTIFIER_EXPR
+	 NODE_CALL, NODE_SUBSCRIPT
+	 
+	 */
+	
+	// fixme
+	gnode_n tag = NODE_TAG(node);
+	switch (tag) {
+		case NODE_UNARY_EXPR: {
+			return is_expression_valid(((gnode_unary_expr_t *)node)->expr);
+		}
+			
+		case NODE_BINARY_EXPR: {
+			gnode_binary_expr_t *expr = (gnode_binary_expr_t *)node;
+			if (expr->op == TOK_OP_ASSIGN) return false;
+			if (!is_expression_valid(expr->left)) return false;
+			return is_expression_valid(expr->right);
+		}
+			
+		case NODE_IDENTIFIER_EXPR: {
+			return true;
+		}
+			
+		case NODE_MODULE_DECL:
+		case NODE_ENUM_DECL: {
+			return false;
+		}
+		
+		default: break;
+	}
+	
+	return true;
+}
+
+static bool is_init_function (gnode_t *node) {
+	if (ISA(node, NODE_FUNCTION_DECL)) {
+		gnode_function_decl_t *f = (gnode_function_decl_t *)node;
+		if (!f->identifier) return false;
+		return (strcmp(f->identifier, CLASS_CONSTRUCTOR_NAME) == 0);
+	}
+	return false;
+}
+
+static bool is_init_infinite_loop(gvisitor_t *self, gnode_identifier_expr_t *identifier, gnode_r *list) {
+	
+	// for example:
+	// class c1 {
+	// 	func init() {
+	// 		var a = c1();	// INFINITE LOOP
+	//		var a = self();	// INFINITE LOOP
+	// 	}
+	// }
+	
+	// conditions for an infinite loop in init:
+	
+	// 1. there should be at least 2 declarations in the stack
+	gnode_r *decls = ((semacheck_t *)self->data)->declarations;
+	size_t len = gnode_array_size(decls);
+	if (len < 2) return false;
+	
+	// 2. current function is init
+	if (!is_init_function(gnode_array_get(decls, len-1))) return false;
+	
+	// 3. outer declaration is a class
+	gnode_t	*target_node = gnode_array_get(decls, len-2);
+	if (!ISA(target_node, NODE_CLASS_DECL)) return false;
+	
+	// 4. identifier is self OR identifier->symbol points to target_node
+	bool continue_check = false;
+	if (identifier->symbol) continue_check = target_node == identifier->symbol;
+	else continue_check = ((identifier->value) && (strcmp(identifier->value, SELF_PARAMETER_NAME) == 0));
+	if (!continue_check) return false;
+	
+	// 5. check if next node is a call
+	size_t count = gnode_array_size(list);
+	if (count < 1) return false;
+	gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(list, 0);
+	return (subnode->base.tag == NODE_CALL_EXPR);
+}
+
+static void check_access_storage_specifiers (gvisitor_t *self, gnode_t *node, gnode_n env, gtoken_t access, gtoken_t storage) {
+	// check for module node
+	if (NODE_TAG(node) == NODE_MODULE_DECL) {
+		if (access != 0) REPORT_ERROR(node, "Access specifier cannot be used for module.");
+		if (storage != 0) REPORT_ERROR(node, "Storage specifier cannot be used for module.");
+	}
+	
+	// check fo access specifiers here
+	// access specifier does make sense only inside module or class declaration
+	// in any other enclosing environment must be considered a semantic error
+	if ((access != 0) && (env != NODE_CLASS_DECL) && (env != NODE_MODULE_DECL)) {
+		REPORT_ERROR(node, "Access specifier does not make sense here.");
+	}
+	
+	// storage specifier (STATIC) makes sense only inside a class declaration
+	if ((storage == TOK_KEY_STATIC) && (env != NODE_CLASS_DECL)) {
+		REPORT_ERROR(node, "Static storage specifier does not make sense outside a class declaration.");
+	}
+}
+
+static bool check_assignment_expression (gvisitor_t *self, gnode_binary_expr_t *node) {
+	// in case of assignment check left node: assure assignment is made to identifier or other valid expressions
+	// for example left expression cannot be a literal (to prevent 3 = 2)
+	
+	gnode_n tag = NODE_TAG(node->left);
+	bool result = ((tag == NODE_IDENTIFIER_EXPR) || (tag == NODE_FILE_EXPR) || (tag == NODE_POSTFIX_EXPR));
+	
+	// more checks in the postfix case
+	if (tag == NODE_POSTFIX_EXPR) {
+		gnode_postfix_expr_t *expr = (gnode_postfix_expr_t *)node->left;
+		
+		// in case of postfix expression
+		// enum has already been processed so it appears as a literal with expr->list NULL
+		// inside a postfix expression node
+		// check enum case (enum cannot be assigned)
+		if (ISA(expr->id, NODE_LITERAL_EXPR)) {
+			result = false;
+		} else {
+			// basically the LATEST node of a postfix expression cannot be a CALL in an assignment
+			// so we are avoiding expressions like: a(123) = ...; or a.b.c(1,2) = ...;
+			size_t count = gnode_array_size(expr->list);
+			gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(expr->list, count-1);
+			result = (NODE_TAG(subnode) != NODE_CALL_EXPR);
+		}
+	}
+	
+	// set is_assignment flag (default to false)
+	node->left->is_assignment = result;
+	
+	if (!result) REPORT_ERROR(node->left, "Wrong assignment expression.");
+	return result;
+}
+
+static bool check_range_expression (gvisitor_t *self, gnode_binary_expr_t *node) {
+	// simple check, if nodes are literals then they must be INT
+	gnode_t *r[2] = {node->left, node->right};
+	
+	for (int i=0; i<2; ++i) {
+		gnode_t *range = r[i];
+		if (ISA_LITERAL(range)) {
+			gnode_literal_expr_t *expr = (gnode_literal_expr_t *)range;
+			if (expr->type != LITERAL_INT) {REPORT_ERROR(node, "Range must be integer.");} return false;
+		}
+	}
+	return true;
+}
+
+static bool check_class_ivar (gvisitor_t *self, gnode_class_decl_t *classnode, gnode_variable_decl_t *node) {
+	size_t count = gnode_array_size(node->decls);
+	
+	gnode_class_decl_t *supernode = (gnode_class_decl_t *)classnode->superclass;
+	for (size_t i=0; i<count; ++i) {
+		gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
+		DEBUG_SEMANTIC("check_ivar %s", p->identifier);
+		
+		// do not check internal outer var
+		if (string_cmp(p->identifier, OUTER_IVAR_NAME) == 0) continue;
+		
+		while (supernode) {
+			symboltable_t *symtable = supernode->symtable;
+			if (symboltable_lookup(symtable, p->identifier) != NULL) {
+				REPORT_WARNING((gnode_t *)node, "Property '%s' defined in class '%s' already defined in its superclass %s.", p->identifier, classnode->identifier, supernode->identifier);
+			}
+			supernode = (gnode_class_decl_t *)supernode->superclass;
+		}
+	}
+	
+	return true;
+}
+
+static void free_postfix_subexpr (gnode_postfix_subexpr_t *subnode) {
+	// check refcount
+	if (subnode->base.refcount > 0) {--subnode->base.refcount; return;}
+	
+	// manually free postfix subnode
+	gnode_n tag = subnode->base.tag;
+	if (tag == NODE_CALL_EXPR) {
+		if (subnode->args) {
+			gnode_array_each(subnode->args, gnode_free(val););
+			gnode_array_free(subnode->args);
+		}
+	} else {
+		gnode_free(subnode->expr);
+	}
+	
+	mem_free((gnode_t*)subnode);
+}
+
+// MARK: - Statements -
+
+static void visit_list_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_list_stmt");
+	
+	PUSH_DECLARATION(node);
+	gnode_array_each(node->stmts, {visit(val);});
+	POP_DECLARATION();
+}
+
+static void visit_compound_stmt (gvisitor_t *self, gnode_compound_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_compound_stmt");
+	
+	gnode_t			*top = TOP_DECLARATION();
+	symboltable_t	*symtable = symtable_from_node(top);
+	
+	symboltable_enter_scope(symtable);
+	gnode_array_each(node->stmts, {visit(val);});
+	
+	symboltable_exit_scope(symtable, &node->nclose);
+}
+
+static void visit_label_stmt (gvisitor_t *self, gnode_label_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_label_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	if (!TOP_STATEMENT_ISA_SWITCH()) {
+		if (type == TOK_KEY_DEFAULT) REPORT_ERROR(node, "'default' statement not in switch statement.");
+		if (type == TOK_KEY_CASE) REPORT_ERROR(node, "'case' statement not in switch statement.");
+	}
+	
+	if (type == TOK_KEY_DEFAULT) {visit(node->stmt);}
+	else if (type == TOK_KEY_CASE) {visit(node->expr); visit(node->stmt);}
+}
+
+static void visit_flow_stmt (gvisitor_t *self, gnode_flow_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_flow_stmt");
+	
+	// assignment has no side effect so report error in case of assignment
+	if (is_expression_assignment(node->cond)) REPORT_ERROR(node->cond, "Assignment not allowed here");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	if (type == TOK_KEY_IF) {
+		visit(node->cond);
+		visit(node->stmt);
+		if (node->elsestmt) visit(node->elsestmt);
+	} else if (type == TOK_KEY_SWITCH) {
+		PUSH_STATEMENT(type);
+		visit(node->cond);
+		visit(node->stmt);
+		POP_STATEMENT();
+	}
+}
+
+static void visit_loop_stmt (gvisitor_t *self, gnode_loop_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_loop_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	PUSH_STATEMENT(type);
+	
+	// check pre-conditions
+	const char	*LOOP_NAME;
+	gnode_t		*cond;
+	if (type == TOK_KEY_WHILE) {LOOP_NAME = "WHILE"; cond = node->cond;}
+	else if (type == TOK_KEY_REPEAT) {LOOP_NAME = "REPEAT"; cond = node->expr;}
+	else if (type == TOK_KEY_FOR) {LOOP_NAME = "FOR"; cond = node->cond;}
+	else {cond = NULL; assert(0);}
+	if (is_expression_assignment(cond))
+		REPORT_ERROR(cond, "Assignments in Gravity does not return a value so cannot be used inside a %s condition.", LOOP_NAME);
+	
+	// FOR condition MUST be a VARIABLE declaration or an IDENTIFIER
+	if (type == TOK_KEY_FOR) {
+		bool type_check = (NODE_ISA(node->cond, NODE_VARIABLE_DECL) || NODE_ISA(node->cond, NODE_IDENTIFIER_EXPR));
+		if (!type_check) REPORT_ERROR(cond, "FOR declaration must be a variable declaration or a local identifier.");
+		
+		if (NODE_ISA(node->cond, NODE_VARIABLE_DECL)) {
+			gnode_variable_decl_t *var = (gnode_variable_decl_t *)node->cond;
+			
+			// assure var declares just ONE variable
+			if (gnode_array_size(var->decls) > 1) REPORT_ERROR(cond, "Cannot declare more than one variable inside a FOR loop.");
+			
+			// assure that there is no assignment expression
+			gnode_var_t *p = (gnode_var_t *)gnode_array_get(var->decls, 0);
+			if (p->expr) REPORT_ERROR(cond, "Assignment expression prohibited in a FOR loop.");
+		}
+	}
+	
+	if (type == TOK_KEY_WHILE) {
+		visit(node->cond);
+		visit(node->stmt);
+	} else if (type == TOK_KEY_REPEAT) {
+		visit(node->stmt);
+		visit(node->expr);
+	} else if (type == TOK_KEY_FOR) {
+		symboltable_t *symtable = symtable_from_node(TOP_DECLARATION());
+		symboltable_enter_scope(symtable);
+		visit(node->cond);
+		if (NODE_ISA(node->cond, NODE_IDENTIFIER_EXPR)) {
+			//if cond is not a var declaration then it must be a local identifier
+			gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)node->cond;
+			if (expr->location.type != LOCATION_LOCAL) REPORT_ERROR(cond, "FOR declaration must be a variable declaration or a local identifier.");
+		}
+		visit(node->expr);
+		visit(node->stmt);
+		
+		symboltable_exit_scope(symtable, &node->nclose);
+	}
+	POP_STATEMENT();
+}
+
+static void visit_jump_stmt (gvisitor_t *self, gnode_jump_stmt_t *node) {
+	DEBUG_SEMANTIC("visit_jump_stmt");
+	
+	gtoken_t type = NODE_TOKEN_TYPE(node);
+	if (type == TOK_KEY_BREAK) {
+		if (!(TOP_STATEMENT_ISA_LOOP() || TOP_STATEMENT_ISA_SWITCH()))
+			REPORT_ERROR(node, "'break' statement not in loop or switch statement.");
+	}
+	else if (type == TOK_KEY_CONTINUE) {
+		if (!TOP_STATEMENT_ISA_LOOP()) REPORT_ERROR(node, "'continue' statement not in loop statement.");
+	}
+	else if (type == TOK_KEY_RETURN) {
+		gnode_t *n1 = TOP_DECLARATION(); // n1 == NULL means globals
+		if (!ISA(n1, NODE_FUNCTION_DECL)) REPORT_ERROR(node, "'return' statement not in a function definition.");
+		
+		if (node->expr) {
+			visit(node->expr);
+			if (!is_expression_valid(node->expr)) {
+				REPORT_ERROR(node->expr, "Invalid expression.");
+				return;
+			}
+		}
+	} else {
+		assert(0);
+	}
+}
+
+static void visit_empty_stmt (gvisitor_t *self, gnode_empty_stmt_t *node) {
+	#pragma unused(self, node)
+	DEBUG_SEMANTIC("visit_empty_stmt");
+	return;
+}
+
+// MARK: - Declarations -
+
+static void visit_function_decl (gvisitor_t *self, gnode_function_decl_t *node) {
+	DEBUG_SEMANTIC("visit_function_decl %s", node->identifier);
+	
+	// set top declaration
+	gnode_t *top = TOP_DECLARATION();
+	
+	// check if optional access and storage specifiers make sense in current context
+	check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
+	
+	// set enclosing declaration
+	node->env = top;
+	
+	// enter function scope
+	PUSH_DECLARATION(node);
+	symboltable_t *symtable = symboltable_create(false);
+	symboltable_enter_scope(symtable);
+	
+	// process parameters
+	node->symtable = symtable;
+	if (node->params) {
+		gnode_array_each(node->params, {
+			gnode_var_t *p = (gnode_var_t *)val;
+			p->env = (gnode_t*)node;
+			if (!symboltable_insert(symtable, p->identifier, (void *)p)) REPORT_ERROR(p, "Parameter %s redeclared.", p->identifier);
+			SET_LOCAL_INDEX(p, symtable);
+			DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
+		});
+	}
+	
+	// process inner block
+	gnode_compound_stmt_t *block = node->block;
+	if (block) {gnode_array_each(block->stmts, {visit(val);});}
+	
+	// exit function scope
+	uint16_t nparams = (node->params) ? (uint16_t)marray_size(*node->params) : 0;
+	uint32_t nlocals = symboltable_exit_scope(symtable, NULL);
+	if (nlocals > MAX_LOCALS) REPORT_ERROR(node, "Maximum number of local variables reached in function %s (max:%d found:%d).",
+										   node->identifier, MAX_LOCALS, nlocals);
+	node->nlocals = (uint16_t)nlocals - nparams;
+	node->nparams = nparams;
+	
+	// check upvalue limit
+	uint32_t nupvalues = (node->uplist) ? (uint32_t)marray_size(*node->uplist) : 0;
+	if (nupvalues > MAX_UPVALUES) REPORT_ERROR(node, "Maximum number of upvalues reached in function %s (max:%d found:%d).",
+											   node->identifier, MAX_LOCALS, nupvalues);
+	
+	POP_DECLARATION();
+	
+	DEBUG_SEMANTIC("MAX LOCALS for function %s: %d", node->identifier, node->nlocals);
+}
+
+static void visit_variable_decl (gvisitor_t *self, gnode_variable_decl_t *node) {
+	gnode_t			*top = TOP_DECLARATION();
+	symboltable_t	*symtable = symtable_from_node(top);
+	size_t			count = gnode_array_size(node->decls);
+	gnode_n			env = NODE_TAG(top);
+	bool			env_is_function = (env == NODE_FUNCTION_DECL);
+	
+	// check if optional access and storage specifiers make sense in current context
+	check_access_storage_specifiers(self, (gnode_t *)node, env, node->access, node->storage);
+	
+	// loop to check each individual declaration
+	for (size_t i=0; i<count; ++i) {
+		gnode_var_t *p = (gnode_var_t *)gnode_array_get(node->decls, i);
+		DEBUG_SEMANTIC("visit_variable_decl %s", p->identifier);
+		
+		// set enclosing environment
+		p->env = top;
+		
+		if (env_is_function) {
+			// local variable defined inside a function
+			if (!symboltable_insert(symtable, p->identifier, (void *)p)) REPORT_ERROR(p, "Identifier %s redeclared.", p->identifier);
+			SET_LOCAL_INDEX(p, symtable);
+			DEBUG_SEMANTIC("Local:%s index:%d", p->identifier, p->index);
+		} else if (env == NODE_CLASS_DECL) {
+			// variable defined inside a class => property
+			gnode_class_decl_t *c = (gnode_class_decl_t *)top;
+			
+			// compute new ivar index
+			uint32_t n1 = (node->storage == TOK_KEY_STATIC) ? c->nsvar++ : c->nivar++;
+			uint32_t n2 = 0;
+			
+			// super class is a static information so I can solve the fragile class problem at compilation time
+			gnode_class_decl_t *super = (gnode_class_decl_t *)c->superclass;
+			while (super) {
+				n2 = (node->storage == TOK_KEY_STATIC) ? super->nsvar : super->nivar;
+				super = (gnode_class_decl_t *)super->superclass;
+			}
+			
+			p->index = n1+n2;
+			DEBUG_SEMANTIC("Class: %s property:%s index:%d (static %d)", c->identifier, p->identifier, p->index, (node->storage == TOK_KEY_STATIC));
+		}
+		
+		// variable with a initial value (or with a getter/setter)
+		if (p->expr) visit(p->expr);
+	}
+}
+
+static void visit_enum_decl (gvisitor_t *self, gnode_enum_decl_t *node) {
+	DEBUG_SEMANTIC("visit_enum_decl %s", node->identifier);
+	
+	// check if optional access and storage specifiers make sense in current context
+	gnode_t *top = TOP_DECLARATION();
+	check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
+}
+
+static void visit_class_decl (gvisitor_t *self, gnode_class_decl_t *node) {
+	DEBUG_SEMANTIC("visit_class_decl %s", node->identifier);
+	
+	gnode_t *top = TOP_DECLARATION();
+	
+	// check if optional access and storage specifiers make sense in current context
+	check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
+	
+	// set class enclosing (can be globals, a class or a function)
+	node->env = top;
+	
+	// check superclass
+	if (node->superclass) {
+		gnode_identifier_expr_t *id = (gnode_identifier_expr_t *)node->superclass;
+		gnode_t *target = lookup_symtable_id(self, id, true);
+		if (!target) REPORT_ERROR(id, "Unable to find superclass %s for class %s.", id->value, node->identifier);
+		node->superclass = target;
+		gnode_free((gnode_t*)id);
+	}
+	
+	// check protocols (disable in this version because protocols are not yet supported)
+	// if (node->protocols) {
+	//	gnode_array_each(node->protocols, {
+	//		gnode_id_expr_t *id = (gnode_id_expr_t *)val;
+	//		gnode_t *target = lookup_symtable_id(self, id, false);
+	//		if (!target) continue;
+	//		id->symbol = target;
+	//	});
+	// }
+	
+	PUSH_DECLARATION(node);
+	gnode_array_each(node->decls, {
+		if ((node->superclass) && (ISA_VAR_DECLARATION(val))) {
+			// check for redeclared ivar and if found report a warning
+			check_class_ivar(self, (gnode_class_decl_t *)node, (gnode_variable_decl_t *)val);
+		}
+		visit(val);
+	});
+	POP_DECLARATION();
+}
+
+static void visit_module_decl (gvisitor_t *self, gnode_module_decl_t *node) {
+	DEBUG_SEMANTIC("visit_module_decl %s", node->identifier);
+	
+	gnode_t *top = TOP_DECLARATION();
+	
+	// set and check module enclosing (only in file)
+	node->env = top;
+	if (NODE_TAG(top) != NODE_LIST_STAT) REPORT_ERROR(node, "Module %s cannot be declared here.", node->identifier);
+	
+	// check if optional access and storage specifiers make sense in current context
+	check_access_storage_specifiers(self, (gnode_t *)node, NODE_TAG(top), node->access, node->storage);
+	
+	PUSH_DECLARATION(node);
+	gnode_array_each(node->decls, {visit(val);});
+	POP_DECLARATION();
+}
+
+// MARK: - Expressions -
+
+static void visit_binary_expr (gvisitor_t *self, gnode_binary_expr_t *node) {
+	DEBUG_SEMANTIC("visit_binary_expr %s", token_name(node->op));
+	
+	// sanity check
+	if (!is_expression(node->left)) REPORT_ERROR(node->left, "LValue must be an expression.");
+	if (!is_expression(node->right)) REPORT_ERROR(node->right, "RValue must be an expression.");
+	
+	// fill missing symbols
+	visit(node->left);
+	visit(node->right);
+	
+	if (!is_expression_valid(node->left)) REPORT_ERROR(node->left, "Invalid left expression.");
+	if (!is_expression_valid(node->right)) REPORT_ERROR(node->right, "Invalid right expression.");
+	
+	// sanity check binary expressions
+	if (is_expression_assignment((gnode_t*)node)) check_assignment_expression(self, node);
+	else if (is_expression_range((gnode_t*)node)) check_range_expression(self, node);
+}
+
+static void visit_unary_expr (gvisitor_t *self, gnode_unary_expr_t *node) {
+	DEBUG_SEMANTIC("visit_unary_expr %s", token_name(node->op));
+	visit(node->expr);
+	if (!is_expression_valid(node->expr)) REPORT_ERROR(node->expr, "Invalid expression.");
+}
+
+static void visit_postfix_expr (gvisitor_t *self, gnode_postfix_expr_t *node) {
+	DEBUG_SEMANTIC("visit_postfix_expr");
+	
+	// a postfix expression is an expression that requires an in-context lookup that depends on id
+	// in a statically typed language the loop should check every member of the postfix expression
+	// usign the context of the previous lookup, for example:
+	// a.b.c.d.e
+	// means
+	// lookup a and get its associated symbol table
+	// lookup b in a
+	// lookup c in the context of the previous lookup
+	// lookup d in the context of the previous lookup
+	// and so on in a loop
+	// Gravity is a dynamically typed language so we cannot statically check membership in a statically way
+	// because the lookup context can vary at runtime, for example
+	// class C1 {...}
+	// class C2 {...}
+	// func foo(n) {if (n % 2 == 0) return C1(); else return C2();}
+	// var c = foo(rand()).bar;
+	// should bar be lookup in C1 or in C2?
+	// we really can't know at compile time but only at runtime
+	
+	// lookup common part (and generate an error if id cannot be found)
+	// id can be a primary expression
+	visit(node->id);
+	
+	// try to obtain symbol table from id (if any)
+	gnode_t *target = NULL;
+	if (ISA(node->id, NODE_IDENTIFIER_EXPR)) {
+		target = ((gnode_identifier_expr_t *)node->id)->symbol;
+		if (ISA(target, NODE_VARIABLE)) target = NULL; // a variable does not contain a symbol table
+	}
+	
+	// special enum case on list[0] (it is a static case)
+	if (ISA(target, NODE_ENUM_DECL)) {
+		// check first expression in the list (in case of enum MUST BE an identifier)
+		gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
+		
+		// enum sanity checks
+		gnode_n tag = subnode->base.tag;
+		if (tag != NODE_ACCESS_EXPR) {REPORT_ERROR(node->id, "Invalid enum expression."); return;}
+		if (node->base.is_assignment) {REPORT_ERROR(node, "Assignment not allowed for an enum type."); return;}
+		if (!ISA(subnode->expr, NODE_IDENTIFIER_EXPR)) {REPORT_ERROR(subnode, "Invalid enum expression."); return;}
+		
+		// lookup enum value
+		gnode_identifier_expr_t *expr = (gnode_identifier_expr_t *)subnode->expr;
+		const char *value = expr->value;
+		gnode_t *v = lookup_node(target, value);
+		if (!v) {REPORT_ERROR(subnode, "Unable to find %s in enum %s.", value, ((gnode_enum_decl_t *)target)->identifier); return;}
+				
+		// node.subnode must be replaced by a literal enum expression (returned by v)
+		size_t n = gnode_array_size(node->list);
+		if (n == 1) {
+			// replace the entire gnode_postfix_expr_t node with v literal value
+			// gnode_replace(node, v); NODE REPLACEMENT FUNCTION TO BE IMPLEMENTED
+			gnode_free(node->id);
+			
+			// we need to explicitly free postfix subexpression here
+			gnode_postfix_subexpr_t *subexpr = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
+			free_postfix_subexpr(subexpr);
+			
+			// list cannot be NULL in a postfix expression, we'll use this flag to identify a transformed enum expression
+			gnode_array_free(node->list);
+			node->list = NULL;
+			node->id = gnode_duplicate(v, false);
+		} else {
+			// postfix expression contains more access nodes so just transform current postfix expression
+			// 1. replace id node
+			gnode_free(node->id);
+			node->id = gnode_duplicate(v, false);
+			
+			// 2. free first node from node->list
+			gnode_postfix_subexpr_t *subexpr = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, 0);
+			free_postfix_subexpr(subexpr);
+			
+			// 3. remove first node from node->list
+			node->list = gnode_array_remove_byindex(node->list, 0);
+		}
+		
+		return;
+	}
+	
+	// check to avoid infinite loop in init
+	if (ISA(node->id, NODE_IDENTIFIER_EXPR)) {
+		if (is_init_infinite_loop(self, (gnode_identifier_expr_t *)node->id, node->list)) {
+			REPORT_ERROR(node, "Infinite loop detected in init func.");
+		}
+	}
+	
+	bool is_super = (NODE_ISA(node->id, NODE_KEYWORD_EXPR) && (((gnode_keyword_expr_t *)node->id)->base.token.type == TOK_KEY_SUPER));
+	bool is_assignment = node->base.is_assignment;
+	
+	// process each subnode
+	size_t count = gnode_array_size(node->list);
+	for (size_t i=0; i<count; ++i) {
+		gnode_postfix_subexpr_t *subnode = (gnode_postfix_subexpr_t *)gnode_array_get(node->list, i);
+		
+		// identify postfix type: NODE_CALL_EXPR, NODE_ACCESS_EXPR, NODE_SUBSCRIPT_EXPR
+		gnode_n tag = subnode->base.tag;
+		
+		// check assignment flag
+		bool is_real_assigment = (is_assignment && (i+1 == count));
+		
+		// assignment sanity check
+		if (is_real_assigment) {
+			if (tag == NODE_CALL_EXPR) {REPORT_ERROR((gnode_t *)subnode, "Unable to assign a value to a function call."); return;}
+			if (is_super) {REPORT_ERROR((gnode_t *)subnode, "Unable to explicitly modify super."); return;}
+		}
+		
+		// for a function/method call visit each argument
+		if (tag == NODE_CALL_EXPR) {
+			size_t n = gnode_array_size(subnode->args);
+			for (size_t j=0; j<n; ++j) {
+				gnode_t *val = (gnode_t *)gnode_array_get(subnode->args, j);
+				visit(val);
+			}
+			continue;
+		}
+		
+		// for a subscript just visit its index expression
+		if (tag == NODE_SUBSCRIPT_EXPR) {
+			if (subnode->expr) visit(subnode->expr);
+			continue;
+		}
+		
+		// for a member access check each lookup type (but do not perform a lookup)
+		if (tag == NODE_ACCESS_EXPR) {
+			if (!ISA(subnode->expr, NODE_IDENTIFIER_EXPR)) REPORT_ERROR(subnode->expr, "Invalid access expression.");
+			continue;
+		}
+		
+		// should never reach this point
+		DEBUG_SEMANTIC("UNRECOGNIZED POSTFIX OPTIONAL EXPRESSION");
+		assert(0);
+	}
+}
+
+static void visit_file_expr (gvisitor_t *self, gnode_file_expr_t *node) {
+	DEBUG_SEMANTIC("visit_file_expr");
+	
+	gnode_r *decls = ((semacheck_t *)self->data)->declarations;
+	gnode_t *globals = gnode_array_get(decls, 0);
+	gnode_t *target = globals;
+	size_t	n = gnode_array_size(node->identifiers);
+	assert(n);
+	
+	// no need to scan the entire list because lookup must be performed at runtime so check just the first element
+	n = 1;
+	for (size_t i=0; i<n; ++i) {
+		const char *identifier = gnode_array_get(node->identifiers, i);
+		DEBUG_SEMANTIC("LOOKUP %s", identifier);
+		
+		gnode_t *symbol = lookup_node(target, identifier);
+		if (!symbol) {REPORT_ERROR(node, "Module identifier %s not found.", identifier); break;}
+		SET_NODE_LOCATION(node, LOCATION_GLOBAL, 0, 0);
+		target = symbol;
+	}
+}
+
+static void visit_literal_expr (gvisitor_t *self, gnode_literal_expr_t *node) {	
+	#pragma unused(self, node)
+	
+	#if GRAVITY_SEMANTIC_DEBUG
+	char value[256];
+	gnode_literal_dump(node, value, sizeof(value));
+	DEBUG_SEMANTIC("visit_literal_expr %s", value);
+	DEBUG_SEMANTIC("end visit_literal_expr");
+	#endif
+}
+
+static void visit_identifier_expr (gvisitor_t *self, gnode_identifier_expr_t *node) {
+	DEBUG_SEMANTIC("visit_identifier_expr %s", node->value);
+	
+	gnode_t *symbol = lookup_identifier(self, node->value, node);
+	if (!symbol) REPORT_ERROR(node, "Identifier %s not found.", node->value);
+}
+
+static void visit_keyword_expr (gvisitor_t *self, gnode_keyword_expr_t *node) {
+	#pragma unused(self, node)
+	DEBUG_SEMANTIC("visit_keyword_expr %s", token_name(node->base.token.type));
+}
+
+static void visit_list_expr (gvisitor_t *self, gnode_list_expr_t *node) {
+	size_t	n = gnode_array_size(node->list1);
+	bool	ismap = (node->list2 != NULL);
+	
+	DEBUG_SEMANTIC("visit_list_expr (n: %zu ismap: %d)", n, ismap);
+	
+	for (size_t j=0; j<n; ++j) {
+		gnode_t *e = gnode_array_get(node->list1, j);
+		visit(e);
+		
+		if (ismap) {
+			// key must be unique
+			for (size_t k=0; k<n; ++k) {
+				if (k == j) continue; // do not check itself
+				gnode_t *key = gnode_array_get(node->list1, k);
+				if (gnode_is_equal(e, key)) {
+					if (gnode_is_literal_string(key)) {
+						gnode_literal_expr_t *v = (gnode_literal_expr_t *)key;
+						REPORT_ERROR(key, "Duplicated key %s in map.", v->value.str);
+					} else REPORT_ERROR(key, "Duplicated key in map.");
+				}
+			}
+			
+			e = gnode_array_get(node->list2, j);
+			visit(e);
+		}
+	}
+}
+
+// MARK: -
+
+bool gravity_semacheck2 (gnode_t *node, gravity_delegate_t *delegate) {
+	semacheck_t	data = {.declarations = gnode_array_create(), .lasterror = 0};
+	marray_init(data.statements);
+	
+	gvisitor_t visitor = {
+		.nerr = 0,							// used to store number of found errors
+		.data = (void *)&data,				// used to store a pointer to the semantic check struct
+		.delegate = (void *)delegate,		// compiler delegate to report errors
+		
+		// STATEMENTS: 7
+		.visit_list_stmt = visit_list_stmt,
+		.visit_compound_stmt = visit_compound_stmt,
+		.visit_label_stmt = visit_label_stmt,
+		.visit_flow_stmt = visit_flow_stmt,
+		.visit_loop_stmt = visit_loop_stmt,
+		.visit_jump_stmt = visit_jump_stmt,
+		.visit_empty_stmt = visit_empty_stmt,
+		
+		// DECLARATIONS: 5
+		.visit_function_decl = visit_function_decl,
+		.visit_variable_decl = visit_variable_decl,
+		.visit_enum_decl = visit_enum_decl,
+		.visit_class_decl = visit_class_decl,
+		.visit_module_decl = visit_module_decl,
+		
+		// EXPRESSIONS: 8
+		.visit_binary_expr = visit_binary_expr,
+		.visit_unary_expr = visit_unary_expr,
+		.visit_file_expr = visit_file_expr,
+		.visit_literal_expr = visit_literal_expr,
+		.visit_identifier_expr = visit_identifier_expr,
+		.visit_keyword_expr = visit_keyword_expr,
+		.visit_list_expr = visit_list_expr,
+		.visit_postfix_expr = visit_postfix_expr,
+	};
+	
+	DEBUG_SEMANTIC("=== SEMANTIC CHECK STEP 2 ===");
+	gvisit(&visitor, node);
+	DEBUG_SEMANTIC("\n");
+	
+	marray_destroy(data.statements);
+	gnode_array_free(data.declarations);
+	return (visitor.nerr == 0);
+}
+

+ 88 - 0
src/compiler/gravity_semacheck2.h

@@ -0,0 +1,88 @@
+//
+//  gravity_semacheck2.h
+//  gravity
+//
+//  Created by Marco Bambini on 12/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_SEMACHECK2__
+#define __GRAVITY_SEMACHECK2__
+
+#include "gravity_ast.h"
+#include "gravity_delegate.h"
+
+// Responsible to gather and check local identifiers
+// Complete check for all identifiers and report not found errors 
+
+bool gravity_semacheck2(gnode_t *node, gravity_delegate_t *delegate);
+
+/*
+ 
+	The following table summarizes what can be defined inside a declaration:
+ 
+    -------+---------------------------------------------------------+
+	       |   func   |   var   |   enum   |   class   |   module    |
+	-------+---------------------------------------------------------+
+	func   |   YES    |   YES   |   NO     |   YES     |   YES       |
+    -------+---------------------------------------------------------+
+	var    |   YES    |   NO    |   NO     |   YES     |   YES       |
+	-------+---------------------------------------------------------+
+	enum   |   YES    |   NO    |   NO     |   YES     |   YES       |
+    -------+---------------------------------------------------------+
+	class  |   YES    |   NO    |   NO     |   YES     |   YES       |
+    -------+---------------------------------------------------------+
+	module |   NO     |   NO    |   NO     |   NO      |   NO        |
+    -------+---------------------------------------------------------+
+ 
+	Everything declared inside a func is a local, so for example:
+ 
+	func foo {
+		func a...;
+		enum b...;
+		class c..;
+	}
+ 
+	is converted by codegen to:
+	
+	func foo {
+		var a = func...;
+		var b = enum...;
+		var c = class..;
+	}
+ 
+	Even if the ONLY valid syntax is anonymous func assignment, user will not be able
+	to assign an anonymous enum or class to a variable. Restriction is applied by parser
+	and reported as a syntax error.
+	Define a module inside a function is not allowed (no real technical reason but I found
+	it a very bad programming practice), restriction is applied by samantic checker.
+ 
+ */
+
+
+// TECH NOTE:
+// At the end of semacheck2:
+//
+// Each declaration and compound statement will have its own symbol table (symtable field)
+// symtable in:
+// NODE_LIST_STAT and NODE_COMPOUND_STAT
+// FUNCTION_DECL and FUNCTION_EXPR
+// ENUM_DECL
+// CLASS_DECL
+// MODULE_DECL
+//
+// Each identifier will have a reference to its declaration (symbol field)
+// symbol field in:
+// NODE_FILE
+// NODE_IDENTIFIER
+// NODE_ID
+//
+// Each declaration will have a reference to its enclosing declaration (env field)
+// env field in:
+// FUNCTION_DECL and FUNCTION_EXPR
+// VARIABLE
+// ENUM_DECL
+// CLASS_DECL
+// MODULE_DECL
+
+#endif

+ 166 - 0
src/compiler/gravity_symboltable.c

@@ -0,0 +1,166 @@
+//
+//  gravity_symboltable.c
+//  gravity
+//
+//  Created by Marco Bambini on 12/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_symboltable.h"
+#include "gravity_macros.h"
+#include "gravity_array.h"
+#include "gravity_hash.h"
+
+// symbol table implementation using a stack of hash tables
+typedef marray_t(gravity_hash_t*)	ghash_r;
+
+#define scope_stack_init(r)			marray_init(*r)
+#define scope_stack_size(r)			marray_size(*r)
+#define scope_stack_get(r,n)		marray_get(*r,n)
+#define scope_stack_free(r)			marray_destroy(*r)
+#define scope_stack_push(r,hash)	marray_push(gravity_hash_t*,*r,hash)
+#define scope_stack_pop(r)			(marray_size(*r) ? marray_pop(*r) : NULL)
+
+#define VALUE_FROM_NODE(x)			((gravity_value_t){.isa = NULL, .p = (gravity_object_t *)(x)})
+#define VALUE_AS_NODE(x)			((gnode_t *)VALUE_AS_OBJECT(x))
+
+// MARK: -
+
+struct symboltable_t {
+	ghash_r		*stack;
+	uint32_t	counter;
+};
+
+// MARK: -
+
+static void check_upvalue_inscope (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(hashtable, key)
+	
+	uint32_t index = *(uint32_t *)data;
+	gnode_t *node = VALUE_AS_NODE(value);
+	if (NODE_ISA(node, NODE_VARIABLE)) {
+		gnode_var_t *var = (gnode_var_t *)node;
+		if (var->upvalue) {
+			if ((index == UINT32_MAX) || (var->index < index)) *(uint32_t *)data = var->index;
+		}
+	}
+}
+	
+// MARK: -
+
+static void symboltable_hash_free (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(hashtable, value, data)
+	// free key only because node is usually retained by other objects and freed in other points
+	gravity_value_free(NULL, key);
+}
+
+static void symboltable_keyvalue_free (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(hashtable, data)
+	// in enum nodes are retained by the symboltable
+	gnode_t *node = VALUE_AS_NODE(value);
+	gnode_free(node);
+	gravity_value_free(NULL, key);
+}
+
+symboltable_t *symboltable_create (bool is_enum) {
+	symboltable_t	*table = mem_alloc(sizeof(symboltable_t));
+	gravity_hash_t	*hash = gravity_hash_create(0, gravity_value_hash, gravity_value_equals,
+												(is_enum) ? symboltable_keyvalue_free : symboltable_hash_free, NULL);
+	if (!table) return NULL;
+	
+	// init symbol table
+	table->counter = 0;
+	table->stack = mem_alloc(sizeof(ghash_r));
+	scope_stack_init(table->stack);
+	scope_stack_push(table->stack, hash);
+	
+	return table;
+}
+
+void symboltable_free (symboltable_t *table) {
+	size_t i, n = scope_stack_size(table->stack);
+	
+	for (i=0; i<n; ++i) {
+		gravity_hash_t *h = scope_stack_get(table->stack, i);
+		gravity_hash_free(h);
+	}
+	
+	if (table->stack) {
+		scope_stack_free(table->stack);
+		mem_free(table->stack);
+	}
+	
+	mem_free(table);
+}
+
+bool symboltable_insert (symboltable_t *table, const char *identifier, gnode_t *node) {
+	size_t			n = scope_stack_size(table->stack);
+	gravity_hash_t	*h = scope_stack_get(table->stack, n-1);
+	
+	// insert node with key identifier into hash table (and check if already exists in current scope)
+	gravity_value_t key = VALUE_FROM_CSTRING(NULL, identifier);
+	if (gravity_hash_lookup(h, key) != NULL) {
+		gravity_value_free(NULL, key);
+		return false;
+	}
+	gravity_hash_insert(h, key, VALUE_FROM_NODE(node));
+						
+	++table->counter;
+	return true;
+}
+
+gnode_t *symboltable_lookup (symboltable_t *table, const char *identifier) {
+	STATICVALUE_FROM_STRING(key, identifier, strlen(identifier));
+	
+	size_t n = scope_stack_size(table->stack);
+	for (int i=(int)n-1; i>=0; --i) {
+		gravity_hash_t *h = scope_stack_get(table->stack, i);
+		gravity_value_t *v = gravity_hash_lookup(h, key);
+		if (v) return VALUE_AS_NODE(*v);
+	}
+	
+	return NULL;
+}
+
+uint32_t symboltable_count (symboltable_t *table, uint32_t index) {
+	gravity_hash_t *h = scope_stack_get(table->stack, index);
+	return gravity_hash_count(h);
+}
+
+// MARK: -
+
+gnode_t *symboltable_global_lookup (symboltable_t *table, const char *identifier) {
+	gravity_hash_t *h = scope_stack_get(table->stack, 0);
+	STATICVALUE_FROM_STRING(key, identifier, strlen(identifier));
+	
+	gravity_value_t *v = gravity_hash_lookup(h, key);
+	return (v) ? VALUE_AS_NODE(*v) : NULL;
+}
+
+void symboltable_enter_scope (symboltable_t *table) {
+	gravity_hash_t *h = gravity_hash_create(0, gravity_value_hash, gravity_value_equals, symboltable_hash_free, NULL);
+	scope_stack_push(table->stack, h);
+}
+
+uint32_t symboltable_local_index (symboltable_t *table) {
+	return table->counter - 1;
+}
+
+uint32_t symboltable_exit_scope (symboltable_t *table, uint32_t *nlevel) {
+	gravity_hash_t *h = (gravity_hash_t *)scope_stack_pop(table->stack);
+	if (nlevel) {
+		*nlevel = UINT32_MAX;
+		gravity_hash_iterate(h, check_upvalue_inscope, (void *)nlevel);
+	}
+	gravity_hash_free(h);
+	return table->counter;
+}
+
+void symboltable_dump (symboltable_t *table) {
+	size_t n = scope_stack_size(table->stack);
+	
+	for (int i=(int)n-1; i>=0; --i) {
+		gravity_hash_t *h = (gravity_hash_t *)scope_stack_get(table->stack, i);
+		gravity_hash_dump(h);
+	}
+}

+ 29 - 0
src/compiler/gravity_symboltable.h

@@ -0,0 +1,29 @@
+//
+//  gravity_symboltable.h
+//  gravity
+//
+//  Created by Marco Bambini on 12/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_SYMTABLE__
+#define __GRAVITY_SYMTABLE__
+
+#include "debug_macros.h"
+#include "gravity_ast.h"
+
+typedef struct symboltable_t	symboltable_t;
+
+symboltable_t	*symboltable_create (bool is_enum);
+gnode_t			*symboltable_lookup (symboltable_t *table, const char *identifier);
+gnode_t			*symboltable_global_lookup (symboltable_t *table, const char *identifier);
+bool			symboltable_insert (symboltable_t *table, const char *identifier, gnode_t *node);
+uint32_t		symboltable_count (symboltable_t *table, uint32_t index);
+
+void			symboltable_enter_scope (symboltable_t *table);
+uint32_t		symboltable_exit_scope (symboltable_t *table, uint32_t *nlevel);
+uint32_t		symboltable_local_index (symboltable_t *table);
+void			symboltable_free (symboltable_t *table);
+void			symboltable_dump (symboltable_t *table);
+
+#endif

+ 348 - 0
src/compiler/gravity_token.c

@@ -0,0 +1,348 @@
+//
+//  gravity_token.c
+//  gravity
+//
+//  Created by Marco Bambini on 31/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_token.h"
+#include "gravity_utils.h"
+
+const char *token_string (gtoken_s token, uint32_t *len) {
+	if (len) *len = token.bytes;
+	return token.value;
+}
+
+const char *token_name (gtoken_t token) {
+	switch (token) {
+		case TOK_EOF: return "EOF";
+		case TOK_ERROR: return "ERROR";
+		case TOK_COMMENT: return "COMMENT";
+		case TOK_STRING: return "STRING";
+		case TOK_NUMBER: return "NUMBER";
+		case TOK_IDENTIFIER: return "IDENTIFIER";
+		case TOK_SPECIAL: return "SPECIAL";
+		case TOK_MACRO: return "MACRO";
+		
+		// keywords
+		case TOK_KEY_FILE: return "file";
+		case TOK_KEY_FUNC: return "func";
+		case TOK_KEY_SUPER: return "super";
+		case TOK_KEY_DEFAULT: return "default";
+		case TOK_KEY_TRUE: return "true";
+		case TOK_KEY_FALSE: return "false";
+		case TOK_KEY_IF: return "if";
+		case TOK_KEY_ELSE: return "else";
+		case TOK_KEY_SWITCH: return "switch";
+		case TOK_KEY_BREAK: return "break";
+		case TOK_KEY_CONTINUE: return "continue";
+		case TOK_KEY_RETURN: return "return";
+		case TOK_KEY_WHILE: return "while";
+		case TOK_KEY_REPEAT: return "repeat";
+		case TOK_KEY_FOR: return "for";
+		case TOK_KEY_IN: return "in";
+		case TOK_KEY_ENUM: return "enum";
+		case TOK_KEY_CLASS: return "class";
+		case TOK_KEY_STRUCT: return "struct";
+		case TOK_KEY_PRIVATE: return "private";
+		case TOK_KEY_INTERNAL: return "internal";
+		case TOK_KEY_PUBLIC: return "public";
+		case TOK_KEY_STATIC: return "static";
+		case TOK_KEY_EXTERN: return "extern";
+		case TOK_KEY_LAZY: return "lazy";
+		case TOK_KEY_CONST: return "const";
+		case TOK_KEY_VAR: return "var";
+		case TOK_KEY_MODULE: return "module";
+		case TOK_KEY_IMPORT: return "import";
+		case TOK_KEY_CASE: return "case";
+		case TOK_KEY_EVENT: return "event";
+		case TOK_KEY_NULL: return "null";
+		case TOK_KEY_UNDEFINED: return "undefined";
+		case TOK_KEY_ISA: return "isa";
+		case TOK_KEY_CURRARGS: return "_args";
+		case TOK_KEY_CURRFUNC: return "_func";
+		
+		// operators
+		case TOK_OP_ADD: return "+";
+		case TOK_OP_SUB: return "-";
+		case TOK_OP_DIV: return "/";
+		case TOK_OP_MUL: return "*";
+		case TOK_OP_REM: return "%";
+		case TOK_OP_ASSIGN: return "=";
+		case TOK_OP_LESS: return "<";
+		case TOK_OP_GREATER: return ">";
+		case TOK_OP_LESS_EQUAL: return "<=";
+		case TOK_OP_GREATER_EQUAL: return ">=";
+		case TOK_OP_ADD_ASSIGN: return "+=";
+		case TOK_OP_SUB_ASSIGN: return "-=";
+		case TOK_OP_DIV_ASSIGN: return "/=";
+		case TOK_OP_MUL_ASSIGN: return "*=";
+		case TOK_OP_REM_ASSIGN: return "%=";
+		case TOK_OP_NOT: return "!";
+		case TOK_OP_AND: return "&&";
+		case TOK_OP_OR: return "||";
+		case TOK_OP_ISEQUAL: return "==";
+		case TOK_OP_ISNOTEQUAL: return "!=";
+		case TOK_OP_RANGE_INCLUDED: return "...";
+		case TOK_OP_RANGE_EXCLUDED: return "..<";
+		case TOK_OP_TERNARY: return "?";
+		case TOK_OP_SHIFT_LEFT: return "<<";
+		case TOK_OP_SHIFT_RIGHT: return ">>";
+		case TOK_OP_BIT_AND: return "&";
+		case TOK_OP_BIT_OR: return "|";
+		case TOK_OP_BIT_XOR: return "^";
+		case TOK_OP_BIT_NOT: return "~";
+		case TOK_OP_ISIDENTICAL: return "===";
+		case TOK_OP_ISNOTIDENTICAL: return "!==";
+		case TOK_OP_PATTERN_MATCH: return "~=";
+		case TOK_OP_SHIFT_LEFT_ASSIGN: return "<<=";
+		case TOK_OP_SHIFT_RIGHT_ASSIGN: return ">>=";
+		case TOK_OP_BIT_AND_ASSIGN: return "&=";
+		case TOK_OP_BIT_OR_ASSIGN: return "|=";
+		case TOK_OP_BIT_XOR_ASSIGN: return "^=";
+		
+		case TOK_OP_OPEN_PARENTHESIS: return "(";
+		case TOK_OP_CLOSED_PARENTHESIS: return ")";
+		case TOK_OP_OPEN_SQUAREBRACKET: return "[";
+		case TOK_OP_CLOSED_SQUAREBRACKET: return "]";
+		case TOK_OP_OPEN_CURLYBRACE: return "{";
+		case TOK_OP_CLOSED_CURLYBRACE: return "}";
+		case TOK_OP_SEMICOLON: return ";";
+		case TOK_OP_COLON: return ":";
+		case TOK_OP_COMMA: return ",";
+		case TOK_OP_DOT: return ".";
+		
+		case TOK_END: return "";
+	}
+	
+	// should never reach this point
+	return "UNRECOGNIZED TOKEN";
+}
+
+void token_keywords_indexes (uint32_t *idx_start, uint32_t *idx_end) {
+	*idx_start = (uint32_t)TOK_KEY_FUNC;
+	*idx_end = (uint32_t)TOK_KEY_CURRARGS;
+};
+
+gtoken_t token_keyword (const char *buffer, int32_t len) {
+	switch (len) {
+		case 2:
+			if (string_casencmp(buffer, "if", len) == 0) return TOK_KEY_IF;
+			if (string_casencmp(buffer, "in", len) == 0) return TOK_KEY_IN;
+			if (string_casencmp(buffer, "or", len) == 0) return TOK_OP_OR;
+			break;
+			
+		case 3:
+			if (string_casencmp(buffer, "isa", len) == 0) return TOK_KEY_ISA;
+			if (string_casencmp(buffer, "for", len) == 0) return TOK_KEY_FOR;
+			if (string_casencmp(buffer, "var", len) == 0) return TOK_KEY_VAR;
+			if (string_casencmp(buffer, "and", len) == 0) return TOK_OP_AND;
+			if (string_casencmp(buffer, "not", len) == 0) return TOK_OP_NOT;
+			break;
+			
+		case 4:
+			if (string_casencmp(buffer, "func", len) == 0) return TOK_KEY_FUNC;
+			if (string_casencmp(buffer, "else", len) == 0) return TOK_KEY_ELSE;
+			if (string_casencmp(buffer, "true", len) == 0) return TOK_KEY_TRUE;
+			if (string_casencmp(buffer, "enum", len) == 0) return TOK_KEY_ENUM;
+			if (string_casencmp(buffer, "case", len) == 0) return TOK_KEY_CASE;
+			if (string_casencmp(buffer, "null", len) == 0) return TOK_KEY_NULL;
+			if (string_casencmp(buffer, "file", len) == 0) return TOK_KEY_FILE;
+			if (string_casencmp(buffer, "lazy", len) == 0) return TOK_KEY_LAZY;
+			break;
+			
+		case 5:
+			if (string_casencmp(buffer, "super", len) == 0) return TOK_KEY_SUPER;
+			if (string_casencmp(buffer, "false", len) == 0) return TOK_KEY_FALSE;
+			if (string_casencmp(buffer, "break", len) == 0) return TOK_KEY_BREAK;
+			if (string_casencmp(buffer, "while", len) == 0) return TOK_KEY_WHILE;
+			if (string_casencmp(buffer, "class", len) == 0) return TOK_KEY_CLASS;
+			if (string_casencmp(buffer, "const", len) == 0) return TOK_KEY_CONST;
+			if (string_casencmp(buffer, "event", len) == 0) return TOK_KEY_EVENT;
+			if (string_casencmp(buffer, "_func", len) == 0) return TOK_KEY_CURRFUNC;
+			if (string_casencmp(buffer, "_args", len) == 0) return TOK_KEY_CURRARGS;
+			break;
+			
+		case 6:
+			if (string_casencmp(buffer, "struct", len) == 0) return TOK_KEY_STRUCT;
+			if (string_casencmp(buffer, "repeat", len) == 0) return TOK_KEY_REPEAT;
+			if (string_casencmp(buffer, "switch", len) == 0) return TOK_KEY_SWITCH;
+			if (string_casencmp(buffer, "return", len) == 0) return TOK_KEY_RETURN;
+			if (string_casencmp(buffer, "public", len) == 0) return TOK_KEY_PUBLIC;
+			if (string_casencmp(buffer, "static", len) == 0) return TOK_KEY_STATIC;
+			if (string_casencmp(buffer, "extern", len) == 0) return TOK_KEY_EXTERN;
+			if (string_casencmp(buffer, "import", len) == 0) return TOK_KEY_IMPORT;
+			if (string_casencmp(buffer, "module", len) == 0) return TOK_KEY_MODULE;
+			break;
+			
+		case 7:
+			if (string_casencmp(buffer, "default", len) == 0) return TOK_KEY_DEFAULT;
+			if (string_casencmp(buffer, "private", len) == 0) return TOK_KEY_PRIVATE;
+			break;
+			
+		case 8:
+			if (string_casencmp(buffer, "continue", len) == 0) return TOK_KEY_CONTINUE;
+			if (string_casencmp(buffer, "internal", len) == 0) return TOK_KEY_INTERNAL;
+			break;
+			
+		case 9:
+			if (string_casencmp(buffer, "undefined", len) == 0) return TOK_KEY_UNDEFINED;
+			break;
+	}
+	
+	return TOK_IDENTIFIER;
+}
+
+const char *token_literal_name (gliteral_t value) {
+	if (value == LITERAL_STRING) return "STRING";
+	else if (value == LITERAL_FLOAT) return "FLOAT";
+	else if (value == LITERAL_INT) return "INTEGER";
+	else if (value == LITERAL_BOOL) return "BOOLEAN";
+	return "N/A";
+}
+
+// MARK: -
+
+bool token_isidentifier (gtoken_t token) {
+	return (token == TOK_IDENTIFIER);
+}
+
+bool token_isvariable_declaration (gtoken_t token) {
+	return ((token == TOK_KEY_CONST) || (token == TOK_KEY_VAR));
+}
+
+bool token_isstatement (gtoken_t token) {
+	// label_statement (case, default)
+	// expression_statement ('+' | '-' | '!' | 'not' | new | raise | file | isPrimaryExpression)
+	// flow_statement (if, select)
+	// loop_statement (while, loop, for)
+	// jump_statement (break, continue, return)
+	// compound_statement ({)
+	// declaration_statement (isDeclarationStatement)
+	// empty_statement (;)
+	// import_statement (import)
+	
+	return (token_islabel_statement(token) || token_isexpression_statement(token) || token_isflow_statement(token) ||
+			token_isloop_statement(token) || token_isjump_statement(token) || token_iscompound_statement(token) ||
+			token_isdeclaration_statement(token) || token_isempty_statement(token) || token_isimport_statement(token));
+}
+
+bool token_isassignment (gtoken_t token) {
+	return ((token == TOK_OP_ASSIGN) || (token == TOK_OP_MUL_ASSIGN) || (token == TOK_OP_DIV_ASSIGN) ||
+			(token == TOK_OP_REM_ASSIGN) || (token == TOK_OP_ADD_ASSIGN) || (token == TOK_OP_SUB_ASSIGN) ||
+			(token == TOK_OP_SHIFT_LEFT_ASSIGN) || (token == TOK_OP_SHIFT_RIGHT_ASSIGN) ||
+			(token == TOK_OP_BIT_AND_ASSIGN) || (token == TOK_OP_BIT_OR_ASSIGN) || (token == TOK_OP_BIT_XOR_ASSIGN));
+}
+
+bool token_isvariable_assignment (gtoken_t token) {
+	return (token == TOK_OP_ASSIGN);
+}
+
+bool token_isaccess_specifier (gtoken_t token) {
+	return ((token == TOK_KEY_PRIVATE) || (token == TOK_KEY_INTERNAL) || (token == TOK_KEY_PUBLIC));
+}
+
+bool token_isstorage_specifier (gtoken_t token) {
+	return ((token == TOK_KEY_STATIC) || (token == TOK_KEY_EXTERN) || (token == TOK_KEY_LAZY));
+}
+
+bool token_isprimary_expression (gtoken_t token) {
+	// literal (number, string)
+	// true, false
+	// IDENTIFIER
+	// 'nil'
+	// 'super'
+	// 'func'
+	// 'undefined'
+	// 'file'
+	// '(' expression ')'
+	// function_expression
+	// list_expression
+	// map_expression
+	
+	return ((token == TOK_NUMBER) || (token == TOK_STRING) || (token == TOK_KEY_TRUE) ||
+			(token == TOK_KEY_FALSE) || (token == TOK_IDENTIFIER) || (token == TOK_KEY_NULL) ||
+			(token == TOK_KEY_SUPER) || (token == TOK_KEY_FUNC) || (token == TOK_KEY_UNDEFINED) ||
+			(token == TOK_OP_OPEN_PARENTHESIS) || (token == TOK_OP_OPEN_SQUAREBRACKET) ||
+			(token == TOK_OP_OPEN_CURLYBRACE) || (token == TOK_KEY_FILE));
+
+}
+
+bool token_isexpression_statement (gtoken_t token) {
+	// reduced to check for unary_expression
+	// postfix_expression: primary_expression | 'module' (was file)
+	// unary_operator: '+' | '-' | '!' | 'not'
+	// raise_expression: 'raise'
+	
+	return (token_isprimary_expression(token) || (token == TOK_OP_ADD) || (token == TOK_OP_SUB) || (token == TOK_OP_NOT));
+}
+
+bool token_islabel_statement (gtoken_t token) {
+	return ((token == TOK_KEY_CASE) || (token == TOK_KEY_DEFAULT));
+}
+
+bool token_isflow_statement (gtoken_t token) {
+	return ((token == TOK_KEY_IF) || (token == TOK_KEY_SWITCH));
+}
+
+bool token_isloop_statement (gtoken_t token) {
+	return ((token == TOK_KEY_WHILE) || (token == TOK_KEY_REPEAT)  || (token == TOK_KEY_FOR));
+}
+
+bool token_isjump_statement (gtoken_t token) {
+	return ((token == TOK_KEY_BREAK) || (token == TOK_KEY_CONTINUE) || (token == TOK_KEY_RETURN));
+}
+
+bool token_iscompound_statement (gtoken_t token) {
+	return (token == TOK_OP_OPEN_CURLYBRACE);
+}
+
+bool token_isdeclaration_statement (gtoken_t token) {
+	// variable_declaration_statement (CONST, VAR)
+	// function_declaration (FUNC)
+	// class_declaration (CLASS | STRUCT)
+	// enum_declaration (ENUM)
+	// module_declaration (MODULE)
+	// event_declaration_statement (EVENT)
+	// empty_declaration (;)
+	
+	return ((token_isaccess_specifier(token) || token_isstorage_specifier(token) || token_isvariable_declaration(token) ||
+			(token == TOK_KEY_FUNC)	|| (token == TOK_KEY_CLASS) || (token == TOK_KEY_STRUCT) || (token == TOK_KEY_ENUM) ||
+			(token == TOK_KEY_MODULE) || (token == TOK_KEY_EVENT)  || (token == TOK_OP_SEMICOLON)));
+}
+
+bool token_isempty_statement (gtoken_t token) {
+	return (token == TOK_OP_SEMICOLON);
+}
+
+bool token_isimport_statement (gtoken_t token) {
+	return (token == TOK_KEY_IMPORT);
+}
+
+bool token_isspecial_statement (gtoken_t token) {
+	return (token == TOK_SPECIAL);
+}
+
+bool token_isoperator (gtoken_t token) {
+	return ((token >= TOK_OP_SHIFT_LEFT) && (token <= TOK_OP_NOT));
+}
+
+bool token_ismacro (gtoken_t token) {
+	return (token == TOK_MACRO);
+}
+
+bool token_iserror (gtoken_t token) {
+	return (token == TOK_ERROR);
+}
+
+bool token_iseof (gtoken_t token) {
+	return (token == TOK_EOF);
+}
+
+bool token_identical (gtoken_s *t1, gtoken_s *t2) {
+	// we can't use memcmp to compare structs for equality due to potential random padding characters between field in structs
+	return ((t1->type == t2->type) && (t1->lineno == t2->lineno) && (t1->colno == t2->colno) && (t1->position == t2->position) &&
+			(t1->bytes == t2->bytes) && (t1->length == t2->length) && (t1->fileid == t2->fileid) && (t1->escaped == t2->escaped) &&
+			(memcmp(t1->value, t2->value, t1->bytes) == 0));
+}

+ 143 - 0
src/compiler/gravity_token.h

@@ -0,0 +1,143 @@
+//
+//  gravity_token.h
+//  gravity
+//
+//  Created by Marco Bambini on 31/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_TOKEN__
+#define __GRAVITY_TOKEN__
+
+#include "debug_macros.h"
+
+//	================
+//	PREFIX OPERATORS
+//	================
+//	+		Unary PLUS
+//	-		Unary MINUS
+//	!		Logical NOT
+//	~		Bitwise NOT
+
+//	================
+//	INFIX OPERATORS
+//	================
+//	<<		Bitwise left shift (160)
+//	>>		Bitwise right shift (160)
+//	*		Multiply (150) (associativity left)
+//	/		Divide (150) (associativity left)
+//	%		Remainder (150) (associativity left)
+//	&		Bitwise AND (150) (associativity left)
+//	+		Add (140) (associativity left)
+//	-		Subtract (140) (associativity left)
+//	|		Bitwise OR (140) (associativity left)
+//	^		Bitwise XOR (140) (associativity left)
+//	..<		Half-open range (135)
+//	...		Closed range (135)
+//	isa		Type check (132)
+//	<		Less than (130)
+//	<=		Less than or equal (130)
+//	>		Greater than (130)
+//	>=		Greater than or equal (130)
+//	==		Equal (130)
+//	!=		Not equal (130)
+//	===		Identical (130)
+//	!==		Not identical (130)
+//	~=		Pattern match (130)
+//	&&		Logical AND (120) (associativity left)
+//	||		Logical OR (110) (associativity left)
+//	?:		Ternary conditional (100) (associativity right)
+//	=		Assign (90) (associativity right)
+//	*=		Multiply and assign (90) (associativity right)
+//	/=		Divide and assign (90) (associativity right)
+//	%=		Remainder and assign (90) (associativity right)
+//	+=		Add and assign (90) (associativity right)
+//	-=		Subtract and assign (90) (associativity right)
+//	<<=		Left bit shift and assign (90) (associativity right)
+//	>>=		Right bit shift and assign (90) (associativity right)
+//	&=		Bitwise AND and assign (90) (associativity right)
+//	^=		Bitwise XOR and assign (90) (associativity right)
+//	|=		Bitwise OR and assign (90) (associativity right)
+
+typedef enum {
+	// General (8)
+	TOK_EOF	= 0, TOK_ERROR, TOK_COMMENT, TOK_STRING, TOK_NUMBER, TOK_IDENTIFIER, TOK_SPECIAL, TOK_MACRO,
+	
+	// Keywords (36)
+	// remember to keep in sync functions token_count and token_value
+	TOK_KEY_FUNC, TOK_KEY_SUPER, TOK_KEY_DEFAULT, TOK_KEY_TRUE, TOK_KEY_FALSE, TOK_KEY_IF,
+	TOK_KEY_ELSE, TOK_KEY_SWITCH, TOK_KEY_BREAK, TOK_KEY_CONTINUE, TOK_KEY_RETURN, TOK_KEY_WHILE,
+	TOK_KEY_REPEAT, TOK_KEY_FOR, TOK_KEY_IN, TOK_KEY_ENUM, TOK_KEY_CLASS, TOK_KEY_STRUCT, TOK_KEY_PRIVATE,
+	TOK_KEY_FILE, TOK_KEY_INTERNAL, TOK_KEY_PUBLIC, TOK_KEY_STATIC, TOK_KEY_EXTERN, TOK_KEY_LAZY, TOK_KEY_CONST,
+	TOK_KEY_VAR, TOK_KEY_MODULE, TOK_KEY_IMPORT, TOK_KEY_CASE, TOK_KEY_EVENT, TOK_KEY_NULL, TOK_KEY_UNDEFINED,
+	TOK_KEY_ISA, TOK_KEY_CURRFUNC, TOK_KEY_CURRARGS,
+	
+	// Operators (36)
+	TOK_OP_SHIFT_LEFT, TOK_OP_SHIFT_RIGHT, TOK_OP_MUL, TOK_OP_DIV, TOK_OP_REM, TOK_OP_BIT_AND, TOK_OP_ADD, TOK_OP_SUB,
+	TOK_OP_BIT_OR, TOK_OP_BIT_XOR, TOK_OP_BIT_NOT, TOK_OP_RANGE_EXCLUDED, TOK_OP_RANGE_INCLUDED, TOK_OP_LESS, TOK_OP_LESS_EQUAL,
+	TOK_OP_GREATER, TOK_OP_GREATER_EQUAL, TOK_OP_ISEQUAL, TOK_OP_ISNOTEQUAL, TOK_OP_ISIDENTICAL, TOK_OP_ISNOTIDENTICAL,
+	TOK_OP_PATTERN_MATCH, TOK_OP_AND, TOK_OP_OR, TOK_OP_TERNARY, TOK_OP_ASSIGN, TOK_OP_MUL_ASSIGN, TOK_OP_DIV_ASSIGN,
+	TOK_OP_REM_ASSIGN, TOK_OP_ADD_ASSIGN, TOK_OP_SUB_ASSIGN, TOK_OP_SHIFT_LEFT_ASSIGN, TOK_OP_SHIFT_RIGHT_ASSIGN,
+	TOK_OP_BIT_AND_ASSIGN, TOK_OP_BIT_OR_ASSIGN, TOK_OP_BIT_XOR_ASSIGN, TOK_OP_NOT,
+	
+	// Punctuators (10)
+	TOK_OP_SEMICOLON, TOK_OP_OPEN_PARENTHESIS, TOK_OP_COLON, TOK_OP_COMMA, TOK_OP_DOT, TOK_OP_CLOSED_PARENTHESIS,
+	TOK_OP_OPEN_SQUAREBRACKET, TOK_OP_CLOSED_SQUAREBRACKET, TOK_OP_OPEN_CURLYBRACE, TOK_OP_CLOSED_CURLYBRACE,
+	
+	// Mark end of tokens (1)
+	TOK_END
+} gtoken_t;
+
+typedef enum {
+	LITERAL_STRING, LITERAL_FLOAT, LITERAL_INT, LITERAL_BOOL
+} gliteral_t;
+
+struct gtoken_s {
+	gtoken_t			type;		// enum based token type
+	uint32_t			lineno;		// token line number (1-based)
+	uint32_t			colno;		// token column number (0-based) at the end of the token
+	uint32_t			position;	// offset of the first character of the token
+	uint32_t			bytes;		// token length in bytes
+	uint32_t			length;		// token length (UTF-8)
+	uint32_t			fileid;		// token file id
+	bool				escaped;	// if true then string_unescape is called when token is finalized
+	const char			*value;		// token value (not null terminated)
+};
+typedef struct gtoken_s	gtoken_s;
+
+#define NO_TOKEN				(gtoken_s){0,0,0,0,0,0,0,0,NULL}
+#define UNDEF_TOKEN				(gtoken_s){TOK_KEY_UNDEFINED,0,0,0,0,0,0,0,NULL}
+#define TOKEN_BYTES(_tok)		_tok.bytes
+#define TOKEN_VALUE(_tok)		_tok.value
+
+const char		*token_string (gtoken_s token, uint32_t *len);
+const char		*token_name (gtoken_t token);
+gtoken_t		token_keyword (const char *buffer, int32_t len);
+void			token_keywords_indexes (uint32_t *idx_start, uint32_t *idx_end);
+const char		*token_literal_name (gliteral_t value);
+
+bool			token_islabel_statement (gtoken_t token);
+bool			token_isflow_statement (gtoken_t token);
+bool			token_isloop_statement (gtoken_t token);
+bool			token_isjump_statement (gtoken_t token);
+bool			token_iscompound_statement (gtoken_t token);
+bool			token_isdeclaration_statement (gtoken_t token);
+bool			token_isempty_statement (gtoken_t token);
+bool			token_isimport_statement (gtoken_t token);
+bool			token_isspecial_statement (gtoken_t token);
+bool			token_isoperator (gtoken_t token);
+bool			token_ismacro (gtoken_t token);
+bool			token_iserror (gtoken_t token);
+bool			token_iseof (gtoken_t token);
+bool			token_isidentifier (gtoken_t token);
+bool			token_isvariable_declaration (gtoken_t token);
+bool			token_isstatement (gtoken_t token);
+bool			token_isassignment (gtoken_t token);
+bool			token_isvariable_assignment (gtoken_t token);
+bool			token_isaccess_specifier (gtoken_t token);
+bool			token_isstorage_specifier (gtoken_t token);
+bool			token_isprimary_expression (gtoken_t token);
+bool			token_isexpression_statement (gtoken_t token);
+bool			token_identical (gtoken_s *token1, gtoken_s *token2);
+
+#endif

+ 59 - 0
src/compiler/gravity_visitor.c

@@ -0,0 +1,59 @@
+//
+//  gravity_visitor.c
+//  gravity
+//
+//  Created by Marco Bambini on 08/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_visitor.h"
+
+// Visit a node invoking the associated callback.
+
+#define VISIT(type)		if (!self->visit_##type) return; \
+						self->visit_##type(self, (gnode_##type##_t *) node); \
+						break;
+
+static void default_action (gnode_t *node) {
+	printf("Visitor unhandled case: %d\n", node->tag);
+	assert(0);
+}
+
+void gvisit(gvisitor_t *self, gnode_t *node) {
+	// this line added after implemented getter and setter,
+	// because they are functions inside a COMPOUND_STATEMENT and can be NULL
+	if (!node) return;
+	
+	switch (node->tag) {
+		
+		// statements (7)
+		case NODE_LIST_STAT: VISIT(list_stmt);
+		case NODE_COMPOUND_STAT: VISIT(compound_stmt);
+		case NODE_LABEL_STAT: VISIT(label_stmt);
+		case NODE_FLOW_STAT: VISIT(flow_stmt);
+		case NODE_JUMP_STAT: VISIT(jump_stmt);
+		case NODE_LOOP_STAT: VISIT(loop_stmt);
+		case NODE_EMPTY_STAT: VISIT(empty_stmt);
+		
+		// declarations (5)
+		case NODE_ENUM_DECL: VISIT(enum_decl);
+		case NODE_FUNCTION_DECL: VISIT(function_decl);
+		case NODE_VARIABLE_DECL: VISIT(variable_decl);
+		case NODE_CLASS_DECL: VISIT(class_decl);
+		case NODE_MODULE_DECL: VISIT(module_decl);
+		// NODE_VARIABLE is handled by NODE_VARIABLE_DECL
+			
+		// expressions (8)
+		case NODE_BINARY_EXPR: VISIT(binary_expr);
+		case NODE_UNARY_EXPR: VISIT(unary_expr);
+		case NODE_FILE_EXPR: VISIT(file_expr);
+		case NODE_LIST_EXPR: VISIT(list_expr);
+		case NODE_LITERAL_EXPR: VISIT(literal_expr);
+		case NODE_IDENTIFIER_EXPR: VISIT(identifier_expr);
+		case NODE_KEYWORD_EXPR: VISIT(keyword_expr);
+		case NODE_POSTFIX_EXPR: VISIT(postfix_expr);
+			
+		// default assert
+		default: default_action(node);
+	}
+}

+ 52 - 0
src/compiler/gravity_visitor.h

@@ -0,0 +1,52 @@
+//
+//  gravity_visitor.h
+//  gravity
+//
+//  Created by Marco Bambini on 08/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_VISITOR__
+#define __GRAVITY_VISITOR__
+
+#include "gravity_ast.h"
+
+#define visit(node) gvisit(self, node)
+
+typedef struct gvisitor {
+	uint32_t	nerr;			// to store err counter state
+	void		*data;			// to store a ptr state
+	void		*delegate;		// delegate callback
+	
+	// count must be equal to enum gnode_n defined in gravity_ast.h less 3
+	
+	// STATEMENTS: 7
+	void (* visit_list_stmt)(struct gvisitor *self, gnode_compound_stmt_t *node);
+	void (* visit_compound_stmt)(struct gvisitor *self, gnode_compound_stmt_t *node);
+	void (* visit_label_stmt)(struct gvisitor *self, gnode_label_stmt_t *node);
+	void (* visit_flow_stmt)(struct gvisitor *self, gnode_flow_stmt_t *node);
+	void (* visit_jump_stmt)(struct gvisitor *self, gnode_jump_stmt_t *node);
+	void (* visit_loop_stmt)(struct gvisitor *self, gnode_loop_stmt_t *node);
+	void (* visit_empty_stmt)(struct gvisitor *self, gnode_empty_stmt_t *node);
+	
+	// DECLARATIONS: 5+1 (NODE_VARIABLE handled by NODE_VARIABLE_DECL case)
+	void (* visit_function_decl)(struct gvisitor *self, gnode_function_decl_t *node);
+	void (* visit_variable_decl)(struct gvisitor *self, gnode_variable_decl_t *node);
+	void (* visit_enum_decl)(struct gvisitor *self, gnode_enum_decl_t *node);
+	void (* visit_class_decl)(struct gvisitor *self, gnode_class_decl_t *node);
+	void (* visit_module_decl)(struct gvisitor *self, gnode_module_decl_t *node);
+	
+	// EXPRESSIONS: 7+3 (CALL EXPRESSIONS handled by one callback)
+	void (* visit_binary_expr)(struct gvisitor *self, gnode_binary_expr_t *node);
+	void (* visit_unary_expr)(struct gvisitor *self, gnode_unary_expr_t *node);
+	void (* visit_file_expr)(struct gvisitor *self, gnode_file_expr_t *node);
+	void (* visit_literal_expr)(struct gvisitor *self, gnode_literal_expr_t *node);
+	void (* visit_identifier_expr)(struct gvisitor *self, gnode_identifier_expr_t *node);
+	void (* visit_keyword_expr)(struct gvisitor *self, gnode_keyword_expr_t *node);
+	void (* visit_list_expr)(struct gvisitor *self, gnode_list_expr_t *node);
+	void (* visit_postfix_expr)(struct gvisitor *self, gnode_postfix_expr_t *node);
+} gvisitor_t;
+
+void gvisit(gvisitor_t *self, gnode_t *node);
+
+#endif

+ 1881 - 0
src/runtime/gravity_core.c

@@ -0,0 +1,1881 @@
+//
+//  gravity_core.c
+//  gravity
+//
+//  Created by Marco Bambini on 10/01/15.
+//  Copyright (c) 2015 CreoLabs. All rights reserved.
+//
+
+#include <math.h>
+#include "gravity_core.h"
+#include "gravity_hash.h"
+#include "gravity_value.h"
+#include "gravity_opcodes.h"
+#include "gravity_macros.h"
+#include "gravity_memory.h"
+
+// Gravity base classes (the isa pointer in each object).
+// Null and Undefined points to same class (Null) and they
+// differs from the n field inside gravity_value_t.
+// n == 0 means NULL while n == 1 means UNDEFINED so I can
+// reuse the same methods for both.
+//	
+// Intrinsic datatypes are:
+// - Int
+// - Float
+// - Boolean
+// - String
+// For these classes 4 conveniente conversion methods are provided.
+
+// How internal conversion works
+//
+// Conversion is driven by the v1 class, so v2 is usually converter to v1 class
+// and if the result is not as expected (very likely in complex expression) then the user
+// is invited to explicitly cast values to the desired types.
+// If a propert conversion function is not found then a runtime error is raised.
+
+// Special note about Integer class
+//
+// Integer not always drives conversion based on v1 class
+// that's because we are trying to fix the common case where
+// an integer is added to a float.
+// Without the smart check an operation like:
+// 1 + 2.3 will result in 3
+// So the first check is about v2 class (v1 is known) and if v2 class is float
+// then v1 is converted to float and the propert operator_float_* function is called.
+
+// Special note about Integer class
+//
+// Bitshift Operators does not make any sense for floating point values
+// as pointed out here: http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/bitshift.html
+// a trick could be to use a pointer to an int to actually manipulate
+// floating point value. Since this is more a trick then a real solution
+// I decided to cast v2 to integer without any extra check.
+// Only operator_float_bit* functions are affected by this trick.
+
+// Special note about Null class
+//
+// Every value in gravity is initialized to Null
+// and can partecipate in math operations.
+// This class should be defined in a way to do be
+// less dangerous as possibile and a Null value should
+// be interpreted as a zero number (where possibile).
+
+static bool core_inited = false;		// initialize global classes just once
+static uint32_t refcount = 0;			// protect deallocation of global classes
+
+// boxed
+gravity_class_t *gravity_class_int;
+gravity_class_t *gravity_class_float;
+gravity_class_t *gravity_class_bool;
+gravity_class_t	*gravity_class_null;
+// objects
+gravity_class_t *gravity_class_string;
+gravity_class_t *gravity_class_object;
+gravity_class_t *gravity_class_function;
+gravity_class_t *gravity_class_closure;
+gravity_class_t *gravity_class_fiber;
+gravity_class_t *gravity_class_class;
+gravity_class_t *gravity_class_instance;
+gravity_class_t *gravity_class_module;
+gravity_class_t *gravity_class_list;
+gravity_class_t *gravity_class_map;
+gravity_class_t *gravity_class_range;
+gravity_class_t *gravity_class_upvalue;
+gravity_class_t *gravity_class_system;
+
+#define SETMETA_INITED(c)						gravity_class_get_meta(c)->is_inited = true
+#define GET_VALUE(_idx)							args[_idx]
+#define RETURN_VALUE(_v,_i)						do {gravity_vm_setslot(vm, _v, _i); return true;} while(0)
+#define RETURN_CLOSURE(_v,_i)					do {gravity_vm_setslot(vm, _v, _i); return false;} while(0)
+#define RETURN_FIBER()							return false
+#define RETURN_NOVALUE()						return true
+#define RETURN_ERROR(...)						do {																		\
+													char buffer[4096];														\
+													snprintf(buffer, sizeof(buffer), __VA_ARGS__);							\
+													gravity_fiber_seterror(gravity_vm_fiber(vm), (const char *) buffer);	\
+													return false;															\
+												} while(0)
+
+#define DECLARE_1VARIABLE(_v,_idx)				register gravity_value_t _v = GET_VALUE(_idx)
+#define DECLARE_2VARIABLES(_v1,_v2,_idx1,_idx2) DECLARE_1VARIABLE(_v1,_idx1);DECLARE_1VARIABLE(_v2,_idx2)
+
+#define CHECK_VALID(_v, _msg)					if (VALUE_ISA_NOTVALID(_v)) RETURN_ERROR(_msg)
+#define INTERNAL_CONVERT_FLOAT(_v)				_v = convert_value2float(vm,_v); CHECK_VALID(_v, "Unable to convert object to Float")
+#define INTERNAL_CONVERT_BOOL(_v)				_v = convert_value2bool(vm,_v); CHECK_VALID(_v, "Unable to convert object to Bool")
+#define INTERNAL_CONVERT_INT(_v)				_v = convert_value2int(vm,_v); CHECK_VALID(_v, "Unable to convert object to Int")
+#define INTERNAL_CONVERT_STRING(_v)				_v = convert_value2string(vm,_v); CHECK_VALID(_v, "Unable to convert object to String")
+
+#define NEW_FUNCTION(_fptr)						(gravity_function_new_internal(NULL, NULL, _fptr, 0))
+#define NEW_CLOSURE_VALUE(_fptr)				((gravity_value_t){	.isa = gravity_class_closure,		\
+																	.p = (gravity_object_t *)gravity_closure_new(NULL, NEW_FUNCTION(_fptr))})
+
+#define FUNCTION_ISA_SPECIAL(_f)				(OBJECT_ISA_FUNCTION(_f) && (_f->tag == EXEC_TYPE_SPECIAL))
+#define FUNCTION_ISA_DEFAULT_GETTER(_f)			((_f->index < GRAVITY_COMPUTED_INDEX) && (_f->special[EXEC_TYPE_SPECIAL_GETTER] == NULL))
+#define FUNCTION_ISA_DEFAULT_SETTER(_f)			((_f->index < GRAVITY_COMPUTED_INDEX) && (_f->special[EXEC_TYPE_SPECIAL_SETTER] == NULL))
+#define FUNCTION_ISA_GETTER(_f)					(_f->special[EXEC_TYPE_SPECIAL_GETTER] != NULL)
+#define FUNCTION_ISA_SETTER(_f)					(_f->special[EXEC_TYPE_SPECIAL_SETTER] != NULL)
+#define FUNCTION_ISA_BRIDGED(_f)				(_f->index == GRAVITY_BRIDGE_INDEX)
+
+// MARK: - Conversions -
+
+static gravity_value_t convert_string2number (gravity_string_t *string, bool float_preferred) {
+	// empty string case
+	if (string->len == 0) return (float_preferred) ? VALUE_FROM_FLOAT(0.0) : VALUE_FROM_INT(0);
+	
+	register const char *s = string->s;
+	register uint32_t len = string->len;
+	register int32_t sign = 1;
+	
+	// check sign first
+	if ((s[0] == '-') || (s[0] == '+')) {
+		if (s[0] == '-') sign = -1;
+		++s; --len;
+	}
+	
+	// check special HEX, OCT, BIN cases
+	if ((s[0] == '0') && (len > 2)) {
+		int64_t n = 0;
+		
+		int c = toupper(s[1]);
+		if (c == 'B') n = number_from_bin(&s[2], len-2);
+		else if (c == 'O') n = number_from_oct(&s[2], len-2);
+		else if (c == 'X') n = number_from_hex(s, len);
+		if (sign == -1) n = -n;
+		return (float_preferred) ? VALUE_FROM_FLOAT((gravity_float_t)n) : VALUE_FROM_INT((gravity_int_t)n);
+	}
+	
+	// default case
+	return (float_preferred) ? VALUE_FROM_FLOAT(strtod(string->s, NULL)) : VALUE_FROM_INT(strtoll(string->s, NULL, 0));
+}
+
+static bool convert_object_int (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_value_t v = convert_value2int(vm, GET_VALUE(0));
+	if (VALUE_ISA_NOTVALID(v)) RETURN_ERROR("Unable to convert object to Int.");
+	RETURN_VALUE(v, rindex);
+}
+
+static bool convert_object_float (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_value_t v = convert_value2float(vm, GET_VALUE(0));
+	if (VALUE_ISA_NOTVALID(v)) RETURN_ERROR("Unable to convert object to Float.");
+	RETURN_VALUE(v, rindex);
+}
+
+static bool convert_object_bool (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_value_t v = convert_value2bool(vm, GET_VALUE(0));
+	if (VALUE_ISA_NOTVALID(v)) RETURN_ERROR("Unable to convert object to Bool.");
+	RETURN_VALUE(v, rindex);
+}
+
+static bool convert_object_string (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_value_t v = convert_value2string(vm, GET_VALUE(0));
+	if (VALUE_ISA_NOTVALID(v)) RETURN_ERROR("Unable to convert object to String.");
+	RETURN_VALUE(v, rindex);
+}
+
+static inline gravity_value_t convert_map2string (gravity_vm *vm, gravity_map_t *map) {
+	#pragma unused(vm, map)
+	return VALUE_FROM_STRING(vm, "MAP", 3); //VALUE_FROM_NULL;
+}
+
+static inline gravity_value_t convert_list2string (gravity_vm *vm, gravity_list_t *list) {
+	// allocate initial memory to a 512 buffer
+	uint32_t len = 512;
+	char *buffer = mem_alloc(len+1);
+	buffer[0] = '[';
+	uint32_t pos = 1;
+	
+	// loop to perform string concat
+	uint32_t count = (uint32_t) marray_size(list->array);
+	for (uint32_t i=0; i<count; ++i) {
+		gravity_value_t value = marray_get(list->array, i);
+		gravity_value_t value2 = convert_value2string(vm, value);
+		gravity_string_t *string = VALUE_ISA_VALID(value2) ? VALUE_AS_STRING(value2) : NULL;
+		
+		char *s1 = (string) ? string->s : "N/A";
+		uint32_t len1 = (string) ? string->len : 3;
+		
+		// check if buffer needs to be reallocated
+		if (len1+pos+2 > len) {
+			len = (len1+pos+2) + len;
+			buffer = mem_realloc(buffer, len);
+		}
+		
+		// copy string to new buffer
+		memcpy(buffer+pos, s1, len1);
+		pos += len1;
+		
+		// add separator
+		if (i+1 < count) {
+			memcpy(buffer+pos, ",", 1);
+			pos += 1;
+		}
+	}
+	
+	// Write latest ] character
+	memcpy(buffer+pos, "]", 1);
+	buffer[++pos] = 0;
+	
+	gravity_value_t result = VALUE_FROM_STRING(vm, buffer, pos);
+	mem_free(buffer);
+	return result;
+}
+
+inline gravity_value_t convert_value2int (gravity_vm *vm, gravity_value_t v) {
+	if (VALUE_ISA_INT(v)) return v;
+	
+	// handle conversion for basic classes
+	if (VALUE_ISA_FLOAT(v)) return VALUE_FROM_INT((gravity_int_t)v.f);
+	if (VALUE_ISA_BOOL(v)) return VALUE_FROM_INT(v.n);
+	if (VALUE_ISA_NULL(v)) return VALUE_FROM_INT(0);
+	if (VALUE_ISA_UNDEFINED(v)) return VALUE_FROM_INT(0);
+	if (VALUE_ISA_STRING(v)) {return convert_string2number(VALUE_AS_STRING(v), false);}
+	
+	// check if class implements the Int method
+	gravity_closure_t *closure = gravity_vm_fastlookup(vm, gravity_value_getclass(v), GRAVITY_INT_INDEX);
+	
+	// sanity check (and break recursion)
+	if ((!closure) || ((closure->f->tag == EXEC_TYPE_INTERNAL) && (closure->f->internal == convert_object_int))) return VALUE_FROM_ERROR(NULL);
+	
+	// execute closure and return its value
+	if (gravity_vm_runclosure(vm, closure, v, NULL, 0)) return gravity_vm_result(vm);
+	
+	return VALUE_FROM_ERROR(NULL);
+}
+
+inline gravity_value_t convert_value2float (gravity_vm *vm, gravity_value_t v) {
+	if (VALUE_ISA_FLOAT(v)) return v;
+	
+	// handle conversion for basic classes
+	if (VALUE_ISA_INT(v)) return VALUE_FROM_FLOAT((gravity_float_t)v.n);
+	if (VALUE_ISA_BOOL(v)) return VALUE_FROM_FLOAT(v.n);
+	if (VALUE_ISA_NULL(v)) return VALUE_FROM_FLOAT(0);
+	if (VALUE_ISA_UNDEFINED(v)) return VALUE_FROM_FLOAT(0);
+	if (VALUE_ISA_STRING(v)) {return convert_string2number(VALUE_AS_STRING(v), true);}
+	
+	// check if class implements the Float method
+	gravity_closure_t *closure = gravity_vm_fastlookup(vm, gravity_value_getclass(v), GRAVITY_FLOAT_INDEX);
+	
+	// sanity check (and break recursion)
+	if ((!closure) || ((closure->f->tag == EXEC_TYPE_INTERNAL) && (closure->f->internal == convert_object_float))) return VALUE_FROM_ERROR(NULL);
+	
+	// execute closure and return its value
+	if (gravity_vm_runclosure(vm, closure, v, NULL, 0)) return gravity_vm_result(vm);
+	
+	return VALUE_FROM_ERROR(NULL);
+}
+
+inline gravity_value_t convert_value2bool (gravity_vm *vm, gravity_value_t v) {
+	if (VALUE_ISA_BOOL(v)) return v;
+	
+	// handle conversion for basic classes
+	if (VALUE_ISA_INT(v)) return VALUE_FROM_BOOL(v.n != 0);
+	if (VALUE_ISA_FLOAT(v)) return VALUE_FROM_BOOL(v.f != 0.0);
+	if (VALUE_ISA_NULL(v)) return VALUE_FROM_FALSE;
+	if (VALUE_ISA_UNDEFINED(v)) return VALUE_FROM_FALSE;
+	if (VALUE_ISA_STRING(v)) {
+		gravity_string_t *string = VALUE_AS_STRING(v);
+		if (string->len == 0) return VALUE_FROM_FALSE;
+		return VALUE_FROM_BOOL((strcmp(string->s, "false") != 0));
+	}
+	
+	// check if class implements the Bool method
+	gravity_closure_t *closure = gravity_vm_fastlookup(vm, gravity_value_getclass(v), GRAVITY_BOOL_INDEX);
+	
+	// sanity check (and break recursion)
+	if ((!closure) || ((closure->f->tag == EXEC_TYPE_INTERNAL) && (closure->f->internal == convert_object_bool))) return VALUE_FROM_BOOL(1);
+	
+	// execute closure and return its value
+	if (gravity_vm_runclosure(vm, closure, v, NULL, 0)) return gravity_vm_result(vm);
+	
+	return VALUE_FROM_ERROR(NULL);
+}
+
+inline gravity_value_t convert_value2string (gravity_vm *vm, gravity_value_t v) {
+	if (VALUE_ISA_STRING(v)) return v;
+	
+	// handle conversion for basic classes
+	if (VALUE_ISA_INT(v)) {
+		char buffer[512];
+		#if GRAVITY_ENABLE_INT64
+		snprintf(buffer, sizeof(buffer), "%lld", v.n);
+		#else
+		snprintf(buffer, sizeof(buffer), "%d", v.n);
+		#endif
+		return VALUE_FROM_CSTRING(vm, buffer);
+		
+	}
+	if (VALUE_ISA_BOOL(v)) return VALUE_FROM_CSTRING(vm, (v.n) ? "true" : "false");
+	if (VALUE_ISA_NULL(v)) return VALUE_FROM_CSTRING(vm, "null");
+	if (VALUE_ISA_UNDEFINED(v)) return VALUE_FROM_CSTRING(vm, "undefined");
+	if (VALUE_ISA_FLOAT(v)) {
+		char buffer[512];
+		snprintf(buffer, sizeof(buffer), "%f", v.f);
+		return VALUE_FROM_CSTRING(vm, buffer);
+	}
+	
+	if (VALUE_ISA_CLASS(v)) {
+		const char *identifier = (VALUE_AS_CLASS(v)->identifier);
+		if (!identifier) identifier = "anonymous class";
+		return VALUE_FROM_CSTRING(vm, identifier);
+	}
+	
+	if (VALUE_ISA_FUNCTION(v)) {
+		const char *identifier = (VALUE_AS_FUNCTION(v)->identifier);
+		if (!identifier) identifier = "anonymous func";
+		return VALUE_FROM_CSTRING(vm, identifier);
+	}
+	
+	if (VALUE_ISA_CLOSURE(v)) {
+		const char *identifier = (VALUE_AS_CLOSURE(v)->f->identifier);
+		if (!identifier) identifier = "anonymous func";
+		return VALUE_FROM_CSTRING(vm, identifier);
+	}
+	
+	if (VALUE_ISA_LIST(v)) {
+		gravity_list_t *list = VALUE_AS_LIST(v);
+		return convert_list2string(vm, list);
+	}
+	
+	if (VALUE_ISA_MAP(v)) {
+		gravity_map_t *map = VALUE_AS_MAP(v);
+		return convert_map2string(vm, map);
+	}
+	
+	// check if class implements the String method (avoiding infinte loop by checking for convert_object_string)
+	gravity_closure_t *closure = gravity_vm_fastlookup(vm, gravity_value_getclass(v), GRAVITY_STRING_INDEX);
+	
+	// sanity check (and break recursion)
+	if ((!closure) || ((closure->f->tag == EXEC_TYPE_INTERNAL) && (closure->f->internal == convert_object_string))) return VALUE_FROM_ERROR(NULL);
+	
+	// execute closure and return its value
+	if (gravity_vm_runclosure(vm, closure, v, NULL, 0)) return gravity_vm_result(vm);
+	
+	return VALUE_FROM_ERROR(NULL);
+}
+
+// MARK: - Object Class -
+
+static bool object_class (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_class_t *c = gravity_value_getclass(GET_VALUE(0));
+	RETURN_VALUE(VALUE_FROM_OBJECT(c), rindex);
+}
+
+static bool object_internal_size (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_int_t size = gravity_value_size(vm, GET_VALUE(0));
+	if (size == 0) size = sizeof(gravity_value_t);
+	RETURN_VALUE(VALUE_FROM_INT(size), rindex);
+}
+
+static bool object_isa (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	gravity_class_t	*c1 = gravity_value_getclass(GET_VALUE(0));
+	gravity_class_t *c2 = VALUE_AS_CLASS(GET_VALUE(1));
+	
+	while (c1 != c2 && c1->superclass != NULL) {
+		c1 = c1->superclass;
+	}
+	
+	RETURN_VALUE(VALUE_FROM_BOOL(c1 == c2), rindex);
+}
+
+
+static bool object_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	if (gravity_value_equals(GET_VALUE(0), GET_VALUE(1))) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+	RETURN_VALUE(VALUE_FROM_INT(1), rindex);
+}
+
+static bool object_not (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	// !obj
+	// if obj is NULL then result is true
+	// everything else must be false
+	RETURN_VALUE(VALUE_FROM_BOOL(VALUE_ISA_NULLCLASS(GET_VALUE(0))), rindex);
+}
+
+static bool object_real_load (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex, bool is_super) {
+	#pragma unused(vm, nargs)
+	
+	// if there is a possibility that gravity_vm_runclosure is called then it is MANDATORY to save arguments before the call
+	gravity_value_t target = GET_VALUE(0);
+	gravity_value_t key = GET_VALUE(1);
+	
+	// check if meta class needs to be initialized (it means if it contains valued static ivars)
+	// meta classes must be inited somewhere, this problem does not exist with instances since object creation itself trigger a class init
+	if (VALUE_ISA_CLASS(target)) {
+		gravity_class_t *c = VALUE_AS_CLASS(target);
+		gravity_class_t *meta = gravity_class_get_meta(c);
+		if (!meta->is_inited) {
+			meta->is_inited = true;
+			gravity_closure_t *closure = gravity_class_lookup_constructor(meta, 0);
+			if (closure) gravity_vm_runclosure(vm, closure, VALUE_FROM_OBJECT(meta), NULL, 0);
+		}
+	}
+	
+	// retrieve class and process key
+	gravity_class_t *c = (is_super) ? VALUE_AS_CLASS(target) : gravity_value_getclass(target);
+	gravity_instance_t *instance = VALUE_ISA_INSTANCE(target) ? VALUE_AS_INSTANCE(target) : NULL;
+	
+	// key is an int its an optimization for faster loading of ivar
+	if (VALUE_ISA_INT(key)) {
+		if (instance) RETURN_VALUE(instance->ivars[key.n], rindex);	// instance case
+		RETURN_VALUE(c->ivars[key.n], rindex);						// class case
+	}
+	
+	// key must be a string in this version
+	if (!VALUE_ISA_STRING(key)) {
+		RETURN_ERROR("Unable to lookup non string value into class %s", c->identifier);
+	}
+	
+	// lookup key in class c
+	gravity_object_t *obj = (gravity_object_t *)gravity_class_lookup(c, key);
+	gravity_closure_t *closure;
+	if (OBJECT_ISA_CLOSURE(obj)) {
+		closure = (gravity_closure_t *)obj;
+		if (!closure || !closure->f) {
+			// not explicitly declared so check for dynamic property in bridge case
+			gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+			if ((instance) && (instance->xdata) && (delegate) && (delegate->bridge_getundef)) {
+				if (delegate->bridge_getundef(vm, instance->xdata, target, VALUE_AS_CSTRING(key), rindex)) return true;
+			}
+			goto execute_notfound;
+		}
+		
+		// execute optimized default getter
+		if (FUNCTION_ISA_SPECIAL(closure->f)) {
+			if (FUNCTION_ISA_DEFAULT_GETTER(closure->f)) {
+				if (instance) RETURN_VALUE(instance->ivars[closure->f->index], rindex);
+				RETURN_VALUE(c->ivars[closure->f->index], rindex);
+			}
+			
+			if (FUNCTION_ISA_GETTER(closure->f)) {
+				// returns a function to be executed using the return false trick
+				RETURN_CLOSURE(VALUE_FROM_OBJECT((gravity_closure_t *)closure->f->special[EXEC_TYPE_SPECIAL_GETTER]), rindex);
+			}
+			goto execute_notfound;
+		}
+	}
+	
+	RETURN_VALUE(VALUE_FROM_OBJECT(obj), rindex);
+	
+execute_notfound:
+	// in case of not found error return the notfound function to be executed (MANDATORY)
+	closure = (gravity_closure_t *)gravity_class_lookup(c, gravity_vm_keyindex(vm, GRAVITY_NOTFOUND_INDEX));
+	RETURN_CLOSURE(VALUE_FROM_OBJECT(closure), rindex);
+}
+
+static bool object_loads (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return object_real_load(vm, args, nargs, rindex, true);
+}
+
+static bool object_load (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return object_real_load(vm, args, nargs, rindex, false);
+}
+
+static bool object_store (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs, rindex)
+	
+	// if there is a possibility that gravity_vm_runfunc is called then it is MANDATORY to save arguments before the call
+	gravity_value_t target = GET_VALUE(0);
+	gravity_value_t key = GET_VALUE(1);
+	gravity_value_t value = GET_VALUE(2);
+	
+	// check if meta class needs to be initialized (it means if it contains valued static ivars)
+	// meta classes must be inited somewhere, this problem does not exist with classes since object creation itself trigger a class init
+	if (VALUE_ISA_CLASS(target)) {
+		gravity_class_t *c = VALUE_AS_CLASS(target);
+		gravity_class_t *meta = gravity_class_get_meta(c);
+		if (!meta->is_inited) {
+			meta->is_inited = true;
+			gravity_closure_t *closure = gravity_class_lookup_constructor(meta, 0);
+			if (closure) gravity_vm_runclosure(vm, closure, VALUE_FROM_OBJECT(meta), NULL, 0);
+		}
+	}
+	
+	// retrieve class and process key
+	gravity_class_t *c = gravity_value_getclass(target);
+	gravity_instance_t *instance = VALUE_ISA_INSTANCE(target) ? VALUE_AS_INSTANCE(target) : NULL;
+	
+	// key is an int its an optimization for faster loading of ivar
+	if (VALUE_ISA_INT(key)) {
+		if (instance) instance->ivars[key.n] = value;
+		else c->ivars[key.n] = value;
+		RETURN_NOVALUE();
+	}
+	
+	// key must be a string in this version
+	if (!VALUE_ISA_STRING(key)) {
+		RETURN_ERROR("Unable to lookup non string value into class %s", c->identifier);
+	}
+	
+	// lookup key in class c
+	gravity_object_t *obj = gravity_class_lookup(c, key);
+	gravity_closure_t *closure;
+	
+	if (OBJECT_ISA_CLOSURE(obj)) {
+		closure = (gravity_closure_t *)obj;
+		if (!closure || !closure->f) {
+			// not explicitly declared so check for dynamic property in bridge case
+			gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+			if ((instance) && (instance->xdata) && (delegate) && (delegate->bridge_setundef)) {
+				if (delegate->bridge_setundef(vm, instance->xdata, target, VALUE_AS_CSTRING(key), value)) RETURN_NOVALUE();
+			}
+			goto execute_notfound;
+		}
+		
+		// check for special functions case
+		if (FUNCTION_ISA_SPECIAL(closure->f)) {
+			// execute optimized default setter
+			if (FUNCTION_ISA_DEFAULT_SETTER(closure->f)) {
+				if (instance) instance->ivars[closure->f->index] = value;
+				else c->ivars[closure->f->index] = value;
+				RETURN_NOVALUE();
+			}
+			
+			if (FUNCTION_ISA_SETTER(closure->f)) {
+				// returns a function to be executed using the return false trick
+				RETURN_CLOSURE(VALUE_FROM_OBJECT((gravity_closure_t *)closure->f->special[EXEC_TYPE_SPECIAL_SETTER]), rindex);
+			}
+			goto execute_notfound;
+		}
+	}
+	
+	RETURN_NOVALUE();
+	
+execute_notfound:
+	// in case of not found error return the notfound function to be executed (MANDATORY)
+	closure = (gravity_closure_t *)gravity_class_lookup(c, gravity_vm_keyindex(vm, GRAVITY_NOTFOUND_INDEX));
+	RETURN_CLOSURE(VALUE_FROM_OBJECT(closure), rindex);
+}
+
+static bool object_notfound (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(nargs,rindex)
+	gravity_class_t *c = gravity_value_getclass(GET_VALUE(0));
+	gravity_value_t key = GET_VALUE(1); // vm_getslot(vm, rindex);
+	RETURN_ERROR("Unable to find %s into class %s", VALUE_AS_CSTRING(key), c->identifier);
+}
+
+static bool object_bind (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(rindex)
+	
+	// sanity check first
+	if (nargs < 3) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_STRING(GET_VALUE(1))) RETURN_ERROR("First argument must be a String.");
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(2))) RETURN_ERROR("Second argument must be a Closure.");
+	
+	gravity_object_t *object = NULL;
+	if (VALUE_ISA_INSTANCE(GET_VALUE(0))) {
+		object = VALUE_AS_OBJECT(GET_VALUE(0));
+	} else if (VALUE_ISA_CLASS(GET_VALUE(0))) {
+		object = VALUE_AS_OBJECT(GET_VALUE(0));
+	} else {
+		RETURN_ERROR("bind method can be applied only to instances or classes.");
+	}
+	
+	gravity_string_t *key = VALUE_AS_STRING(GET_VALUE(1));
+	gravity_class_t *c = gravity_value_getclass(GET_VALUE(0));
+	
+	// check if instance has already an anonymous class added to its hierarchy
+	if (string_nocasencmp(c->identifier, GRAVITY_VM_ANONYMOUS_PREFIX, strlen(GRAVITY_VM_ANONYMOUS_PREFIX) != 0)) {
+		// no super anonymous class found so create a new one, set its super as c, and add it to the hierarchy
+		char *name = gravity_vm_anonymous(vm);
+		gravity_class_t *anon = gravity_class_new_pair(NULL, name, c, 0, 0);
+		object->objclass = anon;
+		c = anon;
+		
+		// store anonymous class (and its meta) into VM special GC stack
+		// manually retains anonymous class that will retain its bound functions
+		gravity_gc_push(vm, (gravity_object_t *)anon);
+		gravity_gc_push(vm, (gravity_object_t *)gravity_class_get_meta(anon));
+	}
+	
+	// add closure to anonymous class
+	gravity_class_bind(c, key->s, GET_VALUE(2));
+	RETURN_NOVALUE();
+}
+
+static bool object_unbind (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(rindex)
+	
+	// sanity check first
+	if (nargs < 2) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_STRING(GET_VALUE(1))) RETURN_ERROR("Argument must be a String.");
+	
+	// remove key/value from hash table
+	gravity_class_t *c = gravity_value_getclass(GET_VALUE(0));
+	gravity_hash_remove(c->htable, GET_VALUE(1));
+	
+	RETURN_NOVALUE();
+}
+
+// MARK: - List Class -
+
+static bool list_count (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_list_t *list = VALUE_AS_LIST(GET_VALUE(0));
+	RETURN_VALUE(VALUE_FROM_INT(marray_size(list->array)), rindex);
+}
+
+static bool list_loadat (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	gravity_value_t value = GET_VALUE(1);
+	if (!VALUE_ISA_INT(value)) RETURN_ERROR("An integer index is required to access a list item.");
+	
+	register int32_t index = (int32_t)VALUE_AS_INT(value);
+	register uint32_t count = (uint32_t)marray_size(list->array);
+	
+	if (index < 0) index = count + index;
+	if ((index < 0) || (index >= count)) RETURN_ERROR("Out of bounds error: index %d beyond bounds 0...%d", index, count-1);
+	
+	RETURN_VALUE(marray_get(list->array, index), rindex);
+}
+
+static bool list_storeat (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs, rindex)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	gravity_value_t idxvalue = GET_VALUE(1);
+	gravity_value_t value = GET_VALUE(2);
+	if (!VALUE_ISA_INT(idxvalue)) RETURN_ERROR("An integer index is required to access a list item.");
+	
+	register int32_t index = (int32_t)VALUE_AS_INT(idxvalue);
+	register uint32_t count = (uint32_t)marray_size(list->array);
+	
+	if (index < 0) index = count + index;
+	if (index < 0) RETURN_ERROR("Out of bounds error: index %d beyond bounds 0...%d", index, count-1);
+	if (index > count) {
+		// handle list resizing here
+		marray_resize(gravity_value_t, list->array, index-count);
+		marray_nset(list->array, index+1);
+		for (size_t i=count; i<index; ++i) {
+			marray_set(list->array, i, VALUE_FROM_NULL);
+		}
+		marray_set(list->array, index, value);
+	}
+		
+	marray_set(list->array, index, value);
+	RETURN_NOVALUE();
+}
+
+static bool list_push (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(nargs)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	gravity_value_t value = GET_VALUE(1);
+	marray_push(gravity_value_t, list->array, value);
+	RETURN_VALUE(VALUE_FROM_INT(marray_size(list->array)), rindex);
+}
+
+static bool list_pop (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(nargs)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	size_t count = marray_size(list->array);
+	if (count < 1) RETURN_ERROR("Unable to pop a value from an empty list.");
+	gravity_value_t value = marray_pop(list->array);
+	RETURN_VALUE(value, rindex);
+}
+	
+static bool list_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	
+	// check for empty list first
+	register uint32_t count = (uint32_t)marray_size(list->array);
+	if (count == 0) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	
+	// check for start of iteration
+	if (VALUE_ISA_NULL(GET_VALUE(1))) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+	
+	// extract value
+	gravity_value_t value = GET_VALUE(1);
+	
+	// check error condition
+	if (!VALUE_ISA_INT(value)) RETURN_ERROR("Iterator expects a numeric value here.");
+	
+	// compute new value
+	gravity_int_t n = value.n;
+	if (n+1 < count) {
+		++n;
+	} else {
+		RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	}
+	
+	// return new iterator
+	RETURN_VALUE(VALUE_FROM_INT(n), rindex);
+}
+
+static bool list_iterator_next (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_list_t	*list = VALUE_AS_LIST(GET_VALUE(0));
+	register int32_t index = (int32_t)VALUE_AS_INT(GET_VALUE(1));
+	RETURN_VALUE(marray_get(list->array, index), rindex);
+}
+
+static bool list_loop (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	if (nargs < 2) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(1))) RETURN_ERROR("Argument must be a Closure.");
+	
+	gravity_closure_t *closure = VALUE_AS_CLOSURE(GET_VALUE(1));	// closure to execute
+	gravity_value_t value = GET_VALUE(0);							// self parameter
+	gravity_list_t *list = VALUE_AS_LIST(value);
+	register gravity_int_t n = marray_size(list->array);			// times to execute the loop
+	register gravity_int_t i = 0;
+	
+	nanotime_t t1 = nanotime();
+	while (i < n) {
+		gravity_vm_runclosure(vm, closure, value, &marray_get(list->array, i), 1);
+		++i;
+	}
+	nanotime_t t2 = nanotime();
+	RETURN_VALUE(VALUE_FROM_INT(t2-t1), rindex);
+}
+
+// MARK: - Map Class -
+
+static bool map_count (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	RETURN_VALUE(VALUE_FROM_INT(gravity_hash_count(map->hash)), rindex);
+}
+
+static void map_keys_array (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused (hashtable, value)
+	gravity_list_t *list = (gravity_list_t *)data;
+	marray_push(gravity_value_t, list->array, key);
+}
+
+static bool map_keys (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	uint32_t count = gravity_hash_count(map->hash);
+	
+	gravity_list_t *list = gravity_list_new(vm, count);
+	gravity_hash_iterate(map->hash, map_keys_array, (void *)list);
+	RETURN_VALUE(VALUE_FROM_OBJECT(list), rindex);
+}
+
+static bool map_loadat (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	gravity_value_t key = GET_VALUE(1);
+	
+	gravity_value_t *value = gravity_hash_lookup(map->hash, key);
+	RETURN_VALUE((value) ? *value : VALUE_FROM_NULL, rindex);
+}
+
+static bool map_storeat (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs, rindex)
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	gravity_value_t key = GET_VALUE(1);
+	gravity_value_t value = GET_VALUE(2);
+	
+	gravity_hash_insert(map->hash, key, value);
+	RETURN_NOVALUE();
+}
+
+static bool map_remove (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	gravity_value_t key = GET_VALUE(1);
+	
+	bool existed = gravity_hash_remove(map->hash, key);
+	RETURN_VALUE(VALUE_FROM_BOOL(existed), rindex);
+}
+
+static bool map_loop (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	if (nargs < 2) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(1))) RETURN_ERROR("Argument must be a Closure.");
+	
+	gravity_closure_t *closure = VALUE_AS_CLOSURE(GET_VALUE(1));	// closure to execute
+	gravity_value_t value = GET_VALUE(0);							// self parameter
+	gravity_map_t *map = VALUE_AS_MAP(GET_VALUE(0));
+	register gravity_int_t n = gravity_hash_count(map->hash);		// times to execute the loop
+	register gravity_int_t i = 0;
+	
+	// build keys array
+	gravity_list_t *list = gravity_list_new(vm, (uint32_t)n);
+	gravity_hash_iterate(map->hash, map_keys_array, (void *)list);
+	
+	nanotime_t t1 = nanotime();
+	while (i < n) {
+		gravity_vm_runclosure(vm, closure, value, &marray_get(list->array, i), 1);
+		++i;
+	}
+	nanotime_t t2 = nanotime();
+	RETURN_VALUE(VALUE_FROM_INT(t2-t1), rindex);
+}
+
+// MARK: - Range Class -
+
+static bool range_count (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_range_t *range = VALUE_AS_RANGE(GET_VALUE(0));
+	gravity_int_t count = (range->to > range->from) ? (range->to - range->from) : (range->from - range->to);
+	RETURN_VALUE(VALUE_FROM_INT(count+1), rindex);
+}
+
+static bool range_loop (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	if (nargs < 2) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(1))) RETURN_ERROR("Argument must be a Closure.");
+	
+	gravity_closure_t *closure = VALUE_AS_CLOSURE(GET_VALUE(1));	// closure to execute
+	gravity_value_t value = GET_VALUE(0);							// self parameter
+	gravity_range_t *range = VALUE_AS_RANGE(value);
+	bool is_forward = range->from < range->to;
+	
+	nanotime_t t1 = nanotime();
+	if (is_forward) {
+		register gravity_int_t n = range->to;
+		register gravity_int_t i = range->from;
+		while (i <= n) {
+			gravity_vm_runclosure(vm, closure, value, &VALUE_FROM_INT(i), 1);
+			++i;
+		}
+	} else {
+		register gravity_int_t n = range->from;			// 5...1
+		register gravity_int_t i = range->to;
+		while (n >= i) {
+			gravity_vm_runclosure(vm, closure, value, &VALUE_FROM_INT(n), 1);
+			--n;
+		}
+	}
+	nanotime_t t2 = nanotime();
+	RETURN_VALUE(VALUE_FROM_INT(t2-t1), rindex);
+}
+
+static bool range_iterator (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_range_t *range = VALUE_AS_RANGE(GET_VALUE(0));
+	
+	// check for empty range first
+	if (range->from == range->to) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	
+	// check for start of iteration
+	if (VALUE_ISA_NULL(GET_VALUE(1))) RETURN_VALUE(VALUE_FROM_INT(range->from), rindex);
+	
+	// extract value
+	gravity_value_t value = GET_VALUE(1);
+	
+	// check error condition
+	if (!VALUE_ISA_INT(value)) RETURN_ERROR("Iterator expects a numeric value here.");
+	
+	// compute new value
+	gravity_int_t n = value.n;
+	if (range->from < range->to) {
+		++n;
+		if (n > range->to) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	} else {
+		--n;
+		if (n < range->to) RETURN_VALUE(VALUE_FROM_FALSE, rindex);
+	}
+	
+	// return new iterator
+	RETURN_VALUE(VALUE_FROM_INT(n), rindex);
+}
+
+static bool range_iterator_next (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(GET_VALUE(1), rindex);
+}
+
+static bool range_contains (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_range_t *range = VALUE_AS_RANGE(GET_VALUE(0));
+	gravity_value_t value = GET_VALUE(1);
+	
+	// check error condition
+	if (!VALUE_ISA_INT(value)) RETURN_ERROR("A numeric value is expected.");
+	
+	RETURN_VALUE(VALUE_FROM_BOOL((value.n >= range->from) && (value.n <= range->to)), rindex);
+}
+
+// MARK: - Class Class -
+
+static bool class_name (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	gravity_class_t *c = (GET_VALUE(0).p);
+	RETURN_VALUE(VALUE_FROM_CSTRING(vm, c->identifier), rindex);
+}
+
+static bool class_exec (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	// if 1st argument is not a class that means that this execution is part of a inner classes chained init
+	// so retrieve class from callable object (that I am sure it is the right class)
+	// this is more an hack than an elegation solution, I really hope to find out a better way
+	if (!VALUE_ISA_CLASS(GET_VALUE(0))) args[0] = *(args-1);
+	
+	// retrieve class (with sanity check)
+	if (!VALUE_ISA_CLASS(GET_VALUE(0))) RETURN_ERROR("Unable to execute non class object.");
+	gravity_class_t *c = (gravity_class_t *)GET_VALUE(0).p;
+	
+	// perform alloc (then check for init)
+	gravity_instance_t *instance = gravity_instance_new(vm, c);
+	
+	// if is inner class then ivar 0 is reserved for a reference to its outer class
+	if (c->has_outer) gravity_instance_setivar(instance, 0, gravity_vm_getslot(vm, 0));
+	
+	// check for constructor function (-1 because self implicit parameter does not count)
+	gravity_closure_t *closure = (gravity_closure_t *)gravity_class_lookup_constructor(c, nargs-1);
+	
+	// replace first parameter (self) to newly allocated instance
+	args[0] = VALUE_FROM_OBJECT(instance);
+	
+	// if constructor found in this class then executes it
+	if (closure) RETURN_CLOSURE(VALUE_FROM_OBJECT(closure), rindex);
+	
+	// no closure found (means no constructor found in this class)
+	gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+	if (c->xdata && delegate && delegate->bridge_initinstance) {
+		// even if no closure is found try to execute the default bridge init instance (if class is bridged)
+		if (nargs != 1) RETURN_ERROR("No init with %d parameters found in class %s", nargs-1, c->identifier);
+		delegate->bridge_initinstance(vm, c->xdata, instance, args, nargs);
+	}
+	
+	// in any case set destination register to newly allocated instance
+	RETURN_VALUE(VALUE_FROM_OBJECT(instance), rindex);
+}
+
+// MARK: - Float Class -
+
+static bool operator_float_add (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	RETURN_VALUE(VALUE_FROM_FLOAT(v1.f + v2.f), rindex);
+}
+
+static bool operator_float_sub (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	RETURN_VALUE(VALUE_FROM_FLOAT(v1.f - v2.f), rindex);
+}
+
+static bool operator_float_div (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	
+	if (v2.f == 0.0) RETURN_ERROR("Division by 0 error.");
+	RETURN_VALUE(VALUE_FROM_FLOAT(v1.f / v2.f), rindex);
+}
+
+static bool operator_float_mul (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	RETURN_VALUE(VALUE_FROM_FLOAT(v1.f * v2.f), rindex);
+}
+
+static bool operator_float_rem (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	
+	// compute floating point modulus
+	#if GRAVITY_ENABLE_DOUBLE
+	RETURN_VALUE(VALUE_FROM_FLOAT(remainder(v1.f, v2.f)), rindex);
+	#else
+	RETURN_VALUE(VALUE_FROM_FLOAT(remainderf(v1.f, v2.f)), rindex);
+	#endif
+}
+
+static bool operator_float_and (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n && v2.n), rindex);
+}
+
+static bool operator_float_or (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n || v2.n), rindex);
+}
+
+static bool operator_float_neg (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_FLOAT(-GET_VALUE(0).f), rindex);
+}
+
+static bool operator_float_not (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_BOOL(!GET_VALUE(0).f), rindex);
+}
+
+static bool operator_float_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_FLOAT(v2);
+	if (v1.f == v2.f) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+	if (v1.f > v2.f) RETURN_VALUE(VALUE_FROM_INT(1), rindex);
+	RETURN_VALUE(VALUE_FROM_INT(-1), rindex);
+}
+
+static bool function_float_round (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	#if GRAVITY_ENABLE_DOUBLE
+	RETURN_VALUE(VALUE_FROM_FLOAT(round(GET_VALUE(0).f)), rindex);
+	#else
+	RETURN_VALUE(VALUE_FROM_FLOAT(roundf(GET_VALUE(0).f)), rindex);
+	#endif
+}
+
+static bool function_float_floor (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	#if GRAVITY_ENABLE_DOUBLE
+	RETURN_VALUE(VALUE_FROM_FLOAT(floor(GET_VALUE(0).f)), rindex);
+	#else
+	RETURN_VALUE(VALUE_FROM_FLOAT(floorf(GET_VALUE(0).f)), rindex);
+	#endif
+}
+
+static bool function_float_ceil (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	#if GRAVITY_ENABLE_DOUBLE
+	RETURN_VALUE(VALUE_FROM_FLOAT(ceil(GET_VALUE(0).f)), rindex);
+	#else
+	RETURN_VALUE(VALUE_FROM_FLOAT(ceilf(GET_VALUE(0).f)), rindex);
+	#endif
+}
+
+// MARK: - Int Class -
+
+// binary operators
+static bool operator_int_add (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	RETURN_VALUE(VALUE_FROM_INT(v1.n + v2.n), rindex);
+}
+
+static bool operator_int_sub (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	RETURN_VALUE(VALUE_FROM_INT(v1.n - v2.n), rindex);
+}
+
+static bool operator_int_div (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	
+	if (v2.n == 0) RETURN_ERROR("Division by 0 error.");
+	RETURN_VALUE(VALUE_FROM_INT(v1.n / v2.n), rindex);
+}
+
+static bool operator_int_mul (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	RETURN_VALUE(VALUE_FROM_INT(v1.n * v2.n), rindex);
+}
+
+static bool operator_int_rem (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	RETURN_VALUE(VALUE_FROM_INT(v1.n % v2.n), rindex);
+}
+
+static bool operator_int_and (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n && v2.n), rindex);
+}
+
+static bool operator_int_or (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs)
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n || v2.n), rindex);
+}
+
+static bool operator_int_neg (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_INT(-GET_VALUE(0).n), rindex);
+}
+
+static bool operator_int_not (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_BOOL(!GET_VALUE(0).n), rindex);
+}
+
+static bool operator_int_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	if (VALUE_ISA_FLOAT(args[1])) return operator_float_cmp(vm, args, nargs, rindex);
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_INT(v2);
+	if (v1.n == v2.n) RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+	if (v1.n > v2.n) RETURN_VALUE(VALUE_FROM_INT(1), rindex);
+	RETURN_VALUE(VALUE_FROM_INT(-1), rindex);
+}
+
+static bool int_loop (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	if (nargs < 2) RETURN_ERROR("Incorrect number of arguments.");
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(1))) RETURN_ERROR("Argument must be a Closure.");
+	
+	gravity_closure_t *closure = VALUE_AS_CLOSURE(GET_VALUE(1));	// closure to execute
+	gravity_value_t value = GET_VALUE(0);							// self parameter
+	register gravity_int_t n = value.n;								// times to execute the loop
+	register gravity_int_t i = 0;
+	
+	nanotime_t t1 = nanotime();
+	while (i < n) {
+		gravity_vm_runclosure(vm, closure, value, &VALUE_FROM_INT(i), 1);
+		++i;
+	}
+	nanotime_t t2 = nanotime();
+	RETURN_VALUE(VALUE_FROM_INT(t2-t1), rindex);
+}
+
+// MARK: - Bool Class -
+
+static bool operator_bool_add (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_add(vm, args, nargs, rindex);
+}
+
+static bool operator_bool_sub (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_sub(vm, args, nargs, rindex);
+}
+
+static bool operator_bool_div (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_div(vm, args, nargs, rindex);
+}
+
+static bool operator_bool_mul (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_mul(vm, args, nargs, rindex);
+}
+
+static bool operator_bool_rem (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_rem(vm, args, nargs, rindex);
+}
+
+static bool operator_bool_and (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n && v2.n), rindex);
+}
+
+static bool operator_bool_or (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n || v2.n), rindex);
+}
+
+static bool operator_bool_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return operator_int_cmp(vm, args, nargs, rindex);
+}
+
+// unary operators
+static bool operator_bool_neg (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_INT(-GET_VALUE(0).n), rindex);
+}
+
+static bool operator_bool_not (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	RETURN_VALUE(VALUE_FROM_INT(!GET_VALUE(0).n), rindex);
+}
+
+// MARK: - String Class -
+
+static bool operator_string_add (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_STRING(v2);
+	
+	gravity_string_t *s1 = VALUE_AS_STRING(v1);
+	gravity_string_t *s2 = VALUE_AS_STRING(v2);
+	
+	uint32_t len = s1->len + s2->len;
+	char buffer[4096];
+	char *s = NULL;
+	
+	// check if I can save an allocation
+	if (len+1<sizeof(buffer)) s = buffer;
+	else s = mem_alloc(len+1);
+	
+	memcpy(s, s1->s, s1->len);
+	memcpy(s+s1->len, s2->s, s2->len);
+	
+	gravity_value_t v = VALUE_FROM_STRING(vm, s, len);
+	if (s != NULL && s != buffer) mem_free(s);
+	
+	RETURN_VALUE(v, rindex);
+}
+
+static bool operator_string_sub (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_STRING(v2);
+	
+	gravity_string_t *s1 = VALUE_AS_STRING(v1);
+	gravity_string_t *s2 = VALUE_AS_STRING(v2);
+	
+	// subtract s2 from s1
+	char *found = strstr(s1->s, s2->s);
+	if (!found) RETURN_VALUE(VALUE_FROM_STRING(vm, s1->s, s1->len), rindex);
+	
+	// substring found
+	// now check if entire substring must be considered
+	uint32_t flen = (uint32_t)strlen(found);
+	if (flen == s2->len) RETURN_VALUE(VALUE_FROM_STRING(vm, s1->s, (uint32_t)(found - s1->s)), rindex);
+	
+	// substring found but cannot be entirely considered
+	uint32_t alloc = MAXNUM(s1->len + s2->len +1, DEFAULT_MINSTRING_SIZE);
+	char *s = mem_alloc(alloc);
+	
+	uint32_t seek = (uint32_t)(found - s1->s);
+	uint32_t len = seek + (flen - s2->len);
+	memcpy(s, s1->s, seek);
+	memcpy(s+seek, found+s2->len, flen - s2->len);
+	
+	gravity_string_t *string = gravity_string_new(vm, s, len, alloc);
+	RETURN_VALUE(VALUE_FROM_OBJECT(string), rindex);
+}
+
+static bool operator_string_and (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n && v2.n), rindex);
+}
+
+static bool operator_string_or (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_BOOL(v1);
+	INTERNAL_CONVERT_BOOL(v2);
+	
+	RETURN_VALUE(VALUE_FROM_BOOL(v1.n || v2.n), rindex);
+}
+
+static bool operator_string_neg (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_1VARIABLE(v1, 0);
+	
+	// reverse the string
+	gravity_string_t *s1 = VALUE_AS_STRING(v1);
+	char *s = (char *)string_dup(s1->s);
+	utf8_reverse(s);
+	
+	gravity_string_t *string = gravity_string_new(vm, s, s1->len, s1->len);
+	RETURN_VALUE(VALUE_FROM_OBJECT(string), rindex);
+}
+
+static bool string_length (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_1VARIABLE(v1, 0);
+	gravity_string_t *s1 = VALUE_AS_STRING(v1);
+	
+	RETURN_VALUE(VALUE_FROM_INT(s1->len), rindex);
+}
+
+static bool operator_string_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm, nargs)
+	
+	DECLARE_2VARIABLES(v1, v2, 0, 1);
+	INTERNAL_CONVERT_STRING(v2);
+	
+	gravity_string_t *s1 = VALUE_AS_STRING(v1);
+	gravity_string_t *s2 = VALUE_AS_STRING(v2);
+	
+	RETURN_VALUE(VALUE_FROM_INT(strcmp(s1->s, s2->s)), rindex);
+}
+
+// MARK: - Fiber Class -
+
+static bool fiber_create (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(nargs)
+	
+	if (!VALUE_ISA_CLOSURE(GET_VALUE(1))) RETURN_ERROR("A function is expected as argument to Fiber.create.");
+	
+	gravity_fiber_t *fiber = gravity_fiber_new(vm, VALUE_AS_CLOSURE(GET_VALUE(1)), 0, 0);
+	RETURN_VALUE(VALUE_FROM_OBJECT(fiber), rindex);
+}
+
+static bool fiber_run (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex, bool is_trying) {
+	#pragma unused(nargs, rindex)
+	
+	gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
+	
+	if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
+	
+	// remember who ran the fiber
+	fiber->caller = gravity_vm_fiber(vm);
+	
+	// set trying flag
+	fiber->trying = is_trying;
+	
+	// switch currently running fiber
+	gravity_vm_setfiber(vm, fiber);
+	
+	RETURN_FIBER();
+}
+
+static bool fiber_exec (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return fiber_run(vm, args, nargs, rindex, false);
+}
+
+static bool fiber_try (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return fiber_run(vm, args, nargs, rindex, true);
+}
+
+static bool fiber_yield (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(args, nargs, rindex)
+	
+	gravity_fiber_t *fiber = gravity_vm_fiber(vm);
+	gravity_vm_setfiber(vm, fiber->caller);
+	
+	// unhook this fiber from the one that called it
+	fiber->caller = NULL;
+	fiber->trying = false;
+	
+	RETURN_FIBER();
+}
+
+static bool fiber_status (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(nargs)
+		
+	gravity_fiber_t *fiber = VALUE_AS_FIBER(GET_VALUE(0));
+	RETURN_VALUE(VALUE_FROM_BOOL(fiber->nframes == 0 || fiber->error), rindex);
+}
+
+static bool fiber_abort (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(rindex)
+	
+	gravity_value_t msg = (nargs > 0) ? GET_VALUE(1) : VALUE_FROM_NULL;
+	if (!VALUE_ISA_STRING(msg)) RETURN_ERROR("Fiber.abort expects a string as argument.");
+	
+	gravity_string_t *s = VALUE_AS_STRING(msg);
+	RETURN_ERROR("%.*s", s->len, s->s);
+}
+
+// MARK: - Null Class -
+
+static bool operator_null_add (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,nargs)
+	// NULL + v2 = v2
+	RETURN_VALUE(args[1], rindex);
+}
+
+static bool operator_null_sub (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	// NULL - v2 should be computed as -v2 in my opinion (since NULL is interpreted as zero)
+	args[0] = VALUE_FROM_INT(0);
+	return operator_int_sub(vm, args, nargs, rindex);
+}
+
+static bool operator_null_div (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	// NULL / v2 = 0
+	RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+}
+
+static bool operator_null_mul (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	// NULL * v2 = 0
+	RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+}
+
+static bool operator_null_rem (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	// NULL % v2 = 0
+	RETURN_VALUE(VALUE_FROM_INT(0), rindex);
+}
+
+static bool operator_null_and (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	RETURN_VALUE(VALUE_FROM_BOOL(0), rindex);
+}
+
+static bool operator_null_or (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,nargs)
+	
+	DECLARE_1VARIABLE(v2, 1);
+	INTERNAL_CONVERT_BOOL(v2);
+	RETURN_VALUE(VALUE_FROM_BOOL(0 || v2.n), rindex);
+}
+
+// unary operators
+static bool operator_null_neg (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	RETURN_VALUE(VALUE_FROM_BOOL(0), rindex);
+}
+
+static bool operator_null_not (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	// !null = true in all the tested programming languages
+	RETURN_VALUE(VALUE_FROM_BOOL(1), rindex);
+}
+
+#if GRAVITY_NULL_SILENT
+static bool operator_null_silent (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,args,nargs)
+	RETURN_VALUE(VALUE_FROM_NULL, rindex);
+}
+#endif
+
+static bool operator_null_cmp (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(vm,nargs)
+	if (VALUE_ISA_UNDEFINED(args[0])) {
+		// undefined case (undefined is equal ONLY to undefined)
+		if (VALUE_ISA_UNDEFINED(args[1])) RETURN_VALUE(VALUE_FROM_BOOL(1), rindex);
+		RETURN_VALUE(VALUE_FROM_BOOL(0), rindex);
+	}
+	
+	args[0] = VALUE_FROM_INT(0);
+	return operator_int_cmp(vm, args, nargs, rindex);
+}
+
+// MARK: - System -
+
+static bool system_nanotime (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused(args,nargs)
+	nanotime_t t = nanotime();
+	RETURN_VALUE(VALUE_FROM_INT(t), rindex);
+}
+
+static bool system_realprint (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex, bool cr) {
+	#pragma unused (rindex)
+	for (uint16_t i=1; i<nargs; ++i) {
+		gravity_value_t v = GET_VALUE(i);
+		INTERNAL_CONVERT_STRING(v);
+		gravity_string_t *s = VALUE_AS_STRING(v);
+		printf("%.*s", s->len, s->s);
+	}
+	if (cr) printf("\n");
+	RETURN_NOVALUE();
+}
+
+static bool system_put (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return system_realprint(vm, args, nargs, rindex, false);
+}
+	
+static bool system_print (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	return system_realprint(vm, args, nargs, rindex, true);
+}
+
+static bool system_get (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (args, nargs)
+	gravity_value_t key = GET_VALUE(1);
+	if (!VALUE_ISA_STRING(key)) RETURN_VALUE(VALUE_FROM_NULL, rindex);
+	RETURN_VALUE(gravity_vm_get(vm, VALUE_AS_CSTRING(key)), rindex);
+}
+
+static bool system_set (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+	#pragma unused (nargs, rindex)
+	gravity_value_t key = GET_VALUE(1);
+	gravity_value_t value = GET_VALUE(2);
+	if (!VALUE_ISA_STRING(key)) RETURN_NOVALUE();
+	
+	bool result = gravity_vm_set(vm, VALUE_AS_CSTRING(key), value);
+	if (!result) RETURN_ERROR("Unable to apply System setting.");
+	RETURN_NOVALUE();
+}
+
+
+// MARK: - CORE -
+
+static gravity_closure_t *computed_property (gravity_vm *vm, gravity_function_t *getter_func, gravity_function_t *setter_func) {
+	gravity_closure_t *getter_closure = (getter_func) ? gravity_closure_new(vm, getter_func) : NULL;
+	gravity_closure_t *setter_closure = (setter_func) ? gravity_closure_new(vm, setter_func) : NULL;
+	gravity_function_t *f = gravity_function_new_special(vm, NULL, GRAVITY_COMPUTED_INDEX, getter_closure, setter_closure);
+	return gravity_closure_new(vm, f);
+}
+
+static void gravity_core_init (void) {
+	// this function must be executed ONCE
+	if (core_inited) return;
+	core_inited = true;
+	
+	mem_check(false);
+	
+	// Creation order here is very important
+	// for example in a earlier version the intrinsic classes
+	// were created before the Function class
+	// so when the isa pointer was set to gravity_class_function
+	// it resulted in a NULL pointer
+	
+	// Object and Class are special classes
+	// Object has no superclass (so the lookup loop knows when to finish)
+	// Class has Object as its superclass
+	// Any other class without an explicit superclass automatically has Object as its super
+	// Both Object and Class classes has Class set as metaclass
+	// Any other class created with gravity_class_new_pair has "class meta" as its metaclass
+	
+	//		CORE CLASS DIAGRAM:
+	//
+	//		---->	means class's superclass
+	//		====>	means class's metaclass
+	//
+	//
+	//           +--------------------+    +=========+
+	//           |                    |    ||       ||
+	//           v                    |    \/       ||
+	//		+--------------+     +--------------+   ||
+	//		|    Object    | ==> |     Class    |====+
+	//		+--------------+     +--------------+
+	//             ^                    ^
+	//             |                    |
+	//		+--------------+     +--------------+
+	//		|     Base     | ==> |   Base meta  |
+	//		+--------------+     +--------------+
+	//             ^                    ^
+	//             |                    |
+	//		+--------------+     +--------------+
+	//		|   Subclass   | ==> |Subclass meta |
+	//		+--------------+     +--------------+
+	
+	// Create classes first and then bind methods
+	// A class without a superclass in a subclass of Object.
+	
+	// every class without a super will have OBJECT as its superclass
+	gravity_class_object = gravity_class_new_single(NULL, GRAVITY_CLASS_OBJECT_NAME, 0);
+	gravity_class_class = gravity_class_new_single(NULL, GRAVITY_CLASS_CLASS_NAME, 0);
+	gravity_class_setsuper(gravity_class_class, gravity_class_object);
+	
+	// manually set meta class and isa pointer for classes created without the gravity_class_new_pair
+	// when gravity_class_new_single was called gravity_class_class was NULL so the isa pointer must be reset
+	gravity_class_object->objclass = gravity_class_class; gravity_class_object->isa = gravity_class_class;
+	gravity_class_class->objclass = gravity_class_class; gravity_class_class->isa = gravity_class_class;
+	
+	// NULL in gravity_class_new_pair and NEW_FUNCTION macro because I do not want them to be in the GC
+	gravity_class_function = gravity_class_new_pair(NULL, GRAVITY_CLASS_FUNCTION_NAME, NULL, 0, 0);
+	gravity_class_fiber = gravity_class_new_pair(NULL, GRAVITY_CLASS_FIBER_NAME, NULL, 0, 0);
+	gravity_class_instance = gravity_class_new_pair(NULL, GRAVITY_CLASS_INSTANCE_NAME, NULL, 0, 0);
+	gravity_class_closure = gravity_class_new_pair(NULL, GRAVITY_CLASS_CLOSURE_NAME, NULL, 0, 0);
+	gravity_class_upvalue = gravity_class_new_pair(NULL, GRAVITY_CLASS_UPVALUE_NAME, NULL, 0, 0);
+	gravity_class_module = NULL;
+	
+	// create intrinsic classes (intrinsic datatypes are: Int, Float, Bool, Null, String, List, Map and Range)
+	gravity_class_int = gravity_class_new_pair(NULL, GRAVITY_CLASS_INT_NAME, NULL, 0, 0);
+	gravity_class_float = gravity_class_new_pair(NULL, GRAVITY_CLASS_FLOAT_NAME, NULL, 0, 0);
+	gravity_class_bool = gravity_class_new_pair(NULL, GRAVITY_CLASS_BOOL_NAME, NULL, 0, 0);
+	gravity_class_null = gravity_class_new_pair(NULL, GRAVITY_CLASS_NULL_NAME, NULL, 0, 0);
+	gravity_class_string = gravity_class_new_pair(NULL, GRAVITY_CLASS_STRING_NAME, NULL, 0, 0);
+	gravity_class_list = gravity_class_new_pair(NULL, GRAVITY_CLASS_LIST_NAME, NULL, 0, 0);
+	gravity_class_map = gravity_class_new_pair(NULL, GRAVITY_CLASS_MAP_NAME, NULL, 0, 0);
+	gravity_class_range = gravity_class_new_pair(NULL, GRAVITY_CLASS_RANGE_NAME, NULL, 0, 0);
+	
+	// OBJECT CLASS
+	gravity_class_bind(gravity_class_object, GRAVITY_CLASS_CLASS_NAME, NEW_CLOSURE_VALUE(object_class));
+	gravity_class_bind(gravity_class_object, GRAVITY_OPERATOR_ISA_NAME, NEW_CLOSURE_VALUE(object_isa));
+	gravity_class_bind(gravity_class_object, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(object_cmp));
+	gravity_class_bind(gravity_class_object, GRAVITY_CLASS_INT_NAME, NEW_CLOSURE_VALUE(convert_object_int));
+	gravity_class_bind(gravity_class_object, GRAVITY_CLASS_FLOAT_NAME, NEW_CLOSURE_VALUE(convert_object_float));
+	gravity_class_bind(gravity_class_object, GRAVITY_CLASS_BOOL_NAME, NEW_CLOSURE_VALUE(convert_object_bool));
+	gravity_class_bind(gravity_class_object, GRAVITY_CLASS_STRING_NAME, NEW_CLOSURE_VALUE(convert_object_string));
+	gravity_class_bind(gravity_class_object, GRAVITY_INTERNAL_LOAD_NAME, NEW_CLOSURE_VALUE(object_load));
+	gravity_class_bind(gravity_class_object, GRAVITY_INTERNAL_LOADS_NAME, NEW_CLOSURE_VALUE(object_loads));
+	gravity_class_bind(gravity_class_object, GRAVITY_INTERNAL_STORE_NAME, NEW_CLOSURE_VALUE(object_store));
+	gravity_class_bind(gravity_class_object, GRAVITY_INTERNAL_NOTFOUND_NAME, NEW_CLOSURE_VALUE(object_notfound));
+	gravity_class_bind(gravity_class_object, "_size", NEW_CLOSURE_VALUE(object_internal_size));
+	gravity_class_bind(gravity_class_object, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(object_not));
+	gravity_class_bind(gravity_class_object, "bind", NEW_CLOSURE_VALUE(object_bind));
+	gravity_class_bind(gravity_class_object, "unbind", NEW_CLOSURE_VALUE(object_unbind));
+	
+	// CLASS CLASS
+	gravity_class_bind(gravity_class_class, "name", NEW_CLOSURE_VALUE(class_name));
+	gravity_class_bind(gravity_class_class, GRAVITY_INTERNAL_EXEC_NAME, NEW_CLOSURE_VALUE(class_exec));
+	
+	// LIST CLASS
+	gravity_class_bind(gravity_class_list, "count", VALUE_FROM_OBJECT(computed_property(NULL, NEW_FUNCTION(list_count), NULL)));
+	gravity_class_bind(gravity_class_list, ITERATOR_INIT_FUNCTION, NEW_CLOSURE_VALUE(list_iterator));
+	gravity_class_bind(gravity_class_list, ITERATOR_NEXT_FUNCTION, NEW_CLOSURE_VALUE(list_iterator_next));
+	gravity_class_bind(gravity_class_list, GRAVITY_INTERNAL_LOADAT_NAME, NEW_CLOSURE_VALUE(list_loadat));
+	gravity_class_bind(gravity_class_list, GRAVITY_INTERNAL_STOREAT_NAME, NEW_CLOSURE_VALUE(list_storeat));
+	gravity_class_bind(gravity_class_list, GRAVITY_INTERNAL_LOOP_NAME, NEW_CLOSURE_VALUE(list_loop));
+	gravity_class_bind(gravity_class_list, "push", NEW_CLOSURE_VALUE(list_push));
+	gravity_class_bind(gravity_class_list, "pop", NEW_CLOSURE_VALUE(list_pop));
+	
+	// MAP CLASS
+	gravity_class_bind(gravity_class_map, "keys", NEW_CLOSURE_VALUE(map_keys));
+	gravity_class_bind(gravity_class_map, "remove", NEW_CLOSURE_VALUE(map_remove));
+	gravity_class_bind(gravity_class_map, "count", VALUE_FROM_OBJECT(computed_property(NULL, NEW_FUNCTION(map_count), NULL)));
+	gravity_class_bind(gravity_class_map, GRAVITY_INTERNAL_LOOP_NAME, NEW_CLOSURE_VALUE(map_loop));
+	gravity_class_bind(gravity_class_map, GRAVITY_INTERNAL_LOADAT_NAME, NEW_CLOSURE_VALUE(map_loadat));
+	gravity_class_bind(gravity_class_map, GRAVITY_INTERNAL_STOREAT_NAME, NEW_CLOSURE_VALUE(map_storeat));
+	#if GRAVITY_MAP_DOTSUGAR
+	gravity_class_bind(gravity_class_map, GRAVITY_INTERNAL_LOAD_NAME, NEW_CLOSURE_VALUE(map_loadat));
+	gravity_class_bind(gravity_class_map, GRAVITY_INTERNAL_STORE_NAME, NEW_CLOSURE_VALUE(map_storeat));
+	#endif
+	
+	// RANGE CLASS
+	gravity_class_bind(gravity_class_range, "count", VALUE_FROM_OBJECT(computed_property(NULL, NEW_FUNCTION(range_count), NULL)));
+	gravity_class_bind(gravity_class_range, ITERATOR_INIT_FUNCTION, NEW_CLOSURE_VALUE(range_iterator));
+	gravity_class_bind(gravity_class_range, ITERATOR_NEXT_FUNCTION, NEW_CLOSURE_VALUE(range_iterator_next));
+	gravity_class_bind(gravity_class_range, "contains", NEW_CLOSURE_VALUE(range_contains));
+	gravity_class_bind(gravity_class_range, GRAVITY_INTERNAL_LOOP_NAME, NEW_CLOSURE_VALUE(range_loop));
+	
+	// INT CLASS
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_ADD_NAME, NEW_CLOSURE_VALUE(operator_int_add));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_SUB_NAME, NEW_CLOSURE_VALUE(operator_int_sub));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_DIV_NAME, NEW_CLOSURE_VALUE(operator_int_div));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_MUL_NAME, NEW_CLOSURE_VALUE(operator_int_mul));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_REM_NAME, NEW_CLOSURE_VALUE(operator_int_rem));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_AND_NAME, NEW_CLOSURE_VALUE(operator_int_and));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_OR_NAME,  NEW_CLOSURE_VALUE(operator_int_or));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(operator_int_cmp));
+//	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_BITOR_NAME, NEW_CLOSURE_VALUE(operator_int_bitor));
+//	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_BITAND_NAME, NEW_CLOSURE_VALUE(operator_int_bitand));
+//	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_BITXOR_NAME, NEW_CLOSURE_VALUE(operator_int_bitxor));
+//	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_BITLS_NAME, NEW_CLOSURE_VALUE(operator_int_bitls));
+//	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_BITRS_NAME, NEW_CLOSURE_VALUE(operator_int_bitrs));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_int_neg));
+	gravity_class_bind(gravity_class_int, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(operator_int_not));
+	gravity_class_bind(gravity_class_int, GRAVITY_INTERNAL_LOOP_NAME, NEW_CLOSURE_VALUE(int_loop));
+	
+	// FLOAT CLASS
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_ADD_NAME, NEW_CLOSURE_VALUE(operator_float_add));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_SUB_NAME, NEW_CLOSURE_VALUE(operator_float_sub));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_DIV_NAME, NEW_CLOSURE_VALUE(operator_float_div));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_MUL_NAME, NEW_CLOSURE_VALUE(operator_float_mul));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_REM_NAME, NEW_CLOSURE_VALUE(operator_float_rem));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_AND_NAME, NEW_CLOSURE_VALUE(operator_float_and));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_OR_NAME,  NEW_CLOSURE_VALUE(operator_float_or));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(operator_float_cmp));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_float_neg));
+	gravity_class_bind(gravity_class_float, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(operator_float_not));
+	gravity_class_bind(gravity_class_float, "round", NEW_CLOSURE_VALUE(function_float_round));
+	gravity_class_bind(gravity_class_float, "floor", NEW_CLOSURE_VALUE(function_float_floor));
+	gravity_class_bind(gravity_class_float, "ceil", NEW_CLOSURE_VALUE(function_float_ceil));
+	
+	// BOOL CLASS
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_ADD_NAME, NEW_CLOSURE_VALUE(operator_bool_add));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_SUB_NAME, NEW_CLOSURE_VALUE(operator_bool_sub));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_DIV_NAME, NEW_CLOSURE_VALUE(operator_bool_div));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_MUL_NAME, NEW_CLOSURE_VALUE(operator_bool_mul));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_REM_NAME, NEW_CLOSURE_VALUE(operator_bool_rem));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_AND_NAME, NEW_CLOSURE_VALUE(operator_bool_and));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_OR_NAME,  NEW_CLOSURE_VALUE(operator_bool_or));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(operator_bool_cmp));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_bool_neg));
+	gravity_class_bind(gravity_class_bool, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(operator_bool_not));
+	
+	// STRING CLASS
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_ADD_NAME, NEW_CLOSURE_VALUE(operator_string_add));
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_SUB_NAME, NEW_CLOSURE_VALUE(operator_string_sub));
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_AND_NAME, NEW_CLOSURE_VALUE(operator_string_and));
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_OR_NAME,  NEW_CLOSURE_VALUE(operator_string_or));
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(operator_string_cmp));
+	gravity_class_bind(gravity_class_string, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_string_neg));
+	gravity_class_bind(gravity_class_string, "length", VALUE_FROM_OBJECT(computed_property(NULL, NEW_FUNCTION(string_length), NULL)));
+	
+	// FIBER CLASS
+	gravity_class_t *fiber_meta = gravity_class_get_meta(gravity_class_fiber);
+	gravity_class_bind(fiber_meta, "create", NEW_CLOSURE_VALUE(fiber_create));
+	gravity_class_bind(gravity_class_fiber, GRAVITY_INTERNAL_EXEC_NAME, NEW_CLOSURE_VALUE(fiber_exec));
+	gravity_class_bind(gravity_class_fiber, "try", NEW_CLOSURE_VALUE(fiber_try));
+	gravity_class_bind(fiber_meta, "yield", NEW_CLOSURE_VALUE(fiber_yield));
+	gravity_class_bind(gravity_class_fiber, "status", NEW_CLOSURE_VALUE(fiber_status));
+	gravity_class_bind(gravity_class_fiber, "abort", NEW_CLOSURE_VALUE(fiber_abort));
+	
+	// BASIC OPERATIONS added also to NULL CLASS (and UNDEFINED since they points to the same class)
+	// this is required because every variable is initialized by default to NULL
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_ADD_NAME, NEW_CLOSURE_VALUE(operator_null_add));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_SUB_NAME, NEW_CLOSURE_VALUE(operator_null_sub));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_DIV_NAME, NEW_CLOSURE_VALUE(operator_null_div));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_MUL_NAME, NEW_CLOSURE_VALUE(operator_null_mul));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_REM_NAME, NEW_CLOSURE_VALUE(operator_null_rem));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_AND_NAME, NEW_CLOSURE_VALUE(operator_null_and));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_OR_NAME,  NEW_CLOSURE_VALUE(operator_null_or));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_CMP_NAME, NEW_CLOSURE_VALUE(operator_null_cmp));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_NEG_NAME, NEW_CLOSURE_VALUE(operator_null_neg));
+	gravity_class_bind(gravity_class_null, GRAVITY_OPERATOR_NOT_NAME, NEW_CLOSURE_VALUE(operator_null_not));
+	#if GRAVITY_NULL_SILENT
+	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_EXEC_NAME, NEW_CLOSURE_VALUE(operator_null_silent));
+	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_LOAD_NAME, NEW_CLOSURE_VALUE(operator_null_silent));
+	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_STORE_NAME, NEW_CLOSURE_VALUE(operator_null_silent));
+	gravity_class_bind(gravity_class_null, GRAVITY_INTERNAL_NOTFOUND_NAME, NEW_CLOSURE_VALUE(operator_null_silent));
+	#endif
+	
+	// SYSTEM class
+	gravity_class_system = gravity_class_new_pair(NULL, GRAVITY_CLASS_SYSTEM_NAME, NULL, 0, 0);
+	gravity_class_t *system_meta = gravity_class_get_meta(gravity_class_system);
+	gravity_class_bind(system_meta, GRAVITY_SYSTEM_NANOTIME_NAME, NEW_CLOSURE_VALUE(system_nanotime));
+	gravity_class_bind(system_meta, GRAVITY_SYSTEM_PRINT_NAME, NEW_CLOSURE_VALUE(system_print));
+	gravity_class_bind(system_meta, GRAVITY_SYSTEM_PUT_NAME, NEW_CLOSURE_VALUE(system_put));
+
+	gravity_value_t value = VALUE_FROM_OBJECT(computed_property(NULL, NEW_FUNCTION(system_get), NEW_FUNCTION(system_set)));
+	gravity_class_bind(system_meta, "gcenabled", value);
+	gravity_class_bind(system_meta, "gcminthreshold", value);
+	gravity_class_bind(system_meta, "gcthreshold", value);
+	gravity_class_bind(system_meta, "gcratio", value);
+	
+	// INIT META
+	SETMETA_INITED(gravity_class_int);
+	SETMETA_INITED(gravity_class_float);
+	SETMETA_INITED(gravity_class_bool);
+	SETMETA_INITED(gravity_class_null);
+	SETMETA_INITED(gravity_class_string);
+	SETMETA_INITED(gravity_class_object);
+	SETMETA_INITED(gravity_class_function);
+	SETMETA_INITED(gravity_class_closure);
+	SETMETA_INITED(gravity_class_fiber);
+	SETMETA_INITED(gravity_class_class);
+	SETMETA_INITED(gravity_class_instance);
+	SETMETA_INITED(gravity_class_list);
+	SETMETA_INITED(gravity_class_map);
+	SETMETA_INITED(gravity_class_range);
+	SETMETA_INITED(gravity_class_upvalue);
+	SETMETA_INITED(gravity_class_system);
+	//SETMETA_INITED(gravity_class_module);
+	
+	mem_check(true);
+}
+
+void gravity_core_free (void) {
+	if (!core_inited) return;
+	
+	// check if others VM are still running
+	if (--refcount != 0) return;
+	
+	// this function should never be called
+	// it is just called when we need to internally check for memory leaks
+	
+	mem_check(false);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_int));
+	gravity_class_free_core(NULL, gravity_class_int);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_float));
+	gravity_class_free_core(NULL, gravity_class_float);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_bool));
+	gravity_class_free_core(NULL, gravity_class_bool);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_string));
+	gravity_class_free_core(NULL, gravity_class_string);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_null));
+	gravity_class_free_core(NULL, gravity_class_null);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_function));
+	gravity_class_free_core(NULL, gravity_class_function);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_closure));
+	gravity_class_free_core(NULL, gravity_class_closure);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_fiber));
+	gravity_class_free_core(NULL, gravity_class_fiber);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_instance));
+	gravity_class_free_core(NULL, gravity_class_instance);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_list));
+	gravity_class_free_core(NULL, gravity_class_list);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_map));
+	gravity_class_free_core(NULL, gravity_class_map);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_range));
+	gravity_class_free_core(NULL, gravity_class_range);
+	gravity_class_free_core(NULL, gravity_class_get_meta(gravity_class_upvalue));
+	gravity_class_free_core(NULL, gravity_class_upvalue);
+	
+	// before freeing the meta class we need to remove entries with duplicated functions
+	gravity_class_t *system_meta = gravity_class_get_meta(gravity_class_system);
+	{STATICVALUE_FROM_STRING(key, "gcminthreshold", strlen("gcminthreshold")); gravity_hash_remove(system_meta->htable, key);}
+	{STATICVALUE_FROM_STRING(key, "gcthreshold", strlen("gcthreshold")); gravity_hash_remove(system_meta->htable, key);}
+	{STATICVALUE_FROM_STRING(key, "gcratio", strlen("gcratio")); gravity_hash_remove(system_meta->htable, key);}
+	gravity_class_free_core(NULL, system_meta);
+	gravity_class_free_core(NULL, gravity_class_system);
+	
+	// object must be the last class to be freed
+	gravity_class_free_core(NULL, gravity_class_class);
+	gravity_class_free_core(NULL, gravity_class_object);
+	mem_check(true);
+	
+	gravity_class_int = NULL;
+	gravity_class_float = NULL;
+	gravity_class_bool = NULL;
+	gravity_class_string = NULL;
+	gravity_class_object = NULL;
+	gravity_class_null = NULL;
+	gravity_class_function = NULL;
+	gravity_class_closure = NULL;
+	gravity_class_fiber = NULL;
+	gravity_class_class = NULL;
+	gravity_class_instance = NULL;
+	gravity_class_list = NULL;
+	gravity_class_map = NULL;
+	gravity_class_range = NULL;
+	gravity_class_upvalue = NULL;
+	gravity_class_system = NULL;
+	gravity_class_module = NULL;
+	
+	core_inited = false;
+}
+
+uint32_t gravity_core_identifiers (const char ***id) {
+	static const char *list[] = {GRAVITY_CLASS_OBJECT_NAME, GRAVITY_CLASS_CLASS_NAME, GRAVITY_CLASS_BOOL_NAME, GRAVITY_CLASS_NULL_NAME,
+		GRAVITY_CLASS_INT_NAME, GRAVITY_CLASS_FLOAT_NAME, GRAVITY_CLASS_FUNCTION_NAME, GRAVITY_CLASS_FIBER_NAME, GRAVITY_CLASS_STRING_NAME,
+		GRAVITY_CLASS_INSTANCE_NAME, GRAVITY_CLASS_LIST_NAME, GRAVITY_CLASS_MAP_NAME, GRAVITY_CLASS_RANGE_NAME, GRAVITY_CLASS_SYSTEM_NAME,
+		GRAVITY_CLASS_CLOSURE_NAME, GRAVITY_CLASS_UPVALUE_NAME};
+	*id = list;
+	return (sizeof(list) / sizeof(const char *));
+}
+
+void gravity_core_register (gravity_vm *vm) {
+	gravity_core_init();
+	++refcount;
+	if (!vm) return;
+	
+	// register core classes inside VM
+	if (gravity_vm_ismini(vm)) return;
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_OBJECT_NAME, VALUE_FROM_OBJECT(gravity_class_object));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_CLASS_NAME, VALUE_FROM_OBJECT(gravity_class_class));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_BOOL_NAME, VALUE_FROM_OBJECT(gravity_class_bool));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_NULL_NAME, VALUE_FROM_OBJECT(gravity_class_null));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_INT_NAME, VALUE_FROM_OBJECT(gravity_class_int));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_FLOAT_NAME, VALUE_FROM_OBJECT(gravity_class_float));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_FUNCTION_NAME, VALUE_FROM_OBJECT(gravity_class_function));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_CLOSURE_NAME, VALUE_FROM_OBJECT(gravity_class_closure));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_FIBER_NAME, VALUE_FROM_OBJECT(gravity_class_fiber));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_STRING_NAME, VALUE_FROM_OBJECT(gravity_class_string));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_INSTANCE_NAME, VALUE_FROM_OBJECT(gravity_class_instance));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_LIST_NAME, VALUE_FROM_OBJECT(gravity_class_list));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_MAP_NAME, VALUE_FROM_OBJECT(gravity_class_map));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_RANGE_NAME, VALUE_FROM_OBJECT(gravity_class_range));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_UPVALUE_NAME, VALUE_FROM_OBJECT(gravity_class_upvalue));
+	gravity_vm_setvalue(vm, GRAVITY_CLASS_SYSTEM_NAME, VALUE_FROM_OBJECT(gravity_class_system));
+}
+
+bool gravity_iscore_class (gravity_class_t *c) {
+	return ((c == gravity_class_object) || (c == gravity_class_class) || (c == gravity_class_bool) ||
+			(c == gravity_class_null) || (c == gravity_class_int) || (c == gravity_class_float) ||
+			(c == gravity_class_function) || (c == gravity_class_fiber) || (c == gravity_class_string) ||
+			(c == gravity_class_instance) || (c == gravity_class_list) || (c == gravity_class_map) ||
+			(c == gravity_class_range) || (c == gravity_class_system) || (c == gravity_class_closure) ||
+			(c == gravity_class_upvalue));
+}

+ 26 - 0
src/runtime/gravity_core.h

@@ -0,0 +1,26 @@
+//
+//  gravity_core.h
+//  gravity
+//
+//  Created by Marco Bambini on 10/01/15.
+//  Copyright (c) 2015 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_CORE__
+#define __GRAVITY_CORE__
+
+#include "gravity_vm.h"
+
+// core functions
+void gravity_core_register (gravity_vm *vm);
+bool gravity_iscore_class (gravity_class_t *c);
+void gravity_core_free (void);
+uint32_t gravity_core_identifiers (const char ***id);
+
+// conversion functions
+gravity_value_t convert_value2int (gravity_vm *vm, gravity_value_t v);
+gravity_value_t convert_value2float (gravity_vm *vm, gravity_value_t v);
+gravity_value_t convert_value2bool (gravity_vm *vm, gravity_value_t v);
+gravity_value_t convert_value2string (gravity_vm *vm, gravity_value_t v);
+
+#endif

+ 2044 - 0
src/runtime/gravity_vm.c

@@ -0,0 +1,2044 @@
+//
+//  gravity_vm.c
+//  gravity
+//
+//  Created by Marco Bambini on 11/11/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_hash.h"
+#include "gravity_array.h"
+#include "gravity_debug.h"
+#include "gravity_macros.h"
+#include "gravity_vm.h"
+#include "gravity_core.h"
+#include "gravity_opcodes.h"
+#include "gravity_memory.h"
+#include "gravity_vmmacros.h"
+#include "gravity_json.h"
+
+// MARK: Internals -
+static void gravity_gc_cleanup (gravity_vm *vm);
+static void gravity_gc_transfer (gravity_vm *vm, gravity_object_t *obj);
+static bool vm_set_superclass (gravity_vm *vm, gravity_object_t *obj);
+static void gravity_gc_transform (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t *value, void *data);
+
+// Internal cache to speed up common operations lookup
+static uint32_t cache_refcount = 0;
+static gravity_value_t cache[GRAVITY_VTABLE_SIZE];
+
+// Opaque VM struct
+struct gravity_vm {
+	gravity_hash_t		*context;							// context hash table
+	gravity_delegate_t	*delegate;							// registered runtime delegate
+	gravity_fiber_t		*fiber;								// current fiber
+	void				*data;								// custom data optionally set by the user
+	uint32_t			pc;									// program counter
+	double				time;								// useful timer for the main function
+	bool				aborted;							// set when VM has generated a runtime error
+	
+	// anonymous names
+	uint32_t			nanon;								// counter for anonymous classes (used in object_bind)
+	char				temp[64];							// temprary buffer used for anonymous names generator
+	
+	// callbacks
+	vm_transfer_cb		transfer;							// function called each time a gravity_object_t is allocated
+	vm_cleanup_cb		cleanup;							// function called when VM must be cleaned-up
+	vm_filter_cb		filter;								// function called to filter objects in the cleanup process
+	
+	// garbage collector
+	bool				gcenabled;							// flag to enable/disable garbage collector
+	gravity_int_t		memallocated;						// total number of allocated memory
+	gravity_object_t	*gchead;							// head of garbage collected objects
+	gravity_int_t		gcminthreshold;						// minimum GC threshold size to avoid spending too much time in GC
+	gravity_int_t		gcthreshold;						// memory required to trigger a GC
+	gravity_float_t		gcratio;							// ratio used in automatic recomputation of the new gcthreshold value
+	gravity_int_t		gccount;							// number of objects into GC
+	gravity_object_r	graylist;							// array of collected objects while GC is in process (gray list)
+	gravity_object_r	gcsave;								// array of temp objects that need to be saved from GC
+	
+	// internal stats fields
+	#if GRAVITY_VM_STATS
+	uint32_t			nfrealloc;							// to check how many frames reallocation occurred
+	uint32_t			nsrealloc;							// to check how many stack reallocation occurred
+	uint32_t			nstat[GRAVITY_LATEST_OPCODE];		// internal used to collect opcode usage stats
+	double				tstat[GRAVITY_LATEST_OPCODE];		// internal used to collect microbenchmarks
+	nanotime_t			t;									// internal timer
+	#endif
+};
+
+// MARK: -
+
+static void report_runtime_error (gravity_vm *vm, error_type_t error_type, const char *format, ...) {
+	char		buffer[1024];
+	va_list		arg;
+	
+	if (vm->aborted) return;
+	vm->aborted = true;
+	
+	if (format) {
+		va_start (arg, format);
+		vsnprintf(buffer, sizeof(buffer), format, arg);
+		va_end (arg);
+	}
+	
+	gravity_error_callback errorf = NULL;
+	if (vm->delegate) errorf = ((gravity_delegate_t *)vm->delegate)->error_callback;
+	
+	if (errorf) {
+		void *data = ((gravity_delegate_t *)vm->delegate)->xdata;
+		errorf(error_type, buffer, ERROR_DESC_NONE, data);
+	} else {
+		printf("%s\n", buffer);
+		fflush(stdout);
+	}
+}
+
+static void gravity_cache_setup (void) {
+	++cache_refcount;
+	
+	// NULL here because I do not want them to be in the GC
+	mem_check(false);
+	cache[GRAVITY_NOTFOUND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_NOTFOUND_NAME);
+	cache[GRAVITY_ADD_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_ADD_NAME);
+	cache[GRAVITY_SUB_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_SUB_NAME);
+	cache[GRAVITY_DIV_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_DIV_NAME);
+	cache[GRAVITY_MUL_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_MUL_NAME);
+	cache[GRAVITY_REM_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_REM_NAME);
+	cache[GRAVITY_AND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_AND_NAME);
+	cache[GRAVITY_OR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_OR_NAME);
+	cache[GRAVITY_CMP_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_CMP_NAME);
+	cache[GRAVITY_EQQ_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_EQQ_NAME);
+	cache[GRAVITY_ISA_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_ISA_NAME);
+	cache[GRAVITY_MATCH_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_MATCH_NAME);
+	cache[GRAVITY_NEG_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_NEG_NAME);
+	cache[GRAVITY_NOT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_NOT_NAME);
+	cache[GRAVITY_LSHIFT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_LSHIFT_NAME);
+	cache[GRAVITY_RSHIFT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_RSHIFT_NAME);
+	cache[GRAVITY_BAND_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BAND_NAME);
+	cache[GRAVITY_BOR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BOR_NAME);
+	cache[GRAVITY_BXOR_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BXOR_NAME);
+	cache[GRAVITY_BNOT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_OPERATOR_BNOT_NAME);
+	cache[GRAVITY_LOAD_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOAD_NAME);
+	cache[GRAVITY_LOADS_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOADS_NAME);
+	cache[GRAVITY_LOADAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_LOADAT_NAME);
+	cache[GRAVITY_STORE_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_STORE_NAME);
+	cache[GRAVITY_STOREAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_STOREAT_NAME);
+	cache[GRAVITY_INT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_INT_NAME);
+	cache[GRAVITY_FLOAT_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_FLOAT_NAME);
+	cache[GRAVITY_BOOL_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_BOOL_NAME);
+	cache[GRAVITY_STRING_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_CLASS_STRING_NAME);
+	cache[GRAVITY_EXEC_INDEX] = VALUE_FROM_CSTRING(NULL, GRAVITY_INTERNAL_EXEC_NAME);
+	mem_check(true);
+}
+
+static void gravity_cache_free (void) {
+	--cache_refcount;
+	if (cache_refcount > 0) return;
+	
+	mem_check(false);
+	for (uint32_t index = 0; index <GRAVITY_VTABLE_SIZE; ++index) {
+		gravity_value_free(NULL, cache[index]);
+	}
+	mem_check(true);
+}
+
+gravity_value_t gravity_vm_keyindex (gravity_vm *vm, uint32_t index) {
+	#pragma unused (vm)
+	return cache[index];
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+static void gravity_stack_dump (gravity_fiber_t *fiber) {
+	uint32_t index = 0;
+	for (gravity_value_t *stack = fiber->stack; stack < fiber->stacktop; ++stack) {
+		printf("[%05d]\t", index++);
+		if (!stack->isa) {printf("\n"); continue;}
+		gravity_value_dump(*stack, NULL, 0);
+	}
+	if (index) printf("\n\n");
+}
+#pragma clang diagnostic pop
+
+static inline gravity_callframe_t *gravity_new_callframe (gravity_vm *vm, gravity_fiber_t *fiber) {
+	#pragma unused(vm)
+	
+	// check if there are enought slots in the call frame and optionally create new cframes
+	if (fiber->framesalloc - fiber->nframes < 1) {
+		uint32_t new_size = fiber->framesalloc * 2;
+		fiber->frames = (gravity_callframe_t *) mem_realloc(fiber->frames, sizeof(gravity_callframe_t) * new_size);
+		fiber->framesalloc = new_size;
+		STAT_FRAMES_REALLOCATED(vm);
+	}
+	
+	// update frames counter
+	++fiber->nframes;
+	
+	// return first available cframe (-1 because I just updated the frames counter)
+	return &fiber->frames[fiber->nframes - 1];
+}
+
+static inline void gravity_check_stack (gravity_vm *vm, gravity_fiber_t *fiber, uint32_t rneeds, gravity_value_t **stackstart) {
+	#pragma unused(vm)
+	
+	// update stacktop pointer before a call
+	fiber->stacktop += rneeds;
+	
+	// check stack size
+	uint32_t stack_size = (uint32_t)(fiber->stacktop - fiber->stack);
+	uint32_t stack_needed = MAXNUM(stack_size + rneeds, DEFAULT_MINSTACK_SIZE);
+	if (fiber->stackalloc >= stack_needed) return;
+	
+	// perform stack reallocation
+	uint32_t new_size = power_of2_ceil(fiber->stackalloc + stack_needed);
+	gravity_value_t *old_stack = fiber->stack;
+	fiber->stack = (gravity_value_t *) mem_realloc(fiber->stack, sizeof(gravity_value_t) * new_size);
+	fiber->stackalloc = new_size;
+	STAT_STACK_REALLOCATED(vm);
+	
+	// check if reallocation moved the stack
+	if (fiber->stack == old_stack) return;
+	
+	// re-compute ptr offset
+	ptrdiff_t offset = (ptrdiff_t)(fiber->stack - old_stack);
+	
+	// adjust stack pointer for each call frame
+	for (uint32_t i=0; i < fiber->nframes; ++i) {
+		fiber->frames[i].stackstart += offset;
+	}
+	
+	// adjust upvalues ptr offeset
+	gravity_upvalue_t* upvalue = fiber->upvalues;
+	while (upvalue) {
+		upvalue->value += offset;
+		upvalue = upvalue->next;
+	}
+	
+	// adjust fiber stack pointer
+	fiber->stacktop += offset;
+	
+	// stack is changed so update currently used stackstart
+	*stackstart += offset;
+}
+
+static gravity_upvalue_t *gravity_capture_upvalue (gravity_vm *vm, gravity_fiber_t *fiber, gravity_value_t *value) {
+	// closures and upvalues implementation inspired by Lua and Wren
+ 	// fiber->upvalues list must be ORDERED by the level of the corrisponding variables in the stack starting from top
+	
+	// if upvalues is empty then create it
+	if (!fiber->upvalues) {
+		fiber->upvalues = gravity_upvalue_new(vm, value);
+		return fiber->upvalues;
+	}
+	
+	// scan list looking for upvalue first (keeping track of the order)
+	gravity_upvalue_t *prevupvalue = NULL;
+	gravity_upvalue_t *upvalue = fiber->upvalues;
+	while (upvalue && upvalue->value > value) {
+		prevupvalue = upvalue;
+		upvalue = upvalue->next;
+	}
+	
+	// if upvalue found then re-use it
+	if (upvalue != NULL && upvalue->value == value) return upvalue;
+	
+	// upvalue not found in list so creates a new one and add it in list ORDERED
+	gravity_upvalue_t *newvalue = gravity_upvalue_new(vm, value);
+	if (prevupvalue == NULL) fiber->upvalues = newvalue;
+	else prevupvalue->next = newvalue;
+	
+	// returns newly created upvalue
+	newvalue->next = upvalue;
+	return newvalue;
+}
+
+static void gravity_close_upvalues (gravity_fiber_t *fiber, gravity_value_t *level) {
+	while (fiber->upvalues != NULL && fiber->upvalues->value >= level) {
+		gravity_upvalue_t *upvalue = fiber->upvalues;
+		
+		// move the value into the upvalue itself and point the upvalue to it
+		upvalue->closed = *upvalue->value;
+		upvalue->value = &upvalue->closed;
+		
+		// remove it from the open upvalue list
+		fiber->upvalues = upvalue->next;
+	}
+}
+
+static void gravity_vm_loadclass (gravity_vm *vm, gravity_class_t *c) {
+	// convert func to closure for class and for its meta class
+	gravity_hash_transform(c->htable, gravity_gc_transform, (void *)vm);
+	gravity_class_t *meta = gravity_class_get_meta(c);
+	gravity_hash_transform(meta->htable, gravity_gc_transform, (void *)vm);
+}
+
+// MARK: -
+
+static bool gravity_vm_exec (gravity_vm *vm) {
+	DECLARE_DISPATCH_TABLE;
+	
+	gravity_fiber_t				*fiber = vm->fiber;			// current fiber
+	gravity_delegate_t			*delegate = vm->delegate;	// current delegate
+	gravity_callframe_t			*frame;						// current executing frame
+	gravity_function_t			*func;						// current executing function
+	gravity_value_t				*stackstart;				// SP => stack pointer
+	register uint32_t			*ip;						// IP => instruction pointer
+	register uint32_t			inst;						// IR => instruction register
+	register opcode_t			op;							// OP => opcode register
+	
+	// load current callframe
+	LOAD_FRAME();
+	DEBUG_CALL("Executing", func);
+	
+	// sanity check
+	if ((ip == NULL) || (!func->bytecode) || (func->ninsts == 0)) return true;
+	
+	DEBUG_STACK();
+	
+	while (1) {
+		INTERPRET_LOOP {
+			
+			// MARK: - OPCODES -
+			// MARK: NOP
+			CASE_CODE(NOP): {
+				DEBUG_VM("NOP");
+				DISPATCH();
+			}
+			
+			// MARK: MOVE
+			CASE_CODE(MOVE): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				DEBUG_VM("MOVE %d %d", r1, r2);
+				
+				SETVALUE(r1, STACK_GET(r2));
+				DISPATCH();
+			}
+			
+			// MARK: LOAD
+			// MARK: LOADS
+			// MARK: LOADAT
+			CASE_CODE(LOAD):
+			CASE_CODE(LOADS):
+			CASE_CODE(LOADAT):{
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DEBUG_VM("%s %d %d %d", (op == LOAD) ? "LOAD" : ((op == LOADAT) ? "LOADAT" : "LOADS"), r1, r2, r3);
+				
+				// r1 result
+				// r2 target
+				// r3 key
+				
+				DEFINE_STACK_VARIABLE(v2,r2);
+				DEFINE_INDEX_VARIABLE(v3,r3);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, (op == LOAD) ? GRAVITY_LOAD_INDEX : ((op == LOADAT) ? GRAVITY_LOADAT_INDEX : GRAVITY_LOADS_INDEX), rwin);
+				
+				// call closure (do not use a macro here because we want to handle both the bridged and special cases)
+				STORE_FRAME();
+				execute_load_function:
+				switch(closure->f->tag) {
+					case EXEC_TYPE_NATIVE: {
+						PUSH_FRAME(closure, &stackstart[rwin], r1, 2);
+					} break;
+						
+					case EXEC_TYPE_INTERNAL: {
+						SETVALUE(r1, VALUE_FROM_NULL);
+						if (!closure->f->internal(vm, &stackstart[rwin], 2, r1)) {
+							
+							// check for special getter trick
+							if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
+								closure = VALUE_AS_CLOSURE(STACK_GET(r1));
+								SETVALUE(r1, VALUE_FROM_NULL);
+								goto execute_load_function;
+							}
+							
+							// check for special fiber error
+							fiber = vm->fiber;
+							if (fiber == NULL) return true;
+							if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+						}
+					} break;
+						
+					case EXEC_TYPE_BRIDGED: {
+						ASSERT(delegate->bridge_getvalue, "bridge_getvalue delegate callback is mandatory");
+						if (!delegate->bridge_getvalue(vm, closure->f->xdata, v2, VALUE_AS_CSTRING(v3), r1)) {
+							if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+						}
+					} break;
+						
+					case EXEC_TYPE_SPECIAL: {
+						if (!closure->f->special[EXEC_TYPE_SPECIAL_GETTER]) RUNTIME_ERROR("Missing special getter function for property %s", VALUE_AS_CSTRING(v3));
+						closure = closure->f->special[EXEC_TYPE_SPECIAL_GETTER];
+						goto execute_load_function;
+					} break;
+				}
+				LOAD_FRAME();
+				SYNC_STACKTOP(closure, _rneed);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: LOADI
+			CASE_CODE(LOADI): {
+				OPCODE_GET_ONE8bit_SIGN_ONE17bit(inst, const uint32_t r1, const int32_t value);
+				DEBUG_VM("LOADI %d %d", r1, value);
+				
+				SETVALUE_INT(r1, value);
+				DISPATCH();
+			}
+			
+			// MARK: LOADK
+			CASE_CODE(LOADK): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t index);
+				DEBUG_VM("LOADK %d %d", r1, index);
+				
+				// constant pool case
+				if (index < CPOOL_INDEX_MAX) {
+					gravity_value_t v = gravity_function_cpool_get(func, index);
+					SETVALUE(r1, v);
+					DISPATCH();
+				}
+				
+				// special value case
+				switch (index) {
+					case CPOOL_VALUE_SUPER: {
+						// get super class from STACK_GET(0) which is self
+						gravity_class_t *super = gravity_value_getsuper(STACK_GET(0));
+						SETVALUE(r1, (super) ? VALUE_FROM_OBJECT(super) : VALUE_FROM_NULL);
+					} break;
+					case CPOOL_VALUE_ARGUMENTS: SETVALUE(r1, VALUE_FROM_OBJECT(frame->args)); break;
+					case CPOOL_VALUE_NULL: SETVALUE(r1, VALUE_FROM_NULL); break;
+					case CPOOL_VALUE_UNDEFINED: SETVALUE(r1, VALUE_FROM_UNDEFINED); break;
+					case CPOOL_VALUE_TRUE: SETVALUE(r1, VALUE_FROM_TRUE); break;
+					case CPOOL_VALUE_FALSE: SETVALUE(r1, VALUE_FROM_FALSE); break;
+					case CPOOL_VALUE_FUNC: SETVALUE(r1, VALUE_FROM_OBJECT(frame->closure)); break;
+					default: RUNTIME_ERROR("Unknown LOADK index"); break;
+				}
+				DISPATCH();
+			}
+			
+			// MARK: LOADG
+			CASE_CODE(LOADG): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
+				DEBUG_VM("LOADG %d %d", r1, index);
+				
+				gravity_value_t key = gravity_function_cpool_get(func, index);
+				gravity_value_t *v = gravity_hash_lookup(vm->context, key);
+				if (!v) RUNTIME_ERROR("Unable to find object %s", VALUE_AS_CSTRING(key));
+				
+				SETVALUE(r1, *v);
+				DISPATCH();
+			}
+			
+			// MARK: LOADU
+			CASE_CODE(LOADU): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				DEBUG_VM("LOADU %d %d", r1, r2);
+				
+				gravity_upvalue_t *upvalue = frame->closure->upvalue[r2];
+				SETVALUE(r1, *upvalue->value);
+				DISPATCH();
+			}
+			
+			// MARK: STORE
+			// MARK: STOREAT
+			CASE_CODE(STORE):
+			CASE_CODE(STOREAT):{
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DEBUG_VM("%s %d %d %d", (op == STORE) ? "STORE" : "STOREAT", r1, r2,r3);
+				
+				// r1 value
+				// r2 target
+				// r3 key
+				
+				DEFINE_STACK_VARIABLE(v1,r1);
+				DEFINE_STACK_VARIABLE(v2,r2);
+				DEFINE_INDEX_VARIABLE(v3,r3);
+				
+				// prepare function call
+				PREPARE_FUNC_CALL3(closure, v2, v3, v1, (op == STORE) ? GRAVITY_STORE_INDEX : GRAVITY_STOREAT_INDEX, rwin);
+				
+				// call function f (do not use a macro here because we want to handle both the bridged and special cases)
+				STORE_FRAME();
+				execute_store_function:
+				switch(closure->f->tag) {
+					case EXEC_TYPE_NATIVE: {
+						SETVALUE(rwin+1, v1);
+						PUSH_FRAME(closure, &stackstart[rwin], r1, 2);
+					} break;
+						
+					case EXEC_TYPE_INTERNAL: {
+						SETVALUE(r1, VALUE_FROM_NULL);
+						if (!closure->f->internal(vm, &stackstart[rwin], 2, r1)) {
+							// check for special getter trick
+							if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
+								closure = VALUE_AS_CLOSURE(STACK_GET(r1));
+								SETVALUE(r1, VALUE_FROM_NULL);
+								goto execute_store_function;
+							}
+							
+							// check for special fiber error
+							fiber = vm->fiber;
+							if (fiber == NULL) return true;
+							if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+						}
+					} break;
+					
+					case EXEC_TYPE_BRIDGED: {
+						ASSERT(delegate->bridge_setvalue, "bridge_setvalue delegate callback is mandatory");
+						if (!delegate->bridge_setvalue(vm, closure->f->xdata, v2, VALUE_AS_CSTRING(v3), v1)) {
+							if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+						}
+					} break;
+						
+					case EXEC_TYPE_SPECIAL: {
+						if (!closure->f->special[EXEC_TYPE_SPECIAL_SETTER]) RUNTIME_ERROR("Missing special setter function for property %s", VALUE_AS_CSTRING(v3));
+						closure = closure->f->special[EXEC_TYPE_SPECIAL_SETTER];
+						goto execute_store_function;
+					} break;
+				}
+				LOAD_FRAME();
+				SYNC_STACKTOP(closure, _rneed);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: STOREG
+			CASE_CODE(STOREG): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
+				DEBUG_VM("STOREG %d %d", r1, index);
+				
+				gravity_value_t key = gravity_function_cpool_get(func, index);
+				gravity_value_t v = STACK_GET(r1);
+				gravity_hash_insert(vm->context, key, v);
+				DISPATCH();
+			}
+			
+			// MARK: STOREU
+			CASE_CODE(STOREU): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				DEBUG_VM("STOREU %d %d", r1, r2);
+				
+				gravity_upvalue_t *upvalue = frame->closure->upvalue[r2];
+				*upvalue->value = STACK_GET(r1);
+				DISPATCH();
+			}
+			
+			// MARK: - EQUALITY
+			CASE_CODE(EQQ):
+			CASE_CODE(NEQQ): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1,r2,r3);
+				
+				// get registers
+				DEFINE_STACK_VARIABLE(v2,r2);
+				DEFINE_STACK_VARIABLE(v3,r3);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_EQQ_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(EQQ, closure, r1, 2, rwin);
+				
+				// get result of the EQQ selector
+				register gravity_int_t result = STACK_GET(r1).n;
+				
+				// save result (NEQQ is not EQQ)
+				SETVALUE_BOOL(r1, (op == EQQ) ? result : !result);
+				
+				DISPATCH();
+			}
+			
+			CASE_CODE(ISA):
+			CASE_CODE(MATCH): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// get registers
+				DEFINE_STACK_VARIABLE(v2,r2);
+				DEFINE_STACK_VARIABLE(v3,r3);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, (op == ISA) ? GRAVITY_ISA_INDEX : GRAVITY_MATCH_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(ISA, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: COMPARISON
+			CASE_CODE(LT):
+			CASE_CODE(GT):
+			CASE_CODE(EQ):
+			CASE_CODE(LEQ):
+			CASE_CODE(GEQ):
+			CASE_CODE(NEQ): {
+				
+				// decode operation
+				DECODE_BINARY_OPERATION(r1,r2,r3);
+				
+				// check fast comparison only if both values are boolean OR if one of them is undefined
+				DEFINE_STACK_VARIABLE(v2,r2);
+				DEFINE_STACK_VARIABLE(v3,r3);
+				if ((VALUE_ISA_BOOL(v2) && (VALUE_ISA_BOOL(v3))) || (VALUE_ISA_UNDEFINED(v2) || (VALUE_ISA_UNDEFINED(v3)))) {
+					register gravity_int_t eq_result = (v2.isa == v3.isa) && (v2.n == v3.n);
+					SETVALUE(r1, VALUE_FROM_BOOL((op == EQ) ? eq_result : !eq_result));
+					DISPATCH();
+				} else if (VALUE_ISA_INT(v2) && VALUE_ISA_INT(v3)) {
+					// INT optimization expecially useful in loops
+					if (v2.n == v3.n) SETVALUE(r1, VALUE_FROM_INT(0));
+					else SETVALUE(r1, VALUE_FROM_INT((v2.n > v3.n) ? 1 : -1));
+				} else {
+					// prepare function call for binary operation
+					PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_CMP_INDEX, rwin);
+					
+					// call function f
+					CALL_FUNC(CMP, closure, r1, 2, rwin);
+				}
+				
+				// compare returns 0 if v1 and v2 are equals, 1 if v1>v2 and -1 if v1<v2
+				register gravity_int_t result = STACK_GET(r1).n;
+				
+				switch(op) {
+					case LT: SETVALUE_BOOL(r1, result < 0); break;
+					case GT: SETVALUE_BOOL(r1, result > 0); break;
+					case EQ: SETVALUE_BOOL(r1, result == 0); break;
+					case LEQ: SETVALUE_BOOL(r1, result <= 0); break;
+					case GEQ: SETVALUE_BOOL(r1, result >= 0); break;
+					case NEQ: SETVALUE_BOOL(r1, result != 0); break;
+					default: assert(0);
+				}
+				
+				// optimize the case where after a comparison there is a JUMPF instruction (usually in a loop)
+				uint32_t inext = *ip++;
+				if ((STACK_GET(r1).n == 0) && (OPCODE_GET_OPCODE(inext) == JUMPF)) {
+					OPCODE_GET_LAST18bit(inext, int32_t value);
+					DEBUG_VM("JUMPF %d %d", (int)result, value);
+					ip = COMPUTE_JUMP(value); // JUMP is an absolute value
+					DISPATCH();
+				}
+				
+				// JUMPF not executed so I need to go back in instruction pointer
+				--ip;
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: - BIT OPERATORS -
+			// MARK: LSHIFT
+			CASE_CODE(LSHIFT): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bit math operation first (only if both v2 and v3 are int)
+				CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, <<);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_LSHIFT_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(LSHIFT, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: RSHIFT
+			CASE_CODE(RSHIFT): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bit math operation first (only if both v2 and v3 are int)
+				CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, >>);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_RSHIFT_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(RSHIFT, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: BAND
+			CASE_CODE(BAND): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bit math operation first (only if both v2 and v3 are int)
+				CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, &);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BAND_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(BAND, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: BOR
+			CASE_CODE(BOR): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bit math operation first (only if both v2 and v3 are int)
+				CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, |);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BOR_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(BOR, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: BXOR
+			CASE_CODE(BXOR):{
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bit math operation first (only if both v2 and v3 are int)
+				CHECK_FAST_BINARY_BIT(r1, r2, r3, v2, v3, ^);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_BXOR_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(BXOR, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: - BINARY OPERATORS -
+			// MARK: ADD
+			CASE_CODE(ADD): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast math operation first (only in case of int and float)
+				CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, +, NO_CHECK);
+				
+				// fast math operation cannot be performed so let's try with a regular call
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_ADD_INDEX, rwin);
+								
+				// call function f
+				CALL_FUNC(ADD, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: SUB
+			CASE_CODE(SUB): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast math operation first (only in case of int and float)
+				CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, -, NO_CHECK);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_SUB_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(SUB, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: DIV
+			CASE_CODE(DIV): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast math operation first  (only in case of int and float)
+				// a special check macro is added in order to check for divide by zero cases
+				CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, /, CHECK_ZERO(v3));
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_DIV_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(DIV, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: MUL
+			CASE_CODE(MUL): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast math operation first (only in case of int and float)
+				CHECK_FAST_BINARY_MATH(r1, r2, r3, v2, v3, *, NO_CHECK);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_MUL_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(MUL, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: REM
+			CASE_CODE(REM): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast math operation first (only in case of int and float)
+				CHECK_FAST_BINARY_REM(r1, r2, r3, v2, v3);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_REM_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(REM, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: AND
+			CASE_CODE(AND): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bool operation first (only if both are bool)
+				CHECK_FAST_BINARY_BOOL(r1, r2, r3, v2, v3, &&);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_AND_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(AND, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: OR
+			CASE_CODE(OR): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				
+				// check fast bool operation first (only if both are bool)
+				CHECK_FAST_BINARY_BOOL(r1, r2, r3, v2, v3, ||);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL2(closure, v2, v3, GRAVITY_OR_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(OR, closure, r1, 2, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: - UNARY OPERATORS -
+			// MARK: NEG
+			CASE_CODE(NEG): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				#pragma unused(r3)
+				
+				// check fast bool operation first (only if it is int or float)
+				CHECK_FAST_UNARY_MATH(r1, r2, v2, -);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL1(closure, v2, GRAVITY_NEG_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(NEG, closure, r1, 1, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: NOT
+			CASE_CODE(NOT): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				#pragma unused(r3)
+				
+				// check fast bool operation first  (only if it is bool)
+				CHECK_FAST_UNARY_BOOL(r1, r2, v2, !);
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL1(closure, v2, GRAVITY_NOT_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(NOT, closure, r1, 1, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: BNOT
+			CASE_CODE(BNOT): {
+				// decode operation
+				DECODE_BINARY_OPERATION(r1, r2, r3);
+				#pragma unused(r3)
+				
+				// check fast int operation first
+				DEFINE_STACK_VARIABLE(v2,r2);
+				// ~v2.n == -v2.n-1
+				if (VALUE_ISA_INT(v2)) {SETVALUE(r1, VALUE_FROM_INT(~v2.n)); DISPATCH();}
+				
+				// prepare function call for binary operation
+				PREPARE_FUNC_CALL1(closure, v2, GRAVITY_BNOT_INDEX, rwin);
+				
+				// call function f
+				CALL_FUNC(BNOT, closure, r1, 1, rwin);
+				
+				// continue execution
+				DISPATCH();
+			}
+			
+			// MARK: -
+			// MARK: JUMPF
+			CASE_CODE(JUMPF): {
+				// JUMPF like JUMP is an absolute value
+				OPCODE_GET_ONE8bit_FLAG_ONE17bit(inst, const uint32_t r1, const uint32_t flag, const int32_t value);
+				DEBUG_VM("JUMPF %d %d (flag %d)", r1, value, flag);
+				
+				if (flag) {
+					// if flag is set then ONLY boolean values must be checked (compiler must guarantee this condition)
+					// this is necessary in a FOR loop over numeric values (with an iterator) where otherwise number 0
+					// could be computed as a false condition
+					if ((VALUE_ISA_BOOL(STACK_GET(r1))) && (GETVALUE_INT(STACK_GET(r1)) == 0)) ip = COMPUTE_JUMP(value);
+					DISPATCH();
+				}
+				
+				// no flag set so convert r1 to BOOL
+				DEFINE_STACK_VARIABLE(v1, r1);
+				
+				// common NULL/UNDEFINED/BOOL/INT/FLOAT/STRING cases
+				// NULL/UNDEFINED	=>	no check
+				// BOOL/INT			=>	check n
+				// FLOAT			=>	check f
+				// STRING			=>	check len
+				if (VALUE_ISA_NULL(v1) || (VALUE_ISA_UNDEFINED(v1))) {ip = COMPUTE_JUMP(value);}
+				else if (VALUE_ISA_BOOL(v1) || (VALUE_ISA_INT(v1))) {if (GETVALUE_INT(v1) == 0) ip = COMPUTE_JUMP(value);}
+				else if (VALUE_ISA_FLOAT(v1)) {if (GETVALUE_FLOAT(v1) == 0.0) ip = COMPUTE_JUMP(value);}
+				else if (VALUE_ISA_STRING(v1)) {if (VALUE_AS_STRING(v1)->len == 0) ip = COMPUTE_JUMP(value);}
+				else {
+					// no common case so check if it implements the Bool command
+					gravity_closure_t *closure = (gravity_closure_t *)gravity_class_lookup_closure(gravity_value_getclass(v1), cache[GRAVITY_BOOL_INDEX]);
+					
+					// if no closure is found then object is considered TRUE
+					if (closure) {
+						// prepare func call
+						uint32_t rwin = FN_COUNTREG(func, frame->nargs);
+						uint32_t _rneed = FN_COUNTREG(closure->f, 1);
+						gravity_check_stack(vm, fiber, _rneed, &stackstart);
+						SETVALUE(rwin, v1);
+						
+						// call func and check result
+						CALL_FUNC(JUMPF, closure, rwin, 2, rwin);
+						gravity_int_t result = STACK_GET(rwin).n;
+						
+						// re-compute ip ONLY if false
+						if (!result) ip = COMPUTE_JUMP(value);
+					}
+				}
+				DISPATCH();
+			}
+			
+			// MARK: JUMP
+			CASE_CODE(JUMP): {
+				OPCODE_GET_ONE26bit(inst, const uint32_t value);
+				DEBUG_VM("JUMP %d", value);
+				
+				ip = COMPUTE_JUMP(value); // JUMP is an absolute value
+				DISPATCH();
+			}
+			
+			// MARK: CALL
+			CASE_CODE(CALL): {
+				// CALL A B C => R(A) = B(B+1...B+C)
+				OPCODE_GET_THREE8bit(inst, const uint32_t r1, const uint32_t r2, register uint32_t r3);
+				DEBUG_VM("CALL %d %d %d", r1, r2, r3);
+				
+				DEBUG_STACK();
+				
+				// r1 is the destination register
+				// r2 is the register which contains the callable object
+				// r3 is the number of arguments (nparams)
+				
+				// register window
+				const uint32_t rwin = r2 + 1;
+				
+				// object to call
+				gravity_value_t v = STACK_GET(r2);
+				
+				// closure to call
+				gravity_closure_t *closure = NULL;
+				
+				if (VALUE_ISA_CLOSURE(v)) {
+					closure = VALUE_AS_CLOSURE(v);
+				} else {
+					// check for exec closure inside object
+					closure = (gravity_closure_t *)gravity_class_lookup_closure(gravity_value_getclass(v), cache[GRAVITY_EXEC_INDEX]);
+				}
+				
+				// sanity check
+				if (!closure) RUNTIME_ERROR("Unable to call object (in function %s)", func->identifier);
+				
+				// check stack size
+				uint32_t _rneed = FN_COUNTREG(closure->f, r3);
+				gravity_check_stack(vm, fiber, _rneed, &stackstart);
+				
+				// if less arguments are passed then fill the holes with UNDEFINED values
+				while (r3 < closure->f->nparams) {
+					SETVALUE(rwin+r3, VALUE_FROM_UNDEFINED);
+					++r3;
+				}
+				
+				DEBUG_STACK();
+				
+				// execute function
+				STORE_FRAME();
+				execute_call_function:
+				switch(closure->f->tag) {
+					case EXEC_TYPE_NATIVE: {
+						PUSH_FRAME(closure, &stackstart[rwin], r1, r3);
+					} break;
+						
+					case EXEC_TYPE_INTERNAL: {
+						SETVALUE(r1, VALUE_FROM_NULL);
+						if (!closure->f->internal(vm, &stackstart[rwin], r3, r1)) {
+							// check for special getter trick
+							if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {
+								closure = VALUE_AS_CLOSURE(STACK_GET(r1));
+								SETVALUE(r1, VALUE_FROM_NULL);
+								goto execute_call_function;
+							}
+							
+							// check for special fiber error
+							fiber = vm->fiber;
+							if (fiber == NULL) return true;
+							if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+						}
+					} break;
+						
+					case EXEC_TYPE_BRIDGED: {
+						bool result;
+						if (VALUE_ISA_CLASS(v)) {
+							ASSERT(delegate->bridge_initinstance, "bridge_initinstance delegate callback is mandatory");
+							gravity_instance_t *instance = (gravity_instance_t *)VALUE_AS_OBJECT(stackstart[rwin]);
+							result = delegate->bridge_initinstance(vm, closure->f->xdata, instance, &stackstart[rwin], r3);
+							SETVALUE(r1, VALUE_FROM_OBJECT(instance));
+						} else {
+							ASSERT(delegate->bridge_execute, "bridge_execute delegate callback is mandatory");
+							result = delegate->bridge_execute(vm, closure->f->xdata, &stackstart[rwin], r3, r1);
+						}
+						if (!result && fiber->error) RUNTIME_FIBER_ERROR(fiber->error);
+					} break;
+						
+					case EXEC_TYPE_SPECIAL:
+						RUNTIME_ERROR("Unable to handle a special function in current context");
+						break;
+				}
+				LOAD_FRAME();
+				SYNC_STACKTOP(closure, _rneed);
+				
+				DISPATCH();
+			}
+			
+			// MARK: RET
+			// MARK: RET0
+			CASE_CODE(RET0):
+			CASE_CODE(RET): {
+				gravity_value_t result;
+				
+				if (op == RET0) {
+					DEBUG_VM("RET0");
+					result = VALUE_FROM_NULL;
+				} else {
+					OPCODE_GET_ONE8bit(inst, const uint32_t r1);
+					DEBUG_VM("RET %d", r1);
+					result = STACK_GET(r1);
+				}
+				
+				// sanity check
+				ASSERT(fiber->nframes > 0, "Number of active frames cannot be 0.");
+				
+				// POP frame
+				--fiber->nframes;
+				
+				// close open upvalues (if any)
+				gravity_close_upvalues(fiber, stackstart);
+				
+				// check if it was a gravity_vm_runfunc execution
+				if (frame->outloop) {
+					fiber->result = result;
+					return true;
+				}
+				
+				// retrieve destination register
+				uint32_t dest = fiber->frames[fiber->nframes].dest;
+				if (fiber->nframes == 0) {
+					if (fiber->caller == NULL) {fiber->result = result; return true;}
+					fiber = fiber->caller;
+					vm->fiber = fiber;
+				} else {
+					fiber->stacktop = frame->stackstart;
+				}
+				
+				LOAD_FRAME();
+				DEBUG_CALL("Resuming", func);
+				SETVALUE(dest, result);
+				
+				DISPATCH();
+			}
+			
+			// MARK: HALT
+			CASE_CODE(HALT): {
+				DEBUG_VM("HALT");
+				return true;
+			}
+			
+			// MARK: SWITCH
+			CASE_CODE(SWITCH): {
+				ASSERT(0, "To be implemented");
+				DISPATCH();
+			}
+			
+			// MARK: -
+			// MARK: MAPNEW
+			CASE_CODE(MAPNEW): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
+				DEBUG_VM("MAPNEW %d %d", r1, n);
+				
+				gravity_map_t *map = gravity_map_new(vm, n);
+				SETVALUE(r1, VALUE_FROM_OBJECT(map));
+				DISPATCH();
+			}
+			
+			// MARK: LISTNEW
+			CASE_CODE(LISTNEW): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
+				DEBUG_VM("LISTNEW %d %d", r1, n);
+				
+				gravity_list_t *list = gravity_list_new(vm, n);
+				SETVALUE(r1, VALUE_FROM_OBJECT(list));
+				DISPATCH();
+			}
+			
+			// MARK: RANGENEW
+			CASE_CODE(RANGENEW): {
+				OPCODE_GET_THREE8bit_ONE2bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3, const bool flag);
+				DEBUG_VM("RANGENEW %d %d %d (flag %d)", r1, r2, r3, flag);
+				
+				// sanity check
+				if ((!VALUE_ISA_INT(STACK_GET(r2))) || (!VALUE_ISA_INT(STACK_GET(r3))))
+					RUNTIME_ERROR("Unable to build Range from a non Int value");
+				
+				gravity_range_t *range = gravity_range_new(vm, VALUE_AS_INT(STACK_GET(r2)), VALUE_AS_INT(STACK_GET(r3)), !flag);
+				SETVALUE(r1, VALUE_FROM_OBJECT(range));
+				DISPATCH();
+			}
+			
+			// MARK: SETLIST
+			CASE_CODE(SETLIST): {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, uint32_t r1, uint32_t r2, const uint32_t r3);
+				DEBUG_VM("SETLIST %d %d", r1, r2);
+				
+				// since this code is produced by the compiler I can trust the fact that if v1 is not a map
+				// then it is an array and nothing else
+				gravity_value_t v1 = STACK_GET(r1);
+				bool v1_is_map = VALUE_ISA_MAP(v1);
+				
+				// r2 == 0 is an optimization, it means that list/map is composed all of literals
+				// and can be filled using the constant pool (r3 in this case represents an index inside cpool)
+				if (r2 == 0) {
+					gravity_value_t v2 = gravity_function_cpool_get(func, r3);
+					if (v1_is_map) {
+						gravity_map_t *map = VALUE_AS_MAP(v1);
+						gravity_map_append_map(vm, map, VALUE_AS_MAP(v2));
+					} else {
+						gravity_list_t *list = VALUE_AS_LIST(v1);
+						gravity_list_append_list(vm, list, VALUE_AS_LIST(v2));
+					}
+					DISPATCH();
+				}
+				
+				if (v1_is_map) {
+					gravity_map_t *map = VALUE_AS_MAP(v1);
+					while (r2) {
+						gravity_value_t key = STACK_GET(++r1);
+						gravity_value_t value = STACK_GET(++r1);
+						gravity_hash_insert(map->hash, key, value);
+						--r2;
+					}
+				} else {
+					gravity_list_t *list = VALUE_AS_LIST(v1);
+					while (r2) {
+						marray_push(gravity_value_t, list->array, STACK_GET(++r1));
+						--r2;
+					}
+				}
+				DISPATCH();
+			}
+			
+			// MARK: -
+			// MARK: CLOSURE
+			CASE_CODE(CLOSURE): {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t index);
+				DEBUG_VM("CLOSURE %d %d", r1, index);
+				
+				// get function prototype from cpool
+				gravity_value_t v = gravity_function_cpool_get(func, index);
+				if (!VALUE_ISA_FUNCTION(v)) RUNTIME_ERROR("Unable to create a closure from a non function object.");
+				gravity_function_t *f = VALUE_AS_FUNCTION(v);
+				
+				// create closure
+				gravity_closure_t *closure = gravity_closure_new(vm, f);
+				
+				// loop for each upvalue setup instruction
+				for (uint16_t i=0; i<f->nupvalues; ++i) {
+					// code is generated by the compiler so op must be MOVE
+					inst = *ip++;
+					op = (opcode_t)OPCODE_GET_OPCODE(inst);
+					OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t p1, const uint32_t p2);
+					
+					// p2 can be 1 (means that upvalue is in the current call frame) or 0 (means that upvalue is in the upvalue list of the caller)
+					ASSERT(op == MOVE, "Wrong OPCODE in CLOSURE statement");
+					closure->upvalue[i] = (p2) ? gravity_capture_upvalue (vm, fiber, &stackstart[p1]) : frame->closure->upvalue[p1];
+				}
+				
+				SETVALUE(r1, VALUE_FROM_OBJECT(closure));
+				DISPATCH();
+			}
+			
+			// MARK: CLOSE
+			CASE_CODE(CLOSE): {
+				OPCODE_GET_ONE8bit(inst, const uint32_t r1);
+				DEBUG_VM("CLOSE %d", r1);
+				
+				// close open upvalues (if any) starting from R1
+				gravity_close_upvalues(fiber, &stackstart[r1]);
+				DISPATCH();
+			}
+			
+			// MARK: - RESERVED
+			CASE_CODE(RESERVED1):
+			CASE_CODE(RESERVED2):
+			CASE_CODE(RESERVED3):
+			CASE_CODE(RESERVED4):
+			CASE_CODE(RESERVED5):
+			CASE_CODE(RESERVED6):{
+				RUNTIME_ERROR("Opcode not implemented in this VM version.");
+				DISPATCH();
+			}
+		}
+		
+		// MARK: -
+		
+		INC_PC;
+	};
+	
+	return true;
+}
+
+gravity_vm *gravity_vm_new (gravity_delegate_t *delegate/*, uint32_t context_size, gravity_int_t gcminthreshold, gravity_int_t gcthreshold, gravity_float_t gcratio*/) {
+	gravity_vm *vm = mem_alloc(sizeof(gravity_vm));
+	if (!vm) return NULL;
+	
+	// setup default callbacks
+	vm->transfer = gravity_gc_transfer;
+	vm->cleanup = gravity_gc_cleanup;
+	
+	vm->pc = 0;
+	vm->delegate = delegate;
+	vm->context = gravity_hash_create(/*(context_size) ? context_size : */DEFAULT_CONTEXT_SIZE, gravity_value_hash, gravity_value_equals, NULL, NULL);
+	
+	// garbage collector
+	vm->gcenabled = true;
+	vm->gcminthreshold = /*(gcminthreshold) ? gcminthreshold :*/ DEFAULT_CG_MINTHRESHOLD;
+	vm->gcthreshold = /*(gcthreshold) ? gcthreshold :*/ DEFAULT_CG_THRESHOLD;
+	vm->gcratio = /*(gcratio) ? gcratio :*/ DEFAULT_CG_RATIO;
+	vm->memallocated = 0;
+	marray_init(vm->graylist);
+	marray_init(vm->gcsave);
+	
+	// init base and core
+	gravity_core_register(vm);
+	gravity_cache_setup();
+	
+	RESET_STATS(vm);
+	return vm;
+}
+
+gravity_vm *gravity_vm_newmini (void) {
+	gravity_vm *vm = mem_alloc(sizeof(gravity_vm));
+	return vm;
+}
+
+void gravity_vm_free (gravity_vm *vm) {
+	if (!vm) return;
+	
+	if (vm->context) gravity_cache_free();
+	gravity_vm_cleanup(vm);
+	if (vm->context) gravity_hash_free(vm->context);
+	marray_destroy(vm->gcsave);
+	marray_destroy(vm->graylist);
+	mem_free(vm);
+}
+
+inline gravity_value_t gravity_vm_lookup (gravity_vm *vm, gravity_value_t key) {
+	gravity_value_t *value = gravity_hash_lookup(vm->context, key);
+	return (value) ? *value : VALUE_NOT_VALID;
+}
+
+inline gravity_closure_t *gravity_vm_fastlookup (gravity_vm *vm, gravity_class_t *c, int index) {
+	#pragma unused(vm)
+	return (gravity_closure_t *)gravity_class_lookup_closure(c, cache[index]);
+}
+
+inline gravity_value_t gravity_vm_getvalue (gravity_vm *vm, const char *key, uint32_t keylen) {
+	STATICVALUE_FROM_STRING(k, key, keylen);
+	return gravity_vm_lookup(vm, k);
+}
+
+inline void gravity_vm_setvalue (gravity_vm *vm, const char *key, gravity_value_t value) {
+	gravity_hash_insert(vm->context, VALUE_FROM_CSTRING(vm, key), value);
+}
+
+double gravity_vm_time (gravity_vm *vm) {
+	return vm->time;
+}
+
+gravity_value_t gravity_vm_result (gravity_vm *vm) {
+	gravity_value_t result = vm->fiber->result;
+	vm->fiber->result = VALUE_FROM_NULL;
+	return result;
+}
+
+gravity_delegate_t *gravity_vm_delegate (gravity_vm *vm) {
+	return vm->delegate;
+}
+
+gravity_fiber_t *gravity_vm_fiber (gravity_vm* vm) {
+	return vm->fiber;
+}
+
+void gravity_vm_setfiber(gravity_vm* vm, gravity_fiber_t *fiber) {
+	vm->fiber = fiber;
+}
+
+void gravity_vm_seterror (gravity_vm* vm, const char *format, ...) {
+	gravity_fiber_t *fiber = vm->fiber;
+	
+	if (fiber->error) mem_free(fiber->error);
+	size_t err_size = 2048;
+	fiber->error = mem_alloc(err_size);
+	
+	va_list arg;
+	va_start (arg, format);
+	vsnprintf(fiber->error, err_size, (format) ? format : "%s", arg);
+	va_end (arg);
+}
+
+void gravity_vm_seterror_string (gravity_vm* vm, const char *s) {
+	gravity_fiber_t *fiber = vm->fiber;
+	if (fiber->error) mem_free(fiber->error);
+	fiber->error = (char *)string_dup(s);
+}
+
+#if GRAVITY_VM_STATS
+static void gravity_vm_stats (gravity_vm *vm) {
+	printf("\n============================================\n");
+	printf("%12s %10s %20s\n", "OPCODE", "USAGE", "MICROBENCH (ms)");
+	printf("============================================\n");
+	
+	double total = 0.0;
+	for (uint32_t i=0; i<GRAVITY_LATEST_OPCODE; ++i) {
+		if (vm->nstat[i]) {
+			total += vm->tstat[i];
+		}
+	}
+	
+	for (uint32_t i=0; i<GRAVITY_LATEST_OPCODE; ++i) {
+		if (vm->nstat[i]) {
+			uint32_t n = vm->nstat[i];
+			double d = vm->tstat[i] / (double)n;
+			double p = (vm->tstat[i] * 100) / total;
+			printf("%12s %*d %*.4f (%.2f%%)\n", opcode_name((opcode_t)i), 10, n, 11, d, p);
+		}
+	}
+	printf("============================================\n");
+	printf("# Frames reallocs: %d (%d)\n", vm->nfrealloc, vm->fiber->framesalloc);
+	printf("# Stack  reallocs: %d (%d)\n", vm->nsrealloc, vm->fiber->stackalloc);
+	printf("============================================\n");
+}
+#endif
+
+bool gravity_vm_runclosure (gravity_vm *vm, gravity_closure_t *closure, gravity_value_t selfvalue, gravity_value_t params[], uint16_t nparams) {
+	if (vm->aborted) return false;
+	
+	// do not waste cycles on empty functions
+	gravity_function_t *f = closure->f;
+	if ((f->tag == EXEC_TYPE_NATIVE) && ((!f->bytecode) || (f->ninsts == 0))) return true;
+	
+	// current execution fiber
+	gravity_fiber_t		*fiber = vm->fiber;
+	gravity_value_t		*stackstart = NULL;
+	uint32_t			rwin = 0;
+	
+	DEBUG_STACK();
+	
+	// if fiber->nframes is not zero it means that this event has been recursively called
+	// from somewhere inside the main function so we need to protect and correctly setup
+	// the new activation frame
+	if (fiber->nframes) {
+		// current call frame
+		gravity_callframe_t *frame = &fiber->frames[fiber->nframes - 1];
+		
+		// current top of the stack
+		stackstart = frame->stackstart;
+		
+		// current instruction pointer
+		uint32_t *ip = frame->ip;
+		
+		// compute register window
+		rwin = FN_COUNTREG(frame->closure->f, frame->nargs);
+		
+		// check stack size
+		gravity_check_stack(vm, vm->fiber, FN_COUNTREG(f,nparams+1), &stackstart);
+		
+		// setup params (first param is self)
+		SETVALUE(rwin, selfvalue);
+		for (uint16_t i=0; i<nparams; ++i) {
+			SETVALUE(rwin+i+1, params[i]);
+		}
+		
+		STORE_FRAME();
+		PUSH_FRAME(closure, &stackstart[rwin], rwin, nparams);
+		SETFRAME_OUTLOOP(cframe);
+	} else {
+		// there are no execution frames when called outside main function
+		gravity_fiber_reassign(vm->fiber, closure, nparams+1);
+		stackstart = vm->fiber->stack;
+		
+		// setup params (first param is self)
+		SETVALUE(rwin, selfvalue);
+		for (uint16_t i=0; i<nparams; ++i) {
+			SETVALUE(rwin+i+1, params[i]);
+		}
+	}
+	
+	// f can be native, internal or bridge because this function
+	// is called also by convert_value2string
+	// for example in Creo:
+	// var mData = Data();
+	// Console.write("data: " + mData);
+	// mData.String is a bridged objc method
+	DEBUG_STACK();
+	
+	bool result = false;
+	switch (f->tag) {
+		case EXEC_TYPE_NATIVE:
+			result = gravity_vm_exec(vm);
+			break;
+			
+		case EXEC_TYPE_INTERNAL:
+			result = f->internal(vm, &stackstart[rwin], nparams, GRAVITY_FIBER_REGISTER);
+			break;
+			
+		case EXEC_TYPE_BRIDGED:
+			if (vm && vm->delegate && vm->delegate->bridge_execute)
+				result = vm->delegate->bridge_execute(vm, f->xdata, &stackstart[rwin], nparams, GRAVITY_FIBER_REGISTER);
+			break;
+			
+		case EXEC_TYPE_SPECIAL:
+			result = false;
+			break;
+	}
+	if (f->tag != EXEC_TYPE_NATIVE) --fiber->nframes;
+	
+	DEBUG_STACK();
+	return result;
+}
+
+bool gravity_vm_run (gravity_vm *vm, gravity_closure_t *closure) {
+	// f is $moduleinit (first function to be executed)
+	vm->fiber = gravity_fiber_new(vm, closure, 0, 0);
+	
+	// execute $moduleinit in order to initialize VM
+	gravity_vm_exec(vm);
+	
+	// lookup main function
+	gravity_value_t main = gravity_vm_getvalue(vm, MAIN_FUNCTION, (uint32_t)strlen(MAIN_FUNCTION));
+	if (!VALUE_ISA_CLOSURE(main)) {
+		report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to find main function.");
+		return false;
+	}
+	
+	// re-use main fiber
+	gravity_closure_t *main_closure = VALUE_AS_CLOSURE(main);
+	gravity_fiber_reassign(vm->fiber, main_closure, 0);
+	
+	// execute main function
+	RESET_STATS(vm);
+	nanotime_t tstart = nanotime();
+	bool result = gravity_vm_exec(vm);
+	nanotime_t tend = nanotime();
+	vm->time = millitime(tstart, tend);
+	
+	PRINT_STATS(vm);
+	return result;
+}
+
+// MARK: - User -
+
+void gravity_vm_setslot (gravity_vm *vm, gravity_value_t value, uint32_t index) {
+	if (index == GRAVITY_FIBER_REGISTER) {
+		vm->fiber->result = value;
+		return;
+	}
+	
+	gravity_callframe_t *frame = &(vm->fiber->frames[vm->fiber->nframes-1]);
+	frame->stackstart[index] = value;
+}
+
+gravity_value_t gravity_vm_getslot (gravity_vm *vm, uint32_t index) {
+	gravity_callframe_t *frame = &(vm->fiber->frames[vm->fiber->nframes-1]);
+	return frame->stackstart[index];
+}
+	
+void gravity_vm_setdata (gravity_vm *vm, void *data) {
+	vm->data = data;
+}
+
+void *gravity_vm_getdata (gravity_vm *vm) {
+	return vm->data;
+}
+
+void gravity_vm_set_callbacks (gravity_vm *vm, vm_transfer_cb vm_transfer, vm_cleanup_cb vm_cleanup) {
+	vm->transfer = vm_transfer;
+	vm->cleanup = vm_cleanup;
+}
+
+void gravity_vm_transfer (gravity_vm* vm, gravity_object_t *obj) {
+	if (vm->transfer) vm->transfer(vm, obj);
+}
+
+void gravity_vm_cleanup (gravity_vm* vm) {
+	if (vm->cleanup) vm->cleanup(vm);
+}
+
+void gravity_vm_filter (gravity_vm* vm, vm_filter_cb cleanup_filter) {
+	vm->filter = cleanup_filter;
+}
+
+bool gravity_vm_ismini (gravity_vm *vm) {
+	return (vm->context == NULL);
+}
+
+bool gravity_vm_isaborted (gravity_vm *vm) {
+	if (!vm) return true;
+	return vm->aborted;
+}
+
+void gravity_vm_setaborted (gravity_vm *vm) {
+	vm->aborted = true;
+}
+
+char *gravity_vm_anonymous (gravity_vm *vm) {
+	snprintf(vm->temp, sizeof(vm->temp), "%sanon%d", GRAVITY_VM_ANONYMOUS_PREFIX, ++vm->nanon);
+	return vm->temp;
+}
+
+void gravity_vm_memupdate (gravity_vm *vm, gravity_int_t value) {
+	vm->memallocated += value;
+}
+
+// MARK: - Get/Set Internal Settings -
+
+gravity_value_t gravity_vm_get (gravity_vm *vm, const char *key) {
+	if (key) {
+		if (strcmp(key, "gcenabled") == 0) return VALUE_FROM_BOOL(vm->gcenabled);
+		if (strcmp(key, "gcminthreshold") == 0) return VALUE_FROM_INT(vm->gcminthreshold);
+		if (strcmp(key, "gcthreshold") == 0) return VALUE_FROM_INT(vm->gcthreshold);
+		if (strcmp(key, "gcratio") == 0) return VALUE_FROM_FLOAT(vm->gcratio);
+	}
+	return VALUE_FROM_NULL;
+}
+
+bool gravity_vm_set (gravity_vm *vm, const char *key, gravity_value_t value) {
+	if (key) {
+		if ((strcmp(key, "gcenabled") == 0) && VALUE_ISA_BOOL(value)) {vm->gcenabled = VALUE_AS_BOOL(value); return true;}
+		if ((strcmp(key, "gcminthreshold") == 0) && VALUE_ISA_INT(value)) {vm->gcminthreshold = VALUE_AS_INT(value); return true;}
+		if ((strcmp(key, "gcthreshold") == 0) && VALUE_ISA_INT(value)) {vm->gcthreshold = VALUE_AS_INT(value); return true;}
+		if ((strcmp(key, "gcratio") == 0) && VALUE_ISA_FLOAT(value)) {vm->gcratio = VALUE_AS_FLOAT(value); return true;}
+	}
+	return false;
+}
+
+
+// MARK: - Deserializer -
+
+static bool real_set_superclass (gravity_vm *vm, gravity_class_t *c, gravity_value_t key, const char *supername) {
+	// 1. LOOKUP in current stack hierarchy
+	STATICVALUE_FROM_STRING(superkey, supername, strlen(supername));
+	void_r *stack = (void_r *)vm->data;
+	size_t n = marray_size(*stack);
+	for (size_t i=0; i<n; i++) {
+		gravity_object_t *obj = marray_get(*stack, i);
+		// if object is a CLASS then lookup hash table
+		if (OBJECT_ISA_CLASS(obj)) {
+			gravity_class_t *c2 = (gravity_class_t *)gravity_class_lookup((gravity_class_t *)obj, superkey);
+			if ((c2) && (OBJECT_ISA_CLASS(c2))) {
+				mem_free(supername);
+				if (!gravity_class_setsuper(c, c2)) goto error_max_ivar;
+				return true;
+			}
+		}
+		// if object is a FUNCTION then iterate the constant pool
+		else if (OBJECT_ISA_FUNCTION(obj)) {
+			gravity_function_t *f = (gravity_function_t *)obj;
+			if (f->tag == EXEC_TYPE_NATIVE) {
+				size_t count = marray_size(f->cpool);
+				for (size_t j=0; j<count; j++) {
+					gravity_value_t v = marray_get(f->cpool, j);
+					if (VALUE_ISA_CLASS(v)) {
+						gravity_class_t *c2 = VALUE_AS_CLASS(v);
+						if (strcmp(c2->identifier, supername) == 0) {
+							mem_free(supername);
+							if (!gravity_class_setsuper(c, c2)) goto error_max_ivar;
+							return true;
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	// 2. not found in stack hierarchy so LOOKUP in VM
+	gravity_value_t v = gravity_vm_lookup(vm, superkey);
+	if (VALUE_ISA_CLASS(v)) {
+		mem_free(supername);
+		if (!gravity_class_setsuper(c, VALUE_AS_CLASS(v))) goto error_max_ivar;
+		return true;
+	}
+	
+	report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "Unable to find superclass %s of class %s.", supername, VALUE_AS_CSTRING(key));
+	mem_free(supername);
+	return false;
+	
+error_max_ivar:
+	report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "Maximum number of allowed ivars (%d) reached for class %s.", MAX_IVARS, VALUE_AS_CSTRING(key));
+	return false;
+}
+
+static void vm_set_superclass_callback (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(hashtable)
+	gravity_vm *vm = (gravity_vm *)data;
+	
+	if (VALUE_ISA_FUNCTION(value)) vm_set_superclass(vm, VALUE_AS_OBJECT(value));
+	if (!VALUE_ISA_CLASS(value)) return;
+	
+	// process class
+	gravity_class_t *c = VALUE_AS_CLASS(value);
+	DEBUG_DESERIALIZE("set_superclass_callback for class %s", c->identifier);
+	
+	const char *supername = c->xdata;
+	c->xdata = NULL;
+	if (supername) {
+		if (!real_set_superclass(vm, c, key, supername)) return;
+	}
+	
+	gravity_hash_iterate(c->htable, vm_set_superclass_callback, vm);
+}
+
+static bool vm_set_superclass (gravity_vm *vm, gravity_object_t *obj) {
+	void_r *stack = (void_r *)vm->data;
+	marray_push(void*, *stack, obj);
+	
+	if (OBJECT_ISA_CLASS(obj)) {
+		// CLASS case: process class and its hash table
+		gravity_class_t *c = (gravity_class_t *)obj;
+		DEBUG_DESERIALIZE("set_superclass for class %s", c->identifier);
+		
+		STATICVALUE_FROM_STRING(key, c->identifier, strlen(c->identifier));
+		const char *supername = c->xdata;
+		c->xdata = NULL;
+		if (supername) real_set_superclass(vm, c, key, supername);
+		gravity_hash_iterate(c->htable, vm_set_superclass_callback, vm);
+		
+	} else if (OBJECT_ISA_FUNCTION(obj)) {
+		// FUNCTION case: scan constant pool and recursively call fixsuper
+		
+		gravity_function_t *f = (gravity_function_t *)obj;
+		if (f->tag == EXEC_TYPE_NATIVE) {
+			size_t n = marray_size(f->cpool);
+			for (size_t i=0; i<n; i++) {
+				gravity_value_t v = marray_get(f->cpool, i);
+				if (VALUE_ISA_FUNCTION(v)) vm_set_superclass(vm, (gravity_object_t *)VALUE_AS_FUNCTION(v));
+				else if (VALUE_ISA_CLASS(v)) vm_set_superclass(vm, (gravity_object_t *)VALUE_AS_CLASS(v));
+			}
+		}
+	} else {
+		report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to recognize object type.");
+		return false;
+	}
+	
+	marray_pop(*stack);
+	return true;
+}
+
+gravity_closure_t *gravity_vm_loadfile (gravity_vm *vm, const char *path) {
+	size_t len;
+	const char *buffer = file_read(path, &len);
+	if (!buffer) return NULL;
+	
+	gravity_closure_t *closure = gravity_vm_loadbuffer(vm, buffer, len);
+	
+	mem_free(buffer);
+	return closure;
+}
+
+gravity_closure_t *gravity_vm_loadbuffer (gravity_vm *vm, const char *buffer, size_t len) {
+	json_value *json = json_parse (buffer, len);
+	if (!json) goto abort_load;
+	if (json->type != json_object) goto abort_load;
+	
+	// state buffer for further processing super classes
+	void_r objects;
+	marray_init(objects);
+	
+	// disable GC while deserializing objects
+	gravity_gc_setenabled(vm, false);
+	
+	// scan loop
+	gravity_closure_t *closure = NULL;
+	uint32_t n = json->u.object.length;
+	for (uint32_t i=0; i<n; ++i) {
+		json_value *entry = json->u.object.values[i].value;
+		if (entry->u.object.length == 0) continue;
+		
+		// each entry must be an object
+		if (entry->type != json_object) goto abort_load;
+		
+		gravity_object_t *obj = NULL;
+		if (!gravity_object_deserialize(vm, entry, &obj)) goto abort_load;
+		if (!obj) continue;
+		
+		// save object for further processing
+		marray_push(void*, objects, obj);
+		
+		// add a sanity check here: obj can be a function or a class, nothing else
+		
+		// transform every function to a closure
+		if (OBJECT_ISA_FUNCTION(obj)) {
+			gravity_function_t *f = (gravity_function_t *)obj;
+			const char *identifier = f->identifier;
+			gravity_closure_t *cl = gravity_closure_new(vm, f);
+			if (string_casencmp(identifier, INITMODULE_NAME, strlen(identifier)) == 0) {
+				closure = cl;
+			} else {
+				gravity_vm_setvalue(vm, identifier, VALUE_FROM_OBJECT(cl));
+			}
+		}
+	}
+	json_value_free(json);
+	
+	// fix superclass(es)
+	size_t count = marray_size(objects);
+	if (count) {
+		void *saved = vm->data;
+		
+		// prepare stack to help resolve nested super classes
+		void_r stack;
+		marray_init(stack);
+		vm->data = (void *)&stack;
+		
+		// loop of each processed object
+		for (size_t i=0; i<count; ++i) {
+			gravity_object_t *obj = (gravity_object_t *)marray_get(objects, i);
+			if (!vm_set_superclass(vm, obj)) goto abort_generic;
+		}
+		
+		marray_destroy(stack);
+		marray_destroy(objects);
+		vm->data = saved;
+	}
+	
+	gravity_gc_setenabled(vm, true);
+	return closure;
+	
+abort_load:
+	report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, "%s", "Unable to parse JSON executable file.");
+	if (json) json_value_free(json);
+	gravity_gc_setenabled(vm, true);
+	return NULL;
+	
+abort_generic:
+	if (json) json_value_free(json);
+	gravity_gc_setenabled(vm, true);
+	return NULL;
+}
+
+// MARK: - Garbage Collector -
+
+void gravity_gray_object (gravity_vm *vm, gravity_object_t *obj) {
+	if (!obj) return;
+	
+	// avoid recursion if object has already been visited
+	if (obj->gc.isdark) return;
+	
+	DEBUG_GC("GRAY %s", gravity_object_debug(obj));
+	
+	// object has been reached
+	obj->gc.isdark = true;
+	
+	// add to marked array
+	marray_push(gravity_object_t *, vm->graylist, obj);
+}
+
+void gravity_gray_value (gravity_vm *vm, gravity_value_t v) {
+	if (gravity_value_isobject(v)) gravity_gray_object(vm, (gravity_object_t *)v.p);
+}
+
+static void gravity_gray_hash (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused (hashtable)
+	gravity_vm *vm = (gravity_vm*)data;
+	gravity_gray_value(vm, key);
+	gravity_gray_value(vm, value);
+}
+
+// MARK: -
+
+static void gravity_gc_transform (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t *value, void *data) {
+	#pragma unused (hashtable)
+	
+	gravity_vm *vm = (gravity_vm *)data;
+	gravity_object_t *obj = VALUE_AS_OBJECT(*value);
+	
+	if (OBJECT_ISA_FUNCTION(obj)) {
+		gravity_function_t *f = (gravity_function_t *)obj;
+		if (f->tag == EXEC_TYPE_SPECIAL) {
+			if (f->special[0]) {
+				gravity_gc_transfer(vm, (gravity_object_t *)f->special[0]);
+				f->special[0] = gravity_closure_new(vm, f->special[0]);
+			}
+			if (f->special[1]) {
+				gravity_gc_transfer(vm, (gravity_object_t *)f->special[1]);
+				f->special[1] = gravity_closure_new(vm, f->special[1]);
+			}
+		} else if (f->tag == EXEC_TYPE_NATIVE) {
+			gravity_vm_initmodule(vm, f);
+		}
+		
+		bool is_super_function = false;
+		if (VALUE_ISA_STRING(key)) {
+			gravity_string_t *s = VALUE_AS_STRING(key);
+			// looking for a string that begins with $init AND it is longer than strlen($init)
+			is_super_function = ((s->len > 5) && (string_casencmp(s->s, CLASS_INTERNAL_INIT_NAME, 5) == 0));
+		}
+		
+		gravity_closure_t *closure = gravity_closure_new(vm, f);
+		*value = VALUE_FROM_OBJECT((gravity_object_t *)closure);
+		if (!is_super_function) gravity_gc_transfer(vm, obj);
+	} else if (OBJECT_ISA_CLASS(obj)) {
+		gravity_class_t *c = (gravity_class_t *)obj;
+		gravity_vm_loadclass(vm, c);
+	} else {
+		// should never reach this point
+		assert(0);
+	}
+}
+
+void gravity_vm_initmodule (gravity_vm *vm, gravity_function_t *f) {
+	size_t n = marray_size(f->cpool);
+	for (size_t i=0; i<n; i++) {
+		gravity_value_t v = marray_get(f->cpool, i);
+		if (VALUE_ISA_CLASS(v)) {
+			gravity_class_t *c =  VALUE_AS_CLASS(v);
+			gravity_vm_loadclass(vm, c);
+		}
+		else if (VALUE_ISA_FUNCTION(v)) {
+			gravity_vm_initmodule(vm, VALUE_AS_FUNCTION(v));
+		}
+	}
+}
+
+static void gravity_gc_transfer_object (gravity_vm *vm, gravity_object_t *obj) {
+	DEBUG_GC("GC TRANSFER %s", gravity_object_debug(obj));
+	++vm->gccount;
+	obj->gc.next = vm->gchead;
+	vm->gchead = obj;
+}
+
+static void gravity_gc_transfer (gravity_vm *vm, gravity_object_t *obj) {
+	if (vm->gcenabled) {
+		#if GRAVITY_GC_STRESSTEST
+		// check if ptr is already in the list
+		gravity_object_t **ptr = &vm->gchead;
+		while (*ptr) {
+			if (obj == *ptr) {
+				printf("Object %s already GC!\n", gravity_object_debug(obj));
+				assert(0);
+			}
+			ptr = &(*ptr)->gc.next;
+		}
+		gravity_gc_start(vm);
+		#else
+		if (vm->memallocated >= vm->gcthreshold) gravity_gc_start(vm);
+		#endif
+	}
+	
+	gravity_gc_transfer_object(vm, obj);
+}
+
+static void gravity_gc_sweep (gravity_vm *vm) {
+	gravity_object_t **obj = &vm->gchead;
+	while (*obj) {
+		if (!(*obj)->gc.isdark) {
+			// object is unreachable so remove from the list and free it
+			gravity_object_t *unreached = *obj;
+			*obj = unreached->gc.next;
+			gravity_object_free(vm, unreached);
+			--vm->gccount;
+		} else {
+			// object was reached so unmark it for the next GC and move on to the next
+			(*obj)->gc.isdark = false;
+			obj = &(*obj)->gc.next;
+		}
+	}
+}
+
+void gravity_gc_start (gravity_vm *vm) {
+	if (!vm->fiber) return;
+	
+	#if GRAVITY_GC_STATS
+	gravity_int_t membefore = vm->memallocated;
+	nanotime_t tstart = nanotime();
+	#endif
+	
+	// reset memory counter
+	vm->memallocated = 0;
+	
+	// mark GC saved temp object
+	for (uint32_t i=0; i<marray_size(vm->gcsave); ++i) {
+		gravity_object_t *obj = marray_get(vm->gcsave, i);
+		gravity_gray_object(vm, obj);
+	}
+	
+	// mark all reachable objects starting from current fiber
+	gravity_gray_object(vm, (gravity_object_t *)vm->fiber);
+	
+	// mark globals
+	gravity_hash_iterate(vm->context, gravity_gray_hash, (void*)vm);
+	
+	// root has been grayed so recursively scan reachable objects
+	while (marray_size(vm->graylist)) {
+		gravity_object_t *obj = marray_pop(vm->graylist);
+		gravity_object_blacken(vm, obj);
+	}
+	
+	// check unreachable objects (collect white objects)
+	gravity_gc_sweep(vm);
+	
+	// dynamically update gcthreshold
+	vm->gcthreshold = vm->memallocated + (vm->memallocated * vm->gcratio / 100);
+	if (vm->gcthreshold < vm->gcminthreshold) vm->gcthreshold = vm->gcminthreshold;
+	
+	#if GRAVITY_GC_STATS
+	nanotime_t tend = nanotime();
+	double gctime = millitime(tstart, tend);
+	printf("GC %lu before, %lu after (%lu collected - %lu objects), next at %lu. Took %.3fs.\n",
+		   (unsigned long)membefore,
+		   (unsigned long)vm->memallocated,
+		   (membefore > vm->memallocated) ? (unsigned long)(membefore - vm->memallocated) : 0,
+		   (unsigned long)vm->gccount,
+		   (unsigned long)vm->gcthreshold,
+		   gctime);
+	#endif
+}
+
+static void gravity_gc_cleanup (gravity_vm *vm) {
+	if (!vm->gchead) return;
+	
+	if (vm->filter) {
+		// filter case
+		vm_filter_cb filter = vm->filter;
+		
+		// we need to remove freed objects from the linked list
+		// so we need a pointer to the previous object in order
+		// to be able to point it to obj's next ptr
+		//
+		//         +--------+      +--------+      +--------+
+		//     --> |  prev  |  --> |   obj  |  --> |  next  |  -->
+		//         +--------+      +--------+      +--------+
+		//             |                               ^
+		//             |                               |
+		//             +-------------------------------+
+		
+		gravity_object_t *obj = vm->gchead;
+		gravity_object_t *prev = NULL;
+		
+		while (obj) {
+			if (!filter(obj)) {
+				prev = obj;
+				obj = obj->gc.next;
+				continue;
+			}
+			
+			// REMOVE obj from the linked list
+			gravity_object_t *next = obj->gc.next;
+			if (!prev) vm->gchead = next;
+			else prev->gc.next = next;
+			
+			gravity_object_free(vm, obj);
+			--vm->gccount;
+			obj = next;
+		}
+		return;
+	}
+	
+	// no filter so free all GC objects
+	gravity_object_t *obj = vm->gchead;
+	while (obj) {
+		gravity_object_t *next = obj->gc.next;
+		gravity_object_free(vm, obj);
+		--vm->gccount;
+		obj = next;
+	}
+	vm->gchead = NULL;
+	
+	// free all temporary allocated objects
+	while (marray_size(vm->gcsave)) {
+		gravity_object_t *tobj = marray_pop(vm->gcsave);
+		gravity_object_free(vm, tobj);
+	}
+}
+
+void gravity_gc_setenabled (gravity_vm *vm, bool enabled) {
+	vm->gcenabled = enabled;
+}
+
+void gravity_gc_push (gravity_vm *vm, gravity_object_t *obj) {
+	marray_push(gravity_object_t *, vm->gcsave, obj);
+}
+
+void gravity_gc_pop (gravity_vm *vm) {
+	marray_pop(vm->gcsave);
+}

+ 68 - 0
src/runtime/gravity_vm.h

@@ -0,0 +1,68 @@
+//
+//  gravity_vm.h
+//  gravity
+//
+//  Created by Marco Bambini on 11/11/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_VM__
+#define __GRAVITY_VM__
+
+#include "gravity_delegate.h"
+#include "gravity_value.h"
+
+typedef bool (*vm_filter_cb) (gravity_object_t *obj);
+typedef void (*vm_transfer_cb) (gravity_vm *vm, gravity_object_t *obj);
+typedef void (*vm_cleanup_cb) (gravity_vm *vm);
+
+gravity_vm			*gravity_vm_new (gravity_delegate_t *delegate);
+gravity_vm			*gravity_vm_newmini (void);
+void				gravity_vm_set_callbacks (gravity_vm *vm, vm_transfer_cb vm_transfer, vm_cleanup_cb vm_cleanup);
+void				gravity_vm_free (gravity_vm *vm);
+void				gravity_vm_reset (gravity_vm *vm);
+bool				gravity_vm_runclosure (gravity_vm *vm, gravity_closure_t *closure, gravity_value_t selfvalue, gravity_value_t params[], uint16_t nparams);
+bool				gravity_vm_run (gravity_vm *vm, gravity_closure_t *closure);
+void				gravity_vm_setvalue (gravity_vm *vm, const char *key, gravity_value_t value);
+gravity_value_t		gravity_vm_lookup (gravity_vm *vm, gravity_value_t key);
+gravity_value_t		gravity_vm_getvalue (gravity_vm *vm, const char *key, uint32_t keylen);
+double				gravity_vm_time (gravity_vm *vm);
+gravity_value_t		gravity_vm_result (gravity_vm *vm);
+gravity_delegate_t	*gravity_vm_delegate (gravity_vm *vm);
+gravity_fiber_t		*gravity_vm_fiber (gravity_vm *vm);
+void				gravity_vm_setfiber(gravity_vm* vm, gravity_fiber_t *fiber);
+void				gravity_vm_seterror (gravity_vm *vm, const char *format, ...);
+void				gravity_vm_seterror_string (gravity_vm* vm, const char *s);
+bool				gravity_vm_ismini (gravity_vm *vm);
+gravity_value_t		gravity_vm_keyindex (gravity_vm *vm, uint32_t index);
+bool				gravity_vm_isaborted (gravity_vm *vm);
+void				gravity_vm_setaborted (gravity_vm *vm);
+
+void				gravity_gray_value (gravity_vm* vm, gravity_value_t v);
+void				gravity_gray_object (gravity_vm* vm, gravity_object_t *obj);
+void				gravity_gc_start (gravity_vm* vm);
+void				gravity_gc_setenabled (gravity_vm* vm, bool enabled);
+void				gravity_gc_push (gravity_vm *vm, gravity_object_t *obj);
+void				gravity_gc_pop (gravity_vm *vm);
+
+void				gravity_vm_transfer (gravity_vm* vm, gravity_object_t *obj);
+void				gravity_vm_cleanup (gravity_vm* vm);
+void				gravity_vm_filter (gravity_vm* vm, vm_filter_cb cleanup_filter);
+
+gravity_closure_t	*gravity_vm_loadfile (gravity_vm *vm, const char *path);
+gravity_closure_t	*gravity_vm_loadbuffer (gravity_vm *vm, const char *buffer, size_t len);
+void				gravity_vm_initmodule (gravity_vm *vm, gravity_function_t *f);
+
+gravity_closure_t	*gravity_vm_fastlookup (gravity_vm *vm, gravity_class_t *c, int index);
+void				gravity_vm_setslot (gravity_vm *vm, gravity_value_t value, uint32_t index);
+gravity_value_t		gravity_vm_getslot (gravity_vm *vm, uint32_t index);
+void				gravity_vm_setdata (gravity_vm *vm, void *data);
+void				*gravity_vm_getdata (gravity_vm *vm);
+void				gravity_vm_memupdate (gravity_vm *vm, gravity_int_t value);
+
+gravity_value_t		gravity_vm_get (gravity_vm *vm, const char *key);
+bool				gravity_vm_set (gravity_vm *vm, const char *key, gravity_value_t value);
+char				*gravity_vm_anonymous (gravity_vm *vm);
+
+#endif
+

+ 273 - 0
src/runtime/gravity_vmmacros.h

@@ -0,0 +1,273 @@
+//
+//  gravity_vmmacros.h
+//  gravity
+//
+//  Created by Marco Bambini on 08/10/15.
+//  Copyright © 2015 Creolabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_VMMACROS__
+#define __GRAVITY_VMMACROS__
+
+// Assertions add significant overhead, so are only enabled in debug builds.
+#if 1
+#define ASSERT(condition, message)						do { \
+															if (!(condition)) { \
+																fprintf(stderr, "[%s:%d] Assert failed in %s(): %s\n", \
+																__FILE__, __LINE__, __func__, message); \
+																abort(); \
+															} \
+														} \
+														while(0)
+#else
+#define ASSERT(condition, message)
+#endif
+
+#if 0
+#define DEBUG_CALL(s, f)								printf("%s %s\n", s, f->identifier)
+#else
+#define DEBUG_CALL(s, f)
+#endif
+
+// signed operation decoding (OPCODE_GET_ONE8bit_SIGN_ONE17bit) from my question on stackoverflow
+// http://stackoverflow.com/questions/37054769/optimize-a-bit-decoding-operation-in-c?noredirect=1#comment61673505_37054769
+
+#define OPCODE_GET_OPCODE(op)							((op >> 26) & 0x3F)
+#define OPCODE_GET_ONE8bit_FLAG_ONE17bit(op,r1,f,n)		r1 = (op >> 18) & 0xFF; f = (op >> 17) & 0x01; n = (int32_t)(op & 0x1FFFF)
+#define OPCODE_GET_ONE8bit_SIGN_ONE17bit(op,r1,n)		r1 = (op >> 18) & 0xFF; n = ((int32_t)(op & 0x1FFFF) - (int32_t)(op & 0x20000))
+#define OPCODE_GET_TWO8bit_ONE10bit(op,r1,r2,r3)		r1 = (op >> 18) & 0xFF; r2 = (op >> 10) & 0xFF; r3 = (op & 0x3FF)
+#define OPCODE_GET_ONE8bit(op,r1)						r1 = (op >> 18) & 0xFF;
+#define OPCODE_GET_SIGN_ONE25bit(op, n)					n = ((op >> 25) & 0x01) ? -(op & 0x1FFFFFF) : (op & 0x1FFFFFF)
+#define OPCODE_GET_ONE8bit_ONE18bit(op,r1,n)			r1 = (op >> 18) & 0xFF; n = (op & 0x3FFFF)
+#define OPCODE_GET_LAST18bit(op,n)						n = (op & 0x3FFFF)
+#define OPCODE_GET_ONE26bit(op, n)						n = (op & 0x3FFFFFF)
+#define OPCODE_GET_ONE8bit_ONE10bit(op,r1,r3)			r1 = (op >> 18) & 0xFF; r3 = (op & 0x3FF)
+#define OPCODE_GET_THREE8bit(op,r1,r2,r3)				OPCODE_GET_TWO8bit_ONE10bit(op,r1,r2,r3)
+#define OPCODE_GET_FOUR8bit(op,r1,r2,r3,r4)				r1 = (op >> 24) & 0xFF; r2 = (op >> 16) & 0xFF; r3 = (op >> 8) & 0xFF; r4 = (op & 0xFF)
+#define OPCODE_GET_THREE8bit_ONE2bit(op,r1,r2,r3,r4)	r1 = (op >> 18) & 0xFF; r2 = (op >> 10) & 0xFF; r3 = (op >> 2) & 0xFF; r4 = (op & 0x03)
+
+#define GRAVITY_VM_DEGUB								0
+#define GRAVITY_VM_STATS								0
+#define GRAVITY_GC_STATS								0
+#define GRAVITY_GC_STRESSTEST							0
+#define GRAVITY_GC_DEBUG								0
+#define GRAVITY_STACK_DEBUG								0
+
+#if GRAVITY_STACK_DEBUG
+#define DEBUG_STACK()									gravity_stack_dump(fiber)
+#else
+#define DEBUG_STACK()
+#endif
+
+#if GRAVITY_GC_DEBUG
+#define DEBUG_GC(...)									printf(__VA_ARGS__);printf("\n");fflush(stdout)
+#else
+#define DEBUG_GC(...)
+#endif
+
+#if GRAVITY_VM_DEGUB
+#define DEBUG_VM(...)									DEBUG_STACK();printf("%06u\t",vm->pc); printf(__VA_ARGS__);printf("\n");fflush(stdout)
+#define DEBUG_VM_NOCR(...)								DEBUG_STACK();printf("%06u\t",vm->pc); printf(__VA_ARGS__);fflush(stdout)
+#define DEBUG_VM_RAW(...)								printf(__VA_ARGS__);fflush(stdout)
+#define INC_PC											++vm->pc;
+#else
+#define DEBUG_VM(...)
+#define DEBUG_VM_NOCR(...)
+#define DEBUG_VM_RAW(...)
+#define INC_PC
+#endif
+
+#if GRAVITY_VM_STATS
+#define RESET_STATS(_vm)								bzero(_vm->nstat, sizeof(_vm->nstat)); bzero(_vm->tstat, sizeof(_vm->tstat))
+#define PRINT_STATS(_vm)								gravity_vm_stats(_vm)
+#define START_MICROBENCH(_vm)							_vm->t = nanotime()
+#define UPDATE_STATS(_vm,_op)							++_vm->nstat[_op]; _vm->tstat[_op] += millitime(_vm->t, nanotime())
+#define STAT_FRAMES_REALLOCATED(_vm)					++_vm->nfrealloc
+#define STAT_STACK_REALLOCATED(_vm)						++_vm->nsrealloc
+#else
+#define RESET_STATS(_vm)
+#define PRINT_STATS(_vm)
+#define START_MICROBENCH(_vm)
+#define UPDATE_STATS(_vm,_op)
+#define STAT_FRAMES_REALLOCATED(_vm)
+#define STAT_STACK_REALLOCATED(_vm)
+#endif
+
+#define RUNTIME_ERROR(...)								do {																\
+															report_runtime_error(vm, GRAVITY_ERROR_RUNTIME, __VA_ARGS__);	\
+															return false;													\
+														} while (0)
+
+#define RUNTIME_FIBER_ERROR(_err)						RUNTIME_ERROR("%s",_err)
+
+#define RUNTIME_WARNING(...)							do {																\
+															report_runtime_error(vm, GRAVITY_WARNING, __VA_ARGS__);			\
+														} while (0)
+
+#define SETVALUE_BOOL(idx, x)							stackstart[idx]=VALUE_FROM_BOOL(x)
+#define SETVALUE_INT(idx, x)							stackstart[idx]=VALUE_FROM_INT(x)
+#define SETVALUE_FLOAT(idx, x)							stackstart[idx]=VALUE_FROM_FLOAT(x)
+#define SETVALUE_NULL(idx)								stackstart[idx]=VALUE_FROM_NULL
+#define SETVALUE(idx, x)								stackstart[idx]=x
+#define GETVALUE_INT(v)									v.n
+#define GETVALUE_FLOAT(v)								v.f
+#define STACK_GET(idx)									stackstart[idx]
+// macro the count number of registers needed by the _f function which is the sum of local variables, temp variables and formal parameters
+#define FN_COUNTREG(_f,_nargs)							(MAXNUM(_f->nparams,_nargs) + _f->nlocals + _f->ntemps)
+
+#if GRAVITY_COMPUTED_GOTO
+#define DECLARE_DISPATCH_TABLE		static void* dispatchTable[] = {								\
+									&&RET0,			&&HALT,			&&NOP,			&&RET,			\
+									&&CALL,			&&LOAD,			&&LOADS,		&&LOADAT,		\
+									&&LOADK,		&&LOADG,		&&LOADI,		&&LOADU,		\
+									&&MOVE,			&&STORE,		&&STOREAT,		&&STOREG,		\
+									&&STOREU,		&&JUMP,			&&JUMPF,		&&SWITCH,		\
+									&&ADD,			&&SUB,			&&DIV,			&&MUL,			\
+									&&REM,			&&AND,			&&OR,			&&LT,			\
+									&&GT,			&&EQ,			&&LEQ,			&&GEQ,			\
+									&&NEQ,			&&EQQ,			&&NEQQ,			&&ISA,			\
+									&&MATCH,		&&NEG,			&&NOT,			&&LSHIFT,		\
+									&&RSHIFT,		&&BAND,			&&BOR,			&&BXOR,			\
+									&&BNOT,			&&MAPNEW,		&&LISTNEW,		&&RANGENEW,		\
+									&&SETLIST,		&&CLOSURE,		&&CLOSE,		&&RESERVED1,	\
+									&&RESERVED2,	&&RESERVED3,	&&RESERVED4,	&&RESERVED5,	\
+									&&RESERVED6														};
+#define INTERPRET_LOOP				DISPATCH();
+#define CASE_CODE(name)				START_MICROBENCH(vm); name
+#if GRAVITY_VM_STATS
+#define DISPATCH()					DEBUG_STACK();INC_PC;inst = *ip++;op = (opcode_t)OPCODE_GET_OPCODE(inst);UPDATE_STATS(vm,op);goto *dispatchTable[op];
+#else
+#define DISPATCH()					DEBUG_STACK();INC_PC;inst = *ip++;goto *dispatchTable[op = (opcode_t)OPCODE_GET_OPCODE(inst)];
+#endif
+#else
+#define DECLARE_DISPATCH_TABLE
+#define INTERPRET_LOOP				inst = *ip++;op = (opcode_t)OPCODE_GET_OPCODE(inst);UPDATE_STATS(op);switch (op)
+#define CASE_CODE(name)				case name
+#define DISPATCH()					break
+#endif
+
+#define INIT_PARAMS(n)				for (uint32_t i=n; i<func->nparams; ++i)															\
+									stackstart[i] = VALUE_FROM_UNDEFINED;
+
+#define STORE_FRAME()				frame->ip = ip
+
+#define LOAD_FRAME()				frame = &fiber->frames[fiber->nframes - 1];															\
+									stackstart = frame->stackstart;																		\
+									ip = frame->ip;																						\
+									func = frame->closure->f;																			\
+									DEBUG_VM_RAW("******\tEXEC %s (%p) ******\n", func->identifier, func);
+
+#define PUSH_FRAME(_c,_n,_r,_p)		gravity_callframe_t *cframe = gravity_new_callframe(vm, fiber);										\
+									cframe->closure = _c;																				\
+									cframe->stackstart = _n;																			\
+									cframe->ip = _c->f->bytecode;																		\
+									cframe->dest = _r;																					\
+									cframe->nargs = _p;																					\
+									cframe->outloop = false;																			\
+									cframe->args = (_c->f->useargs) ? gravity_list_from_array(vm, _p-1, _n+1) : NULL;					\
+
+#define SYNC_STACKTOP(_c,_n)		if (_c->f->tag != EXEC_TYPE_NATIVE) fiber->stacktop -= _n
+#define SETFRAME_OUTLOOP(cframe)	(cframe)->outloop = true
+
+#define COMPUTE_JUMP(value)			(func->bytecode + (value))
+
+// FAST MATH MACROS
+#define FMATH_BIN_INT(_r1,_v2,_v3,_OP)				do {SETVALUE(_r1, VALUE_FROM_INT(_v2 _OP _v3)); DISPATCH();} while(0)
+#define FMATH_BIN_FLOAT(_r1,_v2,_v3,_OP)			do {SETVALUE(_r1, VALUE_FROM_FLOAT(_v2 _OP _v3)); DISPATCH();} while(0)
+#define FMATH_BIN_BOOL(_r1,_v2,_v3,_OP)				do {SETVALUE(_r1, VALUE_FROM_BOOL(_v2 _OP _v3)); DISPATCH();} while(0)
+
+#define DEFINE_STACK_VARIABLE(_v,_r)				register gravity_value_t _v = STACK_GET(_r)
+#define DEFINE_INDEX_VARIABLE(_v,_r)				register gravity_value_t _v = (_r < MAX_REGISTERS) ? STACK_GET(_r) : VALUE_FROM_INT(_r-MAX_REGISTERS)
+
+#define NO_CHECK
+#define CHECK_ZERO(_v)								if ((VALUE_ISA_INT(_v) && (_v.n == 0)) || (VALUE_ISA_FLOAT(_v) && (_v.f == 0.0)))			\
+													RUNTIME_ERROR("Division by 0 error.")
+
+#define CHECK_FAST_BINARY_BOOL(r1,r2,r3,v2,v3,OP)	DEFINE_STACK_VARIABLE(v2,r2);																\
+													DEFINE_STACK_VARIABLE(v3,r3);																\
+													if (VALUE_ISA_BOOL(v2) && VALUE_ISA_BOOL(v3)) FMATH_BIN_BOOL(r1, v2.n, v3.n, OP);
+
+#define CHECK_FAST_UNARY_BOOL(r1,r2,v2,OP)			DEFINE_STACK_VARIABLE(v2,r2);																\
+													if (VALUE_ISA_BOOL(v2)) {SETVALUE(r1, VALUE_FROM_BOOL(OP v2.n)); DISPATCH();}
+
+// fast math only for INT and FLOAT
+#define CHECK_FAST_BINARY_MATH(r1,r2,r3,v2,v3,OP,_CHECK)																						\
+													DEFINE_STACK_VARIABLE(v2,r2);																\
+													DEFINE_STACK_VARIABLE(v3,r3);																\
+													_CHECK;																						\
+													if (VALUE_ISA_INT(v2)) {																	\
+														if (VALUE_ISA_INT(v3)) FMATH_BIN_INT(r1, v2.n, v3.n, OP);								\
+														if (VALUE_ISA_FLOAT(v3)) FMATH_BIN_FLOAT(r1, v2.n, v3.f, OP);							\
+													} else if (VALUE_ISA_FLOAT(v2)) {															\
+														if (VALUE_ISA_FLOAT(v3)) FMATH_BIN_FLOAT(r1, v2.f, v3.f, OP);							\
+														if (VALUE_ISA_INT(v3)) FMATH_BIN_FLOAT(r1, v2.f, v3.n, OP);								\
+													}
+
+#define CHECK_FAST_UNARY_MATH(r1,r2,v2,OP)			DEFINE_STACK_VARIABLE(v2,r2);	\
+													if (VALUE_ISA_INT(v2)) {SETVALUE(r1, VALUE_FROM_INT(OP v2.n)); DISPATCH();}	\
+													if (VALUE_ISA_FLOAT(v2)) {SETVALUE(r1, VALUE_FROM_FLOAT(OP v2.f)); DISPATCH();}
+
+
+#define CHECK_FAST_BINARY_REM(r1,r2,r3,v2,v3)		DEFINE_STACK_VARIABLE(v2,r2);																\
+													DEFINE_STACK_VARIABLE(v3,r3);																\
+													if (VALUE_ISA_INT(v2) && VALUE_ISA_INT(v3)) FMATH_BIN_INT(r1, v2.n, v3.n, %);
+
+#define CHECK_FAST_BINARY_BIT(r1,r2,r3,v2,v3,OP)	DEFINE_STACK_VARIABLE(v2,r2);																\
+													DEFINE_STACK_VARIABLE(v3,r3);																\
+													if (VALUE_ISA_INT(v2) && VALUE_ISA_INT(v3)) FMATH_BIN_INT(r1, v2.n, v3.n, OP);
+
+#define DECODE_BINARY_OPERATION(r1,r2,r3)			OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);	\
+													DEBUG_VM("%s %d %d %d", opcode_name(op), r1, r2, r3)
+
+#define PREPARE_FUNC_CALLN(_c,_i,_w,_N)				gravity_closure_t *_c = (gravity_closure_t *)gravity_class_lookup_closure(gravity_value_getclass(v2), cache[_i]); \
+													if (!_c || !_c->f) RUNTIME_ERROR("Unable to perform operator %s on object", opcode_name(op));	\
+													uint32_t _w = FN_COUNTREG(func, frame->nargs);													\
+													uint32_t _rneed = FN_COUNTREG(_c->f, _N);														\
+													gravity_check_stack(vm, fiber, _rneed, &stackstart)
+
+#define PREPARE_FUNC_CALL1(_c,_v1,_i,_w)			PREPARE_FUNC_CALLN(_c,_i,_w,1);															\
+													SETVALUE(_w, _v1)
+
+
+#define PREPARE_FUNC_CALL2(_c,_v1,_v2,_i,_w)		PREPARE_FUNC_CALLN(_c,_i,_w,2);															\
+													SETVALUE(_w, _v1);																		\
+													SETVALUE(_w+1, _v2)
+
+#define PREPARE_FUNC_CALL3(_c,_v1,_v2,_v3,_i,_w)	PREPARE_FUNC_CALLN(_c,_i,_w,3);															\
+													SETVALUE(_w, _v1);																		\
+													SETVALUE(_w+1, _v2);																	\
+													SETVALUE(_w+2, _v3)
+
+
+#define CALL_FUNC(_name,_c,r1,nargs,rwin)			STORE_FRAME();																			\
+													execute_op_##_name:																		\
+													switch(_c->f->tag) {																	\
+													case EXEC_TYPE_NATIVE: {																\
+														PUSH_FRAME(_c, &stackstart[rwin], r1, nargs);										\
+													} break;																				\
+													case EXEC_TYPE_INTERNAL: {																\
+														if (!_c->f->internal(vm, &stackstart[rwin], nargs, r1)) {							\
+															if (VALUE_ISA_CLOSURE(STACK_GET(r1))) {											\
+																closure = VALUE_AS_CLOSURE(STACK_GET(r1));									\
+																SETVALUE(r1, VALUE_FROM_NULL);												\
+																goto execute_op_##_name;													\
+															}																				\
+															fiber = vm->fiber;																\
+															if (fiber == NULL) return true;													\
+															if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);							\
+														}																					\
+													} break;																				\
+													case EXEC_TYPE_BRIDGED:	{																\
+														ASSERT(delegate->bridge_execute, "bridge_execute delegate callback is mandatory");	\
+														if (!delegate->bridge_execute(vm, _c->f->xdata, &stackstart[rwin], nargs, r1)) {	\
+															if (fiber->error) RUNTIME_FIBER_ERROR(fiber->error);							\
+														}																					\
+													} break;																				\
+													case EXEC_TYPE_SPECIAL:																	\
+														RUNTIME_ERROR("Unable to handle a special function in current context");			\
+														break;																				\
+													}																						\
+													LOAD_FRAME();																			\
+													SYNC_STACKTOP(_c, _rneed)
+
+#endif

+ 45 - 0
src/shared/gravity_array.h

@@ -0,0 +1,45 @@
+//
+//  gravity_array.h
+//	gravity
+//
+//  Created by Marco Bambini on 31/07/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_MUTABLE_ARRAY__
+#define __GRAVITY_MUTABLE_ARRAY__
+
+// Inspired by https://github.com/attractivechaos/klib/blob/master/kvec.h
+
+#define MARRAY_DEFAULT_SIZE			8
+#define marray_t(type)				struct {size_t n, m; type *p;}
+#define marray_init(v)				((v).n = (v).m = 0, (v).p = 0)
+#define marray_decl_init(_t,_v)		_t _v; marray_init(_v)
+#define marray_destroy(v)			if ((v).p) free((v).p)
+#define marray_get(v, i)			((v).p[(i)])
+#define marray_pop(v)				((v).p[--(v).n])
+#define marray_last(v)				((v).p[(v).n-1])
+#define marray_size(v)				((v).n)
+#define marray_max(v)				((v).m)
+#define marray_inc(v)				(++(v).n)
+#define marray_dec(v)				(--(v).n)
+#define marray_nset(v,N)			((v).n = N)
+#define marray_push(type, v, x)		{if ((v).n == (v).m) {										\
+									(v).m = (v).m? (v).m<<1 : MARRAY_DEFAULT_SIZE;				\
+									(v).p = (type*)realloc((v).p, sizeof(type) * (v).m);}		\
+									(v).p[(v).n++] = (x);}
+#define marray_resize(type, v, n)	(v).m += n; (v).p = (type*)realloc((v).p, sizeof(type) * (v).m)
+#define marray_resize0(type, v, n)	(v).p = (type*)realloc((v).p, sizeof(type) * ((v).m+n));	\
+									(v).m ? memset((v).p+(sizeof(type) * n), 0, (sizeof(type) * n)) : memset((v).p, 0, (sizeof(type) * n)); (v).m += n
+#define marray_npop(v,k)			((v).n -= k)
+#define marray_reset(v,k)			((v).n = k)
+#define marray_reset0(v)			marray_reset(v, 0)
+#define marray_set(v,i,x)			(v).p[i] = (x)
+
+// commonly used arrays
+typedef marray_t(uint16_t)			uint16_r;
+typedef marray_t(uint32_t)			uint32_r;
+typedef marray_t(void *)			void_r;
+typedef marray_t(const char *)		cstring_r;
+
+#endif

+ 76 - 0
src/shared/gravity_delegate.h

@@ -0,0 +1,76 @@
+//
+//  gravity_delegate.h
+//  gravity
+//
+//  Created by Marco Bambini on 09/12/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_DELEGATE__
+#define __GRAVITY_DELEGATE__
+
+#include <stdint.h>
+#include "gravity_value.h"
+
+// error type and code definitions
+typedef enum {
+	GRAVITY_ERROR_NONE = 0,
+	GRAVITY_ERROR_SYNTAX,
+	GRAVITY_ERROR_SEMANTIC,
+	GRAVITY_ERROR_RUNTIME,
+	GRAVITY_ERROR_IO,
+	GRAVITY_WARNING,
+} error_type_t;
+
+typedef struct {
+	uint32_t		code;
+	uint32_t		lineno;
+	uint32_t		colno;
+	uint32_t		fileid;
+	uint32_t		offset;
+} error_desc_t;
+
+#define ERROR_DESC_NONE		(error_desc_t){0,0,0,0,0}
+
+typedef void				(*gravity_log_callback)	(const char *message, void *xdata);
+typedef void				(*gravity_error_callback) (error_type_t error_type, const char *description, error_desc_t error_desc, void *xdata);
+typedef void				(*gravity_unittest_callback) (error_type_t error_type, const char *desc, const char *note, gravity_value_t value, int32_t row, int32_t col, void *xdata);
+typedef void				(*gravity_parser_callback) (void *token, void *xdata);
+typedef const char*			(*gravity_precode_callback) (void *xdata);
+typedef const char*			(*gravity_loadfile_callback) (const char *file, size_t *size, uint32_t *fileid, void *xdata);
+typedef const char*			(*gravity_filename_callback) (uint32_t fileid, void *xdata);
+
+typedef bool				(*gravity_bridge_initinstance) (gravity_vm *vm, void *xdata, gravity_instance_t *instance, gravity_value_t args[], int16_t nargs);
+typedef bool				(*gravity_bridge_setvalue) (gravity_vm *vm, void *xdata, gravity_value_t target, const char *key, gravity_value_t value);
+typedef bool				(*gravity_bridge_getvalue) (gravity_vm *vm, void *xdata, gravity_value_t target, const char *key, uint32_t vindex);
+typedef bool				(*gravity_bridge_setundef) (gravity_vm *vm, void *xdata, gravity_value_t target, const char *key, gravity_value_t value);
+typedef bool				(*gravity_bridge_getundef) (gravity_vm *vm, void *xdata, gravity_value_t target, const char *key, uint32_t vindex);
+typedef bool				(*gravity_bridge_execute)  (gravity_vm *vm, void *xdata, gravity_value_t args[], int16_t nargs, uint32_t vindex);
+typedef uint32_t			(*gravity_bridge_size) (gravity_vm *vm, gravity_object_t *obj);
+typedef void				(*gravity_bridge_free) (gravity_vm *vm, gravity_object_t *obj);
+
+typedef struct {
+	// user data
+	void						*xdata;					// optional user data transparently passed between callbacks
+	
+	// callbacks
+	gravity_log_callback		log_callback;			// log reporting callback
+	gravity_error_callback		error_callback;			// error reporting callback
+	gravity_unittest_callback	unittest_callback;		// special unit test callback
+	gravity_parser_callback		parser_callback;		// parser callback used for syntax highlight
+	gravity_precode_callback	precode_callback;		// called at parse time in order to give the opportunity to add custom source code
+	gravity_loadfile_callback	loadfile_callback;		// callback to give the opportunity to load a file from an import statement
+	gravity_filename_callback	filename_callback;		// called while reporting an error in order to be able to convert a fileid to a real filename
+	
+	// bridge
+	gravity_bridge_initinstance	bridge_initinstance;	// init class
+	gravity_bridge_setvalue		bridge_setvalue;		// setter
+	gravity_bridge_getvalue		bridge_getvalue;		// getter
+	gravity_bridge_setundef		bridge_setundef;		// setter not found
+	gravity_bridge_getundef		bridge_getundef;		// getter not found
+	gravity_bridge_execute		bridge_execute;			// execute a method/function
+	gravity_bridge_size			bridge_size;			// size of obj
+	gravity_bridge_free			bridge_free;			// free obj
+} gravity_delegate_t;
+
+#endif

+ 399 - 0
src/shared/gravity_hash.c

@@ -0,0 +1,399 @@
+//
+//  gravity_hash.c
+//  gravity
+//
+//  Created by Marco Bambini on 23/04/15.
+//  Copyright (c) 2015 CreoLabs. All rights reserved.
+//
+
+#include "gravity_hash.h"
+
+#if GRAVITYHASH_ENABLE_STATS
+#define INC_COLLISION(tbl)	++tbl->ncollision
+#define INC_RESIZE(tbl)		++tbl->nresize
+#else
+#define INC_COLLISION(tbl)
+#define INC_RESIZE(tbl)
+#endif
+
+typedef struct hash_node_s {
+	uint32_t				hash;
+	gravity_value_t			key;
+	gravity_value_t			value;
+	struct hash_node_s		*next;
+} hash_node_t;
+
+struct gravity_hash_t {
+	// internals
+	uint32_t				size;
+	uint32_t				count;
+	hash_node_t				**nodes;
+	gravity_hash_compute_fn	compute_fn;
+	gravity_hash_isequal_fn	isequal_fn;
+	gravity_hash_iterate_fn free_fn;
+	void					*data;
+	
+	// stats
+	#if GRAVITYHASH_ENABLE_STATS
+	uint32_t				ncollision;
+	uint32_t				nresize;
+	#endif
+};
+
+// http://programmers.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed
+
+/*
+	Hash algorithm used in Gravity Hash Table is DJB2 which does a pretty good job with string keys and it is fast.
+	Original algorithm is: http://www.cse.yorku.ca/~oz/hash.html
+	
+	DJBX33A (Daniel J. Bernstein, Times 33 with Addition)
+ 
+	This is Daniel J. Bernstein's popular `times 33' hash function as
+	posted by him years ago on comp.lang.c. It basically uses a function
+	like ``hash(i) = hash(i-1) 	33 + str[i]''. This is one of the best
+	known hash functions for strings. Because it is both computed very
+	fast and distributes very well.
+	
+	Why 33? (<< 5 in the code)
+	The magic of number 33, i.e. why it works better than many other
+	constants, prime or not, has never been adequately explained by
+	anyone. So I try an explanation: if one experimentally tests all
+	multipliers between 1 and 256 (as RSE did now) one detects that even
+	numbers are not useable at all. The remaining 128 odd numbers
+	(except for the number 1) work more or less all equally well. They
+	all distribute in an acceptable way and this way fill a hash table
+	with an average percent of approx. 86%.
+	
+	If one compares the Chi^2 values of the variants, the number 33 not
+	even has the best value. But the number 33 and a few other equally
+	good numbers like 17, 31, 63, 127 and 129 have nevertheless a great
+	advantage to the remaining numbers in the large set of possible
+	multipliers: their multiply operation can be replaced by a faster
+	operation based on just one shift plus either a single addition
+	or subtraction operation. And because a hash function has to both
+	distribute good _and_ has to be very fast to compute, those few
+	numbers should be preferred and seems to be the reason why Daniel J.
+	Bernstein also preferred it.
+ 
+	Why 5381?
+	1. odd number
+	2. prime number
+	3. deficient number (https://en.wikipedia.org/wiki/Deficient_number)
+	4. 001/010/100/000/101 b
+	
+	Practically any good multiplier works. I think you're worrying about
+	the fact that 31c + d doesn't cover any reasonable range of hash values
+	if c and d are between 0 and 255. That's why, when I discovered the 33 hash
+	function and started using it in my compressors, I started with a hash value
+	of 5381. I think you'll find that this does just as well as a 261 multiplier.
+ 
+	Note that the starting value of the hash (5381) makes no difference for strings
+	of equal lengths, but will play a role in generating different hash values for
+	strings of different lengths.
+ */
+
+#define HASH_SEED_VALUE						5381
+#define ROT32(x, y)							((x << y) | (x >> (32 - y)))
+#define COMPUTE_HASH(tbl,key,hash)			register uint32_t hash = murmur3_32(key, len, HASH_SEED_VALUE); hash = hash % tbl->size
+#define COMPUTE_HASH_NOMODULO(key,hash)		register uint32_t hash = murmur3_32(key, len, HASH_SEED_VALUE)
+#define RECOMPUTE_HASH(tbl,key,hash)		hash = murmur3_32(key, len, HASH_SEED_VALUE); hash = hash % tbl->size
+
+static inline uint32_t murmur3_32 (const char *key, uint32_t len, uint32_t seed) {
+	static const uint32_t c1 = 0xcc9e2d51;
+	static const uint32_t c2 = 0x1b873593;
+	static const uint32_t r1 = 15;
+	static const uint32_t r2 = 13;
+	static const uint32_t m = 5;
+	static const uint32_t n = 0xe6546b64;
+	
+	uint32_t hash = seed;
+	
+	const int nblocks = len / 4;
+	const uint32_t *blocks = (const uint32_t *) key;
+	for (int i = 0; i < nblocks; i++) {
+		uint32_t k = blocks[i];
+		k *= c1;
+		k = ROT32(k, r1);
+		k *= c2;
+		
+		hash ^= k;
+		hash = ROT32(hash, r2) * m + n;
+	}
+	
+	const uint8_t *tail = (const uint8_t *) (key + nblocks * 4);
+	uint32_t k1 = 0;
+	
+	switch (len & 3) {
+		case 3:
+			k1 ^= tail[2] << 16;
+		case 2:
+			k1 ^= tail[1] << 8;
+		case 1:
+			k1 ^= tail[0];
+			
+			k1 *= c1;
+			k1 = ROT32(k1, r1);
+			k1 *= c2;
+			hash ^= k1;
+	}
+	
+	hash ^= len;
+	hash ^= (hash >> 16);
+	hash *= 0x85ebca6b;
+	hash ^= (hash >> 13);
+	hash *= 0xc2b2ae35;
+	hash ^= (hash >> 16);
+	
+	return hash;
+}
+
+static void table_dump (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused (hashtable, data)
+	const char *k = ((gravity_string_t *)key.p)->s;
+	printf("%-20s=>\t",k);
+	gravity_value_dump(value, NULL, 0);
+}
+
+// MARK: -
+
+gravity_hash_t *gravity_hash_create (uint32_t size, gravity_hash_compute_fn compute, gravity_hash_isequal_fn isequal, gravity_hash_iterate_fn free_fn, void *data) {
+	if ((!compute) || (!isequal)) return NULL;
+	if (size == 0) size = GRAVITYHASH_DEFAULT_SIZE;
+	
+	gravity_hash_t *hashtable = (gravity_hash_t *)mem_alloc(sizeof(gravity_hash_t));
+	if (!hashtable) return NULL;
+	if (!(hashtable->nodes = mem_calloc(size, sizeof(hash_node_t*)))) {mem_free(hashtable); return NULL;}
+	
+	hashtable->compute_fn = compute;
+	hashtable->isequal_fn = isequal;
+	hashtable->free_fn = free_fn;
+	hashtable->data = data;
+	hashtable->size = size;
+	return hashtable;
+}
+
+void gravity_hash_free (gravity_hash_t *hashtable) {
+	if (!hashtable) return;
+	gravity_hash_iterate_fn free_fn = hashtable->free_fn;
+	
+	for (uint32_t n = 0; n < hashtable->size; ++n) {
+		hash_node_t *node = hashtable->nodes[n];
+		while (node) {
+			if (free_fn) free_fn(hashtable, node->key, node->value, hashtable->data);
+			hash_node_t *old_node = node;
+			node = node->next;
+			mem_free(old_node);
+		}
+	}
+	mem_free(hashtable->nodes);
+	mem_free(hashtable);
+}
+
+uint32_t gravity_hash_memsize (gravity_hash_t *hashtable) {
+	uint32_t size = sizeof(gravity_hash_t);
+	size += hashtable->size * sizeof(hash_node_t);
+	return size;
+}
+
+bool gravity_hash_isempty (gravity_hash_t *hashtable) {
+	return (hashtable->count == 0);
+}
+
+static inline int gravity_hash_resize (gravity_hash_t *hashtable) {
+	uint32_t size = (hashtable->size * 2) + 1;
+	gravity_hash_t newtbl = {
+		.size = size,
+		.count = 0,
+		.isequal_fn = hashtable->isequal_fn,
+		.compute_fn = hashtable->compute_fn
+	};
+	if (!(newtbl.nodes = mem_calloc(size, sizeof(hash_node_t*)))) return -1;
+	
+	hash_node_t *node, *next;
+	for (uint32_t n = 0; n < hashtable->size; ++n) {
+		for (node = hashtable->nodes[n]; node; node = next) {
+			next = node->next;
+			gravity_hash_insert(&newtbl, node->key, node->value);
+			// temporary disable free callback registered in hashtable
+			// because both key and values are reused in the new table
+			gravity_hash_iterate_fn free_fn = hashtable->free_fn;
+			hashtable->free_fn = NULL;
+			gravity_hash_remove(hashtable, node->key);
+			hashtable->free_fn = free_fn;
+		}
+	}
+	
+	mem_free(hashtable->nodes);
+	hashtable->size = newtbl.size;
+	hashtable->count = newtbl.count;
+	hashtable->nodes = newtbl.nodes;
+	INC_RESIZE(hashtable);
+	
+	return 0;
+}
+
+bool gravity_hash_remove (gravity_hash_t *hashtable, gravity_value_t key) {
+	register uint32_t hash = hashtable->compute_fn(key);
+	register uint32_t position = hash % hashtable->size;
+	
+	gravity_hash_iterate_fn free_fn = hashtable->free_fn;
+	hash_node_t *node = hashtable->nodes[position];
+	hash_node_t *prevnode = NULL;
+	while (node) {
+		if ((node->hash == hash) && (hashtable->isequal_fn(key, node->key))) {
+			if (free_fn) free_fn(hashtable, node->key, node->value, hashtable->data);
+			if (prevnode) prevnode->next = node->next;
+			else hashtable->nodes[position] = node->next;
+			mem_free(node);
+			hashtable->count--;
+			return true;
+		}
+		
+		prevnode = node;
+		node = node->next;
+	}
+	
+	return false;
+}
+
+bool gravity_hash_insert (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value) {
+	register uint32_t hash = hashtable->compute_fn(key);
+	register uint32_t position = hash % hashtable->size;
+	
+	hash_node_t *node = hashtable->nodes[position];
+	if (node) INC_COLLISION(hashtable);
+	
+	// check if the key is already in the table
+	while (node) {
+		if ((node->hash == hash) && (hashtable->isequal_fn(key, node->key))) {
+			node->value = value;
+			return false;
+		}
+		node = node->next;
+	}
+	
+	// resize table if the threshold is exceeded
+	// default threshold is: <table size> * <load factor GRAVITYHASH_THRESHOLD>
+	if (hashtable->count >= hashtable->size * GRAVITYHASH_THRESHOLD) {
+		if (gravity_hash_resize(hashtable) == -1) return -1;
+		// recompute position here because hashtable->size has changed!
+		position = hash % hashtable->size;
+	}
+	
+	// allocate new entry and set new data
+	if (!(node = mem_alloc(sizeof(hash_node_t)))) return -1;
+	node->key = key;
+	node->hash = hash;
+	node->value = value;
+	node->next = hashtable->nodes[position];
+	hashtable->nodes[position] = node;
+	++hashtable->count;
+	
+	return true;
+}
+
+gravity_value_t *gravity_hash_lookup (gravity_hash_t *hashtable, gravity_value_t key) {
+	register uint32_t hash = hashtable->compute_fn(key);
+	register uint32_t position = hash % hashtable->size;
+	
+	hash_node_t *node = hashtable->nodes[position];
+	while (node) {
+		if ((node->hash == hash) && (hashtable->isequal_fn(key, node->key))) return &node->value;
+		node = node->next;
+	}
+	
+	return NULL;
+}
+
+uint32_t gravity_hash_count (gravity_hash_t *hashtable) {
+	return hashtable->count;
+}
+
+uint32_t gravity_hash_compute_buffer (const char *key, uint32_t len) {
+	return murmur3_32(key, len, HASH_SEED_VALUE);
+}
+
+uint32_t gravity_hash_compute_int (gravity_int_t n) {
+	char buffer[24];
+	snprintf(buffer, sizeof(buffer), "%lld", n);
+	return murmur3_32(buffer, (uint32_t)strlen(buffer), HASH_SEED_VALUE);
+}
+
+uint32_t gravity_hash_compute_float (gravity_float_t f) {
+	char buffer[24];
+	snprintf(buffer, sizeof(buffer), "%f", f);
+	return murmur3_32(buffer, (uint32_t)strlen(buffer), HASH_SEED_VALUE);
+}
+
+void gravity_hash_stat (gravity_hash_t *hashtable) {
+	#if GRAVITYHASH_ENABLE_STATS
+	printf("==============\n");
+	printf("Collision: %d\n", hashtable->ncollision);
+	printf("Resize: %d\n", hashtable->nresize);
+	printf("Size: %d\n", hashtable->size);
+	printf("Count: %d\n", hashtable->count);
+	printf("==============\n");
+	#endif
+}
+
+void gravity_hash_transform (gravity_hash_t *hashtable, gravity_hash_transform_fn transform, void *data) {
+	if ((!hashtable) || (!transform)) return;
+	
+	for (uint32_t i=0; i<hashtable->size; ++i) {
+		hash_node_t *node = hashtable->nodes[i];
+		if (!node) continue;
+		
+		while (node) {
+			transform(hashtable, node->key, &node->value, data);
+			node = node->next;
+		}
+	}
+}
+
+void gravity_hash_iterate (gravity_hash_t *hashtable, gravity_hash_iterate_fn iterate, void *data) {
+	if ((!hashtable) || (!iterate)) return;
+	
+	for (uint32_t i=0; i<hashtable->size; ++i) {
+		hash_node_t *node = hashtable->nodes[i];
+		if (!node) continue;
+		
+		while (node) {
+			iterate(hashtable, node->key, node->value, data);
+			node = node->next;
+		}
+	}
+}
+
+void gravity_hash_iterate2 (gravity_hash_t *hashtable, gravity_hash_iterate2_fn iterate, void *data1, void *data2) {
+	if ((!hashtable) || (!iterate)) return;
+	
+	for (uint32_t i=0; i<hashtable->size; ++i) {
+		hash_node_t *node = hashtable->nodes[i];
+		if (!node) continue;
+		
+		while (node) {
+			iterate(hashtable, node->key, node->value, data1, data2);
+			node = node->next;
+		}
+	}
+}
+
+void gravity_hash_dump (gravity_hash_t *hashtable) {
+	gravity_hash_iterate(hashtable, table_dump, NULL);
+}
+
+void gravity_hash_append (gravity_hash_t *hashtable1, gravity_hash_t *hashtable2) {
+	for (uint32_t i=0; i<hashtable2->size; ++i) {
+		hash_node_t *node = hashtable2->nodes[i];
+		if (!node) continue;
+		
+		while (node) {
+			gravity_hash_insert(hashtable1, node->key, node->value);
+			node = node->next;
+		}
+	}
+}
+
+void gravity_hash_resetfree (gravity_hash_t *hashtable) {
+	hashtable->free_fn = NULL;
+}

+ 52 - 0
src/shared/gravity_hash.h

@@ -0,0 +1,52 @@
+//
+//  gravity_hash.h
+//  gravity
+//
+//  Created by Marco Bambini on 23/04/15.
+//  Copyright (c) 2015 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_HASH__
+#define __GRAVITY_HASH__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "gravity_value.h"
+
+#define GRAVITYHASH_ENABLE_STATS	1						// if 0 then stats are not enabled
+#define GRAVITYHASH_DEFAULT_SIZE	32						// default hash table size (used if 0 is passed in gravity_hash_create)
+#define GRAVITYHASH_THRESHOLD		0.75					// threshold used to decide when re-hash the table
+
+typedef struct 		gravity_hash_t	gravity_hash_t;			// opaque hash table struct
+
+// CALLBACK functions
+typedef uint32_t	(*gravity_hash_compute_fn) (gravity_value_t key);
+typedef bool		(*gravity_hash_isequal_fn) (gravity_value_t v1, gravity_value_t v2);
+typedef void  		(*gravity_hash_iterate_fn) (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data);
+typedef void  		(*gravity_hash_iterate2_fn) (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data1, void *data2);
+typedef void		(*gravity_hash_transform_fn) (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t *value, void *data);
+
+// PUBLIC functions
+gravity_hash_t		*gravity_hash_create (uint32_t size, gravity_hash_compute_fn compute, gravity_hash_isequal_fn isequal, gravity_hash_iterate_fn free, void *data);
+void				gravity_hash_free (gravity_hash_t *hashtable);
+bool				gravity_hash_isempty (gravity_hash_t *hashtable);
+bool				gravity_hash_remove  (gravity_hash_t *hashtable, gravity_value_t key);
+bool				gravity_hash_insert (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value);
+gravity_value_t		*gravity_hash_lookup (gravity_hash_t *hashtable, gravity_value_t key);
+
+uint32_t			gravity_hash_memsize (gravity_hash_t *hashtable);
+uint32_t			gravity_hash_count (gravity_hash_t *hashtable);
+uint32_t			gravity_hash_compute_buffer (const char *key, uint32_t len);
+uint32_t			gravity_hash_compute_int (gravity_int_t n);
+uint32_t			gravity_hash_compute_float (gravity_float_t f);
+void				gravity_hash_stat (gravity_hash_t *hashtable);
+void				gravity_hash_iterate (gravity_hash_t *hashtable, gravity_hash_iterate_fn iterate, void *data);
+void				gravity_hash_iterate2 (gravity_hash_t *hashtable, gravity_hash_iterate2_fn iterate, void *data1, void *data2);
+void				gravity_hash_transform (gravity_hash_t *hashtable, gravity_hash_transform_fn iterate, void *data);
+void				gravity_hash_dump (gravity_hash_t *hashtable);
+void				gravity_hash_append (gravity_hash_t *hashtable1, gravity_hash_t *hashtable2);
+void				gravity_hash_resetfree (gravity_hash_t *hashtable);
+
+#endif

+ 129 - 0
src/shared/gravity_macros.h

@@ -0,0 +1,129 @@
+//
+//  macros.h
+//  gravity
+//
+//  Created by Marco Bambini on 24/04/15.
+//  Copyright (c) 2015 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_MACROS__
+#define __GRAVITY_MACROS__
+
+#define AUTOLENGTH							UINT32_MAX
+
+// MARK: -
+#define VALUE_AS_OBJECT(x)					((x).p)
+#define VALUE_AS_STRING(x)					((gravity_string_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_FIBER(x)					((gravity_fiber_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_FUNCTION(x)				((gravity_function_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_PROPERTY(x)				((gravity_property_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_CLOSURE(x)					((gravity_closure_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_CLASS(x)					((gravity_class_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_INSTANCE(x)				((gravity_instance_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_LIST(x)					((gravity_list_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_MAP(x)						((gravity_map_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_RANGE(x)					((gravity_range_t *)VALUE_AS_OBJECT(x))
+#define VALUE_AS_CSTRING(x)					(VALUE_AS_STRING(x)->s)
+#define VALUE_AS_ERROR(x)					((const char *)(x).p)
+#define VALUE_AS_FLOAT(x)					((x).f)
+#define VALUE_AS_INT(x)						((x).n)
+#define VALUE_AS_BOOL(x)					((x).n)
+
+// MARK: -
+#define VALUE_FROM_ERROR(msg)				((gravity_value_t){.isa = NULL, .p = ((gravity_object_t *)msg)})
+#define VALUE_NOT_VALID						VALUE_FROM_ERROR(NULL)
+#define VALUE_FROM_OBJECT(obj)				((gravity_value_t){.isa = ((gravity_object_t *)(obj)->isa), .p = (gravity_object_t *)(obj)})
+#define VALUE_FROM_STRING(_vm,_s,_len)		(gravity_string_to_value(_vm, _s, _len))
+#define VALUE_FROM_CSTRING(_vm,_s)			(gravity_string_to_value(_vm, _s, AUTOLENGTH))
+#define VALUE_FROM_INT(x)					((gravity_value_t){.isa = gravity_class_int, .n = (x)})
+#define VALUE_FROM_FLOAT(x)					((gravity_value_t){.isa = gravity_class_float, .f = (x)})
+#define VALUE_FROM_NULL						((gravity_value_t){.isa = gravity_class_null, .n = 0})
+#define VALUE_FROM_UNDEFINED				((gravity_value_t){.isa = gravity_class_null, .n = 1})
+#define VALUE_FROM_BOOL(x)					((gravity_value_t){.isa = gravity_class_bool, .n = (x)})
+#define VALUE_FROM_FALSE					VALUE_FROM_BOOL(0)
+#define VALUE_FROM_TRUE						VALUE_FROM_BOOL(1)
+#define STATICVALUE_FROM_STRING(_v,_s,_l)	gravity_string_t __temp = {.isa = gravity_class_string, .s = (char *)_s, .len = (uint32_t)_l, }; \
+											__temp.hash = gravity_hash_compute_buffer(__temp.s, __temp.len); \
+											gravity_value_t _v = {.isa = gravity_class_string, .p = (gravity_object_t *)&__temp };
+// MARK: -
+#define VALUE_ISA_FUNCTION(v)				(v.isa == gravity_class_function)
+#define VALUE_ISA_INSTANCE(v)				(v.isa == gravity_class_instance)
+#define VALUE_ISA_CLOSURE(v)				(v.isa == gravity_class_closure)
+#define VALUE_ISA_FIBER(v)					(v.isa == gravity_class_fiber)
+#define VALUE_ISA_CLASS(v)					(v.isa == gravity_class_class)
+#define VALUE_ISA_STRING(v)					(v.isa == gravity_class_string)
+#define VALUE_ISA_INT(v)					(v.isa == gravity_class_int)
+#define VALUE_ISA_FLOAT(v)					(v.isa == gravity_class_float)
+#define VALUE_ISA_BOOL(v)					(v.isa == gravity_class_bool)
+#define VALUE_ISA_LIST(v)					(v.isa == gravity_class_list)
+#define VALUE_ISA_MAP(v)					(v.isa == gravity_class_map)
+#define VALUE_ISA_RANGE(v)					(v.isa == gravity_class_range)
+#define VALUE_ISA_BASIC_TYPE(v)				(VALUE_ISA_STRING(v) || VALUE_ISA_INT(v) || VALUE_ISA_FLOAT(v) || VALUE_ISA_BOOL(v))
+#define VALUE_ISA_NULLCLASS(v)				(v.isa == gravity_class_null)
+#define VALUE_ISA_NULL(v)					((v.isa == gravity_class_null) && (v.n == 0))
+#define VALUE_ISA_UNDEFINED(v)				((v.isa == gravity_class_null) && (v.n == 1))
+#define VALUE_ISA_CLASS(v)					(v.isa == gravity_class_class)
+#define VALUE_ISA_CALLABLE(v)				(VALUE_ISA_FUNCTION(v) || VALUE_ISA_CLASS(v) || VALUE_ISA_FIBER(v))
+#define VALUE_ISA_VALID(v)					(v.isa != NULL)
+#define VALUE_ISA_NOTVALID(v)				(v.isa == NULL)
+#define VALUE_ISA_ERROR(v)					VALUE_ISA_NOTVALID(v)
+
+// MARK: -
+#define OBJECT_ISA_INT(obj)					(obj->isa == gravity_class_int)
+#define OBJECT_ISA_FLOAT(obj)				(obj->isa == gravity_class_float)
+#define OBJECT_ISA_BOOL(obj)				(obj->isa == gravity_class_bool)
+#define OBJECT_ISA_NULL(obj)				(obj->isa == gravity_class_null)
+#define OBJECT_ISA_CLASS(obj)				(obj->isa == gravity_class_class)
+#define OBJECT_ISA_FUNCTION(obj)			(obj->isa == gravity_class_function)
+#define OBJECT_ISA_CLOSURE(obj)				(obj->isa == gravity_class_closure)
+#define OBJECT_ISA_INSTANCE(obj)			(obj->isa == gravity_class_instance)
+#define OBJECT_ISA_LIST(obj)				(obj->isa == gravity_class_list)
+#define OBJECT_ISA_MAP(obj)					(obj->isa == gravity_class_map)
+#define OBJECT_ISA_STRING(obj)				(obj->isa == gravity_class_string)
+#define OBJECT_ISA_UPVALUE(obj)				(obj->isa == gravity_class_upvalue)
+#define OBJECT_ISA_FIBER(obj)				(obj->isa == gravity_class_fiber)
+#define OBJECT_ISA_RANGE(obj)				(obj->isa == gravity_class_range)
+#define OBJECT_ISA_MODULE(obj)				(obj->isa == gravity_class_module)
+#define OBJECT_IS_VALID(obj)				(obj->isa != NULL)
+
+// MARK: -
+#define LIST_COUNT(v)						(marray_size(VALUE_AS_LIST(v)->array))
+#define LIST_VALUE_AT_INDEX(v, idx)			(marray_get(VALUE_AS_LIST(v)->array, idx))
+
+// MARK: -
+#define GRAVITY_JSON_FUNCTION				"function"
+#define GRAVITY_JSON_CLASS					"class"
+#define GRAVITY_JSON_ENUM					"enum"
+#define GRAVITY_JSON_MAP					"map"
+#define GRAVITY_JSON_VAR					"var"
+#define GRAVITY_JSON_GETTER					"$get"
+#define GRAVITY_JSON_SETTER					"$set"
+
+#define GRAVITY_JSON_LABELTAG				"tag"
+#define GRAVITY_JSON_LABELNAME				"name"
+#define GRAVITY_JSON_LABELTYPE				"type"
+#define GRAVITY_JSON_LABELIDENTIFIER		"identifier"
+#define GRAVITY_JSON_LABELPOOL				"pool"
+#define GRAVITY_JSON_LABELMETA				"meta"
+#define GRAVITY_JSON_LABELBYTECODE			"bytecode"
+#define GRAVITY_JSON_LABELNPARAM			"nparam"
+#define GRAVITY_JSON_LABELNLOCAL			"nlocal"
+#define GRAVITY_JSON_LABELNTEMP				"ntemp"
+#define GRAVITY_JSON_LABELNUPV				"nup"
+#define GRAVITY_JSON_LABELARGS				"args"
+#define GRAVITY_JSON_LABELINDEX				"index"
+#define GRAVITY_JSON_LABELSUPER				"super"
+#define GRAVITY_JSON_LABELNIVAR				"nivar"
+#define GRAVITY_JSON_LABELSIVAR				"sivar"
+#define GRAVITY_JSON_LABELPURITY			"purity"
+#define GRAVITY_JSON_LABELREADONLY			"readonly"
+#define GRAVITY_JSON_LABELSTORE				"store"
+#define GRAVITY_JSON_LABELINIT				"init"
+#define GRAVITY_JSON_LABELSTATIC			"static"
+#define GRAVITY_JSON_LABELPARAMS			"params"
+#define GRAVITY_JSON_LABELSTRUCT			"struct"
+
+#define GRAVITY_VM_ANONYMOUS_PREFIX			"$$"
+
+
+#endif

+ 348 - 0
src/shared/gravity_memory.c

@@ -0,0 +1,348 @@
+//
+//  gravity_memory.c
+//	gravity
+//
+//  Created by Marco Bambini on 20/03/16.
+//  Copyright © 2016 Creolabs. All rights reserved.
+//
+
+#include "gravity_memory.h"
+#if GRAVITY_MEMORY_DEBUG
+
+#include <stdlib.h>
+#include <strings.h>
+
+#if _WIN32
+#include <imagehlp.h>
+#else
+#include <execinfo.h>
+#endif
+
+static void _ptr_add (void *ptr, size_t size);
+static void _ptr_replace (void *old_ptr, void *new_ptr, size_t new_size);
+static void _ptr_remove (void *ptr);
+static uint32_t _ptr_lookup (void *ptr);
+static char **_ptr_stacktrace (size_t *nframes);
+static bool _is_internal(const char *s);
+
+#define STACK_DEPTH				128
+#define SLOT_MIN				128
+#define SLOT_NOTFOUND			UINT32_MAX
+#define BUILD_ERROR(...)		char current_error[1024]; snprintf(current_error, sizeof(current_error), __VA_ARGS__)
+#define BUILD_STACK(v1,v2)		size_t v1; char **v2 = _ptr_stacktrace(&v1)
+#define CHECK_FLAG()			if (!check_flag) return
+
+typedef struct {
+	bool					deleted;
+	void					*ptr;
+	size_t					size;
+	size_t					nrealloc;
+	
+	// record where it has been allocated/reallocated
+	size_t					nframe;
+	char					**frames;
+	
+	// record where is has been freed
+	size_t					nframe2;
+	char					**frames2;
+} memslot;
+
+typedef struct {
+	uint32_t				nalloc;
+	uint32_t				nrealloc;
+	uint32_t				nfree;
+	uint32_t				currmem;
+	uint32_t				maxmem;
+	uint32_t				nslot;		// number of slot filled with data
+	uint32_t				aslot;		// number of allocated slot
+	memslot					*slot;
+} _memdebug;
+
+static _memdebug memdebug;
+static bool check_flag = true;
+
+static void memdebug_report (char *str, char **stack, size_t nstack, memslot *slot) {
+	printf("%s\n", str);
+	for (size_t i=0; i<nstack; ++i) {
+		if (_is_internal(stack[i])) continue;
+		printf("%s\n", stack[i]);
+	}
+	
+	if (slot) {
+		printf("\nallocated:\n");
+		for (size_t i=0; i<slot->nframe; ++i) {
+			if (_is_internal(slot->frames[i])) continue;
+			printf("%s\n", slot->frames[i]);
+		}
+		
+		printf("\nfreed:\n");
+		for (size_t i=0; i<slot->nframe2; ++i) {
+			if (_is_internal(slot->frames2[i])) continue;
+			printf("%s\n", slot->frames2[i]);
+		}
+	}
+	
+	abort();
+}
+
+void memdebug_init (void) {
+	if (memdebug.slot) free(memdebug.slot);
+	bzero(&memdebug, sizeof(_memdebug));
+	
+	memdebug.slot = (memslot *) malloc(sizeof(memslot) * SLOT_MIN);
+	memdebug.aslot = SLOT_MIN;
+}
+
+void *memdebug_malloc(size_t size) {
+	void *ptr = malloc(size);
+	if (!ptr) {
+		BUILD_ERROR("Unable to allocated a block of %zu bytes", size);
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, NULL);
+		return NULL;
+	}
+	
+	_ptr_add(ptr, size);
+	return ptr;
+}
+
+void *memdebug_malloc0(size_t size) {
+	return memdebug_calloc(1, size);
+}
+
+void *memdebug_calloc(size_t num, size_t size) {
+	void *ptr = calloc(num, size);
+	if (!ptr) {
+		BUILD_ERROR("Unable to allocated a block of %zu bytes", size);
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, NULL);
+		return NULL;
+	}
+	
+	_ptr_add(ptr, num*size);
+	return ptr;
+}
+
+void *memdebug_realloc(void *ptr, size_t new_size) {
+	// ensure ptr has been previously allocated by malloc, calloc or realloc and not yet freed with free
+	uint32_t index = _ptr_lookup(ptr);
+	if (index == SLOT_NOTFOUND) {
+		BUILD_ERROR("Pointer being reallocated was now previously allocated");
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, NULL);
+		return NULL;
+	}
+	
+	void *new_ptr = realloc(ptr, new_size);
+	if (!ptr) {
+		BUILD_ERROR("Unable to reallocate a block of %zu bytes", new_size);
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, &memdebug.slot[index]);
+		return NULL;
+	}
+	
+	_ptr_replace(ptr, new_ptr, new_size);
+	return new_ptr;
+}
+
+bool memdebug_remove(void *ptr) {
+	// ensure ptr has been previously allocated by malloc, calloc or realloc and not yet freed with free
+	if (check_flag) {
+		uint32_t index = _ptr_lookup(ptr);
+		if (index == SLOT_NOTFOUND) {
+			BUILD_ERROR("Pointer being freed was not previously allocated");
+			BUILD_STACK(n, stack);
+			memdebug_report(current_error, stack, n, NULL);
+			return false;
+		}
+		
+		memslot m = memdebug.slot[index];
+		if (m.deleted) {
+			BUILD_ERROR("Pointer already freed");
+			BUILD_STACK(n, stack);
+			memdebug_report(current_error, stack, n, &m);
+			return false;
+		}
+	}
+	
+	_ptr_remove(ptr);
+	return true;
+}
+
+void memdebug_free(void *ptr) {
+	if (!memdebug_remove(ptr)) return;
+	free(ptr);
+}
+void memdebug_setcheck(bool flag) {
+	check_flag = flag;
+}
+
+void memdebug_stat(void) {
+	printf("\n========== MEMORY STATS ==========\n");
+	printf("Allocations count: %d\n", memdebug.nalloc);
+	printf("Reallocations count: %d\n", memdebug.nrealloc);
+	printf("Free count: %d\n", memdebug.nfree);
+	printf("Leaked: %d (bytes)\n", memdebug.currmem);
+	printf("Max memory usage: %d (bytes)\n", memdebug.maxmem);
+	printf("==================================\n\n");
+	
+	if (memdebug.currmem > 0) {
+		printf("\n");
+		for (uint32_t i=0; i<memdebug.nslot; ++i) {
+			memslot m = memdebug.slot[i];
+			if ((!m.ptr) || (m.deleted)) continue;
+						
+			printf("Block %p size: %zu (reallocated %zu)\n", m.ptr, m.size, m.nrealloc);
+			printf("Call stack:\n");
+			printf("===========\n");
+			for (size_t j=0; j<m.nframe; ++j) {
+				if (_is_internal(m.frames[j])) continue;
+				printf("%s\n", m.frames[j]);
+			}
+			printf("===========\n\n");
+		}
+	}
+}
+
+size_t memdebug_status (void) {
+	return memdebug.maxmem;
+}
+
+size_t memdebug_leaks (void) {
+	return memdebug.currmem;
+}
+
+// Internals
+void _ptr_add (void *ptr, size_t size) {
+	CHECK_FLAG();
+	
+	if (memdebug.nslot + 1 >= memdebug.aslot) {
+		size_t old_size = sizeof(memslot) * memdebug.nslot;
+		size_t new_size = sizeof(memslot) * SLOT_MIN;
+		memslot *new_slot = (memslot *) realloc(memdebug.slot, old_size+new_size);
+		if (!new_slot) {
+			BUILD_ERROR("Unable to reallocate internal slots");
+			memdebug_report(current_error, NULL, 0, NULL);
+			abort();
+		}
+		memdebug.slot = new_slot;
+		memdebug.aslot += SLOT_MIN;
+	}
+	
+	memslot slot = {
+		.deleted = false,
+		.ptr = ptr,
+		.size = size,
+		.nrealloc = 0,
+		.nframe2 = 0,
+		.frames = NULL
+	};
+	slot.frames = _ptr_stacktrace(&slot.nframe);
+		
+	memdebug.slot[memdebug.nslot] = slot;
+	++memdebug.nslot;
+	
+	++memdebug.nalloc;
+	memdebug.currmem += size;
+	if (memdebug.currmem > memdebug.maxmem)
+		memdebug.maxmem = memdebug.currmem;
+}
+
+void _ptr_replace (void *old_ptr, void *new_ptr, size_t new_size) {
+	CHECK_FLAG();
+	
+	uint32_t index = _ptr_lookup(old_ptr);
+	
+	if (index == SLOT_NOTFOUND) {
+		BUILD_ERROR("Unable to find old pointer to realloc");
+		memdebug_report(current_error, NULL, 0, NULL);
+	}
+	
+	memslot slot = memdebug.slot[index];
+	if (slot.deleted) {
+		BUILD_ERROR("Pointer already freed");
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, &slot);
+	}
+	size_t old_size = memdebug.slot[index].size;
+	slot.ptr = new_ptr;
+	slot.size = new_size;
+	if (slot.frames) free((void *)slot.frames);
+	slot.frames = _ptr_stacktrace(&slot.nframe);
+	++slot.nrealloc;
+	
+	memdebug.slot[index] = slot;
+	++memdebug.nrealloc;
+	memdebug.currmem += (new_size - old_size);
+	if (memdebug.currmem > memdebug.maxmem)
+		memdebug.maxmem = memdebug.currmem;
+}
+
+void _ptr_remove (void *ptr) {
+	CHECK_FLAG();
+	
+	uint32_t index = _ptr_lookup(ptr);
+	
+	if (index == SLOT_NOTFOUND) {
+		BUILD_ERROR("Unable to find old pointer to realloc");
+		memdebug_report(current_error, NULL, 0, NULL);
+	}
+	
+	memslot slot = memdebug.slot[index];
+	if (slot.deleted) {
+		BUILD_ERROR("Pointer already freed");
+		BUILD_STACK(n, stack);
+		memdebug_report(current_error, stack, n, &slot);
+	}
+	
+	size_t old_size = memdebug.slot[index].size;
+	memdebug.slot[index].deleted = true;
+	memdebug.slot[index].frames2 = _ptr_stacktrace(&memdebug.slot[index].nframe2);
+	
+	++memdebug.nfree;
+	memdebug.currmem -= old_size;
+}
+
+uint32_t _ptr_lookup (void *ptr) {
+	for (uint32_t i=0; i<memdebug.nslot; ++i) {
+		if (memdebug.slot[i].ptr == ptr) return i;
+	}
+	return SLOT_NOTFOUND;
+}
+
+char **_ptr_stacktrace (size_t *nframes) {
+	#if _WIN32
+	http://www.codeproject.com/Articles/11132/Walking-the-callstack
+	https://spin.atomicobject.com/2013/01/13/exceptions-stack-traces-c/
+	#else
+	void *callstack[STACK_DEPTH];
+	int n = backtrace(callstack, STACK_DEPTH);
+	char **strs = backtrace_symbols(callstack, n);
+	*nframes = (size_t)n;
+	return strs;
+	#endif
+}
+
+// Default callback
+bool _is_internal(const char *s) {
+	static const char *reserved[] = {"??? ", "libdyld.dylib ", "memdebug_", "_ptr_", NULL};
+	
+	const char **r = reserved;
+	while (*r) {
+		if (strstr(s, *r)) return true;
+		++r;
+	}
+	return false;
+}
+
+#undef STACK_DEPTH
+#undef SLOT_MIN
+#undef SLOT_NOTFOUND
+#undef BUILD_ERROR
+#undef BUILD_STACK
+#undef CHECK_FLAG
+
+#endif
+
+
+

+ 57 - 0
src/shared/gravity_memory.h

@@ -0,0 +1,57 @@
+//
+//  gravity_memory.h
+//	gravity
+//
+//  Created by Marco Bambini on 20/03/16.
+//  Copyright © 2016 Creolabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_MEMORY__
+#define __GRAVITY_MEMORY__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+// memory debugger must be turned on ONLY with GuardMalloc ON
+#define GRAVITY_MEMORY_DEBUG		0
+
+#if GRAVITY_MEMORY_DEBUG
+#define mem_init()					memdebug_init()
+#define mem_stat()					memdebug_stat()
+#define mem_alloc					memdebug_malloc0
+#define mem_calloc					memdebug_calloc
+#define mem_realloc					memdebug_realloc
+#define mem_free(v)					memdebug_free((void *)v)
+#define mem_check(v)				memdebug_setcheck(v)
+#define mem_status					memdebug_status
+#define mem_leaks()					memdebug_leaks()
+#define mem_remove					memdebug_remove
+#else
+#define mem_init()
+#define mem_stat()
+#define mem_alloc(size)				calloc(1, size)
+#define mem_calloc					calloc
+#define mem_realloc					realloc
+#define mem_free(v)					free((void *)v)
+#define mem_check(v)
+#define mem_status()				0
+#define mem_leaks()					0
+#define mem_remove(_v)
+#endif
+
+#if GRAVITY_MEMORY_DEBUG
+void	memdebug_init(void);
+void	*memdebug_malloc(size_t size);
+void	*memdebug_malloc0(size_t size);
+void	*memdebug_calloc(size_t num, size_t size);
+void	*memdebug_realloc(void *ptr, size_t new_size);
+void	memdebug_free(void *ptr);
+size_t	memdebug_leaks (void);
+size_t	memdebug_status (void);
+void	memdebug_setcheck(bool flag);
+void	memdebug_stat(void);
+bool	memdebug_remove(void *ptr);
+#endif
+
+#endif

+ 225 - 0
src/shared/gravity_opcodes.h

@@ -0,0 +1,225 @@
+//
+//  gravity_opcodes.h
+//  gravity
+//
+//  Created by Marco Bambini on 24/09/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_OPCODES__
+#define __GRAVITY_OPCODES__
+
+/*
+		Big-endian vs Little-endian machines
+ 
+		ARM architecture runs both little & big endianess, but the android, iO6, and windows phone platforms run little endian.
+		95% of modern desktop computers are little-endian.
+		All x86 desktops (which is nearly all desktops with the demise of the PowerPC-based Macs several years ago) are little-endian.
+		It's probably actually a lot more than 95% nowadays. PowerPC was the only non-x86 architecture that has been popular for desktop
+		computers in the last 20 years and Apple abandoned it in favor of x86.
+		Sparc, Alpha, and Itanium did exist, but they were all very rare in the desktop market.
+ */
+
+/*
+		Instructions are 32bit in lenght
+ 
+		// 2 registers and 1 register/constant
+		+------------------------------------+
+		|  OP  |   Ax   |   Bx   |    Cx/K   |
+		+------------------------------------+
+ 
+		// instructions with no parameters
+		+------------------------------------+
+		|  OP  |0                            |
+		+------------------------------------+
+ 
+		// unconditional JUMP
+		+------------------------------------+
+		|  OP  |             N1              |
+		+------------------------------------+
+ 
+		// LOADI and JUMPF
+		+------------------------------------+
+		|  OP  |   Ax   |S|       N2         |
+		+------------------------------------+
+
+		OP   =>  6 bits
+		Ax   =>  8 bits
+ 		Bx   =>  8 bits
+ 		Cx/K =>  8/10 bits
+		S    =>  1 bit
+		N1   => 26 bits
+		N2   => 17 bits
+ */
+
+typedef enum {
+	
+	//		****************************************************************************************************
+	//		56 OPCODE INSTRUCTIONS (for a register based virtual machine)
+	//		opcode is a 6 bit value so at maximum 2^6 = 64 opcodes can be declared
+	//		****************************************************************************************************
+	//
+	//		MNEMONIC		PARAMETERS		DESCRIPTION								OPERATION
+	//		--------		----------		------------------------------------	----------------------------
+	//
+											//	*** GENERAL COMMANDS (5) ***
+			RET0 = 0,		//	NONE		//	return nothing from a function		MUST BE THE FIRST OPCODE (because an implict 0 is added
+											//										as a safeguard at the end of any bytecode
+			HALT,			//	NONE		//	stop VM execution
+			NOP,			//	NONE		//	NOP									http://en.wikipedia.org/wiki/NOP
+			RET,			//  A			//	return from a function				R(-1) = R(A)
+			CALL,			//	A, B, C		//	call a function						R(A) = B(C0...Cn) B is callable object and C is num args
+	
+											//	*** LOAD/STORE OPERATIONS (11) ***
+			LOAD,			//	A, B, C		//	load C from B and store in A		R(A) = R(B)[C]
+			LOADS,			//	A, B, C		//	load C from B and store in A		R(A) = R(B)[C] (super variant)
+			LOADAT,			//	A, B, C		//	load C from B and store in A		R(A) = R(B)[C]
+			LOADK,			//	A, B		//	load constant into register			R(A) = K(B)
+			LOADG,			//	A, B		//	load global into register			R(A) = G[K(B)]
+			LOADI,			//	A, B		//	load integer into register			R(A) = I
+			LOADU,			//	A, B		//	load upvalue into register			R(A) = U(B)
+			MOVE,			//	A, B		//	move registers						R(A) = R(B)
+			STORE,			//	A, B, C		//	store A into R(B)[C]				R(B)[C] = R(A)
+			STOREAT,		//	A, B, C		//	store A into R(B)[C]				R(B)[C] = R(A)
+			STOREG,			//	A, B		//	store global						G[K(B)] = R(A)
+			STOREU,			//	A, B		//	store upvalue						U(B) = R(A)
+	
+											//	*** JUMP OPERATIONS (3) ***
+			JUMP,			//	A			//	unconditional jump					PC += A
+			JUMPF,			//	A, B		//	jump if R(A) is false				(R(A) == 0)	? PC += B : 0
+			SWITCH,			//				//	switch statement
+	
+											//	*** MATH OPERATIONS (19) ***
+			ADD,			//	A, B, C		//	add operation						R(A) = R(B) + R(C)
+			SUB,			//	A, B, C		//	sub operation						R(A) = R(B) - R(C)
+			DIV,			//	A, B, C		//	div operation						R(A) = R(B) / R(C)
+			MUL,			//	A, B, C		//	mul operation						R(A) = R(B) * R(C)
+			REM,			//	A, B, C		//	rem operation						R(A) = R(B) % R(C)
+			AND,			//	A, B, C		//	and operation						R(A) = R(B) && R(C)
+			OR,				//	A, B, C		//	or operation						R(A) = R(B) || R(C)
+			LT,				//	A, B, C		//	< comparison						R(A) = R(B) < R(C)
+			GT,				//	A, B, C		//	> comparison						R(A) = R(B) > R(C)
+			EQ,				//	A, B, C		//	== comparison						R(A) = R(B) == R(C)
+			LEQ,			//	A, B, C		//	<= comparison						R(A) = R(B) <= R(C)
+			GEQ,			//	A, B, C		//	>= comparison						R(A) = R(B) >= R(C)
+			NEQ,			//	A, B, C		//	!= comparison						R(A) = R(B) != R(C)
+			EQQ,			//	A, B, C		//	=== comparison						R(A) = R(B) === R(C)
+			NEQQ,			//	A, B, C		//	!== comparison						R(A) = R(B) !== R(C)
+			ISA,			//	A, B, C		//	isa comparison						R(A) = R(A).class == R(B).class
+			MATCH,			//	A, B, C		//	=~ pattern match					R(A) = R(B) =~ R(C)
+			NEG,			//	A, B		//	neg operation						R(A) = -R(B)
+			NOT,			//	A, B		//	not operation						R(A) = !R(B)
+	
+											// *** BIT OPERATIONS (6) ***
+			LSHIFT,			//	A, B, C		// shift left							R(A) = R(B) << R(C)
+			RSHIFT,			//	A, B, C		// shift right							R(A) = R(B) >> R(C)
+			BAND,			//	A, B, C		// bit and								R(A) = R(B) & R(C)
+			BOR,			//	A, B, C		// bit or								R(A) = R(B) | R(C)
+			BXOR,			//	A, B, C		// bit xor								R(A) = R(B) ^ R(C)
+			BNOT,			//	A, B		// bit not								R(A) = ~R(B)
+	
+											// *** ARRAY/MAP/RANGE OPERATIONS (4) ***
+			MAPNEW,			//	A, B		//	create a new map					R(A) = Alloc a MAP(B)
+			LISTNEW,		//	A, B		//	create a new array					R(A) = Alloc a LIST(B)
+			RANGENEW,		//	A, B, C, f	//	create a new range					R(A) = Alloc a RANGE(B,C) f flag tells if B inclusive or exclusive
+			SETLIST,		//	A, B, C		//	set list/map items
+	
+											// *** CLOSURES (2) ***
+			CLOSURE,		//	A, B		//	create a new closure				R(A) = closure(K(B))
+			CLOSE,			//	A			//  close all upvalues from R(A)
+	
+											// *** UNUSED (6) ***
+			RESERVED1,		//				// reserved for future use
+			RESERVED2,		//				// reserved for future use
+			RESERVED3,		//				// reserved for future use
+			RESERVED4,		//				// reserved for future use
+			RESERVED5,		//				// reserved for future use
+			RESERVED6		//				// reserved for future use
+} opcode_t;
+
+#define GRAVITY_LATEST_OPCODE			RESERVED6	// used in some debug code so it is very useful to define the latest opcode here
+
+typedef enum {
+	GRAVITY_NOTFOUND_INDEX				= 0,
+	GRAVITY_ADD_INDEX,
+	GRAVITY_SUB_INDEX,
+	GRAVITY_DIV_INDEX,
+	GRAVITY_MUL_INDEX,
+	GRAVITY_REM_INDEX,
+	GRAVITY_AND_INDEX,
+	GRAVITY_OR_INDEX,
+	GRAVITY_CMP_INDEX,
+	GRAVITY_EQQ_INDEX,
+	GRAVITY_ISA_INDEX,
+	GRAVITY_MATCH_INDEX,
+	GRAVITY_NEG_INDEX,
+	GRAVITY_NOT_INDEX,
+	GRAVITY_LSHIFT_INDEX,
+	GRAVITY_RSHIFT_INDEX,
+	GRAVITY_BAND_INDEX,
+	GRAVITY_BOR_INDEX,
+	GRAVITY_BXOR_INDEX,
+	GRAVITY_BNOT_INDEX,
+	GRAVITY_LOAD_INDEX,
+	GRAVITY_LOADS_INDEX,
+	GRAVITY_LOADAT_INDEX,
+	GRAVITY_STORE_INDEX,
+	GRAVITY_STOREAT_INDEX,
+	GRAVITY_INT_INDEX,
+	GRAVITY_FLOAT_INDEX,
+	GRAVITY_BOOL_INDEX,
+	GRAVITY_STRING_INDEX,
+	GRAVITY_EXEC_INDEX,
+	GRAVITY_VTABLE_SIZE					// MUST BE LAST ENTRY IN THIS ENUM
+} GRAVITY_VTABLE_INDEX;
+
+#define GRAVITY_OPERATOR_ADD_NAME		"+"
+#define GRAVITY_OPERATOR_SUB_NAME		"-"
+#define GRAVITY_OPERATOR_DIV_NAME		"/"
+#define GRAVITY_OPERATOR_MUL_NAME		"*"
+#define GRAVITY_OPERATOR_REM_NAME		"%"
+#define GRAVITY_OPERATOR_AND_NAME		"&&"
+#define GRAVITY_OPERATOR_OR_NAME		"||"
+#define GRAVITY_OPERATOR_CMP_NAME		"=="
+#define GRAVITY_OPERATOR_EQQ_NAME		"==="
+#define GRAVITY_OPERATOR_ISA_NAME		"isa"
+#define GRAVITY_OPERATOR_MATCH_NAME		"=~"
+#define GRAVITY_OPERATOR_NEG_NAME		"neg"
+#define GRAVITY_OPERATOR_NOT_NAME		"!"
+#define GRAVITY_OPERATOR_LSHIFT_NAME	"<<"
+#define GRAVITY_OPERATOR_RSHIFT_NAME	">>"
+#define GRAVITY_OPERATOR_BAND_NAME		"&"
+#define GRAVITY_OPERATOR_BOR_NAME		"|"
+#define GRAVITY_OPERATOR_BXOR_NAME		"^"
+#define GRAVITY_OPERATOR_BNOT_NAME		"~"
+#define GRAVITY_INTERNAL_LOAD_NAME		"load"
+#define GRAVITY_INTERNAL_LOADS_NAME		"loads"
+#define GRAVITY_INTERNAL_STORE_NAME		"store"
+#define GRAVITY_INTERNAL_LOADAT_NAME	"loadat"
+#define GRAVITY_INTERNAL_STOREAT_NAME	"storeat"
+#define GRAVITY_INTERNAL_NOTFOUND_NAME	"notfound"
+#define GRAVITY_INTERNAL_EXEC_NAME		"exec"
+#define GRAVITY_INTERNAL_LOOP_NAME		"loop"
+
+#define GRAVITY_CLASS_INT_NAME			"Int"
+#define GRAVITY_CLASS_FLOAT_NAME		"Float"
+#define GRAVITY_CLASS_BOOL_NAME			"Bool"
+#define GRAVITY_CLASS_STRING_NAME		"String"
+#define GRAVITY_CLASS_OBJECT_NAME		"Object"
+#define GRAVITY_CLASS_CLASS_NAME		"Class"
+#define GRAVITY_CLASS_NULL_NAME			"Null"
+#define GRAVITY_CLASS_FUNCTION_NAME		"Function"
+#define GRAVITY_CLASS_FIBER_NAME		"Fiber"
+#define GRAVITY_CLASS_INSTANCE_NAME		"Instance"
+#define GRAVITY_CLASS_CLOSURE_NAME		"Closure"
+#define GRAVITY_CLASS_LIST_NAME			"List"
+#define GRAVITY_CLASS_MAP_NAME			"Map"
+#define GRAVITY_CLASS_RANGE_NAME		"Range"
+#define GRAVITY_CLASS_UPVALUE_NAME		"Upvalue"
+
+#define GRAVITY_CLASS_SYSTEM_NAME		"System"
+#define GRAVITY_SYSTEM_PRINT_NAME		"print"
+#define GRAVITY_SYSTEM_PUT_NAME			"put"
+#define GRAVITY_SYSTEM_NANOTIME_NAME	"nanotime"
+
+#endif

+ 1897 - 0
src/shared/gravity_value.c

@@ -0,0 +1,1897 @@
+//
+//  gravity_value.c
+//  gravity
+//
+//  Created by Marco Bambini on 11/12/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include "gravity_hash.h"
+#include "gravity_core.h"
+#include "gravity_value.h"
+#include "gravity_utils.h"
+#include "gravity_memory.h"
+#include "gravity_macros.h"
+#include "gravity_opcodes.h"
+#include "gravity_vmmacros.h"
+
+static void gravity_function_special_serialize (gravity_function_t *f, const char *key, json_t *json);
+static gravity_map_t *gravity_map_deserialize (gravity_vm *vm, json_value *json);
+
+static void gravity_hash_serialize (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(table)
+	json_t *json = (json_t *)data;
+	
+	if (VALUE_ISA_FUNCTION(value)) {
+		gravity_function_t *f = VALUE_AS_FUNCTION(value);
+		if (f->tag == EXEC_TYPE_SPECIAL) gravity_function_special_serialize(f, VALUE_AS_CSTRING(key), json);
+		else {
+			// there was an issue here due to the fact that when a subclass needs to use a $init from a superclass
+			// internally it has a unique name (key) but f->identifier continue to be called $init
+			// without this fix the subclass would continue to have 2 or more $init functions
+			gravity_string_t *s = VALUE_AS_STRING(key);
+			bool is_super_function = ((s->len > 5) && (string_casencmp(s->s, CLASS_INTERNAL_INIT_NAME, 5) == 0));
+			const char *saved = f->identifier;
+			if (is_super_function) f->identifier = s->s;
+			gravity_function_serialize(f, json);
+			if (is_super_function) f->identifier = saved;
+		}
+	}
+	else if (VALUE_ISA_CLASS(value)) {
+		gravity_class_serialize(VALUE_AS_CLASS(value), json);
+	}
+	else
+		assert(0);
+}
+
+void gravity_hash_keyvaluefree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(table)
+	gravity_vm *vm = (gravity_vm *)data;
+	gravity_value_free(vm, key);
+	gravity_value_free(vm, value);
+}
+
+void gravity_hash_keyfree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(table, value)
+	gravity_vm *vm = (gravity_vm *)data;
+	gravity_value_free(vm, key);
+}
+
+void gravity_hash_valuefree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data) {
+	#pragma unused(table, key)
+	gravity_vm *vm = (gravity_vm *)data;
+	gravity_value_free(vm, value);
+}
+
+static void gravity_hash_internalsize (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data1, void *data2) {
+	#pragma unused(table)
+	uint32_t	*size = (uint32_t *)data1;
+	gravity_vm	*vm = (gravity_vm *)data2;
+	*size = gravity_value_size(vm, key);
+	*size += gravity_value_size(vm, value);
+}
+
+static void gravity_hash_gray (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data1) {
+	#pragma unused(table)
+	gravity_vm *vm = (gravity_vm *)data1;
+	gravity_gray_value(vm, key);
+	gravity_gray_value(vm, value);
+}
+
+// MARK: -
+
+gravity_module_t *gravity_module_new (gravity_vm *vm, const char *identifier) {
+	gravity_module_t *m = (gravity_module_t *)mem_alloc(sizeof(gravity_module_t));
+	assert(m);
+	
+	m->isa = gravity_class_module;
+	m->identifier = string_dup(identifier);
+	m->htable = gravity_hash_create(0, gravity_value_hash, gravity_value_equals, gravity_hash_keyvaluefree, (void*)vm);
+	
+	gravity_vm_transfer(vm, (gravity_object_t*)m);
+	return m;
+}
+
+void gravity_module_free (gravity_vm *vm, gravity_module_t *m) {
+	#pragma unused(vm)
+	
+	if (m->identifier) mem_free(m->identifier);
+	gravity_hash_free(m->htable);
+	mem_free(m);
+}
+
+uint32_t gravity_module_size (gravity_vm *vm, gravity_module_t *m) {
+	uint32_t hash_size = 0;
+	gravity_hash_iterate2(m->htable, gravity_hash_internalsize, (void*)&hash_size, (void*)vm);
+	return (sizeof(gravity_module_t)) + string_size(m->identifier) + hash_size + gravity_hash_memsize(m->htable);
+}
+
+void gravity_module_blacken (gravity_vm *vm, gravity_module_t *m) {
+	gravity_vm_memupdate(vm, gravity_module_size(vm, m));
+	gravity_hash_iterate(m->htable, gravity_hash_gray, (void*)vm);
+}
+
+// MARK: -
+
+void gravity_class_bind (gravity_class_t *c, const char *key, gravity_value_t value) {
+	if (VALUE_ISA_CLASS(value)) {
+		// set outer has_outer when bind a class inside another class
+		gravity_class_t *obj = VALUE_AS_CLASS(value);
+		obj->has_outer = true;
+	}
+	gravity_hash_insert(c->htable, VALUE_FROM_CSTRING(NULL, key), value);
+}
+
+gravity_class_t *gravity_class_getsuper (gravity_class_t *c) {
+	return c->superclass;
+}
+
+bool gravity_class_grow (gravity_class_t *c, uint32_t n) {
+	if (c->ivars) mem_free(c->ivars);
+	if (c->nivars + n >= MAX_IVARS) return false;
+	c->nivars += n;
+	c->ivars = (gravity_value_t *)mem_alloc(c->nivars * sizeof(gravity_value_t));
+	for (uint32_t i=0; i<c->nivars; ++i) c->ivars[i] = VALUE_FROM_NULL;
+	return true;
+}
+
+bool gravity_class_setsuper (gravity_class_t *baseclass, gravity_class_t *superclass) {
+	if (!superclass) return true;
+	baseclass->superclass = superclass;
+	
+	// check meta class first
+	gravity_class_t *supermeta = (superclass) ? gravity_class_get_meta(superclass) : NULL;
+	uint32_t n1 = (supermeta) ? supermeta->nivars : 0;
+	if (n1) if (!gravity_class_grow (gravity_class_get_meta(baseclass), n1)) return false;
+	
+	// then check real class
+	uint32_t n2 = (superclass) ? superclass->nivars : 0;
+	if (n2) if (!gravity_class_grow (baseclass, n2)) return false;
+	
+	return true;
+}
+
+gravity_class_t *gravity_class_new_single (gravity_vm *vm, const char *identifier, uint32_t nivar) {
+	gravity_class_t *c = (gravity_class_t *)mem_alloc(sizeof(gravity_class_t));
+	assert(c);
+	
+	c->isa = gravity_class_class;
+	c->identifier = string_dup(identifier);
+	c->superclass = NULL;
+	c->nivars = nivar;
+	c->htable = gravity_hash_create(0, gravity_value_hash, gravity_value_equals, gravity_hash_keyfree, NULL);
+	if (nivar) {
+		c->ivars = (gravity_value_t *)mem_alloc(nivar * sizeof(gravity_value_t));
+		for (uint32_t i=0; i<nivar; ++i) c->ivars[i] = VALUE_FROM_NULL;
+	}
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) c);
+	return c;
+}
+
+gravity_class_t *gravity_class_new_pair (gravity_vm *vm, const char *identifier, gravity_class_t *superclass, uint32_t nivar, uint32_t nsvar) {
+	// each class must have a valid identifier
+	if (!identifier) return NULL;
+	
+	char buffer[512];
+	snprintf(buffer, sizeof(buffer), "%s meta", identifier);
+	
+	gravity_class_t *supermeta = (superclass) ? gravity_class_get_meta(superclass) : NULL;
+	uint32_t n1 = (supermeta) ? supermeta->nivars : 0;
+	
+	gravity_class_t *meta = gravity_class_new_single(vm, buffer, nsvar + n1);
+	meta->objclass = gravity_class_object;
+	gravity_class_setsuper(meta, gravity_class_class);
+	
+	uint32_t n2 = (superclass) ? superclass->nivars : 0;
+	gravity_class_t *c = gravity_class_new_single(vm, identifier, nivar + n2);
+	c->objclass = meta;
+	
+	// a class without a superclass in a subclass of Object.
+	gravity_class_setsuper(c, (superclass) ? superclass : gravity_class_object);
+	
+	return c;
+}
+
+gravity_class_t *gravity_class_get_meta (gravity_class_t *c) {
+	// meta classes have objclass set to class object
+	if (c->objclass == gravity_class_object) return c;
+	return c->objclass;
+}
+
+bool gravity_class_is_meta (gravity_class_t *c) {
+	// meta classes have objclass set to class object
+	return (c->objclass == gravity_class_object);
+}
+
+uint32_t gravity_class_count_ivars (gravity_class_t *c) {
+	return (uint32_t)c->nivars;
+}
+
+int16_t gravity_class_add_ivar (gravity_class_t *c, const char *identifier) {
+	#pragma unused(identifier)
+	// TODO: add identifier in array (for easier debugging)
+	++c->nivars;
+	return c->nivars-1; // its a C array so index is 0 based
+}
+
+void gravity_class_dump (gravity_class_t *c) {
+	gravity_hash_dump(c->htable);
+}
+
+void gravity_class_setxdata (gravity_class_t *c, void *xdata) {
+	c->xdata = xdata;
+}
+
+void gravity_class_serialize (gravity_class_t *c, json_t *json) {
+	json_begin_object(json, c->identifier);
+	json_add_cstring(json, GRAVITY_JSON_LABELTYPE, GRAVITY_JSON_CLASS);			// MANDATORY 1st FIELD
+	json_add_cstring(json, GRAVITY_JSON_LABELIDENTIFIER, c->identifier);		// MANDATORY 2nd FIELD
+	
+	// avoid write superclass name if it is the default Object one
+	if ((c->superclass) && (c->superclass->identifier) && (strcmp(c->superclass->identifier, GRAVITY_CLASS_OBJECT_NAME) != 0)) {
+		json_add_cstring(json, GRAVITY_JSON_LABELSUPER, c->superclass->identifier);
+	}
+	
+	// get c meta class
+	gravity_class_t *meta = gravity_class_get_meta(c);
+	
+	// number of instance (and static) variables
+	json_add_int(json, GRAVITY_JSON_LABELNIVAR, c->nivars);
+	if ((c != meta) && (meta->nivars > 0)) json_add_int(json, GRAVITY_JSON_LABELSIVAR, meta->nivars);
+	
+	// struct flag
+	if (c->is_struct) json_add_bool(json, GRAVITY_JSON_LABELSTRUCT, true);
+	
+	// serialize htable
+	if (c->htable) {
+		gravity_hash_iterate(c->htable, gravity_hash_serialize, (void *)json);
+	}
+	
+	// serialize meta class
+	if (c != meta) {
+		// further proceed only if it has something to be serialized
+		if ((meta->htable) && (gravity_hash_count(meta->htable) > 0)) {
+			json_begin_array(json, GRAVITY_JSON_LABELMETA);
+			gravity_hash_iterate(meta->htable, gravity_hash_serialize, (void *)json);
+			json_end_array(json);
+		}
+	}
+	
+	json_end_object(json);
+}
+
+gravity_class_t *gravity_class_deserialize (gravity_vm *vm, json_value *json) {
+	// sanity check
+	if (json->type != json_object) return NULL;
+	if (json->u.object.length < 3) return NULL;
+	
+	// scan identifier
+	json_value *value = json->u.object.values[1].value;
+	const char *key = json->u.object.values[1].name;
+	
+	// sanity check identifier
+	if (string_casencmp(key, GRAVITY_JSON_LABELIDENTIFIER, strlen(key)) != 0) return NULL;
+	assert(value->type == json_string);
+	
+	// create class and meta
+	gravity_class_t *c = gravity_class_new_pair(vm, value->u.string.ptr, NULL, 0, 0);
+	DEBUG_DESERIALIZE("DESERIALIZE CLASS: %p %s\n", c, value->u.string.ptr);
+	
+	// get its meta class
+	gravity_class_t *meta = gravity_class_get_meta(c);
+	
+	uint32_t n = json->u.object.length;
+	for (uint32_t i=2; i<n; ++i) { // from 2 to skip type and identifier
+		
+		// parse values
+		value = json->u.object.values[i].value;
+		key = json->u.object.values[i].name;
+		
+		if (value->type != json_object) {
+			
+			// super
+			if (string_casencmp(key, GRAVITY_JSON_LABELSUPER, strlen(key)) == 0) {
+				// the trick here is to re-use a runtime field to store a temporary static data like superclass name
+				// (only if different than the default Object one)
+				if (strcmp(value->u.string.ptr, GRAVITY_CLASS_OBJECT_NAME) != 0) {
+					c->xdata = (void *)string_dup(value->u.string.ptr);
+				}
+				continue;
+			}
+			
+			// nivar
+			if (string_casencmp(key, GRAVITY_JSON_LABELNIVAR, strlen(key)) == 0) {
+				gravity_class_grow(c, (uint32_t)value->u.integer);
+				continue;
+			}
+			
+			// sivar
+			if (string_casencmp(key, GRAVITY_JSON_LABELSIVAR, strlen(key)) == 0) {
+				gravity_class_grow(meta, (uint32_t)value->u.integer);
+				continue;
+			}
+			
+			// struct
+			if (string_casencmp(key, GRAVITY_JSON_LABELSTRUCT, strlen(key)) == 0) {
+				c->is_struct = true;
+				continue;
+			}
+			
+			// meta
+			if (string_casencmp(key, GRAVITY_JSON_LABELMETA, strlen(key)) == 0) {
+				uint32_t m = value->u.array.length;
+				for (uint32_t j=0; j<m; ++j) {
+					json_value *r = value->u.array.values[j];
+					if (r->type != json_object) continue;
+					gravity_object_t *obj = NULL;
+					bool result = gravity_object_deserialize(vm, r, &obj);
+					
+					const char *identifier = obj->identifier;
+					if (OBJECT_ISA_FUNCTION(obj)) obj = (gravity_object_t *)gravity_closure_new(vm, (gravity_function_t *)obj);
+					if ((result) && (obj)) gravity_class_bind(meta, identifier, VALUE_FROM_OBJECT(obj));
+					else goto abort_load;
+				}
+				continue;
+			}
+			
+			// error here
+			assert(0);
+		}
+		
+		if (value->type == json_object) {
+			gravity_object_t *obj = NULL;
+			if (!gravity_object_deserialize(vm, value, &obj)) goto abort_load;
+			if (!obj) goto abort_load;
+			
+			const char *identifier = obj->identifier;
+			if (OBJECT_ISA_FUNCTION(obj)) obj = (gravity_object_t *)gravity_closure_new(vm, (gravity_function_t *)obj);
+			gravity_class_bind(c, identifier, VALUE_FROM_OBJECT(obj));
+		}
+	}
+	
+	return c;
+	
+abort_load:
+	if (c) gravity_class_free(vm, c);
+	return NULL;
+}
+
+static void gravity_class_free_internal (gravity_vm *vm, gravity_class_t *c, bool skip_base) {
+	if (skip_base && gravity_iscore_class(c)) return;
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)c));
+	
+	// check if bridged data needs to be freeed too
+	if (c->xdata && vm) {
+		gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+		if (delegate && delegate->bridge_free) delegate->bridge_free(vm, (gravity_object_t *)c);
+	}
+	
+	if (c->identifier) mem_free((void *)c->identifier);
+	if (!skip_base) {
+		// base classes have functions not registered inside VM so manually free all of them
+		gravity_hash_iterate(c->htable, gravity_hash_valuefree, NULL);
+	}
+	
+	gravity_hash_free(c->htable);
+	if (c->ivars) mem_free((void *)c->ivars);
+	mem_free((void *)c);
+}
+	
+void gravity_class_free_core (gravity_vm *vm, gravity_class_t *c) {
+	gravity_class_free_internal(vm, c, false);
+}
+
+void gravity_class_free (gravity_vm *vm, gravity_class_t *c) {
+	gravity_class_free_internal(vm, c, true);
+}
+
+inline gravity_object_t *gravity_class_lookup (gravity_class_t *c, gravity_value_t key) {
+	while (c) {
+		gravity_value_t *v = gravity_hash_lookup(c->htable, key);
+		if (v) return (gravity_object_t *)v->p;
+		c = c->superclass;
+	}
+	return NULL;
+}
+
+inline gravity_closure_t *gravity_class_lookup_closure (gravity_class_t *c, gravity_value_t key) {
+	gravity_object_t *obj = gravity_class_lookup(c, key);
+	if (obj && OBJECT_ISA_CLOSURE(obj)) return (gravity_closure_t *)obj;
+	return NULL;
+}
+
+inline gravity_closure_t *gravity_class_lookup_constructor (gravity_class_t *c, uint32_t nparams) {
+	if (c->xdata) {
+		// bridged class so check for special $initN function
+		if (nparams == 0) {
+			STATICVALUE_FROM_STRING(key, CLASS_INTERNAL_INIT_NAME, strlen(CLASS_INTERNAL_INIT_NAME));
+			return (gravity_closure_t *)gravity_class_lookup(c, key);
+		}
+		
+		// for bridged classed (which can have more than one init constructor like in objc) the convention is
+		// to map each bridged init with a special $initN function (where N>0 is num params)
+		char name[256]; snprintf(name, sizeof(name), "%s%d", CLASS_INTERNAL_INIT_NAME, nparams);
+		STATICVALUE_FROM_STRING(key, name, strlen(name));
+		return (gravity_closure_t *)gravity_class_lookup(c, key);
+	}
+	
+	// for non bridge classes just check for constructor
+	STATICVALUE_FROM_STRING(key, CLASS_CONSTRUCTOR_NAME, strlen(CLASS_CONSTRUCTOR_NAME));
+	return (gravity_closure_t *)gravity_class_lookup(c, key);
+}
+
+uint32_t gravity_class_size (gravity_vm *vm, gravity_class_t *c) {
+	uint32_t class_size = sizeof(gravity_class_t) + (c->nivars * sizeof(gravity_value_t)) + string_size(c->identifier);
+	
+	uint32_t hash_size = 0;
+	gravity_hash_iterate2(c->htable, gravity_hash_internalsize, (void *)&hash_size, (void *)vm);
+	hash_size += gravity_hash_memsize(c->htable);
+	
+	gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+	if ((c->xdata) && (delegate) && (delegate->bridge_size))
+		class_size += delegate->bridge_size(vm, c->xdata);
+	
+	return class_size;
+}
+
+void gravity_class_blacken (gravity_vm *vm, gravity_class_t *c) {
+	gravity_vm_memupdate(vm, gravity_class_size(vm, c));
+	
+	// metaclass
+	gravity_gray_object(vm, (gravity_object_t *)c->objclass);
+	
+	// superclass
+	gravity_gray_object(vm, (gravity_object_t *)c->superclass);
+	
+	// internals
+	gravity_hash_iterate(c->htable, gravity_hash_gray, (void *)vm);
+	
+	// ivars
+	for (uint32_t i=0; i<c->nivars; ++i) {
+		gravity_gray_value(vm, c->ivars[i]);
+	}
+}
+
+// MARK: -
+
+gravity_function_t *gravity_function_new (gravity_vm *vm, const char *identifier, uint16_t nparams, uint16_t nlocals, uint16_t ntemps, void *code) {
+	gravity_function_t *f = (gravity_function_t *)mem_alloc(sizeof(gravity_function_t));
+	assert(f);
+	
+	f->isa = gravity_class_function;
+	f->identifier = (identifier) ? string_dup(identifier) : NULL;
+	f->tag = EXEC_TYPE_NATIVE;
+	f->nparams = nparams;
+	f->nlocals = nlocals;
+	f->ntemps = ntemps;
+	f->nupvalues = 0;
+	
+	// only available in EXEC_TYPE_NATIVE case
+	f->useargs = false;
+	f->bytecode = (uint32_t *)code;
+	marray_init(f->cpool);
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*)f);
+	return f;
+}
+
+gravity_function_t *gravity_function_new_internal (gravity_vm *vm, const char *identifier, gravity_c_internal exec, uint16_t nparams) {
+	gravity_function_t *f = gravity_function_new(vm, identifier, nparams, 0, 0, NULL);
+	f->tag = EXEC_TYPE_INTERNAL;
+	f->internal = exec;
+	return f;
+}
+
+gravity_function_t	*gravity_function_new_special (gravity_vm *vm, const char *identifier, uint16_t index, void *getter, void *setter) {
+	gravity_function_t *f = gravity_function_new(vm, identifier, 0, 0, 0, NULL);
+	f->tag = EXEC_TYPE_SPECIAL;
+	f->index = index;
+	f->special[0] = getter;
+	f->special[1] = setter;
+	return f;
+}
+
+gravity_function_t	*gravity_function_new_bridged (gravity_vm *vm, const char *identifier, void *xdata) {
+	gravity_function_t *f = gravity_function_new(vm, identifier, 0, 0, 0, NULL);
+	f->tag = EXEC_TYPE_BRIDGED;
+	f->xdata = xdata;
+	return f;
+}
+
+uint16_t gravity_function_cpool_add (gravity_vm *vm, gravity_function_t *f, gravity_value_t v) {
+	assert(f->tag == EXEC_TYPE_NATIVE);
+	
+	size_t n = marray_size(f->cpool);
+	for (size_t i=0; i<n; i++) {
+		gravity_value_t v2 = marray_get(f->cpool, i);
+		if (gravity_value_equals(v,v2)) {
+			gravity_value_free(NULL, v);
+			return i;
+		}
+	}
+	
+	// vm is required here because I cannot know in advance if v is already in the pool or not
+	// and value object v must be added to the VM only once
+	if ((vm) && (gravity_value_isobject(v))) gravity_vm_transfer(vm, VALUE_AS_OBJECT(v));
+	
+	marray_push(gravity_value_t, f->cpool, v);
+	return (uint16_t)marray_size(f->cpool)-1;
+}
+
+gravity_value_t gravity_function_cpool_get (gravity_function_t *f, uint16_t i) {
+	assert(f->tag == EXEC_TYPE_NATIVE);
+	return marray_get(f->cpool, i);
+}
+
+void gravity_function_setxdata (gravity_function_t *f, void *xdata) {
+	f->xdata = xdata;
+}
+
+static void gravity_function_cpool_serialize (gravity_function_t *f, json_t *json) {
+	assert(f->tag == EXEC_TYPE_NATIVE);
+	size_t n = marray_size(f->cpool);
+	
+	for (size_t i=0; i<n; i++) {
+		gravity_value_t v = marray_get(f->cpool, i);
+		gravity_value_serialize(v, json);
+	}
+}
+
+static void gravity_function_cpool_dump (gravity_function_t *f) {
+	assert(f->tag == EXEC_TYPE_NATIVE);
+	size_t n = marray_size(f->cpool);
+	
+	for (size_t i=0; i<n; i++) {
+		gravity_value_t v = marray_get(f->cpool, i);
+		
+		if (v.isa == gravity_class_bool) {
+			printf("%05zu\tBOOL: %d\n", i, (v.n == 0) ? 0 : 1);
+		} else if (v.isa == gravity_class_int) {
+			printf("%05zu\tINT: %lld\n", i, (int64_t)v.n);
+		} else if (v.isa == gravity_class_float) {
+			printf("%05zu\tFLOAT: %f\n", i, (double)v.f);
+		} else if (v.isa == gravity_class_function) {
+			gravity_function_t *vf = VALUE_AS_FUNCTION(v);
+			printf("%05zu\tFUNC: %s\n", i, (vf->identifier) ? vf->identifier : "$anon");
+		}  else if (v.isa == gravity_class_class) {
+			gravity_class_t *c = VALUE_AS_CLASS(v);
+			printf("%05zu\tCLASS: %s\n", i, (c->identifier) ? c->identifier: "$anon");
+		} else if (v.isa == gravity_class_string) {
+			printf("%05zu\tSTRING: %s\n", i, VALUE_AS_CSTRING(v));
+		} else if (v.isa == gravity_class_list) {
+			gravity_list_t *value = VALUE_AS_LIST(v);
+			size_t count = marray_size(value->array);
+			printf("%05zu\tLIST: %zu items\n", i, count);
+		} else if (v.isa == gravity_class_map) {
+			gravity_map_t *map = VALUE_AS_MAP(v);
+			printf("%05zu\tMAP: %u items\n", i, gravity_hash_count(map->hash));
+		} else {
+			assert(0);
+		}
+	}
+}
+
+static void gravity_function_bytecode_serialize (gravity_function_t *f, json_t *json) {
+	if (!f->bytecode) {
+		json_add_null(json, GRAVITY_JSON_LABELBYTECODE);
+		return;
+	}
+	
+	uint32_t ninst = f->ninsts;
+	uint32_t length = ninst * 2 * sizeof(uint32_t);
+	uint8_t *hexchar = (uint8_t*) mem_alloc(sizeof(uint8_t) * length);
+	
+	for (uint32_t k=0, i=0; i < ninst; ++i) {
+		uint32_t value = f->bytecode[i];
+		
+		for (int32_t j=2*sizeof(value)-1; j>=0; --j) {
+			uint8_t c = "0123456789ABCDEF"[((value >> (j*4)) & 0xF)];
+			hexchar[k++] = c;
+		}
+	}
+	
+	json_add_string(json, GRAVITY_JSON_LABELBYTECODE, (const char *)hexchar, length);
+	mem_free(hexchar);
+}
+
+uint32_t *gravity_bytecode_deserialize (const char *buffer, size_t len, uint32_t *n) {
+	uint32_t ninst = (uint32_t)len / 8;
+	uint32_t *bytecode = (uint32_t *)mem_alloc(sizeof(uint32_t) * (ninst + 1));	// +1 to get a 0 terminated bytecode (0 is opcode RET0)
+	
+	for (uint32_t j=0; j<ninst; ++j) {
+		register uint32_t v = 0;
+		
+		for (uint32_t i=(j*8); i<=(j*8)+7; ++i) {
+			// I was using a conversion code from
+			// https://code.google.com/p/yara-project/source/browse/trunk/libyara/xtoi.c?r=150
+			// but it caused issues under ARM processor so I decided to switch to an easier to read/maintain code
+			// http://codereview.stackexchange.com/questions/42976/hexadecimal-to-integer-conversion-function
+			
+			// no needs to have also the case:
+			// if (c >= 'a' && c <= 'f') {
+			//		c = c - 'a' + 10;
+			// }
+			// because bytecode is always uppercase
+			register uint32_t c = buffer[i];
+			
+			if (c >= 'A' && c <= 'F') {
+				c = c - 'A' + 10;
+			} else if (c >= '0' && c <= '9') {
+				c -= '0';
+			} else goto abort_conversion;
+			
+			v = v << 4 | c;
+			
+		}
+		
+		bytecode[j] = v;
+	}
+	
+	*n = ninst;
+	return bytecode;
+	
+abort_conversion:
+	if (bytecode) mem_free(bytecode);
+	*n = 0;
+	return NULL;
+}
+
+void gravity_function_dump (gravity_function_t *f, code_dump_function codef) {
+	printf("Function: %s\n", (f->identifier) ? f->identifier : "$anon");
+	printf("Params:%d Locals:%d Temp:%d Upvalues:%d\n", f->nparams, f->nlocals, f->ntemps, f->nupvalues);
+	
+	if (f->tag == EXEC_TYPE_NATIVE) {
+		if (marray_size(f->cpool)) printf("======= CPOOL =======\n");
+		gravity_function_cpool_dump(f);
+		
+		printf("======= BYTECODE ====\n");
+		if ((f->bytecode) && (codef)) codef(f->bytecode);
+	}
+	
+	printf("\n");
+}
+
+void gravity_function_special_serialize (gravity_function_t *f, const char *key, json_t *json) {
+	json_begin_object(json, key);
+	
+	json_add_cstring(json, GRAVITY_JSON_LABELTYPE, GRAVITY_JSON_FUNCTION);	// MANDATORY 1st FIELD
+	json_add_cstring(json, GRAVITY_JSON_LABELIDENTIFIER, key);				// MANDATORY 2nd FIELD
+	json_add_int(json, GRAVITY_JSON_LABELTAG, f->tag);
+	
+	// common fields
+	json_add_int(json, GRAVITY_JSON_LABELNPARAM, f->nparams);
+	json_add_bool(json, GRAVITY_JSON_LABELARGS, f->useargs);
+	json_add_int(json, GRAVITY_JSON_LABELINDEX, f->index);
+	
+	if (f->special[0]) {
+		gravity_function_t *f2 = (gravity_function_t*)f->special[0];
+		f2->identifier = GRAVITY_JSON_GETTER;
+		gravity_function_serialize(f2, json);
+		f2->identifier = NULL;
+	}
+	if (f->special[1]) {
+		gravity_function_t *f2 = (gravity_function_t*)f->special[1];
+		f2->identifier = GRAVITY_JSON_SETTER;
+		gravity_function_serialize(f2, json);
+		f2->identifier = NULL;
+	}
+	
+	json_end_object(json);
+}
+	
+void gravity_function_serialize (gravity_function_t *f, json_t *json) {
+	// special functions need a special serialization
+	if (f->tag == EXEC_TYPE_SPECIAL) {
+		gravity_function_special_serialize(f, f->identifier, json);
+		return;
+	}
+	
+	// compute identifier (cannot be NULL)
+	const char *identifier = f->identifier;
+	char temp[256];
+	if (!identifier) {snprintf(temp, sizeof(temp), "$anon_%p", f); identifier = temp;}
+	
+	if (identifier) json_begin_object(json, identifier);
+	json_add_cstring(json, GRAVITY_JSON_LABELTYPE, GRAVITY_JSON_FUNCTION);					// MANDATORY 1st FIELD
+	if (identifier) json_add_cstring(json, GRAVITY_JSON_LABELIDENTIFIER, identifier);		// MANDATORY 2nd FIELD (not for getter/setter)
+	json_add_int(json, GRAVITY_JSON_LABELTAG, f->tag);
+	
+	// common fields
+	json_add_int(json, GRAVITY_JSON_LABELNPARAM, f->nparams);
+	json_add_bool(json, GRAVITY_JSON_LABELARGS, f->useargs);
+	
+	if (f->tag == EXEC_TYPE_NATIVE) {
+		// native only fields
+		json_add_int(json, GRAVITY_JSON_LABELNLOCAL, f->nlocals);
+		json_add_int(json, GRAVITY_JSON_LABELNTEMP, f->ntemps);
+		json_add_int(json, GRAVITY_JSON_LABELNUPV, f->nupvalues);
+		json_add_double(json, GRAVITY_JSON_LABELPURITY, f->purity);
+		
+		// bytecode
+		gravity_function_bytecode_serialize(f, json);
+		
+		// constant pool
+		json_begin_array(json, GRAVITY_JSON_LABELPOOL);
+		gravity_function_cpool_serialize(f, json);
+		json_end_array(json);
+	}
+	
+	if (identifier) json_end_object(json);
+}
+
+gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *json) {
+	gravity_function_t *f = gravity_function_new(vm, NULL, 0, 0, 0, NULL);
+	
+	DEBUG_DESERIALIZE("DESERIALIZE FUNCTION: %p\n", f);
+	
+	uint32_t n = json->u.object.length;
+	for (uint32_t i=1; i<n; ++i) { // from 1 to skip type
+		const char *label = json->u.object.values[i].name;
+		json_value *value = json->u.object.values[i].value;
+		size_t label_size = strlen(label);
+		
+		// identifier
+		if (string_casencmp(label, GRAVITY_JSON_LABELIDENTIFIER, label_size) == 0) {
+			assert(value->type == json_string);
+			if (strncmp(value->u.string.ptr, "$anon", 5) != 0) {
+				f->identifier = string_dup(value->u.string.ptr);
+				DEBUG_DESERIALIZE("IDENTIFIER: %s\n", value->u.string.ptr);
+			}
+			continue;
+		}
+		
+		// tag
+		if (string_casencmp(label, GRAVITY_JSON_LABELTAG, label_size) == 0) {
+			assert(value->type == json_integer);
+			f->tag = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// index (only in special functions)
+		if (string_casencmp(label, GRAVITY_JSON_LABELINDEX, label_size) == 0) {
+			assert(value->type == json_integer);
+			assert(f->tag == EXEC_TYPE_SPECIAL);
+			f->index = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// getter (only in special functions)
+		if (string_casencmp(label, GRAVITY_JSON_GETTER, strlen(GRAVITY_JSON_GETTER)) == 0) {
+			assert(f->tag == EXEC_TYPE_SPECIAL);
+			gravity_function_t *getter = gravity_function_deserialize(vm, value);
+			f->special[0] = gravity_closure_new(vm, getter);
+			continue;
+		}
+		
+		// setter (only in special functions)
+		if (string_casencmp(label, GRAVITY_JSON_SETTER, strlen(GRAVITY_JSON_SETTER)) == 0) {
+			assert(f->tag == EXEC_TYPE_SPECIAL);
+			gravity_function_t *setter = gravity_function_deserialize(vm, value);
+			f->special[1] = gravity_closure_new(vm, setter);
+			continue;
+		}
+		
+		// nparams
+		if (string_casencmp(label, GRAVITY_JSON_LABELNPARAM, label_size) == 0) {
+			assert(value->type == json_integer);
+			f->nparams = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// nlocals
+		if (string_casencmp(label, GRAVITY_JSON_LABELNLOCAL, label_size) == 0) {
+			assert(value->type == json_integer);
+			f->nlocals = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// ntemps
+		if (string_casencmp(label, GRAVITY_JSON_LABELNTEMP, label_size) == 0) {
+			assert(value->type == json_integer);
+			f->ntemps = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// nupvalues
+		if (string_casencmp(label, GRAVITY_JSON_LABELNUPV, label_size) == 0) {
+			assert(value->type == json_integer);
+			f->nupvalues = (uint16_t)value->u.integer;
+			continue;
+		}
+		
+		// args
+		if (string_casencmp(label, GRAVITY_JSON_LABELARGS, label_size) == 0) {
+			assert(value->type == json_boolean);
+			f->useargs = (bool)value->u.boolean;
+			continue;
+		}
+		
+		// bytecode
+		if (string_casencmp(label, GRAVITY_JSON_LABELBYTECODE, label_size) == 0) {
+			if (value->type == json_null) continue;
+			assert(value->type == json_string);
+			f->bytecode = gravity_bytecode_deserialize(value->u.string.ptr, value->u.string.length, &f->ninsts);
+			continue;
+		}
+		
+		// cpool
+		if (string_casencmp(label, GRAVITY_JSON_LABELPOOL, label_size) == 0) {
+			assert(value->type == json_array);
+			uint32_t m = value->u.array.length;
+			for (uint32_t j=0; j<m; ++j) {
+				json_value *r = value->u.array.values[j];
+				switch (r->type) {
+					case json_integer:
+						gravity_function_cpool_add(NULL, f, VALUE_FROM_INT((gravity_int_t)r->u.integer));
+						break;
+						
+					case json_double:
+						gravity_function_cpool_add(NULL, f, VALUE_FROM_FLOAT((gravity_float_t)r->u.dbl));
+						break;
+						
+					case json_boolean:
+						gravity_function_cpool_add(NULL, f, VALUE_FROM_BOOL(r->u.boolean));
+						break;
+						
+					case json_string:
+						gravity_function_cpool_add(vm, f, VALUE_FROM_STRING(NULL, r->u.string.ptr, r->u.string.length));
+						break;
+						
+					case json_object: {
+						gravity_object_t *obj = NULL;
+						bool result = gravity_object_deserialize(vm, r, &obj);
+						if ((result) && (obj)) gravity_function_cpool_add(NULL, f, VALUE_FROM_OBJECT(obj));
+						else goto abort_load;
+						break;
+					}
+						
+					case json_array: {
+						uint32_t count = r->u.array.length;
+						gravity_list_t *list = gravity_list_new(NULL, count);
+						for (uint32_t k=0; k<count; ++k) {
+							json_value *jsonv = r->u.array.values[k];
+							gravity_value_t v;
+							
+							// only literals allowed here
+							switch (jsonv->type) {
+								case json_integer: v = VALUE_FROM_INT((gravity_int_t)jsonv->u.integer); break;
+								case json_double: v = VALUE_FROM_FLOAT((gravity_float_t)jsonv->u.dbl); break;
+								case json_boolean: v = VALUE_FROM_BOOL(jsonv->u.boolean); break;
+								case json_string: v = VALUE_FROM_STRING(vm, jsonv->u.string.ptr, jsonv->u.string.length); break;
+								default:assert(0);
+							}
+							
+							marray_push(gravity_value_t, list->array, v);
+						}
+						gravity_function_cpool_add(vm, f, VALUE_FROM_OBJECT(list));
+					}
+						
+					case json_none:
+					case json_null:
+						gravity_function_cpool_add(NULL, f, VALUE_FROM_NULL);
+						break;
+				}
+			}
+		}
+	}
+		
+	return f;
+	
+abort_load:
+	if (f) gravity_function_free(vm, f);
+	return NULL;
+}
+
+void gravity_function_free (gravity_vm *vm, gravity_function_t *f) {
+	if (!f) return;
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)f));
+	
+	// check if bridged data needs to be freed too
+	if (f->xdata && vm) {
+		gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+		if (delegate && delegate->bridge_free) delegate->bridge_free(vm, (gravity_object_t *)f);
+	}
+	
+	if (f->identifier) mem_free((void *)f->identifier);
+	if (f->tag == EXEC_TYPE_NATIVE) {
+		if (f->bytecode) mem_free((void *)f->bytecode);
+		// DO NOT FREE EACH INDIVIDUAL CPOOL ITEM HERE
+		marray_destroy(f->cpool);
+	}
+	mem_free((void *)f);
+}
+
+uint32_t gravity_function_size (gravity_vm *vm, gravity_function_t *f) {
+	uint32_t func_size = sizeof(gravity_function_t) + string_size(f->identifier);
+	
+	if (f->tag == EXEC_TYPE_NATIVE) {
+		if (f->bytecode) func_size += f->ninsts * sizeof(uint32_t);
+		// cpool size
+		size_t n = marray_size(f->cpool);
+		for (size_t i=0; i<n; i++) {
+			gravity_value_t v = marray_get(f->cpool, i);
+			func_size += gravity_value_size(vm, v);
+		}
+	} else if (f->tag == EXEC_TYPE_SPECIAL) {
+		if (f->special[0]) func_size += gravity_closure_size(vm, (gravity_closure_t *)f->special[0]);
+		if ((f->special[1]) && (f->special[0] != f->special[1])) func_size += gravity_closure_size(vm, (gravity_closure_t *)f->special[1]);
+	} else if (f->tag == EXEC_TYPE_BRIDGED) {
+		gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+		if ((f->xdata) && (delegate) && (delegate->bridge_size))
+			func_size += delegate->bridge_size(vm, f->xdata);
+	}
+	
+	return func_size;
+}
+
+void gravity_function_blacken (gravity_vm *vm, gravity_function_t *f) {
+	gravity_vm_memupdate(vm, gravity_function_size(vm, f));
+	
+	if (f->tag == EXEC_TYPE_SPECIAL) {
+		if (f->special[0]) gravity_gray_object(vm, (gravity_object_t *)f->special[0]);
+		if (f->special[1]) gravity_gray_object(vm, (gravity_object_t *)f->special[1]);
+	}
+	
+	if (f->tag == EXEC_TYPE_NATIVE) {
+		// constant pool
+		size_t n = marray_size(f->cpool);
+		for (size_t i=0; i<n; i++) {
+			gravity_value_t v = marray_get(f->cpool, i);
+			gravity_gray_value(vm, v);
+		}
+	}
+}
+
+// MARK: -
+
+gravity_closure_t *gravity_closure_new (gravity_vm *vm, gravity_function_t *f) {
+	#pragma unused(vm)
+	
+	gravity_closure_t *closure = (gravity_closure_t *)mem_alloc(sizeof(gravity_closure_t));
+	assert(closure);
+	
+	closure->isa = gravity_class_closure;
+	closure->f = f;
+	
+	// allocate upvalue array (+1 so I can simplify the iterator without the needs to access closure->f->nupvalues)
+	uint16_t nupvalues = (f) ? f->nupvalues : 0;
+	closure->upvalue = (nupvalues) ? (gravity_upvalue_t **)mem_alloc(sizeof(gravity_upvalue_t*) * (f->nupvalues + 1)) : NULL;
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*)closure);
+	return closure;
+}
+
+void gravity_closure_free (gravity_vm *vm, gravity_closure_t *closure) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)closure));
+	
+	if (closure->upvalue) mem_free(closure->upvalue);
+	mem_free(closure);
+}
+
+uint32_t gravity_closure_size (gravity_vm *vm, gravity_closure_t *closure) {
+	#pragma unused(vm)
+	
+	uint32_t closure_size = sizeof(gravity_closure_t);
+	gravity_upvalue_t **upvalue = closure->upvalue;
+	while (upvalue) {
+		closure_size += sizeof(gravity_upvalue_t*);
+		++upvalue;
+	}
+	return closure_size;
+}
+
+void gravity_closure_blacken (gravity_vm *vm, gravity_closure_t *closure) {
+	gravity_vm_memupdate(vm, gravity_closure_size(vm, closure));
+	
+	// mark function
+	gravity_gray_object(vm, (gravity_object_t*)closure->f);
+	
+	// mark each upvalue
+	gravity_upvalue_t **upvalue = closure->upvalue;
+	while (upvalue) {
+		gravity_gray_object(vm, (gravity_object_t*)upvalue[0]);
+		++upvalue;
+	}
+}
+
+// MARK: -
+
+gravity_upvalue_t *gravity_upvalue_new (gravity_vm *vm, gravity_value_t *value) {
+	#pragma unused(vm)
+	gravity_upvalue_t *upvalue = (gravity_upvalue_t *)mem_alloc(sizeof(gravity_upvalue_t));
+	
+	upvalue->isa = gravity_class_upvalue;
+	upvalue->value = value;
+	upvalue->closed = VALUE_FROM_NULL;
+	upvalue->next = NULL;
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*)upvalue);
+	return upvalue;
+}
+
+uint32_t gravity_upvalue_size (gravity_vm *vm, gravity_upvalue_t *upvalue) {
+	#pragma unused(vm, upvalue)
+	return sizeof(gravity_upvalue_t);
+}
+
+void gravity_upvalue_blacken (gravity_vm *vm, gravity_upvalue_t *upvalue) {
+	#pragma unused(vm)
+	gravity_vm_memupdate(vm, gravity_upvalue_size(vm, upvalue));
+	gravity_gray_value(vm, upvalue->closed);
+}
+
+void gravity_upvalue_free(gravity_vm *vm, gravity_upvalue_t *upvalue) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)upvalue));
+	mem_free(upvalue);
+}
+
+// MARK: -
+
+gravity_fiber_t *gravity_fiber_new (gravity_vm *vm, gravity_closure_t *closure, uint32_t nstack, uint32_t nframes) {
+	gravity_fiber_t *fiber = (gravity_fiber_t *)mem_alloc(sizeof(gravity_fiber_t));
+	assert(fiber);
+	
+	fiber->isa = gravity_class_fiber;
+	fiber->caller = NULL;
+	fiber->result = VALUE_FROM_NULL;
+	
+	if (nstack < DEFAULT_MINSTACK_SIZE) nstack = DEFAULT_MINSTACK_SIZE;
+	fiber->stack = (gravity_value_t *)mem_alloc(sizeof(gravity_value_t) * nstack);
+	fiber->stacktop = fiber->stack;
+	fiber->stackalloc = nstack;
+	
+	if (nframes < DEFAULT_MINCFRAME_SIZE) nframes = DEFAULT_MINCFRAME_SIZE;
+	fiber->frames = (gravity_callframe_t *)mem_alloc(sizeof(gravity_callframe_t) * nframes);
+	fiber->framesalloc = nframes;
+	fiber->nframes = 1;
+	
+	fiber->upvalues = NULL;
+	
+	gravity_callframe_t *frame = &fiber->frames[0];
+	if (closure) {
+		frame->closure = closure;
+		frame->ip = (closure->f->tag == EXEC_TYPE_NATIVE) ? closure->f->bytecode : NULL;
+	}
+	frame->dest = 0;
+	frame->stackstart = fiber->stack;
+	
+	gravity_vm_transfer(vm, (gravity_object_t*) fiber);
+	return fiber;
+}
+
+void gravity_fiber_free (gravity_vm *vm, gravity_fiber_t *fiber) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)fiber));
+	if (fiber->error) mem_free(fiber->error);
+	mem_free(fiber->stack);
+	mem_free(fiber->frames);
+	mem_free(fiber);
+}
+
+void gravity_fiber_reassign (gravity_fiber_t *fiber, gravity_closure_t *closure, uint16_t nargs) {
+	gravity_callframe_t *frame = &fiber->frames[0];
+	frame->closure = closure;
+	frame->ip = (closure->f->tag == EXEC_TYPE_NATIVE) ? closure->f->bytecode : NULL;
+	
+	frame->dest = 0;
+	frame->stackstart = fiber->stack;
+	
+	fiber->nframes = 1;
+	fiber->upvalues = NULL;
+	
+	// update stacktop in order to be GC friendly
+	fiber->stacktop += FN_COUNTREG(closure->f, nargs);
+}
+
+void gravity_fiber_seterror (gravity_fiber_t *fiber, const char *error) {
+	if (fiber->error) mem_free(fiber->error);
+	fiber->error = (char *)string_dup(error);
+}
+
+uint32_t gravity_fiber_size (gravity_vm *vm, gravity_fiber_t *fiber) {
+	// internal size
+	uint32_t fiber_size = sizeof(gravity_fiber_t);
+	fiber_size += fiber->stackalloc * sizeof(gravity_value_t);
+	fiber_size += fiber->framesalloc * sizeof(gravity_callframe_t);
+	
+	// stack size
+	for (gravity_value_t* slot = fiber->stack; slot < fiber->stacktop; ++slot) {
+		fiber_size += gravity_value_size(vm, *slot);
+	}
+	
+	fiber_size += string_size(fiber->error);
+	fiber_size += gravity_object_size(vm, (gravity_object_t *)fiber->caller);
+	
+	return fiber_size;
+}
+
+void gravity_fiber_blacken (gravity_vm *vm, gravity_fiber_t *fiber) {
+	gravity_vm_memupdate(vm, gravity_fiber_size(vm, fiber));
+	
+	// gray call frame functions
+	for (uint32_t i=0; i < fiber->nframes; ++i) {
+		gravity_gray_object(vm, (gravity_object_t *)fiber->frames[i].closure);
+	}
+	
+	// gray stack variables
+	for (gravity_value_t* slot = fiber->stack; slot < fiber->stacktop; ++slot) {
+		gravity_gray_value(vm, *slot);
+	}
+	
+	// gray upvalues
+	gravity_upvalue_t* upvalue = fiber->upvalues;
+	while (upvalue) {
+		gravity_gray_object(vm, (gravity_object_t *)upvalue);
+		upvalue = upvalue->next;
+	}
+	
+	gravity_gray_object(vm, (gravity_object_t *)fiber->caller);
+}
+	
+// MARK: -
+
+void gravity_object_serialize (gravity_object_t *obj, json_t *json) {
+	if (obj->isa == gravity_class_function)
+		gravity_function_serialize((gravity_function_t *)obj, json);
+	else if (obj->isa == gravity_class_class)
+		gravity_class_serialize((gravity_class_t *)obj, json);
+	else assert(0);
+}
+
+bool gravity_object_deserialize (gravity_vm *vm, json_value *entry, gravity_object_t **obj) {
+	// this function is able to deserialize ONLY objects with a type label
+	
+	// sanity check
+	if (entry->type != json_object) return false;
+	if (entry->u.object.length == 0) return false;
+	
+	// the first entry value must specify gravity object type
+	const char *label = entry->u.object.values[0].name;
+	json_value *value = entry->u.object.values[0].value;
+	
+	if (string_casencmp(label, GRAVITY_JSON_LABELTYPE, 4) != 0) return false;
+	if (value->type != json_string) return false;
+	
+	// FUNCTION case
+	if (string_casencmp(value->u.string.ptr, GRAVITY_JSON_FUNCTION, value->u.string.length) == 0) {
+		gravity_function_t *f = gravity_function_deserialize(vm, entry);
+		if (!f) return false;
+		*obj = (gravity_object_t *)f;
+		return true;
+	}
+	
+	// CLASS case
+	if (string_casencmp(value->u.string.ptr, GRAVITY_JSON_CLASS, value->u.string.length) == 0) {
+		gravity_class_t *c = gravity_class_deserialize(vm, entry);
+		if (!c) return false;
+		*obj = (gravity_object_t *)c;
+		return true;
+	}
+	
+	// MAP/ENUM case
+	if ((string_casencmp(value->u.string.ptr, GRAVITY_JSON_MAP, value->u.string.length) == 0) ||
+		(string_casencmp(value->u.string.ptr, GRAVITY_JSON_ENUM, value->u.string.length) == 0)) {
+		gravity_map_t *m = gravity_map_deserialize(vm, entry);
+		if (!m) return false;
+		*obj = (gravity_object_t *)m;
+		return true;
+	}
+	
+	// unhandled case
+	DEBUG_DESERIALIZE("gravity_object_deserialize unknown type");
+	return false;
+}
+#undef REPORT_JSON_ERROR
+
+const char *gravity_object_debug (gravity_object_t *obj) {
+	if ((!obj) || (!OBJECT_IS_VALID(obj))) return "";
+	
+	if (OBJECT_ISA_INT(obj)) return "INT";
+	if (OBJECT_ISA_FLOAT(obj)) return "FLOAT";
+	if (OBJECT_ISA_BOOL(obj)) return "BOOL";
+	if (OBJECT_ISA_NULL(obj)) return "NULL";
+	
+	static char buffer[512];
+	if (OBJECT_ISA_FUNCTION(obj)) {
+		const char *name = ((gravity_function_t*)obj)->identifier;
+		if (!name) name = "ANONYMOUS";
+		snprintf(buffer, sizeof(buffer), "FUNCTION %p %s", obj, name);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_CLOSURE(obj)) {
+		const char *name = ((gravity_closure_t*)obj)->f->identifier;
+		if (!name) name = "ANONYMOUS";
+		snprintf(buffer, sizeof(buffer), "CLOSURE %p %s", obj, name);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_CLASS(obj)) {
+		const char *name = ((gravity_class_t*)obj)->identifier;
+		if (!name) name = "ANONYMOUS";
+		snprintf(buffer, sizeof(buffer), "CLASS %p %s", obj, name);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_STRING(obj)) {
+		snprintf(buffer, sizeof(buffer), "STRING %p %s", obj, ((gravity_string_t*)obj)->s);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_INSTANCE(obj)) {
+		gravity_class_t *c = ((gravity_instance_t*)obj)->objclass;
+		const char *name = (c->identifier) ? c->identifier : "ANONYMOUS";
+		snprintf(buffer, sizeof(buffer), "INSTANCE %p OF %s", obj, name);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_RANGE(obj)) {
+		snprintf(buffer, sizeof(buffer), "RANGE %p %ld %ld", obj, (long)((gravity_range_t*)obj)->from, (long)((gravity_range_t*)obj)->to);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_LIST(obj)) {
+		snprintf(buffer, sizeof(buffer), "LIST %p (%ld items)", obj, (long)marray_size(((gravity_list_t*)obj)->array));
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_MAP(obj)) {
+		 snprintf(buffer, sizeof(buffer), "MAP %p (%ld items)", obj, (long)gravity_hash_count(((gravity_map_t*)obj)->hash));
+		 return buffer;
+	}
+	
+	if (OBJECT_ISA_FIBER(obj)) {
+		snprintf(buffer, sizeof(buffer), "FIBER %p", obj);
+		return buffer;
+	}
+	
+	if (OBJECT_ISA_UPVALUE(obj)) {
+		snprintf(buffer, sizeof(buffer), "UPVALUE %p", obj);
+		return buffer;
+	}
+		
+	return "N/A";
+}
+	
+void gravity_object_free (gravity_vm *vm, gravity_object_t *obj) {
+	if ((!obj) || (!OBJECT_IS_VALID(obj))) return;
+	
+	if (OBJECT_ISA_CLASS(obj)) gravity_class_free(vm, (gravity_class_t *)obj);
+	else if (OBJECT_ISA_FUNCTION(obj)) gravity_function_free(vm, (gravity_function_t *)obj);
+	else if (OBJECT_ISA_CLOSURE(obj)) gravity_closure_free(vm, (gravity_closure_t *)obj);
+	else if (OBJECT_ISA_INSTANCE(obj)) gravity_instance_free(vm, (gravity_instance_t *)obj);
+	else if (OBJECT_ISA_LIST(obj)) gravity_list_free(vm, (gravity_list_t *)obj);
+	else if (OBJECT_ISA_MAP(obj)) gravity_map_free(vm, (gravity_map_t *)obj);
+	else if (OBJECT_ISA_FIBER(obj)) gravity_fiber_free(vm, (gravity_fiber_t *)obj);
+	else if (OBJECT_ISA_RANGE(obj)) gravity_range_free(vm, (gravity_range_t *)obj);
+	else if (OBJECT_ISA_MODULE(obj)) gravity_module_free(vm, (gravity_module_t *)obj);
+	else if (OBJECT_ISA_STRING(obj)) gravity_string_free(vm, (gravity_string_t *)obj);
+	else if (OBJECT_ISA_UPVALUE(obj)) gravity_upvalue_free(vm, (gravity_upvalue_t *)obj);
+	else assert(0); // should never reach this point
+}
+
+uint32_t gravity_object_size (gravity_vm *vm, gravity_object_t *obj) {
+	if ((!obj) || (!OBJECT_IS_VALID(obj))) return 0;
+	
+	if (OBJECT_ISA_CLASS(obj)) return gravity_class_size(vm, (gravity_class_t *)obj);
+	else if (OBJECT_ISA_FUNCTION(obj)) return gravity_function_size(vm, (gravity_function_t *)obj);
+	else if (OBJECT_ISA_CLOSURE(obj)) return gravity_closure_size(vm, (gravity_closure_t *)obj);
+	else if (OBJECT_ISA_INSTANCE(obj)) return gravity_instance_size(vm, (gravity_instance_t *)obj);
+	else if (OBJECT_ISA_LIST(obj)) return gravity_list_size(vm, (gravity_list_t *)obj);
+	else if (OBJECT_ISA_MAP(obj)) return gravity_map_size(vm, (gravity_map_t *)obj);
+	else if (OBJECT_ISA_FIBER(obj)) return gravity_fiber_size(vm, (gravity_fiber_t *)obj);
+	else if (OBJECT_ISA_RANGE(obj)) return gravity_range_size(vm, (gravity_range_t *)obj);
+	else if (OBJECT_ISA_MODULE(obj)) return gravity_module_size(vm, (gravity_module_t *)obj);
+	else if (OBJECT_ISA_STRING(obj)) return gravity_string_size(vm, (gravity_string_t *)obj);
+	else if (OBJECT_ISA_UPVALUE(obj)) return gravity_upvalue_size(vm, (gravity_upvalue_t *)obj);
+	return 0;
+}
+
+void gravity_object_blacken (gravity_vm *vm, gravity_object_t *obj) {
+	if ((!obj) || (!OBJECT_IS_VALID(obj))) return;
+	
+	if (OBJECT_ISA_CLASS(obj)) gravity_class_blacken(vm, (gravity_class_t *)obj);
+	else if (OBJECT_ISA_FUNCTION(obj)) gravity_function_blacken(vm, (gravity_function_t *)obj);
+	else if (OBJECT_ISA_CLOSURE(obj)) gravity_closure_blacken(vm, (gravity_closure_t *)obj);
+	else if (OBJECT_ISA_INSTANCE(obj)) gravity_instance_blacken(vm, (gravity_instance_t *)obj);
+	else if (OBJECT_ISA_LIST(obj)) gravity_list_blacken(vm, (gravity_list_t *)obj);
+	else if (OBJECT_ISA_MAP(obj)) gravity_map_blacken(vm, (gravity_map_t *)obj);
+	else if (OBJECT_ISA_FIBER(obj)) gravity_fiber_blacken(vm, (gravity_fiber_t *)obj);
+	else if (OBJECT_ISA_RANGE(obj)) gravity_range_blacken(vm, (gravity_range_t *)obj);
+	else if (OBJECT_ISA_MODULE(obj)) gravity_module_blacken(vm, (gravity_module_t *)obj);
+	else if (OBJECT_ISA_STRING(obj)) gravity_string_blacken(vm, (gravity_string_t *)obj);
+	else if (OBJECT_ISA_UPVALUE(obj)) gravity_upvalue_blacken(vm, (gravity_upvalue_t *)obj);
+	else assert(0); // should never reach this point
+}
+
+// MARK: -
+
+gravity_instance_t *gravity_instance_new (gravity_vm *vm, gravity_class_t *c) {
+	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(sizeof(gravity_instance_t) + (c->nivars * sizeof(gravity_value_t)));
+	
+	instance->isa = gravity_class_instance;
+	instance->objclass = c;
+	for (uint32_t i=0; i<c->nivars; ++i) instance->ivars[i] = VALUE_FROM_NULL;
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) instance);
+	return instance;
+}
+
+gravity_instance_t *gravity_instance_dup (gravity_vm *vm, gravity_instance_t *src) {
+	gravity_class_t *c = src->objclass;
+	
+	gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(sizeof(gravity_instance_t) + (c->nivars * sizeof(gravity_value_t)));
+	instance->objclass = c;
+	for (uint32_t i=0; i<c->nivars; ++i) instance->ivars[i] = src->ivars[i];
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) instance);
+	return instance;
+}
+
+void gravity_instance_setivar (gravity_instance_t *instance, uint32_t idx, gravity_value_t value) {
+	if (idx < instance->objclass->nivars) instance->ivars[idx] = value;
+}
+
+void gravity_instance_setxdata (gravity_instance_t *i, void *xdata) {
+	i->xdata = xdata;
+}
+
+void gravity_instance_free (gravity_vm *vm, gravity_instance_t *i) {
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)i));
+	
+	// check if bridged data needs to be freed too
+	if (i->xdata && vm) {
+		gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+		if (delegate && delegate->bridge_free) delegate->bridge_free(vm, (gravity_object_t *)i);
+	}
+	
+	mem_free((void *)i);
+}
+
+gravity_closure_t *gravity_instance_lookup_event (gravity_instance_t *i, const char *name) {
+	// TODO: implemented as gravity_class_lookup but should be the exact opposite
+	
+	STATICVALUE_FROM_STRING(key, name, strlen(name));
+	gravity_class_t *c = i->objclass;
+	while (c) {
+		gravity_value_t *v = gravity_hash_lookup(c->htable, key);
+		// NOTE: there could be events (like InitContainer) which are empty (bytecode NULL) should I handle them here?
+		if ((v) && (OBJECT_ISA_CLOSURE(v->p))) return (gravity_closure_t *)v->p;
+		c = c->superclass;
+	}
+	return NULL;
+}
+
+uint32_t gravity_instance_size (gravity_vm *vm, gravity_instance_t *i) {
+	uint32_t instance_size = sizeof(gravity_instance_t) + (i->objclass->nivars * sizeof(gravity_value_t));
+	
+	gravity_delegate_t *delegate = gravity_vm_delegate(vm);
+	if ((i->xdata) && (delegate) && (delegate->bridge_size))
+		instance_size += delegate->bridge_size(vm, i->xdata);
+	
+	return instance_size;
+}
+
+void gravity_instance_blacken (gravity_vm *vm, gravity_instance_t *i) {
+	gravity_vm_memupdate(vm, gravity_instance_size(vm, i));
+	
+	// instance class
+	gravity_gray_object(vm, (gravity_object_t *)i->objclass);
+	
+	// ivars
+	for (uint32_t j=0; j<i->objclass->nivars; ++j) {
+		gravity_gray_value(vm, i->ivars[j]);
+	}
+}
+	
+// MARK: -
+
+bool gravity_value_equals (gravity_value_t v1, gravity_value_t v2) {
+	
+	// check same class
+	if (v1.isa != v2.isa) return false;
+	
+	// check same value for value types
+	if ((v1.isa == gravity_class_int) || (v1.isa == gravity_class_bool) || (v1.isa == gravity_class_null)) {
+		return (v1.n == v2.n);
+	} else if (v1.isa == gravity_class_float) {
+		#if GRAVITY_ENABLE_DOUBLE
+		return (fabs(v1.f - v2.f) < EPSILON);
+		#else
+		return (fabsf(v1.f - v2.f) < EPSILON);
+		#endif
+	} else if (v1.isa == gravity_class_string) {
+		gravity_string_t *s1 = VALUE_AS_STRING(v1);
+		gravity_string_t *s2 = VALUE_AS_STRING(v2);
+		if (s1->hash != s2->hash) return false;
+		if (s1->len != s2->len) return false;
+		// same hash and same len so let's compare bytes
+		return (memcmp(s1->s, s2->s, s1->len) == 0);
+	}
+	
+	// if here means that they are two heap allocated objects
+	gravity_object_t *obj1 = VALUE_AS_OBJECT(v1);
+	gravity_object_t *obj2 = VALUE_AS_OBJECT(v2);
+	if (obj1->isa != obj2->isa) return false;
+	
+	return (obj1 == obj2);
+}
+
+uint32_t gravity_value_hash (gravity_value_t value) {
+	if (value.isa == gravity_class_string)
+		return VALUE_AS_STRING(value)->hash;
+	
+	if ((value.isa == gravity_class_int) || (value.isa == gravity_class_bool) || (value.isa == gravity_class_null))
+		return gravity_hash_compute_int(value.n);
+	
+	if (value.isa == gravity_class_float)
+		return gravity_hash_compute_float(value.f);
+	
+	return gravity_hash_compute_buffer((const char *)value.p, sizeof(gravity_object_t*));
+}
+
+inline gravity_class_t *gravity_value_getclass (gravity_value_t v) {
+	if ((v.isa == gravity_class_class) && (v.p->objclass == gravity_class_object)) return (gravity_class_t *)v.p;
+	if ((v.isa == gravity_class_instance) || (v.isa == gravity_class_class)) return v.p->objclass;
+	return v.isa;
+}
+
+inline gravity_class_t *gravity_value_getsuper (gravity_value_t v) {
+	gravity_class_t *c = gravity_value_getclass(v);
+	return (c->superclass) ? c->superclass : NULL;
+}
+
+void gravity_value_free (gravity_vm *vm, gravity_value_t v) {
+	if (v.isa == gravity_class_int) return;
+	if (v.isa == gravity_class_float) return;
+	if (v.isa == gravity_class_bool) return;
+	if (v.isa == gravity_class_null) return;
+	
+	gravity_object_free(vm, VALUE_AS_OBJECT(v));
+}
+
+static void gravity_map_serialize_iterator (gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t v, void *data) {
+	#pragma unused(hashtable)
+	assert(key.isa == gravity_class_string);
+	
+	json_t *json = (json_t *)data;
+	const char *key_value = VALUE_AS_STRING(key)->s;
+	
+	// BOOL
+	if (VALUE_ISA_BOOL(v)) {
+		json_add_bool(json, key_value, (v.n == 0) ? false : true);
+		return;
+	}
+	
+	// INT
+	if (VALUE_ISA_INT(v)) {
+		json_add_int(json, key_value, (int64_t)v.n);
+		return;
+	}
+	
+	// FLOAT
+	if (VALUE_ISA_FLOAT(v)) {
+		json_add_double(json, key_value, (double)v.f);
+		return;
+	}
+	
+	// STRING
+	if (VALUE_ISA_STRING(v)) {
+		gravity_string_t *value = VALUE_AS_STRING(v);
+		json_add_string(json, key_value, value->s, value->len);
+		return;
+	}
+	
+	// should never reach this point
+	assert(0);
+}
+
+void gravity_value_serialize (gravity_value_t v, json_t *json) {
+	
+	// BOOL
+	if (VALUE_ISA_BOOL(v)) {
+		json_add_bool(json, NULL, (v.n == 0) ? false : true);
+		return;
+	}
+	
+	// INT
+	if (VALUE_ISA_INT(v)) {
+		json_add_int(json, NULL, (int64_t)v.n);
+		return;
+	}
+	
+	// FLOAT
+	if (VALUE_ISA_FLOAT(v)) {
+		json_add_double(json, NULL, (double)v.f);
+		return;
+	}
+ 
+	// FUNCTION
+	if (VALUE_ISA_FUNCTION(v)) {
+		gravity_function_serialize(VALUE_AS_FUNCTION(v), json);
+		return;
+	}
+	
+	// CLASS
+	if (VALUE_ISA_CLASS(v)) {
+		gravity_class_serialize(VALUE_AS_CLASS(v), json);
+		return;
+	}
+ 
+	// STRING
+	if (VALUE_ISA_STRING(v)) {
+		gravity_string_t *value = VALUE_AS_STRING(v);
+		json_add_string(json, NULL, value->s, value->len);
+		return;
+	}
+ 
+	// LIST (ARRAY)
+	if (VALUE_ISA_LIST(v)) {
+		gravity_list_t *value = VALUE_AS_LIST(v);
+		json_begin_array(json, NULL);
+		size_t count = marray_size(value->array);
+		for (size_t j=0; j<count; j++) {
+			gravity_value_t item = marray_get(value->array, j);
+			// here I am sure that value is a literal value
+			gravity_value_serialize(item, json);
+		}
+		json_end_array(json);
+		return;
+	}
+	
+	// MAP (HASH)
+	// a map is serialized only if it contains only literals, otherwise it is computed at runtime
+	if (VALUE_ISA_MAP(v)) {
+		gravity_map_t *value = VALUE_AS_MAP(v);
+		json_begin_object(json, NULL);
+		json_add_cstring(json, GRAVITY_JSON_LABELTYPE, GRAVITY_JSON_MAP);
+		gravity_hash_iterate(value->hash, gravity_map_serialize_iterator, json);
+		json_end_object(json);
+		return;
+	}
+	
+	// should never reach this point
+	assert(0);
+}
+
+bool gravity_value_isobject (gravity_value_t v) {
+	// was:
+	// if (VALUE_ISA_NOTVALID(v)) return false;
+	// if (VALUE_ISA_INT(v)) return false;
+	// if (VALUE_ISA_FLOAT(v)) return false;
+	// if (VALUE_ISA_BOOL(v)) return false;
+	// if (VALUE_ISA_NULL(v)) return false;
+	// if (VALUE_ISA_UNDEFINED(v)) return false;
+	// return true;
+	
+	if ((v.isa == NULL) || (v.isa == gravity_class_int) || (v.isa == gravity_class_float) ||
+		(v.isa == gravity_class_bool) || (v.isa == gravity_class_null)) return false;
+	return true;
+}
+
+uint32_t gravity_value_size (gravity_vm *vm, gravity_value_t v) {
+	return (gravity_value_isobject(v)) ? gravity_object_size(vm, (gravity_object_t*)v.p) : 0;
+}
+
+void *gravity_value_xdata (gravity_value_t value) {
+	if (VALUE_ISA_INSTANCE(value)) {
+		gravity_instance_t *i = VALUE_AS_INSTANCE(value);
+		return i->xdata;
+	} else if (VALUE_ISA_CLASS(value)) {
+		gravity_class_t *c = VALUE_AS_CLASS(value);
+		return c->xdata;
+	}
+	return NULL;
+}
+
+void gravity_value_dump (gravity_value_t v, char *buffer, uint16_t len) {
+	const char *type = NULL;
+	const char *value = NULL;
+	char		sbuffer[1024];
+	
+	if (buffer == NULL) buffer = sbuffer;
+	if (len == 0) len = sizeof(sbuffer);
+	
+	if (v.isa == NULL) {
+		type = "INVALID!";
+		snprintf(buffer, len, "%s", type);
+		value = buffer;
+	} else if (v.isa == gravity_class_bool) {
+		type = "BOOL";
+		value = (v.n == 0) ? "false" : "true";
+		snprintf(buffer, len, "(%s) %s", type, value);
+		value = buffer;
+	} else if (v.isa == gravity_class_null) {
+		type = (v.n == 0) ? "NULL" : "UNDEFINED";
+		snprintf(buffer, len, "%s", type);
+		value = buffer;
+	} else if (v.isa == gravity_class_int) {
+		type = "INT";
+		snprintf(buffer, len, "(%s) %lld", type, v.n);
+		value = buffer;
+	} else if (v.isa == gravity_class_float) {
+		type = "FLOAT";
+		snprintf(buffer, len, "(%s) %f", type, v.f);
+		value = buffer;
+	} else if (v.isa == gravity_class_function) {
+		type = "FUNCTION";
+		value = VALUE_AS_FUNCTION(v)->identifier;
+		snprintf(buffer, len, "(%s) %s (%p)", type, value, VALUE_AS_FUNCTION(v));
+		value = buffer;
+	} else if (v.isa == gravity_class_class) {
+		type = "CLASS";
+		value = VALUE_AS_CLASS(v)->identifier;
+		snprintf(buffer, len, "(%s) %s (%p)", type, value, VALUE_AS_CLASS(v));
+		value = buffer;
+	} else if (v.isa == gravity_class_string) {
+		type = "STRING";
+		gravity_string_t *s = VALUE_AS_STRING(v);
+		snprintf(buffer, len, "(%s) %.*s (%p)", type, s->len, s->s, s);
+		value = buffer;
+	} else if (v.isa == gravity_class_instance) {
+		type = "INSTANCE OF CLASS";
+		gravity_instance_t *i = VALUE_AS_INSTANCE(v);
+		gravity_class_t *c = i->objclass;
+		value = c->identifier;
+		snprintf(buffer, len, "(%s) %s (%p)", type, value, i);
+		value = buffer;
+	} else if (v.isa == gravity_class_list) {
+		type = "LIST";
+		value = "N/A";
+		snprintf(buffer, len, "(%s) %s", type, value);
+		value = buffer;
+	} else if (v.isa == gravity_class_map) {
+		type = "MAP";
+		value = "N/A";
+		snprintf(buffer, len, "(%s) %s", type, value);
+		value = buffer;
+	} else if (v.isa == gravity_class_range) {
+		type = "RANGE";
+		gravity_range_t *r = VALUE_AS_RANGE(v);
+		snprintf(buffer, len, "(%s) from %lld to %lld", type, r->from, r->to);
+		value = buffer;
+	} else if (v.isa == gravity_class_object) {
+		type = "OBJECT";
+		value = "N/A";
+		snprintf(buffer, len, "(%s) %s", type, value);
+		value = buffer;
+	} else if (v.isa == gravity_class_fiber) {
+		type = "FIBER";
+		snprintf(buffer, len, "(%s) %p", type, v.p);
+		value = buffer;
+	} else {
+		type = "N/A";
+		value = "N/A";
+		snprintf(buffer, len, "(%s) %s", type, value);
+		value = buffer;
+	}
+	
+	if (buffer == sbuffer) printf("%s\n", value);
+}
+
+// MARK: -
+gravity_list_t *gravity_list_new (gravity_vm *vm, uint32_t n) {
+	gravity_list_t *list = (gravity_list_t *)mem_alloc(sizeof(gravity_list_t));
+	
+	list->isa = gravity_class_list;
+	marray_init(list->array);
+	marray_resize(gravity_value_t, list->array, n + MARRAY_DEFAULT_SIZE);
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) list);
+	return list;
+}
+
+gravity_list_t *gravity_list_from_array (gravity_vm *vm, uint32_t n, gravity_value_t *p) {
+	gravity_list_t *list = (gravity_list_t *)mem_alloc(sizeof(gravity_list_t));
+	
+	list->isa = gravity_class_list;
+	marray_init(list->array);
+	// elements must be copied because for the compiler their registers are TEMP
+	// and could be reused by other successive operations
+	for (size_t i=0; i<n; ++i) marray_push(gravity_value_t, list->array, p[i]);
+	
+	gravity_vm_transfer(vm, (gravity_object_t*) list);
+	return list;
+}
+
+void gravity_list_free (gravity_vm *vm, gravity_list_t *list) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)list));
+	marray_destroy(list->array);
+	mem_free((void *)list);
+}
+
+void gravity_list_append_list (gravity_vm *vm, gravity_list_t *list1, gravity_list_t *list2) {
+	#pragma unused(vm)
+	// append list2 to list1
+	size_t count = marray_size(list2->array);
+	for (size_t i=0; i<count; ++i) {
+		marray_push(gravity_value_t, list1->array, marray_get(list2->array, i));
+	}
+}
+
+uint32_t gravity_list_size (gravity_vm *vm, gravity_list_t *list) {
+	uint32_t internal_size = 0;
+	size_t count = marray_size(list->array);
+	for (size_t i=0; i<count; ++i) {
+		internal_size += gravity_value_size(vm, marray_get(list->array, i));
+	}
+	return sizeof(gravity_list_t) + internal_size;
+}
+
+void gravity_list_blacken (gravity_vm *vm, gravity_list_t *list) {
+	gravity_vm_memupdate(vm, gravity_list_size(vm, list));
+	
+	size_t count = marray_size(list->array);
+	for (size_t i=0; i<count; ++i) {
+		gravity_gray_value(vm, marray_get(list->array, i));
+	}
+}
+
+// MARK: -
+gravity_map_t *gravity_map_new (gravity_vm *vm, uint32_t n) {
+	gravity_map_t *map = (gravity_map_t *)mem_alloc(sizeof(gravity_map_t));
+	
+	map->isa = gravity_class_map;
+	map->hash = gravity_hash_create(n, gravity_value_hash, gravity_value_equals, NULL, NULL);
+	
+	gravity_vm_transfer(vm, (gravity_object_t*) map);
+	return map;
+}
+
+void gravity_map_free (gravity_vm *vm, gravity_map_t *map) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)map));
+	gravity_hash_free(map->hash);
+	mem_free((void *)map);
+}
+
+void gravity_map_append_map (gravity_vm *vm, gravity_map_t *map1, gravity_map_t *map2) {
+	#pragma unused(vm)
+	// append map2 to map1
+	gravity_hash_append(map1->hash, map2->hash);
+}
+
+void gravity_map_insert (gravity_vm *vm, gravity_map_t *map, gravity_value_t key, gravity_value_t value) {
+	#pragma unused(vm)
+	gravity_hash_insert(map->hash, key, value);
+}
+
+static gravity_map_t *gravity_map_deserialize (gravity_vm *vm, json_value *json) {
+	uint32_t n = json->u.object.length;
+	gravity_map_t *map = gravity_map_new(vm, n);
+	
+	DEBUG_DESERIALIZE("DESERIALIZE MAP: %p\n", map);
+	
+	for (uint32_t i=1; i<n; ++i) { // from 1 to skip type
+		const char *label = json->u.object.values[i].name;
+		json_value *jsonv = json->u.object.values[i].value;
+		
+		gravity_value_t	key = VALUE_FROM_CSTRING(vm, label);
+		gravity_value_t	value;
+		
+		switch (jsonv->type) {
+			case json_integer: value = VALUE_FROM_INT((gravity_int_t)jsonv->u.integer); break;
+			case json_double: value = VALUE_FROM_FLOAT((gravity_float_t)jsonv->u.dbl); break;
+			case json_boolean: value = VALUE_FROM_BOOL(jsonv->u.boolean); break;
+			case json_string: value = VALUE_FROM_STRING(vm, jsonv->u.string.ptr, jsonv->u.string.length); break;
+			default:assert(0);
+		}
+		
+		gravity_map_insert(NULL, map, key, value);
+	}
+	
+	return map;
+}
+
+uint32_t gravity_map_size (gravity_vm *vm, gravity_map_t *map) {
+	uint32_t hash_size = 0;
+	gravity_hash_iterate2(map->hash, gravity_hash_internalsize, (void *)&hash_size, (void *)vm);
+	hash_size += gravity_hash_memsize(map->hash);
+	return sizeof(gravity_map_t) + hash_size;
+}
+
+void gravity_map_blacken (gravity_vm *vm, gravity_map_t *map) {
+	gravity_vm_memupdate(vm, gravity_map_size(vm, map));
+	gravity_hash_iterate(map->hash, gravity_hash_gray, (void *)vm);
+}
+
+// MARK: -
+
+gravity_range_t *gravity_range_new (gravity_vm *vm, gravity_int_t from_range, gravity_int_t to_range, bool inclusive) {
+	gravity_range_t *range = mem_alloc(sizeof(gravity_range_t));
+	
+	range->isa = gravity_class_range;
+	range->from = from_range;
+	range->to = (inclusive) ? to_range : --to_range;
+	
+	gravity_vm_transfer(vm, (gravity_object_t*) range);
+	return range;
+}
+
+void gravity_range_free (gravity_vm *vm, gravity_range_t *range) {
+	#pragma unused(vm)
+	
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)range));
+	mem_free((void *)range);
+}
+
+uint32_t gravity_range_size (gravity_vm *vm, gravity_range_t *range) {
+	#pragma unused(vm, range)
+	return sizeof(gravity_range_t);
+}
+
+void gravity_range_blacken (gravity_vm *vm, gravity_range_t *range) {
+	gravity_vm_memupdate(vm, gravity_range_size(vm, range));
+}
+
+// MARK: -
+
+inline gravity_value_t gravity_string_to_value (gravity_vm *vm, const char *s, uint32_t len) {
+	gravity_string_t *obj = mem_alloc(sizeof(gravity_string_t));
+	if (len == AUTOLENGTH) len = (uint32_t)strlen(s);
+	
+	uint32_t alloc = MAXNUM(len+1, DEFAULT_MINSTRING_SIZE);
+	char *ptr = mem_alloc(alloc);
+	memcpy(ptr, s, len);
+	
+	obj->isa = gravity_class_string;
+	obj->s = ptr;
+	obj->len = len;
+	obj->alloc = alloc;
+	obj->hash = gravity_hash_compute_buffer((const char *)ptr, len);
+	
+	gravity_value_t value;
+	value.isa = gravity_class_string;
+	value.p = (gravity_object_t *)obj;
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) obj);
+	return value;
+}
+
+inline gravity_string_t *gravity_string_new (gravity_vm *vm, char *s, uint32_t len, uint32_t alloc) {
+	gravity_string_t *obj = mem_alloc(sizeof(gravity_string_t));
+	if (len == AUTOLENGTH) len = (uint32_t)strlen(s);
+	
+	obj->isa = gravity_class_string;
+	obj->s = (char *)s;
+	obj->len = len;
+	obj->alloc = alloc;
+	if (s && len) obj->hash = gravity_hash_compute_buffer((const char *)s, len);
+	
+	if (vm) gravity_vm_transfer(vm, (gravity_object_t*) obj);
+	return obj;
+}
+
+inline void gravity_string_set (gravity_string_t *obj, char *s, uint32_t len) {
+	obj->s = (char *)s;
+	obj->len = len;
+	obj->hash = gravity_hash_compute_buffer((const char *)s, len);
+}
+
+inline void gravity_string_free (gravity_vm *vm, gravity_string_t *value) {
+	#pragma unused(vm)
+	DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)value));
+	if (value->alloc) mem_free(value->s);
+	mem_free(value);
+}
+
+uint32_t gravity_string_size (gravity_vm *vm, gravity_string_t *string) {
+	#pragma unused(vm)
+	return (sizeof(gravity_string_t)) + string->alloc;
+}
+
+void gravity_string_blacken (gravity_vm *vm, gravity_string_t *string) {
+	gravity_vm_memupdate(vm, gravity_string_size(vm, string));
+}
+

+ 480 - 0
src/shared/gravity_value.h

@@ -0,0 +1,480 @@
+//
+//  gravity_value.h
+//  gravity
+//
+//  Created by Marco Bambini on 11/12/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_VALUES__
+#define __GRAVITY_VALUES__
+
+#include "gravity_memory.h"
+#include "gravity_utils.h"
+#include "gravity_array.h"
+#include "gravity_json.h"
+#include "debug_macros.h"
+
+// Gravity is a dynamically typed language so a variable (gravity_value_t) can hold a value of any type.
+
+// The representation of values in a dynamicly typed language is very important since it can lead to a big
+// difference in terms of performance. Such representation has several contraints:
+// - fast access
+// - must represent several kind of values
+// - be able to cope with the gargabe collector
+// - low memory overhead (when allocating a lot of small values)
+
+// In modern 64bit processor with OS that always returns aligned allocated memory blocks that means that each ptr is 8 bytes.
+// That means that passing a value as an argument or storing it involves copying these bytes around (requiring 2/4 machine words).
+// Values are not pointers but structures.
+
+// The built-in types for booleans, numbers, floats, null, undefs are unboxed: their value is stored directly into gravity_value_t.
+// Other types like classes, instances, functions, lists, and strings are all reference types. They are stored on the heap and
+// the gravity_value_t just stores a pointer to it.
+
+// So each value is a pointer to a FIXED size block of memory (16 bytes). Having all values of the same size greatly reduce the complexitly
+// of a memory pool and since allocating a large amount of values is very commond is a dynamicly typed language like Gravity.
+// In a future update I could introduce NaN tagging and squeeze value size to 8 bytes (that would mean nearly double performance).
+
+// Internal settings to set integer and float size.
+// Default is to have both int and float as 64bit.
+
+// In a 64bit OS:
+// sizeof(float)	=> 4 bytes
+// sizeof(double)	=> 8 bytes
+// sizeof(void*)	=> 8 bytes
+// sizeof(int64_t)	=> 8 bytes
+//
+// sizeof various structs in a 64bit OS:
+// STRUCT					BYTES
+// ======					=====
+// gravity_function_t		104
+// gravity_value_t			16
+// gravity_upvalue_t		56
+// gravity_closure_t		40
+// gravity_list_t			48
+// gravity_map_t			32
+// gravity_callframe_t		48
+// gravity_fiber_t			112
+// gravity_class_t			88
+// gravity_module_t			40
+// gravity_instance_t		40
+// gravity_string_t			48
+// gravity_range_t			40
+
+#define GRAVITY_VERSION						"0.2.5"
+#define GRAVITY_VERSION_NUMBER				0x000205
+#define GRAVITY_BUILD_DATE					__DATE__
+
+#define GRAVITY_ENABLE_DOUBLE				1			// if 1 enable gravity_float_t to be a double (instead of a float)
+#define GRAVITY_ENABLE_INT64				1			// if 1 enable gravity_int_t to be a 64bit int (intead of a 32bit int)
+#define GRAVITY_COMPUTED_GOTO				1			// if 1 enable faster computed goto (instead of switch) for compilers that support it
+#define GRAVITY_NULL_SILENT					1			// if 1 then messages sent to null does not produce any runtime error
+#define GRAVITY_MAP_DOTSUGAR				0			// if 1 then map objects can be accessed with both map[key] and map.key
+
+#define MAIN_FUNCTION						"main"
+#define ITERATOR_INIT_FUNCTION				"iterate"
+#define ITERATOR_NEXT_FUNCTION				"next"
+#define INITMODULE_NAME						"$moduleinit"
+#define CLASS_INTERNAL_INIT_NAME			"$init"
+#define CLASS_CONSTRUCTOR_NAME				"init"
+#define CLASS_DESTRUCTOR_NAME				"deinit"
+#define SELF_PARAMETER_NAME					"self"
+#define OUTER_IVAR_NAME						"outer"
+#define GETTER_FUNCTION_NAME				"get"
+#define SETTER_FUNCTION_NAME				"set"
+#define SETTER_PARAMETER_NAME				"value"
+
+#define GLOBALS_DEFAULT_SLOT				4096
+#define	CPOOL_INDEX_MAX						4096		// 2^12
+#define CPOOL_VALUE_SUPER					CPOOL_INDEX_MAX+1
+#define CPOOL_VALUE_NULL					CPOOL_INDEX_MAX+2
+#define CPOOL_VALUE_UNDEFINED				CPOOL_INDEX_MAX+3
+#define CPOOL_VALUE_ARGUMENTS				CPOOL_INDEX_MAX+4
+#define CPOOL_VALUE_TRUE					CPOOL_INDEX_MAX+5
+#define CPOOL_VALUE_FALSE					CPOOL_INDEX_MAX+6
+#define CPOOL_VALUE_FUNC					CPOOL_INDEX_MAX+7
+
+#define MAX_INSTRUCTION_OPCODE				64			// 2^6
+#define MAX_REGISTERS						256			// 2^8
+#define MAX_LOCALS							200			// maximum number of local variables
+#define MAX_UPVALUES						200			// maximum number of upvalues
+#define MAX_INLINE_INT						131072		// 32 - 6 (OPCODE) - 8 (register) - 1 bit sign = 17
+#define MAX_FIELDSxFLUSH					64			// used in list/map serialization
+#define MAX_IVARS							768			// 2^10 - 2^8
+
+#define DEFAULT_CONTEXT_SIZE				256			// default VM context entries (can grow)
+#define DEFAULT_MINSTRING_SIZE				64			// minimum string allocation size
+#define DEFAULT_MINSTACK_SIZE				256			// sizeof(gravity_value_t) * 256	 = 16 * 256 => 4 KB
+#define DEFAULT_MINCFRAME_SIZE				32			// sizeof(gravity_callframe_t) * 48  = 32 * 48 => 1.5 KB
+#define DEFAULT_CG_THRESHOLD				5*1024*1024 // 5MB
+#define DEFAULT_CG_MINTHRESHOLD				1024*1024	// 1MB
+#define DEFAULT_CG_RATIO					0.5			// 50%
+
+#define MAXNUM(a,b)							((a) > (b) ? a : b)
+#define MINNUM(a,b)							((a) < (b) ? a : b)
+#define EPSILON								0.000001
+
+#define GRAVITY_DATA_REGISTER				UINT32_MAX
+#define GRAVITY_FIBER_REGISTER				UINT32_MAX-1
+#define GRAVITY_MSG_REGISTER				UINT32_MAX-2
+
+#define GRAVITY_BRIDGE_INDEX				UINT16_MAX
+#define GRAVITY_COMPUTED_INDEX				UINT16_MAX-1
+
+// MARK: - STRUCT -
+
+#if GRAVITY_ENABLE_DOUBLE
+typedef double								gravity_float_t;
+#else
+typedef float								gravity_float_t;
+#endif
+
+#if GRAVITY_ENABLE_INT64
+typedef int64_t								gravity_int_t;
+#else
+typedef int32_t								gravity_int_t;
+#endif
+
+// Forward references (an object ptr is just its isa pointer)
+typedef struct gravity_class_s				gravity_class_t;
+typedef struct gravity_class_s				gravity_object_t;
+
+// Everything inside Gravity VM is a gravity_value_t struct
+typedef struct {
+	gravity_class_t			*isa;			// EVERY object must have an ISA pointer (8 bytes on a 64bit system)
+	union {									// union takes 8 bytes on a 64bit system
+		gravity_int_t		n;				// integer slot
+		gravity_float_t		f;				// float/double slot
+		gravity_object_t	*p;				// ptr to object slot
+	};
+} gravity_value_t;
+
+// All VM shares the same foundation classes
+extern gravity_class_t *gravity_class_object;
+extern gravity_class_t *gravity_class_bool;
+extern gravity_class_t *gravity_class_null;
+extern gravity_class_t *gravity_class_undefined;
+extern gravity_class_t *gravity_class_int;
+extern gravity_class_t *gravity_class_float;
+extern gravity_class_t *gravity_class_function;
+extern gravity_class_t *gravity_class_closure;
+extern gravity_class_t *gravity_class_fiber;
+extern gravity_class_t *gravity_class_class;
+extern gravity_class_t *gravity_class_string;
+extern gravity_class_t *gravity_class_instance;
+extern gravity_class_t *gravity_class_list;
+extern gravity_class_t *gravity_class_map;
+extern gravity_class_t *gravity_class_module;
+extern gravity_class_t *gravity_class_range;
+extern gravity_class_t *gravity_class_upvalue;
+
+typedef marray_t(gravity_value_t)		gravity_value_r;		// array of values
+typedef struct gravity_hash_t			gravity_hash_t;			// forward declaration
+typedef struct gravity_vm				gravity_vm;				// vm is an opaque data type
+typedef bool (*gravity_c_internal)(gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex);
+
+typedef enum {
+	EXEC_TYPE_SPECIAL_GETTER = 0,				// index inside special gravity_function_t union to represent getter func
+	EXEC_TYPE_SPECIAL_SETTER = 1,				// index inside special gravity_function_t union to represent setter func
+} gravity_special_index;
+
+typedef enum {
+	EXEC_TYPE_NATIVE,							// native gravity code (can change stack)
+	EXEC_TYPE_INTERNAL,							// c internal code (can change stack)
+	EXEC_TYPE_BRIDGED,							// external code to be executed by delegate (can change stack)
+	EXEC_TYPE_SPECIAL							// special execution like getter and setter (can be NATIVE, INTERNAL)
+} gravity_exec_type;
+
+typedef struct {
+	bool					isdark;				// flag to check if object is reachable
+	gravity_object_t		*next;				// to track next object in the linked list
+} gravity_gc_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+
+	void					*xdata;				// extra bridged data
+	const char				*identifier;		// function name
+	uint16_t				nparams;			// number of formal parameters
+	uint16_t				nlocals;			// number of local variables
+	uint16_t				ntemps;				// number of temporary values used
+	uint16_t				nupvalues;			// number of up values (if any)
+	gravity_exec_type		tag;				// can be EXEC_TYPE_NATIVE (default), EXEC_TYPE_INTERNAL, EXEC_TYPE_BRIDGED or EXEC_TYPE_SPECIAL
+	union {
+		// tag == EXEC_TYPE_NATIVE
+		struct {
+			gravity_value_r	cpool;				// constant pool
+			uint32_t		ninsts;				// number of instructions in the bytecode
+			uint32_t		*bytecode;			// bytecode as array of 32bit values
+			float			purity;				// experimental value
+			bool			useargs;			// flag set by the compiler to optimize the creation of the arguments array only if needed
+		};
+		
+		// tag == EXEC_TYPE_INTERNAL
+		gravity_c_internal	internal;			// function callback
+		
+		// tag == EXEC_TYPE_SPECIAL
+		struct {
+			uint16_t		index;				// property index to speed-up default getter and setter
+			void			*special[2];		// getter/setter functions
+		};
+	};
+} gravity_function_t;
+
+typedef struct upvalue_s {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_value_t			*value;				// ptr to open value on the stack or to closed value on this struct
+	gravity_value_t			closed;				// copy of the value once has been closed
+	struct upvalue_s		*next;				// ptr to the next open upvalue
+} gravity_upvalue_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_function_t		*f;					// function prototype
+	gravity_upvalue_t		**upvalue;			// upvalue array
+} gravity_closure_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_value_r			array;				// dinamic array of values
+} gravity_list_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_hash_t			*hash;				// hash table
+} gravity_map_t;
+
+// Call frame used for function call
+typedef struct {
+	uint32_t				*ip;				// instruction pointer
+	uint32_t				dest;				// destination register that will receive result
+	uint16_t				nargs;				// number of effective arguments passed to the function
+	gravity_list_t			*args;				// implicit special _args array
+	gravity_closure_t		*closure;			// closure being executed
+	gravity_value_t			*stackstart;		// first stack slot used by this call frame (receiver, plus parameters, locals and temporaries)
+	bool					outloop;			// special case for events or native code executed from C that must be executed separately
+} gravity_callframe_t;
+
+// Fiber is the core executable model
+typedef struct fiber_s {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_value_t			*stack;				// stack buffer (grown as needed and it holds locals and temps)
+	gravity_value_t			*stacktop;			// current stack ptr
+	uint32_t				stackalloc;			// number of allocated values
+	
+	gravity_callframe_t		*frames;			// callframes buffer (grown as needed but never shrinks)
+	uint32_t				nframes;			// number of frames currently in use
+	uint32_t				framesalloc;		// number of allocated frames
+	
+	gravity_upvalue_t		*upvalues;			// linked list used to keep track of open upvalues
+	
+	char					*error;				// runtime error message
+	bool					trying;				// set when the try flag is set by the user
+	struct fiber_s			*caller;			// optional caller fiber
+	gravity_value_t			result;				// end result of the fiber
+} gravity_fiber_t;
+
+typedef struct gravity_class_s {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_class_t			*objclass;			// meta class
+	const char				*identifier;		// class name
+	bool					has_outer;			// flag used to automatically set ivar 0 to outer class (if any)
+	bool					is_struct;			// flag to mark class as a struct
+	bool					is_inited;			// flag used to mark already init meta-classes (to be improved)
+	bool					unused;				// unused padding byte
+	void					*xdata;				// extra bridged data
+	struct gravity_class_s	*superclass;		// reference to the super class
+	gravity_hash_t			*htable;			// hash table
+	uint32_t				nivars;				// number of instance variables
+	//cstring_r				debug;				// ivar index to name debug info
+	gravity_value_t			*ivars;				// static variables
+} gravity_class_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	const char				*identifier;		// module name
+	gravity_hash_t			*htable;			// hash table
+} gravity_module_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_class_t			*objclass;			// real instance class
+	void					*xdata;				// extra bridged data
+	gravity_value_t			ivars[];			// instance variables (MUST BE LAST in the struct!)
+} gravity_instance_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	char					*s;					// pointer to NULL terminated string
+	uint32_t				hash;				// string hash (type to be keeped in sync with gravity_hash_size_t)
+	uint32_t				len;				// actual string length
+	uint32_t				alloc;				// bytes allocated for string
+} gravity_string_t;
+
+typedef struct {
+	gravity_class_t			*isa;				// to be an object
+	gravity_gc_t			gc;					// to be collectable by the garbage collector
+	
+	gravity_int_t			from;				// range start
+	gravity_int_t			to;					// range end
+} gravity_range_t;
+
+typedef void (*code_dump_function) (void *code);
+typedef marray_t(gravity_function_t*)		gravity_function_r;		// array of functions
+typedef marray_t(gravity_class_t*)			gravity_class_r;		// array of classes
+typedef marray_t(gravity_object_t*)			gravity_object_r;		// array of objects
+
+// MARK: - MODULE -
+gravity_module_t	*gravity_module_new (gravity_vm *vm, const char *identifier);
+void				gravity_module_free (gravity_vm *vm, gravity_module_t *m);
+void				gravity_module_blacken (gravity_vm *vm, gravity_module_t *m);
+uint32_t			gravity_module_size (gravity_vm *vm, gravity_module_t *m);
+
+// MARK: - FUNCTION -
+gravity_function_t	*gravity_function_new (gravity_vm *vm, const char *identifier, uint16_t nparams, uint16_t nlocals, uint16_t ntemps, void *code);
+gravity_function_t	*gravity_function_new_internal (gravity_vm *vm, const char *identifier, gravity_c_internal exec, uint16_t nparams);
+gravity_function_t	*gravity_function_new_special (gravity_vm *vm, const char *identifier, uint16_t index, void *getter, void *setter);
+gravity_function_t	*gravity_function_new_bridged (gravity_vm *vm, const char *identifier, void *xdata);
+uint16_t			gravity_function_cpool_add (gravity_vm *vm, gravity_function_t *f, gravity_value_t v);
+gravity_value_t		gravity_function_cpool_get (gravity_function_t *f, uint16_t i);
+void				gravity_function_dump (gravity_function_t *f, code_dump_function codef);
+void				gravity_function_setouter (gravity_function_t *f, gravity_object_t *outer);
+void				gravity_function_setxdata (gravity_function_t *f, void *xdata);
+void				gravity_function_serialize (gravity_function_t *f, json_t *json);
+uint32_t			*gravity_bytecode_deserialize (const char *buffer, size_t len, uint32_t *ninst);
+gravity_function_t	*gravity_function_deserialize (gravity_vm *vm, json_value *json);
+void				gravity_function_free (gravity_vm *vm, gravity_function_t *f);
+void				gravity_function_blacken (gravity_vm *vm, gravity_function_t *f);
+uint32_t			gravity_function_size (gravity_vm *vm, gravity_function_t *f);
+
+// MARK: - CLOSURE -
+gravity_closure_t	*gravity_closure_new (gravity_vm *vm, gravity_function_t *f);
+void				gravity_closure_free (gravity_vm *vm, gravity_closure_t *closure);
+uint32_t			gravity_closure_size (gravity_vm *vm, gravity_closure_t *closure);
+void				gravity_closure_blacken (gravity_vm *vm, gravity_closure_t *closure);
+
+// MARK: - UPVALUE -
+gravity_upvalue_t	*gravity_upvalue_new (gravity_vm *vm, gravity_value_t *value);
+uint32_t			gravity_upvalue_size (gravity_vm *vm, gravity_upvalue_t *upvalue);
+void				gravity_upvalue_blacken (gravity_vm *vm, gravity_upvalue_t *upvalue);
+void				gravity_upvalue_free(gravity_vm *vm, gravity_upvalue_t *upvalue);
+
+// MARK: - CLASS -
+void				gravity_class_bind (gravity_class_t *c, const char *key, gravity_value_t value);
+gravity_class_t		*gravity_class_getsuper (gravity_class_t *c);
+bool				gravity_class_grow (gravity_class_t *c, uint32_t n);
+bool				gravity_class_setsuper (gravity_class_t *subclass, gravity_class_t *superclass);
+gravity_class_t		*gravity_class_new_single (gravity_vm *vm, const char *identifier, uint32_t nfields);
+gravity_class_t		*gravity_class_new_pair (gravity_vm *vm, const char *identifier, gravity_class_t *superclass, uint32_t nivar, uint32_t nsvar);
+gravity_class_t		*gravity_class_get_meta (gravity_class_t *c);
+bool				gravity_class_is_meta (gravity_class_t *c);
+uint32_t			gravity_class_count_ivars (gravity_class_t *c);
+void				gravity_class_dump (gravity_class_t *c);
+void				gravity_class_setxdata (gravity_class_t *c, void *xdata);
+int16_t				gravity_class_add_ivar (gravity_class_t *c, const char *identifier);
+void				gravity_class_serialize (gravity_class_t *c, json_t *json);
+gravity_class_t		*gravity_class_deserialize (gravity_vm *vm, json_value *json);
+void				gravity_class_free (gravity_vm *vm, gravity_class_t *c);
+void				gravity_class_free_core (gravity_vm *vm, gravity_class_t *c);
+gravity_object_t	*gravity_class_lookup (gravity_class_t *c, gravity_value_t key);
+gravity_closure_t	*gravity_class_lookup_closure (gravity_class_t *c, gravity_value_t key);
+gravity_closure_t	*gravity_class_lookup_constructor (gravity_class_t *c, uint32_t nparams);
+void				gravity_class_blacken (gravity_vm *vm, gravity_class_t *c);
+uint32_t			gravity_class_size (gravity_vm *vm, gravity_class_t *c);
+
+// MARK: - FIBER -
+gravity_fiber_t		*gravity_fiber_new (gravity_vm *vm, gravity_closure_t *closure, uint32_t nstack, uint32_t nframes);
+void				gravity_fiber_reassign (gravity_fiber_t *fiber, gravity_closure_t *closure, uint16_t nargs);
+void				gravity_fiber_seterror (gravity_fiber_t *fiber, const char *error);
+void				gravity_fiber_free (gravity_vm *vm, gravity_fiber_t *fiber);
+void				gravity_fiber_blacken (gravity_vm *vm, gravity_fiber_t *fiber);
+uint32_t			gravity_fiber_size (gravity_vm *vm, gravity_fiber_t *fiber);
+
+// MARK: - INSTANCE -
+gravity_instance_t	*gravity_instance_new (gravity_vm *vm, gravity_class_t *c);
+gravity_instance_t	*gravity_instance_dup (gravity_vm *vm, gravity_instance_t *src);
+void				gravity_instance_setivar (gravity_instance_t *instance, uint32_t idx, gravity_value_t value);
+void				gravity_instance_setxdata (gravity_instance_t *i, void *xdata);
+void				gravity_instance_free (gravity_vm *vm, gravity_instance_t *i);
+gravity_closure_t	*gravity_instance_lookup_event (gravity_instance_t *i, const char *name);
+void				gravity_instance_blacken (gravity_vm *vm, gravity_instance_t *i);
+uint32_t			gravity_instance_size (gravity_vm *vm, gravity_instance_t *i);
+
+// MARK: - VALUE -
+bool				gravity_value_equals (gravity_value_t v1, gravity_value_t v2);
+uint32_t			gravity_value_hash (gravity_value_t value);
+gravity_class_t		*gravity_value_getclass (gravity_value_t v);
+gravity_class_t		*gravity_value_getsuper (gravity_value_t v);
+void				gravity_value_free (gravity_vm *vm, gravity_value_t v);
+void				gravity_value_serialize (gravity_value_t v, json_t *json);
+void				gravity_value_dump (gravity_value_t v, char *buffer, uint16_t len);
+bool				gravity_value_isobject (gravity_value_t v);
+void				*gravity_value_xdata (gravity_value_t value);
+void				gravity_value_blacken (gravity_vm *vm, gravity_value_t v);
+uint32_t			gravity_value_size (gravity_vm *vm, gravity_value_t v);
+
+// MARK: - OBJECT -
+void				gravity_object_serialize (gravity_object_t *obj, json_t *json);
+bool				gravity_object_deserialize (gravity_vm *vm, json_value *entry, gravity_object_t **obj);
+void				gravity_object_free (gravity_vm *vm, gravity_object_t *obj);
+void				gravity_object_blacken (gravity_vm *vm, gravity_object_t *obj);
+uint32_t			gravity_object_size (gravity_vm *vm, gravity_object_t *obj);
+const char			*gravity_object_debug (gravity_object_t *obj);
+
+// MARK: - LIST -
+gravity_list_t		*gravity_list_new (gravity_vm *vm, uint32_t n);
+gravity_list_t		*gravity_list_from_array (gravity_vm *vm, uint32_t n, gravity_value_t *p);
+void				gravity_list_free (gravity_vm *vm, gravity_list_t *list);
+void				gravity_list_append_list (gravity_vm *vm, gravity_list_t *list1, gravity_list_t *list2);
+void				gravity_list_blacken (gravity_vm *vm, gravity_list_t *list);
+uint32_t			gravity_list_size (gravity_vm *vm, gravity_list_t *list);
+
+// MARK: - MAP -
+gravity_map_t		*gravity_map_new (gravity_vm *vm, uint32_t n);
+void				gravity_map_free (gravity_vm *vm, gravity_map_t *map);
+void				gravity_map_append_map (gravity_vm *vm, gravity_map_t *map1, gravity_map_t *map2);
+void				gravity_map_insert (gravity_vm *vm, gravity_map_t *map, gravity_value_t key, gravity_value_t value);
+void				gravity_map_blacken (gravity_vm *vm, gravity_map_t *map);
+uint32_t			gravity_map_size (gravity_vm *vm, gravity_map_t *map);
+
+// MARK: - RANGE -
+gravity_range_t		*gravity_range_new (gravity_vm *vm, gravity_int_t from, gravity_int_t to, bool inclusive);
+void				gravity_range_free (gravity_vm *vm, gravity_range_t *range);
+void				gravity_range_blacken (gravity_vm *vm, gravity_range_t *range);
+uint32_t			gravity_range_size (gravity_vm *vm, gravity_range_t *range);
+
+/// MARK: - STRING -
+gravity_value_t		gravity_string_to_value (gravity_vm *vm, const char *s, uint32_t len);
+gravity_string_t	*gravity_string_new (gravity_vm *vm, char *s, uint32_t len, uint32_t alloc);
+inline void			gravity_string_set (gravity_string_t *obj, char *s, uint32_t len);
+void				gravity_string_free (gravity_vm *vm, gravity_string_t *value);
+void				gravity_string_blacken (gravity_vm *vm, gravity_string_t *string);
+uint32_t			gravity_string_size (gravity_vm *vm, gravity_string_t *string);
+
+// MARK: - CALLBACKS -
+// HASH FREE CALLBACK FUNCTION
+void				gravity_hash_keyvaluefree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data);
+void				gravity_hash_keyfree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data);
+void				gravity_hash_valuefree (gravity_hash_t *table, gravity_value_t key, gravity_value_t value, void *data);
+
+#endif

+ 294 - 0
src/utils/gravity_debug.c

@@ -0,0 +1,294 @@
+//
+//  gravity_debug.c
+//  gravity
+//
+//  Created by Marco Bambini on 01/04/16.
+//  Copyright (c) 2016 CreoLabs. All rights reserved.
+//
+
+#include <assert.h>
+#include "gravity_value.h"
+#include "gravity_vmmacros.h"
+#include "gravity_debug.h"
+
+const char *opcode_constname (int n) {
+	switch (n) {
+		case CPOOL_VALUE_SUPER: return "SUPER";
+		case CPOOL_VALUE_NULL: return "NULL";
+		case CPOOL_VALUE_UNDEFINED: return "UNDEFINED";
+		case CPOOL_VALUE_ARGUMENTS: return "ARGUMENTS";
+		case CPOOL_VALUE_TRUE: return "TRUE";
+		case CPOOL_VALUE_FALSE: return "FALSE";
+		case CPOOL_VALUE_FUNC: return "FUNC";
+	}
+	
+	assert(0);
+	return "N/A";
+}
+
+const char *opcode_name (opcode_t op) {
+	static const char *optable[] = {
+		"RET0", "HALT", "NOP", "RET", "CALL", "LOAD", "LOADS", "LOADAT",
+		"LOADK", "LOADG", "LOADI", "LOADU", "MOVE", "STORE", "STOREAT",
+		"STOREG", "STOREU", "JUMP", "JUMPF", "SWITCH", "ADD", "SUB", "DIV",
+		"MUL", "REM", "AND", "OR", "LT", "GT", "EQ", "LEQ", "GEQ", "NEQ",
+		"EQQ", "NEQQ", "ISA", "MATCH", "NEG", "NOT", "LSHIFT", "RSHIFT", "BAND",
+		"BOR", "BXOR", "BNOT", "MAPNEW", "LISTNEW", "RANGENEW", "SETLIST",
+		"CLOSURE", "CLOSE", "RESERVED1", "RESERVED2", "RESERVED3", "RESERVED4",
+		"RESERVED5", "RESERVED6"};
+	return optable[op];
+}
+
+#define DUMP_VM(buffer, bindex, ...)					bindex += snprintf(&buffer[bindex], balloc-bindex, "%06u\t", pc);	\
+														bindex += snprintf(&buffer[bindex], balloc-bindex, __VA_ARGS__);		\
+														bindex += snprintf(&buffer[bindex], balloc-bindex, "\n");
+
+#define DUMP_VM_NOCR(buffer, bindex, ...)				bindex += snprintf(&buffer[bindex], balloc-bindex, "%06u\t", pc);	\
+														bindex += snprintf(&buffer[bindex], balloc-bindex, __VA_ARGS__);
+
+#define DUMP_VM_RAW(buffer, bindex, ...)				bindex += snprintf(&buffer[bindex], balloc-bindex, __VA_ARGS__);
+
+const char *gravity_disassemble (const char *bcode, uint32_t blen) {
+	uint32_t	*ip = NULL;
+	uint32_t	pc = 0, inst = 0, ninsts = 0;
+	opcode_t	op;
+	
+	const int	rowlen = 256;
+	uint32_t	bindex = 0;
+	uint32_t	balloc = 0;
+	char		*buffer = NULL;
+	
+	// decode textual buffer to real bytecode
+	ip = gravity_bytecode_deserialize(bcode, blen, &ninsts);
+	if ((ip == NULL) || (ninsts == 0)) goto abort_disassemble;
+	
+	// allocate a buffer big enought to fit all disassembled bytecode
+	// I assume that each instruction (each row) will be 256 chars long
+	balloc = ninsts * rowlen;
+	buffer = mem_alloc(balloc);
+	if (!buffer) goto abort_disassemble;
+		
+	// conversion loop
+	while (pc < ninsts) {
+		inst = *ip++;
+		op = (opcode_t)OPCODE_GET_OPCODE(inst);
+		
+		switch (op) {
+			case NOP: {
+				DUMP_VM(buffer, bindex, "NOP");
+				break;
+			}
+				
+			case MOVE: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				DUMP_VM(buffer, bindex, "MOVE %d %d", r1, r2);
+				break;
+			}
+				
+			case LOADI: {
+				//OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t value); if no support for signed int
+				OPCODE_GET_ONE8bit_SIGN_ONE17bit(inst, const uint32_t r1, const int32_t value);
+				DUMP_VM(buffer, bindex, "LOADI %d %d", r1, value);
+				break;
+			}
+				
+			case LOADK: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t index);
+				if (index < CPOOL_INDEX_MAX) {
+					DUMP_VM(buffer, bindex, "LOADK %d %d", r1, index);
+				} else {
+					const char *special;
+					switch (index) {
+						case CPOOL_VALUE_SUPER: special = "SUPER"; break;
+						case CPOOL_VALUE_NULL: special = "NULL"; break;
+						case CPOOL_VALUE_UNDEFINED: special = "UNDEFINED"; break;
+						case CPOOL_VALUE_ARGUMENTS: special = "ARGUMENTS"; break;
+						case CPOOL_VALUE_TRUE: special = "TRUE"; break;
+						case CPOOL_VALUE_FALSE: special = "FALSE"; break;
+						default: ASSERT(0, "Invalid index in LOADK opcode"); break;
+					}
+					DUMP_VM(buffer, bindex, "LOADK %d %s", r1, special);
+				}
+				break;
+			}
+				
+			case LOADG: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
+				DUMP_VM(buffer, bindex, "LOADG %d %d", r1, index);
+				break;
+			}
+			
+			case LOAD:
+			case LOADS:
+			case LOADAT: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DUMP_VM(buffer, bindex, "%s %d %d %d", (op == LOAD) ? "LOAD" : "LOADAT", r1, r2, r3);
+				break;
+			}
+				
+			case LOADU: {
+				ASSERT(0, "To be implemented");
+				break;
+			}
+				
+			case STOREG: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, uint32_t r1, int32_t index);
+				DUMP_VM(buffer, bindex, "STOREG %d %d", r1, index);
+				break;
+			}
+				
+			case STOREU: {
+				ASSERT(0, "To be implemented");
+				break;
+			}
+				
+			case STORE:
+			case STOREAT: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DUMP_VM(buffer, bindex, "%s %d %d %d", (op == STORE) ? "STORE" : "STOREAT", r1, r2, r3);
+				break;
+			}
+			
+			case EQQ:
+			case NEQQ:
+			case ISA:
+			case MATCH:
+			case LT:
+			case GT:
+			case EQ:
+			case LEQ:
+			case GEQ:
+			case NEQ: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DUMP_VM(buffer, bindex, "%s %d %d %d", opcode_name(op), r1, r2, r3);
+				break;
+			}
+				
+			// binary operators
+			case LSHIFT:
+			case RSHIFT:
+			case BAND:
+			case BOR:
+			case BXOR:
+			case ADD:
+			case SUB:
+			case DIV:
+			case MUL:
+			case REM:
+			case AND:
+			case OR: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DUMP_VM(buffer, bindex, "%s %d %d %d", opcode_name(op), r1, r2, r3);
+				break;
+			}
+				
+			// unary operators
+			case BNOT:
+			case NEG:
+			case NOT: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				#pragma unused(r3)
+				DUMP_VM(buffer, bindex, "%s %d %d %d", opcode_name(op), r1, r2, r3);
+				break;
+			}
+			
+			case RANGENEW: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, const uint32_t r2, const uint32_t r3);
+				DUMP_VM(buffer, bindex, "%s %d %d %d", opcode_name(op), r1, r2, r3);
+				break;
+			}
+			
+			case JUMPF: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const int32_t value);
+				DUMP_VM(buffer, bindex, "JUMPF %d %d", r1, value);
+				break;
+			}
+				
+			case JUMP: {
+				OPCODE_GET_ONE26bit(inst, const uint32_t value);
+				DUMP_VM(buffer, bindex, "JUMP %d", value);
+				break;
+			}
+				
+			case CALL: {
+				// CALL A B C => R(A) = B(C0... CN)
+				OPCODE_GET_THREE8bit(inst, const uint32_t r1, const uint32_t r2, uint32_t r3);
+				DUMP_VM(buffer, bindex, "CALL %d %d %d", r1, r2, r3);
+				break;
+			}
+				
+			case RET0:
+			case RET: {
+				
+				if (op == RET0) {
+					DUMP_VM(buffer, bindex, "RET0");
+				} else {
+					OPCODE_GET_ONE8bit(inst, const uint32_t r1);
+					DUMP_VM(buffer, bindex, "RET %d", r1);
+				}
+				break;
+			}
+				
+			case HALT: {
+				DUMP_VM(buffer, bindex, "HALT");
+				break;
+			}
+				
+			case SWITCH: {
+				ASSERT(0, "To be implemented");
+				break;
+			}
+				
+			case MAPNEW: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
+				DUMP_VM(buffer, bindex, "MAPNEW %d %d", r1, n);
+				break;
+			}
+				
+			case LISTNEW: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t n);
+				DUMP_VM(buffer, bindex, "LISTNEW %d %d", r1, n);
+				break;
+			}
+				
+			case SETLIST: {
+				OPCODE_GET_TWO8bit_ONE10bit(inst, const uint32_t r1, uint32_t r2, const uint32_t r3);
+				#pragma unused(r3)
+				DUMP_VM(buffer, bindex, "SETLIST %d %d", r1, r2);
+				break;
+			}
+			
+			case CLOSURE: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				DUMP_VM(buffer, bindex, "CLOSURE %d %d", r1, r2);
+				break;
+			}
+				
+			case CLOSE: {
+				OPCODE_GET_ONE8bit_ONE18bit(inst, const uint32_t r1, const uint32_t r2);
+				#pragma unused(r2)
+				DUMP_VM(buffer, bindex, "CLOSE %d", r1);
+				break;
+			}
+				
+			case RESERVED1:
+			case RESERVED2:
+			case RESERVED3:
+			case RESERVED4:
+			case RESERVED5:
+			case RESERVED6: {
+				DUMP_VM(buffer, bindex, "RESERVED");
+				break;
+			}
+		}
+		
+		++pc;
+	}
+	
+	return buffer;
+	
+abort_disassemble:
+	if (ip) mem_free(ip);
+	if (buffer) mem_free(buffer);
+	return NULL;
+}

+ 18 - 0
src/utils/gravity_debug.h

@@ -0,0 +1,18 @@
+//
+//  gravity_debug.h
+//  gravity
+//
+//  Created by Marco Bambini on 01/04/16.
+//  Copyright © 2016 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_DEBUG__
+#define __GRAVITY_DEBUG__
+
+#include "gravity_opcodes.h"
+
+const char *opcode_constname (int n);
+const char *opcode_name (opcode_t op);
+const char *gravity_disassemble (const char *bcode, uint32_t blen);
+
+#endif

+ 1292 - 0
src/utils/gravity_json.c

@@ -0,0 +1,1292 @@
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "gravity_json.h"
+#include "gravity_utils.h"
+#include "gravity_memory.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <assert.h>
+
+#ifdef _MSC_VER
+   #ifndef _CRT_SECURE_NO_WARNINGS
+      #define _CRT_SECURE_NO_WARNINGS
+   #endif
+#endif
+
+// MARK: - JSON Serializer -
+// Written by Marco Bambini
+
+#define JSON_MINSIZE		4096
+#define JSON_NEWLINE		"\n"
+#define JSON_NEWLINE_CHAR	'\n'
+#define JSON_PRETTYLINE		"    "
+#define JSON_PRETTYSIZE		4
+#define JSON_WRITE_SEP		json_write_raw(json, " : ", 3, false, false)
+#define JSON_TERM_FIELD		json_write_raw(json, ",", 1, false, false); json_write_raw(json, JSON_NEWLINE, 1, false, false)
+#define JSON_MAX_NESTED		1024
+#define JSON_POP_CTX(j)		--j->ctxidx
+#define JSON_PUSH_CTX(j,x)	if(j->ctxidx<JSON_MAX_NESTED-1) j->ctx[j->ctxidx++] = x
+#define JSON_CURR_XTX(j)	j->ctx[j->ctxidx-1]
+#define JSON_ESCAPE(c)		do {		\
+								new_buffer[j] = '\\';		\
+								new_buffer[j+1] = (c);		\
+								j+=2;						\
+							} while(0);						\
+
+
+typedef enum {
+	json_ctx_object,
+	json_ctx_array
+} json_ctx_t;
+
+struct json_t {
+	char		*buffer;
+	size_t		blen;
+	size_t		bused;
+	uint32_t	ident;
+	
+	json_ctx_t	ctx[JSON_MAX_NESTED];
+	size_t		ctxidx;
+};
+
+json_t *json_new (void) {
+	json_t *json = mem_alloc(sizeof(json_t));
+	assert(json);
+	
+	json->buffer = mem_alloc(JSON_MINSIZE);
+	assert(json->buffer);
+	
+	json->blen = JSON_MINSIZE;
+	json->bused = 0;
+	json->ident = 0;
+	json->ctxidx = 0;
+	
+	return json;
+}
+
+static void json_write_raw (json_t *json, const char *buffer, size_t len, bool escape, bool is_pretty) {
+	size_t	prettylen = (is_pretty) ? (json->ident * JSON_PRETTYSIZE)+1 : 0;
+	size_t	escapelen = (escape) ? 2 : 0;
+	
+	// check buffer reallocation
+	size_t reqlen = json->bused + len + prettylen + escapelen + JSON_MINSIZE;
+	if (reqlen >= json->blen) {
+		json->buffer = mem_realloc(json->buffer, json->blen + reqlen);
+		assert(json->buffer);
+		json->blen += reqlen;
+	}
+	
+	if (is_pretty) {
+		for (uint32_t i=0; i<json->ident; ++i) {
+			memcpy(json->buffer+json->bused, JSON_PRETTYLINE, JSON_PRETTYSIZE);
+			json->bused += JSON_PRETTYSIZE;
+		}
+	}
+	
+	if (escape) {
+		memcpy(json->buffer+json->bused, "\"", 1);
+		json->bused += 1;
+	}
+	
+	memcpy(json->buffer+json->bused, buffer, len);
+	json->bused += len;
+	
+	if (escape) {
+		memcpy(json->buffer+json->bused, "\"", 1);
+		json->bused += 1;
+	}
+}
+
+static void json_write_escaped (json_t *json, const char *buffer, size_t len, bool escape, bool is_pretty) {
+	char	*new_buffer = mem_alloc (len*2);
+	size_t	j = 0;
+	assert(new_buffer);
+	
+	for (size_t i=0; i<len; ++i) {
+		char c = buffer[i];
+		switch (c) {
+			case '"' : JSON_ESCAPE ('\"');	continue;
+			case '\\': JSON_ESCAPE ('\\');  continue;
+			case '\b': JSON_ESCAPE ('b');   continue;
+			case '\f': JSON_ESCAPE ('f');   continue;
+			case '\n': JSON_ESCAPE ('n');   continue;
+			case '\r': JSON_ESCAPE ('r');   continue;
+			case '\t': JSON_ESCAPE ('t');   continue;
+				
+			default: new_buffer[j] = c; ++j;break;
+		};
+	}
+	
+	json_write_raw(json, new_buffer, j, escape, is_pretty);
+	mem_free(new_buffer);
+}
+
+
+void json_begin_object (json_t *json, const char *key) {
+	if (key) {
+		// check context here and if context is json_ctx_array skip
+		if (JSON_CURR_XTX(json) == json_ctx_object) {
+			json_write_raw (json, key, strlen(key), true, true);
+			JSON_WRITE_SEP;
+		}
+	}
+	
+	JSON_PUSH_CTX(json, json_ctx_object);
+	json_write_raw(json, "{", 1, false, false);
+	json_write_raw(json, JSON_NEWLINE, 1, false, false);
+	
+	++json->ident;
+}
+
+void json_end_object (json_t *json) {
+	--json->ident;
+	JSON_POP_CTX(json);
+	
+	// check latest 2 characters
+	if ((json->buffer[json->bused-1] == JSON_NEWLINE_CHAR) && (json->buffer[json->bused-2] == ',')) {
+		json->buffer[json->bused-2] = JSON_NEWLINE_CHAR;
+		json->buffer[json->bused-1] = 0;
+		--json->bused;
+	}
+	
+	json_write_raw(json, "}", 1, false, true);
+	if (json->ident) {JSON_TERM_FIELD;}
+	else json_write_raw(json, JSON_NEWLINE, 1, false, false);
+}
+
+void json_begin_array (json_t *json, const char *key) {
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	
+	JSON_PUSH_CTX(json, json_ctx_array);
+	json_write_raw(json, "[", 1, false, false);
+	json_write_raw(json, JSON_NEWLINE, 1, false, false);
+	
+	++json->ident;
+}
+
+void json_end_array (json_t *json) {
+	--json->ident;
+	JSON_POP_CTX(json);
+	
+	// check latest 2 characters
+	if ((json->buffer[json->bused-1] == JSON_NEWLINE_CHAR) && (json->buffer[json->bused-2] == ',')) {
+		json->buffer[json->bused-2] = JSON_NEWLINE_CHAR;
+		json->buffer[json->bused-1] = 0;
+		--json->bused;
+	}
+	
+	json_write_raw(json, "]", 1, false, true);
+	JSON_TERM_FIELD;
+}
+
+void json_add_string (json_t *json, const char *key, const char *value, size_t len) {
+	if (!value) {
+		json_add_null(json, key);
+		return;
+	}
+	
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	
+	// check if string value needs to be escaped
+	bool write_escaped = false;
+	for (size_t i=0; i<len; ++i) {
+		if (value[i] == '"') {write_escaped = true; break;}
+	}
+	if (len == 0)
+		write_escaped = true;
+	
+	if (write_escaped)
+		json_write_escaped(json, value, len, true, (key == NULL));
+	else
+		json_write_raw(json, value, len, true, (key == NULL));
+	JSON_TERM_FIELD;
+}
+
+void json_add_cstring (json_t *json, const char *key, const char *value) {
+	json_add_string(json, key, value, (value) ? strlen(value) : 0);
+}
+
+void json_add_int (json_t *json, const char *key, int64_t value) {
+	char buffer[512];
+	size_t len = snprintf(buffer, sizeof(buffer), "%lld", value);
+	
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	json_write_raw(json, buffer, len, false, (key == NULL));
+	JSON_TERM_FIELD;
+	
+}
+
+void json_add_double (json_t *json, const char *key, double value) {
+	char buffer[512];
+	size_t len = snprintf(buffer, sizeof(buffer), "%f", value);
+	
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	json_write_raw(json, buffer, len, false, (key == NULL));
+	JSON_TERM_FIELD;
+}
+
+void json_add_bool (json_t *json, const char *key, bool bvalue) {
+	const char *value = (bvalue) ? "true" : "false";
+	
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	json_write_raw(json, value, strlen(value), false, (key == NULL));
+	JSON_TERM_FIELD;
+}
+
+void json_add_null (json_t *json, const char *key) {
+	if (key) {
+		json_write_raw (json, key, strlen(key), true, true);
+		JSON_WRITE_SEP;
+	}
+	json_write_raw(json, "null", 4, false, (key == NULL));
+	JSON_TERM_FIELD;
+}
+
+void json_free (json_t *json) {
+	mem_free(json->buffer);
+	mem_free(json);
+}
+
+const char *json_buffer (json_t *json, size_t *len) {
+	assert(json->buffer);
+	if (len) *len = json->bused;
+	return json->buffer;
+}
+
+bool json_write_file (json_t *json, const char *path) {
+	return file_write(path, json->buffer, json->bused);
+}
+
+void json_pop (json_t *json, uint32_t n) {
+	json->bused -= n;
+}
+
+#undef JSON_MINSIZE
+#undef JSON_NEWLINE
+#undef JSON_NEWLINE_CHAR
+#undef JSON_PRETTYLINE
+#undef JSON_PRETTYSIZE
+#undef JSON_WRITE_SEP
+#undef JSON_TERM_FIELD
+
+// MARK: - JSON Parser -
+// Written by https://github.com/udp/json-parser
+
+const struct _json_value json_value_none;
+typedef unsigned int json_uchar;
+
+static unsigned char hex_value (json_char c)
+{
+   if (isdigit(c))
+      return c - '0';
+
+   switch (c) {
+      case 'a': case 'A': return 0x0A;
+      case 'b': case 'B': return 0x0B;
+      case 'c': case 'C': return 0x0C;
+      case 'd': case 'D': return 0x0D;
+      case 'e': case 'E': return 0x0E;
+      case 'f': case 'F': return 0x0F;
+      default: return 0xFF;
+   }
+}
+
+typedef struct
+{
+   unsigned long used_memory;
+
+   unsigned int uint_max;
+   unsigned long ulong_max;
+
+   json_settings settings;
+   int first_pass;
+
+   const json_char * ptr;
+   unsigned int cur_line, cur_col;
+
+} json_state;
+
+static void * default_alloc (size_t size, int zero, void * user_data)
+{
+	#pragma unused(zero, user_data)
+	return mem_alloc(size);
+	//return zero ? calloc (1, size) : malloc (size);
+}
+
+static void default_free (void * ptr, void * user_data)
+{
+	#pragma unused(user_data)
+	mem_free(ptr);
+	//free (ptr);
+}
+
+static void * json_alloc (json_state * state, unsigned long size, int zero)
+{
+   if ((state->ulong_max - state->used_memory) < size)
+      return 0;
+
+   if (state->settings.max_memory
+         && (state->used_memory += size) > state->settings.max_memory)
+   {
+      return 0;
+   }
+
+   return state->settings.memory_alloc (size, zero, state->settings.user_data);
+}
+
+static int new_value (json_state * state,
+                      json_value ** top, json_value ** root, json_value ** alloc,
+                      json_type type)
+{
+   json_value * value;
+   int values_size;
+
+   if (!state->first_pass)
+   {
+      value = *top = *alloc;
+      *alloc = (*alloc)->_reserved.next_alloc;
+
+      if (!*root)
+         *root = value;
+
+      switch (value->type)
+      {
+         case json_array:
+
+            if (value->u.array.length == 0)
+               break;
+
+            if (! (value->u.array.values = (json_value **) json_alloc
+               (state, value->u.array.length * sizeof (json_value *), 0)) )
+            {
+               return 0;
+            }
+
+            value->u.array.length = 0;
+            break;
+
+         case json_object:
+
+            if (value->u.object.length == 0)
+               break;
+
+            values_size = sizeof (*value->u.object.values) * value->u.object.length;
+
+            if (! (value->u.object.values = (json_object_entry *) json_alloc
+                  (state, values_size + ((unsigned long) value->u.object.values), 0)) )
+            {
+               return 0;
+            }
+
+            value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size;
+
+            value->u.object.length = 0;
+            break;
+
+         case json_string:
+
+            if (! (value->u.string.ptr = (json_char *) json_alloc
+               (state, (value->u.string.length + 1) * sizeof (json_char), 0)) )
+            {
+               return 0;
+            }
+
+            value->u.string.length = 0;
+            break;
+
+         default:
+            break;
+      };
+
+      return 1;
+   }
+
+   if (! (value = (json_value *) json_alloc
+         (state, sizeof (json_value) + state->settings.value_extra, 1)))
+   {
+      return 0;
+   }
+
+   if (!*root)
+      *root = value;
+
+   value->type = type;
+   value->parent = *top;
+
+   #ifdef JSON_TRACK_SOURCE
+      value->line = state->cur_line;
+      value->col = state->cur_col;
+   #endif
+
+   if (*alloc)
+      (*alloc)->_reserved.next_alloc = value;
+
+   *alloc = *top = value;
+
+   return 1;
+}
+
+#define whitespace \
+   case '\n': ++ state.cur_line;  state.cur_col = 0; \
+   case ' ': case '\t': case '\r'
+
+#define string_add(b)  \
+   do { if (!state.first_pass) string [string_length] = b;  ++ string_length; } while (0);
+
+#define line_and_col \
+   state.cur_line, state.cur_col
+
+static const long
+   flag_next             = 1 << 0,
+   flag_reproc           = 1 << 1,
+   flag_need_comma       = 1 << 2,
+   flag_seek_value       = 1 << 3, 
+   flag_escaped          = 1 << 4,
+   flag_string           = 1 << 5,
+   flag_need_colon       = 1 << 6,
+   flag_done             = 1 << 7,
+   flag_num_negative     = 1 << 8,
+   flag_num_zero         = 1 << 9,
+   flag_num_e            = 1 << 10,
+   flag_num_e_got_sign   = 1 << 11,
+   flag_num_e_negative   = 1 << 12,
+   flag_line_comment     = 1 << 13,
+   flag_block_comment    = 1 << 14;
+
+json_value * json_parse_ex (json_settings * settings,
+                            const json_char * json,
+                            size_t length,
+                            char * error_buf)
+{
+   json_char error [json_error_max];
+   const json_char * end;
+   json_value *top, *root = NULL, * alloc = 0;
+   json_state state = EMPTY_STATE_STRUCT;
+   long flags;
+   long num_digits = 0, num_e = 0;
+   json_int_t num_fraction = 0;
+
+   /* Skip UTF-8 BOM
+    */
+   if (length >= 3 && ((unsigned char) json [0]) == 0xEF
+                   && ((unsigned char) json [1]) == 0xBB
+                   && ((unsigned char) json [2]) == 0xBF)
+   {
+      json += 3;
+      length -= 3;
+   }
+
+   error[0] = '\0';
+   end = (json + length);
+
+   memcpy (&state.settings, settings, sizeof (json_settings));
+
+   if (!state.settings.memory_alloc)
+      state.settings.memory_alloc = default_alloc;
+
+   if (!state.settings.memory_free)
+      state.settings.memory_free = default_free;
+
+   memset (&state.uint_max, 0xFF, sizeof (state.uint_max));
+   memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max));
+
+   state.uint_max -= 8; /* limit of how much can be added before next check */
+   state.ulong_max -= 8;
+
+   for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass)
+   {
+      json_uchar uchar;
+      unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
+      json_char * string = 0;
+      unsigned int string_length = 0;
+
+      top = root = 0;
+      flags = flag_seek_value;
+
+      state.cur_line = 1;
+
+      for (state.ptr = json ;; ++ state.ptr)
+      {
+         json_char b = (state.ptr == end ? 0 : *state.ptr);
+         
+         if (flags & flag_string)
+         {
+            if (!b)
+            {  sprintf (error, "Unexpected EOF in string (at %d:%d)", line_and_col);
+               goto e_failed;
+            }
+
+            if (string_length > state.uint_max)
+               goto e_overflow;
+
+            if (flags & flag_escaped)
+            {
+               flags &= ~ flag_escaped;
+
+               switch (b)
+               {
+                  case 'b':  string_add ('\b');  break;
+                  case 'f':  string_add ('\f');  break;
+                  case 'n':  string_add ('\n');  break;
+                  case 'r':  string_add ('\r');  break;
+                  case 't':  string_add ('\t');  break;
+                  case 'u':
+
+                    if (end - state.ptr < 4 || 
+                        (uc_b1 = hex_value (*++ state.ptr)) == 0xFF ||
+                        (uc_b2 = hex_value (*++ state.ptr)) == 0xFF ||
+                        (uc_b3 = hex_value (*++ state.ptr)) == 0xFF ||
+                        (uc_b4 = hex_value (*++ state.ptr)) == 0xFF)
+                    {
+                        sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col);
+                        goto e_failed;
+                    }
+
+                    uc_b1 = (uc_b1 << 4) | uc_b2;
+                    uc_b2 = (uc_b3 << 4) | uc_b4;
+                    uchar = (uc_b1 << 8) | uc_b2;
+
+                    if ((uchar & 0xF800) == 0xD800) {
+                        json_uchar uchar2;
+                        
+                        if (end - state.ptr < 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' ||
+                            (uc_b1 = hex_value (*++ state.ptr)) == 0xFF ||
+                            (uc_b2 = hex_value (*++ state.ptr)) == 0xFF ||
+                            (uc_b3 = hex_value (*++ state.ptr)) == 0xFF ||
+                            (uc_b4 = hex_value (*++ state.ptr)) == 0xFF)
+                        {
+                            sprintf (error, "Invalid character value `%c` (at %d:%d)", b, line_and_col);
+                            goto e_failed;
+                        }
+
+                        uc_b1 = (uc_b1 << 4) | uc_b2;
+                        uc_b2 = (uc_b3 << 4) | uc_b4;
+                        uchar2 = (uc_b1 << 8) | uc_b2;
+                        
+                        uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF);
+                    }
+
+                    if (sizeof (json_char) >= sizeof (json_uchar) || (uchar <= 0x7F))
+                    {
+                       string_add ((json_char) uchar);
+                       break;
+                    }
+
+                    if (uchar <= 0x7FF)
+                    {
+                        if (state.first_pass)
+                           string_length += 2;
+                        else
+                        {  string [string_length ++] = 0xC0 | (uchar >> 6);
+                           string [string_length ++] = 0x80 | (uchar & 0x3F);
+                        }
+
+                        break;
+                    }
+
+                    if (uchar <= 0xFFFF) {
+                        if (state.first_pass)
+                           string_length += 3;
+                        else
+                        {  string [string_length ++] = 0xE0 | (uchar >> 12);
+                           string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F);
+                           string [string_length ++] = 0x80 | (uchar & 0x3F);
+                        }
+                        
+                        break;
+                    }
+
+                    if (state.first_pass)
+                       string_length += 4;
+                    else
+                    {  string [string_length ++] = 0xF0 | (uchar >> 18);
+                       string [string_length ++] = 0x80 | ((uchar >> 12) & 0x3F);
+                       string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F);
+                       string [string_length ++] = 0x80 | (uchar & 0x3F);
+                    }
+
+                    break;
+
+                  default:
+                     string_add (b);
+               };
+
+               continue;
+            }
+
+            if (b == '\\')
+            {
+               flags |= flag_escaped;
+               continue;
+            }
+
+            if (b == '"')
+            {
+               if (!state.first_pass)
+                  string [string_length] = 0;
+
+               flags &= ~ flag_string;
+               string = 0;
+
+               switch (top->type)
+               {
+                  case json_string:
+
+                     top->u.string.length = string_length;
+                     flags |= flag_next;
+
+                     break;
+
+                  case json_object:
+
+                     if (state.first_pass)
+                        (*(json_char **) &top->u.object.values) += string_length + 1;
+                     else
+                     {  
+                        top->u.object.values [top->u.object.length].name
+                           = (json_char *) top->_reserved.object_mem;
+
+                        top->u.object.values [top->u.object.length].name_length
+                           = string_length;
+
+                        (*(json_char **) &top->_reserved.object_mem) += string_length + 1;
+                     }
+
+                     flags |= flag_seek_value | flag_need_colon;
+                     continue;
+
+                  default:
+                     break;
+               };
+            }
+            else
+            {
+               string_add (b);
+               continue;
+            }
+         }
+
+         if (state.settings.settings & json_enable_comments)
+         {
+            if (flags & (flag_line_comment | flag_block_comment))
+            {
+               if (flags & flag_line_comment)
+               {
+                  if (b == '\r' || b == '\n' || !b)
+                  {
+                     flags &= ~ flag_line_comment;
+                     -- state.ptr;  /* so null can be reproc'd */
+                  }
+
+                  continue;
+               }
+
+               if (flags & flag_block_comment)
+               {
+                  if (!b)
+                  {  sprintf (error, "%d:%d: Unexpected EOF in block comment", line_and_col);
+                     goto e_failed;
+                  }
+
+                  if (b == '*' && state.ptr < (end - 1) && state.ptr [1] == '/')
+                  {
+                     flags &= ~ flag_block_comment;
+                     ++ state.ptr;  /* skip closing sequence */
+                  }
+
+                  continue;
+               }
+            }
+            else if (b == '/')
+            {
+               if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object)
+               {  sprintf (error, "%d:%d: Comment not allowed here", line_and_col);
+                  goto e_failed;
+               }
+
+               if (++ state.ptr == end)
+               {  sprintf (error, "%d:%d: EOF unexpected", line_and_col);
+                  goto e_failed;
+               }
+
+               switch (b = *state.ptr)
+               {
+                  case '/':
+                     flags |= flag_line_comment;
+                     continue;
+
+                  case '*':
+                     flags |= flag_block_comment;
+                     continue;
+
+                  default:
+                     sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", line_and_col, b);
+                     goto e_failed;
+               };
+            }
+         }
+
+         if (flags & flag_done)
+         {
+            if (!b)
+               break;
+
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               default:
+
+                  sprintf (error, "%d:%d: Trailing garbage: `%c`",
+                           state.cur_line, state.cur_col, b);
+
+                  goto e_failed;
+            };
+         }
+
+         if (flags & flag_seek_value)
+         {
+            switch (b)
+            {
+               whitespace:
+                  continue;
+
+               case ']':
+
+                  if (top && top->type == json_array)
+                     flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
+                  else
+                  {  sprintf (error, "%d:%d: Unexpected ]", line_and_col);
+                     goto e_failed;
+                  }
+
+                  break;
+
+               default:
+
+                  if (flags & flag_need_comma)
+                  {
+                     if (b == ',')
+                     {  flags &= ~ flag_need_comma;
+                        continue;
+                     }
+                     else
+                     {
+                        sprintf (error, "%d:%d: Expected , before %c",
+                                 state.cur_line, state.cur_col, b);
+
+                        goto e_failed;
+                     }
+                  }
+
+                  if (flags & flag_need_colon)
+                  {
+                     if (b == ':')
+                     {  flags &= ~ flag_need_colon;
+                        continue;
+                     }
+                     else
+                     { 
+                        sprintf (error, "%d:%d: Expected : before %c",
+                                 state.cur_line, state.cur_col, b);
+
+                        goto e_failed;
+                     }
+                  }
+
+                  flags &= ~ flag_seek_value;
+
+                  switch (b)
+                  {
+                     case '{':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_object))
+                           goto e_alloc_failure;
+
+                        continue;
+
+                     case '[':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_array))
+                           goto e_alloc_failure;
+
+                        flags |= flag_seek_value;
+                        continue;
+
+                     case '"':
+
+                        if (!new_value (&state, &top, &root, &alloc, json_string))
+                           goto e_alloc_failure;
+
+                        flags |= flag_string;
+
+                        string = top->u.string.ptr;
+                        string_length = 0;
+
+                        continue;
+
+                     case 't':
+
+                        if ((end - state.ptr) < 3 || *(++ state.ptr) != 'r' ||
+                            *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e')
+                        {
+                           goto e_unknown_value;
+                        }
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        top->u.boolean = 1;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'f':
+
+                        if ((end - state.ptr) < 4 || *(++ state.ptr) != 'a' ||
+                            *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' ||
+                            *(++ state.ptr) != 'e')
+                        {
+                           goto e_unknown_value;
+                        }
+
+                        if (!new_value (&state, &top, &root, &alloc, json_boolean))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     case 'n':
+
+                        if ((end - state.ptr) < 3 || *(++ state.ptr) != 'u' ||
+                            *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l')
+                        {
+                           goto e_unknown_value;
+                        }
+
+                        if (!new_value (&state, &top, &root, &alloc, json_null))
+                           goto e_alloc_failure;
+
+                        flags |= flag_next;
+                        break;
+
+                     default:
+
+                        if (isdigit (b) || b == '-')
+                        {
+                           if (!new_value (&state, &top, &root, &alloc, json_integer))
+                              goto e_alloc_failure;
+
+                           if (!state.first_pass)
+                           {
+                              while (isdigit (b) || b == '+' || b == '-'
+                                        || b == 'e' || b == 'E' || b == '.')
+                              {
+                                 if ( (++ state.ptr) == end)
+                                 {
+                                    b = 0;
+                                    break;
+                                 }
+
+                                 b = *state.ptr;
+                              }
+
+                              flags |= flag_next | flag_reproc;
+                              break;
+                           }
+
+                           flags &= ~ (flag_num_negative | flag_num_e |
+                                        flag_num_e_got_sign | flag_num_e_negative |
+                                           flag_num_zero);
+
+                           num_digits = 0;
+                           num_fraction = 0;
+                           num_e = 0;
+
+                           if (b != '-')
+                           {
+                              flags |= flag_reproc;
+                              break;
+                           }
+
+                           flags |= flag_num_negative;
+                           continue;
+                        }
+                        else
+                        {  sprintf (error, "%d:%d: Unexpected %c when seeking value", line_and_col, b);
+                           goto e_failed;
+                        }
+                  };
+            };
+         }
+         else
+         {
+            switch (top->type)
+            {
+            case json_object:
+               
+               switch (b)
+               {
+                  whitespace:
+                     continue;
+
+                  case '"':
+
+                     if (flags & flag_need_comma)
+                     {  sprintf (error, "%d:%d: Expected , before \"", line_and_col);
+                        goto e_failed;
+                     }
+
+                     flags |= flag_string;
+
+                     string = (json_char *) top->_reserved.object_mem;
+                     string_length = 0;
+
+                     break;
+                  
+                  case '}':
+
+                     flags = (flags & ~ flag_need_comma) | flag_next;
+                     break;
+
+                  case ',':
+
+                     if (flags & flag_need_comma)
+                     {
+                        flags &= ~ flag_need_comma;
+                        break;
+                     }
+
+                  default:
+                     sprintf (error, "%d:%d: Unexpected `%c` in object", line_and_col, b);
+                     goto e_failed;
+               };
+
+               break;
+
+            case json_integer:
+            case json_double:
+
+               if (isdigit (b))
+               {
+                  ++ num_digits;
+
+                  if (top->type == json_integer || flags & flag_num_e)
+                  {
+                     if (! (flags & flag_num_e))
+                     {
+                        if (flags & flag_num_zero)
+                        {  sprintf (error, "%d:%d: Unexpected `0` before `%c`", line_and_col, b);
+                           goto e_failed;
+                        }
+
+                        if (num_digits == 1 && b == '0')
+                           flags |= flag_num_zero;
+                     }
+                     else
+                     {
+                        flags |= flag_num_e_got_sign;
+                        num_e = (num_e * 10) + (b - '0');
+                        continue;
+                     }
+
+                     top->u.integer = (top->u.integer * 10) + (b - '0');
+                     continue;
+                  }
+
+                  num_fraction = (num_fraction * 10) + (b - '0');
+                  continue;
+               }
+
+               if (b == '+' || b == '-')
+               {
+                  if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign))
+                  {
+                     flags |= flag_num_e_got_sign;
+
+                     if (b == '-')
+                        flags |= flag_num_e_negative;
+
+                     continue;
+                  }
+               }
+               else if (b == '.' && top->type == json_integer)
+               {
+                  if (!num_digits)
+                  {  sprintf (error, "%d:%d: Expected digit before `.`", line_and_col);
+                     goto e_failed;
+                  }
+
+                  top->type = json_double;
+                  top->u.dbl = (double) top->u.integer;
+
+                  num_digits = 0;
+                  continue;
+               }
+
+               if (! (flags & flag_num_e))
+               {
+                  if (top->type == json_double)
+                  {
+                     if (!num_digits)
+                     {  sprintf (error, "%d:%d: Expected digit after `.`", line_and_col);
+                        goto e_failed;
+                     }
+
+                     top->u.dbl += ((double) num_fraction) / (pow (10.0, (double) num_digits));
+                  }
+
+                  if (b == 'e' || b == 'E')
+                  {
+                     flags |= flag_num_e;
+
+                     if (top->type == json_integer)
+                     {
+                        top->type = json_double;
+                        top->u.dbl = (double) top->u.integer;
+                     }
+
+                     num_digits = 0;
+                     flags &= ~ flag_num_zero;
+
+                     continue;
+                  }
+               }
+               else
+               {
+                  if (!num_digits)
+                  {  sprintf (error, "%d:%d: Expected digit after `e`", line_and_col);
+                     goto e_failed;
+                  }
+
+                  top->u.dbl *= pow (10.0, (double)
+                      (flags & flag_num_e_negative ? - num_e : num_e));
+               }
+
+               if (flags & flag_num_negative)
+               {
+                  if (top->type == json_integer)
+                     top->u.integer = - top->u.integer;
+                  else
+                     top->u.dbl = - top->u.dbl;
+               }
+
+               flags |= flag_next | flag_reproc;
+               break;
+
+            default:
+               break;
+            };
+         }
+
+         if (flags & flag_reproc)
+         {
+            flags &= ~ flag_reproc;
+            -- state.ptr;
+         }
+
+         if (flags & flag_next)
+         {
+            flags = (flags & ~ flag_next) | flag_need_comma;
+
+            if (!top->parent)
+            {
+               /* root value done */
+
+               flags |= flag_done;
+               continue;
+            }
+
+            if (top->parent->type == json_array)
+               flags |= flag_seek_value;
+               
+            if (!state.first_pass)
+            {
+               json_value * parent = top->parent;
+
+               switch (parent->type)
+               {
+                  case json_object:
+
+                     parent->u.object.values
+                        [parent->u.object.length].value = top;
+
+                     break;
+
+                  case json_array:
+
+                     parent->u.array.values
+                           [parent->u.array.length] = top;
+
+                     break;
+
+                  default:
+                     break;
+               };
+            }
+
+            if ( (++ top->parent->u.array.length) > state.uint_max)
+               goto e_overflow;
+
+            top = top->parent;
+
+            continue;
+         }
+      }
+
+      alloc = root;
+   }
+
+   return root;
+
+e_unknown_value:
+
+   sprintf (error, "%d:%d: Unknown value", line_and_col);
+   goto e_failed;
+
+e_alloc_failure:
+
+   strcpy (error, "Memory allocation failure");
+   goto e_failed;
+
+e_overflow:
+
+   sprintf (error, "%d:%d: Too long (caught overflow)", line_and_col);
+   goto e_failed;
+
+e_failed:
+
+   if (error_buf)
+   {
+      if (*error)
+         strcpy (error_buf, error);
+      else
+         strcpy (error_buf, "Unknown error");
+   }
+
+   if (state.first_pass)
+      alloc = root;
+
+   while (alloc)
+   {
+      top = alloc->_reserved.next_alloc;
+      state.settings.memory_free (alloc, state.settings.user_data);
+      alloc = top;
+   }
+
+   if (!state.first_pass)
+      json_value_free_ex (&state.settings, root);
+
+   return 0;
+}
+
+json_value * json_parse (const json_char * json, size_t length)
+{
+   json_settings settings = EMPTY_SETTINGS_STRUCT;
+   return json_parse_ex (&settings, json, length, 0);
+}
+
+void json_value_free_ex (json_settings * settings, json_value * value)
+{
+   json_value * cur_value;
+
+   if (!value)
+      return;
+
+   value->parent = 0;
+
+   while (value)
+   {
+      switch (value->type)
+      {
+         case json_array:
+
+            if (!value->u.array.length)
+            {
+               if (value->u.array.values) settings->memory_free (value->u.array.values, settings->user_data);
+               break;
+            }
+
+            value = value->u.array.values [-- value->u.array.length];
+            continue;
+
+         case json_object:
+
+            if (!value->u.object.length)
+            {
+               settings->memory_free (value->u.object.values, settings->user_data);
+               break;
+            }
+
+            value = value->u.object.values [-- value->u.object.length].value;
+            continue;
+
+         case json_string:
+
+            settings->memory_free (value->u.string.ptr, settings->user_data);
+            break;
+
+         default:
+            break;
+      };
+
+      cur_value = value;
+      value = value->parent;
+      settings->memory_free (cur_value, settings->user_data);
+   }
+}
+
+void json_value_free (json_value * value)
+{
+   json_settings settings = EMPTY_SETTINGS_STRUCT;
+   settings.memory_free = default_free;
+   json_value_free_ex (&settings, value);
+}
+

+ 314 - 0
src/utils/gravity_json.h

@@ -0,0 +1,314 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifndef __GRAVITY_JSON_SERIALIZER__
+#define __GRAVITY_JSON_SERIALIZER__
+
+// MARK: JSON serializer -
+
+typedef struct json_t	json_t;
+json_t		*json_new (void);
+void		json_begin_object (json_t *json, const char *key);
+void		json_end_object (json_t *json);
+void		json_begin_array (json_t *json, const char *key);
+void		json_end_array (json_t *json);
+void		json_add_cstring (json_t *json, const char *key, const char *value);
+void		json_add_string (json_t *json, const char *key, const char *value, size_t len);
+void		json_add_int (json_t *json, const char *key, int64_t value);
+void		json_add_double (json_t *json, const char *key, double value);
+void		json_add_bool (json_t *json, const char *key, bool value);
+void		json_add_null (json_t *json, const char *key);
+void		json_free (json_t *json);
+const char	*json_buffer (json_t *json, size_t *len);
+bool		json_write_file (json_t *json, const char *path);
+void		json_pop (json_t *json, uint32_t n);
+
+#endif
+
+// MARK: - JSON Parser -
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _JSON_H
+#define _JSON_H
+
+#ifndef json_char
+   #define json_char char
+#endif
+
+#ifndef json_int_t
+   #ifndef _MSC_VER
+      #include <inttypes.h>
+      #define json_int_t int64_t
+   #else
+      #define json_int_t __int64
+   #endif
+#endif
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+
+   #include <string.h>
+
+   extern "C"
+   {
+
+#endif
+
+typedef struct
+{
+   unsigned long max_memory;
+   int settings;
+
+   /* Custom allocator support (leave null to use malloc/free)
+    */
+
+   void * (* memory_alloc) (size_t, int zero, void * user_data);
+   void (* memory_free) (void *, void * user_data);
+
+   void * user_data;  /* will be passed to mem_alloc and mem_free */
+
+   size_t value_extra;  /* how much extra space to allocate for values? */
+
+} json_settings;
+
+#define json_enable_comments  0x01
+
+typedef enum
+{
+   json_none,
+   json_object,
+   json_array,
+   json_integer,
+   json_double,
+   json_string,
+   json_boolean,
+   json_null
+
+} json_type;
+
+extern const struct _json_value json_value_none;
+       
+typedef struct _json_object_entry
+{
+    json_char * name;
+    unsigned int name_length;
+    
+    struct _json_value * value;
+    
+} json_object_entry;
+
+typedef struct _json_value
+{
+   struct _json_value * parent;
+
+   json_type type;
+
+   union
+   {
+      int boolean;
+      json_int_t integer;
+      double dbl;
+
+      struct
+      {
+         unsigned int length;
+         json_char * ptr; /* null terminated */
+
+      } string;
+
+      struct
+      {
+         unsigned int length;
+
+         json_object_entry * values;
+
+         #if defined(__cplusplus) && __cplusplus >= 201103L
+         decltype(values) begin () const
+         {  return values;
+         }
+         decltype(values) end () const
+         {  return values + length;
+         }
+         #endif
+
+      } object;
+
+      struct
+      {
+         unsigned int length;
+         struct _json_value ** values;
+
+         #if defined(__cplusplus) && __cplusplus >= 201103L
+         decltype(values) begin () const
+         {  return values;
+         }
+         decltype(values) end () const
+         {  return values + length;
+         }
+         #endif
+
+      } array;
+
+   } u;
+
+   union
+   {
+      struct _json_value * next_alloc;
+      void * object_mem;
+
+   } _reserved;
+
+   #ifdef JSON_TRACK_SOURCE
+
+      /* Location of the value in the source JSON
+       */
+      unsigned int line, col;
+
+   #endif
+
+
+   /* Some C++ operator sugar */
+
+   #ifdef __cplusplus
+
+      public:
+
+         inline _json_value ()
+         {  memset (this, 0, sizeof (_json_value));
+         }
+
+         inline const struct _json_value &operator [] (int index) const
+         {
+            if (type != json_array || index < 0
+                     || ((unsigned int) index) >= u.array.length)
+            {
+               return json_value_none;
+            }
+
+            return *u.array.values [index];
+         }
+
+         inline const struct _json_value &operator [] (const char * index) const
+         { 
+            if (type != json_object)
+               return json_value_none;
+
+            for (unsigned int i = 0; i < u.object.length; ++ i)
+               if (!strcmp (u.object.values [i].name, index))
+                  return *u.object.values [i].value;
+
+            return json_value_none;
+         }
+
+         inline operator const char * () const
+         {  
+            switch (type)
+            {
+               case json_string:
+                  return u.string.ptr;
+
+               default:
+                  return "";
+            };
+         }
+
+         inline operator json_int_t () const
+         {  
+            switch (type)
+            {
+               case json_integer:
+                  return u.integer;
+
+               case json_double:
+                  return (json_int_t) u.dbl;
+
+               default:
+                  return 0;
+            };
+         }
+
+         inline operator bool () const
+         {  
+            if (type != json_boolean)
+               return false;
+
+            return u.boolean != 0;
+         }
+
+         inline operator double () const
+         {  
+            switch (type)
+            {
+               case json_integer:
+                  return (double) u.integer;
+
+               case json_double:
+                  return u.dbl;
+
+               default:
+                  return 0;
+            };
+         }
+
+   #endif
+
+} json_value;
+
+#define EMPTY_SETTINGS_STRUCT	{0,0,0,0,0,0}
+#define EMPTY_STATE_STRUCT		{0,0,0,EMPTY_SETTINGS_STRUCT,0,0,0,0}
+       
+json_value * json_parse (const json_char * json,
+                         size_t length);
+
+#define json_error_max 128
+json_value * json_parse_ex (json_settings * settings,
+                            const json_char * json,
+                            size_t length,
+                            char * error);
+
+void json_value_free (json_value *);
+
+
+/* Not usually necessary, unless you used a custom mem_alloc and now want to
+ * use a custom mem_free.
+ */
+void json_value_free_ex (json_settings * settings,
+                         json_value *);
+
+
+#ifdef __cplusplus
+   } /* extern "C" */
+#endif
+
+#endif
+
+

+ 445 - 0
src/utils/gravity_utils.c

@@ -0,0 +1,445 @@
+//
+//  gravity_utils.c
+//  gravity
+//
+//  Created by Marco Bambini on 29/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#include <time.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#if defined(__linux)
+#include <sys/time.h>
+#endif
+#if defined(__MACH__)
+#include <mach/mach_time.h>
+#endif
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#include "gravity_utils.h"
+#include "gravity_memory.h"
+
+#define SWP(x,y) (x^=y, y^=x, x^=y)
+
+// MARK: Timer -
+
+nanotime_t nanotime (void) {
+	nanotime_t value;
+	
+	#if defined(_WIN32)
+	static LARGE_INTEGER	win_frequency;
+	static BOOL				flag = QueryPerformanceFrequency(&s_frequency);
+	LARGE_INTEGER			t;
+	
+	if (!QueryPerformanceCounter(&t)) return 0;
+	value = (t.QuadPart / win_frequency.QuadPart) * 1000000000;
+	value += (t.QuadPart % win_frequency.QuadPart) * 1000000000 / win_frequency.QuadPart;
+	
+	#elif defined(__MACH__)
+	mach_timebase_info_data_t	info;
+	kern_return_t				r;
+	nanotime_t					t;
+	
+	t = mach_absolute_time();
+	r = mach_timebase_info(&info);
+	if (r != 0) return 0;
+	value = (t / info.denom) * info.numer;
+	value += (t % info.denom) * info.numer / info.denom;
+	
+	#elif defined(__linux)
+	struct timespec ts;
+	int				r;
+	
+	r = clock_gettime(CLOCK_MONOTONIC, &ts);
+	if (r != 0) return 0;
+	value = ts.tv_sec * (nanotime_t)1000000000 + ts.tv_nsec;
+	
+	#else
+	struct timeval	tv;
+	int				r;
+	
+	r = gettimeofday(&tv, 0);
+	if (r != 0) return 0;
+	value = tv.tv_sec * (nanotime_t)1000000000 + tv.tv_usec * 1000;
+	#endif
+	
+	return value;
+}
+
+double microtime (nanotime_t tstart, nanotime_t tend) {
+	nanotime_t t = tend - tstart;
+	return ((double)t / 1000.0f);
+}
+
+double millitime (nanotime_t tstart, nanotime_t tend) {
+	nanotime_t t = tend - tstart;
+	return ((double)t / 1000000.0f);
+}
+
+// MARK: - I/O Functions -
+
+uint64_t file_size (const char *path) {
+	#ifdef WIN32
+	WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
+	if (GetFileAttributesExA(path, GetFileExInfoStandard, (void*)&fileInfo) == 0) return -1;
+	return (uint64_t)(((__int64)fileInfo.nFileSizeHigh) << 32 ) + fileInfo.nFileSizeLow;
+	#else
+	struct stat sb;
+	if (stat(path, &sb) > 0) return -1;
+	return (uint64_t)sb.st_size;
+	#endif
+}
+
+const char *file_read(const char *path, size_t *len) {
+	int		fd = 0;
+	off_t	fsize = 0;
+	size_t	fsize2 = 0;
+	char	*buffer = NULL;
+	
+	fsize = (size_t) file_size(path);
+	if (fsize < 0) goto abort_read;
+	
+	fd = open(path, O_RDONLY);
+	if (fd < 0) goto abort_read;
+	
+	buffer = (char *)mem_alloc((size_t)fsize + 1);
+	if (buffer == NULL) goto abort_read;
+	buffer[fsize] = 0;
+	
+	fsize2 = read(fd, buffer, fsize);
+	if (fsize != fsize2) goto abort_read;
+	
+	if (len) *len = fsize;
+	close(fd);
+	return (const char *)buffer;
+	
+abort_read:
+	if (buffer) mem_free((void *)buffer);
+	if (fd >= 0) close(fd);
+	return NULL;
+}
+
+bool file_exists (const char *path) {
+	#ifdef WIN32
+	BOOL isDirectory;
+	DWORD attributes = GetFileAttributesA(path);
+	
+	// special directory case to drive the network path check
+	if (attributes == INVALID_FILE_ATTRIBUTES)
+		isDirectory = (GetLastError() == ERROR_BAD_NETPATH);
+	else
+		isDirectory = (FILE_ATTRIBUTE_DIRECTORY & attributes);
+	
+	if (isDirectory) {
+		if (PathIsNetworkPathA(path)) return true;
+		if (PathIsUNCA(path)) return true;
+	}
+	
+	if (PathFileExistsA(path) == 1) return true;
+	#else
+	if (access(path, F_OK)==0) return true;
+	#endif
+	
+	return false;
+}
+
+const char *file_buildpath (const char *filename, const char *dirpath) {
+//	#ifdef WIN32
+//	PathCombineA(result, filename, dirpath);
+//	#else
+	size_t len1 = strlen(filename);
+	size_t len2 = strlen(dirpath);
+	size_t len = len1+len2+2;
+	
+	char *full_path = (char *)mem_alloc(len);
+	if (!full_path) return NULL;
+	
+	if ((len2) && (dirpath[len2-1] != '/'))
+		snprintf(full_path, len, "%s/%s", dirpath, filename);
+	else
+		snprintf(full_path, len, "%s%s", dirpath, filename);
+//	#endif
+	
+	return (const char *)full_path;
+}
+
+bool file_write (const char *path, const char *buffer, size_t len) {
+	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // RW for owner, R for group, R for others
+	int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+	if (fd < 0) return false;
+	
+	ssize_t nwrite = write(fd, buffer, len);
+	close(fd);
+	
+	return (nwrite == len);
+}
+
+// MARK: - Directory Functions -
+
+bool is_directory (const char *path) {
+	#ifdef WIN32
+	DWORD dwAttrs;
+	
+	dwAttrs = GetFileAttributesA(path);
+	if (dwAttrs == INVALID_FILE_ATTRIBUTES) return false;
+	if (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) return true;
+	#else
+	struct stat buf;
+	
+	if (lstat(path, &buf) < 0) return false;
+	if (S_ISDIR(buf.st_mode)) return true;
+	#endif
+	
+	return false;
+}
+
+DIRREF directory_init (const char *dirpath) {
+	#ifdef WIN32
+	WIN32_FIND_DATA findData;
+	WCHAR			path[MAX_PATH];
+	WCHAR			dirpathW[MAX_PATH];
+	HANDLE			hFind;
+	
+	// convert dirpath to dirpathW
+	MultiByteToWideChar(CP_UTF8, 0, dirpath, -1, dirpathW, MAX_PATH);
+	
+	// in this way I can be sure that the first file returned (and lost) is .
+	PathCombine(path, dirpathW, _T("*"));
+	
+	// if the path points to a symbolic link, the WIN32_FIND_DATA buffer contains
+	// information about the symbolic link, not the target
+	return FindFirstFile(path, &findData);
+	#else
+	return opendir(dirpath);
+	#endif
+}
+
+const char *directory_read (DIRREF ref) {
+	if (ref == NULL) return NULL;
+	
+	while (1) {
+		#ifdef WIN32
+		WIN32_FIND_DATA findData;
+		if (FindNextFile(ref, &findData) == 0) {
+			FindClose(dref);
+			return NULL;
+		}
+		if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
+		if (findData.cFileName == NULL) continue;
+		if (findData.cFileName[0] == '.') continue;
+		return (const char *)findData.cFileName;
+		#else
+		struct dirent *d;
+		if ((d = readdir(ref)) == NULL) {
+			closedir(ref);
+			return NULL;
+		}
+		if (d->d_name[0] == 0) continue;
+		if (d->d_name[0] == '.') continue;
+		return (const char *)d->d_name;
+		#endif
+	}
+	return NULL;
+}
+
+// MARK: - String Functions -
+
+int string_nocasencmp(const char *s1, const char *s2, size_t n) {
+	while(n > 0 && tolower((unsigned char)*s1) == tolower((unsigned char)*s2)) {
+		if(*s1 == '\0') return 0;
+		s1++;
+		s2++;
+		n--;
+	}
+	
+	if(n == 0) return 0;
+	return tolower((unsigned char)*s1) - tolower((unsigned char)*s2);
+}
+
+int string_casencmp(const char *s1, const char *s2, size_t n) {
+	while(n > 0 && ((unsigned char)*s1) == ((unsigned char)*s2)) {
+		if(*s1 == '\0') return 0;
+		s1++;
+		s2++;
+		n--;
+	}
+	
+	if(n == 0) return 0;
+	return ((unsigned char)*s1) - ((unsigned char)*s2);
+}
+
+int string_cmp (const char *s1, const char *s2) {
+	if (!s1) return 1;
+	return strcmp(s1, s2);
+}
+
+const char *string_dup (const char *s1) {
+	size_t	len = (size_t)strlen(s1);
+	char	*s = (char *)mem_alloc(len + 1);
+	
+	memcpy(s, s1, len);
+	return s;
+}
+
+const char *string_ndup (const char *s1, size_t n) {
+	char *s = (char *)mem_alloc(n + 1);
+	memcpy(s, s1, n);
+	return s;
+}
+
+char *string_unescape (const char *s1, uint32_t *s1len, char *buffer) {
+	uint32_t len = *s1len;
+	uint32_t orig_len = len;
+	
+	for (uint32_t i=0, j=0; i<orig_len; ++i, ++j) {
+		char c = s1[i];
+		if ((c == '\\') && (i+1<orig_len)) {
+			c = s1[i+1];
+			switch (c) {
+				case '"':
+				case '\\':
+				case '\b':
+				case '\f':
+				case '\n':
+				case '\r':
+				case '\t':
+					++i; --len; break;
+				default:
+					c = s1[i]; break;
+			}
+		}
+		buffer[j] = c;
+	}
+	
+	*s1len = len;
+	return buffer;
+}
+
+// From: http://stackoverflow.com/questions/198199/how-do-you-reverse-a-string-in-place-in-c-or-c
+void string_reverse (char *p) {
+	char *q = p;
+	while(q && *q) ++q; /* find eos */
+	for(--q; p < q; ++p, --q) SWP(*p, *q);
+}
+
+uint32_t string_size (const char *p) {
+	if (!p) return 0;
+	return (uint32_t)strlen(p);
+}
+
+// MARK: - UTF-8 Functions -
+
+/*
+	Based on: https://github.com/Stepets/utf8.lua/blob/master/utf8.lua
+	ABNF from RFC 3629
+
+	UTF8-octets = *( UTF8-char )
+	UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
+	UTF8-1      = %x00-7F
+	UTF8-2      = %xC2-DF UTF8-tail
+	UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
+				  %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
+	UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
+				  %xF4 %x80-8F 2( UTF8-tail )
+	UTF8-tail   = %x80-BF
+ 
+ */
+
+inline uint32_t utf8_charbytes (const char *s, uint32_t i) {
+	unsigned char c = s[i];
+	
+	// determine bytes needed for character, based on RFC 3629
+	if ((c > 0) && (c <= 127)) return 1;
+	if ((c >= 194) && (c <= 223)) return 2;
+	if ((c >= 224) && (c <= 239)) return 3;
+	if ((c >= 240) && (c <= 244)) return 4;
+	
+	// means error
+	return 0;
+}
+
+uint32_t utf8_len (const char *s, uint32_t nbytes) {
+	if (nbytes == 0) nbytes = (uint32_t)strlen(s);
+	
+	uint32_t pos = 1;
+	uint32_t len = 0;
+	
+	while (pos <= nbytes) {
+		++len;
+		uint32_t n = utf8_charbytes(s, pos);
+		if (n == 0) return 0; // means error
+		pos += n;
+	}
+	
+	return len;
+}
+
+// From: http://stackoverflow.com/questions/198199/how-do-you-reverse-a-string-in-place-in-c-or-c
+void utf8_reverse (char *p) {
+	char *q = p;
+	string_reverse(p);
+	
+	// now fix bass-ackwards UTF chars.
+	while(q && *q) ++q; // find eos
+	while(p < --q)
+		switch( (*q & 0xF0) >> 4 ) {
+			case 0xF: /* U+010000-U+10FFFF: four bytes. */
+				SWP(*(q-0), *(q-3));
+				SWP(*(q-1), *(q-2));
+				q -= 3;
+				break;
+			case 0xE: /* U+000800-U+00FFFF: three bytes. */
+				SWP(*(q-0), *(q-2));
+				q -= 2;
+				break;
+			case 0xC: /* fall-through */
+			case 0xD: /* U+000080-U+0007FF: two bytes. */
+				SWP(*(q-0), *(q-1));
+				q--;
+				break;
+		}
+}
+
+// MARK: - Math -
+
+// From: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2Float
+uint32_t power_of2_ceil (uint32_t n) {
+	n--;
+	n |= n >> 1;
+	n |= n >> 2;
+	n |= n >> 4;
+	n |= n >> 8;
+	n |= n >> 16;
+	n++;
+	
+	return n;
+}
+
+int64_t number_from_hex (const char *s, uint32_t len) {
+	#pragma unused(len)
+	return (int64_t) strtoll(s, NULL, 16);
+}
+
+int64_t number_from_oct (const char *s, uint32_t len) {
+	#pragma unused(len)
+	return (int64_t) strtoll(s, NULL, 8);
+}
+
+int64_t number_from_bin (const char *s, uint32_t len) {
+	int64_t value = 0;
+	
+	for (uint32_t i=0; i<len; ++i) {
+		int c = s[i];
+		value = (value << 1) + (c - '0');
+	}
+	return value;
+}
+

+ 62 - 0
src/utils/gravity_utils.h

@@ -0,0 +1,62 @@
+//
+//  gravity_utils.h
+//  gravity
+//
+//  Created by Marco Bambini on 29/08/14.
+//  Copyright (c) 2014 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_UTILS__
+#define __GRAVITY_UTILS__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#if defined(_WIN32)
+typedef unsigned _int64 nanotime_t;
+#define DIRREF			HANDLE
+#else
+#include <dirent.h>
+typedef uint64_t		nanotime_t;
+#define DIRREF			DIR*
+#endif
+
+// TIMER
+nanotime_t	nanotime (void);
+double		microtime (nanotime_t tstart, nanotime_t tend);
+double		millitime (nanotime_t tstart, nanotime_t tend);
+
+// FILE
+uint64_t	file_size (const char *path);
+const char	*file_read (const char *path, size_t *len);
+bool		file_exists (const char *path);
+const char	*file_buildpath (const char *filename, const char *dirpath);
+bool		file_write (const char *path, const char *buffer, size_t len);
+
+// DIRECTORY
+bool		is_directory (const char *path);
+DIRREF		directory_init (const char *path);
+const char	*directory_read (DIRREF ref);
+
+// STRING
+int			string_nocasencmp (const char *s1, const char *s2, size_t n);
+int			string_casencmp (const char *s1, const char *s2, size_t n);
+int			string_cmp (const char *s1, const char *s2);
+const char	*string_dup (const char *s1);
+const char	*string_ndup (const char *s1, size_t n);
+char		*string_unescape (const char *s1, uint32_t *s1len, char *buffer);
+void		string_reverse (char *p);
+uint32_t	string_size (const char *p);
+
+// UTF-8
+uint32_t	utf8_charbytes (const char *s, uint32_t i);
+uint32_t	utf8_len (const char *s, uint32_t nbytes);
+void		utf8_reverse (char *p);
+
+// MATH and NUMBERS
+uint32_t	power_of2_ceil (uint32_t n);
+int64_t		number_from_hex (const char *s, uint32_t len);
+int64_t		number_from_oct (const char *s, uint32_t len);
+int64_t		number_from_bin (const char *s, uint32_t len);
+
+#endif

+ 44 - 0
test/01-syntax/class_declaration.gravity

@@ -0,0 +1,44 @@
+#unittest {
+	name: "Test classes declaration with both static and non static members.";
+	error: NONE;
+};
+
+// class declarations
+class bar {
+	// class var
+	static var a1 = 10;
+	static var a2 = 20;
+	
+	// class const
+	static var b1 = 100;
+	static var b2 = 200;
+	
+	// instance var
+	var c1 = 1000;
+	var c2 = 2000;
+	
+	// instance const
+	const d1 = 10000;
+	const d2 = 20000;
+	
+	static func f1() {
+		return a1+a2+b1+b2;
+	}
+	
+	func f2() {
+		return c1+c2+d1+d2;
+	}
+}
+
+class foo:bar {
+	var e1 = 10;
+	var e2 = 20;
+	
+	func init() {
+		e1 = 100;
+	}
+}
+
+func main() {
+	return;
+}

+ 4 - 0
test/01-syntax/empty.gravity

@@ -0,0 +1,4 @@
+#unittest {
+	name: "Empty file.";
+	error: RUNTIME;
+};

+ 9 - 0
test/01-syntax/empty_enum.gravity

@@ -0,0 +1,9 @@
+#unittest {
+	name: "Testing empty enum.";
+	error: SYNTAX;
+	error_row: 8;
+	error_col: 1;
+};
+
+enum math {
+}

+ 9 - 0
test/01-syntax/empty_return.gravity

@@ -0,0 +1,9 @@
+#unittest {
+	name: "Test empty return.";
+	error: NONE;
+	result: null;
+};
+
+func main() {
+	return;
+}

+ 10 - 0
test/01-syntax/func_error_params.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Test missing parameters.";
+	error: SYNTAX;
+	error_row:8;
+	error_col:9;
+};
+
+func f1 {
+	return 1;
+}

+ 14 - 0
test/01-syntax/module_declaration.gravity

@@ -0,0 +1,14 @@
+#unittest {
+	name: "Test module declaration.";
+	error: RUNTIME;
+};
+
+module m1 {
+	var a = 100;
+	func f1() {return a;}
+}
+
+module m2 {
+	var a = 200;
+	func f1() {return a;}
+}

+ 10 - 0
test/01-syntax/var_in_if.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Test variable declaration in if flow statement.";
+	error: SYNTAX;
+	error_row: 9;
+	error_col: 6;
+};
+
+func main() {
+	if (var a) {};
+}

+ 11 - 0
test/01-syntax/var_in_repeat.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Test variable declaration in repeat loop statement.";
+	error: SYNTAX;
+	error_row: 10;
+	error_col: 11;
+};
+
+func main() {
+	repeat {
+	} while (var a);
+}

+ 11 - 0
test/01-syntax/var_in_switch.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Test variable declaration in switch flow statement.";
+	error: SYNTAX;
+	error_row: 9;
+	error_col: 10;
+};
+
+func main() {
+	switch (var a) {
+	};
+}

+ 11 - 0
test/01-syntax/var_in_while.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Test variable declaration in while loop statement.";
+	error: SYNTAX;
+	error_row: 9;
+	error_col: 9;
+};
+
+func main() {
+	while (var a) {
+	};
+}

+ 12 - 0
test/02-semantic_step1/class1_redeclared.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Class redeclared.";
+	error: SEMANTIC;
+	error_row: 11;
+	error_col: 1;
+};
+
+class c {
+}
+
+func c() {
+}

+ 11 - 0
test/02-semantic_step1/class2_internal_redeclared.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Class internal redeclared.";
+	error: SEMANTIC;
+	error_row: 10;
+	error_col: 2;
+};
+
+class c {
+	var a;
+	func a() {return 1;}
+}

+ 14 - 0
test/02-semantic_step1/enum1_redeclared.gravity

@@ -0,0 +1,14 @@
+#unittest {
+	name: "Enum redeclared.";
+	error: SEMANTIC;
+	error_row: 12;
+	error_col: 1;
+};
+
+enum list1{
+	one = 1
+}
+
+enum list1{
+	two = 2
+}

+ 11 - 0
test/02-semantic_step1/enum2_internal_redeclared.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Enum item redeclared.";
+	error: SYNTAX;
+	error_row: 10;
+	error_col: 2;
+};
+
+enum list1{
+	one = 1,
+	one = 2
+}

+ 9 - 0
test/02-semantic_step1/function_redeclared.gravity

@@ -0,0 +1,9 @@
+#unittest {
+	name: "Test function redeclared.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 1;
+};
+
+func foo() {return 1;}
+func foo() {return 1;}

+ 28 - 0
test/02-semantic_step1/local_variables_number.gravity

@@ -0,0 +1,28 @@
+#unittest {
+	name: "Test maximum number of local variables.";
+	error: SEMANTIC;
+};
+
+func main() {
+	var a001,a002,a003,a004,a005,a006,a007,a008,a009,a010;
+	var a011,a012,a013,a014,a015,a016,a017,a018,a019,a020;
+	var b001,b002,b003,b004,b005,b006,b007,b008,b009,b010;
+	var b011,b012,b013,b014,b015,b016,b017,b018,b019,b020;
+	var c001,c002,c003,c004,c005,c006,c007,c008,c009,c010;
+	var c011,c012,c013,c014,c015,c016,c017,c018,c019,c020;
+	var d001,d002,d003,d004,d005,d006,d007,d008,d009,d010;
+	var d011,d012,d013,d014,d015,d016,d017,d018,d019,d020;
+	var e001,e002,e003,e004,e005,e006,e007,e008,e009,e010;
+	var e011,e012,e013,e014,e015,e016,e017,e018,e019,e020;
+	var f001,f002,f003,f004,f005,f006,f007,f008,f009,f010;
+	var f011,f012,f013,f014,f015,f016,f017,f018,f019,f020;
+	var g001,g002,g003,g004,g005,g006,g007,g008,g009,g010;
+	var g011,g012,g013,g014,g015,g016,g017,g018,g019,g020;
+	var h001,h002,h003,h004,h005,h006,h007,h008,h009,h010;
+	var h011,h012,h013,h014,h015,h016,h017,h018,h019,h020;
+	var i001,i002,i003,i004,i005,i006,i007,i008,i009,i010;
+	var i011,i012,i013,i014,i015,i016,i017,i018,i019,i020;
+	var l001,l002,l003,l004,l005,l006,l007,l008,l009,l010;
+	var l011,l012,l013,l014,l015,l016,l017,l018,l019,l020;
+	var m001;
+}

+ 12 - 0
test/02-semantic_step1/module1_redeclared.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Module redeclared.";
+	error: SEMANTIC;
+	error_row: 11;
+	error_col: 1;
+};
+
+module module1 {
+}
+
+module module1 {
+}

+ 11 - 0
test/02-semantic_step1/module2_internal_redeclared.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Module internal redeclared.";
+	error: SEMANTIC;
+	error_row: 10;
+	error_col: 2;
+};
+
+module module1 {
+	var v1;
+	func v1() {return 1;}
+}

+ 9 - 0
test/02-semantic_step1/var_redeclared.gravity

@@ -0,0 +1,9 @@
+#unittest {
+	name: "Test variable redeclared.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 5;
+};
+
+var v1;
+var v1;

+ 10 - 0
test/03-semantic_step2/class1_access_specifier.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Testing class access specifier in global.";
+	error: SEMANTIC;
+	error_row: 8;
+	error_col: 8;
+};
+
+public class math {
+	
+}

+ 11 - 0
test/03-semantic_step2/class2_access_specifier.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Testing class access specifier in a function.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 9;
+};
+
+func foo() {
+	public class math {
+	}
+}

+ 14 - 0
test/03-semantic_step2/class_container.gravity

@@ -0,0 +1,14 @@
+#unittest {
+	name: "Testing class container (module cannot be declared inside a class).";
+	error: SEMANTIC;
+	error_row: 13;
+	error_col: 2;
+};
+
+class foo {
+	var a;
+	func b() {return 1;}
+	class c {}
+	enum d { one = 1}
+	module e {var one;}
+}

+ 10 - 0
test/03-semantic_step2/enum1_access_specifier.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Testing enum access specifier in global.";
+	error: SEMANTIC;
+	error_row: 8;
+	error_col: 8;
+};
+
+public enum math {
+	one = 1
+}

+ 12 - 0
test/03-semantic_step2/enum2_access_specifier.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Testing enum access specifier in a function.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 9;
+};
+
+func foo() {
+	public enum math {
+		one = 1
+	}
+}

+ 12 - 0
test/03-semantic_step2/for1_identifier_not_found.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Testing identifier not found in for loop.";
+	error: SEMANTIC;
+	error_row: 10;
+	error_col: 7;
+};
+
+func foo() {
+	var a;
+	for (i in a) {
+	}
+}

+ 10 - 0
test/03-semantic_step2/func1_access_specifier.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Testing function access specifier in global.";
+	error: SEMANTIC;
+	error_row: 8;
+	error_col: 8;
+};
+
+public func foo() {
+	return 1;
+}

+ 12 - 0
test/03-semantic_step2/func2_access_specifier.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Testing function access specifier in function.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 9;
+};
+
+func bar() {
+	public func foo() {
+		return 1;
+	}
+}

+ 11 - 0
test/03-semantic_step2/func3_identifier_redeclared.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Testing function closure declaration redeclared.";
+	error: SEMANTIC;
+	error_row: 10;
+	error_col: 2;
+};
+
+func foo() {
+	var bar;
+	func bar() {return 1;}
+}

+ 11 - 0
test/03-semantic_step2/func4_identifier_redeclared.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Testing local class declaration redeclared.";
+	error: SEMANTIC;
+	error_row: 10;
+	error_col: 2;
+};
+
+func foo() {
+	var bar;
+	class bar {}
+}

+ 14 - 0
test/03-semantic_step2/func_container.gravity

@@ -0,0 +1,14 @@
+#unittest {
+	name: "Testing function container (module cannot be declared inside a function).";
+	error: SEMANTIC;
+	error_row: 13;
+	error_col: 2;
+};
+
+func foo() {
+	var a;
+	func b() {return 1;}
+	class c {}
+	enum d { one = 1}
+	module e {var one;}
+}

+ 17 - 0
test/03-semantic_step2/invalid_condition_if.gravity

@@ -0,0 +1,17 @@
+#unittest {
+	name: "Semantic, If statement condition, Assignment not allowed here.";
+	error: SEMANTIC;
+	error_row: 11;
+	error_col: 6;
+};
+
+func main () {
+	var i,v = 0;
+
+	if (i = 1) {	// Assignment not allowed as a condition
+		v = 1;
+		v = 2;
+	} else v = 3;
+
+return v;
+}

+ 18 - 0
test/03-semantic_step2/invalid_condition_while.gravity

@@ -0,0 +1,18 @@
+#unittest {
+	name: "Semantic, While loop condition, Assignment not allowed here.";
+	error: SEMANTIC;
+	error_row: 12;
+	error_col: 9;
+};
+
+func main () {
+	var i,sum = 0;
+
+	// assignment instead of a condition
+	while (i = 50) {
+		sum += i;
+		i += 1;
+	}
+
+	return sum;
+}

+ 9 - 0
test/03-semantic_step2/module_access_specifier.gravity

@@ -0,0 +1,9 @@
+#unittest {
+	name: "Testing module access specifier.";
+	error: SEMANTIC;
+	error_row: 8;
+	error_col: 8;
+};
+
+public module math {
+}

+ 14 - 0
test/03-semantic_step2/module_container.gravity

@@ -0,0 +1,14 @@
+#unittest {
+	name: "Testing module container (module cannot be declared inside a module).";
+	error: SEMANTIC;
+	error_row: 13;
+	error_col: 2;
+};
+
+module foo {
+	var a;
+	func b() {return 1;}
+	class c {}
+	enum d { one = 1}
+	module e {var one;}
+}

+ 16 - 0
test/03-semantic_step2/override_property.gravity

@@ -0,0 +1,16 @@
+#unittest {
+	name: "Test property overriden warning.";
+	error: WARNING;
+	error_row: 14;
+	error_col: 2;
+};
+
+class class1 {
+	var a;
+	func f1() {return a;}
+}
+
+class class2:class1 {
+	var a;
+	func f1() {return a;}
+}

+ 8 - 0
test/03-semantic_step2/var1_access_specifier.gravity

@@ -0,0 +1,8 @@
+#unittest {
+	name: "Testing var access specifier in global.";
+	error: SEMANTIC;
+	error_row: 8;
+	error_col: 8;
+};
+
+public var a;

+ 10 - 0
test/03-semantic_step2/var2_access_specifier.gravity

@@ -0,0 +1,10 @@
+#unittest {
+	name: "Testing var access specifier in function.";
+	error: SEMANTIC;
+	error_row: 9;
+	error_col: 9;
+};
+
+func foo() {
+	public var a;
+}

+ 8 - 0
test/03-semantic_step2/var_container.gravity

@@ -0,0 +1,8 @@
+#unittest {
+	name: "Testing anonymous function declaration.";
+	error: NONE;
+};
+
+func main() {
+	var a = func() {return 1;};
+}

+ 11 - 0
test/04-codegen/assignment1.gravity

@@ -0,0 +1,11 @@
+#unittest {
+	name: "Test local assignment.";
+	error: NONE;
+	result: 10;
+};
+
+func main() {
+	var a;
+	a = 10;
+	return a;
+}

+ 13 - 0
test/04-codegen/assignment2.gravity

@@ -0,0 +1,13 @@
+#unittest {
+	name: "Test file assignment.";
+	error: NONE;
+	result: 10;
+};
+
+var a;
+
+func main() {
+	var a = 20;
+	file.a = 10;
+	return file.a;
+}

+ 12 - 0
test/04-codegen/assignment3.gravity

@@ -0,0 +1,12 @@
+#unittest {
+	name: "Test global literal assignment.";
+	error: NONE;
+	result: 10;
+};
+
+var a;
+
+func main() {
+	a = 10;
+	return a;
+}

+ 13 - 0
test/04-codegen/assignment4.gravity

@@ -0,0 +1,13 @@
+#unittest {
+	name: "Test global literal assignment.";
+	error: NONE;
+	result: 10;
+};
+
+var a;
+
+func main() {
+	var a = 20;
+	a = 10;
+	return a;
+}

+ 25 - 0
test/04-codegen/class/1.gravity

@@ -0,0 +1,25 @@
+#unittest {
+	name: "Test class instantiation.";
+	error: NONE;
+	result: 3;
+};
+
+class foo {
+	var a = 1;
+	var b = 2;
+	
+	func f1() {
+		return self.a;
+	}
+	
+	func f2() {
+		return b;
+	}
+}
+
+func main() {
+	var f = foo();
+	var a = f.f1();
+	var b = f.f2();
+	return a+b;
+}

+ 19 - 0
test/04-codegen/class/12.gravity

@@ -0,0 +1,19 @@
+#unittest {
+	name: "Test combined init/call init.";
+	error: NONE;
+	result: 150;
+};
+
+// no init
+class c1 {
+	var p1;
+	func f1() {
+		p1 = 150;
+		return p1;
+	}
+}
+
+func main() {
+	var x1 = c1().f1();
+	return x1;
+}

Some files were not shown because too many files changed in this diff