Skip to content

Commit 2fc72bf

Browse files
committed
tl gen: --keep-hashbang flag
Closes #646.
1 parent 3002e0c commit 2fc72bf

File tree

6 files changed

+106
-26
lines changed

6 files changed

+106
-26
lines changed

docs/compiler_options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ return {
2626
| `--gen-target` | `gen_target` | `string` | `build` `gen` `run` | Minimum targeted Lua version for generated code. Options are `5.1`, `5.3` and `5.4`. See [below](#generated-code) for details.
2727
| | `include` | `{string}` | `build` | The set of files to compile/check. See below for details on patterns.
2828
| | `exclude` | `{string}` | `build` | The set of files to exclude. See below for details on patterns.
29+
| `--keep-hashbang` | | | `gen` | Preserve hashbang line (`#!`) at the top of file if present.
2930
| `-s --source-dir` | `source_dir` | `string` | `build` | Set the directory to be searched for files. `build` will compile every .tl file in every subdirectory by default.
3031
| `-b --build-dir` | `build_dir` | `string` | `build` | Set the directory for generated files, mimicking the file structure of the source files.
3132
| | `files` | `{string}` | `build` | The names of files to be compiled. Does not accept patterns like `include`.

spec/cli/gen_spec.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ end
7272
local c = 100
7373
]]
7474

75+
local script_with_hashbang = [[
76+
#!/usr/bin/env lua
77+
print("hello world")
78+
]]
79+
80+
local script_without_hashbang = [[
81+
82+
print("hello world")
83+
]]
84+
7585
local function tl_to_lua(name)
7686
return (name:gsub("%.tl$", ".lua"):gsub("^" .. util.os_tmp .. util.os_sep, ""))
7787
end
@@ -185,6 +195,26 @@ describe("tl gen", function()
185195
end)
186196
end)
187197

