ClsRuby 1.0.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.
Files changed (71) hide show
  1. data/LICENSE +26 -0
  2. data/README +55 -0
  3. data/THANKS +0 -0
  4. data/docs/base_formatting_methods +89 -0
  5. data/docs/base_parsing_methods +79 -0
  6. data/docs/constructor_params +131 -0
  7. data/docs/examples/log_single_line_format +3 -0
  8. data/docs/examples/service_description +3 -0
  9. data/docs/examples/sms-hist +3 -0
  10. data/docs/examples/tag_any +3 -0
  11. data/docs/fragments/custom_tag_field.rb +20 -0
  12. data/docs/fragments/custom_tag_include.rb +21 -0
  13. data/docs/fragments/field.cls +2 -0
  14. data/docs/fragments/include.cls +4 -0
  15. data/docs/fragments/inherit_tag_params.rb +21 -0
  16. data/docs/fragments/message.cls +6 -0
  17. data/docs/fragments/tag_field.rb +24 -0
  18. data/docs/fragments/tag_message.rb +74 -0
  19. data/docs/fragments/tags_order.rb +41 -0
  20. data/docs/principles +402 -0
  21. data/docs/std_tags_short_description +278 -0
  22. data/docs/syntax +227 -0
  23. data/docs/why_cls +178 -0
  24. data/examples/hex_stream.txt +1 -0
  25. data/examples/log_single_line_formatter.rb +79 -0
  26. data/examples/service_description.day_time.cfg +11 -0
  27. data/examples/service_description.rb +119 -0
  28. data/examples/sms-hist.rb +164 -0
  29. data/examples/space_concat.txt +3 -0
  30. data/examples/tag_any.rb +28 -0
  31. data/lib/cls-ruby/basic_scalars.rb +270 -0
  32. data/lib/cls-ruby/constraints/one_of.rb +36 -0
  33. data/lib/cls-ruby/default_formatter.rb +50 -0
  34. data/lib/cls-ruby/ex.rb +121 -0
  35. data/lib/cls-ruby/formatter.rb +31 -0
  36. data/lib/cls-ruby/lexers/char_classifier.rb +157 -0
  37. data/lib/cls-ruby/lexers/first_stage.rb +112 -0
  38. data/lib/cls-ruby/lexers/lexer.rb +74 -0
  39. data/lib/cls-ruby/oneline_formatter.rb +35 -0
  40. data/lib/cls-ruby/parser.rb +249 -0
  41. data/lib/cls-ruby/tag.rb +428 -0
  42. data/lib/cls-ruby/tag_any.rb +111 -0
  43. data/lib/cls-ruby/tag_no_value.rb +55 -0
  44. data/lib/cls-ruby/tag_scalar.rb +197 -0
  45. data/lib/cls-ruby/tag_scalar_helpers.rb +70 -0
  46. data/lib/cls-ruby/tag_scalar_vector.rb +148 -0
  47. data/lib/cls-ruby/tag_vector_of_different_tags.rb +172 -0
  48. data/lib/cls-ruby/tag_vector_of_tags.rb +116 -0
  49. data/lib/cls-ruby/vector_of_tags_impl.rb +129 -0
  50. data/lib/cls-ruby.rb +5 -0
  51. data/tests/tc_child_tag.rb +51 -0
  52. data/tests/tc_constraint_one_of.rb +47 -0
  53. data/tests/tc_format_helper.rb +27 -0
  54. data/tests/tc_formatters.rb +125 -0
  55. data/tests/tc_lexer.rb +124 -0
  56. data/tests/tc_lexer_char_classifier.rb +121 -0
  57. data/tests/tc_lexer_first_stage.rb +72 -0
  58. data/tests/tc_parser_simple.rb +93 -0
  59. data/tests/tc_scalar_parsers.rb +189 -0
  60. data/tests/tc_tag.rb +68 -0
  61. data/tests/tc_tag_no_value.rb +46 -0
  62. data/tests/tc_tag_scalar_int.rb +83 -0
  63. data/tests/tc_tag_scalar_nonspace_string.rb +46 -0
  64. data/tests/tc_tag_scalar_string.rb +47 -0
  65. data/tests/tc_tag_scalar_vector_int.rb +88 -0
  66. data/tests/tc_tag_scalar_vector_string.rb +48 -0
  67. data/tests/tc_tag_scalar_vector_string_empty.rb +40 -0
  68. data/tests/tc_tag_vector_of_different_tags.rb +109 -0
  69. data/tests/tc_tag_vector_of_tags.rb +103 -0
  70. data/tests/ts_cls_ruby.rb +7 -0
  71. metadata +140 -0
