lib/jsrb/base.rb in jsrb-0.2.1 vs lib/jsrb/base.rb in jsrb-0.3.0

- old
+ new

@@ -1,15 +1,16 @@ # frozen_string_literal: true module Jsrb - # Base is a centralized class for Jsrb template. - # `jsrb`, accessed from views (i.e. `*.js.jsrb` files), is an instance of Jsrb::Base. + # `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 some utilities to push statements, - # construct expressions and generate the final JavaScript code, - # with preserving a statement context to build the abstract syntax tree. + # `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 # @@ -22,76 +23,206 @@ sourceType: 'script', body: @context.stacks.first end # - # Pushes a VariableDeclaration to the current context - # and returns an access to created identifier. + # **Pushes** an ExpressionStatement to the current context # - # @param optional [Symbol] name a name of identifier, autogenerated if not specified. + # @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 (optional) - # @return [Jsrb::ExprChain] the expression that represents constructed new identifier + # @yieldreturn an initializer expression + # @return [Jsrb::ExprChain] the expression which represents a newly created identifier def var!(id = nil) id ||= @context.gen_var_name! - if block_given? - raw_expr = yield - val = raw_expr.is_a?(ExprChain) ? raw_expr : expr(@context.ruby_to_js_ast(raw_expr)) - val.as_variable_declaration!(id) + 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 - expr.as_variable_declaration!(id) + @context.push( + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: id.to_s + } + }], + kind: 'var' + ) end - expr.member!(id) + expr[id] end # - # Constructs a new conditional chain that **pushes an IfStatement** to the current context - # after the chain ended. + # **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 **returns a conditional expression** after the chain ended. + # **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. + # **Constructs** a new expression chain with a given JavaScript AST node. # - # @param optional [convertible ruby values] object represents JavaScript expression 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. + # 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 you - # rewrite in Ruby*. + # 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 - + # @private def code_generator_class @code_generator_class ||= Object.const_get(code_generator) end end end