198+
it("preserves hashbang with --keep-hashbang", function()
199+
local name = util.write_tmp_file(finally, script_with_hashbang)
200+
local pd = io.popen(util.tl_cmd("gen", "--keep-hashbang", name), "r")
201+
local output = pd:read("*a")
202+
util.assert_popen_close(0, pd:close())
203+
local lua_name = tl_to_lua(name)
204+
assert.match("Wrote: " .. lua_name, output, 1, true)
205+
util.assert_line_by_line(script_with_hashbang, util.read_file(lua_name))
206+
end)
207+
208+
it("drops hashbang when not using --keep-hashbang", function()
209+
local name = util.write_tmp_file(finally, script_with_hashbang)
210+
local pd = io.popen(util.tl_cmd("gen", name), "r")
211+
local output = pd:read("*a")
212+
util.assert_popen_close(0, pd:close())
213+
local lua_name = tl_to_lua(name)
214+
assert.match("Wrote: " .. lua_name, output, 1, true)
215+
util.assert_line_by_line(script_without_hashbang, util.read_file(lua_name))
216+
end)
217+
188218
describe("with --gen-target=5.1", function()
189219
it("targets generated code to Lua 5.1+", function()
190220
local name = util.write_tmp_file(finally, [[

spec/lexer/hashbang_spec.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ describe("lexer", function()
1212
it("skips hashbang at the beginning of a file", function()
1313
local syntax_errors = {}
1414
local tokens = tl.lex("#!/usr/bin/env lua\nlocal x = 1")
15+
assert.same({"#!/usr/bin/env lua\n", "local", "x", "=", "1", "$EOF$"}, map(function(x) return x.tk end, tokens))
16+
1517
tl.parse_program(tokens, syntax_errors)
1618
assert.same({}, syntax_errors)
17-
assert.same(5, #tokens)
18-
assert.same({"local", "x", "=", "1", "$EOF$"}, map(function(x) return x.tk end, tokens))
1919
end)
2020
end)

tl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ local function type_check_and_load(tlconfig, filename)
230230
return chunk
231231
end
232232

233-
local function write_out(tlconfig, result, output_file)
233+
local function write_out(tlconfig, result, output_file, pp_opts)
234234
if tlconfig["pretend"] then
235235
print("Would Write: " .. output_file)
236236
return
@@ -243,7 +243,7 @@ local function write_out(tlconfig, result, output_file)
243243
end
244244

245245
local _
246-
_, err = ofd:write(tl.pretty_print_ast(result.ast, tlconfig.gen_target) .. "\n")
246+
_, err = ofd:write(tl.pretty_print_ast(result.ast, tlconfig.gen_target, pp_opts) .. "\n")
247247
if err then
248248
die("error writing " .. output_file .. ": " .. err)
249249
end
@@ -863,6 +863,7 @@ local function get_args_parser()
863863
local gen_command = parser:command("gen", "Generate a Lua file for one or more Teal files.")
864864
gen_command:argument("file", "The Teal source file."):args("+")
865865
gen_command:flag("-c --check", "Type check and fail on type errors.")
866+
gen_command:flag("--keep-hashbang", "Preserve hashbang line (#!) at the top of file if present.")
866867
gen_command:option("-o --output", "Write to <filename> instead.")
867868
:argname("<filename>")
868869

@@ -1227,9 +1228,15 @@ commands["gen"] = function(tlconfig, args)
12271228
local results = {}
12281229
local err
12291230
local env
1231+
local pp_opts
12301232
for i, input_file in ipairs(args["file"]) do
12311233
if not env then
12321234
env = setup_env(tlconfig, input_file)
1235+
pp_opts = {
1236+
preserve_indent = true,
1237+
preserve_newlines = true,
1238+
preserve_hashbang = args["keep_hashbang"]
1239+
}
12331240
end
12341241

12351242
local res = {
@@ -1248,7 +1255,7 @@ commands["gen"] = function(tlconfig, args)
12481255

12491256
for _, res in ipairs(results) do
12501257
if #res.tl_result.syntax_errors == 0 then
1251-
write_out(tlconfig, res.tl_result, args["output"] or res.output_file)
1258+
write_out(tlconfig, res.tl_result, args["output"] or res.output_file, pp_opts)
12521259
end
12531260
end
12541261

tl.lua

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local load = _tl_compat and _tl_compat.load or load; local math = _tl_compat and _tl_compat.math or math; local _tl_math_maxinteger = math.maxinteger or math.pow(2, 53); local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack
22
local VERSION = "0.15.3+dev"
33

4-
local tl = {TypeCheckOptions = {}, Env = {}, Symbol = {}, Result = {}, Error = {}, TypeInfo = {}, TypeReport = {}, TypeReportEnv = {}, }
4+
local tl = {PrettyPrintOptions = {}, TypeCheckOptions = {}, Env = {}, Symbol = {}, Result = {}, Error = {}, TypeInfo = {}, TypeReport = {}, TypeReportEnv = {}, }
5+
6+
7+
8+
9+
10+
511

612

713

@@ -217,6 +223,7 @@ tl.typecodes = {
217223

218224

219225

226+
220227
local TL_DEBUG = os.getenv("TL_DEBUG")
221228
local TL_DEBUG_MAXLINE = _tl_math_maxinteger
222229

@@ -279,6 +286,7 @@ end
279286

280287

281288

289+
282290

283291

284292
do
@@ -592,10 +600,12 @@ do
592600

593601
local len = #input
594602
if input:sub(1, 2) == "#!" then
603+
begin_token()
595604
i = input:find("\n")
596605
if not i then
597606
i = len + 1
598607
end
608+
end_token_here("hashbang")
599609
y = 2
600610
x = 0
601611
end
@@ -1327,6 +1337,7 @@ local is_attribute = attributes
13271337

13281338

13291339

1340+
13301341

13311342

13321343
local function is_array_type(t)
@@ -3164,7 +3175,16 @@ function tl.parse_program(tokens, errs, filename)
31643175
filename = filename or "",
31653176
required_modules = {},
31663177
}
3167-
local _, node = parse_statements(ps, 1, true)
3178+
local i = 1
3179+
local hashbang
3180+
if ps.tokens[i].kind == "hashbang" then
3181+
hashbang = ps.tokens[i].tk
3182+
i = i + 1
3183+
end
3184+
local _, node = parse_statements(ps, i, true)
3185+
if hashbang then
3186+
node.hashbang = hashbang
3187+
end
31683188

31693189
clear_redundant_errors(errs)
31703190
return node, ps.required_modules
@@ -3689,18 +3709,16 @@ local spaced_op = {
36893709
}
36903710

36913711

3692-
3693-
3694-
3695-
36963712
local default_pretty_print_ast_opts = {
36973713
preserve_indent = true,
36983714
preserve_newlines = true,
3715+
preserve_hashbang = false,
36993716
}
37003717

37013718
local fast_pretty_print_ast_opts = {
37023719
preserve_indent = false,
37033720
preserve_newlines = true,
3721+
preserve_hashbang = false,
37043722
}
37053723

37063724
local primitive = {
@@ -3837,6 +3855,9 @@ function tl.pretty_print_ast(ast, gen_target, mode)
38373855
["statements"] = {
38383856
after = function(node, children)
38393857
local out = { y = node.y, h = 0 }
3858+
if opts.preserve_hashbang and node.hashbang then
3859+
table.insert(out, node.hashbang)
3860+
end
38403861
local space
38413862
for i, child in ipairs(children) do
38423863
add_child(out, child, space, indent)
@@ -10854,7 +10875,7 @@ function tl.process_string(input, is_lua, env, filename, module_name)
1085410875
return result
1085510876
end
1085610877

10857-
tl.gen = function(input, env)
10878+
tl.gen = function(input, env, pp)
1085810879
env = env or assert(tl.init_env(), "Default environment initialization failed")
1085910880
local result = tl.process_string(input, false, env)
1086010881

@@ -10863,7 +10884,7 @@ tl.gen = function(input, env)
1086310884
end
1086410885

1086510886
local code
10866-
code, result.gen_error = tl.pretty_print_ast(result.ast, env.gen_target)
10887+
code, result.gen_error = tl.pretty_print_ast(result.ast, env.gen_target, pp)
1086710888
return code, result
1086810889
end
1086910890

tl.tl

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ local record tl
2424
"5.4"
2525
end
2626

27+
record PrettyPrintOptions
28+
preserve_indent: boolean
29+
preserve_newlines: boolean
30+
preserve_hashbang: boolean
31+
end
32+
2733
record TypeCheckOptions
2834
lax: boolean
2935
filename: string
@@ -125,7 +131,7 @@ local record tl
125131
load: function(string, string, LoadMode, {any:any}): LoadFunction, string
126132
process: function(string, Env, string, FILE): (Result, string)
127133
process_string: function(string, boolean, Env, string, string): Result
128-
gen: function(string, Env): string, Result
134+
gen: function(string, Env, PrettyPrintOptions): string, Result
129135
type_check: function(Node, TypeCheckOptions): Result, string
130136
init_env: function(boolean, boolean | CompatMode, TargetMode, {string}): Env, string
131137
version: function(): string
@@ -204,6 +210,7 @@ local type Result = tl.Result
204210
local type Env = tl.Env
205211
local type Error = tl.Error
206212
local type CompatMode = tl.CompatMode
213+
local type PrettyPrintOptions = tl.PrettyPrintOptions
207214
local type TypeCheckOptions = tl.TypeCheckOptions
208215
local type LoadMode = tl.LoadMode
209216
local type LoadFunction = tl.LoadFunction
@@ -258,6 +265,7 @@ end
258265
--------------------------------------------------------------------------------
259266

260267
local enum TokenKind
268+
"hashbang"
261269
"keyword"
262270
"op"
263271
"string"
@@ -592,10 +600,12 @@ do
592600

593601
local len = #input
594602
if input:sub(1,2) == "#!" then
603+
begin_token()
595604
i = input:find("\n")
596605
if not i then
597606
i = len + 1
598607
end
608+
end_token_here("hashbang")
599609
y = 2
600610
x = 0
601611
end
@@ -1250,6 +1260,7 @@ local record Node
12501260
kind: NodeKind
12511261
symbol_list_slot: integer
12521262
semicolon: boolean
1263+
hashbang: string
12531264

12541265
is_longstring: boolean
12551266

@@ -3164,7 +3175,16 @@ function tl.parse_program(tokens: {Token}, errs: {Error}, filename: string): Nod
31643175
filename = filename or "",
31653176
required_modules = {},
31663177
}
3167-
local _, node = parse_statements(ps, 1, true)
3178+
local i = 1
3179+
local hashbang: string
3180+
if ps.tokens[i].kind == "hashbang" then
3181+
hashbang = ps.tokens[i].tk
3182+
i = i + 1
3183+
end
3184+
local _, node = parse_statements(ps, i, true)
3185+
if hashbang then
3186+
node.hashbang = hashbang
3187+
end
31683188

31693189
clear_redundant_errors(errs)
31703190
return node, ps.required_modules
@@ -3688,19 +3708,17 @@ local spaced_op: {integer:{string:boolean}} = {
36883708
},
36893709
}
36903710

3691-
local record PrettyPrintOpts
3692-
preserve_indent: boolean
3693-
preserve_newlines: boolean
3694-
end
36953711

3696-
local default_pretty_print_ast_opts: PrettyPrintOpts = {
3712+
local default_pretty_print_ast_opts: PrettyPrintOptions = {
36973713
preserve_indent = true,
36983714
preserve_newlines = true,
3715+
preserve_hashbang = false,
36993716
}
37003717

3701-
local fast_pretty_print_ast_opts: PrettyPrintOpts = {
3718+
local fast_pretty_print_ast_opts: PrettyPrintOptions = {
37023719
preserve_indent = false,
37033720
preserve_newlines = true,
3721+
preserve_hashbang = false,
37043722
}
37053723

37063724
local primitive: {TypeName:string} = {
@@ -3714,12 +3732,12 @@ local primitive: {TypeName:string} = {
37143732
["thread"] = "thread",
37153733
}
37163734

3717-
function tl.pretty_print_ast(ast: Node, gen_target: TargetMode, mode: boolean | PrettyPrintOpts): string, string
3735+
function tl.pretty_print_ast(ast: Node, gen_target: TargetMode, mode: boolean | PrettyPrintOptions): string, string
37183736
local err: string
37193737
local indent = 0
37203738

3721-
local opts: PrettyPrintOpts
3722-
if mode is PrettyPrintOpts then
3739+
local opts: PrettyPrintOptions
3740+
if mode is PrettyPrintOptions then
37233741
opts = mode
37243742
elseif mode == true then
37253743
opts = fast_pretty_print_ast_opts
@@ -3837,6 +3855,9 @@ function tl.pretty_print_ast(ast: Node, gen_target: TargetMode, mode: boolean |
38373855
["statements"] = {
38383856
after = function(node: Node, children: {Output}): Output
38393857
local out: Output = { y = node.y, h = 0 }
3858+
if opts.preserve_hashbang and node.hashbang then
3859+
table.insert(out, node.hashbang)
3860+
end
38403861
local space: string
38413862
for i, child in ipairs(children) do
38423863
add_child(out, child, space, indent)
@@ -10854,7 +10875,7 @@ function tl.process_string(input: string, is_lua: boolean, env: Env, filename: s
1085410875
return result
1085510876
end
1085610877

10857-
tl.gen = function(input: string, env: Env): string, Result
10878+
tl.gen = function(input: string, env: Env, pp: PrettyPrintOptions): string, Result
1085810879
env = env or assert(tl.init_env(), "Default environment initialization failed")
1085910880
local result = tl.process_string(input, false, env)
1086010881

@@ -10863,7 +10884,7 @@ tl.gen = function(input: string, env: Env): string, Result
1086310884
end
1086410885

1086510886
local code: string
10866-
code, result.gen_error = tl.pretty_print_ast(result.ast, env.gen_target)
10887+
code, result.gen_error = tl.pretty_print_ast(result.ast, env.gen_target, pp)
1086710888
return code, result
1086810889
end
1086910890

0 commit comments

Comments
 (0)