turmali 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06bcafea0f78c8e5a95c998bae921490a31b233a
4
- data.tar.gz: 8c0618191772b4335fe85a17a5a99bdcf72e46a2
3
+ metadata.gz: a69583d2a7925678fa9c48d77b797381222ddf1f
4
+ data.tar.gz: 5a45ddc15b1641e8310116b3efa33d64d39f0218
5
5
  SHA512:
6
- metadata.gz: ca731d0ed9efb286a5ee7c602951878831af0db2d54887a451be0b97a5ce3781f2a616bbe7f6638ff129baf426ce511809caafc8a04d628f4e4c552ec1b9dbfb
7
- data.tar.gz: 697603935ef127cfe705943b6e9c83f742a078212453ee132c3bb4e3d273d12a9767946748d85937ce4c6eb37d5ffd300d124c067951a075102f25091a51f3ee
6
+ metadata.gz: 20a56029b2eee2414ea964de2fd6e6fe4f9e9eee2a1af1356941998bc1fce4a73e6aad02a56f3cc373dbbe73988f80815632bb027dca05218db5902f40664972
7
+ data.tar.gz: 68d49e35f6fb59e1e856773b6b3f407a2d9e4c31ab05c27861b44e80f170d5f4db59491668fc98c8a2034e87b709e22c050cd41413baf88fd4c99d9a3d1cbc76
data/README.md CHANGED
@@ -55,7 +55,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/eiffel
55
55
  ## License
56
56
 
57
57
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
58
-
59
- ## Code of Conduct
60
-
61
- Everyone interacting in the Turmali project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/eiffelqiu/turmali/blob/master/CODE_OF_CONDUCT.md).
data/bin/tml CHANGED
@@ -1,31 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
- # The Turmali language!
3
- #
4
- # usage:
5
- # ./tml example.tml # to eval a file
6
- # ./tml # to start the REPL
7
- #
8
- # on Windows run with: ruby -I. tml [options]
9
2
 
10
3
  require "bundler/setup"
11
4
  require "turmali"
12
-
13
5
  require "readline"
14
6
 
15
7
  interpreter = Interpreter.new
16
8
 
17
- # If a file is given we eval it.
18
9
  if file = ARGV.first
19
10
  interpreter.eval File.read(file)
20
-
21
- # Start the REPL, read-eval-print-loop, or interactive interpreter
22
11
  else
23
- puts "Turmali REPL, CTRL+C to quit"
12
+ puts "Turmali (#{Turmali::VERSION}) REPL, Type 'quit' or 'exit' to exit"
24
13
  loop do
25
- line = Readline::readline(">> ") # 1. Read
14
+ line = Readline::readline(">> ")
15
+ exit if line == 'quit'
16
+ exit if line == 'exit'
26
17
  Readline::HISTORY.push(line)
27
- value = interpreter.eval(line) # 2. Eval
28
- puts "=> #{value.ruby_value.inspect}" # 3. Print
29
- end # 4. Loop
30
-
18
+ value = interpreter.eval(line)
19
+ puts "=> #{value.ruby_value.inspect}"
20
+ end
31
21
  end
@@ -1,7 +1,5 @@
1
1
  class Parser
2
2
 
3
- # We need to tell the parser what tokens to expect. So each type of token produced
4
- # by our lexer needs to be declared here.
5
3
  token IF
6
4
  token DEF
7
5
  token CLASS
@@ -13,9 +11,6 @@ token IDENTIFIER
13
11
  token CONSTANT
14
12
  token INDENT DEDENT
15
13
 
16
- # Here is the Operator Precedence Table. As presented before, it tells the parser in
17
- # which order to parse expressions containing operators.
18
- # This table is based on the [C and C++ Operator Precedence Table](http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence).
19
14
  prechigh
20
15
  left '.'
21
16
  right '!'
@@ -29,41 +24,13 @@ prechigh
29
24
  left ','
30
25
  preclow
31
26
 
