rus3 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/.github/workflows/main.yml +18 -0
- data/.gitignore +56 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +21 -0
- data/README.md +182 -0
- data/Rakefile +20 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/examples/fact.scm +10 -0
- data/examples/fib.scm +9 -0
- data/examples/iota.scm +13 -0
- data/exe/rus3 +5 -0
- data/lib/rus3.rb +52 -0
- data/lib/rus3/char.rb +6 -0
- data/lib/rus3/error.rb +127 -0
- data/lib/rus3/evaluator.rb +75 -0
- data/lib/rus3/pair.rb +67 -0
- data/lib/rus3/parser.rb +71 -0
- data/lib/rus3/parser/lexer.rb +119 -0
- data/lib/rus3/parser/scheme_parser.rb +322 -0
- data/lib/rus3/port.rb +6 -0
- data/lib/rus3/printer.rb +28 -0
- data/lib/rus3/procedure/control.rb +35 -0
- data/lib/rus3/procedure/list.rb +289 -0
- data/lib/rus3/procedure/predicate.rb +308 -0
- data/lib/rus3/procedure/write.rb +130 -0
- data/lib/rus3/repl.rb +236 -0
- data/lib/rus3/version.rb +5 -0
- data/rus3.gemspec +34 -0
- metadata +77 -0
data/lib/rus3/char.rb
ADDED
data/lib/rus3/error.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rus3
|
4
|
+
class Error < StandardError
|
5
|
+
include EmptyList
|
6
|
+
|
7
|
+
def smart_error_value(obj) # :nodoc:
|
8
|
+
case obj
|
9
|
+
when Array
|
10
|
+
if obj.empty? # an empty list
|
11
|
+
"()"
|
12
|
+
else # a normal proper list
|
13
|
+
list_notation = obj.to_s().gsub(/[\[\],]/, A2L_MAP)
|
14
|
+
"list( %s )" % obj
|
15
|
+
end
|
16
|
+
when Numeric
|
17
|
+
"number(%d)" % obj
|
18
|
+
when Rus3::Pair
|
19
|
+
"pair(%s)" % obj
|
20
|
+
else
|
21
|
+
"%s(%s)" % [obj.class, obj]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
A2L_MAP = { "[" => "(", "," => "", "]" => ")"} # :nodoc:
|
26
|
+
end
|
27
|
+
|
28
|
+
# :stopdoc:
|
29
|
+
|
30
|
+
EMSG = {
|
31
|
+
:pair_required => "pair required: got=%s",
|
32
|
+
:list_required => "proper list required: got=%s",
|
33
|
+
:pair_or_list_required => "pair or proper list required: got=%s",
|
34
|
+
:out_of_range => "argument out of range: got=%s",
|
35
|
+
:unsupported_method => "specified method does not work now.",
|
36
|
+
:wrong_type => "wrong type argument: got=%s, wants=%s",
|
37
|
+
:integer_required => "integer required: got=%s",
|
38
|
+
:real_number_required => "real number required: got=%s",
|
39
|
+
:number_required => "number required: got=%s",
|
40
|
+
:string_required => "string required: got=%s",
|
41
|
+
:scheme_syntax_error => "syntax error as Scheme: got=%s",
|
42
|
+
:cannot_find_file => "cannot find %s",
|
43
|
+
:unsupported_feature => "specified feature (`%s`) does not support for %s"
|
44
|
+
}
|
45
|
+
|
46
|
+
# :startdoc:
|
47
|
+
|
48
|
+
class PairRequiredError < Error
|
49
|
+
def initialize(obj)
|
50
|
+
super(EMSG[:pair_required] % smart_error_value(obj))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ListRequiredError < Error
|
55
|
+
def initialize(obj)
|
56
|
+
super(EMSG[:list_required] % smart_error_value(obj))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class PairOrListRequiredError < Error
|
61
|
+
def initialize(obj)
|
62
|
+
super(EMSG[:pair_or_list_required] % smart_error_value(obj))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class OutOfRangeError < Error
|
67
|
+
def initialize(obj)
|
68
|
+
super(EMSG[:out_of_range] % smart_error_value(obj))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class UnsupportedMethodError < Error
|
73
|
+
def initialize
|
74
|
+
super(EMSG[:unsupported_method])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class WrongTypeError < Error
|
79
|
+
def initialize(obj, expected)
|
80
|
+
emsg = EMSG[:wrong_type] % [obj, expected]
|
81
|
+
super(emsg)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class IntegerRequiredError < Error
|
86
|
+
def initialize(obj)
|
87
|
+
super(EMSG[:integer_required] % smart_error_value(obj))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class RealNumberRequiredError < Error
|
92
|
+
def initialize(obj)
|
93
|
+
super(EMSG[:real_number_required] % smart_error_value(obj))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class NumberRequiredError < Error
|
98
|
+
def initialize(obj)
|
99
|
+
super(EMSG[:number_required] % smart_error_value(obj))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class StringRequiredError < Error
|
104
|
+
def initialize(obj)
|
105
|
+
super(EMSG[:string_required] % smart_error_value(obj))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class SchemeSyntaxError < Error
|
110
|
+
def initialize(obj)
|
111
|
+
super(EMSG[:scheme_syntax_error] % smart_error_value(obj))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class CannotFindFileError < Error
|
116
|
+
def initialize(obj)
|
117
|
+
super(EMSG[:cannot_find_file] % smart_error_value(obj))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class UnsupportedFeatureError < Error
|
122
|
+
def initialize(feature, obj)
|
123
|
+
super(EMSG[:unsupported_feature] % [feature, obj])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rus3
|
4
|
+
|
5
|
+
# An evaluator.
|
6
|
+
|
7
|
+
class Evaluator
|
8
|
+
|
9
|
+
# Indicates the version of the evaluator class.
|
10
|
+
VERSION = "0.1.0"
|
11
|
+
|
12
|
+
include EmptyList
|
13
|
+
|
14
|
+
class Environment
|
15
|
+
include Rus3::Procedure::Control
|
16
|
+
include Rus3::Procedure::Write
|
17
|
+
include Rus3::Procedure::List
|
18
|
+
include Rus3::Procedure::Predicate
|
19
|
+
include Rus3::EmptyList
|
20
|
+
|
21
|
+
attr_reader :binding
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@binding = Kernel.binding
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor :verbose
|
30
|
+
|
31
|
+
attr_reader :environment
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@verbose = false
|
35
|
+
@env = Environment.new
|
36
|
+
define_procs_for_infix_ops
|
37
|
+
end
|
38
|
+
|
39
|
+
def eval(exp)
|
40
|
+
pp exp if @verbose
|
41
|
+
@env.binding.eval(exp)
|
42
|
+
end
|
43
|
+
|
44
|
+
def binding
|
45
|
+
@env.binding
|
46
|
+
end
|
47
|
+
|
48
|
+
def version
|
49
|
+
"Evaluator version: #{VERSION}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
INFIX_OPS = {
|
55
|
+
:+ => :add,
|
56
|
+
:- => :subtract,
|
57
|
+
:* => :mul,
|
58
|
+
:/ => :div,
|
59
|
+
:% => :mod,
|
60
|
+
:< => :lt?,
|
61
|
+
:<= => :le?,
|
62
|
+
:> => :gt?,
|
63
|
+
:>= => :ge?,
|
64
|
+
:== => :eqv?,
|
65
|
+
}
|
66
|
+
|
67
|
+
def define_procs_for_infix_ops
|
68
|
+
r = @env.binding.receiver
|
69
|
+
INFIX_OPS.each { |op, proc_name|
|
70
|
+
r.instance_eval("def #{proc_name}(op1, op2); op1 #{op} op2; end")
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/rus3/pair.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rus3
|
4
|
+
|
5
|
+
# A building block to construct a dotted pair.
|
6
|
+
|
7
|
+
class Pair
|
8
|
+
include EmptyList
|
9
|
+
|
10
|
+
# CAR part of the pair.
|
11
|
+
attr_reader :car
|
12
|
+
|
13
|
+
# CDR part of the pair.
|
14
|
+
attr_reader :cdr
|
15
|
+
|
16
|
+
# :call-seq:
|
17
|
+
# new(car, cdr) -> a new Pair object
|
18
|
+
|
19
|
+
def initialize(car = EMPTY_LIST, cdr = EMPTY_LIST)
|
20
|
+
@car = car
|
21
|
+
@cdr = cdr
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replaces the CAR part with the argument.
|
25
|
+
|
26
|
+
def set_car!(obj)
|
27
|
+
@car = obj
|
28
|
+
end
|
29
|
+
|
30
|
+
# Replaces the CDR part with the argument.
|
31
|
+
|
32
|
+
def set_cdr!(obj)
|
33
|
+
@cdr = obj
|
34
|
+
end
|
35
|
+
|
36
|
+
# Compares to an other pair.
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
other.instance_of?(Pair) and @car == other.car and @cdr == other.cdr
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts to an Array, which looks like as follows:
|
43
|
+
#
|
44
|
+
# [CAR, CDR]
|
45
|
+
#
|
46
|
+
# When CAR or CDR part is also a Pair object, converts
|
47
|
+
# recursively.
|
48
|
+
|
49
|
+
def to_a
|
50
|
+
[@car, @cdr].map { |e| Pair === e ? e.to_a : e}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Converts to a String. Normally, uses the dot-pair notaion which
|
54
|
+
# looks like "(CAR . CDR)". If the CDR part is an empty string,
|
55
|
+
# looks like a normal list which has a single element, like
|
56
|
+
# "(CAR)".
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
car_part = null?(@car) ? "()" : @car
|
60
|
+
if null?(@cdr)
|
61
|
+
"(#{car_part})"
|
62
|
+
else
|
63
|
+
"(#{car_part} . #{@cdr})"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/rus3/parser.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rus3
|
4
|
+
|
5
|
+
module Parser
|
6
|
+
require "readline"
|
7
|
+
require_relative "parser/lexer"
|
8
|
+
|
9
|
+
# Indicates the version of the parser module.
|
10
|
+
VERSION = "0.1.0"
|
11
|
+
|
12
|
+
# A base class to derived a parser.
|
13
|
+
class Parser
|
14
|
+
|
15
|
+
# Holds a prompt string. It is intended to be set in the REPL
|
16
|
+
# loop.
|
17
|
+
attr_accessor :prompt
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@prompt = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
# Constructs the version string.
|
24
|
+
|
25
|
+
def version
|
26
|
+
"Parser version #{VERSION}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reads an expression from the passed IO instance. Returns nil
|
30
|
+
# when reaches to EOF.
|
31
|
+
|
32
|
+
def read(io = STDIN)
|
33
|
+
exp = Readline::readline(@prompt, true)
|
34
|
+
exp.nil? ? nil : parse(exp)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# Parses the passed expression, then returns the processed
|
40
|
+
# expression to be evaluated by an evaluator. How to process
|
41
|
+
# depends on each derived parser class.
|
42
|
+
|
43
|
+
def parse(exp)
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
require_relative "parser/scheme_parser"
|
50
|
+
|
51
|
+
DEFAULT_PARSER = SchemeParser # :nodoc:
|
52
|
+
|
53
|
+
# :stopdoc:
|
54
|
+
|
55
|
+
class PassthroughParser < Parser
|
56
|
+
PARSER_VERSION = "0.1.0"
|
57
|
+
|
58
|
+
def version
|
59
|
+
super + " (Pass Through Parser version: #{PARSER_VERSION})"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse(exp)
|
64
|
+
exp
|
65
|
+
end
|
66
|
+
|
67
|
+
# :startdoc:
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rus3::Parser
|
4
|
+
|
5
|
+
class Lexer < Enumerator
|
6
|
+
|
7
|
+
# Indicates the version of the lexer class
|
8
|
+
LEXER_VERSION = "0.1.0"
|
9
|
+
|
10
|
+
# :stopdoc:
|
11
|
+
|
12
|
+
TYPES = [
|
13
|
+
# delimiters
|
14
|
+
:lparen,
|
15
|
+
:rparen,
|
16
|
+
# value types
|
17
|
+
:boolean,
|
18
|
+
:ident,
|
19
|
+
:string,
|
20
|
+
:number,
|
21
|
+
# operators
|
22
|
+
:op_proc,
|
23
|
+
# keywords
|
24
|
+
:if,
|
25
|
+
:define,
|
26
|
+
# control
|
27
|
+
:illegal,
|
28
|
+
]
|
29
|
+
|
30
|
+
BOOLEAN = /\A#(f|t)\Z/
|
31
|
+
STRING = /\A\"[^\"]*\"\Z/
|
32
|
+
|
33
|
+
# idents
|
34
|
+
EXTENDED = "\\-"
|
35
|
+
IDENT_PAT = "[a-zA-Z_][\\w?!#{EXTENDED}]*"
|
36
|
+
IDENTIFIER = Regexp.new("\\A#{IDENT_PAT}\\Z")
|
37
|
+
|
38
|
+
EXTENDED_REGEXP = Regexp.new("[#{EXTENDED}]")
|
39
|
+
EXTENDED_MAP = { "-" => "_", }
|
40
|
+
|
41
|
+
# operators
|
42
|
+
ARITHMETIC_OPS = /\A[+\-*\/%]\Z/
|
43
|
+
COMPARISON_OPS = /\A[<>]=?\Z/
|
44
|
+
|
45
|
+
# numbers
|
46
|
+
REAL_PAT = "(([1-9][0-9]*)|0)(\.[0-9]+)?"
|
47
|
+
RAT_PAT = "#{REAL_PAT}\\/#{REAL_PAT}"
|
48
|
+
C_REAL_PAT = "(#{REAL_PAT}|#{RAT_PAT})"
|
49
|
+
C_IMAG_PAT = "#{C_REAL_PAT}"
|
50
|
+
COMP_PAT = "#{C_REAL_PAT}(\\+|\\-)#{C_IMAG_PAT}i"
|
51
|
+
|
52
|
+
REAL_NUM = Regexp.new("\\A[+-]?#{REAL_PAT}\\Z")
|
53
|
+
RATIONAL = Regexp.new("\\A[+-]?#{RAT_PAT}\\Z")
|
54
|
+
COMPLEX = Regexp.new("\\A[+-]?#{COMP_PAT}\\Z")
|
55
|
+
PURE_IMAG = Regexp.new("\\A[+-](#{C_IMAG_PAT})?i\\Z")
|
56
|
+
|
57
|
+
KEYWORDS = {
|
58
|
+
"LAMBDA" => :lambda,
|
59
|
+
"IF" => :if,
|
60
|
+
"SET!" => :set!,
|
61
|
+
"DEFINE" => :define,
|
62
|
+
"COND" => :cond,
|
63
|
+
"LET" => :let,
|
64
|
+
"ELSE" => :else, # may use with :cond
|
65
|
+
}
|
66
|
+
|
67
|
+
# :startdoc:
|
68
|
+
|
69
|
+
Token = Struct.new(:type, :literal) # :nodoc:
|
70
|
+
|
71
|
+
class << self
|
72
|
+
|
73
|
+
def new(exp, _ = nil)
|
74
|
+
tokens = tokenize(exp)
|
75
|
+
super(tokens.size) { |y|
|
76
|
+
tokens.each { |tk|
|
77
|
+
y.yield(tk)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
S2R_MAP = { "(" => "[ ", ")" => " ]" } # :nodoc:
|
83
|
+
|
84
|
+
def tokenize(exp)
|
85
|
+
source = exp.gsub(/[()]/, S2R_MAP)
|
86
|
+
|
87
|
+
source.split(" ").map { |literal|
|
88
|
+
case literal
|
89
|
+
when "["
|
90
|
+
Token.new(:lparen, literal)
|
91
|
+
when "]"
|
92
|
+
Token.new(:rparen, literal)
|
93
|
+
when BOOLEAN
|
94
|
+
Token.new(:boolean, literal)
|
95
|
+
when IDENTIFIER
|
96
|
+
key = literal.upcase
|
97
|
+
if KEYWORDS.keys.include?(key)
|
98
|
+
Token.new(KEYWORDS[key], literal)
|
99
|
+
else
|
100
|
+
Token.new(:ident, literal.gsub(EXTENDED_REGEXP, EXTENDED_MAP))
|
101
|
+
end
|
102
|
+
when STRING
|
103
|
+
Token.new(:string, literal)
|
104
|
+
when "="
|
105
|
+
Token.new(:op_proc, "==")
|
106
|
+
when ARITHMETIC_OPS, COMPARISON_OPS
|
107
|
+
Token.new(:op_proc, literal)
|
108
|
+
when REAL_NUM, RATIONAL, COMPLEX, PURE_IMAG
|
109
|
+
Token.new(:number, literal)
|
110
|
+
else
|
111
|
+
Token.new(:illegal, literal)
|
112
|
+
end
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|