cldr-plurals 1.0.0

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