Skip to content

Commit 2b57d02

Browse files
committed
Greatly improved and cleaned up codebase. Fixed several bugs.
1 parent 31fa8de commit 2b57d02

File tree

9 files changed

+320
-261
lines changed

9 files changed

+320
-261
lines changed

jink.py

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,70 @@
1-
import sys
2-
import json
1+
import sys, argparse
2+
from jink import optimizer
33
from jink.lexer import Lexer
44
from jink.parser import Parser
55
from jink.optimizer import optimize
6-
from jink.interpreter import Interpreter
7-
from jink.utils.classes import Environment
6+
from jink.interpreter import Interpreter, Environment
87
from jink.repl import REPL
9-
lexer = Lexer()
10-
parser = Parser()
11-
interpreter = Interpreter()
12-
env = Environment()
8+
# from jink.compiler import Compiler
139

14-
env.def_func('print', lambda scope, args: print('\n'.join([str(x) for x in args]) or 'null'))
15-
env.def_func('string', lambda scope, args: [str(x or 'null') for x in args][0] if len(args) == 1 else [str(x or 'null') for x in args])
16-
env.def_func('input', lambda scope, args: input(' '.join(args)))
10+
def get_code_from_path(path):
11+
if not path.endswith('.jk'):
12+
path += '.jk'
13+
code = open(path).read()
14+
if not code:
15+
raise Exception(f"Error reading file {sys.argv[0]}")
16+
return code
1717

18-
code = ''
19-
if len(sys.argv) > 1 and '-v' not in sys.argv:
18+
if len(sys.argv) >= 1 and sys.argv[0] == 'jink.py':
2019
sys.argv.pop(0)
21-
_path = ' '.join(sys.argv)
22-
if not _path.endswith('.jk'):
23-
_path += '.jk'
24-
code = open(_path).read()
2520

26-
if not code:
27-
raise Exception(f"Error reading file {sys.argv[0]}")
21+
verbose = False
22+
to_compile = False
2823

29-
AST = optimize(parser.parse(lexer.parse(code)))
24+
if '-v' in sys.argv:
25+
sys.argv.remove('-v')
26+
verbose = True
27+
if '-c' in sys.argv:
28+
sys.argv.remove('-c')
3029

31-
# For testing (printing the AST)
32-
# _AST = ""
33-
# for expr in AST:
34-
# _AST += (f"{expr}, ").replace("'", '"')
35-
# print(_AST[:-2])
30+
# Launch REPL
31+
if len(sys.argv) == 0 or (len(sys.argv) == 1 and sys.argv[0] == '-v'):
32+
print("jink REPL - use '[jink] help' for help - type 'exit' to exit.")
33+
repl = REPL(sys.stdin, sys.stdout, verbose=verbose)
34+
repl.main_loop()
35+
36+
elif len(sys.argv) >= 1:
37+
if sys.argv[0] == 'help':
38+
print('\n'.join([
39+
"jink - strongly typed, JavaScript-like programming language.",
40+
"https://www.github.com/jink-lang/jink",
41+
"",
42+
"args:",
43+
" > -v -- verbose; will output AST." # and if compiling, both optimized and unoptimized LLVM IR.",
44+
# " > -c -- compile; will use compiler instead of interpreter."
45+
"",
46+
"usage:",
47+
" > [jink] help -- shows this prompt.",
48+
" > [jink] path/to/file[.jk] -- executes interpreter on file.",
49+
" > [jink] -v path/to/file[.jk] -- executes interpreter on file verbose mode.",
50+
# " > [jink] -c path/to/file[.jk] -- executes compiler on file.",
51+
# " > [jink] -c -v path/to/file[.jk] -- executes compiler on file in verbose mode.",
52+
" > [jink] -- launches interpreted interactive REPL.",
53+
" > [jink] -v -- launches interpreted interactive REPL in verbose mode."
54+
]))
3655

