rus3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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