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.
data/lib/rus3/char.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rus3
4
+ class Char
5
+ end
6
+ end
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
@@ -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