babelfish-ruby 1.0.1

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.
@@ -0,0 +1,70 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'babelfish/phrase/literal'
3
+ require 'babelfish/phrase/variable'
4
+ require 'babelfish/phrase/plural_forms'
5
+
6
+ class Babelfish
7
+ module Phrase
8
+ # Babelfish AST Compiler.
9
+ # Compiles AST to string or to Proc.
10
+ class Compiler
11
+ attr_accessor :ast
12
+
13
+ def initialize( ast = nil )
14
+ init( ast ) unless ast.nil?
15
+ end
16
+
17
+ # Initializes compiler. Should not be called directly.
18
+ def init( ast )
19
+ self.ast = ast
20
+ end
21
+
22
+ # Throws given message in compiler context.
23
+ def throw( message )
24
+ raise "Cannot compile: #{message}";
25
+ end
26
+
27
+ # Compiles AST.
28
+
29
+ # Result is string when possible; Proc otherwise.
30
+ def compile ( ast )
31
+ init( ast ) unless ast.nil?
32
+
33
+ throw("No AST given") if ast.nil?
34
+ throw("Empty AST given") if ast.length == 0;
35
+
36
+ if ast.length == 1 && ast.first.kind_of?(Babelfish::Phrase::Literal)
37
+ # просто строка
38
+ return ast.first.text
39
+ end
40
+
41
+ ready = ast.map do |node|
42
+ case node
43
+ when Babelfish::Phrase::Literal
44
+ node.text
45
+ when Babelfish::Phrase::Variable
46
+ node
47
+ when Babelfish::Phrase::PluralForms
48
+ sub = node.to_ruby_method
49
+ else
50
+ throw("Unknown AST node: #{node}")
51
+ end
52
+ end
53
+
54
+ lambda do |params|
55
+ data = ready.map do |what|
56
+ case what
57
+ when Babelfish::Phrase::Variable
58
+ params[what.name.to_s].to_s
59
+ when Proc
60
+ what = what.call(params)
61
+ else
62
+ what
63
+ end
64
+ end.join('')
65
+ data
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,12 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'babelfish/phrase/node'
3
+
4
+ class Babelfish
5
+ module Phrase
6
+ # Babelfish AST Literal node.
7
+ class Literal < Node
8
+
9
+ attr_accessor :text
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class Babelfish
3
+ module Phrase
4
+ # Babelfish AST abstract node.
5
+ class Node
6
+
7
+ def initialize( args = {} )
8
+ args.keys.each do |key|
9
+ send("#{key}=", args[key]) if respond_to?("#{key}=")
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,173 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'babelfish/phrase/parser_base'
3
+ require 'babelfish/phrase/literal'
4
+ require 'babelfish/phrase/variable'
5
+ require 'babelfish/phrase/plural_forms'
6
+ require 'babelfish/phrase/plural_forms_parser'
7
+
8
+ class Babelfish
9
+ module Phrase
10
+ # Babelfish syntax parser.
11
+ class Parser < ParserBase
12
+
13
+ attr_accessor :locale, :mode, :pieces, :escape, :pf0
14
+
15
+ LITERAL_MODE = 'Literal'.freeze
16
+ VARIABLE_MODE = 'Variable'.freeze
17
+ PLURALS_MODE = 'Plurals'.freeze
18
+ VARIABLE_RE = /^[a-zA-Z0-9_\.]+$/
19
+
20
+ AST_MAP = {
21
+ LITERAL_MODE => Babelfish::Phrase::Literal,
22
+ VARIABLE_MODE => Babelfish::Phrase::Variable,
23
+ PLURALS_MODE => Babelfish::Phrase::PluralForms,
24
+ }
25
+
26
+ # Instantiates parser.
27
+ def initialize( phrase = nil, locale = nil )
28
+ super( phrase )
29
+ init( phrase ) unless phrase.nil?
30
+ self.locale = locale if locale
31
+ end
32
+
33
+ # Initializes parser. Should not be called directly.
34
+ def init( phrase )
35
+ super( phrase )
36
+ self.mode = LITERAL_MODE
37
+ self.pieces = []
38
+ self.pf0 = nil # plural forms without name yet
39
+ end
40
+
41
+ # Finalizes all operations after phrase end.
42
+ def finalize_mode
43
+ case mode
44
+ when LITERAL_MODE
45
+ pieces.push( AST_MAP[LITERAL_MODE].new( text: piece ) ) if !piece.empty? || pieces.size == 0;
46
+ when VARIABLE_MODE
47
+ throw( "Variable definition not ended with \"}\": " + piece )
48
+ when PLURALS_MODE
49
+ throw( "Plural forms definition not ended with \"))\": " + piece ) if pf0.nil?
50
+ pieces.push( AST_MAP[PLURALS_MODE].new( forms: pf0, name: piece, locale: locale ) )
51
+ else
52
+ throw( "Logic broken, unknown parser mode: " + mode );
53
+ end
54
+ end
55
+
56
+ class << self
57
+ attr_accessor :plurals_parser
58
+ end
59
+
60
+ def plurals_parser
61
+ Parser.plurals_parser ||= Babelfish::Phrase::PluralFormsParser.new
62
+ end
63
+
64
+ def escape?
65
+ !! self.escape
66
+ end
67
+
68
+ # Parses specified phrase.
69
+ def parse( phrase = nil, locale = nil )
70
+ super( phrase )
71
+ self.locale = locale unless locale.nil?
72
+
73
+ while true
74
+ _char = to_next_char
75
+
76
+ if _char.empty?
77
+ finalize_mode
78
+ return pieces
79
+ end
80
+
81
+ case mode
82
+ when LITERAL_MODE
83
+ if escape?
84
+ add_to_piece( _char )
85
+ self.escape = false
86
+ next
87
+ end
88
+
89
+ if _char == "\\"
90
+ self.escape = true
91
+ next
92
+ end
93
+
94
+ if _char == '#' && next_char == '{'
95
+ unless piece.empty?
96
+ pieces.push( AST_MAP[LITERAL_MODE].new( text: piece ) )
97
+ self.piece = ''
98
+ end
99
+ self.to_next_char # skip "{"
100
+ self.mode = VARIABLE_MODE
101
+ next
102
+ end
103
+
104
+ if _char == '(' && next_char == '('
105
+ unless piece.empty?
106
+ pieces.push( AST_MAP[LITERAL_MODE].new( text: piece ) )
107
+ self.piece = ''
108
+ end
109
+ to_next_char # skip second "("
110
+ self.mode = PLURALS_MODE
111
+ next
112
+ end
113
+
114
+ when VARIABLE_MODE
115
+ if escape?
116
+ add_to_piece( _char )
117
+ self.escape = false
118
+ next
119
+ end
120
+
121
+ if _char == "\\"
122
+ self.escape = true
123
+ next
124
+ end
125
+
126
+ if _char == '}'
127
+ name = piece.strip
128
+ if name.empty?
129
+ throw( "No variable name given." );
130
+ end
131
+ if name !~ VARIABLE_RE
132
+ throw( "Variable name doesn't meet conditions: #{name}." );
133
+ end
134
+ pieces.push( AST_MAP[VARIABLE_MODE].new( name: name ) )
135
+ self.piece = ''
136
+ self.mode = LITERAL_MODE
137
+ next
138
+ end
139
+
140
+ when PLURALS_MODE
141
+ unless pf0.nil?
142
+ if _char =~ VARIABLE_RE && (_char != '.' || next_char =~ VARIABLE_RE)
143
+ add_to_piece( _char )
144
+ next
145
+ else
146
+ pieces.push( AST_MAP[PLURALS_MODE].new( forms: pf0, name: piece, locale:locale ) )
147
+ self.pf0 = nil
148
+ self.mode = LITERAL_MODE
149
+ self.piece = ''
150
+ backward
151
+ next
152
+ end
153
+ end
154
+ if _char == ')' && next_char == ')'
155
+ self.pf0 = plurals_parser.parse( piece )
156
+ self.piece = ''
157
+ to_next_char # skip second ")"
158
+ if next_char == ':'
159
+ to_next_char # skip ":"
160
+ next
161
+ end
162
+ pieces.push( AST_MAP[PLURALS_MODE].new( forms: pf0, name: 'count', locale: locale ) )
163
+ self.pf0 = nil
164
+ self.mode = LITERAL_MODE
165
+ next
166
+ end
167
+ end
168
+ add_to_piece( _char )
169
+ end # while ( 1 )
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,73 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class Babelfish
3
+ module Phrase
4
+ # Babelfish abstract parser.
5
+ class ParserBase
6
+ attr_accessor :phrase, :index, :length, :prev, :piece, :escape
7
+
8
+
9
+ def initialize(phrase = nil)
10
+ init(phrase) if phrase
11
+ end
12
+
13
+ def init(phrase)
14
+ self.phrase = phrase
15
+ self.index = -1
16
+ self.prev = nil
17
+ self.length = phrase.length
18
+ self.piece = ''
19
+ self.escape = false
20
+ end
21
+
22
+ # Gets character on current cursor position.
23
+ # Will return empty string if no character.
24
+ def char
25
+ r = phrase[ index ]
26
+ r.nil? ? '' : r
27
+ end
28
+
29
+ # Gets character on next cursor position.
30
+ # Will return empty string if no character.
31
+ def next_char
32
+ return '' if index >= length - 1
33
+ r = phrase[ index + 1 ]
34
+ r.nil? ? '' : r
35
+ end
36
+
37
+ # Moves cursor to next position.
38
+ # Return new current character.
39
+ def to_next_char
40
+ self.prev = char if self.index >= 0
41
+ self.index = self.index + 1
42
+ return '' if self.index == length
43
+ char()
44
+ end
45
+
46
+ # Throws given message in phrase context.
47
+ def throw( message )
48
+ raise "Cannot parse phrase \""+ ( phrase || 'nil' )+ "\" at ". ( index || '-1' )+ " index: #{message}"
49
+ end
50
+
51
+ # Adds given chars to current piece.
52
+ def add_to_piece(chars)
53
+ self.piece += chars
54
+ end
55
+
56
+ # Moves cursor backward.
57
+ def backward
58
+ self.index = index - 1
59
+ if index > 0
60
+ r = phrase[ index - 1 ]
61
+ self.prev = r.nil? ? '' : r
62
+ end
63
+ end
64
+
65
+ # Parses specified phrase.
66
+ def parse( phrase = nil )
67
+ init(phrase) unless phrase.nil?
68
+ throw( "No phrase given" ) if phrase.nil?
69
+ phrase
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'babelfish/phrase/node'
3
+ require 'babelfish/phrase/pluralizer'
4
+ require 'babelfish/phrase/compiler'
5
+
6
+ class Babelfish
7
+ module Phrase
8
+ # Babelfish AST pluralization node.
9
+ class PluralForms < Node
10
+
11
+ class << self
12
+ attr_accessor :sub_data, :compiler
13
+
14
+ end
15
+
16
+ PluralForms.sub_data = []
17
+
18
+ attr_accessor :forms, :name, :compiled, :locale
19
+
20
+ def to_ruby_method
21
+ unless compiled
22
+ PluralForms.compiler ||= Babelfish::Phrase::Compiler.new
23
+ forms[:regular].map! do |form|
24
+ PluralForms.compiler.compile( form )
25
+ end
26
+ new_strict = {}
27
+ forms[:strict].each_pair do |key, form|
28
+ new_strict[key] = PluralForms.compiler.compile( form )
29
+ end
30
+ forms[:strict].replace(new_strict)
31
+ self.compiled = true
32
+ end
33
+
34
+ rule = Babelfish::Phrase::Pluralizer::find_rule( locale )
35
+
36
+ PluralForms.sub_data << [
37
+ rule,
38
+ forms[:strict],
39
+ forms[:regular],
40
+ ]
41
+
42
+ return _to_ruby_method( name, PluralForms.sub_data.length - 1 );
43
+ end
44
+
45
+ def _to_ruby_method( name, index )
46
+ lambda do |params|
47
+ value = params[name].to_f
48
+ rule, strict_forms, regular_forms = PluralForms.sub_data[index]
49
+ r = nil
50
+ if value.nan?
51
+ warn "#{name} parameter is not numeric"
52
+ r = regular_forms[-1]
53
+ else
54
+ r = strict_forms[value] || regular_forms[rule.call(value)] || regular_forms[-1];
55
+ end
56
+ return r if r.kind_of?(String)
57
+ return '' if r.nil?
58
+ r.call(params)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,64 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'babelfish/phrase/plural_forms'
3
+
4
+ class Babelfish
5
+ module Phrase
6
+ # Babelfish plurals syntax parser.
7
+
8
+ # Returns { script_forms: {}, regular_forms: [] }
9
+
10
+ # Every plural form represented as AST.
11
+
12
+ class PluralFormsParser
13
+ attr_accessor :phrase, :strict_forms, :regular_forms
14
+
15
+ class << self
16
+ attr_accessor :phrase_parser
17
+ end
18
+
19
+ def phrase_parser
20
+ PluralFormsParser.phrase_parser ||= Babelfish::Phrase::Parser.new
21
+ end
22
+
23
+
24
+ # Instantiates parser.
25
+ def initialize( phrase = nil )
26
+ init( phrase ) unless phrase.nil?
27
+ end
28
+
29
+ # Initializes parser. Should not be called directly.
30
+ def init( phrase )
31
+ self.phrase = phrase
32
+ self.regular_forms = []
33
+ self.strict_forms = {}
34
+ end
35
+
36
+ # Parses specified phrase.
37
+ def parse( phrase )
38
+ init( $phrase ) unless phrase.nil?
39
+
40
+ # тут проще регуляркой
41
+ forms = phrase.split( /(?<!\\)\|/ )
42
+
43
+ forms.each do |form|
44
+ value = nil
45
+ if form =~ /^=([0-9]+)\s*(.+)$/
46
+ value, form = $1, $2
47
+ end
48
+ form = phrase_parser.parse( form )
49
+
50
+ if value.nil?
51
+ regular_forms.push form
52
+ else
53
+ strict_forms[value] = form
54
+ end
55
+ end
56
+
57
+ return {
58
+ strict: strict_forms,
59
+ regular: regular_forms,
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end