@@ -0,0 +1,112 @@
1
+ #
2
+ # ���������� ������ ClsRuby::Lexers::FirstStage
3
+ #
4
+
5
+ require 'cls-ruby/ex.rb'
6
+
7
+ module ClsRuby
8
+
9
+ module Lexers
10
+
11
+ # ����� ��� ���������� ������ ������ ������������ �������:
12
+ # �������������� �������� escape-������������������� � �������.
13
+ class FirstStage
14
+ # ������ � ����� �������� ������.
15
+ attr_reader :stream_name
16
+ # ������ � ������ ������ �������� ������, �� ������� ����������� ������.
17
+ attr_reader :line_no
18
+
19
+ # �����������.
20
+ #
21
+ # [_stream_] ������� �����. ������, ������� ������������� ������
22
+ # getc � ungetc ������������� ������� IO#getc � IO#ungetc.
23
+ # [_stream_name_] ��� �������� ������.
24
+ def initialize( stream, stream_name )
25
+ @stream = stream
26
+ @stream_name = stream_name
27
+ @line_no = 1
28
+ end
29
+
30
+ # ���������� ���������� ������� �� �������� ������.
31
+ #
32
+ # ���������� nil ��� ���������� ����� ������. ��� ��������� �����������
33
+ # ������.
34
+ #
35
+ def next
36
+ ch = @stream.getc
37
+ if ch
38
+ process_next_char( ch.chr )
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # ��������� ���������� ������������ �� �������� ������ �������.
47
+ # ��������� �������� �� ������ �������� escape-������������������.
48
+ #
49
+ # ���������� ���� ���������� ��� � �������� ��������� ������, ����
50
+ # ������������ � escape-������������������ ������.
51
+ def process_next_char( ch )
52
+ if '\\' == ch
53
+ n = @stream.getc
54
+ raise UnclosedEscapeSeqEx.new( stream_name, line_no,
55
+ "unexpected EOF after \\" ) unless n
56
+
57
+ case n.chr
58
+ when /[obx]/i
59
+ parse_digital_escape_seq( n.chr )
60
+ else
61
+ @stream.ungetc( n )
62
+ ch
63
+ end
64
+ else
65
+ @line_no += 1 if "\n" == ch
66
+ ch
67
+ end
68
+ end
69
+
70
+ # ���������� � ��������� �������� ������������������ ��
71
+ # �������� ������ � �������������� �� � ������.
72
+ #
73
+ # [_escape_leading_symbol_] ������� ������������������, �������
74
+ # ���������� ������� ���������.
75
+ def parse_digital_escape_seq( escape_leading_symbol )
76
+ enabled_chars, base, max_len = case escape_leading_symbol
77
+ when /b/i
78
+ [ /[01]/, 2, 8 ]
79
+ when /o/i
80
+ [ /[0-7]/, 8, 3 ]
81
+ when /x/i
82
+ [ /[[:xdigit:]]/, 16, 2 ]
83
+ end
84
+
85
+ tmp = ''
86
+ for i in 1..max_len
87
+ n = @stream.getc
88
+ break unless n
89
+
90
+ c = n.chr
91
+ if enabled_chars =~ c
92
+ tmp << c
93
+ else
94
+ # ������ ������ ������, ������� �� �����������
95
+ # escape-������������������. �������� ����� ���������.
96
+ @stream.ungetc( n )
97
+ break
98
+ end
99
+ end
100
+
101
+ raise UnclosedEscapeSeqEx.new( stream_name, line_no,
102
+ "zero length digital escape sequence \\#{escape_leading_symbol}" ) if
103
+ 0 == tmp.size
104
+
105
+ tmp.to_i( base ).chr
106
+ end
107
+ end
108
+
109
+ end # Lexers
110
+
111
+ end # ClsRuby
112
+
@@ -0,0 +1,74 @@
1
+ #
2
+ # ����������� ���������� ������� ������ -- ����������� ��������������� ������
3
+ # ������� :tok_space � :tok_nonspace � ���� �������.
4
+ #
5
+
6
+ require 'cls-ruby/ex'
7
+ require 'cls-ruby/lexers/char_classifier'
8
+
9
+ module ClsRuby
10
+
11
+ module Lexers
12
+
13
+ # ����������� ����������, ������� ���������� � ���� ������� ������������������
14
+ # ������� :tok_space � :tok_nonspace.
15
+ #
16
+ class Lexer
17
+ EOF = CharClassifier::EOF
18
+
19
+ def initialize( stream, stream_name )
20
+ @classifier = CharClassifier.new( stream, stream_name )
21
+
22
+ # �������� ��������� ����������� �� �������� ������ �����,
23
+ # ������� �� �������� :tok_space ��� :tok_nonspace.
24
+ # ���� ����� nil, �� �� ������ ����� ��������� ��������� ������.
25
+ @last_unprocessed_pair = nil
26
+ end
27
+
28
+ def stream_name; @classifier.stream_name; end
29
+ def line_no; @classifier.line_no; end
30
+
31
+ # ���������� ��������� �������.
32
+ #
33
+ # ���������� �������� EOF, ���� ��������� ����� ������.
34
+ def next
35
+ pair = get_next_pair
36
+ if :tok_space == pair[ 1 ] || :tok_nonspace == pair[ 1 ]
37
+ union_sequence( pair )
38
+ else
39
+ pair
40
+ end
41
+ end
42
+
43
+ private
44
+ # ���������� ��������� ������� �� ������.
45
+ #
46
+ # ���� ���� �������������� ������� � @last_unprocessed_pair, ��
47
+ # ������������ ��� ��� ������������� ������.
48
+ def get_next_pair
49
+ n = @last_unprocessed_pair; @last_unprocessed_pair = nil
50
+ n = @classifier.next unless n
51
+ n
52
+ end
53
+
54
+ # ����������� ���� ��������������� ������ ������ � ����,
55
+ # ���� ��� ��������� � ������ ��������.
56
+ def union_sequence( first )
57
+ result = first.dup
58
+ while EOF != ( n = @classifier.next )
59
+ if result[ 1 ] == n[ 1 ]
60
+ result[ 0 ] << n[ 0 ]
61
+ else
62
+ @last_unprocessed_pair = n
63
+ break
64
+ end
65
+ end
66
+
67
+ result
68
+ end
69
+ end
70
+
71
+ end # Lexers
72
+
73
+ end # ClsRuby
74
+
@@ -0,0 +1,35 @@
1
+ #
2
+ # ����� ��������� �����, ������� ����������� ��� �������� � ���� ������.
3
+ #
4
+
5
+ module ClsRuby
6
+
7
+ # ����� ��������� �����, ������� ����������� ��� �������� � ���� ������.
8
+ #
9
+ # ������ �������������:
10
+ # result = ''
11
+ # formatter = ClsRuby::OneLineFormatter.new( result )
12
+ # some_tag.tag_format( formatter )
13
+ #
14
+ class OneLineFormatter
15
+ # [_receiver_] ��� ������, ��� �������� ��������� �������� ������ <<.
16
+ #
17
+ def initialize( receiver )
18
+ @receiver = receiver
19
+ end
20
+
21
+ def start( name )
22
+ @receiver << '{' << name << ' '
23
+ end
24
+
25
+ def value( what )
26
+ @receiver << what << ' '
27
+ end
28
+
29
+ def finish
30
+ @receiver << '} '
31
+ end
32
+ end
33
+
34
+ end # module ClsRuby
35
+
@@ -0,0 +1,249 @@
1
+ #
2
+ # ����� ������� CLS ������.
3
+ #
4
+
5
+ require 'stringio'
6
+
7
+ require 'cls-ruby/tag'
8
+ require 'cls-ruby/lexers/lexer'
9
+
10
+ module ClsRuby
11
+
12
+ # ������� ������, ������������ � ������� IO.
13
+ #
14
+ # ������:
15
+ # fisrt = TagFirst.new
16
+ # second = TagSecond.new
17
+ # ...
18
+ # ClsRuby.parse_io( stream, 'some_name', first, second )
19
+ #
20
+ def ClsRuby.parse_io( stream, stream_name, *tags )
21
+ parser = Parser.new( stream, stream_name, tags )
22
+ parser.parse
23
+
24
+ tags
25
+ end
26
+
27
+ # ������� ������, ������������ � ������� String.
28
+ #
29
+ # ������:
30
+ # content = File.read( some_name )
31
+ # first = TagFirst.new
32
+ # second = TagSecond.new
33
+ # ...
34
+ # ClsRuby.parse_string( content, first, second )
35
+ #
36
+ # ������ ����� ������ ����� �������������� �������� '-'.
37
+ def ClsRuby.parse_string( string, *tags )
38
+ stream = StringIO.new( string )
39
+ parse_io( stream, '-', *tags )
40
+ end
41
+
42
+ # ������� �����.
43
+ #
44
+ # ������:
45
+ # fisrt = TagFirst.new
46
+ # second = TagSecond.new
47
+ # ...
48
+ # ClsRuby.parse_file( 'some_name', first, second )
49
+ def ClsRuby.parse_file( file_name, *tags )
50
+ File.open( file_name, 'r' ) do |f|
51
+ parse_io( f, file_name, *tags )
52
+ end
53
+
54
+ tags
55
+ end
56
+
57
+ # ����� ������� CLS ������.
58
+ #
59
+ # ������ �������������
60
+ # parser = Parser.new( stream, stream_name, tags )
61
+ # parser.parse
62
+ # ���� �� ����� �������� �������������� ������, �� ����� Parser#parse
63
+ # ����������� ����������.
64
+ #
65
+ class Parser
66
+ # �����������.
67
+ #
68
+ # [_stream_] ������� �����. ������ ������������� ������ getc � ungetc
69
+ # ����������� ������� �� ������ IO.
70
+ # [_stream_name_] ��� �������� ������.
71
+ # [_tags_] ������ �����, ������� ����� ����������� � ��������.
72
+ def initialize( stream, stream_name, tags )
73
+ @stack = [ TopLevelTag.new( tags ) ]
74
+ @lexer = Lexers::Lexer.new( stream, stream_name )
75
+ end
76
+
77
+ # ���������� �������� �������� ������.
78
+ #
79
+ # ��������� �������� ����� ����������� � �����, ������� ����
80
+ # ��������� � ������������.
81
+ def parse
82
+ begin
83
+ run_parsing
84
+ rescue Exception => x
85
+ envelop_and_reraise_exception( x )
86
+ end
87
+ end
88
+
89
+ private
90
+ EOF = Lexers::Lexer::EOF
91
+
92
+ # ������������ ����� ������ ������� � ��������, ������� �����
93
+ # �������� � �������� ���� ��� ��������� ����.
94
+ TOKEN_HANDLES = {
95
+ :tok_space => :tag_on_tok_space,
96
+ :tok_nonspace => :tag_on_tok_nonspace,
97
+ :tok_string => :tag_on_tok_string
98
+ }
99
+
100
+ # ���������, ������� ��������, ��� ����������� ������ � ������
101
+ # �������� �������� ����� �����, ���� ���� ��� TopLevelTag.
102
+ ACCESS_TO_TOPLEVEL_ENABLED = 0
103
+
104
+ # ��������������� ����� ����, ������� ����� ��������� � �������
105
+ # ����� �������. ��� ������ -- ��������� ���������� ����������, ����
106
+ # �� ����� ������� ������ ����������� ������ :tok_string ���
107
+ # :tok_nonspace.
108
+ #
109
+ # �������� � ������������ ������ �������� ����� � ������������� ���������
110
+ # �� � ���� � �������� ��������.
111
+ class TopLevelTag < Tag
112
+ def initialize( tags )
113
+ super( { :name => '' } )
114
+
115
+ tags.each do |t| tag_add( t ) end
116
+ end
117
+
118
+ def tag_on_tok_nonspace( token )
119
+ raise UnexpectedTokenEx.new(
120
+ "unexpected :tok_nonspace at top level ('#{token}')" )
121
+ end
122
+
123
+ def tag_on_tok_string( token )
124
+ raise UnexpectedTokenEx.new(
125
+ "expected :tok_string at top level ('#{token}')" )
126
+ end
127
+ end
128
+
129
+ # ���������� �������� �������� �� ��������.
130
+ def run_parsing
131
+ while EOF != ( n = @lexer.next )
132
+ if n[1] == :tok_open_block
133
+ process_tag_start
134
+ elsif n[1] == :tok_close_block
135
+ process_tag_finish
136
+ else
137
+ process_token( n )
138
+ end
139
+ end
140
+
141
+ process_parsing_finish
142
+ end
143
+
144
+ # ��������� ���������� ParsingErrorEx, � ������� ��������� ��������
145
+ # �������������� ����������. ������ � ������� � ����� �������������
146
+ # ��������.
147
+ def envelop_and_reraise_exception( x )
148
+ open_tags_names = make_open_tags_names.chop
149
+ open_tags_names += ': ' if 0 != open_tags_names.size
150
+ envelop = ParsingErrorEx.new(
151
+ @lexer.stream_name,
152
+ @lexer.line_no,
153
+ "#{open_tags_names} #{x.message} [#{x.class.name}]" )
154
+ envelop.nested_exception = x
155
+ envelop.set_backtrace( x.backtrace )
156
+ raise envelop
157
+ end
158
+
159
+ # ��������� ������ ���������� ����.
160
+ #
161
+ # ��������� ������, ���� ������ ��������� �� �������� ������
162
+ # ���-����, �������� �� :tok_non_space.
163
+ def process_tag_start
164
+ n = @lexer.next
165
+ if :tok_nonspace == n[1]
166
+ try_start_tag_with_name( n[0] )
167
+ else
168
+ raise UnexpectedTokenEx.new( "tag name expected instead of #{n[1]}" )
169
+ end
170
+ end
171
+
172
+ # ��������� ���������� ���������� ����.
173
+ def process_tag_finish
174
+ tag_to_finish = top_tag
175
+ tag_to_finish.tag_on_finish
176
+ pop_tag
177
+
178
+ top_tag( ACCESS_TO_TOPLEVEL_ENABLED ).tag_on_tag( tag_to_finish )
179
+ end
180
+
181
+ # �������� ���� �������� ����.
182
+ def process_token( token )
183
+ method = TOKEN_HANDLES.fetch( token[1], nil )
184
+ raise UnexpectedTokenEx.new( "unable to handle token #{token[1]} " +
185
+ "('#{token[0]}')" ) unless method
186
+
187
+ # ���� ����� ����� �������� ���� � TopLevelTag.
188
+ top_tag( ACCESS_TO_TOPLEVEL_ENABLED ).send( method, token[0] )
189
+ end
190
+
191
+ # ��������� ���������� ��������.
192
+ #
193
+ # � ����� ������ ���� ������ ���� ��� � ��� TopLevelTag ����������
194
+ # tag_on_finish ��� ����, ����� ���������, ��� ��� ������������ ����
195
+ # ����������.
196
+ def process_parsing_finish
197
+ raise NonEmptyStackEx.new( "too few }" ) if 1 != @stack.size
198
+ @stack[ 0 ].tag_on_finish
199
+ end
200
+
201
+ # ������� ������ ���� � ��������� ������.
202
+ #
203
+ # ���������, ���� �� ��� � ����� ������ � ������ �������� �����
204
+ # �������� ����.
205
+ def try_start_tag_with_name( name )
206
+ tag_to_start = top_tag( ACCESS_TO_TOPLEVEL_ENABLED ).tag_tags.find do |tag|
207
+ tag.tag_compare_name( name )
208
+ end
209
+
210
+ raise UnknownTagEx.new(
211
+ "unknown tag name: #{name}" ) unless tag_to_start
212
+
213
+ tag_to_start.tag_on_start( name )
214
+ push_tag( tag_to_start )
215
+ end
216
+
217
+ # ��������� ������� � ������ �������� ���� � �����.
218
+ #
219
+ # ��������� ����������, ���� � ����� �������� ����� ���� TopLevelTag.
220
+ def top_tag( danger_limit = 1 )
221
+ raise EmptyTagStackEx.new( "no tags in stack" ) if danger_limit == @stack.size
222
+ @stack.last
223
+ end
224
+
225
+ # ��������� ���������� ���� � ������� �����.
226
+ def push_tag( tag )
227
+ @stack.push( tag )
228
+ end
229
+
230
+ # ������ ���� � ������� �����.
231
+ #
232
+ # ��������� ����������, ���� � ����� �������� ����� ���� TopLevelTag.
233
+ def pop_tag
234
+ raise EmptyTagStackEx.new( "too many }" ) if 1 >= @stack.size
235
+ @stack.pop
236
+ end
237
+
238
+ # ���������� � ���� ������ ����� ���� ���������� � �������� ������� �����.
239
+ def make_open_tags_names
240
+ @stack[ 1...@stack.size ].inject( '' ) do
241
+ |names, tag|
242
+ names << "{#{tag.tag_name} "
243
+ end
244
+ end
245
+
246
+ end
247
+
248
+ end # module ClsRuby
249
+