Class: Jsrb::ExprChain

Inherits:
Object
  • Object
show all
Defined in:
lib/jsrb/expr_chain.rb

Overview

ExprChain is a builder class that constructs JavaScript expressions in natural phrase by chaining methods.

Note that ExprChain does NOT add any statement to be rendered. If you want to add the expression as an ExpressionStatement, use Jsrb::Base#do!:

js.do!(obj.foo = 100) # pushes a statement `obj.foo = 100;`.
# where `obj.foo = 100` is still a chainable ExprChain instance,
# so that we have to explicitly push it as statement with `js.do!`.

Constant Summary collapse

JS_LOGICAL_OPS =
%w[&& ||].freeze

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Responds to arbitrary method names

Examples:

obj = js.expr[:obj]

# If the last character of the method name is neither `=` nor `?`,
# and no argument and no block given,
# it constructs a **MemberExpression**.

obj.foo # => ExprChain `obj.foo`

# If the last character of the method name is neither `=` nor `?`,
# at least one argument or block given,
# it constructs a **CallExpression** of MemberExpression.

obj.foo(100) # => ExprChain `obj.foo(100)`
obj.foo { |x| x + 1 } # ExprChain `obj.foo(function (x) { return x; })`

# If the last character of the method name is `=`,
# it constructs **AssignmentExpression** of a member assignment expression.

(obj.foo = 100) # ExprChain `obj.foo = 100`

# If the last character of the method name is `?`,
# it constructs **ConditionalExpression**.

obj.foo?(100, 200) # => ExprChain `obj.foo ? 100 : 200`

62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/jsrb/expr_chain.rb', line 62

def method_missing(name, *args, &block)
  if (matches = name.to_s.match(/\A(.+)\?\z/))
    self[matches[1]].cond?(*args)
  elsif (matches = name.to_s.match(/\A(.+)=\z/))
    self[matches[1]].set(*args)
  elsif (function_name = self.class.custom_chains[name.to_sym])
    _bind_chain!(function_name, *args, &block)
  elsif args.empty? && !block
    self[name.to_s]
  else
    self[name.to_s].call(*args, &block)
  end
end

Class Method Details

.add_custom_chain(chain_method_name, function_name) ⇒ Object

Adds a new chain method.

Examples:

Jsrb::ExprChain.add_custom_chain('log_here', '__tap_log__')

js.expr[:foo][:bar].log_here # => ExprChain `__tap_log__(foo['bar'])`

Parameters:

  • chain_method_name (Symbol)

    name of a chain method

  • function_name (Symbol)

    name of wrapper function


278
279
280
281
# File 'lib/jsrb/expr_chain.rb', line 278

def add_custom_chain(chain_method_name, function_name)
  @_custom_chains ||= {}
  @_custom_chains[chain_method_name] = function_name
end

Instance Method Details

#[](value) ⇒ Jsrb::ExprChain

Constructs a MemberExpression

Examples:

obj = js.expr[:someObj]
obj[:field] # => ExprChain `someObj['field']`

Parameters:

  • value (String, Symbol)

    name of member

Returns:


84
85
86
87
88
89
90
91
92
93
94
# File 'lib/jsrb/expr_chain.rb', line 84

def [](value)
  if @object
    self.class.new @context, type: 'MemberExpression',
                             computed: true,
                             object: @object,
                             property: @context.ruby_to_js_ast(value)
  else
    self.class.new @context, type: 'Identifier',
                             name: value.to_s
  end
end

#call(*args) { ... } ⇒ Jsrb::ExprChain

Constructs a CallExpression.

Examples:

console = js.expr[:console]
console.log.('foo') # => ExprChain `console.log('foo')`
console.log.call('bar') # => ExprChain `console.log('foo')`

Parameters:

Yields:

  • optional block as a final function argument

Returns:

Raises:

  • (ArgumentError)

124
125
126
127
128
129
130
131
132
133
134
# File 'lib/jsrb/expr_chain.rb', line 124

def call(*args, &block)
  raise ArgumentError, "Can't chain call on empty context" unless @object

  js_args = args.map do |arg|
    @context.ruby_to_js_ast(arg)
  end
  js_args << @context.ruby_to_js_ast(block) if block
  self.class.new @context, type: 'CallExpression',
                           callee: @object,
                           arguments: js_args
end

#cond?(consequent, alternate) ⇒ Jsrb::ExprChain

