turmali 0.0.1 → 0.0.2

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