# frozen_string_literal: true module Jsrb # `Jsrb::Base` is a centralized class for Jsrb template. # `js`, accessed from views (i.e. `*.jsrb` files), is an instance of Jsrb::Base. # # `Jsrb::Base` provides the interface for pushing statements, # constructing expressions and generating JavaScript outputs # with handling an internal statement context properly. class Base # @private def initialize @context = JSStatementContext.new end # # Generates executable JavaScript code from current context. # # @return [String] generated executable JavaScript code def generate_code generator = self.class.code_generator_class.new generator.call type: 'Program', sourceType: 'script', body: @context.stacks.first end # # **Pushes** an ExpressionStatement to the current context # # @example # js.do!(js.expr[:x].set(100)) # # => # # x = 100; # @param [Jsrb::ExprChain] expr target expression. # @return [nil] def do!(expr) ast = expr.object raise ArgumentError, 'Expression is empty' unless ast @context.push( type: 'ExpressionStatement', expression: ast ) end # # **Pushes** an assignment statement `lhs = rhs;`. # This is a short hand of `js.do! lhs_expr.set(rhs_expr)` # # @example # a = js.var! :a # js.set!(a, 'dog') # # => # # var a; # # a = 'dog'; # # # directly pass a symbol of identifier name # js.set!(:x, 100) # # => # # x = 100; # @param [Jsrb::ExprChain, String, Symbol] expr target expression. # @return [nil] def set!(lhs, rhs) lhs_expr = lhs.is_a?(ExprChain) ? lhs : expr(lhs) do! lhs_expr.set(rhs) end # # **Pushes** a VariableDeclaration to the current context # and returns an access to the created identifier. # @example # name = js.var!(:name) { 'foo' } # # var name = 'foo'; # # ary = js.var! :ary # # var ary; # # obj = js.var! :obj # # var obj; # # result = js.var! # # var _v1; //<- auto generate variable name # @param [Symbol] name a name of identifier, autogenerated if not given # @yield optional block for initializer # @yieldreturn an initializer expression # @return [Jsrb::ExprChain] the expression which represents a newly created identifier def var!(id = nil) id ||= @context.gen_var_name! val_ast = if block_given? raw_expr = yield raw_expr.is_a?(ExprChain) ? raw_expr.unwrap : @context.ruby_to_js_ast(raw_expr) end if val_ast @context.push( type: 'VariableDeclaration', declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: id.to_s }, init: val_ast }], kind: 'var' ) else @context.push( type: 'VariableDeclaration', declarations: [{ type: 'VariableDeclarator', id: { type: 'Identifier', name: id.to_s } }], kind: 'var' ) end expr[id] end # # **Starts** a new conditional chain that pushes an IfStatement # to the current context at end. # # @example # js.if!(expr1) { # # .. # }.elsif(expr2) { # # .. # }.else { # # .. # } # # => # # // The actual output will have certain immediate functions # # // that preserve variable scope for each case. # # if (..expr1..) { # # // .. # # } else if (..expr2..) { # # // .. # # } else { # # // .. # # } # # # If you don't have else clause, close with `#end`. # js.if!(expr1) { # # .. # }.elsif(expr2) { # # .. # }.end # # => # # if (..expr1..) { # # // .. # # } else if (..expr2..) { # # // .. # # } # @param [Jsrb::ExprChain, convertible ruby values] cond_expr an expression for the test # @yield new context block for the consequent case # @return [Jsrb::CondChain] condition chainable instance def if!(cond_expr, &block) CondChain.new(@context, false).elsif(cond_expr, &block) end # # **Constructs** a new conditional chain that # represents a conditional expression at end. # # @example # result = var! # js.do! result, js.if(expr1) { # result_expr1 # }.elsif(expr2) { # result_expr2 # }.else { # result_expr3 # } # # => # # // The actual output will have certain immediate functions # # // that preserve variable scope for each case. # # var _v1; # # _v1 = (function() { # # if (..expr1..) { # # return ..result_expr1..; # # } else if (..expr2..) { # # return ..result_expr2..; # # } else { # # return ..result_expr3..; # # } # # })() # @param [Jsrb::ExprChain, convertible ruby values] cond_expr an expression for the test # @yield new context block for the consequent case # @return [Jsrb::CondChain] condition chainable instance def if(cond_expr, &block) CondChain.new(@context, true).elsif(cond_expr, &block) end # # **Constructs** a new expression chain with a given JavaScript AST node. # # @param [convertible ruby values] object represents JavaScript expression AST node # @return [Jsrb::ExprChain] chainable instance def expr(object = nil) @context.new_expression(object) end class << self # # Shows JavaScript generator class name, `'Jsrb::NotFastGenerator'` by default. # # ### **Help wanted!** # # *Jsrb::NotFastGenerator uses ExecJS and escodegen to generate JavaScript. # It could be more efficient and get better error messages if we # implement it in Ruby*. # # @return [String] class name of a code generator def code_generator @code_generator || 'Jsrb::NotFastGenerator' end attr_writer :code_generator # @private def code_generator_class @code_generator_class ||= Object.const_get(code_generator) end end end end