37-
interpreter.evaluate(AST, env)
38-
else:
39-
if '-v' in sys.argv:
40-
repl = REPL(sys.stdin, sys.stdout, env, verbose=True)
4156
else:
42-
repl = REPL(sys.stdin, sys.stdout, env)
43-
while True:
44-
repl.main_loop()
57+
path = ' '.join(sys.argv)
58+
code = get_code_from_path(path)
59+
60+
if to_compile:
61+
raise NotImplementedError("Compiler not yet implemented.")
62+
# Compiler()._eval(code, optimize=True, verbose=verbose)
63+
else:
64+
AST = optimize(Parser().parse(Lexer().parse(code), verbose=verbose))
65+
env = Environment()
66+
env.add_builtins()
67+
Interpreter().evaluate(AST, env)
68+
69+
if __name__ == "__main__":
70+
pass

jink/interpreter.py

Lines changed: 110 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,93 @@
1-
from .utils.classes import *
2-
from .utils.evals import *
1+
from jink.utils.classes import *
2+
from jink.utils.evals import *
3+
4+
TYPES = {
5+
int: 'int',
6+
float: 'float',
7+
str: 'string',
8+
dict: 'obj'
9+
}
10+
11+
# The interpreter environment
12+
class Environment:
13+
def __init__(self, parent=None, s_type=None, debug=False):
14+
self._id = parent._id + 1 if parent else 0
15+
self.index = {}
16+
self.parent = parent
17+
self.type = s_type
18+
self.debug = debug
19+
20+
# To define builtin methods - only for use on the top level interpreter environment
21+
def add_builtins(self):
22+
self.def_func('print', lambda scope, args: print('\n'.join([str(x) for x in args]) or 'null'))
23+
self.def_func('string', lambda scope, args: [str(x or 'null') for x in args][0] if len(args) == 1 else [str(x or 'null') for x in args])
24+
self.def_func('input', lambda scope, args: input(' '.join(args)))
25+
26+
def extend(self, s_type):
27+
return Environment(self, s_type)
28+
29+
def find_scope(self, name):
30+
if self.debug:
31+
print(f"Searching for {name} in scopes..")
32+
scope = self
33+
while scope:
34+
if self.debug:
35+
print(f"Searching {scope._id}.. found {list(self.index.keys())}")
36+
if name in scope.index:
37+
return scope
38+
scope = scope.parent
39+
40+
def get_var(self, name):
41+
scope = self.find_scope(name)
42+
if not scope:
43+
raise Exception(f"{name} is not defined.")
44+
45+
elif name in scope.index:
46+
return scope.index[name]
47+
48+
raise Exception(f"{name} is not defined.")
49+
50+
# Functions override to update values
51+
# from parent scopes in local scope during their lifecycle
52+
def set_var(self, name, value, var_type=None, fn_scoped=False):
53+
scope = self.find_scope(name)
54+
55+
for py_type, _type in TYPES.items():
56+
if isinstance(value, py_type):
57+
val_type = _type
58+
59+
if fn_scoped:
60+
self.index[name] = { 'value': value, 'type': val_type, 'var_type': var_type }
61+
return value
62+
63+
# Assignments
64+
if scope:
65+
v = scope.get_var(name)
66+
67+
if var_type != None:
68+
raise Exception(f"{name} is already defined.")
69+
elif v['var_type'] == 'const':
70+
raise Exception(f"Constant {name} is not reassignable.")
71+
72+
scope.index[name]['value'] = value
73+
scope.index[name]['type'] = val_type
74+
75+
# Definitions
76+
else:
77+
if not var_type:
78+
raise Exception(f"Expected let or const, got 'null' for {name}.")
79+
self.index[name] = { 'value': value, 'type': val_type, 'var_type': var_type }
80+
81+
return value
82+
83+
def def_func(self, name, func):
84+
if self.debug:
85+
print(f"Defining {name} in {self._id}")
86+
self.index[name] = func
87+
return func
88+
89+
def __str__(self):
90+
return f"{self.parent or 'null'}->{self._id}:{list(self.index.keys())}"
391

