-
Notifications
You must be signed in to change notification settings - Fork 9
/
ante.rb
executable file
·161 lines (144 loc) · 3.92 KB
/
ante.rb
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env ruby
# encoding: utf-8
#
# Copyright (c) 2013-2104 Michael Dvorkin
#
# Ante is an esoteric programming language where all you've got is
# a deck of cards.
#
# 95% of this code was developed on the way back from RubyConf 2013
# during 5-hour flight from Miami to San Francisco.
#
class Array
def rank; self[0] end
def suit; self[1] end
def rank=(value); self[0] = value end
def suit=(value); self[1] = value end
end
class Ante
def initialize()
@♦, @♥, @♠, @♣ = 0, 0, 0, 0
@line, @pc = 0, 0
@code, @labels = [], {}
end
def run(source)
parse(source)
while card = @code[@pc]
@pc += 1
case card.rank
when nil then newline(card)
when "K" then jump(card)
when "Q" then next
when "J" then dump(card, :char)
when 10 then dump(card)
else assign(card)
end
end
end
def parse(source)
lines = source.split("\n").map { |line| line.sub(/#.*$/, "").strip }
# Turn source file into array of cards. Each card is 2-item
# array of rank and suit.
lines.each_with_index do |line, i|
@code += [[ nil, i + 1 ]] # <-- Line number cards have nil rank.
@code += line.scan(/(10|[2-9JQKA])([♦♥♠♣])/)
end
# A pass to convert ranks to Fixnum and extract labels.
pc = 0
while card = @code[pc]
pc += 1
if card.rank =~ /\d/
card.rank = card.rank.to_i
elsif card.rank == "Q"
queen = card.suit
while @code[pc] && @code[pc].rank == "Q" && @code[pc].suit == card.suit
queen += card.suit
pc += 1
end
@labels[queen] = pc
end
end
end
def newline(card)
# puts "newline #{card}"
@line = card.suit
end
def assign(card)
# puts "assign #{card.inspect}"
operands = remaining(card)
expression(operands)
end
def jump(card)
# puts "jump #{card.inspect}, pc: #{@pc.inspect}, #{@labels.inspect}"
suit = card.suit
while @code[@pc] && @code[@pc].rank == "K" && @code[@pc].suit == card.suit
suit += card.suit
@pc += 1
end
if instance_variable_get("@#{suit[0]}") != 0
# puts "jumping to " << "Q#{suit[0]}" * suit.size
if @labels[suit]
@pc = @labels[suit]
else
exception("can't find " << "Q#{suit[0]}" * suit.size << " to go to")
end
end
end
def dump(card, char = nil)
value = instance_variable_get("@#{card.suit}")
# puts "dump #{card.inspect} => #{value.inspect}"
if char
if value.between?(0, 255)
print value < 128 ? value.chr : [value].pack("U")
else
exception("character code #{value} is not in 0..255 range")
end
else
print value
end
end
# Fetch the rest of the assignment expression.
def remaining(card)
operands = [ card ]
while card = @code[@pc]
break if card.rank.nil? || card.rank == 10 || card.rank =~ /[KQJ]/
operands += [ card ]
@pc += 1
end
operands
end
def expression(operands)
initial, target = operands.shift
initial = instance_variable_get("@#{target}") if initial == "A"
operands.each do |rank, suit|
# puts "rank: #{rank.inspect}, suit: #{suit.inspect}"
rank = instance_variable_get("@#{suit}") if rank == "A"
case suit
when "♦" then initial += rank
when "♥" then initial *= rank
when "♠" then initial -= rank
when "♣" then
if rank != 0
initial /= rank
else
exception("division by zero")
end
end
end
instance_variable_set("@#{target}", initial)
# dump_registers
end
def exception(message)
abort("Ante exception: #{message} on line #{@line} (pc:#{@pc})")
end
def dump_registers
instance_variables.each do |i|
puts " #{i}: " + instance_variable_get("#{i}").to_s if i.size == 2
end
end
end
if ARGV[0]
Ante.new.run(IO.read(ARGV[0], encoding: "utf-8"))
else
puts "usage: ante filename.ante"
end