This repository was archived by the owner on Mar 25, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathserialization.rb
More file actions
169 lines (144 loc) · 5.3 KB
/
Copy pathserialization.rb
File metadata and controls
169 lines (144 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# frozen_string_literal: true
module SyntaxTree
module JSON
module Serialization
# Returns the three integers corresponding to the version of this library.
def self.version
VERSION.split(".").map(&:to_i)
end
# When dumping out objects, we need an indicator of which object is coming
# next. This module tracks those constants so they are consistent.
module Directive
ARRAY = "A"
FALSE = "F"
OBJECT = "O"
NULL = "N"
NUMBER = "#"
ROOT = "R"
STRING = "S"
TRUE = "T"
# This is the prefix that ensures it's quick to verify that this is a
# validly serialized AST.
MAGIC = "STJN"
end
# This class is a visitor responsible for serializing the AST to a binary
# string.
class Dump < Visitor
attr_reader :output
def initialize
@output = StringIO.new
@output.write([Directive::MAGIC, *Serialization.version].pack("A4 I_3"))
end
def dumped
output.string
end
# Visit an AST::Location.
def visit_location(location)
output.write([location.start_offset, location.end_offset].pack("L_2"))
end
# Visit an AST::Array node.
def visit_array(node)
output.write(Directive::ARRAY)
visit_location(node.location)
output.write([node.values.length].pack("L_"))
node.values.each { |value| visit(value) }
end
# Visit an AST::False node.
def visit_false(node)
output.write(Directive::FALSE)
visit_location(node.location)
end
# Visit an AST::Null node.
def visit_null(node)
output.write(Directive::NULL)
visit_location(node.location)
end
# Visit an AST::Number node.
def visit_number(node)
output.write(Directive::NUMBER)
visit_location(node.location)
end
# Visit an AST::Object node.
def visit_object(node)
output.write(Directive::OBJECT)
visit_location(node.location)
output.write([node.values.length].pack("L_"))
node.values.each do |key, value|
visit(key)
visit(value)
end
end
# Visit an AST::Root node.
def visit_root(node)
output.write(Directive::ROOT)
visit_location(node.location)
visit(node.value)
end
# Visit an AST::String node.
def visit_string(node)
output.write(Directive::STRING)
visit_location(node.location)
end
# Visit an AST::True node.
def visit_true(node)
output.write(Directive::TRUE)
visit_location(node.location)
end
end
# This class is responsible for taking a source string and the serialized
# AST representation and reifying the AST.
class Load
attr_reader :source, :dumped
def initialize(source, dumped)
@source = source
@dumped = StringIO.new(dumped)
end
def loaded
major, minor, patch = Serialization.version
dumped.read(16).unpack("A4 I_3") => [Directive::MAGIC, ^major, ^minor, ^patch]
commands = [:unpack_root]
values = []
while (command = commands.pop)
case command
in :unpack_root
dumped.read(17).unpack("A L_2") => [^(Directive::ROOT), start_offset, end_offset]
location = AST::Location.new(start_offset, end_offset)
commands += [[:pack_root, location], :unpack_value]
in :unpack_value
dumped.read(17).unpack("A L_2") => [type, start_offset, end_offset]
location = AST::Location.new(start_offset, end_offset)
case type
in Directive::ARRAY
dumped.read(8).unpack1("L_") => length
commands << [:pack_array, length, location]
commands += length.times.map { :unpack_value }
in Directive::FALSE
values << AST::False.new(location: location)
in Directive::NULL
values << AST::Null.new(location: location)
in Directive::NUMBER
values << AST::Number.new(value: source[location.to_range], location: location)
in Directive::OBJECT
dumped.read(8).unpack1("L_") => length
commands << [:pack_object, length, location]
commands += (length * 2).times.map { :unpack_value }
in Directive::STRING
values << AST::String.new(value: source[location.to_range], location: location)
in Directive::TRUE
values << AST::True.new(location: location)
end
in [:pack_array, length, location]
values << AST::Array.new(values: values.pop(length), location: location)
in [:pack_object, length, location]
values << AST::Object.new(values: values.pop(length * 2).each_slice(2).to_a, location: location)
in [:pack_root, location]
values << AST::Root.new(value: values.pop, location: location)
end
end
values => [AST::Root => value]
value
end
end
end
end
end