-
Notifications
You must be signed in to change notification settings - Fork 1
/
TinyScanner.rb
146 lines (123 loc) · 3.25 KB
/
TinyScanner.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
# https://www.cs.rochester.edu/~brown/173/readings/05_grammars.txt
#
# "TINY" Grammar
#
# PGM --> STMT+
# STMT --> ASSIGN | "print" EXP
# ASSIGN --> ID "=" EXP
# EXP --> TERM ETAIL
# ETAIL --> "+" TERM ETAIL | "-" TERM ETAIL | EPSILON
# TERM --> FACTOR TTAIL
# TTAIL --> "*" FACTOR TTAIL | "/" FACTOR TTAIL | EPSILON
# FACTOR --> "(" EXP ")" | INT | ID
#
# ID --> ALPHA+
# ALPHA --> a | b | … | z or
# A | B | … | Z
# INT --> DIGIT+
# DIGIT --> 0 | 1 | … | 9
# WHITESPACE --> Ruby Whitespace
# Comment out the below line when not using the IDE for editing...
load 'TinyToken.rb'
#
# Class Scanner - Reads a TINY program and emits tokens
#
class Scanner
# Constructor - Is passed a file to scan and outputs a token
# each time nextToken() is invoked.
# @c_look - A one character lookahead
def initialize(filename)
if File.exist?(filename)
@file = File.open(filename, 'r:utf-8')
if !@file.eof?
@c_look = @file.getc
else
@c_look = '!eof!'
@file.close
end
else
puts 'File does not exist or cannot be opened.'
@c_look = '!eof!'
end
end
# Method next_ch returns the next character in the file
def next_ch
@c_look =
if !@file.eof?
@file.getc
else
'!eof!'
end
end
# Method next_token reads characters in the file and returns
# the next token
def next_token
if @c_look == '!eof!'
Token.new(Token::EOF, '!eof!')
elsif whitespace?(@c_look)
str = ''
while whitespace?(@c_look)
str += @c_look
next_ch
end
Token.new(Token::WHITESPACE, str)
elsif letter?(@c_look)
str = ''
while letter?(@c_look)
str += @c_look
next_ch
end
if str == 'print'
Token.new(Token::PRINT, str)
else
Token.new(Token::IDENT, str)
end
elsif numeric?(@c_look)
str = ''
while numeric?(@c_look)
str += @c_look
next_ch
end
Token.new(Token::INT, str)
elsif @c_look == '='
next_ch
Token.new(Token::ASSIGN, '=')
elsif @c_look == '('
next_ch
Token.new(Token::LPAREN, '(')
elsif @c_look == ')'
next_ch
Token.new(Token::RPAREN, ')')
elsif @c_look == '+'
next_ch
Token.new(Token::ADDOP, '+')
elsif @c_look == '-'
next_ch
Token.new(Token::SUBOP, '-')
elsif @c_look == '*'
next_ch
Token.new(Token::MULTOP, '*')
elsif @c_look == '/'
next_ch
Token.new(Token::DIVOP, '/')
else
next_ch
# don't want to give back nil token!
# remember to include some case to handle
# unknown or unrecognized tokens.
Token.new('unknown', 'unknown')
end
end
#
# Helper methods for Scanner
#
def letter?(look_ahead)
look_ahead =~ /^[a-z]|[A-Z]$/
end
def numeric?(look_ahead)
look_ahead =~ /^(\d)+$/
end
def whitespace?(look_ahead)
look_ahead =~ /^(\s)+$/
end
end