32
- # In the following `rule` section, we define the parsing rules.
33
- # All rules are declared using the following format:
34
- #
35
- # RuleName:
36
- # OtherRule TOKEN AnotherRule { result = Node.new }
37
- # | OtherRule { ... }
38
- # ;
39
- #
40
- # In the action section (inside the `{...}` on the right), you can do the following:
41
- #
42
- # * Assign to `result` the value returned by the rule, usually a node for the AST.
43
- # * Use `val[index of expression]` to get the `result` of a matched
44
- # expressions on the left.
27
+
45
28
  rule
46
- # First, parsers are dumb, we need to explicitly tell it how to handle empty
47
- # programs. This is what the first rule does. Note that everything between `/* ... */` is
48
- # a comment.
49
29
  Program:
50
30
  /* nothing */ { result = Nodes.new([]) }
51
31
  | Expressions { result = val[0] }
52
32
  ;
53
33
 
54
- # Next, we define what a list of expressions is. Simply put, it's series of expressions separated by a
55
- # terminator (a new line or `;` as defined later). But once again, we need to explicitly
56
- # define how to handle trailing and orphans line breaks (the last two lines).
57
- #
58
- # One very powerful trick we'll use to define variable rules like this one
59
- # (rules which can match any number of tokens) is *left-recursion*. Which means we reference
60
- # the rule itself, directly or indirectly, on the left side **only**. This is true for the current
61
- # type of parser we're using (LR). For other types of parsers like ANTLR (LL), it's the opposite,
62
- # you can only use right-recursion.
63
- #
64
- # As you'll see bellow, the `Expressions` rule references `Expressions` itself.
65
- # In other words, a list of expressions can be another list of expressions followed by
66
- # another expression.
67
34
  Expressions:
68
35
  Expression { result = Nodes.new(val) }
69
36
  | Expressions Terminator Expression { result = val[0] << val[2] }
@@ -71,7 +38,6 @@ rule
71
38
  | Terminator { result = Nodes.new([]) }
72
39
  ;
73
40
 
74
- # Every type of expression supported by our language is defined here.
75
41
  Expression:
76
42
  Literal
77
43
  | Call
@@ -86,20 +52,11 @@ rule
86
52
  | '(' Expression ')' { result = val[1] }
87
53
  ;
88
54
 
89
- # Notice how we implement support for parentheses using the previous rule.
90
- # `'(' Expression ')'` will force the parsing of `Expression` in its
91
- # entirety first. Parentheses will then be discarded leaving only the fully parsed expression.
92
- #
93
- # Terminators are tokens that can terminate an expression.
94
- # When using tokens to define rules, we simply reference them by their type which we defined in
95
- # the lexer.
96
55
  Terminator:
97
56
  NEWLINE
98
57
  | ";"
99
58
  ;
100
-
101
- # Literals are the hard-coded values inside the program. If you want to add support
102
- # for other literal types, such as arrays or hashes, this it where you'd do it.
59
+
103
60
  Literal:
104
61
  NUMBER { result = NumberNode.new(val[0]) }
105
62
  | STRING { result = StringNode.new(val[0]) }
@@ -107,15 +64,7 @@ rule
107
64
  | FALSE { result = FalseNode.new }
108
65
  | NIL { result = NilNode.new }
109
66
  ;
110
-
111
- # Method calls can take three forms:
112
- #
113
- # * Without a receiver (`self` is assumed): `method(arguments)`.
114
- # * With a receiver: `receiver.method(arguments)`.
115
- # * And a hint of syntactic sugar so that we can drop
116
- # the `()` if no arguments are given: `receiver.method`.
117
- #
118
- # Each one of those is handled by the following rule.
67
+
119
68
  Call:
120
69
  IDENTIFIER Arguments { result = CallNode.new(nil, val[0], val[1]) }
121
70
  | Expression "." IDENTIFIER
@@ -133,13 +82,6 @@ rule
133
82
  | ArgList "," Expression { result = val[0] << val[2] }
134
83
  ;
135
84
 
