psych 2.0.14-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +18 -0
  3. data/.gemtest +0 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG.rdoc +576 -0
  6. data/Manifest.txt +114 -0
  7. data/README.rdoc +71 -0
  8. data/Rakefile +123 -0
  9. data/ext/psych/depend +3 -0
  10. data/ext/psych/extconf.rb +38 -0
  11. data/ext/psych/psych.c +34 -0
  12. data/ext/psych/psych.h +20 -0
  13. data/ext/psych/psych_emitter.c +555 -0
  14. data/ext/psych/psych_emitter.h +8 -0
  15. data/ext/psych/psych_parser.c +597 -0
  16. data/ext/psych/psych_parser.h +6 -0
  17. data/ext/psych/psych_to_ruby.c +43 -0
  18. data/ext/psych/psych_to_ruby.h +8 -0
  19. data/ext/psych/psych_yaml_tree.c +24 -0
  20. data/ext/psych/psych_yaml_tree.h +8 -0
  21. data/ext/psych/yaml/LICENSE +19 -0
  22. data/ext/psych/yaml/api.c +1415 -0
  23. data/ext/psych/yaml/config.h +10 -0
  24. data/ext/psych/yaml/dumper.c +394 -0
  25. data/ext/psych/yaml/emitter.c +2329 -0
  26. data/ext/psych/yaml/loader.c +459 -0
  27. data/ext/psych/yaml/parser.c +1370 -0
  28. data/ext/psych/yaml/reader.c +469 -0
  29. data/ext/psych/yaml/scanner.c +3576 -0
  30. data/ext/psych/yaml/writer.c +141 -0
  31. data/ext/psych/yaml/yaml.h +1971 -0
  32. data/ext/psych/yaml/yaml_private.h +664 -0
  33. data/lib/psych.jar +0 -0
  34. data/lib/psych.rb +504 -0
  35. data/lib/psych/class_loader.rb +101 -0
  36. data/lib/psych/coder.rb +94 -0
  37. data/lib/psych/core_ext.rb +35 -0
  38. data/lib/psych/deprecated.rb +85 -0
  39. data/lib/psych/exception.rb +13 -0
  40. data/lib/psych/handler.rb +249 -0
  41. data/lib/psych/handlers/document_stream.rb +22 -0
  42. data/lib/psych/handlers/recorder.rb +39 -0
  43. data/lib/psych/json/ruby_events.rb +19 -0
  44. data/lib/psych/json/stream.rb +16 -0
  45. data/lib/psych/json/tree_builder.rb +12 -0
  46. data/lib/psych/json/yaml_events.rb +29 -0
  47. data/lib/psych/nodes.rb +77 -0
  48. data/lib/psych/nodes/alias.rb +18 -0
  49. data/lib/psych/nodes/document.rb +60 -0
  50. data/lib/psych/nodes/mapping.rb +56 -0
  51. data/lib/psych/nodes/node.rb +55 -0
  52. data/lib/psych/nodes/scalar.rb +67 -0
  53. data/lib/psych/nodes/sequence.rb +81 -0
  54. data/lib/psych/nodes/stream.rb +37 -0
  55. data/lib/psych/omap.rb +4 -0
  56. data/lib/psych/parser.rb +51 -0
  57. data/lib/psych/scalar_scanner.rb +149 -0
  58. data/lib/psych/set.rb +4 -0
  59. data/lib/psych/stream.rb +37 -0
  60. data/lib/psych/streaming.rb +27 -0
  61. data/lib/psych/syntax_error.rb +21 -0
  62. data/lib/psych/tree_builder.rb +96 -0
  63. data/lib/psych/versions.rb +3 -0
  64. data/lib/psych/visitors.rb +6 -0
  65. data/lib/psych/visitors/depth_first.rb +26 -0
  66. data/lib/psych/visitors/emitter.rb +51 -0
  67. data/lib/psych/visitors/json_tree.rb +24 -0
  68. data/lib/psych/visitors/to_ruby.rb +404 -0
  69. data/lib/psych/visitors/visitor.rb +19 -0
  70. data/lib/psych/visitors/yaml_tree.rb +605 -0
  71. data/lib/psych/y.rb +9 -0
  72. data/lib/psych_jars.rb +5 -0
  73. data/test/psych/handlers/test_recorder.rb +25 -0
  74. data/test/psych/helper.rb +121 -0
  75. data/test/psych/json/test_stream.rb +109 -0
  76. data/test/psych/nodes/test_enumerable.rb +43 -0
  77. data/test/psych/test_alias_and_anchor.rb +96 -0
  78. data/test/psych/test_array.rb +57 -0
  79. data/test/psych/test_boolean.rb +36 -0
  80. data/test/psych/test_class.rb +36 -0
  81. data/test/psych/test_coder.rb +206 -0
  82. data/test/psych/test_date_time.rb +38 -0
  83. data/test/psych/test_deprecated.rb +214 -0
  84. data/test/psych/test_document.rb +46 -0
  85. data/test/psych/test_emitter.rb +93 -0
  86. data/test/psych/test_encoding.rb +259 -0
  87. data/test/psych/test_exception.rb +157 -0
  88. data/test/psych/test_hash.rb +94 -0
  89. data/test/psych/test_json_tree.rb +65 -0
  90. data/test/psych/test_merge_keys.rb +180 -0
  91. data/test/psych/test_nil.rb +18 -0
  92. data/test/psych/test_null.rb +19 -0
  93. data/test/psych/test_numeric.rb +45 -0
  94. data/test/psych/test_object.rb +44 -0
  95. data/test/psych/test_object_references.rb +71 -0
  96. data/test/psych/test_omap.rb +75 -0
  97. data/test/psych/test_parser.rb +339 -0
  98. data/test/psych/test_psych.rb +168 -0
  99. data/test/psych/test_safe_load.rb +97 -0
  100. data/test/psych/test_scalar.rb +11 -0
  101. data/test/psych/test_scalar_scanner.rb +106 -0
  102. data/test/psych/test_serialize_subclasses.rb +38 -0
  103. data/test/psych/test_set.rb +49 -0
  104. data/test/psych/test_stream.rb +93 -0
  105. data/test/psych/test_string.rb +226 -0
  106. data/test/psych/test_struct.rb +49 -0
  107. data/test/psych/test_symbol.rb +25 -0
  108. data/test/psych/test_tainted.rb +130 -0
  109. data/test/psych/test_to_yaml_properties.rb +63 -0
  110. data/test/psych/test_tree_builder.rb +79 -0
  111. data/test/psych/test_yaml.rb +1292 -0
  112. data/test/psych/test_yamldbm.rb +193 -0
  113. data/test/psych/test_yamlstore.rb +85 -0
  114. data/test/psych/visitors/test_depth_first.rb +49 -0
  115. data/test/psych/visitors/test_emitter.rb +144 -0
  116. data/test/psych/visitors/test_to_ruby.rb +333 -0
  117. data/test/psych/visitors/test_yaml_tree.rb +173 -0
  118. metadata +240 -0
