cldr-plurals 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'cldr-plurals/compiler/nodes'
4
+
5
+ module CldrPlurals
6
+ module Compiler
7
+ class Parser
8
+
9
+ OPERATORS = [
10
+ :modulo, :equals, :not_equals
11
+ ]
12
+
13
+ SAMPLES = [
14
+ :int_sample, :dec_sample
15
+ ]
16
+
17
+ class UnexpectedTokenError < StandardError; end
18
+
19
+ attr_reader :token_list, :stack, :counter
20
+
21
+ def initialize(token_list)
22
+ @token_list = token_list
23
+ @stack = []
24
+ @counter = 0
25
+ end
26
+
27
+ def parse
28
+ until eol? || SAMPLES.include?(current_token.type) do
29
+ condition
30
+ end
31
+
32
+ Rule.new(stack.pop)
33
+ end
34
+
35
+ private
36
+
37
+ # and/or
38
+ def condition
39
+ and_condition
40
+
41
+ while consume(:or)
42
+ and_condition
43
+ stack.push(
44
+ OrCondition.new(stack.pop, stack.pop)
45
+ )
46
+ end
47
+ end
48
+
49
+ def and_condition
50
+ relation
51
+
52
+ while consume(:and)
53
+ relation
54
+ stack.push(
55
+ AndCondition.new(stack.pop, stack.pop)
56
+ )
57
+ end
58
+ end
59
+
60
+ def relation
61
+ expr = expression
62
+
63
+ stack.push(
64
+ if OPERATORS.include?(current_token.type)
65
+ Relation.new(expr, operator, value_set)
66
+ else
67
+ expr
68
+ end
69
+ )
70
+ end
71
+
72
+ def expression
73
+ Expression.new(operand, operator, value_set)
74
+ end
75
+
76
+ def operand
77
+ token = current_token
78
+ consume!(:operand)
79
+ Operand.new(token.value)
80
+ end
81
+
82
+ def operator
83
+ token = current_token
84
+ consume!(*OPERATORS)
85
+ Operator.new(token.value)
86
+ end
87
+
88
+ def value_set
89
+ values = [value]
90
+
91
+ while current_token.type == :comma
92
+ consume!(:comma)
93
+ values << value
94
+ end
95
+
96
+ if values.size == 1
97
+ values.first
98
+ else
99
+ ValueSet.new(values)
100
+ end
101
+ end
102
+
103
+ def value
104
+ first_token = current_token
105
+ consume!(:number)
106
+
107
+ case current_token.type
108
+ when :range
109
+ consume!(:range)
110
+ second_token = current_token
111
+ consume!(:number)
112
+ Range.new(first_token.value, second_token.value)
113
+ else
114
+ first_token.value
115
+ end
116
+ end
117
+
118
+ def next_token
119
+ @counter += 1
120
+ current_token
121
+ end
122
+
123
+ def current_token
124
+ if eol?
125
+ @eol_token ||= Token.new(nil, :eol)
126
+ else
127
+ token_list[counter]
128
+ end
129
+ end
130
+
131
+ def consume!(*token_types)
132
+ unless consume(*token_types)
133
+ raise UnexpectedTokenError,
134
+ "expected '#{token_types.join(', ')}', got '#{current_token.type}'"
135
+ end
136
+ end
137
+
138
+ def consume(*token_types)
139
+ if token_types.include?(current_token.type)
140
+ next_token
141
+ true
142
+ else
143
+ false
144
+ end
145
+ end
146
+
147
+ def eol?
148
+ counter >= token_list.size
149
+ end
150
+
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: UTF-8
2
+
3
+ module CldrPlurals
4
+ module Compiler
5
+ class Token
6
+ attr_reader :value, :type
7
+
8
+ def initialize(value, type)
9
+ @value = value
10
+ @type = type
11
+ end
12
+ end
13
+
14
+ class Tokenizer
15
+
16
+ TOKENS = {
17
+ /@integer/ => :int_sample,
18
+ /@decimal/ => :dec_sample,
19
+ /\u2026/ => :infinite_set,
20
+ /~/ => :sample_range,
21
+ /and/ => :and,
22
+ /or/ => :or,
23
+ /[niftvw]/ => :operand,
24
+ /,/ => :comma,
25
+ /\.\./ => :range,
26
+ /%/ => :modulo,
27
+ /=/ => :equals,
28
+ /\!=/ => :not_equals,
29
+ /[\d]+/ => :number
30
+ }
31
+
32
+ ALL_TOKENS = Regexp.compile(
33
+ TOKENS.map { |r, _| r.source }.join('|')
34
+ )
35
+
36
+ def self.tokenize(text)
37
+ text.scan(ALL_TOKENS).each_with_object([]) do |token, ret|
38
+ found_type = TOKENS.each_pair do |regex, token_type|
39
+ break token_type if token =~ regex
40
+ end
41
+
42
+ if found_type.is_a?(Symbol)
43
+ ret << make_token(token, found_type)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def self.make_token(value, type)
51
+ Token.new(value, type)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: UTF-8
2
+
3
+ module CldrPlurals
4
+ class JavascriptEmitter < Compiler::Emitter
5
+ class << self
6
+
7
+ RUNTIME_VARS = %w(n i v w f t)
8
+
9
+ def emit_rules(rule_list)
10
+ parts = rule_list.rules.map do |rule|
11
+ "(#{emit_rule(rule)} ? '#{rule.name}' : "
12
+ end + ["'other'"]
13
+
14
+ chooser = "#{parts.join('')}#{')' * (parts.size - 1)}"
15
+ "(function(num, runtime) { #{build_runtime_vars}; return #{chooser}; })"
16
+ end
17
+
18
+ def emit_rule(rule)
19
+ emit(rule.root)
20
+ end
21
+
22
+ def emit_rule_standalone(rule)
23
+ "(function(n, i, f, t, v, w) { return #{emit_rule(rule)}; })"
24
+ end
25
+
26
+ protected
27
+
28
+ def build_runtime_vars
29
+ RUNTIME_VARS.map do |var|
30
+ "var #{var} = runtime.#{var}(num)"
31
+ end.join('; ')
32
+ end
33
+
34
+ def emit_or_condition(cond)
35
+ "(#{emit(cond.left)} || #{emit(cond.right)})"
36
+ end
37
+
38
+ def emit_and_condition(cond)
39
+ "(#{emit(cond.left)} && #{emit(cond.right)})"
40
+ end
41
+
42
+ def emit_expression(expr)
43
+ case expr.value
44
+ when CldrPlurals::Compiler::Range
45
+ neg = expr.operation.symbol == '!=' ? '!' : ''
46
+ operand_str = emit(expr.operand)
47
+ "#{neg}((#{operand_str} >= #{expr.value.start}) && (#{operand_str} <= #{expr.value.finish}))"
48
+ when CldrPlurals::Compiler::ValueSet
49
+ "(#{emit_value_set(expr.value, expr.operand, expr.operation)})"
50
+ else
51
+ emit_all(expr.operand, expr.operation, expr.value).join(' ')
52
+ end
53
+ end
54
+
55
+ def emit_relation(rel)
56
+ case rel.value
57
+ when CldrPlurals::Compiler::Range
58
+ expr = emit(rel.expression)
59
+ neg = rel.operation.symbol == '!=' ? '!' : ''
60
+ "#{neg}((#{expr} >= #{rel.value.start}) && (#{expr} <= #{rel.value.finish}))"
61
+ when CldrPlurals::Compiler::ValueSet
62
+ "(#{emit_value_set(rel.value, rel.expression, rel.operation)})"
63
+ else
64
+ emit_all(rel.expression, rel.operation, rel.value).join(' ')
65
+ end
66
+ end
67
+
68
+ def emit_value_set(value_set, operand, operator)
69
+ values = value_set.values.map do |value|
70
+ case value
71
+ when CldrPlurals::Compiler::Range
72
+ neg = operator.symbol == '!=' ? '!' : ''
73
+ operand_str = emit(operand)
74
+ "#{neg}((#{operand_str} >= #{value.start}) && (#{operand_str} <= #{value.finish}))"
75
+ else
76
+ "(#{emit(operand)} #{emit(operator)} #{emit(value)})"
77
+ end
78
+ end
79
+
80
+ if operator.symbol == '!='
81
+ values.join(' && ')
82
+ else
83
+ values.join(' || ')
84
+ end
85
+ end
86
+
87
+ def emit_operator(op)
88
+ case op.symbol
89
+ when '='
90
+ '=='
91
+ else
92
+ op.symbol
93
+ end
94
+ end
95
+
96
+ def emit_operand(op)
97
+ op.symbol
98
+ end
99
+
100
+ def emit_string(str)
101
+ str
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,108 @@
1
+ # encoding: UTF-8
2
+
3
+ module CldrPlurals
4
+ class RubyEmitter < Compiler::Emitter
5
+ class << self
6
+
7
+ RUNTIME_VARS = %w(n i v w f t)
8
+
9
+ def emit_rules(rule_list)
10
+ parts = rule_list.rules.map do |rule|
11
+ "(#{emit_rule(rule)} ? :#{rule.name} : "
12
+ end + [':other']
13
+
14
+ chooser = "#{parts.join('')}#{')' * (parts.size - 1)}"
15
+ "lambda { |num, runtime| #{build_runtime_vars}; #{chooser} }"
16
+ end
17
+
18
+ def emit_rule(rule)
19
+ emit(rule.root)
20
+ end
21
+
22
+ def emit_rule_standalone(rule)
23
+ "lambda { |n, i, f, t, v, w| #{emit_rule(rule)} }"
24
+ end
25
+
26
+ protected
27
+
28
+ def build_runtime_vars
29
+ RUNTIME_VARS.map do |var|
30
+ "#{var} = runtime.#{var}(num)"
31
+ end.join('; ')
32
+ end
33
+
34
+ def emit_or_condition(cond)
35
+ "(#{emit(cond.left)} || #{emit(cond.right)})"
36
+ end
37
+
38
+ def emit_and_condition(cond)
39
+ "(#{emit(cond.left)} && #{emit(cond.right)})"
40
+ end
41
+
42
+ def emit_expression(expr)
43
+ case expr.value
44
+ when CldrPlurals::Compiler::Range
45
+ neg = expr.operation.symbol == '!=' ? '!' : ''
46
+ "#{neg}(#{emit_range(expr.value)}).include?(#{emit(expr.operand)})"
47
+ when CldrPlurals::Compiler::ValueSet
48
+ "(#{emit_value_set(expr.value, expr.operand, expr.operation)})"
49
+ else
50
+ emit_all(expr.operand, expr.operation, expr.value).join(' ')
51
+ end
52
+ end
53
+
54
+ def emit_relation(rel)
55
+ case rel.value
56
+ when CldrPlurals::Compiler::Range
57
+ expr = emit(rel.expression)
58
+ neg = rel.operation.symbol == '!=' ? '!' : ''
59
+ "#{neg}(#{emit_range(rel.value)}).include?(#{expr})"
60
+ when CldrPlurals::Compiler::ValueSet
61
+ "(#{emit_value_set(rel.value, rel.expression, rel.operation)})"
62
+ else
63
+ emit_all(rel.expression, rel.operation, rel.value).join(' ')
64
+ end
65
+ end
66
+
67
+ def emit_value_set(value_set, operand, operator)
68
+ values = value_set.values.map do |value|
69
+ case value
70
+ when CldrPlurals::Compiler::Range
71
+ neg = operator.symbol == '!=' ? '!' : ''
72
+ "#{neg}(#{emit_range(value)}).include?(#{emit(operand)})"
73
+ else
74
+ "(#{emit(operand)} #{emit(operator)} #{emit(value)})"
75
+ end
76
+ end
77
+
78
+ if operator.symbol == '!='
79
+ values.join(' && ')
80
+ else
81
+ values.join(' || ')
82
+ end
83
+ end
84
+
85
+ def emit_range(range)
86
+ "#{emit(range.start)}..#{emit(range.finish)}"
87
+ end
88
+
89
+ def emit_operator(op)
90
+ case op.symbol
91
+ when '='
92
+ '=='
93
+ else
94
+ op.symbol
95
+ end
96
+ end
97
+
98
+ def emit_operand(op)
99
+ op.symbol
100
+ end
101
+
102
+ def emit_string(str)
103
+ str
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ module CldrPlurals
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'v8'
4
+ require 'yaml'
5
+ require 'spec_helper'
6
+ require 'cldr-plurals/javascript_runtime'
7
+
8
+ include CldrPlurals
9
+ include CldrPlurals::Compiler
10
+
11
+ describe 'ruby rules' do
12
+ each_rule do |locales, rule, samples|
13
+ context = V8::Context.new
14
+ js_code = JavascriptEmitter.emit_rule_standalone(rule)
15
+ rule_obj = context.eval(js_code)
16
+ runtime = context.eval(JavascriptRuntime.source)
17
+
18
+ samples.each do |sample_info|
19
+ context "#{sample_info[:type]} samples" do
20
+ sample_info[:samples].each do |sample|
21
+ it sample do
22
+ args = runtime.buildArgsFor(sample)
23
+ expect(rule_obj.call(*args)).to eq(true)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'ruby rule lists' do
32
+ each_rule_list do |rule_list, samples_per_name|
33
+ context rule_list.locale do
34
+ context = V8::Context.new
35
+ js_code = rule_list.to_code(:javascript)
36
+ rule_proc = context.eval(js_code)
37
+ runtime = context.eval(JavascriptRuntime.source)
38
+
39
+ samples_per_name.each_pair do |name, samples|
40
+ samples.each do |sample|
41
+ it "#{name} #{sample}" do
42
+ expect(rule_proc.call(sample, runtime)).to eq(name.to_s)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end