136
-
137
- # In our language, like in Ruby, operators are converted to method calls.
138
- # So `1 + 2` will be converted to `1.+(2)`.
139
- # `1` is the receiver of the `+` method call, passing `2`
140
- # as an argument.
141
- # Operators need to be defined individually for the Operator Precedence Table to take
142
- # action.
143
85
  Operator:
144
86
  Expression '||' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
145
87
  | Expression '&&' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
@@ -155,7 +97,6 @@ rule
155
97
  | Expression '/' Expression { result = CallNode.new(val[0], val[1], [val[2]]) }
156
98
  ;
157
99
 
158
- # Then we have rules for getting and setting values of constants and local variables.
159
100
  GetConstant:
160
101
  CONSTANT { result = GetConstantNode.new(val[0]) }
161
102
  ;
@@ -172,20 +113,10 @@ rule
172
113
  IDENTIFIER "=" Expression { result = SetLocalNode.new(val[0], val[2]) }
173
114
  ;
174
115
 
175
- # Our language uses indentation to separate blocks of code. But the lexer took care of all
176
- # that complexity for us and wrapped all blocks in `INDENT ... DEDENT`. A block
177
- # is simply an increment in indentation followed by some code and closing with an equivalent
178
- # decrement in indentation.
179
- #
180
- # If you'd like to use curly brackets or `end` to delimit blocks instead, you'd
181
- # simply need to modify this one rule.
182
- # You'll also need to remove the indentation logic from the lexer.
183
116
  Block:
184
117
  INDENT Expressions DEDENT { result = val[1] }
185
118
  ;
186
-
187
- # The `def` keyword is used for defining methods. Once again, we're introducing
188
- # a bit of syntactic sugar here to allow skipping the parentheses when there are no parameters.
119
+
189
120
  Def:
190
121
  DEF IDENTIFIER Block { result = DefNode.new(val[1], [], val[2]) }
191
122
  | DEF IDENTIFIER
@@ -197,24 +128,19 @@ rule
197
128
  | IDENTIFIER { result = val }
198
129
  | ParamList "," IDENTIFIER { result = val[0] << val[2] }
199
130
  ;
200
-
201
- # Class definition is similar to method definition.
202
- # Class names are also constants because they start with a capital letter.
131
+
203
132
  Class:
204
133
  CLASS CONSTANT Block { result = ClassNode.new(val[1], val[2]) }
205
134
  ;
206
-
207
- # Finally, `if` is similar to `class` but receives a *condition*.
135
+
208
136
  If:
209
137
  IF Expression Block { result = IfNode.new(val[1], val[2]) }
210
138
  ;
211
139
  end
212
140
 
213
- # The final code at the bottom of this Racc file will be put as-is in the generated `Parser` class.
214
- # You can put some code at the top (`header`) and some inside the class (`inner`).
215
141
  ---- header
216
- require "lexer"
217
- require "nodes"
142
+ require "turmali/lexer"
143
+ require "turmali/nodes"
218
144
 
219
145
  ---- inner
220
146
  def parse(code, show_tokens=false)
@@ -5,8 +5,6 @@ require "turmali/runtime/context"
5
5
  require "turmali/runtime/class"
6
6
  require "turmali/runtime/bootstrap"
7
7
 
8
- # First, we create an simple wrapper class to encapsulate the interpretation process.
9
- # All this does is parse the code and call `eval` on the node at the top of the AST.
10
8
  class Interpreter
11
9
  def initialize
12
10
  @parser = Parser.new
@@ -17,28 +15,16 @@ class Interpreter
17
15
  end
18
16
  end
19
17
 
20
- # The `Nodes` class will always be at the top of the AST. Its only purpose it to
21
- # contain other nodes. It correspond to a block of code or a series of expressions.
22
- #
23
- # The `eval` method of every node is the "interpreter" part of our language.
24
- # All nodes know how to evalualte themselves and return the result of their evaluation.
25
- # The `context` variable is the `Context` in which the node is evaluated (local
26
- # variables, current self and current class).
27
18
  class Nodes
28
19
  def eval(context)
29
20
  return_value = nil
30
21
  nodes.each do |node|
