talk 2.0.1 → 2.0.2

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.
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