-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpaths.jou
262 lines (204 loc) · 7.55 KB
/
paths.jou
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
import "stdlib/mem.jou"
import "stdlib/str.jou"
import "stdlib/io.jou"
import "stdlib/process.jou"
import "./command_line_args.jou"
if WINDOWS:
declare GetModuleFileNameA(hModule: void*, lpFilename: byte*, nSize: int) -> int
elif MACOS:
declare _NSGetExecutablePath(buf: byte*, bufsize: int*) -> int
else:
declare readlink(linkpath: byte*, result: byte*, result_size: long) -> long
if WINDOWS:
declare _mkdir(path: byte*) -> int
else:
declare mkdir(path: byte*, mode: int) -> int # posix
declare dirname(path: byte*) -> byte*
declare stat(path: byte*, buf: byte[1000]*) -> int # lol
def fail_finding_exe() -> noreturn:
# TODO: include os error message (GetLastError / errno)
fprintf(stderr, "error: cannot locate currently running executable, needed for finding the Jou standard library\n")
exit(1)
if WINDOWS:
def find_current_executable() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = GetModuleFileNameA(NULL, buf, size as int)
if ret <= 0:
fail_finding_exe()
if ret < size:
# buffer is big enough, it fits
return buf
elif MACOS:
def find_current_executable() -> byte*:
n = 1
result: byte* = malloc(n)
ret = _NSGetExecutablePath(result, &n) # sets n to desired size
assert ret < 0 # didn't fit
result = realloc(result, n)
ret = _NSGetExecutablePath(result, &n)
if ret != 0:
fail_finding_exe()
return result
else:
def find_current_executable() -> byte*:
buf = NULL
for size = 2L; True; size *= 2:
buf = realloc(buf, size)
memset(buf, 0, size)
ret = readlink("/proc/self/exe", buf, size)
if ret <= 0:
fail_finding_exe()
if ret < size:
# buffer is big enough, it fits
return buf
def find_installation_directory() -> byte*:
exe = find_current_executable()
result = strdup(dirname(exe))
free(exe)
return result
def find_stdlib() -> byte*:
checked: byte*[3]
memset(&checked, 0, sizeof checked)
exedir = find_current_executable()
while WINDOWS and strstr(exedir, "\\") != NULL:
*strstr(exedir, "\\") = '/'
for i = 0; i < sizeof checked / sizeof checked[0]; i++:
tmp = strdup(dirname(exedir))
free(exedir)
exedir = tmp
if strlen(exedir) <= 3:
# give up, seems like we reached root of file system (e.g. "C:/" or "/")
break
path = malloc(strlen(exedir) + 10)
sprintf(path, "%s/stdlib", exedir)
iojou: byte* = malloc(strlen(path) + 10)
sprintf(iojou, "%s/io.jou", path)
buf: byte[1000]
stat_result = stat(iojou, &buf)
free(iojou)
if stat_result == 0:
free(exedir)
return path
checked[i] = path
# TODO: test this
fprintf(stderr, "error: cannot find the Jou standard library in any of the following locations:\n")
for i = 0; i < sizeof checked / sizeof checked[0] and checked[i] != NULL; i++:
fprintf(stderr, " %s\n", checked[i])
exit(1)
# Ignoring return values, because there's currently no way to check errno.
# We need to ignore the error when directory exists already (EEXIST).
# Ideally we wouldn't ignore any other errors.
if WINDOWS:
def my_mkdir(path: byte*) -> None:
_mkdir(path)
else:
def my_mkdir(path: byte*) -> None:
mkdir(path, 0o777) # this is what mkdir in bash does according to strace
def write_gitignore(p: byte*) -> None:
filename: byte* = malloc(strlen(p) + 100)
sprintf(filename, "%s/.gitignore", p)
f = fopen(filename, "r")
if f != NULL:
# already exists
fclose(f)
else:
# write '*' into gitignore, so that git won't track any compiled files
f = fopen(filename, "w")
if f != NULL:
fprintf(f, "*")
fclose(f)
free(filename)
def mkdir_exist_ok(p: byte*) -> None:
# TODO: check if errno == EEXIST
# Currently no good way to access EEXIST constant
my_mkdir(p)
def get_path_to_file_in_jou_compiled(filename: byte*) -> byte*:
# Place compiled files so that it's difficult to get race conditions when
# compiling multiple Jou files simultaneously (tests do that)
tmp = strdup(command_line_args.infile)
infile_folder = strdup(dirname(tmp))
free(tmp)
subfolder = get_filename_without_jou_suffix(command_line_args.infile)
result: byte* = malloc(strlen(infile_folder) + strlen(subfolder) + strlen(filename) + 100)
assert result != NULL
sprintf(result, "%s/jou_compiled", infile_folder)
mkdir_exist_ok(result)
write_gitignore(result)
sprintf(result, "%s/jou_compiled/%s", infile_folder, subfolder)
mkdir_exist_ok(result)
sprintf(result, "%s/jou_compiled/%s/%s", infile_folder, subfolder, filename)
free(infile_folder)
free(subfolder)
return result
def get_filename_without_jou_suffix(path: byte*) -> byte*:
last_slash = strrchr(path, '/')
if last_slash != NULL:
path = &last_slash[1]
if WINDOWS:
last_slash = strrchr(path, '\\')
if last_slash != NULL:
path = &last_slash[1]
len = strlen(path)
if len > 4 and ends_with(path, ".jou"):
len -= 4
result: byte* = malloc(len+1)
assert result != NULL
memcpy(result, path, len)
result[len] = '\0'
return result
def decide_exe_path() -> byte*:
if command_line_args.outfile != NULL:
return strdup(command_line_args.outfile)
name = get_filename_without_jou_suffix(command_line_args.infile)
if WINDOWS:
name = realloc(name, strlen(name) + 10)
strcat(name, ".exe")
path = get_path_to_file_in_jou_compiled(name)
free(name)
return path
# TODO: put this to stdlib? or does it do too much for a stdlib function?
def delete_slice(start: byte*, end: byte*) -> None:
memmove(start, end, strlen(end) + 1)
# In paths, "foo/../" is usually unnecessary, because it goes to a folder "foo" and then
# immediately back up. However, it makes a difference in a few cases:
#
# 1. folder "foo" doesn't exist
# 2. folder "foo" is a symlink to a different place
# 3. we are actually looking at "../../" (so "foo" is "..")
#
# Special cases 1 and 2 are not relevant in the Jou compiler, but special case 3 is relevant
# when importing from "../../file.jou" (bad style, but should work).
#
# This function deletes one unnecessary "foo/../", and may be called recursively to delete
# all of them.
def simplify_dotdot_once(path: byte*) -> bool:
assert strstr(path, "\\") == NULL # should be already taken care of when calling this
for p = strstr(path, "/../"); p != NULL; p = strstr(&p[1], "/../"):
end = &p[4]
start = p
while start > path and start[-1] != '/':
start--
if not starts_with(start, "../"):
delete_slice(start, end)
return True
return False
def simplify_path(path: byte*) -> None:
if WINDOWS:
# Backslash to forward slash.
for p = path; *p != '\0'; p++:
if *p == '\\':
*p = '/'
# Delete "." components.
while starts_with(path, "./"):
delete_slice(path, &path[2])
while True:
p = strstr(path, "/./")
if p == NULL:
break # TODO: walrus operator p := strstr(...)
delete_slice(p, &p[2])
# Delete unnecessary ".." components.
while simplify_dotdot_once(path):
pass