31
22
  return_value = node.eval(context)
32
23
  end
33
- return_value || Constants["nil"] # Last result is return value (or nil if none).
24
+ return_value || Constants["nil"]
34
25
  end
35
26
  end
36
27
 
37
- # We're using `Constants` that we created before when bootstrapping the runtime to access
38
- # the objects and classes from inside the runtime.
39
- #
40
- # Next, we implement `eval` on other node types. Think of that `eval` method as how the
41
- # node bring itself to life inside the runtime.
42
28
  class NumberNode
43
29
  def eval(context)
44
30
  Constants["Number"].new_with_value(value)
@@ -81,9 +67,6 @@ class GetLocalNode
81
67
  end
82
68
  end
83
69
 
84
- # When setting the value of a constant or a local variable, the `value` attribute
85
- # is a node, created by the parser. We need to evaluate the node first, to convert
86
- # it to an object, before storing it into a variable or constant.
87
70
  class SetConstantNode
88
71
  def eval(context)
89
72
  Constants[name] = value.eval(context)
@@ -96,14 +79,12 @@ class SetLocalNode
96
79
  end
97
80
  end
98
81
 
99
- # The `CallNode` for calling a method is a little more complex. It needs to set the receiver
100
- # first and then evaluate the arguments before calling the method.
101
82
  class CallNode
102
83
  def eval(context)
103
84
  if receiver
104
85
  value = receiver.eval(context)
105
86
  else
106
- value = context.current_self # Default to `self` if no receiver.
87
+ value = context.current_self
107
88
  end
108
89
 
109
90
  evaluated_arguments = arguments.map { |arg| arg.eval(context) }
@@ -111,7 +92,6 @@ class CallNode
111
92
  end
112
93
  end
113
94
 
114
- # Defining a method, using the `def` keyword, is done by adding a method to the current class.
115
95
  class DefNode
116
96
  def eval(context)
117
97
  method = TurmaliMethod.new(params, body)
@@ -119,21 +99,13 @@ class DefNode
119
99
  end
120
100
  end
121
101
 
122
- # Defining a class is done in three steps:
123
- #
124
- # 1. Reopen or define the class.
125
- # 2. Create a special context of evaluation (set `current_self` and `current_class` to the new class).
126
- # 3. Evaluate the body of the class inside that context.
127
- #
128
- # Check back how `DefNode` was implemented, adding methods to `context.current_class`. Here is
129
- # where we set the value of `current_class`.
130
102
  class ClassNode
131
103
  def eval(context)
132
- turmali_class = Constants[name] # Check if class is already defined
104
+ turmali_class = Constants[name]
133
105
 
134
- unless turmali_class # Class doesn't exist yet
106
+ unless turmali_class
135
107
  turmali_class = TurmaliClass.new
136
- Constants[name] = turmali_class # Define the class in the runtime
108
+ Constants[name] = turmali_class
137
109
  end
138
110
 
139
111
  class_context = Context.new(turmali_class, turmali_class)
@@ -143,14 +115,12 @@ class ClassNode
143
115
  end
144
116
  end
145
117
 
146
- # Finally, to implement `if` in our language,
147
- # we turn the condition node into a Ruby value to use Ruby's `if`.
148
118
  class IfNode
149
119
  def eval(context)
150
120
  if condition.eval(context).ruby_value
151
121
  body.eval(context)
152
- else # If no body is evaluated, we return nil.
122
+ else
153
123
  Constants["nil"]
154
124
  end
155
125
  end
156
- end
126
+ end
data/lib/turmali/lexer.rb CHANGED
@@ -1,72 +1,40 @@
1
- # Our lexer will be used like so: `Lexer.new.tokenize("code")`,
2
- # and will return an array of tokens (a token being a tuple of `[TOKEN_TYPE, TOKEN_VALUE]`).
3
1
  class Lexer
4
- # First we define the special keywords of our language in a constant.
5
- # It will be used later on in the tokenizing process to disambiguate
6
- # an identifier (method name, local variable, etc.) from a keyword.
2
+
7
3
  KEYWORDS = ["def", "class", "if", "true", "false", "nil"]
