stomp_parser 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/stompede/stomp_parser.png?branch=master)](https://travis-ci.org/stompede/stomp_parser)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/stomp_parser.png)](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
|
+
}
|