# frozen_string_literal: true module LIT module Parser # @api private # @since 0.1.0 class ASTBuilder def initialize(raw_ast) @raw_ast = raw_ast end def build statements = @raw_ast.fetch("statements").map { |raw_stmt| build_statement(raw_stmt) } AST::Program.new(statements) end private def build_statement(raw_stmt) type = raw_stmt.fetch("type") case type when "type_declaration_statement" build_type_declaration_statement(raw_stmt.fetch("statement")) when "endpoint_statement" build_endpoint_statement(raw_stmt) else raise InvalidASTError, "invalid statement type: `#{type}`" end end def build_type_declaration_statement(raw_stmt) name = raw_stmt.fetch("name") type = raw_stmt.fetch("type") case type when "struct" fields = build_struct_fields(raw_stmt.fetch("fields")) AST::TypeDeclarationStatement::Struct.new(name, fields) when "enum" variants = build_enum_variants(raw_stmt.fetch("variants")) AST::TypeDeclarationStatement::Enum.new(name, variants) when "type_alias" type = build_type(raw_stmt.fetch("reference")) AST::TypeDeclarationStatement::TypeAlias.new(name, type) else raise InvalidASTError, "invalid type declaration type: `#{type}`" end end def build_endpoint_statement(raw_stmt) AST::EndpointStatement.new( raw_stmt.fetch("name"), build_type(raw_stmt.fetch("request")), build_type(raw_stmt.fetch("response")), ) end def build_struct_fields(raw_fields) raw_fields.reduce({}) do |acc, (field_name, field_data)| acc.merge(field_name.to_sym => build_type(field_data.fetch("reference"))) end end def build_enum_variants(raw_variants) raw_variants.reduce({}) do |acc, (variant_name, variant_data)| type = variant_data.fetch("type") variant = case type when "unit" then AST::EnumVariant::Unit when "struct" AST::EnumVariant::Struct.new( build_struct_fields(variant_data.fetch("fields")), ) else raise InvalidASTError, "invalid variant type: `#{type}`" end acc.merge(variant_name.to_sym => variant) end end # rubocop:disable Metrics/MethodLength def build_type(raw_type) type = raw_type.fetch("type") case type when "type_alias" AST::Type::Alias.new(raw_type.fetch("name")) when "struct" AST::Type::AnonymousStruct.new(build_struct_fields(raw_type.fetch("fields"))) when "enum" AST::Type::AnonymousEnum.new(build_enum_variants(raw_type.fetch("variants"))) when "primitive_type" value = raw_type.fetch("value") case value when "String" then AST::Type::Primitive::String when "Integer" then AST::Type::Primitive::Integer when "Float" then AST::Type::Primitive::Float when "Boolean" then AST::Type::Primitive::Boolean when "Option" then AST::Type::Primitive::Option.new(build_type(raw_type.fetch("attrs"))) when "Array" then AST::Type::Primitive::Array.new(build_type(raw_type.fetch("attrs"))) when "Map" attrs = raw_type.fetch("attrs") AST::Type::Primitive::Map.new( build_type(attrs.fetch("key_type")), build_type(attrs.fetch("value_type")), ) else raise InvalidASTError, "invalid primitive type: `#{value}`" end else raise InvalidASTError, "invalid type: `#{type}`" end end # rubocop:enable Metrics/MethodLength end end end