492
class Interpreter:
593
def __init__(self):
@@ -28,8 +116,10 @@ def evaluate_top(self, expr):
28116
raise Exception(f"Object '{expr.name}' does not contain the property '{expr.index['index'].name}'")
29117
else:
30118
return obj[expr.index['index'].name]
119+
120+
# TODO: Object methods, classes.
31121
elif isinstance(expr.index['index'], CallExpression):
32-
pass
122+
return self.call_function(expr.index['index'])
33123

34124
elif isinstance(expr, (StringLiteral, IntegerLiteral, FloatingPointLiteral)):
35125
return self.unwrap_value(expr)
@@ -40,6 +130,8 @@ def evaluate_top(self, expr):
40130
elif isinstance(expr, Null):
41131
return { 'type': 'null', 'value': 'null' }
42132

133+
# TODO Properly evaluate unary operators modifying variables
134+
# (e.g. pre and post increment ++i and i++)
43135
elif isinstance(expr, UnaryOperator):
44136
value = self.evaluate_top(expr.value)
45137
return UNOP_EVALS[expr.operator](self.unwrap_value(value)) or 0
@@ -70,17 +162,15 @@ def evaluate_top(self, expr):
70162
self.evaluate(expr.body, self.env)
71163

72164
elif isinstance(expr, CallExpression):
73-
scope = self.env.extend('call')
74-
func = self.evaluate_top(expr.name)
75-
return func(scope, [self.unwrap_value(self.evaluate_top(arg)) for arg in expr.args])
165+
return self.call_function(expr)
76166

77167
elif isinstance(expr, Function):
78168
return self.make_function(expr)
79169

80170
elif isinstance(expr, Return):
81-
result = self.evaluate_top(expr.expression)
171+
result = self.evaluate_top(expr.value)
82172
return { 'type': 'return', 'value': self.unwrap_value(result) }
83-
173+
84174
elif isinstance(expr, dict):
85175
return expr
86176

@@ -95,6 +185,12 @@ def evaluate_condition(self, cond):
95185
elif cond['type'] != 'bool':
96186
return 'true'
97187

188+
# Call a function in a new scope
189+
def call_function(self, expr):
190+
scope = self.env.extend(f"call_{expr.name.name}")
191+
func = self.evaluate_top(expr.name)
192+
return func(scope, [self.unwrap_value(self.evaluate_top(arg)) for arg in expr.args])
193+
98194
# Make a function
99195
def make_function(self, func):
100196
def function(scope, args):
@@ -104,21 +200,14 @@ def function(scope, args):
104200
if len(args) > len(params):
105201
raise Exception(f"Function '{func.name}' takes {len(params)} arguments but {len(args)} were given.")
106202

107-
# Apply arguments to this call's scope, otherwise use function defaults if any
203+
# Apply arguments to this call's scope
204+
# If argument doesn't exist use function default if it exists
108205
i = 0
109206

110207
for p in params:
111208
default = None
112209

113-
if isinstance(p, Assignment):
114-
default = p.value or 'null'
115-
name = p.ident.name
116-
_type = p.type
117-
class Object(): pass
118-
p = Object()
119-
setattr(p, 'name', name)
120-
setattr(p, 'type', _type)
121-
elif p.default:
210+
if p.default:
122211
default = self.unwrap_value(p.default)
123212

124213
if len(args) > i:
@@ -128,9 +217,9 @@ class Object(): pass
128217

129218
if value != None:
130219
try:
131-
scope.set_var(p.name, value, p.type)
132-
except:
133-
raise Exception(f"Improper function parameter or call argument at function '{func.name}'.")
220+
scope.set_var(p.name, value, p.type, fn_scoped=True)
221+
except Exception as e:
222+
raise Exception(f"{e}\nException: Improper function parameter or call argument at function '{func.name}'.")
134223
i += 1
135224

136225
# Ensure returning of the correct value

jink/lexer.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from .utils.names import *
2-
from .utils.classes import Token
3-
from .utils.future_iter import FutureIter
1+
from jink.utils.names import *
2+
from jink.utils.classes import Token, TokenType
3+
from jink.utils.future_iter import FutureIter
44