Constructs a ConditionalExpression whose test expression is the current expression.

Examples:

(js.expr[:height] < 300).cond?('lo', 'hi') # => ExprChain `(height < 300) ? 'lo' : 'hi'`

Parameters:

  • consequent (Jsrb::ExprChain, convertible ruby values)

    expression used in consequent

  • alternate (Jsrb::ExprChain, convertible ruby values)

    expression used in alternate

Returns:

Raises:

  • (ArgumentError)

215
216
217
218
219
220
221
222
# File 'lib/jsrb/expr_chain.rb', line 215

def cond?(consequent, alternate)
  raise ArgumentError, "Can't chain cond? on empty context" unless @object

  self.class.new @context, type: 'ConditionalExpression',
                           test: @object,
                           consequent: @context.ruby_to_js_ast(consequent),
                           alternate: @context.ruby_to_js_ast(alternate)
end

#forall(*args) ⇒ Jsrb::ExprChain

Constructs a FunctionExpression whose returned body is the current context.

Examples:

(js.expr[:x] * 2).forall(:x) # => ExprChain `function (x) { return x * 2; }`

Parameters:

  • args (String, Symbol)

    argument identifiers

Returns:

Raises:

  • (ArgumentError)

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/jsrb/expr_chain.rb', line 231

def forall(*args)
  raise ArgumentError, "Can't chain forall on empty context" unless @object

  self.class.new @context, type: 'FunctionExpression',
                           id: nil,
                           params: args.map { |arg|
                             {
                               type: 'Identifier',
                               name: arg.to_s
                             }
                           },
                           body: {
                             type: 'BlockStatement',
                             body: [
                               {
                                 type: 'ReturnStatement',
                                 argument: @object
                               }
                             ]
                           }
end

#new(*args) ⇒ Jsrb::ExprChain

Constructs a NewExpression.

Examples:

js.expr[:Date].new # => ExprChain `new Date`
js.expr[:Date].new(2020, 1, 1) # => ExprChain `new Date(2020, 1, 1)`

Parameters:

Returns:

Raises:

  • (ArgumentError)

144
145
146
147
148
149
150
151
152
153
# File 'lib/jsrb/expr_chain.rb', line 144

def new(*args)
  raise ArgumentError, "Can't chain new on empty context" unless @object

  arguments = args.map do |arg|
    @context.ruby_to_js_ast(arg)
  end
  self.class.new @context, type: 'NewExpression',
                           callee: @object,
                           arguments: arguments
end

#op(operator, *args) ⇒ Jsrb::ExprChain

Constructs a UnaryExpression, BinaryExpression or LogicalExpression.

All ruby-overridable operators can also be used: ** + - * / % >> << & ^ | <= < > >= == === != ! && ||

Examples:

(js.expr[:n] < 100) # => ExprChain `n < 100`
!(js.expr[:b]) # => ExprChain `!b`
js.expr[:a].op('||', 'default') # => ExprChain `a || 'default'`

Parameters:

  • operator (String, Symbol)

    operator

  • args (Jsrb::ExprChain, convertible ruby values)

    RHS value for binary operators

Returns:

  • (Jsrb::ExprChain)

    a chain represents UnaryExpression, BinaryExpression or LogicalExpression

Raises:

  • (ArgumentError)

167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/jsrb/expr_chain.rb', line 167

def op(operator, *args)
  raise ArgumentError, "Can't chain op on empty context" unless @object

  opstr = operator.to_s
  if args.size == 1
    _binary_op(opstr, *args)
  elsif args.empty?
    _unary_op(opstr)
  else
    raise ArgumentError, "#{opstr} is not a valid operator"
  end
end

#set(value) ⇒ Jsrb::ExprChain

Constructs a AssignmentExpression whose RHS is the current expression.

Examples:

x = js.expr[:x]
y = js.expr[:y]
y.set(x.set 100) # => ExprChain `x = y = 100`

Parameters:

  • value (Jsrb::ExprChain, convertible ruby values)

    RHS value of assignment

Returns:

Raises:

  • (ArgumentError)

105
106
107
108
109
110
111
112
# File 'lib/jsrb/expr_chain.rb', line 105

def set(value)
  raise ArgumentError, "Can't chain set on empty context" unless @object

  self.class.new @context, type: 'AssignmentExpression',
                           operator: '=',
                           left: @object,
                           right: @context.ruby_to_js_ast(value)
end