@@ -0,0 +1,81 @@
1
+ module Psych
2
+ module Nodes
3
+ ###
4
+ # This class represents a
5
+ # {YAML sequence}[http://yaml.org/spec/1.1/#sequence/syntax].
6
+ #
7
+ # A YAML sequence is basically a list, and looks like this:
8
+ #
9
+ # %YAML 1.1
10
+ # ---
11
+ # - I am
12
+ # - a Sequence
13
+ #
14
+ # A YAML sequence may have an anchor like this:
15
+ #
16
+ # %YAML 1.1
17
+ # ---
18
+ # &A [
19
+ # "This sequence",
20
+ # "has an anchor"
21
+ # ]
22
+ #
23
+ # A YAML sequence may also have a tag like this:
24
+ #
25
+ # %YAML 1.1
26
+ # ---
27
+ # !!seq [
28
+ # "This sequence",
29
+ # "has a tag"
30
+ # ]
31
+ #
32
+ # This class represents a sequence in a YAML document. A
33
+ # Psych::Nodes::Sequence node may have 0 or more children. Valid children
34
+ # for this node are:
35
+ #
36
+ # * Psych::Nodes::Sequence
37
+ # * Psych::Nodes::Mapping
38
+ # * Psych::Nodes::Scalar
39
+ # * Psych::Nodes::Alias
40
+ class Sequence < Psych::Nodes::Node
41
+ # Any Styles, emitter chooses
42
+ ANY = 0
43
+
44
+ # Block style sequence
45
+ BLOCK = 1
46
+
47
+ # Flow style sequence
48
+ FLOW = 2
49
+
50
+ # The anchor for this sequence (if any)
51
+ attr_accessor :anchor
52
+
53
+ # The tag name for this sequence (if any)
54
+ attr_accessor :tag
55
+
56
+ # Is this sequence started implicitly?
57
+ attr_accessor :implicit
58
+
59
+ # The sequence style used
60
+ attr_accessor :style
61
+
62
+ ###
63
+ # Create a new object representing a YAML sequence.
64
+ #
65
+ # +anchor+ is the anchor associated with the sequence or nil.
66
+ # +tag+ is the tag associated with the sequence or nil.
67
+ # +implicit+ a boolean indicating whether or not the sequence was
68
+ # implicitly started.
69
+ # +style+ is an integer indicating the list style.
70
+ #
71
+ # See Psych::Handler#start_sequence
72
+ def initialize anchor = nil, tag = nil, implicit = true, style = BLOCK
73
+ super()
74
+ @anchor = anchor
75
+ @tag = tag
76
+ @implicit = implicit
77
+ @style = style
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ module Psych
2
+ module Nodes
3
+ ###
4
+ # Represents a YAML stream. This is the root node for any YAML parse
5
+ # tree. This node must have one or more child nodes. The only valid
6
+ # child node for a Psych::Nodes::Stream node is Psych::Nodes::Document.
7
+ class Stream < Psych::Nodes::Node
8
+
9
+ # Encodings supported by Psych (and libyaml)
10
+
11
+ # Any encoding
12
+ ANY = Psych::Parser::ANY
13
+
14
+ # UTF-8 encoding
15
+ UTF8 = Psych::Parser::UTF8
16
+
17
+ # UTF-16LE encoding
18
+ UTF16LE = Psych::Parser::UTF16LE
19
+
20
+ # UTF-16BE encoding
21
+ UTF16BE = Psych::Parser::UTF16BE
22
+
23
+ # The encoding used for this stream
24
+ attr_accessor :encoding
25
+
26
+ ###
27
+ # Create a new Psych::Nodes::Stream node with an +encoding+ that
28
+ # defaults to Psych::Nodes::Stream::UTF8.
29
+ #
30
+ # See also Psych::Handler#start_stream
31
+ def initialize encoding = UTF8
32
+ super()
33
+ @encoding = encoding
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/psych/omap.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Psych
2
+ class Omap < ::Hash
3
+ end
4
+ end
@@ -0,0 +1,51 @@
1
+ module Psych
2
+ ###
3
+ # YAML event parser class. This class parses a YAML document and calls
4
+ # events on the handler that is passed to the constructor. The events can
5
+ # be used for things such as constructing a YAML AST or deserializing YAML
6
+ # documents. It can even be fed back to Psych::Emitter to emit the same
7
+ # document that was parsed.
8
+ #
9
+ # See Psych::Handler for documentation on the events that Psych::Parser emits.
10
+ #
11
+ # Here is an example that prints out ever scalar found in a YAML document:
12
+ #
13
+ # # Handler for detecting scalar values
14
+ # class ScalarHandler < Psych::Handler
15
+ # def scalar value, anchor, tag, plain, quoted, style
16
+ # puts value
17
+ # end
18
+ # end
19
+ #
20
+ # parser = Psych::Parser.new(ScalarHandler.new)
21
+ # parser.parse(yaml_document)
22
+ #
23
+ # Here is an example that feeds the parser back in to Psych::Emitter. The
24
+ # YAML document is read from STDIN and written back out to STDERR:
25
+ #
26
+ # parser = Psych::Parser.new(Psych::Emitter.new($stderr))
27
+ # parser.parse($stdin)
28
+ #
29
+ # Psych uses Psych::Parser in combination with Psych::TreeBuilder to
30
+ # construct an AST of the parsed YAML document.
31
+
32
+ class Parser
33
+ class Mark < Struct.new(:index, :line, :column)
34
+ end
35
+
36
+ # The handler on which events will be called
37
+ attr_accessor :handler
38
+
39
+ # Set the encoding for this parser to +encoding+
40
+ attr_writer :external_encoding
41
+
42
+ ###
43
+ # Creates a new Psych::Parser instance with +handler+. YAML events will
44
+ # be called on +handler+. See Psych::Parser for more details.
45
+
46
+ def initialize handler = Handler.new
47
+ @handler = handler
48
+ @external_encoding = ANY
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,149 @@
1
+ require 'strscan'
2
+
3
+ module Psych
4
+ ###
5
+ # Scan scalars for built in types
6
+ class ScalarScanner
7
+ # Taken from http://yaml.org/type/timestamp.html
8
+ TIME = /^-?\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d\d:\d\d(?:\.\d*)?(?:\s*(?:Z|[-+]\d{1,2}:?(?:\d\d)?))?$/
9
+
10
+ # Taken from http://yaml.org/type/float.html
11
+ FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10)
12
+ |[-+]?[0-9][0-9_,]*(:[0-5]?[0-9])+\.[0-9_]*(?# base 60)
13
+ |[-+]?\.(inf|Inf|INF)(?# infinity)
14
+ |\.(nan|NaN|NAN)(?# not a number))$/x
15
+
16
+ # Taken from http://yaml.org/type/int.html
17
+ INTEGER = /^(?:[-+]?0b[0-1_]+ (?# base 2)
18
+ |[-+]?0[0-7_]+ (?# base 8)
19
+ |[-+]?(?:0|[1-9][0-9_]*) (?# base 10)
20
+ |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x
21
+
22
+ attr_reader :class_loader
23
+
24
+ # Create a new scanner
25
+ def initialize class_loader
26
+ @string_cache = {}
27
+ @symbol_cache = {}
28
+ @class_loader = class_loader
29
+ end
30
+
31
+ # Tokenize +string+ returning the Ruby object
32
+ def tokenize string
33
+ return nil if string.empty?
34
+ return string if @string_cache.key?(string)
35
+ return @symbol_cache[string] if @symbol_cache.key?(string)
36
+
37
+ case string
38
+ # Check for a String type, being careful not to get caught by hash keys, hex values, and
39
+ # special floats (e.g., -.inf).
40
+ when /^[^\d\.:-]?[A-Za-z_\s!@#\$%\^&\*\(\)\{\}\<\>\|\/\\~;=]+/, /\n/
41
+ if string.length > 5
42
+ @string_cache[string] = true
43
+ return string
44
+ end
45
+
46
+ case string
47
+ when /^[^ytonf~]/i
48
+ @string_cache[string] = true
49
+ string
50
+ when '~', /^null$/i
51
+ nil
52
+ when /^(yes|true|on)$/i
53
+ true
54
+ when /^(no|false|off)$/i
55
+ false
56
+ else
57
+ @string_cache[string] = true
58
+ string
59
+ end
60
+ when TIME
61
+ begin
62
+ parse_time string
63
+ rescue ArgumentError
64
+ string
65
+ end
66
+ when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
67
+ require 'date'
68
+ begin
69
+ class_loader.date.strptime(string, '%Y-%m-%d')
70
+ rescue ArgumentError
71
+ string
72
+ end
73
+ when /^\.inf$/i
74
+ Float::INFINITY
75
+ when /^-\.inf$/i
76
+ -Float::INFINITY
77
+ when /^\.nan$/i
78
+ Float::NAN
79
+ when /^:./
80
+ if string =~ /^:(["'])(.*)\1/
81
+ @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, ''))
82
+ else
83
+ @symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, ''))
84
+ end
85
+ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
86
+ i = 0
87
+ string.split(':').each_with_index do |n,e|
88
+ i += (n.to_i * 60 ** (e - 2).abs)
89
+ end
90
+ i
91
+ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*$/
92
+ i = 0
93
+ string.split(':').each_with_index do |n,e|
94
+ i += (n.to_f * 60 ** (e - 2).abs)
95
+ end
96
+ i
97
+ when FLOAT
98
+ if string =~ /\A[-+]?\.\Z/
99
+ @string_cache[string] = true
100
+ string
101
+ else
102
+ Float(string.gsub(/[,_]|\.$/, ''))
103
+ end
104
+ else
105
+ int = parse_int string.gsub(/[,_]/, '')
106
+ return int if int
107
+
108
+ @string_cache[string] = true
109
+ string
110
+ end
111
+ end
112
+
113
+ ###
114
+ # Parse and return an int from +string+
115
+ def parse_int string
116
+ return unless INTEGER === string
117
+ Integer(string)
118
+ end
119
+
120
+ ###
121
+ # Parse and return a Time from +string+
122
+ def parse_time string
123
+ klass = class_loader.load 'Time'
124
+
125
+ date, time = *(string.split(/[ tT]/, 2))
126
+ (yy, m, dd) = date.match(/^(-?\d{4})-(\d{1,2})-(\d{1,2})/).captures.map { |x| x.to_i }
127
+ md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/)
128
+
129
+ (hh, mm, ss) = md[1].split(':').map { |x| x.to_i }
130
+ us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000
131
+
132
+ time = klass.utc(yy, m, dd, hh, mm, ss, us)
133
+
134
+ return time if 'Z' == md[3]
135
+ return klass.at(time.to_i, us) unless md[3]
136
+
137
+ tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) }
138
+ offset = tz.first * 3600
139
+
140
+ if offset < 0
141
+ offset -= ((tz[1] || 0) * 60)
142
+ else
143
+ offset += ((tz[1] || 0) * 60)
144
+ end
145
+
146
+ klass.at((time - offset).to_i, us)
147
+ end
148
+ end
149
+ end
data/lib/psych/set.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Psych
2
+ class Set < ::Hash
3
+ end
4
+ end
@@ -0,0 +1,37 @@
1
+ module Psych
2
+ ###
3
+ # Psych::Stream is a streaming YAML emitter. It will not buffer your YAML,
4
+ # but send it straight to an IO.
5
+ #
6
+ # Here is an example use:
7
+ #
8
+ # stream = Psych::Stream.new($stdout)
9
+ # stream.start
10
+ # stream.push({:foo => 'bar'})
11
+ # stream.finish
12
+ #
13
+ # YAML will be immediately emitted to $stdout with no buffering.
14
+ #
15
+ # Psych::Stream#start will take a block and ensure that Psych::Stream#finish
16
+ # is called, so you can do this form:
17
+ #
18
+ # stream = Psych::Stream.new($stdout)
19
+ # stream.start do |em|
20
+ # em.push(:foo => 'bar')
21
+ # end
22
+ #
23
+ class Stream < Psych::Visitors::YAMLTree
24
+ class Emitter < Psych::Emitter # :nodoc:
25
+ def end_document implicit_end = !streaming?
26
+ super
27
+ end
28
+
29
+ def streaming?
30
+ true
31
+ end
32
+ end
33
+
34
+ include Psych::Streaming
35
+ extend Psych::Streaming::ClassMethods
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ module Psych
2
+ module Streaming
3
+ module ClassMethods
4
+ ###
5
+ # Create a new streaming emitter. Emitter will print to +io+. See
6
+ # Psych::Stream for an example.
7
+ def new io
8
+ emitter = const_get(:Emitter).new(io)
9
+ class_loader = ClassLoader.new
10
+ ss = ScalarScanner.new class_loader
11
+ super(emitter, ss, {})
12
+ end
13
+ end
14
+
15
+ ###
16
+ # Start streaming using +encoding+
17
+ def start encoding = Nodes::Stream::UTF8
18
+ super.tap { yield self if block_given? }
19
+ ensure
20
+ finish if block_given?
21
+ end
22
+
23
+ private
24
+ def register target, obj
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ require 'psych/exception'
2
+
3
+ module Psych
4
+ class SyntaxError < Psych::Exception
5
+ attr_reader :file, :line, :column, :offset, :problem, :context
6
+
7
+ def initialize file, line, col, offset, problem, context
8
+ err = [problem, context].compact.join ' '
9
+ filename = file || '<unknown>'
10
+ message = "(%s): %s at line %d column %d" % [filename, err, line, col]
11
+
12
+ @file = file
13
+ @line = line
14
+ @column = col
15
+ @offset = offset
16
+ @problem = problem
17
+ @context = context
18
+ super(message)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,96 @@
1
+ require 'psych/handler'
2
+
3
+ module Psych
4
+ ###
5
+ # This class works in conjunction with Psych::Parser to build an in-memory
6
+ # parse tree that represents a YAML document.
7
+ #
8
+ # == Example
9
+ #
10
+ # parser = Psych::Parser.new Psych::TreeBuilder.new
11
+ # parser.parse('--- foo')
12
+ # tree = parser.handler.root
13
+ #
14
+ # See Psych::Handler for documentation on the event methods used in this
15
+ # class.
16
+ class TreeBuilder < Psych::Handler
17
+ # Returns the root node for the built tree
18
+ attr_reader :root
19
+
20
+ # Create a new TreeBuilder instance
21
+ def initialize
22
+ @stack = []
23
+ @last = nil
24
+ @root = nil
25
+ end
26
+
27
+ %w{
28
+ Sequence
29
+ Mapping
30
+ }.each do |node|
31
+ class_eval %{
32
+ def start_#{node.downcase}(anchor, tag, implicit, style)
33
+ n = Nodes::#{node}.new(anchor, tag, implicit, style)
34
+ @last.children << n
35
+ push n
36
+ end
37
+
38
+ def end_#{node.downcase}
39
+ pop
40
+ end
41
+ }
42
+ end
43
+
44
+ ###
45
+ # Handles start_document events with +version+, +tag_directives+,
46
+ # and +implicit+ styling.
47
+ #
48
+ # See Psych::Handler#start_document
49
+ def start_document version, tag_directives, implicit
50
+ n = Nodes::Document.new version, tag_directives, implicit
51
+ @last.children << n
52
+ push n
53
+ end
54
+
55
+ ###
56
+ # Handles end_document events with +version+, +tag_directives+,
57
+ # and +implicit+ styling.
58
+ #
59
+ # See Psych::Handler#start_document
60
+ def end_document implicit_end = !streaming?
61
+ @last.implicit_end = implicit_end
62
+ pop
63
+ end
64
+
65
+ def start_stream encoding
66
+ @root = Nodes::Stream.new(encoding)
67
+ push @root
68
+ end
69
+
70
+ def end_stream
71
+ pop
72
+ end
73
+
74
+ def scalar value, anchor, tag, plain, quoted, style
75
+ s = Nodes::Scalar.new(value,anchor,tag,plain,quoted,style)
76
+ @last.children << s
77
+ s
78
+ end
79
+
80
+ def alias anchor
81
+ @last.children << Nodes::Alias.new(anchor)
82
+ end
83
+
84
+ private
85
+ def push value
86
+ @stack.push value
87
+ @last = value
88
+ end
89
+
90
+ def pop
91
+ x = @stack.pop
92
+ @last = @stack.last
93
+ x
94
+ end
95
+ end
96
+ end