vidarh / writing-a-compiler-in-ruby

Code from my series on writing a Ruby compiler in Ruby

writing-a-compiler-in-ruby / operators.rb
100644 125 lines (101 sloc) 4.262 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
require 'set'
 
# Represents Operators within the language.
# An operator is defined by up to 6 components:
#
# - Priority (pri)
# - Unique Name / Identifier (sym)
# - Type (prefix, infix or suffix)
# - Arity (how many arguments? Most operators are either unary or binary)
# - Minarity (The minimum arity, for operators with optional arguments)
# - Association: Whether the operator binds to the left or right argument first (the default is right)
#
# The priority defines the precedence-rules for the parser.
# Smaller numbers mean higher priority.
class Oper
  attr_accessor :pri, :sym, :type, :arity, :minarity,:assoc
 
  def initialize(pri, sym, type, arity = nil, minarity = nil, assoc = :right)
    @pri = pri
    @sym = sym
    @type = type
    if !arity
      @arity = 0 if type == :lp
      @arity = 1 if type != :lp
      @arity = 2 if type == :infix
    else
      @arity = arity
    end
    @minarity = minarity || @arity
    @assoc = assoc
  end
 
  def self.expect(s)
    # expect any of the defined operators
    # if operator found, return it's symbol (e.g. "*" -> :*)
    # otherwise simply return nil,
    # as no operator was found by scanner
    Operators.keys.each do |op|
      if s.expect(op)
        return op.to_sym
      end
    end
    return nil
  end
end
 
 
# A hash of all operators within the language.
# The keys are the actual identifiers for each operator.
# The values are the operators themself (instances of the Oper class).
# The priorities (first argument to Oper.new) does not actually
# *mean* anything other than establish order. The only
# reason for gaps is convenience when having to change them
# during development.
Operators = {
  # "Fake" operator injected for blocks.
  "#block#" => Oper.new( 1, :block, :infix),
  "#flatten#" => Oper.new( 1, :flatten, :infix),
  "#,#" => Oper.new( 1, :comma, :infix,2,1),
  "or" => Oper.new( 1, :or, :infix),
  "and" => Oper.new( 1, :and, :infix),
 
  "=>" => Oper.new( 5, :pair, :infix),
  "&" => Oper.new( 5, :to_block, :prefix), # This will need to be treated like "*" when I add bitwise and.
 
  "return" => Oper.new( 6, :return, :prefix,1,0),
  "&&" => Oper.new( 6, :and, :infix),
  "||" => Oper.new( 6, :or, :infix),
 
  "=" => Oper.new( 6, :assign, :infix),
  "||=" => Oper.new( 6, :or_assign,:infix),
  "-=" => Oper.new( 6, :decr, :infix),
  "+=" => Oper.new( 6, :incr, :infix),
 
  "?" => Oper.new( 7, :ternif, :infix),
  ":" => Oper.new( 7, :ternalt, :infix),
  "<<" => Oper.new( 7, :<<, :infix),
 
  "<" => Oper.new( 9, :lt, :infix),
  "<=" => Oper.new( 9, :le, :infix),
  ">" => Oper.new( 9, :gt, :infix),
  ">=" => Oper.new( 9, :ge, :infix),
  "==" => Oper.new( 9, :eq, :infix),
  "!=" => Oper.new( 9, :ne, :infix),
  "<=>" => Oper.new( 9, :cmp, :infix),
 
  "+" => Oper.new( 10, :add, :infix),
  "-" => Oper.new( 10, :sub, :infix),
  "!" => Oper.new( 10, :not, :prefix),
 
 
  "/" => Oper.new( 20, :div, :infix),
  "*" => {
    :infix_or_postfix => Oper.new( 20, :mul, :infix),
    :prefix => Oper.new(100, :splat, :prefix)
  },
 
  # "Fake" operator for function calls
  "#call#" => Oper.new( 99, :call, :prefix,2,1),
  "," => Oper.new( 99, :comma, :infix, 2,1),
 
  # "Fake" operator for [] following a name
  "#index#" => Oper.new(100, :index, :infix),
  "." => Oper.new(100, :callm, :infix, 2,2,:left),
  "::" => Oper.new(100, :callm, :infix, 2,2,:left),
  ".." => Oper.new(100, :range, :infix), # FIXME: Check pri - probably not right.
 
 
  #### Parentheses ####
 
  "[" => Oper.new( 99, :array, :lp,1),
  "]" => Oper.new( 0, nil, :rp),
 
  "do" => Oper.new( 99, :block, :lp,1),
  "{" => Oper.new( 99, :hash_or_block, :lp,1),
  "#hash#" => Oper.new( 99, :hash, :lp,1),
  "}" => Oper.new( 0, nil, :rp),
 
  "(" => Oper.new( 99, nil, :lp),
  ")" => Oper.new( 0, nil, :rp),
 
}