lisp-interpreter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +75 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/Rakefile +6 -0
- data/bin/console.bat +2 -0
- data/bin/setup +8 -0
- data/bin/start.rb +2 -0
- data/lib/lisp/interpreter.rb +9 -0
- data/lib/lisp/interpreter/boolean.rb +42 -0
- data/lib/lisp/interpreter/checker.rb +45 -0
- data/lib/lisp/interpreter/errors.rb +13 -0
- data/lib/lisp/interpreter/functional.rb +272 -0
- data/lib/lisp/interpreter/list.rb +162 -0
- data/lib/lisp/interpreter/numbers.rb +161 -0
- data/lib/lisp/interpreter/object.rb +51 -0
- data/lib/lisp/interpreter/parser.rb +72 -0
- data/lib/lisp/interpreter/run.rb +4 -0
- data/lib/lisp/interpreter/strings.rb +131 -0
- data/lib/lisp/interpreter/tokenizer.rb +185 -0
- data/lib/lisp/interpreter/validator.rb +53 -0
- data/lib/lisp/interpreter/value_finder.rb +60 -0
- data/lib/lisp/interpreter/version.rb +5 -0
- data/lisp-interpreter.gemspec +35 -0
- data/spec/lisp/interpreter_spec.rb +987 -0
- data/spec/spec_helper.rb +14 -0
- metadata +116 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# redefine method in Object class
|
2
|
+
class Object
|
3
|
+
def number?
|
4
|
+
to_f.to_s == to_s || to_i.to_s == to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_num
|
8
|
+
return to_f if to_f.to_s == to_s
|
9
|
+
return to_i if to_i.to_s == to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def character?
|
13
|
+
(start_with? '#\\') && (('a'..'z').to_a.include? self[2]) && size == 3
|
14
|
+
end
|
15
|
+
|
16
|
+
def string?
|
17
|
+
return false unless self.class == String
|
18
|
+
(start_with? '"') && (end_with? '"') && (size != 1)
|
19
|
+
end
|
20
|
+
|
21
|
+
def list?
|
22
|
+
return false if size < 3
|
23
|
+
check_for_list
|
24
|
+
end
|
25
|
+
|
26
|
+
def pair?
|
27
|
+
res = object_split if is_a? String
|
28
|
+
res = to_a if is_a? Array
|
29
|
+
return true if res[-3] == '.'
|
30
|
+
list? && !res[2..-2].empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def quote?
|
34
|
+
return true if start_with? '\''
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def object_split
|
41
|
+
result = to_s.split(/(\(|\)|\.)|\ /)
|
42
|
+
result.delete('')
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_for_list
|
47
|
+
res = to_a if is_a? Array
|
48
|
+
res = object_split if is_a? String
|
49
|
+
res[0..1].join == '\'(' && res[-1] == ')' && res[-3] != '.'
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require_relative 'validator'
|
3
|
+
require_relative 'tokenizer'
|
4
|
+
|
5
|
+
# Environment type
|
6
|
+
module Environment
|
7
|
+
TEST = 1
|
8
|
+
PROD = 2
|
9
|
+
end
|
10
|
+
|
11
|
+
# Parser is used to validate the user input and parse it to the tokenizer
|
12
|
+
class Parser
|
13
|
+
include ErrorMessages
|
14
|
+
include Validator
|
15
|
+
include Environment
|
16
|
+
|
17
|
+
def initialize(env_type = Environment::TEST)
|
18
|
+
@ENV_TYPE = env_type
|
19
|
+
@tokenizer = Tokenizer.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
loop do
|
24
|
+
print 'zakichan> ' if @ENV_TYPE == Environment::PROD
|
25
|
+
token = ''
|
26
|
+
until (validate_token token).nil? && token != ''
|
27
|
+
crr_input = STDIN.gets.chomp
|
28
|
+
token << crr_input
|
29
|
+
break if crr_input == ''
|
30
|
+
end
|
31
|
+
parse token
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def split_token(token)
|
36
|
+
result = []
|
37
|
+
token.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/).each do |t|
|
38
|
+
if !t.string? && (t.include?('(') || t.include?(')'))
|
39
|
+
t.to_s.split(/(\(|\))/).each { |p| result << p }
|
40
|
+
else
|
41
|
+
result << t
|
42
|
+
end
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(token)
|
48
|
+
token_error = validate_token token
|
49
|
+
result =
|
50
|
+
if token_error.nil?
|
51
|
+
@tokenizer.tokenize split_token token
|
52
|
+
else
|
53
|
+
token_error
|
54
|
+
end
|
55
|
+
print_result result unless result.to_s.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_token(token)
|
59
|
+
if !balanced_brackets? token
|
60
|
+
unbalanced_brackets_error
|
61
|
+
elsif !balanced_quotes? token
|
62
|
+
unbalanced_quotes_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def print_result(result)
|
67
|
+
to_remove = result.to_s.list? || result.to_s.pair? || result.to_s.quote?
|
68
|
+
result = result.delete('\'') if to_remove
|
69
|
+
puts result if @ENV_TYPE == Environment::PROD
|
70
|
+
result
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Helper functions for SchemeStrings
|
2
|
+
module SchemeStringsHelper
|
3
|
+
def substring_builder(str, from, to)
|
4
|
+
result = (str[1..-2])[from..(to.nil? ? -1 : to - 1)]
|
5
|
+
return '""' if result.nil?
|
6
|
+
'"' + result + '"'
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_delimeter(other)
|
10
|
+
return ' ' if other.nil?
|
11
|
+
other[1..-2]
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_as_string_helper(other, idx)
|
15
|
+
value = other[0..idx].join(' ').gsub('( ', '(').gsub(' )', ')')
|
16
|
+
[value, other[idx + 1..-1]]
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_next_value_as_string(other)
|
20
|
+
idx = find_idx_for_list other
|
21
|
+
if other[0] == '('
|
22
|
+
build_as_string_helper other, idx
|
23
|
+
elsif other[0..1].join == '\'('
|
24
|
+
[(get_raw_value other[0..idx]), other[idx + 1..-1]]
|
25
|
+
else
|
26
|
+
[other[0], other[1..-1]]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_character(char)
|
31
|
+
'#\\' + (char == ' ' ? 'space' : char)
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_carriage(str)
|
35
|
+
str = str[1..-2]
|
36
|
+
str.gsub('\n', '').gsub('\r', '').gsub('\t', '').strip.squeeze(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
def arg_function_validator(other, vars = 1)
|
40
|
+
raise 'Incorrect number of arguments' if other.size != vars
|
41
|
+
result = other[0..vars - 1].all? { |v| check_for_string v }
|
42
|
+
raise 'Invalid data type' unless result
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def string_join_helper(other, dilimeter)
|
47
|
+
values = split_list_as_string other.to_s
|
48
|
+
delim_result = find_delimeter dilimeter
|
49
|
+
'"' + (values.join delim_result) + '"'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Scheme numbers module
|
54
|
+
module SchemeStrings
|
55
|
+
include SchemeStringsHelper
|
56
|
+
def substring(other)
|
57
|
+
raise 'Incorrect number of arguments' unless other.size.between? 2, 3
|
58
|
+
str, from, to = other
|
59
|
+
arg_function_validator [str]
|
60
|
+
valid = (check_for_number from) && (to.nil? || (check_for_number to))
|
61
|
+
raise 'Incorrect parameter type' unless valid
|
62
|
+
substring_builder str, from.to_num, to.to_num
|
63
|
+
end
|
64
|
+
|
65
|
+
def string?(other)
|
66
|
+
raise 'Incorrect number of arguments' if other.size != 1
|
67
|
+
result = check_for_string other[0].to_s
|
68
|
+
result ? '#t' : '#f'
|
69
|
+
end
|
70
|
+
|
71
|
+
def strlen(other)
|
72
|
+
arg_function_validator other
|
73
|
+
other[0][1..-2].length
|
74
|
+
end
|
75
|
+
|
76
|
+
def strupcase(other)
|
77
|
+
arg_function_validator other
|
78
|
+
other[0].upcase
|
79
|
+
end
|
80
|
+
|
81
|
+
def strdowncase(other)
|
82
|
+
arg_function_validator other
|
83
|
+
other[0].downcase
|
84
|
+
end
|
85
|
+
|
86
|
+
def strcontains(other)
|
87
|
+
arg_function_validator other, 2
|
88
|
+
result = other[0][1..-2].include? other[1][1..-2]
|
89
|
+
result ? '#t' : '#f'
|
90
|
+
end
|
91
|
+
|
92
|
+
def strsplit(other)
|
93
|
+
arg_function_validator other
|
94
|
+
str = remove_carriage other[0]
|
95
|
+
result = str.split(' ').map { |s| '"' + s + '"' }
|
96
|
+
build_list result
|
97
|
+
end
|
98
|
+
|
99
|
+
def strlist(other)
|
100
|
+
arg_function_validator other
|
101
|
+
result = other[0][1..-2].chars.map { |c| build_character c }
|
102
|
+
build_list result
|
103
|
+
end
|
104
|
+
|
105
|
+
def strreplace(other)
|
106
|
+
arg_function_validator other, 3
|
107
|
+
str, to_replace, replace_with = other.map { |t| t[1..-2] }
|
108
|
+
'"' + (str.gsub to_replace, replace_with) + '"'
|
109
|
+
end
|
110
|
+
|
111
|
+
def strprefix(other)
|
112
|
+
arg_function_validator other, 2
|
113
|
+
str, to_check = other.map { |t| t[1..-2] }
|
114
|
+
result = str.start_with? to_check
|
115
|
+
result ? '#t' : '#f'
|
116
|
+
end
|
117
|
+
|
118
|
+
def strsufix(other)
|
119
|
+
arg_function_validator other, 2
|
120
|
+
str, to_check = other.map { |t| t[1..-2] }
|
121
|
+
result = str.end_with? to_check
|
122
|
+
result ? '#t' : '#f'
|
123
|
+
end
|
124
|
+
|
125
|
+
def strjoin(other)
|
126
|
+
raise 'Incorrect number of arguments' unless other.size.between? 1, 2
|
127
|
+
raise 'Invalid data type' unless other[0].to_s.list?
|
128
|
+
arg_function_validator [other[1]] if other.size == 2
|
129
|
+
string_join_helper other[0], other[1]
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require_relative 'object'
|
2
|
+
require_relative 'errors'
|
3
|
+
require_relative 'value_finder'
|
4
|
+
require_relative 'checker'
|
5
|
+
require_relative 'validator'
|
6
|
+
require_relative 'numbers'
|
7
|
+
require_relative 'strings'
|
8
|
+
require_relative 'boolean'
|
9
|
+
require_relative 'list'
|
10
|
+
require_relative 'functional'
|
11
|
+
|
12
|
+
# Tokenizer helper
|
13
|
+
module TokenizerHelper
|
14
|
+
def initialize
|
15
|
+
@other = []
|
16
|
+
@procs = {}
|
17
|
+
@do_not_calculate = init_do_not_calculate_fn
|
18
|
+
@reserved = init_reserved_fn
|
19
|
+
set_reserved_keywords
|
20
|
+
@functions = init_functions
|
21
|
+
init_predefined.each { |f| @functions[f] = f }
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@other = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def init_do_not_calculate_fn
|
29
|
+
%w[
|
30
|
+
foldl foldr map filter
|
31
|
+
if apply numerator denominator
|
32
|
+
lambda compose define
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
def init_functions
|
37
|
+
{
|
38
|
+
'string-downcase' => 'strdowncase', 'string-upcase' => 'strupcase',
|
39
|
+
'string-contains?' => 'strcontains', 'string-length' => 'strlen',
|
40
|
+
'string->list' => 'strlist', 'string-split' => 'strsplit',
|
41
|
+
'string-sufix?' => 'strsufix', 'string-prefix?' => 'strprefix',
|
42
|
+
'string-replace' => 'strreplace', 'string-join' => 'strjoin',
|
43
|
+
'list-ref' => 'listref', 'list-tail' => 'listtail'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def init_predefined
|
48
|
+
%w[
|
49
|
+
define not equal? if quotient remainder modulo numerator denominator
|
50
|
+
min max sub1 add1 abs string? substring null? cons null list car
|
51
|
+
cdr list? pair? length reverse remove shuffle map foldl foldr filter
|
52
|
+
member lambda apply compose
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
def init_reserved_fn
|
57
|
+
{
|
58
|
+
'null' => '\'()'
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_reserved_keywords
|
63
|
+
@reserved.each do |key, value|
|
64
|
+
instance_variable_set("@#{key}", value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_var_helper(var, value)
|
69
|
+
valid = (valid_var value.to_s) || (value.is_a? Proc)
|
70
|
+
raise 'Invalid parameter' unless valid
|
71
|
+
if value.is_a? Proc
|
72
|
+
remove_instance_variable("@#{var}") if check_instance_var var
|
73
|
+
@procs[var] = value if value.is_a? Proc
|
74
|
+
else
|
75
|
+
@procs.delete var
|
76
|
+
set_var var, value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_var(var, value)
|
81
|
+
raise 'Cannot predefine reserved keyword' if @reserved.key? var
|
82
|
+
instance_variable_set("@#{var}", value)
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_var(var)
|
86
|
+
check = check_instance_var var
|
87
|
+
return instance_variable_get("@#{var}") if check
|
88
|
+
val = (predefined_method_caller [var])
|
89
|
+
return val unless val.nil?
|
90
|
+
valid = valid_var var
|
91
|
+
valid ? var : (raise 'Invalid data type')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Tokenizer class
|
96
|
+
class Tokenizer
|
97
|
+
include TokenizerHelper
|
98
|
+
include ValueFinder
|
99
|
+
include SchemeChecker
|
100
|
+
include Validator
|
101
|
+
include SchemeNumbers
|
102
|
+
include SchemeStrings
|
103
|
+
include SchemeBooleans
|
104
|
+
include SchemeLists
|
105
|
+
include FunctionalScheme
|
106
|
+
|
107
|
+
def tokenize(token)
|
108
|
+
reset
|
109
|
+
token.delete('')
|
110
|
+
@other = token
|
111
|
+
begin
|
112
|
+
calc_input_val @other
|
113
|
+
rescue ZeroDivisionError, RuntimeError => e
|
114
|
+
e.message
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_car_cdr(arr)
|
119
|
+
result = arr[1].match(/c[ad]{2,}r/)
|
120
|
+
raise 'No procedure found' if result.nil?
|
121
|
+
car_cdr_infinite arr
|
122
|
+
end
|
123
|
+
|
124
|
+
def calc_input_val(arr)
|
125
|
+
get_raw = (arr.is_a? Array) && arr.size > 1 && arr[0..1].join != '\'('
|
126
|
+
return get_raw_value arr unless get_raw
|
127
|
+
m_name = predefined_method_caller arr
|
128
|
+
return check_car_cdr arr if m_name.nil?
|
129
|
+
call_predefined_method m_name, arr
|
130
|
+
end
|
131
|
+
|
132
|
+
def special_check_proc(m_name, arr)
|
133
|
+
if arr[0..1].join == '(('
|
134
|
+
idx = find_bracket_idx arr, 1
|
135
|
+
func, = valid_function arr[1..idx]
|
136
|
+
values = find_all_values arr[idx + 1..-2]
|
137
|
+
func.call(*values)
|
138
|
+
else
|
139
|
+
m_name.call(*arr[2..-2])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def call_predefined_method(m_name, arr)
|
144
|
+
return special_check_proc m_name, arr if m_name.is_a? Proc
|
145
|
+
if @do_not_calculate.include? m_name
|
146
|
+
send m_name.to_s, arr[2..-2]
|
147
|
+
elsif !m_name.nil?
|
148
|
+
values = find_all_values arr[2..-2]
|
149
|
+
send m_name.to_s, values
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def predefined_method_caller_helper(m_name, operations)
|
154
|
+
return m_name if m_name.is_a? Proc
|
155
|
+
return @procs[m_name] if @procs.key? m_name
|
156
|
+
return m_name if operations.include? m_name
|
157
|
+
return @functions[m_name] if @functions.key? m_name
|
158
|
+
m_name if @functions.value? m_name
|
159
|
+
end
|
160
|
+
|
161
|
+
def method_caller_checker(token, operations)
|
162
|
+
!token.to_s.match(/[[:alpha:]]/).nil? || (operations.include? token.to_s)
|
163
|
+
end
|
164
|
+
|
165
|
+
def predefined_method_caller(arr)
|
166
|
+
operations = ['+', '-', '/', '*', '<', '<=', '>', '>=']
|
167
|
+
m_name =
|
168
|
+
arr.each do |t|
|
169
|
+
break t if t.is_a? Proc
|
170
|
+
break t if method_caller_checker t, operations
|
171
|
+
break t unless t.match(/[[:digit:]]/).nil?
|
172
|
+
end
|
173
|
+
predefined_method_caller_helper m_name, operations
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_raw_value(token)
|
177
|
+
if token.pair? || token.list?
|
178
|
+
build_list no_eval_list token[2..-2]
|
179
|
+
else
|
180
|
+
return if token.empty?
|
181
|
+
token = token.join('') if token.is_a? Array
|
182
|
+
get_var token.to_s
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|