eturk/marvin

View on GitHub
lib/marvin/parser.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength, Metrics/ParameterLists

require 'rltk/parser'

module Marvin

  # This parser takes in a stream of tokens, verifies the input is correct, and
  # outputs an abstract syntax tree.
  class Parser < RLTK::Parser
    include ::Marvin::AST

    production(:program) do
      clause('statements') { |s| Program.new(s) }
    end

    list(:statements, 'statement')

    production(:statement) do
      clause('assign')     { |s| s }
      clause('function')   { |s| s }
      clause('print')      { |s| s }
      clause('if')         { |s| s }
      clause('return')     { |s| s }
      clause('expression') { |s| s }
    end

    production(:assign) do
      clause('T_IDENT T_ASSIGN expression') do |i, _, e|
        Assignment.new(i, e)
      end
    end

    production(:print) do
      clause('T_PRINT T_LPAREN expression T_RPAREN') do |_, _, e, _|
        Print.new(e)
      end
    end

    production(:if) do
      clause('T_IF T_LPAREN test T_RPAREN block') do |_, _, test, _, body|
        If.new(test, body)
      end

      clause('T_IF T_LPAREN boolean T_RPAREN block') do |_, _, bool, _, body|
        If.new(Marvin::AST::EqualTo.new(bool, bool), body)
      end
    end

    list(:arg_keys, :T_IDENT, :T_COMMA)

    production(:function) do
      clause('T_FUNCTION T_IDENT T_LPAREN arg_keys T_RPAREN block') do |_, name, _, args, _, body|
        Function.new(name, args, body)
      end
    end

    production(:expression) do
      clause('integer') { |i| i }
      clause('float') { |i| i }
      clause('boolean') { |i| i }
      clause('string') { |i| i }
      clause('arithmetic') { |i| i }
      clause('test') { |i| i }
      clause('call') { |i| i }
    end

    production(:integer) do
      clause('T_INTEGER') { |i| Integer.new(i) }
    end

    production(:float) do
      clause('T_FLOAT') { |i| Float.new(i) }
    end

    production(:boolean) do
      clause('T_BOOLEAN') { |i| Boolean.new(i ? 1 : 0) }
    end

    production(:test) do
      clause('expression T_BOOLOP expression') do |left, op, right|
        case op
        when :==
          EqualTo.new(left, right)
        when :!=
          NotEqualTo.new(left, right)
        when :<
          LessThan.new(left, right)
        when :>
          GreaterThan.new(left, right)
        end
      end
    end

    production(:string) do
      clause('T_STRING') { |i| String.new(i) }
    end

    production('arithmetic') do
      clause('expression T_INTOP expression') do |left, op, right|
        case op
        when :+
          Addition.new(left, right)
        when :-
          Subtraction.new(left, right)
        when :*
          Multiplication.new(left, right)
        when :/
          Division.new(left, right)
        end
      end
    end

    list(:expression_list, 'expression', :T_COMMA)

    production(:arg_values) do
      clause('T_LPAREN expression_list T_RPAREN') { |_, e, _| e }
    end

    production('call') do
      clause('T_IDENT arg_values?') { |name, values| Call.new(name, values.to_a) }
    end

    production('return') do
      clause('T_RETURN expression') { |_, e| Call.new(:return, [e]) }
    end

    production(:block) do
      clause('T_LBRACKET statements T_RBRACKET') { |_, s, _| Block.new(s) }
    end

    finalize lookahead: true
  end
end