8
4
 
9
5
  def tokenize(code)
10
- code.chomp! # Remove extra line breaks
11
- tokens = [] # This will hold the generated tokens
12
-
13
- # We need to know how deep we are in the indentation so
14
- # we keep track of the current indentation level we are in, and previous ones in the stack
15
- # so that when we dedent, we can check if we're on the correct level.
16
- current_indent = 0 # number of spaces in the last indent
6
+ code.chomp!
7
+ tokens = []
8
+
9
+ current_indent = 0
17
10
  indent_stack = []
18
-
19
- # Here is how to implement a very simple scanner.
20
- # Advance one character at the time until you find something to parse.
21
- # We'll use regular expressions to scan from the current position (`i`)
22
- # up to the end of the code.
23
- i = 0 # Current character position
11
+
12
+ i = 0
24
13
  while i < code.size
25
14
  chunk = code[i..-1]
26
15
 
27
- # Each of the following `if/elsif`s will test the current code chunk with
28
- # a regular expression. The order is important as we want to match `if`
29
- # as a keyword, and not a method name, we'll need to apply it first.
30
- #
31
- # First, we'll scan for names: method names and variable names, which we'll call identifiers.
32
- # Also scanning for special reserved keywords such as `if`, `def`
33
- # and `true`.
34
16
  if identifier = chunk[/\A([a-z]\w*)/, 1]
35
- if KEYWORDS.include?(identifier) # keywords will generate [:IF, "if"]
17
+ if KEYWORDS.include?(identifier)
36
18
  tokens << [identifier.upcase.to_sym, identifier]
37
19
  else
38
20
  tokens << [:IDENTIFIER, identifier]
39
21
  end
40
- i += identifier.size # skip what we just parsed
22
+ i += identifier.size
41
23
 
42
- # Now scanning for constants, names starting with a capital letter.
43
- # Which means, class names are constants in our language.
44
24
  elsif constant = chunk[/\A([A-Z]\w*)/, 1]
45
25
  tokens << [:CONSTANT, constant]
46
26
  i += constant.size
47
-
48
- # Next, matching numbers. Our language will only support integers. But to add support for floats,
49
- # you'd simply need to add a similar rule and adapt the regular expression accordingly.
27
+
50
28
  elsif number = chunk[/\A([0-9]+)/, 1]
51
29
  tokens << [:NUMBER, number.to_i]
52
30
  i += number.size
53
-
54
- # Of course, matching strings too. Anything between `"..."`.
31
+
55
32
  elsif string = chunk[/\A"([^"]*)"/, 1]
56
33
  tokens << [:STRING, string]
57
- i += string.size + 2 # skip two more to exclude the `"`.
58
-
59
- # And here's the indentation magic! We have to take care of 3 cases:
60
- #
61
- # if true: # 1) The block is created.
62
- # line 1
63
- # line 2 # 2) New line inside a block, at the same level.
64
- # continue # 3) Dedent.
65
- #
66
- # This `elsif` takes care of the first case. The number of spaces will determine
67
- # the indent level.
68
- elsif indent = chunk[/\A\:\n( +)/m, 1] # Matches ": <newline> <spaces>"
69
- if indent.size <= current_indent # indent should go up when creating a block
34
+ i += string.size + 2
35
+
36
+ elsif indent = chunk[/\A\:\n( +)/m, 1]
37
+ if indent.size <= current_indent
70
38
  raise "Bad indent level, got #{indent.size} indents, " +
71
39
  "expected > #{current_indent}"
72
40
  end
@@ -74,41 +42,29 @@ class Lexer
74
42
  indent_stack.push(current_indent)
75
43
  tokens << [:INDENT, indent.size]
76
44
  i += indent.size + 2
