litl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Builder
5
+ # @api private
6
+ # @since 0.1.0
7
+ class Option
8
+ def initialize(mod, type)
9
+ @type_checker = TypeChecker.new(mod)
10
+ @type = type
11
+ end
12
+
13
+ def build
14
+ mod = Module.new do
15
+ include ::LIT::Object::Option
16
+ end
17
+
18
+ type_checker = @type_checker
19
+ type = @type
20
+
21
+ mod::Some.define_method(:check_type!) do |value|
22
+ # NOTE: actually covered, but SimpleCov cannot detect that
23
+ # :nocov:
24
+ type_checker.check_type!(type, value)
25
+ # :nocov:
26
+ end
27
+
28
+ mod
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ TypeError = Class.new(::TypeError)
7
+
8
+ InvalidASTError = Class.new(StandardError)
9
+ InvalidObjectError = Class.new(StandardError)
10
+ InvalidTypeError = Class.new(StandardError)
11
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class Loader
7
+ FILE_EXTENSION = ".lit"
8
+
9
+ def initialize
10
+ loader = self
11
+
12
+ @target_module = Module.new do
13
+ include ::LIT::Object
14
+
15
+ define_singleton_method(:reload_file) do |file_path|
16
+ loader.reload_file(file_path)
17
+ end
18
+
19
+ define_singleton_method(:serialize) do |object|
20
+ Serializer.new(object).serialize
21
+ end
22
+
23
+ define_singleton_method(:deserialize_request) do |data|
24
+ RequestDeserializer.new(data, self).deserialize_request
25
+ end
26
+ end
27
+
28
+ @working_module = @target_module
29
+ end
30
+
31
+ def load_directory(dir_path)
32
+ glob = File.join("**", "*#{FILE_EXTENSION}")
33
+ @base_dir = Pathname.new(dir_path)
34
+
35
+ Dir.glob(glob, base: dir_path) do |filename|
36
+ load_file(filename)
37
+ end
38
+
39
+ @target_module
40
+ end
41
+
42
+ def reload_file(file_path)
43
+ @working_module = @target_module
44
+ load_file(file_path)
45
+ end
46
+
47
+ private
48
+
49
+ def load_file(filename)
50
+ file_path = Pathname.new(File.expand_path(filename, @base_dir))
51
+ relative_path = file_path.relative_path_from(@base_dir).to_s
52
+ namespace = [*File.dirname(relative_path).split(File::SEPARATOR).slice(1),
53
+ File.basename(relative_path, FILE_EXTENSION)]
54
+
55
+ if namespace == %w[main]
56
+ load_main_file(file_path)
57
+ else
58
+ load_regular_file(file_path, namespace)
59
+ end
60
+ end
61
+
62
+ def load_main_file(file_path)
63
+ ast = Parser.parse_file(file_path)
64
+ Builder::Object.new(ast, @target_module).build
65
+ end
66
+
67
+ def load_regular_file(file_path, namespace)
68
+ namespace.each do |mod|
69
+ @working_module = Utils.const_reset(@working_module, Utils.camelize(mod), Module.new)
70
+ end
71
+
72
+ ast = Parser.parse_file(file_path)
73
+
74
+ Builder::Object.new(ast, @working_module).build
75
+ @working_module.include(@target_module)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ module ModuleObserver
7
+ def self.included(object)
8
+ object.define_singleton_method(:observe) do |target_module|
9
+ namespace = target_module.name.split("::")
10
+
11
+ define_singleton_method(:const_missing) do |const_name|
12
+ mod = namespace.reduce(self) do |mod, child_name|
13
+ mod.const_get(child_name)
14
+ end
15
+
16
+ mod.const_get(const_name)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ module Object
7
+ require_relative "object/array"
8
+ require_relative "object/enum"
9
+ require_relative "object/enum_variant"
10
+ require_relative "object/map"
11
+ require_relative "object/option"
12
+ require_relative "object/struct"
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Array
8
+ def initialize(*values)
9
+ values.each { |v| check_type!(v) }
10
+ @values = values
11
+ end
12
+
13
+ def each(&block)
14
+ @values.each(&block)
15
+ end
16
+
17
+ def to_a
18
+ @values
19
+ end
20
+
21
+ def [](index)
22
+ @values[index]
23
+ end
24
+
25
+ def __values__
26
+ @values
27
+ end
28
+
29
+ private
30
+
31
+ def check_type!(value); end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ module Enum; end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ module EnumVariant; end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Map
8
+ attr_reader :values
9
+
10
+ def initialize(values = {})
11
+ values = Utils::MapHash.new(values)
12
+ values.each { |k, v| check_type!(k, v) }
13
+ @values = values
14
+ end
15
+
16
+ def [](key)
17
+ @values[key]
18
+ end
19
+
20
+ def __values__
21
+ @values.to_h
22
+ end
23
+
24
+ private
25
+
26
+ def check_type!(key, value); end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ def self.included(object)
6
+ object.define_singleton_method(:Some) { |value| Option::Some.new(value) }
7
+ Utils.const_reset(object, "None", Option::None)
8
+ Utils.const_reset(object, "Some", Class.new(Option::Some))
9
+ end
10
+
11
+ # @api public
12
+ # @since 0.1.0
13
+ module Option
14
+ def self.included(object)
15
+ object.define_singleton_method(:Some) { |value| Some.new(value) }
16
+ Utils.const_reset(object, "Some", Class.new(Some))
17
+ end
18
+
19
+ None = Class.new
20
+
21
+ class Some
22
+ attr_reader :value
23
+
24
+ def initialize(value)
25
+ check_type!(value)
26
+ @value = value
27
+ end
28
+
29
+ def __value__
30
+ @value
31
+ end
32
+
33
+ private
34
+
35
+ def check_type!(value); end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Struct; end
8
+ end
9
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class RequestDeserializer
7
+ AST = Parser::AST
8
+
9
+ def initialize(raw_request, type_root)
10
+ # { action, payload }
11
+ @raw_request = raw_request
12
+ @type_root = type_root
13
+ end
14
+
15
+ def deserialize_request
16
+ action_namespace = @raw_request.fetch("action").split(".").map { |x| Utils.camelize(x) }
17
+ action_type = action_namespace.reduce(@type_root) do |object, namespace|
18
+ object.const_get(namespace)
19
+ end
20
+
21
+ request_type = action_type.const_get("Request")
22
+ @type_module = action_type.const_get("DefinedIn")
23
+
24
+ deserialize(request_type, @raw_request.fetch("payload"))
25
+ end
26
+
27
+ private
28
+
29
+ # rubocop:disable Metrics/MethodLength
30
+ def deserialize(type, payload)
31
+ if type.is_a?(Module)
32
+ if type <= Object::Struct
33
+ deserialize_struct(type, payload)
34
+ elsif type <= Object::Enum
35
+ deserialize_enum(type, payload)
36
+ elsif type == AST::Type::Primitive::String ||
37
+ type == AST::Type::Primitive::Integer ||
38
+ type == AST::Type::Primitive::Float ||
39
+ type == AST::Type::Primitive::Boolean
40
+ payload
41
+ else
42
+ deserialize(type::TYPE, payload)
43
+ end
44
+ elsif type.is_a?(AST::Type::Alias)
45
+ deserialize(@type_module.const_get(type.name), payload)
46
+ elsif type.is_a?(AST::Type::Primitive::Array)
47
+ deserialize_array(type, payload)
48
+ elsif type.is_a?(AST::Type::Primitive::Map)
49
+ deserialize_map(type, payload)
50
+ elsif type.is_a?(AST::Type::Primitive::Option)
51
+ deserialize_option(type.type, payload)
52
+ else
53
+ raise InvalidTypeError, "invalid type: #{type}"
54
+ end
55
+ end
56
+ # rubocop:enable Metrics/MethodLength
57
+
58
+ def deserialize_struct(struct_klass, struct_data)
59
+ params = struct_data.reduce({}) do |acc, (key, value)|
60
+ field_name = key.to_sym
61
+ field_type = struct_klass.__fields__[field_name]
62
+ acc.merge(field_name => deserialize(field_type, value))
63
+ end
64
+
65
+ struct_klass.new(params)
66
+ end
67
+
68
+ def deserialize_enum(enum_module, enum_data)
69
+ variant_name = Utils.camelize(enum_data.fetch("kind"))
70
+ variant = enum_module.const_get(variant_name)
71
+
72
+ if variant.__kind__ == :unit
73
+ variant
74
+ elsif variant.__kind__ == :struct
75
+ deserialize_struct(variant, enum_data.fetch("data"))
76
+ end
77
+ end
78
+
79
+ def deserialize_option(type, data)
80
+ if data.nil?
81
+ Object::Option::None
82
+ else
83
+ deserialized_data = deserialize(type, data)
84
+ option = Builder::Option.new(@type_module, type).build
85
+ option::Some.new(deserialized_data)
86
+ end
87
+ end
88
+
89
+ def deserialize_map(type, data)
90
+ map = Builder::Map.new(@type_module, type.key_type, type.value_type).build
91
+
92
+ deserialized_data = data.reduce({}) do |acc, (key, value)|
93
+ acc.merge(deserialize(type.key_type, key) => deserialize(type.value_type, value))
94
+ end
95
+
96
+ map.new(deserialized_data)
97
+ end
98
+
99
+ def deserialize_array(type, data)
100
+ array = Builder::Array.new(@type_module, type.type).build
101
+ array.new(*data.map { |x| deserialize(type.type, x) })
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class Serializer
7
+ def initialize(object)
8
+ @object = object
9
+ end
10
+
11
+ def serialize
12
+ serialize_object(@object)
13
+ end
14
+
15
+ private
16
+
17
+ # rubocop:disable Metrics/MethodLength
18
+ def serialize_object(object)
19
+ if object.is_a?(Object::Struct)
20
+ if object.is_a?(Object::EnumVariant)
21
+ serialize_enum_variant(object)
22
+ else
23
+ serialize_struct(object)
24
+ end
25
+ elsif object.is_a?(Module) && object <= Object::EnumVariant
26
+ serialize_enum_variant(object)
27
+ elsif object.is_a?(Object::Array)
28
+ serialize_array(object)
29
+ elsif object.is_a?(Object::Map)
30
+ serialize_map(object)
31
+ elsif object.is_a?(Object::Option::Some) || object == Object::Option::None
32
+ serialize_option(object)
33
+ elsif object.is_a?(String) ||
34
+ object.is_a?(Integer) ||
35
+ object.is_a?(Float) ||
36
+ object == true ||
37
+ object == false
38
+ object
39
+ else
40
+ raise InvalidObjectError, "invalid object: #{object}"
41
+ end
42
+ end
43
+ # rubocop:enable Metrics/MethodLength
44
+
45
+ def serialize_struct(struct)
46
+ struct.instance_variables.reduce({}) do |hsh, ivar|
47
+ field_name = ivar.to_s.sub("@", "").to_sym
48
+ hsh.merge(field_name => serialize_object(struct.public_send(field_name)))
49
+ end
50
+ end
51
+
52
+ def serialize_enum_variant(variant)
53
+ data =
54
+ if variant.__kind__ == :struct
55
+ serialize_struct(variant)
56
+ else
57
+ nil
58
+ end
59
+
60
+ { kind: variant.__name__, data: data }
61
+ end
62
+
63
+ def serialize_array(array)
64
+ array.__values__.map { |value| serialize_object(value) }
65
+ end
66
+
67
+ def serialize_map(map)
68
+ map.__values__.reduce({}) do |acc, (key, value)|
69
+ acc.merge(serialize_object(key) => serialize_object(value))
70
+ end
71
+ end
72
+
73
+ def serialize_option(option)
74
+ if option == Object::Option::None
75
+ nil
76
+ else
77
+ serialize_object(option.__value__)
78
+ end
79
+ end
80
+ end
81
+ end