55
KEYWORDS = KEYWORDS + TYPES
66

@@ -50,7 +50,7 @@ def parse_tokens(self):
5050
if char == '\n':
5151
self.line_pos = 0
5252
self.line += 1
53-
yield Token('newline', 'newline', self.line, self.pos)
53+
yield Token(TokenType.NEWLINE, 'newline', self.line, self.pos)
5454

5555
# Comments
5656
elif char == '/':
@@ -61,23 +61,23 @@ def parse_tokens(self):
6161

6262
# Brackets
6363
elif char == '(':
64-
yield Token('lparen', '(', self.line, self.pos)
64+
yield Token(TokenType.LPAREN, '(', self.line, self.pos)
6565
elif char == ')':
66-
yield Token('rparen', ')', self.line, self.pos)
66+
yield Token(TokenType.RPAREN, ')', self.line, self.pos)
6767
elif char == '[':
68-
yield Token('lbracket', '[', self.line, self.pos)
68+
yield Token(TokenType.LBRACKET, '[', self.line, self.pos)
6969
elif char == ']':
70-
yield Token('rbracket', ']', self.line, self.pos)
70+
yield Token(TokenType.RBRACKET, ']', self.line, self.pos)
7171
elif char == '{':
72-
yield Token('lbrace', '{', self.line, self.pos)
72+
yield Token(TokenType.LBRACE, '{', self.line, self.pos)
7373
elif char == '}':
74-
yield Token('rbrace', '}', self.line, self.pos)
74+
yield Token(TokenType.RBRACE, '}', self.line, self.pos)
7575

7676
elif char == ';':
77-
yield Token('semicolon', ';', self.line, self.pos)
77+
yield Token(TokenType.SEMICOLON, ';', self.line, self.pos)
7878

7979
elif char == ',':
80-
yield Token('comma', ',', self.line, self.pos)
80+
yield Token(TokenType.COMMA, ',', self.line, self.pos)
8181

8282
elif char in ("'", '"'):
8383
yield self.parse_string(char)
@@ -104,8 +104,8 @@ def parse_ident(self, char):
104104
self.code._next()
105105
self.line_pos += 1
106106
if ident in KEYWORDS:
107-
return Token('keyword', ident, self.line, self.pos)
108-
return Token('identifier', ident, self.line, self.pos)
107+
return Token(TokenType.KEYWORD, ident, self.line, self.pos)
108+
return Token(TokenType.IDENTIFIER, ident, self.line, self.pos)
109109

110110
# 2 + 2 = 4 - 1 = 3
111111
def parse_operator(self, operator):
@@ -119,7 +119,7 @@ def parse_operator(self, operator):
119119
raise Exception('Invalid operator on {0}:{1}\n {2}\n {3}'.format(
120120
self.line, line_start, str(self.code).split('\n')[self.line - 1], f"{' ' * (line_start - 1)}^"
121121
))
122-
return Token('operator', operator, self.line, start)
122+
return Token(TokenType.OPERATOR, operator, self.line, start)
123123

124124
# Yay, I can "Hello world" now!
125125
def parse_string(self, char):
@@ -163,7 +163,7 @@ def parse_string(self, char):
163163
self.line, start, str(self.code).split('\n')[self.line - 1], f"{' ' * (start - 1)}^"
164164
))
165165

166-
return Token('string', string, self.line, start)
166+
return Token(TokenType.STRING, string, self.line, start)
167167

168168
# Crunch those numbers.
169169
def parse_number(self, char):
@@ -180,7 +180,7 @@ def parse_number(self, char):
180180
self.line, line_start, str(self.code).split('\n')[self.line - 1], f"{' ' * (line_start - 1)}^"
181181
))
182182
else:
183-
return Token('number', num, self.line, self.pos)
183+
return Token(TokenType.NUMBER, num, self.line, self.pos)
184184

185185
# Do I really need to comment on comments?
186186
def process_comment(self):

0 commit comments

Comments
 (0)