sweet-lang 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/sweet ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'sweet'
@@ -0,0 +1,141 @@
1
+ require "parser"
2
+ require "runtime"
3
+
4
+ class Interpreter
5
+ def initialize
6
+ @parser = Parser.new
7
+ end
8
+
9
+ def eval(code)
10
+ @parser.parse(code).eval(Runtime)
11
+ end
12
+ end
13
+
14
+ class Nodes
15
+ # This method is the "interpreter" part of our language. All nodes know how to eval
16
+ # itself and returns the result of its evaluation by implementing the "eval" method.
17
+ # The "context" variable is the environment in which the node is evaluated (local
18
+ # variables, current class, etc.).
19
+ def eval(context)
20
+ return_value = nil
21
+ nodes.each do |node|
22
+ return_value = node.eval(context)
23
+ end
24
+ # The last value evaluated in a method is the return value. Or nil if none.
25
+ return_value || Runtime["nil"]
26
+ end
27
+ end
28
+
29
+ class NumberNode
30
+ def eval(context)
31
+ # Here we access the Runtime, which we'll see in the next section, to create a new
32
+ # instance of the Number class.
33
+ Runtime["Number"].new_with_value(value)
34
+ end
35
+ end
36
+
37
+ class StringNode
38
+ def eval(context)
39
+ Runtime["String"].new_with_value(value)
40
+ end
41
+ end
42
+
43
+ class TrueNode
44
+ def eval(context)
45
+ Runtime["true"]
46
+ end
47
+ end
48
+
49
+ class FalseNode
50
+ def eval(context)
51
+ Runtime["false"]
52
+ end
53
+ end
54
+
55
+ class NilNode
56
+ def eval(context)
57
+ Runtime["nil"]
58
+ end
59
+ end
60
+
61
+ class CallNode
62
+ def eval(context)
63
+ # If there's no receiver and the method name is the name of a local variable, then
64
+ # it's a local variable access. This trick allows us to skip the () when calling a
65
+ # method.
66
+ if receiver.nil? && context.locals[method] && arguments.empty?
67
+ context.locals[method]
68
+
69
+ # Method call
70
+ else
71
+ if receiver
72
+ value = receiver.eval(context)
73
+ else
74
+ # In case there's no receiver we default to self, calling "print" is like
75
+ # "self.print".
76
+ value = context.current_self
77
+ end
78
+
79
+ eval_arguments = arguments.map { |arg| arg.eval(context) }
80
+ value.call(method, eval_arguments)
81
+ end
82
+ end
83
+ end
84
+
85
+ class GetConstantNode
86
+ def eval(context)
87
+ context[name]
88
+ end
89
+ end
90
+
91
+ class SetConstantNode
92
+ def eval(context)
93
+ context[name] = value.eval(context)
94
+ end
95
+ end
96
+
97
+ class SetLocalNode
98
+ def eval(context)
99
+ context.locals[name] = value.eval(context)
100
+ end
101
+ end
102
+
103
+ class DefNode
104
+ def eval(context)
105
+ # Defining a method is adding a method to the current class.
106
+ method = SweetMethod.new(params, body)
107
+ context.current_class.runtime_methods[name] = method
108
+ end
109
+ end
110
+
111
+ class ClassNode
112
+ def eval(context)
113
+ # Try to locate the class. Allows reopening classes to add methods.
114
+ sweet_class = context[name]
115
+
116
+ unless sweet_class # Class doesn't exist yet
117
+ sweet_class = SweetClass.new
118
+ # Register the class as a constant in the runtime.
119
+ context[name] = sweet_class
120
+ end
121
+
122
+ # Evaluate the body of the class in its context. Providing a custom context allows
123
+ # to control where methods are added when defined with the def keyword. In this
124
+ # case, we add them to the newly created class.
125
+ class_context = Context.new(sweet_class, sweet_class)
126
+
127
+ body.eval(class_context)
128
+
129
+ sweet_class
130
+ end
131
+ end
132
+
133
+ class IfNode
134
+ def eval(context)
135
+ # We turn the condition node into a Ruby value to use Ruby's "if" control
136
+ # structure.
137
+ if condition.eval(context).ruby_value
138
+ body.eval(context)
139
+ end
140
+ end
141
+ end
data/lib/lexer.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "lexer/tags"
2
+ require "lexer/strings"
3
+ require "lexer/core"
data/lib/lexer/core.rb ADDED
@@ -0,0 +1,128 @@
1
+ class Lexer
2
+ KEYWORDS = ["def", "class", "if", "true", "false", "nil"]
3
+
4
+ def treat(code)
5
+ tags = Tags.new
6
+ code = tags.implement_tag(code)
7
+ end
8
+
9
+ def tokenize(code)
10
+ # Cleanup code by remove extra line breaks
11
+ code.chomp!
12
+
13
+ # Replacing all spaces, inputing tabs
14
+ code = code.gsub( /[ ]+\n/, "\n" )
15
+ code = code.gsub( "\n ", "\n\t" )
16
+ code = code.gsub( /^\s*$/m, '' )
17
+
18
+ tags = Tags.new
19
+
20
+ code = treat(code)
21
+
22
+ # Current character position we're parsing
23
+ i = 0
24
+
25
+ # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
26
+ tokens = []
27
+
28
+ # Current indent level is the number of spaces in the last indent.
29
+ current_indent = 0
30
+ # We keep track of the indentation levels we are in so that when we dedent, we can
31
+ # check if we're on the correct level.
32
+ indent_stack = []
33
+
34
+ # This is how to implement a very simple scanner.
35
+ # Scan one character at the time until you find something to parse.
36
+ while i < code.size
37
+ chunk = code[i..-1]
38
+
39
+ # Matching standard tokens.
40
+ #
41
+ # Matching if, print, method names, etc.
42
+ if identifier = chunk[/\A([a-z]\w*)/, 1]
43
+ # Keywords are special identifiers tagged with their own name, 'if' will result
44
+ # in an [:IF, "if"] token
45
+ if KEYWORDS.include?(identifier)
46
+ tokens << [identifier.upcase.to_sym, identifier]
47
+ # Non-keyword identifiers include method and variable names.
48
+ else
49
+ tokens << [:IDENTIFIER, identifier]
50
+ end
51
+ # skip what we just parsed
52
+ i += identifier.size
53
+
54
+ # Matching class names and constants starting with a capital letter.
55
+ elsif constant = chunk[/\A([A-Z]\w*)/, 1]
56
+ tokens << [:CONSTANT, constant]
57
+ i += constant.size
58
+
59
+ elsif number = chunk[/\A([0-9]+)/, 1]
60
+ tokens << [:NUMBER, number.to_i]
61
+ i += number.size
62
+
63
+ elsif string = chunk[/\A"(.*?)"/, 1]
64
+ tokens << [:STRING, string]
65
+ i += string.size + 2
66
+
67
+ # Here's the indentation magic!
68
+ elsif indent = chunk[/\A\n(\t+)/m, 1]
69
+ # Create a new block we expect the indent level to go up.
70
+ if indent.size < current_indent
71
+ indent_stack.pop
72
+ current_indent = indent_stack.last || 0
73
+ tokens << [:DEDENT, indent.size]
74
+ tokens << [:NEWLINE, "\n"]
75
+
76
+ elsif indent.size == current_indent
77
+ tokens << [:NEWLINE, "\n"]
78
+
79
+ else
80
+ # Adjust the current indentation level.
81
+ current_indent = indent.size
82
+ indent_stack.push(current_indent)
83
+ tokens << [:INDENT, indent.size]
84
+ end
85
+
86
+ i += indent.size + 1
87
+
88
+ elsif indent = chunk[/\A\n( *)/m, 1]
89
+ if indent.size == current_indent
90
+ # Nothing to do, we're still in the same block
91
+ tokens << [:NEWLINE, "\n"]
92
+ elsif indent.size < current_indent
93
+ indent_stack.pop
94
+ current_indent = indent_stack.last || 0
95
+ tokens << [:DEDENT, indent.size]
96
+ tokens << [:NEWLINE, "\n"]
97
+ end
98
+ i += indent.size + 1
99
+
100
+ # Match long operators such as ||, &&, ==, !=, <= and >=.
101
+ # One character long operators are matched by the catch all `else` at the bottom.
102
+ elsif operator = chunk[/\A(\|\||&&|==|!=|<=|>=)/, 1]
103
+ tokens << [operator, operator]
104
+ i += operator.size
105
+
106
+ # Ignore whitespace
107
+ elsif chunk.match(/\A /)
108
+ i += 1
109
+
110
+ # Catch all single characters
111
+ # We treat all other single characters as a token. Eg.: ( ) , . ! + - <
112
+ else
113
+ value = chunk[0,1]
114
+ tokens << [value, value]
115
+ i += 1
116
+
117
+ end
118
+
119
+ end
120
+
121
+ # Close all open blocks
122
+ while indent = indent_stack.pop
123
+ tokens << [:DEDENT, indent_stack.last || 0]
124
+ end
125
+
126
+ tokens
127
+ end
128
+ end
@@ -0,0 +1,13 @@
1
+ class Strings
2
+
3
+ def convert_string(code)
4
+ strings = code.scan(/\t(\"[a-zA-Z0-9].*\")/)
5
+ strings.uniq!
6
+ strings.each do |string|
7
+ new_string = "print(#{string[0]})"
8
+ code = code.gsub(string[0], new_string)
9
+ end
10
+ return code
11
+ end
12
+
13
+ end
data/lib/lexer/tags.rb ADDED
@@ -0,0 +1,100 @@
1
+ class Tags
2
+
3
+ @@inline_tags = [ "area", "img", "param", "input", "option", "base", "link",
4
+ "meta", "hr", "br", "wbr" ]
5
+
6
+ @@tags = ["DOCTYPE","a","abbr","acronym","address","applet","b",
7
+ "bdo","big","blockquote","body","button","caption","center","cite","code",
8
+ "colgroup","dd","del","dfn","dir","div","dl","dt","em","fieldset","font",
9
+ "form","frameset","h1","h2","h3","h4","h5","h6","head","html","i","iframe",
10
+ "ins","kbd","label","legend","li","map","menu","noframes","noscript",
11
+ "object","ol","optgroup","option","p","pre","q","s","samp","script",
12
+ "select","small","span","strike","strong","style","sub","sup","table",
13
+ "tbody","td","textarea","tfoot","th","thead","title","tr","tt","u","ul",
14
+ "var"]
15
+
16
+ def convert_tag(code, index, indent, tag, atr, inline=false)
17
+ content = code.scan(/(\t+[a-zA-Z0-9]+.*)/)
18
+ tab = ""
19
+ p_tab = ""
20
+ for i in 0..indent
21
+ if i == (indent - 1 )
22
+ p_tab = tab
23
+ else
24
+ tab += "\t"
25
+ end
26
+ end
27
+ attributes = convert_attr(atr).join(" ")
28
+ if inline
29
+ new_def = "#{p_tab}def tag_#{index}\n#{tab}print(\"<#{tag} #{attributes} />\")\n#{p_tab}tag_#{index}"
30
+ else
31
+ new_def = "def tag_#{index}\n#{tab}print(\"<#{tag} #{attributes}>\")"
32
+ content.each do |lol|
33
+ new_def += "\n" + lol[0]
34
+ end
35
+ new_def += "\n#{tab}print(\"</#{tag}>\")\n#{p_tab}tag_#{index}"
36
+ end
37
+ return new_def
38
+ end
39
+
40
+ def convert_attr(atr)
41
+ atr.map {|x| x[0] + "'#{x[1]}'" }
42
+ end
43
+
44
+ def adding_attributes(code, tag)
45
+ _attr = [ [ "id", "#" ], [ "class", "\\." ] ]
46
+ _attr.each do |a|
47
+ orig = code.scan(/#{tag} ([.a-zA-Z0-9=-_ ]+)?(#{a[1]}([a-zA-Z0-9]+))/)
48
+ orig.map! {|x| [x[1], x[2]] }
49
+ orig.uniq!
50
+ orig.each do |_old|
51
+ _new = "#{a[0]}=#{_old[1]}"
52
+ code = code.gsub(_old[0], _new)
53
+ end
54
+ end
55
+ return code
56
+ end
57
+
58
+ def implement_tag(code, indent=1)
59
+ new_code = []
60
+ @@inline_tags.each do |tag|
61
+ general = code.scan(/(^(\t)+?(#{tag}(()$| )([ #.=_\-a-zA-Z0-9]+)?((\n\t.*)+)?).*)/)
62
+ general.map! {|x| x[0]}
63
+ general.each_with_index do |block, index|
64
+ new_indent = general[0].split("\t").count
65
+ _code_block = block
66
+ code_block = adding_attributes(block, tag)
67
+ strings = Strings.new
68
+ code_block = strings.convert_string( code_block )
69
+ atr = code_block.scan(/([a-zA-Z0-9_-]+=)'?"?([a-zA-Z0-9_-]+)'?"?/)
70
+ code_block = convert_tag( code_block , index, new_indent, tag, atr, true)
71
+ code = code.gsub(_code_block, code_block)
72
+ end
73
+ end
74
+ @@tags.each do |tag|
75
+ general = code.scan(/(^(#{tag}(()$| )([ #.=_\-a-zA-Z0-9]+)?((\n\t.*)+)?).*)/)
76
+ general.map! {|x| x[0]}
77
+ general.each_with_index do |block, index|
78
+ _code_block = block
79
+ code_block = adding_attributes(block, tag)
80
+ @@tags.each do |r_tag|
81
+ if !code_block.scan(/^(\t){#{indent}}(#{r_tag}(()$| )([a-zA-Z0-9= \t_\-#.\\'\/]+)?((\n\1{#{indent+1},})?(["#a= ()a-zA-Z0-9_.-\\'\/]+)?)+)/m).empty?
82
+ r_code = code_block.scan(/^(\t){#{indent}}(#{r_tag}(()$| )([a-zA-Z0-9= \t_\-#.\\'\/]+)?((\n\1{#{indent+1},})?(["#a= ()a-zA-Z0-9_.-\\'\/]+)?)+)/m)
83
+ r_code.each do |r|
84
+ new_indent = indent + 1
85
+ new_block = implement_tag(r[1], new_indent)
86
+ code_block = code_block.gsub(r[1], new_block)
87
+ end
88
+ end
89
+ end
90
+ strings = Strings.new
91
+ code_block = strings.convert_string( code_block )
92
+ atr = code_block.scan(/([a-zA-Z0-9_-]+=)([a-zA-Z0-9_-]+)/)
93
+ code_block = convert_tag( code_block , index, indent, tag, atr)
94
+ code = code.gsub(_code_block, code_block)
95
+ end
96
+ end
97
+ return code
98
+ end
99
+
100
+ end
data/lib/nodes.rb ADDED
@@ -0,0 +1,56 @@
1
+ # Collection of nodes each one representing an expression.
2
+ class Nodes < Struct.new(:nodes)
3
+ def <<(node)
4
+ nodes << node
5
+ self
6
+ end
7
+ end
8
+
9
+ # Literals are static values that have a Ruby representation, eg.: a string, a number,
10
+ # true, false, nil, etc.
11
+ class LiteralNode < Struct.new(:value); end
12
+ class NumberNode < LiteralNode; end
13
+ class StringNode < LiteralNode; end
14
+ class TrueNode < LiteralNode
15
+ def initialize
16
+ super(true)
17
+ end
18
+ end
19
+ class FalseNode < LiteralNode
20
+ def initialize
21
+ super(false)
22
+ end
23
+ end
24
+ class NilNode < LiteralNode
25
+ def initialize
26
+ super(nil)
27
+ end
28
+ end
29
+
30
+ # Node of a method call or local variable access, can take any of these forms:
31
+ #
32
+ # method # this form can also be a local variable
33
+ # method(argument1, argument2)
34
+ # receiver.method
35
+ # receiver.method(argument1, argument2)
36
+ #
37
+ class CallNode < Struct.new(:receiver, :method, :arguments); end
38
+
39
+ # Retrieving the value of a constant.
40
+ class GetConstantNode < Struct.new(:name); end
41
+
42
+ # Setting the value of a constant.
43
+ class SetConstantNode < Struct.new(:name, :value); end
44
+
45
+ # Setting the value of a local variable.
46
+ class SetLocalNode < Struct.new(:name, :value); end
47
+
48
+ # Method definition.
49
+ class DefNode < Struct.new(:name, :params, :body); end
50
+
51
+ # Class definition.
52
+ class ClassNode < Struct.new(:name, :body); end
53
+
54
+ # "if" control structure. Look at this node if you want to implement other control
55
+ # structures like while, for, loop, etc.
56
+ class IfNode < Struct.new(:condition, :body); end