talk 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +10 -0
  5. data/README.md +6 -0
  6. data/Rakefile +9 -0
  7. data/features/class-field.feature +226 -0
  8. data/features/class.feature +95 -0
  9. data/features/enumeration-constant.feature +76 -0
  10. data/features/enumeration.feature +35 -0
  11. data/features/glossary-term.feature +46 -0
  12. data/features/glossary.feature +35 -0
  13. data/features/step_definitions/class-field.rb +74 -0
  14. data/features/step_definitions/class.rb +50 -0
  15. data/features/step_definitions/enumeration-constant.rb +42 -0
  16. data/features/step_definitions/enumeration.rb +29 -0
  17. data/features/step_definitions/glossary-term.rb +31 -0
  18. data/features/step_definitions/glossary.rb +23 -0
  19. data/features/support/env.rb +261 -0
  20. data/lib/context.rb +282 -0
  21. data/lib/context_class.rb +224 -0
  22. data/lib/contexts/README.md +274 -0
  23. data/lib/contexts/base.rb +6 -0
  24. data/lib/contexts/boolean.rb +5 -0
  25. data/lib/contexts/class.rb +11 -0
  26. data/lib/contexts/constant.rb +5 -0
  27. data/lib/contexts/enumeration.rb +20 -0
  28. data/lib/contexts/field.rb +47 -0
  29. data/lib/contexts/glossary.rb +7 -0
  30. data/lib/contexts/inherits.rb +3 -0
  31. data/lib/contexts/map.rb +7 -0
  32. data/lib/contexts/meta.rb +2 -0
  33. data/lib/contexts/method.rb +15 -0
  34. data/lib/contexts/numeric.rb +5 -0
  35. data/lib/contexts/protocol.rb +8 -0
  36. data/lib/contexts/reference.rb +6 -0
  37. data/lib/contexts/string.rb +5 -0
  38. data/lib/contexts/target.rb +11 -0
  39. data/lib/contexts/term.rb +11 -0
  40. data/lib/languages/java/java.rb +145 -0
  41. data/lib/languages/java/templates/class.java.erb +22 -0
  42. data/lib/languages/java/templates/enumeration.java.erb +10 -0
  43. data/lib/languages/java/templates/glossary.java.erb +8 -0
  44. data/lib/languages/language.rb +172 -0
  45. data/lib/languages/objc/objc.rb +162 -0
  46. data/lib/languages/objc/templates/TalkClasses.h.erb +3 -0
  47. data/lib/languages/objc/templates/TalkClassesForward.h.erb +2 -0
  48. data/lib/languages/objc/templates/TalkConstants.h.erb +22 -0
  49. data/lib/languages/objc/templates/TalkObjectList.h.erb +4 -0
  50. data/lib/languages/objc/templates/class.h.erb +21 -0
  51. data/lib/languages/objc/templates/class.m.erb +43 -0
  52. data/lib/parse_error.rb +4 -0
  53. data/lib/parser.rb +119 -0
  54. data/lib/registry.rb +151 -0
  55. data/lib/talk.rb +5 -0
  56. data/talk.gemspec +18 -0
  57. metadata +71 -3
