# frozen_string_literal: true module Jsrb class JSStatementContext attr_reader :stacks def initialize @var_count = 0 @stacks = [[]] end def gen_var_name! @var_count += 1 "_v#{@var_count}" end def push(*stmt) @stacks.last.push(*stmt) nil end def new_expression(object = nil) ExprChain.new(self, object) end def new_block _result, statements = in_new_context { yield } { type: 'BlockStatement', body: statements } end # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/AbcSize def ruby_to_js_ast(rbv) case rbv when ExprChain rbv.unwrap! when Proc chainables = rbv.parameters.map do |_type, name| new_expression.member!(name.to_s) end result, statements = in_new_context { rbv.call(*chainables) } if result statements.push( type: 'ReturnStatement', argument: ruby_to_js_ast(result) ) end { type: 'FunctionExpression', id: nil, params: chainables.map { |arg| ruby_to_js_ast(arg) }, body: { type: 'BlockStatement', body: statements } } when Hash var_name = gen_var_name! statements = rbv.map do |key, v| { type: 'ExpressionStatement', expression: new_expression.member!(var_name).member!(key).assign!(v).unwrap! } end { type: 'CallExpression', callee: { type: 'FunctionExpression', id: nil, params: [new_expression.member!(var_name).unwrap!], body: { type: 'BlockStatement', body: statements.concat( [ { type: 'ReturnStatement', argument: new_expression.member!(var_name).unwrap! } ] ) } }, arguments: [ { type: 'ObjectExpression', properties: [] } ] } when Array { type: 'ArrayExpression', elements: rbv.map { |v| ruby_to_js_ast(v) } } when TrueClass, FalseClass, String, NilClass build_literal_ast(rbv) when Float::INFINITY new_expression.member!('Number').member!('POSITIVE_INFINITY').unwrap! when -Float::INFINITY new_expression.member!('Number').member!('NEGATIVE_INFINITY').unwrap! when Float if rbv.nan? new_expression.member!('Number').member!('NaN').unwrap! else build_finite_number_ast(rbv) end when Integer build_finite_number_ast(rbv) when Symbol ruby_to_js_ast(rbv.to_s) else raise "Can't convert to JavaScript AST from: #{rbv.inspect}" end end # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/AbcSize private def in_new_context @stacks.push([]) result = yield statements = @stacks.pop [result, statements] end def build_finite_number_ast(value) if value < 0 { type: 'UnaryExpression', operator: '-', argument: build_literal_ast(-value) } else build_literal_ast(value) end end def build_literal_ast(value) { type: 'Literal', value: value } end end end