77
-
78
- # The next `elsif` takes care of the two last cases:
79
- #
80
- # * Case 2: We stay in the same block if the indent level (number of spaces) is the
81
- # same as `current_indent`.
82
- # * Case 3: Close the current block, if indent level is lower than `current_indent`.
83
- elsif indent = chunk[/\A\n( *)/m, 1] # Matches "<newline> <spaces>"
84
- if indent.size == current_indent # Case 2
85
- tokens << [:NEWLINE, "\n"] # Nothing to do, we're still in the same block
86
- elsif indent.size < current_indent # Case 3
45
+
46
+ elsif indent = chunk[/\A\n( *)/m, 1]
47
+ if indent.size == current_indent
48
+ tokens << [:NEWLINE, "\n"]
49
+ elsif indent.size < current_indent
87
50
  while indent.size < current_indent
88
51
  indent_stack.pop
89
52
  current_indent = indent_stack.last || 0
90
53
  tokens << [:DEDENT, indent.size]
91
54
  end
92
55
  tokens << [:NEWLINE, "\n"]
93
- else # indent.size > current_indent, error!
94
- raise "Missing ':'" # Cannot increase indent level without using ":"
56
+ else
57
+ raise "Missing ':'"
95
58
  end
96
59
  i += indent.size + 1
97
60
 
98
- # Long operators such as `||`, `&&`, `==`, etc.
99
- # will be matched by the following block.
100
- # One character long operators are matched by the catch all `else` at the bottom.
101
61
  elsif operator = chunk[/\A(\|\||&&|==|!=|<=|>=)/, 1]
102
62
  tokens << [operator, operator]
103
63
  i += operator.size
104
-
105
- # We're ignoring spaces. Contrary to line breaks, spaces are meaningless in our language.
106
- # That's why we don't create tokens for them. They are only used to separate other tokens.
64
+
107
65
  elsif chunk.match(/\A /)
108
66
  i += 1
109
-
110
- # Finally, catch all single characters, mainly operators.
111
- # We treat all other single characters as a token. Eg.: `( ) , . ! + - <`.
67
+
112
68
  else
113
69
  value = chunk[0,1]
114
70
  tokens << [value, value]
@@ -117,13 +73,11 @@ class Lexer
117
73
  end
118
74
 
119
75
  end
120
-
121
- # Close all open blocks. If the code ends without dedenting, this will take care of
122
- # balancing the `INDENT`...`DEDENT`s.
76
+
123
77
  while indent = indent_stack.pop
124
78
  tokens << [:DEDENT, indent_stack.first || 0]
125
79
  end
126
80
 
127
81
  tokens
128
82
  end
129
- end
83
+ end
data/lib/turmali/nodes.rb CHANGED
@@ -1,26 +1,3 @@
1
- # The first type is responsible for holding a collection of nodes,
2
- # each one representing an expression. You can think of it as the internal
3
- # representation of a block of code.
4
- #
5
- # Here we define nodes as Ruby classes that inherit from a `Struct`. This is a
6
- # simple way, in Ruby, to create a class that holds some attributes (values).
7
- # It is almost equivalent to:
8
- #
9
- # class Nodes
10
- # def initialize(nodes)
11
- # @nodes = nodes
12
- # end
13
- #
14
- # def nodes
15
- # @nodes
16
- # end
17
- # end
18
- #
19
- # n = Nodes.new("this is stored @nodes")
20
- # n.nodes # => "this is stored @nodes"
21
- #
22
- # But Ruby's `Struct` takes care of overriding the `==` operator for us and a bunch of
23
- # other things that will make testing easier.
24
1
  class Nodes < Struct.new(:nodes)
25
2
  def <<(node) # Useful method for adding a node on the fly.
26
3
  nodes << node
@@ -28,9 +5,6 @@ class Nodes < Struct.new(:nodes)
28
5
  end
29
6
  end
30
7
 
31
- # Literals are static values that have a Ruby representation. For example, a string, a number,
32
- # `true`, `false`, `nil`, etc. We define a node for each one of those and store their Ruby
33
- # representation inside their `value` attribute.
34
8
  class LiteralNode < Struct.new(:value); end
35
9
 
36
10
  class NumberNode < LiteralNode; end
