Created
March 26, 2011 20:53
-
-
Save frsyuki/888627 to your computer and use it in GitHub Desktop.
MessagePack IDL言語仕様案とパーサの実装
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// example1.msgspec | |
namespace com.example | |
namespace cpp example | |
namespace ruby Example | |
message BasicTypeExample { | |
1: int8 f1 | |
2: int16 f2 | |
3: int32 f3 | |
4: int64 f4 | |
5: uint8 f5 | |
6: uint16 f6 | |
7: uint32 f7 | |
8: uint64 f8 | |
9: float f9 | |
10: double f10 | |
11: bool f11 | |
12: raw f12 | |
13: string f13 | |
14: date f14 | |
} | |
message ContainerTypeExample { | |
1: list<string> f1 | |
2: map<string,string> f2 | |
3: set<string> f3 | |
} | |
message OptionalExample { | |
1: string f1 // required non-nullable | |
2: required string f2 // required non-nullable | |
3: optional string f3 // optional non-nullable | |
4: int32 f4 // required non-nullable | |
5: required int32 f5 // required non-nullable | |
6: optional int32 f6 // optional non-nullable | |
} | |
message NullableExample { | |
1: string? f1 // required nullable | |
2: required string? f2 // required nullable | |
3: optional string? f3 // optional nullable | |
4: int32? f4 // required nullable | |
5: required int32? f5 // required nullable | |
6: optional int32? f6 // optional nullable | |
} | |
message EmptyExample { | |
} | |
message DefaultExample { | |
1: int32 f1 = 1212 | |
2: string f2 = "test" | |
//3: list<int16> f3 = [1,2,3] | |
//4: map<string,int16> f4 = {"a":1, "b":10} | |
} | |
const int32 CONST1 = 11211 | |
const string CONST2 = "test" | |
//const list<int16> CONST3 = [1,2,3] | |
//const map<string,int16> CONST4 = {"a":1, "b":10} | |
message ConstExample { | |
1: int32 f1 = CONST1 | |
2: string f2 = CONST2 | |
//3: list<int16> f2 = CONST3 | |
//4: map<string,int16> f4 = CONST4 | |
} | |
message BuiltInConstExample { | |
1: int8 f1 = INT8_MAX | |
2: int16 f2 = INT16_MAX | |
3: int32 f3 = INT32_MAX | |
4: int64 f4 = INT64_MAX | |
5: bool f5 = true | |
6: bool f6 = false | |
} | |
enum EnumExample { | |
1: RED | |
2: GREEN | |
3: BLUE | |
} | |
typedef map<string,string> PropertyMap; | |
message TypedefExampel { | |
1: PropertyMap f1 | |
} | |
typedef<V> map<string,V> GenericStringMap; | |
message<V> GenericExample { | |
1: V f1 | |
2: list<V> f2 | |
3: GenericStringMap<V> f3 | |
} | |
typedef GenericExample<string> GenericExampleSpecific1 | |
typedef GenericExample<bool> GenericExampleSpecific2 | |
message TypeSpecExample { | |
1: list<string> f1 | |
} | |
typespec cpp TypeSpecExample.f1 std::vector<std::string> | |
typespec cpp PropertyMap std::tr1::unordered_map<std::string,std::string> | |
typespec<V> cpp GenericStringMap<V> std::unordered_map<string,V> | |
exception BasicExceptionExample { | |
1: string message | |
2: int32 code | |
} | |
exception SuperExceptionExample { | |
1: string message | |
2: int32 code | |
/* | |
exception SubExceptionExample1 { | |
3: string key | |
4: raw value | |
} | |
exception SubExceptionExample2 { | |
3: int32 flags | |
exception SubSubExceptionExample { | |
4: string value | |
} | |
} | |
*/ | |
} | |
exception SubExceptionExample3 < SuperExceptionExample { | |
3: raw key | |
} | |
service BasicServiceExample { | |
string func1(1: string key, 2: string value) | |
string func2() | |
string? func3(1: string key, 2: string value) | |
string? func4() | |
void func5(1: string key, 2: string value) | |
} | |
service OptionalServiceExample { | |
string func1(1: string key, 2: string value) | |
string func2(1: string key, 2: optional string value) | |
string func3(1: string? key, 2: optional string? value) | |
string? func4(1: string key, 2: string value) | |
} | |
service ExceptionServiceExample { | |
void func1() throws SubExceptionExample2, SubExceptionExample3 | |
void func2() throws BasicExceptionExample | |
} | |
service SubServiceExample < BasicExceptionExample { | |
! string func1(1: string key, 2: string value) // override | |
- string func1(1: string key, 2: string value) // delete | |
+ string func6() // add | |
string func7() // add | |
} | |
// バージョニング案1(関数ごとのバージョン) | |
/* | |
service VersionServiceExample { | |
void func1(1: string key) // version 0 | |
void func1:1(1: string key, 2: optional string? value) | |
uint32? func1:2(1: string key, 2: optional string? value) | |
void func2:0(1: string key) | |
string func3() | |
string func3:1(1: optional int32 flags) | |
// func1 // func1:2 | |
// func1:0 // func1:0 | |
// func1:1 // func1:1 | |
// func1:2 // func1:2 | |
// func1:3 // エラー (VersionMismatch) | |
// func2 // func2:0 | |
// func2:0 // func2:0 | |
// func2:1 // エラー (VersionMismatch) | |
// func2:2 // エラー (VersionMismatch) | |
// func2:3 // エラー (VersionMismatch) | |
// func3 // func3:1 | |
// func3:0 // func3:0 | |
// func3:1 // func3:1 | |
// func3:2 // エラー (VersionMismatch) | |
// func3:3 // エラー (VersionMismatch) | |
} | |
*/ | |
// バージョニング案2(関数セットごとのバージョン) | |
service VersionServiceExample { | |
void func1(1: string key) | |
void func2(1: string key) | |
1: | |
! void func1(1: string key, 2: optional string? value) | |
+ string func3() | |
2: | |
! uint32? func1(1: string key, 2: optional string? value) | |
- void func2(1: string key) | |
! string func3(1: optional int32 flags) | |
// func1 // func1:2 | |
// func1:0 // func1:0 | |
// func1:1 // func1:1 | |
// func1:2 // func1:2 | |
// func1:3 // エラー (VersionMismatch) | |
// func2 // func2:0 | |
// func2:0 // func2:0 | |
// func2:1 // func2:0 | |
// func2:2 // エラー (VersionMismatch) | |
// func2:3 // エラー (VersionMismatch) | |
// func3 // func3:2 | |
// func3:0 // エラー (NoMethodFound) | |
// func3:1 // func3:2 | |
// func3:2 // func3:2 | |
// func3:3 // エラー (VersionMismatch) | |
} | |
include memo2.msgspec | |
server MyApplication { | |
OptionalServiceExample scope1 default | |
// func1 | |
// func2 | |
// func1:scope1 | |
// func2:scope1 | |
ExceptionServiceExample scope2 | |
// func1:scope2 | |
// func2:scope2 | |
VersionServiceExample scope3 | |
// func1:scope3 | |
// func2:scope3 | |
// func3:scope3 | |
// func1:scope3:1 | |
// func2:scope3:1 | |
// func3:scope3:1 | |
// ... | |
} | |
// example1-impl.msgspec | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
message IncludeTest { | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'parslet' | |
require 'parslet/convenience' | |
require 'msgspec_visitor' | |
require 'msgspec_parser' | |
require 'msgspec_processor' | |
require 'pp' | |
processor = MessageSpec::Processor.new | |
begin | |
processor.parse_file('memo.msgspec') | |
rescue MessageSpec::MessageSpecError => e | |
puts e | |
end | |
pp processor.ast | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module MessageSpec | |
class ParsletParser < Parslet::Parser | |
class << self | |
def sequence(name, separator, element, min=0) | |
if min == 0 | |
eval %[rule(:#{name.to_s.dump}) { | |
(#{element}.as(:sequence_x) >> (#{separator} >> #{element}.as(:sequence_xs)).repeat).maybe.as(:sequence) | |
}] | |
else | |
eval %[rule(:#{name.to_s.dump}) { | |
(#{element}.as(:sequence_x) >> (#{separator} >> #{element}.as(:sequence_xs)).repeat(#{min-1})).as(:sequence) | |
}] | |
end | |
end | |
def keyword(string, name="k_"+string) | |
rule(name.to_sym) { | |
space? >> str(string) >> boundary | |
} | |
end | |
def separator(char, name) | |
rule(name.to_sym) { | |
space? >> str(char) | |
} | |
end | |
end | |
root :expression | |
rule(:expression) { | |
space? >> document >> space? | |
} | |
rule(:document) { | |
(include_ | definition).repeat.as(:document) | |
} | |
rule(:include_) { | |
k_include >> | |
path.as(:include) >> | |
eol | |
} | |
rule(:definition) { | |
namespace | | |
message | | |
enum | | |
exception | | |
const | | |
typedef | | |
typespec | | |
service | | |
server | |
} | |
rule(:namespace) { | |
k_namespace >> ( | |
lang_name.as(:namespace_lang) >> namespace_name.as(:namespace_name) | | |
namespace_name.as(:namespace_name) | |
) >> eol | |
} | |
rule(:message) { | |
k_message >> | |
type_param_decl.maybe.as(:type_param_decl) >> | |
class_name.as(:message_name) >> | |
lt_extend_class.maybe.as(:super_class) >> | |
k_lwing >> | |
field.repeat.as(:message_body) >> | |
k_rwing | |
} | |
rule(:exception) { | |
k_exception >> | |
type_param_decl.maybe.as(:type_param_decl) >> | |
class_name.as(:exception_name) >> | |
lt_extend_class.maybe.as(:super_class) >> | |
k_lwing >> | |
field.repeat.as(:exception_body) >> # TODO nested exception? | |
k_rwing | |
} | |
rule(:field) { | |
field_element >> eol | |
} | |
rule(:field_element) { | |
field_id.as(:field_id) >> | |
field_modifier.maybe.as(:field_modifier) >> | |
field_type.as(:field_type) >> | |
field_name.as(:field_name) >> | |
eq_default_value.maybe.as(:field_default) | |
} | |
rule(:field_id) { | |
# terminal | |
space? >> | |
(str('0') | (match('[1-9]') >> match('[0-9]').repeat)).as(:val_int) >> | |
str(':') >> str(':').absent? | |
} | |
rule(:field_modifier) { | |
k_optional | k_required | |
} | |
rule(:eq_default_value) { | |
k_equal >> literal | |
} | |
rule(:enum) { | |
k_enum >> | |
class_name.as(:enum_name) >> | |
k_lwing >> | |
enum_field.repeat.as(:enum_body) >> | |
k_rwing | |
} | |
rule(:enum_field) { | |
enum_field_element >> eol | |
} | |
rule(:enum_field_element) { | |
field_id.as(:enum_field_id) >> field_name.as(:enum_field_name) | |
} | |
rule(:typedef) { | |
k_typedef >> | |
type_param_decl.maybe.as(:type_param_decl) >> | |
generic_type.as(:typedef_type) >> | |
generic_type.as(:typedef_name) >> | |
eol | |
} | |
rule(:const) { | |
k_const >> | |
field_type.as(:const_type) >> | |
const_name.as(:const_name) >> | |
k_equal >> literal.as(:const_value) >> | |
eol | |
} | |
rule(:typespec) { | |
k_typespec >> | |
type_param_decl.maybe.as(:type_param_decl) >> | |
lang_name.as(:typespec_lang) >> ( | |
generic_type.as(:typespec_class) >> str('.') >> field_name.as(:typespec_field) | | |
generic_type.as(:typespec_type) | |
) >> lang_type.as(:typespec_spec) >> eol | |
} | |
rule(:lang_type) { | |
space? >> lang_type_lexer.as(:lang_type_tokens) | |
} | |
rule(:lang_type_lexer) { | |
(lang_type_word.as(:lang_type_token) | lang_type_separator.as(:lang_type_token)).repeat(1) | |
} | |
rule(:lang_type_word) { | |
match('[a-zA-Z0-9_\-]').repeat(1) | |
} | |
rule(:lang_type_separator) { | |
match('[\<\>\[\]\:\.\,]').repeat(1) | |
} | |
rule(:service) { | |
k_service >> | |
service_name.as(:service_name) >> | |
#type_param_decl.maybe.as(:type_param_decl) >> | |
lt_extend_class.maybe.as(:super_class) >> | |
k_lwing >> | |
service_description.as(:service_versions) >> | |
k_rwing | |
} | |
rule(:service_description) { | |
(func | version_label).repeat.as(:service_description) | |
} | |
rule(:version_label) { | |
# terminal | |
field_id | |
} | |
rule(:func) { | |
func_modifier.maybe.as(:func_modifier) >> | |
return_type.as(:return_type) >> | |
func_name.as(:func_name) >> | |
k_lparen >> | |
func_args.as(:func_args) >> | |
k_rparen >> | |
throws_classes.maybe.as(:func_throws) >> | |
eol | |
} | |
rule(:func_modifier) { | |
k_bang.as(:val_override) | | |
k_minus.as(:val_remove) | | |
k_plus.as(:val_add) | |
} | |
sequence :func_args_seq, :k_comma, :field_element | |
rule(:func_args) { | |
func_args_seq | |
} | |
sequence :throws_classes_seq, :k_comma, :generic_type, 1 | |
rule(:throws_classes) { | |
k_throws >> throws_classes_seq | |
} | |
rule(:server) { | |
k_server >> | |
service_name.as(:server_name) >> | |
k_lwing >> | |
scope.repeat.as(:server_body) >> | |
k_rwing | |
} | |
rule(:scope) { | |
generic_type.as(:scope_type) >> | |
field_name.as(:scope_name) >> | |
k_default.maybe.as(:scope_default) >> | |
eol | |
} | |
rule(:lt_extend_class) { | |
k_lpoint >> generic_type | |
} | |
rule(:field_type) { | |
generic_type.as(:field_type) >> k_question.maybe.as(:field_type_maybe) | |
} | |
rule(:return_type) { | |
field_type | |
} | |
rule(:generic_type) { | |
class_name.as(:generic_type) >> type_param.maybe.as(:type_params) | |
} | |
sequence :type_param_seq, :k_comma, :generic_type, 1 | |
rule(:type_param) { | |
k_lpoint >> type_param_seq >> k_rpoint | |
} | |
sequence :type_param_decl_seq, :k_comma, :class_name, 1 | |
rule(:type_param_decl) { | |
k_lpoint >> type_param_decl_seq >> k_rpoint | |
} | |
rule(:literal) { | |
literal_nil | literal_bool | literal_int | literal_float | literal_str | literal_list | literal_map | literal_const | |
} | |
rule(:literal_nil) { | |
k_nil.as(:literal_nil) | |
} | |
rule(:literal_bool) { | |
k_true.as(:literal_true) | k_false.as(:literal_false) | |
} | |
rule(:literal_const) { | |
const_name.as(:literal_const) | |
} | |
rule(:literal_int) { | |
space? >> ( | |
(str('-') | str('+')).maybe >> | |
(str('0') | (match('[1-9]') >> match('[0-9]').repeat)) | |
).as(:literal_int) >> | |
boundary | |
} | |
rule(:literal_float) { | |
space? >> ( | |
(str('0') | (match('[1-9]') >> match('[0-9]').repeat)) >> str('.') >> match('[0-9]').repeat(1) | |
).as(:literal_float) >> | |
boundary | |
} | |
rule(:literal_str_dq) { | |
space? >> str('"') >> ( | |
(str('\\') >> any | str('"').absent? >> any ).repeat | |
).as(:literal_str_dq) >> | |
str('"') | |
} | |
rule(:literal_str_sq) { | |
space? >> str("'") >> ( | |
(str('\\') >> any | str("'").absent? >> any ).repeat | |
).as(:literal_str_sq) >> | |
str("'") | |
} | |
rule(:literal_str) { | |
(literal_str_dq | literal_str_sq).repeat(1).as(:literal_str_seq) | |
} | |
sequence :literal_list_seq, :k_comma, :literal | |
rule(:literal_list) { | |
space? >> k_lbracket >> | |
literal_list_seq.as(:literal_list) >> | |
k_rbracket | |
} | |
sequence :literal_map_seq, :k_comma, :literal_map_pair | |
rule(:literal_map) { | |
space? >> k_lwing >> | |
literal_map_seq.as(:literal_map) >> | |
k_rwing | |
} | |
rule(:literal_map_pair) { | |
literal.as(:literal_map_key) >> k_colon >> literal.as(:literal_map_value) | |
} | |
rule(:path) { | |
# TODO path | |
space? >> match('[a-zA-Z0-9_\-\.\ ]').repeat(1).as(:path) >> boundary | |
} | |
rule(:lang_name) { | |
name | |
} | |
rule(:namespace_name) { | |
(name >> ((str('.') | str('::')) >> name).repeat).as(:sequence) | |
} | |
rule(:const_name) { | |
name | |
} | |
rule(:class_name) { | |
name | |
} | |
rule(:service_name) { | |
class_name | |
} | |
rule(:field_name) { | |
name | |
} | |
rule(:func_name) { | |
name | |
} | |
rule(:name) { | |
# terminal | |
space? >> (match('[a-zA-Z]') >> match('[a-zA-Z0-9_]').repeat).as(:name) >> boundary | |
} | |
rule(:inline_comment) { | |
str('/*') >> ( | |
inline_comment | # accepts nested comments | |
(str('*') >> str('/').absent?) | | |
(str('*').absent? >> any) | |
).repeat >> str('*/') | |
} | |
rule(:line_comment) { | |
(str('//') | str('#')) >> | |
(match('[\r\n]').absent? >> any).repeat >> | |
match('[\r\n]').repeat(1) | |
} | |
rule(:comment) { | |
inline_comment | line_comment | |
} | |
rule(:space) { | |
(match('[ \r\n\t]') | comment).repeat(1) | |
} | |
rule(:space?) { | |
space.maybe | |
} | |
rule(:eol) { | |
match('[ \t]').repeat >> | |
(match('[ \t;\r\n]') | line_comment).repeat(1) | |
} | |
rule(:boundary) { | |
match('[a-zA-Z0-9_]').absent? | |
} | |
keyword('include') | |
keyword('namespace') | |
keyword('message') | |
keyword('enum') | |
keyword('exception') | |
keyword('const') | |
keyword('typedef') | |
keyword('typespec') | |
keyword('service') | |
keyword('server') | |
keyword('optional') | |
keyword('required') | |
keyword('throws') | |
keyword('default') | |
keyword('nil') | |
keyword('true') | |
keyword('false') | |
keyword('void') | |
separator('*', :k_star) | |
separator('=', :k_equal) | |
separator('{', :k_lwing) | |
separator('}', :k_rwing) | |
separator('(', :k_lparen) | |
separator(')', :k_rparen) | |
separator(':', :k_colon) | |
separator(',', :k_comma) | |
separator(';', :k_semi) | |
separator('<', :k_lpoint) | |
separator('>', :k_rpoint) | |
separator('[', :k_lbracket) | |
separator(']', :k_rbracket) | |
separator('!', :k_bang) | |
separator('-', :k_minus) | |
separator('+', :k_plus) | |
separator('?', :k_question) | |
separator('.', :k_dot) | |
LINE_HEAD_FORMAT = " % 4d: " | |
LINE_HEAD_SIZE = (LINE_HEAD_FORMAT % 0).size | |
AFTER_BUFFER = 200 | |
AFTER_LINES = 3 | |
BEFORE_BUFFER = 200 | |
BEFORE_LINES = 4 | |
def print_error(error, fname, out=STDERR) | |
error_tree = self.root.error_tree | |
last = error_tree | |
until last.children.empty? | |
last = last.children.last | |
end | |
last_cause = last.parslet.instance_eval('@last_cause') | |
source = last_cause.source | |
row, col = source.line_and_column(last_cause.pos) | |
old_pos = source.pos | |
begin | |
source.pos = last_cause.pos - col + 1 | |
line, *after = source.read(AFTER_BUFFER).to_s.split("\n") | |
after = after[0,AFTER_LINES] | |
source.pos = last_cause.pos - col - BEFORE_BUFFER | |
before = source.read(BEFORE_BUFFER).to_s.split("\n") | |
before = before[-BEFORE_LINES,BEFORE_LINES] || [] | |
ensure | |
source.pos = old_pos | |
end | |
if m = /[ \t\r\n]*/.match(line) | |
heading = m[0] | |
else | |
heading = "" | |
end | |
out.puts "syntax error:" | |
( | |
error.to_s.split("\n") + | |
error_tree.to_s.split("\n") | |
).each {|ln| | |
out.puts " "+ln | |
} | |
out.puts "" | |
out.puts "around line #{row} column #{heading.size}-#{col}:" | |
out.puts "" | |
before.each_with_index {|ln,i| | |
l = row - after.size - 1 + i | |
out.print LINE_HEAD_FORMAT % l | |
out.puts ln | |
} | |
out.print LINE_HEAD_FORMAT % row | |
out.puts line | |
out.print " "*LINE_HEAD_SIZE | |
out.puts heading + '^'*(col - heading.size) | |
after.each_with_index {|ln,i| | |
l = row + 1 + i | |
out.print LINE_HEAD_FORMAT % l | |
out.puts ln | |
} | |
out.puts "" | |
out | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module MessageSpec | |
class MessageSpecError < StandardError | |
end | |
class SyntaxError < MessageSpecError | |
end | |
class IncludeError < MessageSpecError | |
end | |
class Processor | |
require 'stringio' | |
def initialize(search_paths = []) | |
@parslet = ParsletParser.new | |
@visitor = Visitor.new | |
@evaluator = nil | |
@search_paths = search_paths | |
@ast = [] | |
end | |
attr_reader :parslet | |
attr_reader :visitor | |
attr_reader :evaluator | |
attr_reader :ast | |
def parse(src, fname, dir) | |
begin | |
tree = @parslet.parse(src) | |
ast = @visitor.apply(tree) | |
rescue Parslet::ParseFailed => error | |
msg = @parslet.print_error(error, fname, StringIO.new).string | |
raise SyntaxError, msg | |
end | |
ast.each {|e| | |
if e.class == AST::Include | |
parse_include(e.path, dir, fname) | |
else | |
@ast << e | |
end | |
} | |
self | |
end | |
def parse_file(path) | |
parse(File.read(path), File.basename(path), File.dirname(path)) | |
end | |
def evaluate | |
@evaluator ||= Evaluator.new | |
@evaluator.evaluate(@ast) | |
self | |
end | |
def generate(specs, dir) | |
# real_type_table | |
end | |
protected | |
def parse_include(inc, dir, fname) | |
if dir | |
search_paths = @search_paths + [dir] | |
else | |
search_paths = @search_paths | |
end | |
search_paths.each {|dir| | |
real_path = File.join(dir, inc) | |
if File.file?(real_path) | |
return parse_file(real_path) | |
end | |
} | |
raise IncludeError, format_include_error(inc, fname) | |
end | |
def format_include_error(inc, fname) | |
[ | |
"#{fname}:", | |
" Can't include file #{inc}" | |
].join("\n") | |
end | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module MessageSpec | |
module AST | |
class Element | |
end | |
module ValueAssigned | |
attr_reader :value | |
end | |
class Document < Array | |
end | |
class Include | |
def initialize(path) | |
@path = path | |
end | |
attr_reader :path | |
end | |
class Namespace < Element | |
def initialize(scopes, lang) | |
@scopes = scopes | |
@lang = lang | |
end | |
attr_reader :scopes, :lang | |
def scope(separator='::') | |
@scopes.join(separator) | |
end | |
def lang_specific? | |
!!@lang | |
end | |
end | |
class Generics < Element | |
def initialize(type_params) | |
@type_params = type_params | |
end | |
attr_reader :type_params | |
end | |
class Message < Element | |
def initialize(name, super_class, fields) | |
@name = name | |
@super_class = super_class | |
@fields = fields | |
end | |
attr_reader :name, :super_class, :fields | |
end | |
class GenericMessage < Generics | |
def initialize(name, super_class, fields, type_params) | |
super(type_params) | |
@name = name | |
@super_class = super_class | |
@fields = fields | |
end | |
attr_reader :name, :super_class, :fields | |
end | |
class Exception < Message | |
end | |
class GenericException < GenericMessage | |
end | |
class Field < Element | |
def initialize(field_id, field_type, modifier, name) | |
@field_id = field_id | |
@field_type = field_type | |
@name = name | |
@modifier = modifier | |
end | |
attr_reader :field_id, :field_type, :name | |
def required? | |
@modifier == FIELD_REQUIRED | |
end | |
def optional? | |
@modifier == FIELD_OPTIONAL | |
end | |
end | |
class ValueAssignedField < Field | |
include ValueAssigned | |
def initialize(field_id, type, modifier, name, value) | |
@field_id = field_id | |
@type = type | |
@modifier = modifier | |
@name = name | |
@value = value | |
end | |
end | |
class Enum < Element | |
def initialize(name, fields) | |
@name = name | |
@fields = fields | |
end | |
attr_reader :name, :fields | |
end | |
class EnumField < Element | |
def initialize(field_id, name) | |
@field_id = field_id | |
@name = name | |
end | |
end | |
class Typedef < Element | |
def initialize(type, new_type) | |
@type = type | |
@new_type = new_type | |
end | |
attr_reader :type, :new_type | |
end | |
class GenericTypedef < Generics | |
def initialize(type, new_type, type_params) | |
super(type_params) | |
@type = type | |
@new_type = new_type | |
end | |
attr_reader :type, :new_type | |
end | |
class Const < Element | |
include ValueAssigned | |
def initialize(field_type, name, value) | |
@field_type = field_type | |
@name = name | |
@value = value | |
end | |
attr_reader :field_type, :name | |
end | |
class FieldSpec < Element | |
def initialize(name, target_class, target_field, spec) | |
@name = name | |
@target_class = target_class | |
@target_field = target_field | |
@spec = spec | |
end | |
attr_reader :name, :target_class, :target_field, :spec | |
end | |
class GenericFieldSpec < Generics | |
def initialize(name, target_class, target_field, spec, type_params) | |
super(type_params) | |
@name = name | |
@target_class = target_class | |
@target_field = target_field | |
@spec = spec | |
end | |
attr_reader :name, :target_class, :target_field, :spec | |
end | |
class TypeSpec < Element | |
def initialize(name, target_type, spec) | |
@name = name | |
@target_type = target_type | |
@spec = spec | |
end | |
attr_reader :name, :target_type, :spec | |
end | |
class GenericTypeSpec < Generics | |
def initialize(name, target_type, spec, type_params) | |
super(type_params) | |
@name = name | |
@target_type = target_type | |
@spec = spec | |
end | |
attr_reader :name, :target_type, :spec | |
end | |
class LangType < Element | |
def initialize(tokens) | |
@tokens = tokens | |
end | |
attr_reader :tokens | |
end | |
class Service < Element | |
def initialize(name, super_class, versions) | |
@name = name | |
@super_class = super_class | |
@versions = versions | |
end | |
attr_reader :name, :super_class, :versions | |
end | |
class ServiceVersion < Element | |
def initialize(version, funcs) | |
@version = version | |
@funcs = funcs | |
end | |
attr_reader :version, :funcs | |
end | |
class Server < Element | |
def initialize(name, scopes) | |
@name = name | |
@scopes = scopes | |
end | |
attr_reader :name | |
attr_reader :scopes | |
end | |
class Scope < Element | |
def initialize(type, name, default) | |
@type = type | |
@name = name | |
@default = default | |
end | |
attr_reader :type, :name | |
def default? | |
@default | |
end | |
end | |
class Func < Element | |
def initialize(name, modifier, return_type, args, exceptions) | |
@name = name | |
@modifier = modifier | |
@return_type = return_type | |
@args = args | |
@exceptions = exceptions | |
end | |
attr_reader :name, :return_type, :args, :exceptions | |
def override? | |
@modifier == FUNC_OVERRIDE | |
end | |
def remove? | |
@modifier == FUNC_REMOVE | |
end | |
def add? | |
@modifier == FUNC_ADD | |
end | |
attr_reader :modifier | |
def has_exceptions? | |
!@exceptions.empty? | |
end | |
end | |
class FieldType | |
def initialize(type, nullable) | |
@type = type | |
@nullable = nullable | |
end | |
attr_reader :type | |
def nullable? | |
@nullable | |
end | |
end | |
class Type < Element | |
def initialize(name) | |
@name = name | |
end | |
attr_reader :name | |
end | |
class GenericSpecifiedType < Type | |
def initialize(name, type_params) | |
super(name) | |
@type_params = type_params | |
end | |
attr_reader :type_params | |
end | |
class Literal | |
end | |
class ConstLiteral < Literal | |
def initialize(name) | |
@name = name | |
end | |
end | |
class IntLiteral < Literal | |
def initialize(value) | |
@value = value | |
end | |
end | |
class FlaotLiteral < Literal | |
def initialize(value) | |
@value = value | |
end | |
end | |
class NilLiteral < Literal | |
end | |
class BoolLiteral < Literal | |
end | |
class TrueLiteral < BoolLiteral | |
end | |
class FalseLiteral < BoolLiteral | |
end | |
class StringLiteral < Literal | |
def initialize(value) | |
@value = value | |
end | |
end | |
# TODO container literal | |
#class ListLiteral < Literal | |
# def initialize(array) | |
# @array = array | |
# end | |
#end | |
# TODO container literal | |
#class MapLiteralPair | |
# def initialize(k, v) | |
# @key = k | |
# @value = v | |
# end | |
#end | |
# TODO container literal | |
#class MapLiteral < Literal | |
# def initialize(pairs) | |
# @pairs = pairs | |
# end | |
#end | |
FIELD_OPTIONAL = :optional | |
FIELD_REQUIRED = :required | |
FUNC_OVERRIDE = :override | |
FUNC_REMOVE = :remove | |
FUNC_ADD = :add | |
class Sequence < Array | |
end | |
end | |
class Visitor < Parslet::Transform | |
rule(:name => simple(:n)) { | |
n.to_s | |
} | |
rule(:sequence_x => simple(:x)) { | |
x | |
} | |
rule(:sequence_xs => simple(:xs)) { | |
xs | |
} | |
rule(:sequence => simple(:x)) { | |
x ? AST::Sequence.new([x]) : AST::Sequence.new | |
} | |
rule(:sequence => sequence(:x)) { | |
AST::Sequence.new(x) | |
} | |
rule(:val_int => simple(:i)) { | |
i.to_i | |
} | |
rule(:val_optional => simple(:x)) { | |
AST::FIELD_OPTIONAL | |
} | |
rule(:val_required => simple(:x)) { | |
AST::FIELD_REQUIRED | |
} | |
rule(:val_override => simple(:x)) { | |
AST::FUNC_OVERRIDE | |
} | |
rule(:val_remove => simple(:x)) { | |
AST::FUNC_REMOVE | |
} | |
rule(:val_add => simple(:x)) { | |
AST::FUNC_ADD | |
} | |
rule(:generic_type => simple(:n), | |
:type_params => simple(:tp)) { | |
if tp | |
AST::GenericSpecifiedType.new(n, tp) | |
else | |
AST::Type.new(n) | |
end | |
} | |
rule(:field_id => simple(:i), | |
:field_modifier => simple(:m), | |
:field_type => simple(:t), | |
:field_name => simple(:n), | |
:field_default => simple(:v)) { | |
m ||= AST::FIELD_REQUIRED | |
if v == nil | |
AST::Field.new(i, t, m, n) | |
else | |
AST::ValueAssignedField.new(i, t, m, n, v) | |
end | |
} | |
rule(:field_type => simple(:t), | |
:field_type_maybe => simple(:n)) { | |
if n | |
AST::FieldType.new(t, true) | |
else | |
AST::FieldType.new(t, false) | |
end | |
} | |
rule(:lang_type_token => simple(:t)) { | |
t.to_s | |
} | |
rule(:lang_type_tokens => sequence(:ts)) { | |
AST::LangType.new(ts) | |
} | |
rule(:literal_const => simple(:n)) { | |
AST::ConstLiteral.new(n) | |
} | |
rule(:literal_int => simple(:i)) { | |
AST::IntLiteral.new(i.to_i) | |
} | |
rule(:literal_float => simple(:f)) { | |
AST::FloatLiteral.new(f.to_f) | |
} | |
rule(:literal_str_dq => simple(:s)) { | |
s.to_s.gsub(/\\(.)/) {|e| | |
eval("\"\\#{$~[1]}\"") # TODO escape | |
} | |
} | |
rule(:literal_str_sq => simple(:s)) { | |
s.to_s | |
} | |
rule(:literal_str_seq => sequence(:ss)) { | |
AST::StringLiteral.new(ss.join) | |
} | |
rule(:literal_nil => simple(:_)) { | |
AST::NilLiteral.new | |
} | |
rule(:literal_true => simple(:_)) { | |
AST::TrueLiteral.new | |
} | |
rule(:literal_false => simple(:_)) { | |
AST::FalseLiteral.new | |
} | |
# TODO container literal | |
#rule(:literal_list => simple(:a)) { | |
# AST::ListLiteral.new(Array.new(a)) | |
#} | |
# TODO container literal | |
#rule(:literal_map => simple(:ps)) { | |
# AST::MapLiteral.new(Array.new(ps)) | |
#} | |
# TODO container literal | |
#rule(:literal_map_key => simple(:k), :literal_map_value => simple(:v)) { | |
# AST::MapLiteralPair.new(k, v) | |
#} | |
rule(:type_param_decl => simple(:tp), | |
:message_name => simple(:n), | |
:message_body => sequence(:b), | |
:super_class => simple(:sc)) { | |
if tp | |
AST::GenericMessage.new(n, sc, b, tp) | |
else | |
AST::Message.new(n, sc, b) | |
end | |
} | |
rule(:type_param_decl => simple(:tp), | |
:exception_name => simple(:n), | |
:exception_body => sequence(:b), | |
:super_class => simple(:sc)) { | |
if tp | |
AST::GenericException.new(n, sc, b, tp) | |
else | |
AST::Exception.new(n, sc, b) | |
end | |
} | |
rule(:const_type => simple(:t), | |
:const_name => simple(:n), | |
:const_value => simple(:v)) { | |
AST::Const.new(t, n, v) | |
} | |
rule(:type_param_decl => simple(:tp), | |
:typedef_type => simple(:t), | |
:typedef_name => simple(:n)) { | |
if tp | |
AST::GenericTypedef.new(t, n, tp) | |
else | |
AST::Typedef.new(t, n) | |
end | |
} | |
rule(:type_param_decl => simple(:tp), | |
:typespec_lang => simple(:n), | |
:typespec_class => simple(:c), | |
:typespec_field => simple(:f), | |
:typespec_spec => simple(:s)) { | |
if tp | |
AST::GenericFieldSpec.new(n, c, f, s, tp) | |
else | |
AST::FieldSpec.new(n, c, f, s) | |
end | |
} | |
rule(:type_param_decl => simple(:tp), | |
:typespec_lang => simple(:n), | |
:typespec_type => simple(:t), | |
:typespec_spec => simple(:s)) { | |
if tp | |
AST::GenericTypeSpec.new(n, t, s, tp) | |
else | |
AST::TypeSpec.new(n, t, s) | |
end | |
} | |
rule(:enum_field_id => simple(:i), | |
:enum_field_name => simple(:n)) { | |
AST::EnumField.new(i, n) | |
} | |
rule(:enum_name => simple(:n), | |
:enum_body => sequence(:b)) { | |
AST::Enum.new(n, b) | |
} | |
rule(:func_modifier => simple(:m), | |
:return_type => simple(:rt), | |
:func_name => simple(:n), | |
:func_args => simple(:a), | |
:func_throws => simple(:ex)) { | |
AST::Func.new(n, m, rt, a, ex) | |
} | |
rule(:service_description => sequence(:s)) { | |
current = AST::ServiceVersion.new(0, []) | |
versions = [current] | |
s.each {|l| | |
case l | |
when Integer | |
v = versions.find {|v| v.version == l } | |
if v | |
current = v | |
else | |
current = AST::ServiceVersion.new(l, []) | |
versions << current | |
end | |
else | |
current.funcs << l | |
end | |
} | |
versions | |
} | |
rule(:service_name => simple(:n), | |
:service_versions => sequence(:vs), | |
:super_class => simple(:sc)) { | |
AST::Service.new(n, sc, vs) | |
} | |
rule(:scope_type => simple(:t), | |
:scope_name => simple(:n), | |
:scope_default => simple(:d)) { | |
if d | |
AST::Scope.new(t, n, true) | |
else | |
AST::Scope.new(t, n, false) | |
end | |
} | |
rule(:server_name => simple(:n), | |
:server_body => sequence(:b)) { | |
AST::Server.new(n, b) | |
} | |
rule(:namespace_name => simple(:n)) { | |
AST::Namespace.new(n, nil) | |
} | |
rule(:namespace_name => simple(:n), | |
:namespace_lang => simple(:l)) { | |
AST::Namespace.new(n, l) | |
} | |
rule(:path => simple(:n)) { | |
n | |
} | |
rule(:include => simple(:n)) { | |
AST::Include.new(n.to_s) | |
} | |
rule(:document => sequence(:es)) { | |
AST::Document.new(es) | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment