# frozen_string_literal: true module Rus3 module Parser # A simple parser to read a s-expression for Scheme. class SchemeParser < Parser # Indicates the version of the parser class PARSER_VERSION = "0.2.0" # Constructs the version string. def version vmsg = "(scheme-parser :version #{PARSER_VERSION})" vmsg += " #{Lexer.version}" super + " (#{vmsg})" end def initialize super @lexer = nil @curr_token = @peek_token = nil end # Parses a portion of or the whole program source then returns a # AST structure. def parse(program_source) @lexer = Lexer.new(program_source) parse_program end # :stopdoc: private def next_token if @peek_token @curr_token = @peek_token @peek_token = nil else @curr_token = @lexer.next end @curr_token end def peek_token if @peek_token.nil? @peek_token = @lexer.next end @peek_token end def current_token @curr_token end def push_back_token(token) @peek_token = token end # -> * def parse_program program = Rus3::AST.instantiate(:program) Kernel.loop { node = parse_expression program << node } program end TOKEN_START_DELIMITERS = [ # :nodoc: :lparen, # list: ( ... ) :vec_lparen, # vector: #( ... ) :bytevec_lparen, # bytevector: #u8( ... ) :quotation, # quotation: ' :backquote, # quasiquote: ` :comma, # used in quasiquote :comma_at, # used in quasiquote :comment_lparen, # comment start ] def start_delimiter?(token) TOKEN_START_DELIMITERS.include?(token.type) end # -> | # -> * # -> | | | # | # | | | # # -> | def parse_expression if start_delimiter?(peek_token) parse_compound_expression else parse_simple_expression end end # -> | # -> | | | def parse_simple_expression type, literal = *next_token type = :peculiar_identifier if type == :op_proc Rus3::AST.instantiate(type, literal) end def parse_identifier Rus3::AST.instantiate(:identifier, next_token.literal) end # -> | # | # def parse_compound_expression node = nil token = peek_token case token.type when :vec_lparen node = parse_vector when :lparen node = parse_list_expression when :quotation node = parse_quotation else raise Rus3::SchemeSynaxError, token.to_a end node end # -> # | # | # | # | # | # | # | # | # def parse_list_expression node = nil next_token # read :lparen case peek_token.type when :rparen # an empty list node = Rus3::AST.instantiate(:list) next_token # skip :rparen when :identifier case peek_token.literal when "lambda" # lambda expression node = parse_lambda_expression when "if" # conditional node = parse_conditional when "set!" # assignment node = parse_assignment when "let-syntax", "letrec-syntax" # macro block node = parse_macro_block when "define", "define-syntax", "define-values", "define-record-type", "begin" node = parse_definition else node = parse_derived_expression node = parse_macro_use if node.nil? end end node || parse_procedure_call end def parse_macro_use nil end # -> ( * ) def parse_procedure_call proc_call_node = Rus3::AST.instantiate(:procedure_call, current_token.literal) proc_call_node.operator = parse_operator Kernel.loop { if peek_token.type == :rparen next_token # skip :rparen break end proc_call_node.add_operand(parse_operand) } proc_call_node end # -> def parse_operator parse_expression end # -> def parse_operand parse_expression end # -> ( lambda ) # -> * # -> # ... see parse_definition def parse_lambda_expression lambda_node = Rus3::AST.instantiate(:lambda_expression, next_token.literal) lambda_node.formals = parse_formals lambda_node.body = read_body next_token # skip :rparen lambda_node end # -> ( * ) | | # ( + . ) def parse_formals token = next_token formals = nil if token.type == :lparen formals = Rus3::AST.instantiate(:list) Kernel.loop { if peek_token.type == :rparen next_token break end formals << Rus3::AST.instantiate(:identifier, next_token.literal) } else formals = Rus3::AST.instantiate(:identifier, token.literal) end formals end # -> * def read_body body = [] Kernel.loop { break if peek_token.type == :rparen # end of body << parse_expression } body end # -> ( if ) def parse_conditional if_node = Rus3::AST.instantiate(:conditional, next_token.literal) if_node.test = parse_test if_node.consequent = parse_consequent if peek_token.type != :rparen if_node.alternate = parse_alternate end next_token # skip :rparen if_node end # -> def parse_test parse_expression end # -> def parse_consequent parse_expression end # -> | # -> "" def parse_alternate parse_expression end # -> ( set! ) def parse_assignment assignment_node = Rus3::AST.instantiate(:assignment, next_token.literal) assignment_node.identifier = parse_identifier assignment_node.expression = parse_expression next_token # skip :rparen assignment_node end def parse_macro_block nil end # -> ( define ) | # ( define ( ) ) | # | # ( define-values ) | # ( define-record-type # * ) | # ( begin * ) # # -> * | # * . # -> ( * ) # -> ( ) | # ( ) # -> # -> # -> # -> ( define-syntax ) # -> ... # : def parse_definition case peek_token.literal when "define" parse_identifier_definition when "define-syntax" nil # not implemented yet when "define-values" nil # not implemented yet when "define-record-type" nil # not implemented yet when "begin" nil # not implemented yet else raise Rus3::SchemeSynaxError, token.to_a end end # ( define ) # ( define ( ) ) # # ( define foo 3 ) # ( define bar ( lambda ( x y ) ( + x y ))) # ( deifne ( hoge n m ) ( * n m )) def parse_identifier_definition token = next_token # define define_node = Rus3::AST.instantiate(:identifier_definition, token.literal) case peek_token.type when :lparen next_token # skip :lparen # procedure name define_node.identifier = parse_identifier def_formals = Rus3::AST.instantiate(:list) Kernel.loop { if peek_token.type == :rparen next_token break end def_formals << parse_identifier } lambda_node = Rus3::AST.instantiate(:lambda_expression, nil) lambda_node.formals = def_formals lambda_node.body = read_body next_token # skip :rparen define_node.expression = lambda_node when :identifier define_node.identifier = parse_identifier define_node.expression = parse_expression next_token else raise Rus3::SchemeSynaxError, current_token.to_a end define_node end # -> ' | ( quote ) def parse_quotation token = next_token quote_node = Rus3::AST.instantiate(:quotation, token.literal) quote_node << parse_datum quote_node end # -> #( * ) def parse_vector parse_data_to_rparen end def parse_data_to_rparen token = next_token ast_type = nil case token.type when :lparen ast_type = :list when :vec_lparen ast_type = :vector else ast_type = :list end node = Rus3::AST.instantiate(ast_type, nil) raise Rus3::SchemeSyntaxError, token.to_a unless node.branch? Kernel.loop { token = peek_token break if token.type == :rparen node << parse_datum } next_token # skip :rparen node end # -> | def parse_datum if start_delimiter?(peek_token) parse_compound_datum else parse_simple_datum end end # -> | | | # | # -> # # See `parse_simple_expression`. def parse_simple_datum parse_simple_expression end # -> | | | # -> def parse_compound_datum case peek_token.type when :lparen parse_list when :vec_lparen parse_vector else raise Rus3::SchemeSyntaxError, peek_token.to_a end end # -> ( * ) | ( + . ) def parse_list parse_data_to_rparen end DERIVED_IDENTIFIERS = [ "cond", "case", "and", "or", "when", "unless", "let", "let*", "letrec", "letrec*", "let-values", "let*-values", "begin", "do", "delay", "delay-force", "parameterize", "guard", "case-lambda", ] # -> # ( cond + ) | # ( cond * ( else ) ) | # ( case + ) | # ( case * ( else ) ) | # ( case * ( else => ) ) | # ( and * ) | # ( or * ) | # ( when ) | # ( unless ) | # ( let ( * ) ) | # ( let ( * ) ) | # ( let* ( * ) ) | # ( letrec ( * ) ) | # ( letrec* ( * ) ) | # ( let-values ( * ) ) | # ( let*-values ( * ) ) | # ( begin ) | # ( do ( * ) ( ) * ) | # ( delay ) | # ( delay-force ) | # ( parameterize ( ( )* ) ) | # ( guard ( * ) ) | # ( case-lambda * ) | # def parse_derived_expression node = nil token = next_token if token.type == :identifier if DERIVED_IDENTIFIERS.include?(token.literal) method_name = compose_method_name("parse_", token.literal).intern method = self.method(method_name) node = method.call else node = parse_quasiquotation end end push_back_token(token) if node.nil? node end SCM_CHAR_TO_RB_MAP = { "*" => "_star", "-" => "_", } def compose_method_name(prefix, type_name) converted_name = type_name.gsub(/[*\-]/, SCM_CHAR_TO_RB_MAP) prefix + converted_name end def not_implemented_yet(feature) raise Rus3::NotImplementedYetError, feature end # ( cond + ) # ( cond * ( else ) ) def parse_cond cond_node = Rus3::AST.instantiate(:cond, current_token.literal) Kernel.loop { if peek_token.type == :rparen next_token # skip :rparen break end cond_node.add_clause(parse_cond_clause) } cond_node end # -> ( ) | # ( ) | # ( => ) # -> # -> # -> * # -> def parse_cond_clause clause_node = Rus3::AST.instantiate(:cond_clause) next_token # skip :lparen clause_node.test = parse_test Kernel.loop { if peek_token.type == :rparen next_token break end clause_node.add_expression(parse_expression) } clause_node end def prase_test parse_expression end def parse_case not_implemented_yet("case") end def parse_and not_implemented_yet("and") end def parse_or not_implemented_yet("or") end def parse_when not_implemented_yet("when") end def parse_unless not_implemented_yet("unless") end # ( let ( * ) ) | # -> ( ) # # `Named let` has not supported yet. # ( let ( * ) ) def parse_let let_node = Rus3::AST.instantiate(:let, current_token.literal) let_node.bind_specs = parse_bind_specs let_node.body = read_body next_token # skip :rparen let_node end def parse_bind_specs specs_node = Rus3::AST.instantiate(:list) next_token # skip :lparen Kernel.loop { if peek_token.type == :rparen next_token # skip :rparen break end specs_node << parse_bind_spec } specs_node end def parse_bind_spec spec_node = Rus3::AST::instantiate(:bind_spec) next_token # skip :lpraren spec_node.identifier = parse_identifier spec_node.expression = parse_expression next_token # skip :rparen spec_node end def parse_let_star not_implemented_yet("let*") end def parse_letrec not_implemented_yet("letrec") end def parse_letrec_star not_implemented_yet("letrec*") end def parse_let_values not_implemented_yet("let-values") end def parse_let_star_values not_implemented_yet("let*-values") end def parse_begin not_implemented_yet("begin") end def parse_do not_implemented_yet("do") end def parse_delay not_implemented_yet("delay") end def parse_delay_force not_implemented_yet("delay-force") end def parse_parameterize not_implemented_yet("parameterize") end def parse_guard not_implemented_yet("guard") end def parse_case_lambda not_implemented_yet("case-lambda") end def parse_quasiquotation nil end # :startdoc: end end end