@@ -55,34 +29,18 @@ class NilNode < LiteralNode
55
29
  end
56
30
  end
57
31
 
58
- # The node for a method call holds the `receiver`,
59
- # the object on which the method is called, the `method` name and its
60
- # arguments, which are other nodes.
61
32
  class CallNode < Struct.new(:receiver, :method, :arguments); end
62
33
 
63
- # Retrieving the value of a constant by its `name` is done by the following node.
64
34
  class GetConstantNode < Struct.new(:name); end
65
35
 
66
- # And setting its value is done by this one. The `value` will be a node. If we're
67
- # storing a number inside a constant, for example, `value` would contain an instance
68
- # of `NumberNode`.
69
36
  class SetConstantNode < Struct.new(:name, :value); end
70
37
 
71
- # Similar to the previous nodes, the next ones are for dealing with local variables.
72
38
  class GetLocalNode < Struct.new(:name); end
73
39
 
74
40
  class SetLocalNode < Struct.new(:name, :value); end
75
41
 
76
- # Each method definition will be stored into the following node. It holds the `name` of the method,
77
- # the name of its parameters (`params`) and the `body` to evaluate when the method is called, which
78
- # is a tree of node, the root one being a `Nodes` instance.
79
42
  class DefNode < Struct.new(:name, :params, :body); end
80
43
 
81
- # Class definitions are stored into the following node. Once again, the `name` of the class and
82
- # its `body`, a tree of nodes.
83
44
  class ClassNode < Struct.new(:name, :body); end
84
45
 
85
- # `if` control structures are stored in a node of their own. The `condition` and `body` will also
86
- # be nodes that need to be evaluated at some point.
87
- # Look at this node if you want to implement other control structures like `while`, `for`, `loop`, etc.
88
46
  class IfNode < Struct.new(:condition, :body); end
@@ -1,23 +1,16 @@
1
1
  require "turmali/runtime/class"
2
- # First, we create a Ruby Hash in which we'll store all constants accessible from inside
3
- # our runtime.
4
- # Then, we populate this Hash with the core classes of our language.
2
+
5
3
  Constants = {}
6
4
 
7
- Constants["Class"] = TurmaliClass.new # Defining the `Class` class.
8
- Constants["Class"].runtime_class = Constants["Class"] # Setting `Class.class = Class`.
9
- Constants["Object"] = TurmaliClass.new # Defining the `Object` class
10
- Constants["Number"] = TurmaliClass.new # Defining the `Number` class
5
+ Constants["Class"] = TurmaliClass.new
6
+ Constants["Class"].runtime_class = Constants["Class"]
7
+ Constants["Object"] = TurmaliClass.new
8
+ Constants["Number"] = TurmaliClass.new
11
9
  Constants["String"] = TurmaliClass.new
12
10
 
13
- # The root context will be the starting point where all our programs will
14
- # start their evaluation. This will also set the value of `self` at the root
15
- # of our programs.
16
11
  root_self = Constants["Object"].new
17
12
  RootContext = Context.new(root_self)
18
13
 
19
- # Everything is an object in our language, even `true`, `false` and `nil`. So they need
20
- # to have a class too.
21
14
  Constants["TrueClass"] = TurmaliClass.new
22
15
  Constants["FalseClass"] = TurmaliClass.new
23
16
  Constants["NilClass"] = TurmaliClass.new
@@ -26,23 +19,11 @@ Constants["true"] = Constants["TrueClass"].new_with_value(true)
26
19
  Constants["false"] = Constants["FalseClass"].new_with_value(false)
27
20
  Constants["nil"] = Constants["NilClass"].new_with_value(nil)
28
21
 
29
- # Now that we have injected all the core classes into the runtime, we can define
30
- # methods on those classes.
31
- #
32
- # The first method we'll define will allow us to do `Object.new` or
33
- # `Number.new`. Keep in mind, `Object` or `Number`
34
- # are instances of the `Class` class. By defining the `new` method
35
- # on `Class`, it will be accessible on all its instances.
36
22
  Constants["Class"].def :new do |receiver, arguments|
