stomp_parser 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.
- checksums.yaml +7 -0
- data/.gitignore +33 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Brewfile +2 -0
- data/Gemfile +11 -0
- data/MIT-LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +85 -0
- data/ext/java/stomp_parser/JavaParser.java.rl +179 -0
- data/ext/java/stomp_parser/JavaParserService.java +23 -0
- data/ext/stomp_parser/c_parser.c.rl +225 -0
- data/ext/stomp_parser/extconf.rb +15 -0
- data/lib/stomp_parser.rb +46 -0
- data/lib/stomp_parser/error.rb +18 -0
- data/lib/stomp_parser/frame.rb +133 -0
- data/lib/stomp_parser/ruby_parser.rb.rl +155 -0
- data/lib/stomp_parser/version.rb +3 -0
- data/parser_common.rl +25 -0
- data/spec/bench_helper.rb +67 -0
- data/spec/benchmarks/message_bench.rb +50 -0
- data/spec/benchmarks/parser_bench.rb +43 -0
- data/spec/profile.rb +27 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stomp_parser/c_parser_spec.rb +5 -0
- data/spec/stomp_parser/java_parser_spec.rb +5 -0
- data/spec/stomp_parser/message_spec.rb +50 -0
- data/spec/stomp_parser/ruby_parser_spec.rb +3 -0
- data/spec/stomp_parser_spec.rb +9 -0
- data/spec/support/shared_parser_examples.rb +268 -0
- data/stomp_parser.gemspec +28 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a04a9e96b78143103517610d026fe12603b2ebe
|
4
|
+
data.tar.gz: 660c92162ef6ad5cdc05f321ced1f36ce52999ab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f20b1607fbba7e6f3a6be6a736c0bf5a63e90ef4e2f76d086cc617a3f564166b0b61523bdecb7d5a5748bfef0f164d1ccb8fbddd0c744da9991bf9e51c3a1096
|
7
|
+
data.tar.gz: c4f7189b20e941cb84547c970d9a3d4b8c3e118d15539ef3387c8bbda60ebd6cc98f7a1efab3560f65f624cf778e9b30515bb435f564818ceb74258bd1c3d3c3
|
data/.gitignore
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
spec/profile
|
19
|
+
|
20
|
+
# C extension
|
21
|
+
*.o
|
22
|
+
*.bundle
|
23
|
+
mkmf.log
|
24
|
+
Makefile
|
25
|
+
tmp/
|
26
|
+
spec/spec.log
|
27
|
+
|
28
|
+
# Ragel
|
29
|
+
lib/stomp_parser/ruby_parser.rb
|
30
|
+
lib/stomp_parser/c_parser.rb
|
31
|
+
ext/stomp_parser/c_parser.c
|
32
|
+
ext/java/stomp_parser/JavaParser.java
|
33
|
+
lib/stomp_parser/java_parser.jar
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-r spec_helper
|
data/.travis.yml
ADDED
data/Brewfile
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kim Burgestrand, Jonas Nicklas
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# StompParser
|
2
|
+
|
3
|
+
[](https://travis-ci.org/stompede/stomp_parser)
|
4
|
+
[](http://badge.fury.io/rb/stomp_parser)
|
5
|
+
|
6
|
+
Fast STOMP parser and serializer for Ruby with native extensions in C for MRI
|
7
|
+
and Rubinius and in Java for JRuby, as well as a pure Ruby parser.
|
8
|
+
|
9
|
+
## Parsing
|
10
|
+
|
11
|
+
``` ruby
|
12
|
+
parser = StompParser::Parser.new
|
13
|
+
parser.parse(chunk) do |frame|
|
14
|
+
puts "We received #{frame.command} frame with #{frame.body} and headers #{frame.headers}!"
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
The chunks do not have to be complete STOMP frames. The callback will be called
|
19
|
+
whenever a complete frame has been parsed. Additionally, StompParser handles
|
20
|
+
escape sequences in headers and body encoding for you.
|
21
|
+
|
22
|
+
## Serializing
|
23
|
+
|
24
|
+
``` ruby
|
25
|
+
StompParser::Frame.new("SEND", { some: "header" }, "Hello").to_str
|
26
|
+
```
|
27
|
+
|
28
|
+
## Development
|
29
|
+
|
30
|
+
Development should be ez.
|
31
|
+
|
32
|
+
``` bash
|
33
|
+
git clone git@github.com:stompede/stomp_parser.git # git, http://git-scm.com/
|
34
|
+
cd stomp_parser
|
35
|
+
brew bundle # Homebrew, http://brew.sh/
|
36
|
+
bundle install # Bundler, http://bundler.io/
|
37
|
+
rake # compile state machine, run test suite
|
38
|
+
```
|
39
|
+
|
40
|
+
A few notes:
|
41
|
+
|
42
|
+
- Native dependencies are listed in the Brewfile.
|
43
|
+
- The stomp message parser is written in [Ragel](http://www.complang.org/ragel/), see [parser_common.rl](parser_common.rl).
|
44
|
+
- Graphviz is used to visualize the Ragel state machine.
|
45
|
+
- Most rake tasks will compile the state machine anew if needed.
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it on GitHub (<http://github.com/stompede/stomp_parser/fork>).
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
51
|
+
3. Follow the [Development](#development) instructions in this README.
|
52
|
+
4. Create your changes, please add tests.
|
53
|
+
5. Commit your changes (`git commit -am 'Add some feature'`).
|
54
|
+
6. Push to the branch (`git push origin my-new-feature`).
|
55
|
+
7. Create new pull request on GitHub.
|
56
|
+
|
57
|
+
## License
|
58
|
+
|
59
|
+
[MIT](MIT-LICENSE.txt)
|
data/Rakefile
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
def ragel(*args)
|
4
|
+
sh "ragel", "-I.", *args
|
5
|
+
end
|
6
|
+
|
7
|
+
# Build state machine before building gem.
|
8
|
+
task :build => :compile
|
9
|
+
|
10
|
+
file "parser_common.rl"
|
11
|
+
|
12
|
+
rule ".rb" => %w[.rb.rl parser_common.rl] do |t|
|
13
|
+
ragel "-F1", "-R", t.source, "-o", t.name
|
14
|
+
end
|
15
|
+
|
16
|
+
rule ".c" => %w[.c.rl parser_common.rl] do |t|
|
17
|
+
ragel "-G2", "-C", t.source, "-o", t.name
|
18
|
+
end
|
19
|
+
|
20
|
+
rule ".java" => %w[.java.rl parser_common.rl] do |t|
|
21
|
+
ragel "-T0", "-J", t.source, "-o", t.name
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "ragel machines"
|
25
|
+
task :compile => %w[lib/stomp_parser/ruby_parser.rb]
|
26
|
+
|
27
|
+
case RUBY_ENGINE
|
28
|
+
when "rbx", "ruby"
|
29
|
+
require "rake/extensiontask"
|
30
|
+
task :compile => %w[ext/stomp_parser/c_parser.c]
|
31
|
+
|
32
|
+
Rake::ExtensionTask.new do |ext|
|
33
|
+
ext.name = "c_parser"
|
34
|
+
ext.ext_dir = "ext/stomp_parser"
|
35
|
+
ext.lib_dir = "lib/stomp_parser"
|
36
|
+
end
|
37
|
+
when "jruby"
|
38
|
+
require "rake/javaextensiontask"
|
39
|
+
task :compile => %w[ext/java/stomp_parser/JavaParser.java]
|
40
|
+
|
41
|
+
Rake::JavaExtensionTask.new do |ext|
|
42
|
+
ext.name = "java_parser"
|
43
|
+
ext.lib_dir = "lib/stomp_parser"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "ragel machines"
|
48
|
+
task :clean do |t|
|
49
|
+
source_tasks = Rake::Task[:compile].prerequisite_tasks.grep(Rake::FileTask)
|
50
|
+
rm_f source_tasks.map(&:name)
|
51
|
+
end
|
52
|
+
|
53
|
+
namespace :ragel do
|
54
|
+
desc "Show stomp parser state machine as an image"
|
55
|
+
task :show => "lib/stomp_parser/ruby_parser.rb" do |t|
|
56
|
+
mkdir_p "tmp"
|
57
|
+
ragel "-V", "-p", t.prerequisite_tasks[0].source, "-o", "tmp/parser.dot"
|
58
|
+
sh "dot -Tpng -O tmp/parser.dot"
|
59
|
+
rm "tmp/parser.dot"
|
60
|
+
sh "open tmp/parser.dot.png"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Start a pry session with the gem loaded."
|
65
|
+
task :console => :compile do
|
66
|
+
exec "pry", "-rbundler/setup", "-rstomp_parser"
|
67
|
+
end
|
68
|
+
|
69
|
+
require "rspec/core/rake_task"
|
70
|
+
RSpec::Core::RakeTask.new(:spec)
|
71
|
+
task :spec => :compile
|
72
|
+
|
73
|
+
desc "Run all benchmarks."
|
74
|
+
task :bench => :compile do
|
75
|
+
sh "ruby", "-I.", *FileList["spec/benchmarks/**/*.rb"].flat_map { |x| ["-r", x] }, "-e", "''"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Run the profiler and show a gif, requires perftools.rb"
|
79
|
+
task :profile => :compile do
|
80
|
+
# CPUPROFILE_METHODS=0 CPUPROFILE_OBJECTS=0 CPUPROFILE_REALTIME=1
|
81
|
+
sh "CPUPROFILE_REALTIME=1 ruby spec/profile.rb"
|
82
|
+
sh "pprof.rb --text spec/profile/parser.profile"
|
83
|
+
end
|
84
|
+
|
85
|
+
task :default => :spec
|
@@ -0,0 +1,179 @@
|
|
1
|
+
package stomp_parser;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyModule;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.RubyFixnum;
|
8
|
+
import org.jruby.RubyString;
|
9
|
+
import org.jruby.RubyNumeric;
|
10
|
+
import org.jruby.RubyException;
|
11
|
+
import org.jruby.exceptions.RaiseException;
|
12
|
+
|
13
|
+
import org.jruby.runtime.ThreadContext;
|
14
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
15
|
+
import org.jruby.runtime.Block;
|
16
|
+
|
17
|
+
import org.jruby.anno.JRubyClass;
|
18
|
+
import org.jruby.anno.JRubyMethod;
|
19
|
+
|
20
|
+
%%{
|
21
|
+
machine frame;
|
22
|
+
alphtype byte;
|
23
|
+
|
24
|
+
action mark {
|
25
|
+
mark = p;
|
26
|
+
}
|
27
|
+
|
28
|
+
action mark_frame {
|
29
|
+
mark_frame = context.runtime.getClassFromPath("StompParser::Frame").callMethod("new", context.nil, context.nil);
|
30
|
+
mark_frame_size = 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
action write_command {
|
34
|
+
mark_frame.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
|
35
|
+
mark = -1;
|
36
|
+
}
|
37
|
+
|
38
|
+
action mark_key {
|
39
|
+
mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
|
40
|
+
mark = -1;
|
41
|
+
}
|
42
|
+
|
43
|
+
action write_header {
|
44
|
+
IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
|
45
|
+
mark_frame.callMethod(context, "write_header", args);
|
46
|
+
mark_key = null;
|
47
|
+
mark = -1;
|
48
|
+
}
|
49
|
+
|
50
|
+
action finish_headers {
|
51
|
+
IRubyObject content_length = mark_frame.callMethod(context, "content_length");
|
52
|
+
|
53
|
+
if ( ! content_length.isNil()) {
|
54
|
+
mark_content_length = RubyNumeric.num2int(content_length);
|
55
|
+
} else {
|
56
|
+
mark_content_length = -1;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
action write_body {
|
61
|
+
mark_frame.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
|
62
|
+
mark = -1;
|
63
|
+
}
|
64
|
+
|
65
|
+
action consume_null {
|
66
|
+
((mark_content_length != -1) && ((p - mark) < mark_content_length))
|
67
|
+
}
|
68
|
+
|
69
|
+
action consume_octet {
|
70
|
+
((mark_content_length == -1) || ((p - mark) < mark_content_length))
|
71
|
+
}
|
72
|
+
|
73
|
+
action check_frame_size {
|
74
|
+
mark_frame_size += 1;
|
75
|
+
if (mark_frame_size > maxFrameSize) {
|
76
|
+
RubyModule frameSizeExceeded = context.runtime.getClassFromPath("StompParser::FrameSizeExceeded");
|
77
|
+
RubyException error = (RubyException) frameSizeExceeded.callMethod("new");
|
78
|
+
throw new RaiseException(error);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
action finish_frame {
|
83
|
+
block.yield(context, mark_frame);
|
84
|
+
mark_frame = null;
|
85
|
+
}
|
86
|
+
|
87
|
+
include frame_common "parser_common.rl";
|
88
|
+
}%%
|
89
|
+
|
90
|
+
@JRubyClass(name="JavaParser", parent="Object")
|
91
|
+
public class JavaParser extends RubyObject {
|
92
|
+
%% write data noprefix;
|
93
|
+
|
94
|
+
private class State {
|
95
|
+
public int cs = JavaParser.start;
|
96
|
+
public byte[] chunk;
|
97
|
+
public int mark = -1;
|
98
|
+
public RubyString mark_key;
|
99
|
+
public IRubyObject mark_frame;
|
100
|
+
public int mark_frame_size = -1;
|
101
|
+
public int mark_content_length = -1;
|
102
|
+
}
|
103
|
+
|
104
|
+
private RubyException parseError;
|
105
|
+
private long maxFrameSize;
|
106
|
+
private State state;
|
107
|
+
|
108
|
+
public JavaParser(Ruby runtime, RubyClass klass) {
|
109
|
+
super(runtime, klass);
|
110
|
+
state = new State();
|
111
|
+
parseError = null;
|
112
|
+
}
|
113
|
+
|
114
|
+
@JRubyMethod
|
115
|
+
public IRubyObject initialize(ThreadContext context) {
|
116
|
+
RubyModule mStompParser = context.runtime.getClassFromPath("StompParser");
|
117
|
+
return initialize(context, mStompParser.callMethod("max_frame_size"));
|
118
|
+
}
|
119
|
+
|
120
|
+
@JRubyMethod(argTypes = {RubyFixnum.class})
|
121
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject maxFrameSize) {
|
122
|
+
this.maxFrameSize = ((RubyFixnum) maxFrameSize).getLongValue();
|
123
|
+
return context.nil;
|
124
|
+
}
|
125
|
+
|
126
|
+
@JRubyMethod(argTypes = {RubyString.class})
|
127
|
+
public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
|
128
|
+
if (parseError == null) {
|
129
|
+
int p;
|
130
|
+
byte data[] = null;
|
131
|
+
byte bytes[] = ((RubyString) chunk).getBytes();
|
132
|
+
|
133
|
+
if (state.chunk != null) {
|
134
|
+
p = state.chunk.length;
|
135
|
+
data = new byte[state.chunk.length + bytes.length];
|
136
|
+
System.arraycopy(state.chunk, 0, data, 0, state.chunk.length);
|
137
|
+
System.arraycopy(bytes, 0, data, state.chunk.length, bytes.length);
|
138
|
+
} else {
|
139
|
+
p = 0;
|
140
|
+
data = bytes;
|
141
|
+
}
|
142
|
+
|
143
|
+
int pe = data.length;
|
144
|
+
|
145
|
+
int cs = state.cs;
|
146
|
+
int mark = state.mark;
|
147
|
+
RubyString mark_key = state.mark_key;
|
148
|
+
IRubyObject mark_frame = state.mark_frame;
|
149
|
+
int mark_frame_size = state.mark_frame_size;
|
150
|
+
int mark_content_length = state.mark_content_length;
|
151
|
+
|
152
|
+
%% write exec;
|
153
|
+
|
154
|
+
if (mark != -1) {
|
155
|
+
state.chunk = data;
|
156
|
+
} else {
|
157
|
+
state.chunk = null;
|
158
|
+
}
|
159
|
+
|
160
|
+
state.cs = cs;
|
161
|
+
state.mark = mark;
|
162
|
+
state.mark_key = mark_key;
|
163
|
+
state.mark_frame = mark_frame;
|
164
|
+
state.mark_frame_size = mark_frame_size;
|
165
|
+
state.mark_content_length = mark_content_length;
|
166
|
+
|
167
|
+
if (cs == error) {
|
168
|
+
IRubyObject args[] = { RubyString.newString(context.runtime, data), RubyFixnum.newFixnum(context.runtime, (long) p) };
|
169
|
+
parseError = (RubyException) context.runtime.getClassFromPath("StompParser").callMethod(context, "build_parse_error", args);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
if (parseError != null) {
|
174
|
+
throw new RaiseException(parseError);
|
175
|
+
}
|
176
|
+
|
177
|
+
return context.nil;
|
178
|
+
}
|
179
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
package stomp_parser;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyClass;
|
5
|
+
import org.jruby.RubyModule;
|
6
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
7
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
8
|
+
import org.jruby.runtime.ObjectAllocator;
|
9
|
+
|
10
|
+
public class JavaParserService implements BasicLibraryService {
|
11
|
+
public boolean basicLoad(Ruby ruby) {
|
12
|
+
RubyModule mStomp = ruby.getClassFromPath("StompParser");
|
13
|
+
RubyClass cJavaParser = ruby.defineClassUnder("JavaParser", ruby.getObject(), JAVA_PARSER_ALLOCATOR, mStomp);
|
14
|
+
cJavaParser.defineAnnotatedMethods(JavaParser.class);
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
|
18
|
+
private static final ObjectAllocator JAVA_PARSER_ALLOCATOR = new ObjectAllocator() {
|
19
|
+
public IRubyObject allocate(Ruby ruby, RubyClass klass) {
|
20
|
+
return new JavaParser(ruby, klass);
|
21
|
+
}
|
22
|
+
};
|
23
|
+
}
|
@@ -0,0 +1,225 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#if DEBUG_H
|
4
|
+
# define DEBUG(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); } while(0)
|
5
|
+
#else
|
6
|
+
# define DEBUG(...)
|
7
|
+
#endif
|
8
|
+
|
9
|
+
#define UNUSED(x) (void)(x)
|
10
|
+
#define MARK_LEN (p - mark)
|
11
|
+
#define MARK_STR_NEW() rb_str_new(mark, MARK_LEN)
|
12
|
+
|
13
|
+
#define true 1
|
14
|
+
#define false 0
|
15
|
+
|
16
|
+
typedef struct {
|
17
|
+
VALUE error;
|
18
|
+
long max_frame_size;
|
19
|
+
|
20
|
+
VALUE chunk;
|
21
|
+
const char *p;
|
22
|
+
int cs;
|
23
|
+
const char *mark;
|
24
|
+
VALUE mark_key;
|
25
|
+
VALUE mark_frame;
|
26
|
+
long mark_frame_size;
|
27
|
+
long mark_content_length;
|
28
|
+
} parser_state_t;
|
29
|
+
|
30
|
+
VALUE mStompParser = Qnil;
|
31
|
+
VALUE cFrame = Qnil;
|
32
|
+
VALUE eFrameSizeExceeded = Qnil;
|
33
|
+
ID g_new;
|
34
|
+
ID g_write_command;
|
35
|
+
ID g_write_header;
|
36
|
+
ID g_write_body;
|
37
|
+
ID g_content_length;
|
38
|
+
ID g_build_parse_error;
|
39
|
+
ID g_max_frame_size;
|
40
|
+
|
41
|
+
%%{
|
42
|
+
machine frame;
|
43
|
+
|
44
|
+
action mark {
|
45
|
+
mark = p;
|
46
|
+
}
|
47
|
+
|
48
|
+
action mark_frame {
|
49
|
+
mark_frame = rb_funcall(cFrame, g_new, 2, Qnil, Qnil);
|
50
|
+
mark_frame_size = 0;
|
51
|
+
}
|
52
|
+
|
53
|
+
action write_command {
|
54
|
+
rb_funcall(mark_frame, g_write_command, 1, MARK_STR_NEW());
|
55
|
+
mark = NULL;
|
56
|
+
}
|
57
|
+
|
58
|
+
action mark_key {
|
59
|
+
mark_key = MARK_STR_NEW();
|
60
|
+
mark = NULL;
|
61
|
+
}
|
62
|
+
|
63
|
+
action write_header {
|
64
|
+
rb_funcall(mark_frame, g_write_header, 2, mark_key, MARK_STR_NEW());
|
65
|
+
mark_key = Qnil;
|
66
|
+
mark = NULL;
|
67
|
+
}
|
68
|
+
|
69
|
+
action finish_headers {
|
70
|
+
VALUE length = rb_funcall(mark_frame, g_content_length, 0);
|
71
|
+
if ( ! NIL_P(length)) {
|
72
|
+
mark_content_length = NUM2LONG(length);
|
73
|
+
} else {
|
74
|
+
mark_content_length = -1;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
action write_body {
|
79
|
+
rb_funcall(mark_frame, g_write_body, 1, MARK_STR_NEW());
|
80
|
+
mark = NULL;
|
81
|
+
}
|
82
|
+
|
83
|
+
action consume_null {
|
84
|
+
((mark_content_length != -1) && (MARK_LEN < mark_content_length))
|
85
|
+
}
|
86
|
+
|
87
|
+
action consume_octet {
|
88
|
+
((mark_content_length == -1) || (MARK_LEN < mark_content_length))
|
89
|
+
}
|
90
|
+
|
91
|
+
action check_frame_size {
|
92
|
+
mark_frame_size += 1;
|
93
|
+
if (mark_frame_size > max_frame_size) {
|
94
|
+
rb_raise(eFrameSizeExceeded, "");
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
action finish_frame {
|
99
|
+
rb_yield(mark_frame);
|
100
|
+
mark_frame = Qnil;
|
101
|
+
}
|
102
|
+
|
103
|
+
include frame_common "parser_common.rl";
|
104
|
+
|
105
|
+
write data noprefix;
|
106
|
+
}%%
|
107
|
+
|
108
|
+
static void parser_free(parser_state_t *state) {
|
109
|
+
// TODO: free memory inside struct!
|
110
|
+
xfree(state);
|
111
|
+
}
|
112
|
+
|
113
|
+
static void parser_mark(parser_state_t *state) {
|
114
|
+
rb_gc_mark(state->error);
|
115
|
+
rb_gc_mark(state->mark_key);
|
116
|
+
rb_gc_mark(state->mark_frame);
|
117
|
+
rb_gc_mark(state->chunk);
|
118
|
+
}
|
119
|
+
|
120
|
+
static VALUE parser_alloc(VALUE klass) {
|
121
|
+
parser_state_t *state = ALLOC(parser_state_t);
|
122
|
+
return Data_Wrap_Struct(klass, parser_mark, parser_free, state);
|
123
|
+
}
|
124
|
+
|
125
|
+
static VALUE parser_initialize(int argc, VALUE *argv, VALUE self) {
|
126
|
+
parser_state_t *state;
|
127
|
+
Data_Get_Struct(self, parser_state_t, state);
|
128
|
+
|
129
|
+
VALUE max_frame_size;
|
130
|
+
rb_scan_args(argc, argv, "01", &max_frame_size);
|
131
|
+
|
132
|
+
if (max_frame_size == Qnil) {
|
133
|
+
max_frame_size = rb_funcall(mStompParser, g_max_frame_size, 0);
|
134
|
+
}
|
135
|
+
|
136
|
+
state->error = Qnil;
|
137
|
+
state->max_frame_size = FIX2LONG(max_frame_size);
|
138
|
+
state->chunk = Qnil;
|
139
|
+
state->cs = start;
|
140
|
+
state->mark = NULL;
|
141
|
+
state->mark_key = Qnil;
|
142
|
+
state->mark_frame = Qnil;
|
143
|
+
state->mark_frame_size = 0;
|
144
|
+
state->mark_content_length = 0;
|
145
|
+
|
146
|
+
return self;
|
147
|
+
}
|
148
|
+
|
149
|
+
static VALUE parser_parse(VALUE self, VALUE new_chunk) {
|
150
|
+
parser_state_t *state;
|
151
|
+
Data_Get_Struct(self, parser_state_t, state);
|
152
|
+
|
153
|
+
if (NIL_P(state->error)) {
|
154
|
+
VALUE chunk = Qnil;
|
155
|
+
const char *p = NULL;
|
156
|
+
const char *mark = NULL;
|
157
|
+
|
158
|
+
if ( ! NIL_P(state->chunk)) {
|
159
|
+
long offset = RSTRING_LEN(state->chunk);
|
160
|
+
long mark_offset = state->mark - RSTRING_PTR(state->chunk);
|
161
|
+
|
162
|
+
chunk = rb_str_append(state->chunk, new_chunk);
|
163
|
+
p = RSTRING_PTR(chunk) + offset;
|
164
|
+
mark = RSTRING_PTR(chunk) + mark_offset;
|
165
|
+
} else {
|
166
|
+
chunk = new_chunk;
|
167
|
+
p = RSTRING_PTR(chunk);
|
168
|
+
}
|
169
|
+
|
170
|
+
const char *pe = RSTRING_END(chunk);
|
171
|
+
long max_frame_size = state->max_frame_size;
|
172
|
+
|
173
|
+
int cs = state->cs;
|
174
|
+
VALUE mark_key = state->mark_key;
|
175
|
+
VALUE mark_frame = state->mark_frame;
|
176
|
+
long mark_frame_size = state->mark_frame_size;
|
177
|
+
long mark_content_length = state->mark_content_length;
|
178
|
+
|
179
|
+
%% write exec;
|
180
|
+
|
181
|
+
if (mark != NULL) {
|
182
|
+
state->chunk = chunk;
|
183
|
+
} else {
|
184
|
+
state->chunk = Qnil;
|
185
|
+
}
|
186
|
+
|
187
|
+
state->cs = cs;
|
188
|
+
state->mark = mark;
|
189
|
+
state->mark_key = mark_key;
|
190
|
+
state->mark_frame = mark_frame;
|
191
|
+
state->mark_frame_size = mark_frame_size;
|
192
|
+
state->mark_content_length = mark_content_length;
|
193
|
+
|
194
|
+
if (cs == error) {
|
195
|
+
long index = p - RSTRING_PTR(chunk);
|
196
|
+
state->error = rb_funcall(mStompParser, g_build_parse_error, 2, chunk, LONG2NUM(index));
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
if ( ! NIL_P(state->error)) {
|
201
|
+
rb_exc_raise(state->error);
|
202
|
+
}
|
203
|
+
|
204
|
+
return Qnil;
|
205
|
+
}
|
206
|
+
|
207
|
+
void Init_c_parser(void) {
|
208
|
+
mStompParser = rb_const_get(rb_cObject, rb_intern("StompParser"));
|
209
|
+
cFrame = rb_const_get(mStompParser, rb_intern("Frame"));
|
210
|
+
eFrameSizeExceeded = rb_const_get(mStompParser, rb_intern("FrameSizeExceeded"));
|
211
|
+
|
212
|
+
g_new = rb_intern("new");
|
213
|
+
g_write_command = rb_intern("write_command");
|
214
|
+
g_write_header = rb_intern("write_header");
|
215
|
+
g_write_body = rb_intern("write_body");
|
216
|
+
g_content_length = rb_intern("content_length");
|
217
|
+
g_build_parse_error = rb_intern("build_parse_error");
|
218
|
+
g_max_frame_size = rb_intern("max_frame_size");
|
219
|
+
|
220
|
+
VALUE cParser = rb_define_class_under(mStompParser, "CParser", rb_cObject);
|
221
|
+
rb_define_alloc_func(cParser, parser_alloc);
|
222
|
+
|
223
|
+
rb_define_method(cParser, "initialize", parser_initialize, -1);
|
224
|
+
rb_define_method(cParser, "parse", parser_parse, 1);
|
225
|
+
}
|