@@ -0,0 +1,11 @@
1
+ property :name
2
+
3
+ tag_description
4
+ tag :language, :class => :string
5
+ tag :destination, :class => :string
6
+ tag :map, :multi => true
7
+ tag :meta, :multi => true, :unique => :name
8
+ tag :rootclass, :class => :string
9
+ tag :template, :class => :string
10
+ tag :prune, :class => :boolean
11
+ tag_end
@@ -0,0 +1,11 @@
1
+ property :name
2
+ property :value, :length => [0,nil]
3
+
4
+ tag_description :bridge => false, :required => false
5
+ tag_end
6
+
7
+ postprocess lambda { |ctx|
8
+ if ctx[:value].nil? or ctx[:value].length == 0 then
9
+ ctx[:value] = ctx[:name]
10
+ end
11
+ }
@@ -0,0 +1,145 @@
1
+ require 'set'
2
+
3
+ def make_source
4
+ types = [ :class, :enumeration, :glossary]
5
+ types.each do |type|
6
+ @base[type].each do |current|
7
+ @current = current
8
+ @current[:field] ||= [] if type == :class
9
+ generate_template(filename_for_entity(@current), type.to_s+".java.erb")
10
+ end
11
+ end
12
+ end
13
+
14
+ def filename_for_entity(name)
15
+ name = name[:name] if name.is_a? Hash
16
+ name.gsub(".", "/") + ".java"
17
+ end
18
+
19
+ def autogenerated_warning
20
+ <<-AUTOGEN_DONE
21
+ /* Autogenerated from Talk
22
+ ** Please do not edit this file directly. Instead, modify the underlying .talk files. */
23
+ AUTOGEN_DONE
24
+ end
25
+
26
+ def definition_reference(tag)
27
+ "@talkFile #{tag[:__meta][:file]}:#{tag[:__meta][:line]}"
28
+ end
29
+
30
+ def rootclass
31
+ @target[:rootclass] || "io.usetalk.TalkObject"
32
+ end
33
+
34
+ def superclass(cls)
35
+ cls[:inherits] || rootclass
36
+ end
37
+
38
+ def class_package(cls)
39
+ cls[:name].split(".")[0..-2].join(".")
40
+ end
41
+
42
+ def list_references_for_class(cls)
43
+ references = Set.new
44
+ cls[:field].each do |field|
45
+ unless is_primitive?(field[:type].first) then
46
+ references.add(field[:type])
47
+ end
48
+ end
49
+
50
+ references.to_a
51
+ end
52
+
53
+ def import_classes
54
+ (list_references_for_class(@current).map { |name| "import #{name};"}).join("\n")
55
+ end
56
+
57
+ def comment_block(tag, indent_level=0)
58
+ lines = []
59
+ indent = "\t" * indent_level
60
+ lines.push(indent + "/*!")
61
+ lines.push(wrap_text_to_width(tag[:description], 80, indent + " * ")) unless tag[:description].nil?
62
+ lines.push(indent + " * ")
63
+ lines.push(indent + " * " + definition_reference(tag))
64
+ lines.push(indent + " */")
65
+
66
+ lines.join("\n")
67
+ end
68
+
69
+ def detail_comment_block(cls)
70
+ lines = []
71
+ lines.push("\t/**")
72
+ lines.push("\t * #{truncated_name cls[:name]}")
73
+ cls[:field].each { |f| lines.push("\t * @param #{field_datatype f} #{f[:name]}") }
74
+ lines.join("\n")
75
+ end
76
+
77
+ def field_datatype_rec(field, stack)
78
+ return field_datatype_basic(field, stack.last) if stack.length == 1
79
+ t = stack.last
80
+
81
+ r = field_datatype_rec(field, stack[0 .. -2])
82
+ if is_array? t then
83
+ "[]"
84
+ elsif is_dict? t then
85
+ "Map<String, #{r}>"
86
+ else
87
+ nil
88
+ end
89
+ end
90
+
91
+ def field_datatype_basic(field, type)
92
+ if is_primitive? type then
93
+ case type
94
+ when "string"
95
+ "String"
96
+ when "real"
97
+ "double"
98
+ when "bool"
99
+ "boolean"
100
+ when "object"
101
+ "Object"
102
+ when "talkobject"
103
+ "TalkObject"
104
+ else
105
+ size = integer_size(type)
106
+ size *= 2 if is_unsigned? type # java has no unsigned type; just use the next size up
107
+ case
108
+ when 8
109
+ "byte"
110
+ when 16
111
+ "short"
112
+ when 32
113
+ "int"
114
+ when 64
115
+ "long"
116
+ when 128
117
+ "BigDecimal"
118
+ end
119
+ end
120
+ else
121
+ truncated_name type
122
+ end
123
+ end
124
+
125
+ def field_datatype(field)
126
+ field_datatype_rec(field, field[:type])
127
+ end
128
+
129
+ def field_variable(field)
130
+ lines = []
131
+ lines.push comment_block(field, 1)
132
+ lines.push "\tprotected #{field_datatype(field)} #{field[:name]};"
133
+ lines.join("\n")
134
+ end
135
+
136
+ def field_accessors(field)
137
+ lines.push wrap_text(field[:description], " * ", 1)
138
+ lines.push field[:deprecated] if field.has_key? :deprecated
139
+ lines.push definition_reference(field)
140
+ lines.push " * @param #{field[:name]} #{field[:description]}"
141
+ lines.push "\tpublic void #{setter_name(field)}(#{field_datatype(field)} #{field[:name]}) {"
142
+ lines.push "\t\tthis.#{field[:name]} = #{field[:name]}"
143
+ lines.push "\t}"
144
+ lines.join "\n"
145
+ end
@@ -0,0 +1,22 @@
1
+ <%= autogenerated_warning %>
2
+ package <%= class_package(@current) %>;
3
+ <%= import_classes %>
4
+
5
+ <%= comment_block(@current) %>
6
+ public class <%= truncated_name(@current) %> extends <%= superclass(@current) %>
7
+ {
8
+ <%= detail_comment_block(@current) %>
9
+ /**
10
+ * <%= truncated_name @current %> - Default Constructor
11
+ *
12
+ */
13
+ public <%= truncated_name @current %>() {}
14
+ <%=
15
+ (@current[:field].map do |f|
16
+ field_variable(f)
17
+ end).join("\n")
18
+
19
+ (@current[:field].map do |f|
20
+ field_accessors(f)
21
+ end).join("\n")
22
+ %>}
@@ -0,0 +1,10 @@
1
+ <%= autogenerated_warning %>
2
+ package <%= class_package(@current) %>;
3
+
4
+ <%= comment_block(@current) %>
5
+ public class <%= truncated_name(@current) %> extends <%= rootclass %>
6
+ {<% @current[:field].each do |field| %>
7
+ // <%= field[:description] %>
8
+ @TalkDescription(description = "<%= field[:description] %>")
9
+ public final static int <%= field[:name] %> = <%= field[:value] %>;
10
+ <%= end %>}
@@ -0,0 +1,8 @@
1
+ <%= autogenerated_warning %>
2
+ package <%= class_package(@current) %>;
3
+
4
+ <%= comment_block(@current) %>
5
+ public class <%= truncated_name(@current) %>
6
+ {<%= @current[:field].each do |field| %>
7
+ public final static String <%= field[:name] %> = "<%= field[:value] %>";<% end %>
8
+ }
@@ -0,0 +1,172 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+ def classname_for_filename(name) # /path/to/file_name.rb to FileName
5
+ File.basename(name.to_s, ".rb").split('_').collect { |word| word.capitalize }.join("")
6
+ end
7
+
8
+ def is_primitive?(type)
9
+ primitives = [
10
+ "uint8", "uint16", "uint32", "uint64",
11
+ "int8", "int16", "int32", "int64",
12
+ "string", "real", "bool", "object", "talkobject" ]
13
+ primitives.include? type
14
+ end
15
+
16
+ module Talk
17
+ class Language
18
+ attr_reader :supported_languages
19
+
20
+ class << self
21
+ def path_for_language(lang)
22
+ File.absolute_path(File.join(File.dirname(__FILE__), "#{lang}/#{lang}.rb"))
23
+ end
24
+
25
+ def load_supported_languages
26
+ @languages ||= {}
27
+
28
+ local_dir = File.dirname(__FILE__)
29
+ trimmed = Dir["#{local_dir}/*/"].map { |subdir| subdir.gsub(/\/$/, "") }
30
+ supported = trimmed.select { |subdir| File.exists?(File.join(subdir, File.basename(subdir)+".rb")) }
31
+ supported.each { |lang| load_language(lang) }
32
+ supported.map { |lang| lang.to_sym }
33
+ end
34
+
35
+ def load_language(lang_name)
36
+ lang_name = File.basename(lang_name.to_s, ".rb").to_sym
37
+ new_classname = classname_for_filename(lang_name)
38
+ source_file = path_for_language(lang_name)
39
+
40
+ lang = Class.new(Talk::Language) {}
41
+
42
+ lang.class_eval( IO.read(source_file), source_file )
43
+ lang.class_eval( "def path\n\t\"#{File.dirname(source_file)}\"\nend" )
44
+ lang.class_eval( "def name\n\t\"#{lang_name}\"\nend" )
45
+ @languages[lang_name.to_sym] = lang
46
+ end
47
+
48
+ def language_named(lang_name)
49
+ load_supported_languages if @languages.nil?
50
+ @languages[lang_name.to_sym].new
51
+ end
52
+ end
53
+
54
+ def initialize
55
+ @written = {}
56
+ end
57
+
58
+ def render(base, target)
59
+ @base = base
60
+ @target = target
61
+ @output_path = find_output_path
62
+
63
+ make_source
64
+ prune if @target[:prune]
65
+ end
66
+
67
+ def write(path, contents, conditional=true)
68
+ @written[File.absolute_path(path)] = contents
69
+ return if conditional and File.exists? path and IO.read(path) == contents
70
+
71
+ FileUtils::mkdir_p(File.dirname(path))
72
+ File.write(path, contents)
73
+ end
74
+
75
+ def prune
76
+ # Delete anything we didn't conditionally write, unless it's a directory
77
+ dirs = []
78
+ Dir.glob(@output_path+"/**/*").each do |path|
79
+ abs_path = File.absolute_path(path)
80
+ unless @written.has_key?(abs_path) then
81
+ if File.directory?(abs_path) then
82
+ dirs.push abs_path
83
+ else
84
+ FileUtils::rm(abs_path)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Now sort the directories by length, so longest directories are first
90
+ # Since these are abspaths, that should guarantee subdirectories come before parents
91
+ # Then delete each empty directory, tested by containing no entries other than '.' and '..'
92
+ dirs.sort! { |a,b| b.length <=> a.length }
93
+ dirs.each { |dir| FileUtils::rmdir(dir) if (Dir.entries(dir) - %w{ . .. }).empty? }
94
+ end
95
+
96
+ def find_output_path
97
+ @target[:destination]
98
+ end
99
+
100
+ def generate_template(output_file, template_file=nil)
101
+ template_file ||= output_file + ".erb"
102
+ template_contents = IO.read(File.join(self.path, "templates", template_file))
103
+ erb = ERB.new(template_contents)
104
+ erb.filename = template_file
105
+ source = erb.result(binding)
106
+ filename = File.join(@output_path, output_file)
107
+ write(filename, source)
108
+ source
109
+ end
110
+
111
+ def meta(name)
112
+ return nil if @target[:meta].nil?
113
+ name = name.to_s
114
+
115
+ @target[:meta].each do |meta|
116
+ return meta[:value] if meta[:name].to_s == name
117
+ end
118
+
119
+ nil
120
+ end
121
+
122
+ def string_overlap(a,b)
123
+ [a.length, b.length].min.times do |i|
124
+ if a[i] != b[i] then
125
+ return "" if i == 0
126
+ return a[0..i-1]
127
+ end
128
+ end
129
+
130
+ a.length < b.length ? a : b
131
+ end
132
+
133
+ def wrap_text_to_width(text, width=80, preamble="")
134
+ width -= preamble.length
135
+ words = text.split(/\s+/)
136
+ lines = []
137
+
138
+ words.each do |word|
139
+ if lines.empty? or (lines.last + " " + word).length >= width then
140
+ lines.push (preamble + word)
141
+ else
142
+ lines.last << " " + word
143
+ end
144
+ end
145
+
146
+ lines.empty? ? "" : lines.join("\n")
147
+ end
148
+
149
+ def common_class_prefix
150
+ prefix = nil
151
+ @base[:class].each do |cls|
152
+ if prefix.nil? then
153
+ prefix = cls[:name]
154
+ else
155
+ prefix = string_overlap(prefix, cls[:name])
156
+ return nil if prefix.length == 0
157
+ end
158
+ end
159
+
160
+ prefix
161
+ end
162
+
163
+ def classname_for_filename(name) # /path/to/file_name.rb to FileName
164
+ File.basename(name.to_s, ".rb").split('_').collect { |word| word.capitalize }.join("")
165
+ end
166
+
167
+ def truncated_name(name)
168
+ name = name[:name] if name.is_a? Hash
169
+ name.split('.').last
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,162 @@
1
+ def make_source
2
+ @prefix = common_class_prefix if meta(:namespace) == "true"
3
+ master_files = [ "TalkClasses.h", "TalkClassesForward.h", "TalkConstants.h", "TalkObjectList.h"]
4
+ master_files.each { |template| generate_template(template) }
5
+
6
+ @base[:class].each do |cls|
7
+ @current_class = cls
8
+ @current_class[:field] ||= []
9
+ file_base = filename_for_class(cls)
10
+ [".h", ".m"].each { |ext| generate_template(file_base+ext, "class"+ext+".erb") }
11
+ end
12
+ end
13
+
14
+ def filename_for_class(cls)
15
+ class_dir = "classes"
16
+ if meta(:namespace) == "true" then
17
+ namespace = cls[:name][@prefix.length..-1].split(".")[0..-2]
18
+ return File.join(class_dir, truncated_name(cls)) if namespace.empty?
19
+
20
+ namespace = namespace[1..-1] while namespace[0].length == 0
21
+ File.join(class_dir, namespace.join("/"), truncated_name(cls))
22
+ else
23
+ File.join(class_dir, truncated_name(cls))
24
+ end
25
+ end
26
+
27
+ def autogenerated_warning
28
+ <<-AUTOGEN_DONE
29
+ // Autogenerated from Talk
30
+ // Please do not edit this file directly. Instead, modify the underlying .talk files.
31
+ AUTOGEN_DONE
32
+ end
33
+
34
+ def glossary_term_name(name)
35
+ "k"+name
36
+ end
37
+
38
+ def constant_definition(constant)
39
+ "#{constant[:name]} = #{constant[:value].to_i}, #{comment_line(constant)}"
40
+ end
41
+
42
+ def comment_line(tag)
43
+ tag[:description].nil? ? "" : "//!< #{tag[:description]}"
44
+ end
45
+
46
+ def comment_block(tag, indent_level=0)
47
+ lines = []
48
+ indent = "\t" * indent_level
49
+ lines.push(indent + "/*!")
50
+ lines.push(wrap_text_to_width(tag[:description], 80, indent + " * ")) unless tag[:description].nil?
51
+ lines.push(indent + " * ")
52
+ lines.push(indent + " * " + definition_reference(tag))
53
+ lines.push(indent + " */")
54
+
55
+ lines.join("\n")
56
+ end
57
+
58
+ def definition_reference(tag)
59
+ "@talkFile #{tag[:__meta][:file]}:#{tag[:__meta][:line]}"
60
+ end
61
+
62
+ def rootclass
63
+ @target[:rootclass] || "TalkObject"
64
+ end
65
+
66
+ def superclass(cls)
67
+ cls[:inherits] || rootclass
68
+ end
69
+
70
+ def is_native?(type)
71
+ type != "talkobject" and is_primitive?(type)
72
+ end
73
+
74
+ def is_array?(type)
75
+ type == "[]"
76
+ end
77
+
78
+ def is_dict?(type)
79
+ type == "{}"
80
+ end
81
+
82
+ def mapped_name(container_name, object_name, type, name_key=:name)
83
+ object_name = object_name[:name] if object_name.is_a? Hash
84
+ container_name = container_name[:name] if container_name.is_a? Hash
85
+ container_name = truncated_name(container_name)
86
+
87
+ @target[:map].each do |map|
88
+ matches = (map[:type] == type.to_s && map[:class_name] == container_name && map[:field_name] == object_name)
89
+ return map[:new_field_name] if matches
90
+ end
91
+
92
+ object_name
93
+ end
94
+
95
+ def assist_line(field)
96
+ return nil if field[:type].length <= 1
97
+ elements = []
98
+ field[:type].reverse.each do |type|
99
+ elements.push case
100
+ when is_array?(type)
101
+ "array"
102
+ when is_dict?(type)
103
+ "dict"
104
+ when type == "talkobject"
105
+ "TalkObject"
106
+ when is_native?(type)
107
+ "native"
108
+ else
109
+ truncated_name(type)
110
+ end
111
+ end
112
+
113
+ elements.join(".")
114
+ end
115
+
116
+ def trimmed_fields(cls)
117
+ return cls[:field] unless truncated_name(cls[:name]) == 'NamedObjectWrapper'
118
+ cls[:field].reject { |f| f[:name] == "body" }
119
+ end
120
+
121
+ def dynamic_body_for_named_wrapper
122
+ return "@dynamic body;" if truncated_name(@current_class[:name]) == 'NamedObjectWrapper'
123
+ ""
124
+ end
125
+
126
+ def primitive_type(unsigned, size)
127
+ type = "int#{size}_t"
128
+ type = "u" + type if unsigned
129
+ type
130
+ end
131
+
132
+ def field_tags(field)
133
+ case field[:type].last
134
+ when "real", "bool", "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64"
135
+ "(readwrite,assign)"
136
+ else
137
+ "(readwrite,nonatomic,retain)"
138
+ end
139
+ end
140
+
141
+ def field_definition(cls, field)
142
+ base_type = field[:type].last
143
+ objc_type = case
144
+ when is_array?(base_type)
145
+ "NSMutableArray *"
146
+ when is_dict?(base_type)
147
+ "NSMutableDictionary *"
148
+ when (base_type == "talkobject" or base_type == "object")
149
+ "TalkObject *"
150
+ when base_type.match(/(u)?int(8|16|32|64)/)
151
+ primitive_type(not($1.nil?), $2)
152
+ when base_type == "real"
153
+ "double"
154
+ when base_type == "bool"
155
+ "BOOL"
156
+ when base_type == "string"
157
+ "NSString *"
158
+ else
159
+ truncated_name(base_type) + " *"
160
+ end
161
+ "#{objc_type} #{mapped_name(cls, field, :field)}"
162
+ end