babelfish-ruby 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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