tildeath 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tildeath.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative 'tildeath/version'
2
+ require_relative 'tildeath/interpreter'
@@ -0,0 +1,10 @@
1
+ require_relative 'ast_nodes/program'
2
+ require_relative 'ast_nodes/statements'
3
+ require_relative 'ast_nodes/bifurcate'
4
+ require_relative 'ast_nodes/split'
5
+ require_relative 'ast_nodes/import'
6
+ require_relative 'ast_nodes/tildeath'
7
+ require_relative 'ast_nodes/null'
8
+ require_relative 'ast_nodes/dot_die'
9
+ require_relative 'ast_nodes/bang'
10
+ require_relative 'ast_nodes/value'
@@ -0,0 +1,17 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class Bang
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+
8
+ def execute(context)
9
+ !@value.execute(context)
10
+ end
11
+
12
+ def to_s
13
+ "!#{@value}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../imminently_deceased_object'
2
+
3
+ module Tildeath
4
+ module ASTNodes
5
+ class Bifurcate
6
+ def initialize(orig, parts)
7
+ @orig = orig
8
+ @parts = parts
9
+ end
10
+
11
+ def execute(context)
12
+ type = context[@orig].type
13
+ @parts.each do |part|
14
+ context[part] = ImminentlyDeceasedObject.new(type, part)
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ @parts[1..-1].reduce("bifurcate #{@orig}[#{@parts[0]}") {|memo, part|
20
+ memo << ', ' << part.to_s
21
+ } << ']'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class DotDie
4
+ def initialize(victim)
5
+ @victim = victim
6
+ end
7
+
8
+ def execute(context)
9
+ return unless context[:THIS].alive?
10
+ context[@victim].die
11
+ end
12
+
13
+ def to_s
14
+ "#{@victim}.DIE()"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../imminently_deceased_object'
2
+
3
+ module Tildeath
4
+ module ASTNodes
5
+ class Import
6
+ def initialize(type, name)
7
+ @type = type
8
+ @name = name
9
+ end
10
+
11
+ def execute(context)
12
+ return unless context[:THIS].alive?
13
+ # Create new object of the specified type and name and store it in context
14
+ context[@name] = ImminentlyDeceasedObject.new(@type, @name)
15
+ end
16
+
17
+ def to_s
18
+ "import #{@type} #{@name}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class Null
4
+ def execute(context); end
5
+
6
+ def to_s
7
+ 'NULL'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../imminently_deceased_object'
2
+
3
+ module Tildeath
4
+ module ASTNodes
5
+ class Program
6
+ def initialize(statements)
7
+ @statements = statements
8
+ end
9
+
10
+ def execute
11
+ context = {
12
+ THIS: ImminentlyDeceasedObject.new(:program, :THIS)
13
+ }
14
+ @statements.execute(context)
15
+ end
16
+
17
+ def to_s
18
+ @statements.to_s
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../imminently_deceased_object'
2
+
3
+ module Tildeath
4
+ module ASTNodes
5
+ class Split
6
+ def initialize(orig, parts)
7
+ @orig = orig
8
+ @parts = parts
9
+ end
10
+
11
+ def execute(context)
12
+ type = context[@orig].type
13
+ @parts.each do |part|
14
+ context[part] = ImminentlyDeceasedObject.new(type, part)
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ @parts[1..-1].reduce("split #{@orig}[#{@parts[0]}") {|memo, part|
20
+ memo << ', ' << part.to_s
21
+ } << ']'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class Statements
4
+ def initialize(statements=[])
5
+ @statements = statements
6
+ end
7
+
8
+ def execute(context)
9
+ return unless context[:THIS].alive?
10
+ @statements.each do |statement|
11
+ return unless context[:THIS].alive?
12
+ statement.execute(context)
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ @statements.reduce('') do |memo, stmt|
18
+ memo << stmt.to_s << ";\n"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class Tildeath
4
+ def initialize(victim, tildeath_body, execute_body)
5
+ @victim = victim
6
+ @tildeath_body = tildeath_body
7
+ @execute_body = execute_body
8
+ end
9
+
10
+ def execute(context)
11
+ fail "error: no such object: #{@victim}" unless context[@victim]
12
+ # loop over first set of statements while victim is alive
13
+ while context[@victim].alive?
14
+ return unless context[:THIS].alive?
15
+ @tildeath_body.execute(context)
16
+ end
17
+ # run second set of statements when victim dies
18
+ return unless context[:THIS].alive?
19
+ @execute_body.execute(context)
20
+ end
21
+
22
+ def to_s
23
+ "~ATH(#{@victim}) {
24
+ #{@tildeath_body}
25
+ } EXECUTE(#{@execute_body})"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Tildeath
2
+ module ASTNodes
3
+ class Value
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+
8
+ def execute(context)
9
+ context[@value].alive?
10
+ end
11
+
12
+ def to_s
13
+ @value
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Tildeath
2
+ class ImminentlyDeceasedObject
3
+ attr_reader :type, :name
4
+
5
+ def initialize(type, name)
6
+ @type = type
7
+ @name = name
8
+ @alive = true
9
+ end
10
+
11
+ def die
12
+ @alive = false
13
+ end
14
+
15
+ def alive?
16
+ @alive
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'tildeath_error'
2
+ require_relative 'lexer'
3
+ require_relative 'parser'
4
+
5
+ module Tildeath
6
+ module Interpreter
7
+ def self.interpret(script, filename:, verbose: false)
8
+ # discard shebang line if present
9
+ script.slice!(0, script.index("\n") + 1) if script[0..1] == "#!"
10
+ # scan string into tokens
11
+ tokens = Lexer.lex(script)
12
+ # parse tokens into abstract syntax tree
13
+ tree = Parser.parse(tokens)
14
+ # show gussed original source based on AST
15
+ puts tree if verbose
16
+ # execute AST starting at its root
17
+ tree.execute
18
+ rescue TildeathError => ex
19
+ puts "#{filename}:#{ex.line_number}:#{ex.column}: #{ex.message}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'tildeath_error'
2
+ require_relative 'token'
3
+
4
+ module Tildeath
5
+ module Lexer
6
+ # token types
7
+ TOKENS = {
8
+ COMMENT: /\/\/.*$/,
9
+ IMPORT: /import/,
10
+ TILDEATH: /~ATH/,
11
+ EXECUTE: /EXECUTE/,
12
+ THIS: /THIS/,
13
+ BIFURC8: /bifurcate/,
14
+ SPLIT: /split/,
15
+ LBRACE: /\{/,
16
+ RBRACE: /\}/,
17
+ LPAREN: /\(/,
18
+ RPAREN: /\)/,
19
+ LBRACKET: /\[/,
20
+ RBRACKET: /\]/,
21
+ DOT: /\./,
22
+ DIE: /DIE/,
23
+ NULL: /NULL/,
24
+ COMMA: /,/,
25
+ SEMI: /;/,
26
+ BANG: /!/,
27
+ # one or more whitespace characters
28
+ WS: /\s+/,
29
+ # a letter or underscore followed by zero or more letters, underscores, and digits
30
+ IDENT: /[a-z_][a-z_0-9]*/i
31
+ }
32
+
33
+ def self.lex(input)
34
+ # tokens found so far
35
+ tokens = []
36
+ # current position in script
37
+ pos = 0
38
+ line_number = 0
39
+ column = 0
40
+ match = nil
41
+ # while pos isn't outside the script...
42
+ while pos < input.length
43
+ if input[pos] != "\n"
44
+ column += 1
45
+ else
46
+ line_number += 1
47
+ column = 0
48
+ end
49
+ # for each token type...
50
+ good = TOKENS.any? do |sym, regex|
51
+ # try to match it at the current position in the script
52
+ match = input.match(regex, pos)
53
+ # skip to next token type unless it matched at the current position
54
+ next unless match && match.begin(0) == pos
55
+ # ignore whitespace and comments
56
+ unless [:WS, :COMMENT].include?(sym)
57
+ # add new token to list of found tokens
58
+ # if it's an identifier, save the actual text found as well
59
+ tokens << Token.new(sym, line_number, column, sym == :IDENT ? match[0] : nil)
60
+ end
61
+ # move current position to just after the end of the found token
62
+ pos += match[0].length
63
+ true
64
+ end
65
+ fail TildeathError.new(line_number, column), "error: unrecognized token #{input[pos]}" unless good
66
+ end
67
+ return tokens
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,136 @@
1
+ require_relative 'tildeath_error'
2
+ require_relative 'ast_nodes'
3
+
4
+ module Tildeath
5
+ module Parser
6
+ # null: NULL
7
+ def self.parse_null(tokens)
8
+ # shift the NULL off the token queue
9
+ tokens.shift
10
+ # return a new Null object
11
+ ASTNodes::Null.new
12
+ end
13
+
14
+ # import: IMPORT IDENT IDENT
15
+ def self.parse_import(tokens)
16
+ # Shift the IMPORT IDENT IDENT tokens off the queue, saving the type and
17
+ # name as symbols
18
+ type, name = tokens.shift(3)[1..2].map{|token| token.value.to_sym}
19
+ # return a new Import with the given type and name
20
+ ASTNodes::Import.new(type, name)
21
+ end
22
+
23
+ # value: THIS | IDENT
24
+ def self.parse_value(tokens)
25
+ ASTNodes::Value.new(tokens.shift.value.to_sym)
26
+ end
27
+
28
+ # bang: BANG value
29
+ def self.parse_bang(tokens)
30
+ # shift off BANG
31
+ tokens.shift
32
+ value = parse_value(tokens)
33
+ ASTNodes::Bang.new(operand)
34
+ end
35
+
36
+ # expression: bang | value
37
+ def self.parse_expression(tokens)
38
+ return parse_bang(tokens) if tokens[0].name == :BANG
39
+ parse_value(tokens)
40
+ end
41
+
42
+ # tildeath: TILDEATH LPAREN expression RPAREN LBRACE statements RBRACE EXECUTE LPAREN statements RPAREN
43
+ def self.parse_tildeath(tokens)
44
+ # shift off the first five tokens, saving the IDENT
45
+ victim = tokens.shift(5)[2]
46
+ victim = victim == :THIS ? victim.name : victim.value.to_sym
47
+ # parse the first statements
48
+ tildeath_body = parse_statements(tokens)
49
+ # shift off some punctuation
50
+ tokens.shift(3)
51
+ # parse the EXECUTE statements (or NULL)
52
+ execute_body = tokens[0].name == :NULL ? parse_null(tokens) : parse_statements(tokens)
53
+ # shift off the last RPAREN
54
+ tokens.shift
55
+ # return a new Tildeath with the parsed victim and statements
56
+ ASTNodes::Tildeath.new(victim, tildeath_body, execute_body)
57
+ end
58
+
59
+ # array: LBRACKET expression (COMMA expression)* RBRACKET
60
+ def self.parse_array(tokens)
61
+ elements = []
62
+ elements << tokens.shift(2)[1].value.to_sym
63
+ ASTNodes::Array.new(elements)
64
+ end
65
+
66
+ # dot_die: expression DOT DIE LPAREN RPAREN
67
+ def self.parse_dot_die(tokens)
68
+ # shift off all the tokens, keeping the IDENT's value
69
+ victim = tokens.shift(5)[0]
70
+ victim = victim.name == :THIS ? victim.name.to_sym : victim.value.to_sym
71
+ ASTNodes::DotDie.new(victim)
72
+ end
73
+
74
+ # bifurcate: BIFURC8 IDENT LBRACKET IDENT COMMA IDENT RBRACKET
75
+ def self.parse_bifurcate(tokens)
76
+ orig = tokens.shift(3)[1].value.to_sym
77
+ parts = tokens.shift(4).values_at(0, 2).map do |token|
78
+ token.value.to_sym
79
+ end
80
+ ASTNodes::Bifurcate.new(orig, parts)
81
+ end
82
+
83
+ # split: SPLIT IDENT LBRACKET IDENT (COMMA IDENT)* RBRACKET
84
+ def self.parse_split(tokens)
85
+ orig = tokens.shift(3)[1].value.to_sym
86
+ parts = [tokens.shift.value.to_sym]
87
+ while true
88
+ part = tokens.shift
89
+ case part.name
90
+ when :COMMA
91
+ parts << tokens.shift.value.to_sym
92
+ when :RBRACKET
93
+ break
94
+ end
95
+ end
96
+ ASTNodes::Split.new(orig, parts)
97
+ end
98
+
99
+ # statement: (import | tildeath | dot_die | bifurcate | split) SEMI
100
+ # TODO: make [THIS, THIS].DIE() legal
101
+ def self.parse_statement(tokens)
102
+ # Determine statement type based on first token, and parse it
103
+ token = tokens[0].name
104
+ ret = case token
105
+ when :IMPORT then parse_import(tokens)
106
+ when :TILDEATH then parse_tildeath(tokens)
107
+ when :THIS, :IDENT then parse_dot_die(tokens)
108
+ when :BIFURC8 then parse_bifucate(tokens)
109
+ when :SPLIT then parse_split(tokens)
110
+ when :RBRACE then return
111
+ else fail TildeathError.new(token.line_number, token.column), "error: unexpected token #{token}"
112
+ end
113
+ # shift off SEMI
114
+ fail TildeathError.new(tokens[0].line_number, tokens[0].column), 'missing semicolon' unless tokens[0].name == :SEMI
115
+ tokens.shift
116
+ ret
117
+ end
118
+
119
+ # statements: statement*
120
+ def self.parse_statements(tokens)
121
+ statements = []
122
+ # while there are tokens left and parse_statement returns non-nil...
123
+ while tokens.length > 0 && statement = parse_statement(tokens)
124
+ # add parsed statement to list of parsed statements
125
+ statements << statement
126
+ end
127
+ # return a new Statements object with the list of parsed statements
128
+ ASTNodes::Statements.new(statements)
129
+ end
130
+
131
+ # program: statements
132
+ def self.parse(tokens)
133
+ ASTNodes::Program.new(parse_statements(tokens))
134
+ end
135
+ end
136
+ end