From c103fa7476e7f26b0c33919027d52d1a81012e8c Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Thu, 20 Jul 2023 20:10:00 -0300 Subject: [PATCH] docs: rework 'files' example * Use more auxiliary functions, showing the record being passed in as an argument and out as a return * Returning a number from `return main()` does not do what one would expect (it's not equivalent to `os.exit`) * Do not reference snippets of the code itself in the markdown, makes it harder to maintain in sync * Uppercase-convert the input as a trivial example of data transformation between input and output * Use more descriptive variable/field names instead of comments * Do not use `""` as a sentinel value, as it is less idiomatic for Lua --- docs/examples/README.md | 51 ++++------- docs/examples/files/build/main.lua | 111 ++++++++++++++--------- docs/examples/files/src/main.tl | 137 ++++++++++++++++++----------- 3 files changed, 173 insertions(+), 126 deletions(-) diff --git a/docs/examples/README.md b/docs/examples/README.md index ae3e6eff5..1ba840721 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -13,9 +13,9 @@ In all of these examples it is assumed that the root directory is 'examples'. ## Files -This example demonstrates `cyan init` to create a Teal project, as well as the -Teal language construct of `record`, the type `FILE` for file handles, and -basic IO. +This example demonstrates using `cyan` to create and build a Teal project, as +well as the Teal language construct of `record`, the type `FILE` for file +handles, and basic IO. ### Create the Project @@ -43,31 +43,11 @@ files> find . ### Create main.tl -In these examples `main.tl` will be the program that gets run. In other -examples you will have modules that are called from main. +In this example `main.tl` will be the program that gets run. More complex +projects will have modules that are called from main. Create [src/main.tl](files/src/main.tl). -The file handles appear like this in main.tl - -``` --- record to hold file handles -local type Handles = record - i: FILE -- input - o: FILE -- output -end -``` - -And are used like this: -``` -local function main(f: Handles): integer - local lines = f.i:read("a") - -- do work on input, then write it out -- - f.o:write(lines) - return 0 -end -``` - ### Build the Project Use Cyan to build the project. @@ -92,20 +72,25 @@ files> tree . ### Run the Project -In this example the program reads from STDIN and writes to a file. +The program reads input arguments `-i ` and `-o ` from +the command line to select input and output filenames. If those are not +given, it uses standard input and standard output as fallback defaults. + +In this example the program reads data from the `ls` command piped via +standard input, and writes to a file: ``` files> ls -R1 | lua build/main.lua -o tmp.out files> cat tmp.out -build -src -tlconfig.lua +BUILD +SRC +TLCONFIG.LUA -./build: -main.lua +./BUILD: +MAIN.LUA -./src: -main.tl +./SRC: +MAIN.TL ``` You can delete the temporary file. diff --git a/docs/examples/files/build/main.lua b/docs/examples/files/build/main.lua index 3f0f54c1d..57348775e 100644 --- a/docs/examples/files/build/main.lua +++ b/docs/examples/files/build/main.lua @@ -1,5 +1,4 @@ -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 io = _tl_compat and _tl_compat.io or io - +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 io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string @@ -11,58 +10,90 @@ local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 th +local function parse_arguments(args) + local i_name, o_name + local err + local i = 1 + while i <= #args do + local a, b = args[i], args[i + 1] + if a == "-i" then + if not b then + err = "-i name?" + else + i_name = b + i = i + 2 + end + elseif a == "-o" then + if not b then + err = "-o name?" + else + o_name = b + i = i + 2 + end + else + err = "unknown arg: " .. a + end + if err then + error(err) + end + end + return i_name, o_name +end +local function get_fd(name, mode, default) + if not name then + return default + end -local function main(f) - local lines = f.i:read("a") + local fd, err = io.open(name, mode) + if not fd then + return nil, err + end - f.o:write(lines) - return 0 + return fd end +local function get_handles(input_name, output_name) + local handles = {} + local err -local fin, fout = "", "" -local errstr = "" -local i = 1 -while i <= #arg do - local a, b = arg[i], arg[i + 1] - if a == "-i" then - if not b then - errstr = "-i filename?" - else - fin = b - i = i + 2 - end - elseif a == "-o" then - if not b then - errstr = "-o filename?" - else - fout = b - i = i + 2 - end - else - errstr = "unknown arg: " .. a + handles.input, err = get_fd(input_name, "r", io.stdin) + if err then + error(err) end - if errstr ~= "" then - error(errstr) + + handles.output, err = get_fd(output_name, "w", io.stdout) + if err then + error(err) end + + return handles end -local handle = {} -if fin == "" then - handle.i = io.stdin -else - handle.i = assert(io.open(fin, "r")) -end -if fout == "" then - handle.o = io.stdout -else - handle.o = assert(io.open(fout, "w")) +local function process_handles(h) + local lines = h.input:read("*a") + h.input:close() + + + + lines = lines:upper() + + h.output:write(lines) + h.output:close() end +local function main(args) + local input_name, output_name = parse_arguments(args) + + local h = get_handles(input_name, output_name) + + process_handles(h) + + os.exit(0) +end -return main(handle) +main(arg) diff --git a/docs/examples/files/src/main.tl b/docs/examples/files/src/main.tl index 8e8b63ccb..a040e28ec 100644 --- a/docs/examples/files/src/main.tl +++ b/docs/examples/files/src/main.tl @@ -1,68 +1,99 @@ -- files/src/main.tl --- Read content from STDIN or file; write content to STDOUT or file. --- --- build: cyan build --- --- usage: lua files.lua [-i ] [-o ] --- +-- Reads content from STDIN or file; +-- writes content in uppercase to STDOUT or file. ------------------------------------------------------------------------------- -- record to hold file handles -local type Handles = record - i: FILE -- input - o: FILE -- output +local record Handles + input: FILE + output: FILE end +-- a simple command-line parser +local function parse_arguments(args: {string}): string, string + local i_name, o_name: string, string + local err: string + local i = 1 + while i <= #args do + local a, b = args[i], args[i+1] + if a == "-i" then + if not b then + err = "-i name?" + else + i_name = b + i = i + 2 + end + elseif a == "-o" then + if not b then + err = "-o name?" + else + o_name = b + i = i + 2 + end + else + err = "unknown arg: " .. a + end + if err then + -- exit, giving a hint on the way out + error(err) + end + end + return i_name, o_name +end + +-- create or return file handles +local function get_fd(name: string, mode: string, default: FILE): FILE, string + if not name then + return default + end + + local fd, err = io.open(name, mode) + if not fd then + return nil, err + end --- main ----------------------------------------------------------------------- -local function main(f: Handles): integer - local lines = f.i:read("a") - -- do work on input, then write it out -- - f.o:write(lines) - return 0 + return fd end +-- get handles record +local function get_handles(input_name: string, output_name: string): Handles + local handles: Handles = {} + local err: string --- command line --------------------------------------------------------------- -local fin, fout = "", "" -- input and output file names -local errstr = "" -local i = 1 -while i <= #arg do - local a, b = arg[i], arg[i+1] - if a == "-i" then - if not b then - errstr = "-i filename?" - else - fin = b - i = i + 2 - end - elseif a == "-o" then - if not b then - errstr = "-o filename?" - else - fout = b - i = i + 2 - end - else - errstr = "unknown arg: " .. a - end - if errstr ~= "" then -- exit, giving a hint on the way out - error(errstr) - end + handles.input, err = get_fd(input_name, "r", io.stdin) + if err then + error(err) + end + + handles.output, err = get_fd(output_name, "w", io.stdout) + if err then + error(err) + end + + return handles end --- create file handles, as needed -local handle: Handles = {} -if fin == "" then - handle.i = io.stdin -else - handle.i = assert(io.open(fin, "r")) +-- do some work using the file handles +local function process_handles(h: Handles) + local lines = h.input:read("*a") + h.input:close() + + -- simple example of doing work on the input: + -- convert it to ASCII uppercase + lines = lines:upper() + + h.output:write(lines) + h.output:close() end -if fout == "" then - handle.o = io.stdout -else - handle.o = assert(io.open(fout, "w")) + +local function main(args: {string}): integer + local input_name, output_name = parse_arguments(args) + + local h = get_handles(input_name, output_name) + + process_handles(h) + + os.exit(0) end --- launch the main function -return main(handle) +main(arg)