37
23
  receiver.new
38
24
  end
39
25
 
40
- # Next, we'll define the `print` method. Since we want to be able to call it
41
- # from everywhere, we'll define it on `Object`.
42
- # Remember from the parser's `Call` rule, methods without any receiver will be
43
- # sent to `self`. So `print()` is the same as `self.print()`, and
44
- # `self` will always be an instance of `Object`.
45
26
  Constants["Object"].def :print do |receiver, arguments|
46
27
  puts arguments.first.ruby_value
47
- Constants["nil"] # We always want to return objects from our runtime
28
+ Constants["nil"]
48
29
  end
@@ -2,8 +2,7 @@ require "turmali/runtime/object"
2
2
  require "turmali/runtime/context"
3
3
 
4
4
  class TurmaliClass < TurmaliObject
5
- # Classes are objects in Turmali so they inherit from TurmaliObject.
6
-
5
+
7
6
  attr_reader :runtime_methods
8
7
 
9
8
  def initialize
@@ -11,25 +10,20 @@ class TurmaliClass < TurmaliObject
11
10
  @runtime_class = Constants["Class"]
12
11
  end
13
12
 
14
- # Lookup a method
15
13
  def lookup(method_name)
16
14
  method = @runtime_methods[method_name]
17
15
  raise "Method not found: #{method_name}" if method.nil?
18
16
  method
19
17
  end
20
18
 
21
- # Helper method to define a method on this class from Ruby.
22
19
  def def(name, &block)
23
20
  @runtime_methods[name.to_s] = block
24
21
  end
25
22
 
26
- # Create a new instance of this class
27
23
  def new
28
24
  TurmaliObject.new(self)
29
25
  end
30
-
31
- # Create an instance of this Turmali class that holds a Ruby value. Like a String,
32
- # Number or true.
26
+
33
27
  def new_with_value(value)
34
28
  TurmaliObject.new(self, value)
35
29
  end
@@ -5,16 +5,12 @@ class TurmaliMethod
5
5
  end
6
6
 
7
7
  def call(receiver, arguments)
8
- # Create a context of evaluation in which the method will execute.
9
8
  context = Context.new(receiver)
10
-
11
- # Assign passed arguments to local variables.
9
+
12
10
  @params.each_with_index do |param, index|
13
11
  context.locals[param] = arguments[index]
14
12
  end
15
-
16
- # The body is a node (created in the parser).
17
- # We'll talk in details about the `eval` method in the interpreter chapter.
13
+
18
14
  @body.eval(context)
19
15
  end
20
16
  end
@@ -1,8 +1,4 @@
1
1
  class TurmaliObject
2
- # Each object has a class (named <code>runtime_class</code> to prevent conflicts
3
- # with Ruby's <code>class</code> keyword).
4
- # Optionally an object can hold a Ruby value. Eg.: numbers and strings will store their
5
- # number or string Ruby equivalent in that variable.
6
2
  attr_accessor :runtime_class, :ruby_value
7
3
 
8
4
  def initialize(runtime_class, ruby_value=self)
@@ -10,9 +6,6 @@ class TurmaliObject
10
6
  @ruby_value = ruby_value
11
7
  end
12
8
 
13
- # Like a typical Class-based runtime model, we store methods in the class of the
14
- # object. When calling a method on an object, we need to first lookup that
15
- # method in the class, and then call it.
16
9
  def call(method, arguments=[])
17
10
  @runtime_class.lookup(method).call(self, arguments)
18
11
  end
@@ -1,3 +1,3 @@
1
1
  module Turmali
2
- VERSION = "0.0.1"
3
- end
2
+ VERSION = "0.0.2"
3
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turmali
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eiffel Qiu
@@ -64,7 +64,6 @@ files:
64
64
  - ".gitignore"
65
65
  - ".rspec"
66
66
  - ".travis.yml"
67
- - CODE_OF_CONDUCT.md
68
67
  - Gemfile
69
68
  - LICENSE.txt
70
69
  - README.md
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at eiffelqiu@qq.com. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/