2021-01-18 13:20:06 +01:00
|
|
|
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
2019-12-23 11:02:50 +01:00
|
|
|
// Use of this source code is governed by an MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
|
|
|
module main
|
|
|
|
|
2020-04-16 15:54:17 +02:00
|
|
|
import os
|
|
|
|
import os.cmdline
|
2020-10-18 12:46:30 +02:00
|
|
|
import rand
|
2020-04-16 15:54:17 +02:00
|
|
|
import v.ast
|
|
|
|
import v.pref
|
|
|
|
import v.fmt
|
|
|
|
import v.util
|
|
|
|
import v.parser
|
|
|
|
import v.table
|
|
|
|
import vhelp
|
2019-12-23 11:02:50 +01:00
|
|
|
|
|
|
|
struct FormatOptions {
|
2019-12-27 17:59:04 +01:00
|
|
|
is_l bool
|
2020-07-17 13:03:07 +02:00
|
|
|
is_c bool // NB: This refers to the '-c' fmt flag, NOT the C backend
|
2019-12-23 11:02:50 +01:00
|
|
|
is_w bool
|
|
|
|
is_diff bool
|
|
|
|
is_verbose bool
|
|
|
|
is_all bool
|
2019-12-27 17:59:04 +01:00
|
|
|
is_debug bool
|
2019-12-30 05:42:23 +01:00
|
|
|
is_noerror bool
|
2020-07-16 19:46:03 +02:00
|
|
|
is_verify bool // exit(1) if the file is not vfmt'ed
|
2020-12-27 16:15:26 +01:00
|
|
|
is_worker bool // true *only* in the worker processes. NB: workers can crash.
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
|
|
|
|
2019-12-27 17:59:04 +01:00
|
|
|
const (
|
2021-01-30 11:38:54 +01:00
|
|
|
formatted_file_token = '\@\@\@' + 'FORMATTED_FILE: '
|
|
|
|
vtmp_folder = util.get_vtmp_folder()
|
2019-12-27 17:59:04 +01:00
|
|
|
)
|
|
|
|
|
2019-12-23 11:02:50 +01:00
|
|
|
fn main() {
|
2020-04-16 15:54:17 +02:00
|
|
|
// if os.getenv('VFMT_ENABLE') == '' {
|
|
|
|
// eprintln('v fmt is disabled for now')
|
|
|
|
// exit(1)
|
|
|
|
// }
|
2019-12-23 11:02:50 +01:00
|
|
|
toolexe := os.executable()
|
2020-04-03 17:38:41 +02:00
|
|
|
util.set_vroot_folder(os.dir(os.dir(os.dir(toolexe))))
|
2020-04-11 09:18:59 +02:00
|
|
|
args := util.join_env_vflags_and_os_args()
|
2020-12-27 14:36:59 +01:00
|
|
|
mut foptions := FormatOptions{
|
2019-12-27 17:59:04 +01:00
|
|
|
is_c: '-c' in args
|
|
|
|
is_l: '-l' in args
|
2020-06-26 03:22:27 +02:00
|
|
|
is_w: '-w' in args
|
2019-12-24 03:43:31 +01:00
|
|
|
is_diff: '-diff' in args
|
|
|
|
is_verbose: '-verbose' in args || '--verbose' in args
|
|
|
|
is_all: '-all' in args || '--all' in args
|
|
|
|
is_worker: '-worker' in args
|
2019-12-27 17:59:04 +01:00
|
|
|
is_debug: '-debug' in args
|
2019-12-30 05:42:23 +01:00
|
|
|
is_noerror: '-noerror' in args
|
2020-07-16 19:46:03 +02:00
|
|
|
is_verify: '-verify' in args
|
2019-12-27 17:59:04 +01:00
|
|
|
}
|
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('vfmt foptions: $foptions')
|
2019-12-24 03:43:31 +01:00
|
|
|
}
|
|
|
|
if foptions.is_worker {
|
|
|
|
// -worker should be added by a parent vfmt process.
|
|
|
|
// We launch a sub process for each file because
|
|
|
|
// the v compiler can do an early exit if it detects
|
|
|
|
// a syntax error, but we want to process ALL passed
|
|
|
|
// files if possible.
|
|
|
|
foptions.format_file(cmdline.option(args, '-worker', ''))
|
|
|
|
exit(0)
|
|
|
|
}
|
|
|
|
// we are NOT a worker at this stage, i.e. we are a parent vfmt process
|
2020-02-16 12:42:28 +01:00
|
|
|
possible_files := cmdline.only_non_options(cmdline.options_after(args, ['fmt']))
|
2019-12-23 11:02:50 +01:00
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('vfmt toolexe: $toolexe')
|
|
|
|
eprintln('vfmt args: ' + os.args.str())
|
|
|
|
eprintln('vfmt env_vflags_and_os_args: ' + args.str())
|
2019-12-24 03:43:31 +01:00
|
|
|
eprintln('vfmt possible_files: ' + possible_files.str())
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2020-04-26 13:49:31 +02:00
|
|
|
mut files := []string{}
|
2019-12-24 03:43:31 +01:00
|
|
|
for file in possible_files {
|
2020-10-13 21:03:53 +02:00
|
|
|
if os.is_dir(file) {
|
|
|
|
files << os.walk_ext(file, '.v')
|
|
|
|
files << os.walk_ext(file, '.vsh')
|
|
|
|
continue
|
|
|
|
}
|
2020-10-04 06:33:38 +02:00
|
|
|
if !file.ends_with('.v') && !file.ends_with('.vv') && !file.ends_with('.vsh') {
|
2020-04-08 14:19:13 +02:00
|
|
|
verror('v fmt can only be used on .v files.\nOffending file: "$file"')
|
|
|
|
continue
|
2020-02-16 12:42:28 +01:00
|
|
|
}
|
|
|
|
if !os.exists(file) {
|
2020-04-08 14:19:13 +02:00
|
|
|
verror('"$file" does not exist')
|
2020-02-16 12:42:28 +01:00
|
|
|
continue
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2019-12-24 03:43:31 +01:00
|
|
|
files << file
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2021-01-30 11:38:54 +01:00
|
|
|
if is_atty(0) == 0 && files.len == 0 {
|
|
|
|
foptions.format_pipe()
|
|
|
|
exit(0)
|
|
|
|
}
|
|
|
|
if files.len == 0 || '-help' in args || '--help' in args {
|
2020-04-16 15:54:17 +02:00
|
|
|
vhelp.show_topic('fmt')
|
2019-12-23 11:02:50 +01:00
|
|
|
exit(0)
|
|
|
|
}
|
2020-04-26 13:49:31 +02:00
|
|
|
mut cli_args_no_files := []string{}
|
2019-12-24 03:43:31 +01:00
|
|
|
for a in os.args {
|
2020-04-26 06:39:23 +02:00
|
|
|
if a !in files {
|
2019-12-24 03:43:31 +01:00
|
|
|
cli_args_no_files << a
|
|
|
|
}
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2020-04-23 01:16:58 +02:00
|
|
|
mut errors := 0
|
2019-12-23 11:02:50 +01:00
|
|
|
for file in files {
|
2020-03-20 16:41:18 +01:00
|
|
|
fpath := os.real_path(file)
|
2020-04-23 01:16:58 +02:00
|
|
|
mut worker_command_array := cli_args_no_files.clone()
|
2020-11-16 18:26:44 +01:00
|
|
|
worker_command_array << ['-worker', util.quote_path(fpath)]
|
2019-12-24 03:43:31 +01:00
|
|
|
worker_cmd := worker_command_array.join(' ')
|
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('vfmt worker_cmd: $worker_cmd')
|
|
|
|
}
|
2019-12-31 06:02:49 +01:00
|
|
|
worker_result := os.exec(worker_cmd) or {
|
|
|
|
errors++
|
|
|
|
continue
|
|
|
|
}
|
2020-12-27 15:26:51 +01:00
|
|
|
// Guard against a possibly crashing worker process.
|
2019-12-31 06:02:49 +01:00
|
|
|
if worker_result.exit_code != 0 {
|
|
|
|
eprintln(worker_result.output)
|
|
|
|
if worker_result.exit_code == 1 {
|
2019-12-27 17:59:04 +01:00
|
|
|
eprintln('vfmt error while formatting file: $file .')
|
|
|
|
}
|
2019-12-24 03:43:31 +01:00
|
|
|
errors++
|
2019-12-31 06:02:49 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if worker_result.output.len > 0 {
|
2020-05-22 17:36:09 +02:00
|
|
|
if worker_result.output.contains(formatted_file_token) {
|
|
|
|
wresult := worker_result.output.split(formatted_file_token)
|
2019-12-31 06:02:49 +01:00
|
|
|
formatted_warn_errs := wresult[0]
|
2020-06-15 13:42:41 +02:00
|
|
|
formatted_file_path := wresult[1].trim_right('\n\r')
|
2020-12-27 16:15:26 +01:00
|
|
|
foptions.post_process_file(fpath, formatted_file_path) or { errors = errors + 1 }
|
2019-12-31 06:02:49 +01:00
|
|
|
if formatted_warn_errs.len > 0 {
|
|
|
|
eprintln(formatted_warn_errs)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2019-12-24 03:43:31 +01:00
|
|
|
}
|
2019-12-31 06:02:49 +01:00
|
|
|
errors++
|
2019-12-24 03:43:31 +01:00
|
|
|
}
|
|
|
|
if errors > 0 {
|
2020-07-17 13:03:07 +02:00
|
|
|
eprintln('Encountered a total of: $errors errors.')
|
2019-12-30 05:42:23 +01:00
|
|
|
if foptions.is_noerror {
|
|
|
|
exit(0)
|
|
|
|
}
|
2020-12-27 15:26:51 +01:00
|
|
|
if foptions.is_verify {
|
|
|
|
exit(1)
|
|
|
|
}
|
|
|
|
if foptions.is_c {
|
|
|
|
exit(2)
|
|
|
|
}
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-24 03:43:31 +01:00
|
|
|
fn (foptions &FormatOptions) format_file(file string) {
|
2020-04-28 15:04:37 +02:00
|
|
|
mut prefs := pref.new_preferences()
|
2020-06-18 18:48:23 +02:00
|
|
|
prefs.is_fmt = true
|
2019-12-23 11:02:50 +01:00
|
|
|
if foptions.is_verbose {
|
2020-04-03 17:38:41 +02:00
|
|
|
eprintln('vfmt2 running fmt.fmt over file: $file')
|
|
|
|
}
|
|
|
|
table := table.new_table()
|
2020-04-16 15:54:17 +02:00
|
|
|
// checker := checker.new_checker(table, prefs)
|
|
|
|
file_ast := parser.parse_file(file, table, .parse_comments, prefs, &ast.Scope{
|
|
|
|
parent: 0
|
|
|
|
})
|
|
|
|
// checker.check(file_ast)
|
2020-05-04 16:22:41 +02:00
|
|
|
formatted_content := fmt.fmt(file_ast, table, foptions.is_debug)
|
2020-04-03 17:38:41 +02:00
|
|
|
file_name := os.file_name(file)
|
2020-10-18 12:46:30 +02:00
|
|
|
ulid := rand.ulid()
|
2021-01-09 19:41:15 +01:00
|
|
|
vfmt_output_path := os.join_path(vtmp_folder, 'vfmt_${ulid}_$file_name')
|
2021-01-26 15:43:10 +01:00
|
|
|
os.write_file(vfmt_output_path, formatted_content) or { panic(err) }
|
2020-02-09 10:08:04 +01:00
|
|
|
if foptions.is_verbose {
|
2020-07-17 13:03:07 +02:00
|
|
|
eprintln('fmt.fmt worked and $formatted_content.len bytes were written to $vfmt_output_path .')
|
2019-12-24 03:43:31 +01:00
|
|
|
}
|
2020-07-17 13:03:07 +02:00
|
|
|
eprintln('$formatted_file_token$vfmt_output_path')
|
2019-12-31 06:02:49 +01:00
|
|
|
}
|
|
|
|
|
2021-01-30 11:38:54 +01:00
|
|
|
fn (foptions &FormatOptions) format_pipe() {
|
|
|
|
mut prefs := pref.new_preferences()
|
|
|
|
prefs.is_fmt = true
|
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('vfmt2 running fmt.fmt over stdin')
|
|
|
|
}
|
|
|
|
input_text := os.get_raw_lines_joined()
|
|
|
|
table := table.new_table()
|
|
|
|
// checker := checker.new_checker(table, prefs)
|
|
|
|
file_ast := parser.parse_text(input_text, '', table, .parse_comments, prefs, &ast.Scope{
|
|
|
|
parent: 0
|
|
|
|
})
|
|
|
|
// checker.check(file_ast)
|
|
|
|
formatted_content := fmt.fmt(file_ast, table, foptions.is_debug)
|
|
|
|
print(formatted_content)
|
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('fmt.fmt worked and $formatted_content.len bytes were written to stdout.')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-16 15:54:17 +02:00
|
|
|
fn print_compiler_options(compiler_params &pref.Preferences) {
|
|
|
|
eprintln(' os: ' + compiler_params.os.str())
|
|
|
|
eprintln(' ccompiler: $compiler_params.ccompiler')
|
2020-03-06 18:53:29 +01:00
|
|
|
eprintln(' path: $compiler_params.path ')
|
|
|
|
eprintln(' out_name: $compiler_params.out_name ')
|
|
|
|
eprintln(' vroot: $compiler_params.vroot ')
|
|
|
|
eprintln('lookup_path: $compiler_params.lookup_path ')
|
|
|
|
eprintln(' out_name: $compiler_params.out_name ')
|
|
|
|
eprintln(' cflags: $compiler_params.cflags ')
|
|
|
|
eprintln(' is_test: $compiler_params.is_test ')
|
|
|
|
eprintln(' is_script: $compiler_params.is_script ')
|
2020-02-09 10:08:04 +01:00
|
|
|
}
|
|
|
|
|
2020-12-27 15:26:51 +01:00
|
|
|
fn (foptions &FormatOptions) post_process_file(file string, formatted_file_path string) ? {
|
2019-12-24 03:43:31 +01:00
|
|
|
if formatted_file_path.len == 0 {
|
|
|
|
return
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
|
|
|
if foptions.is_diff {
|
2020-04-29 10:50:33 +02:00
|
|
|
diff_cmd := util.find_working_diff_command() or {
|
2020-07-17 13:03:07 +02:00
|
|
|
eprintln(err)
|
2019-12-23 11:02:50 +01:00
|
|
|
return
|
|
|
|
}
|
2020-07-17 13:03:07 +02:00
|
|
|
if foptions.is_verbose {
|
|
|
|
eprintln('Using diff command: $diff_cmd')
|
|
|
|
}
|
2020-04-29 10:50:33 +02:00
|
|
|
println(util.color_compare_files(diff_cmd, file, formatted_file_path))
|
2019-12-23 11:02:50 +01:00
|
|
|
return
|
|
|
|
}
|
2020-07-16 19:46:03 +02:00
|
|
|
if foptions.is_verify {
|
|
|
|
diff_cmd := util.find_working_diff_command() or {
|
2020-07-17 13:03:07 +02:00
|
|
|
eprintln(err)
|
2020-07-16 19:46:03 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
x := util.color_compare_files(diff_cmd, file, formatted_file_path)
|
|
|
|
if x.len != 0 {
|
2020-07-20 22:26:44 +02:00
|
|
|
println("$file is not vfmt'ed")
|
2020-12-27 15:26:51 +01:00
|
|
|
return error('')
|
2020-07-16 19:46:03 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2019-12-27 17:59:04 +01:00
|
|
|
fc := os.read_file(file) or {
|
|
|
|
eprintln('File $file could not be read')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
formatted_fc := os.read_file(formatted_file_path) or {
|
|
|
|
eprintln('File $formatted_file_path could not be read')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
is_formatted_different := fc != formatted_fc
|
2020-05-19 17:12:47 +02:00
|
|
|
if foptions.is_c {
|
2019-12-27 17:59:04 +01:00
|
|
|
if is_formatted_different {
|
|
|
|
eprintln('File is not formatted: $file')
|
2020-12-27 15:26:51 +01:00
|
|
|
return error('')
|
2019-12-27 17:59:04 +01:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if foptions.is_l {
|
|
|
|
if is_formatted_different {
|
|
|
|
eprintln('File needs formatting: $file')
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2019-12-23 11:02:50 +01:00
|
|
|
if foptions.is_w {
|
2019-12-27 17:59:04 +01:00
|
|
|
if is_formatted_different {
|
2020-12-27 16:15:26 +01:00
|
|
|
os.mv_by_cp(formatted_file_path, file) or { panic(err) }
|
2019-12-27 17:59:04 +01:00
|
|
|
eprintln('Reformatted file: $file')
|
2020-04-16 15:54:17 +02:00
|
|
|
} else {
|
2019-12-27 17:59:04 +01:00
|
|
|
eprintln('Already formatted file: $file')
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2019-12-27 17:59:04 +01:00
|
|
|
return
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
2019-12-31 06:02:49 +01:00
|
|
|
print(formatted_fc)
|
2019-12-23 11:02:50 +01:00
|
|
|
}
|
|
|
|
|
2020-04-19 00:07:57 +02:00
|
|
|
fn (f FormatOptions) str() string {
|
2021-01-26 15:43:10 +01:00
|
|
|
return
|
|
|
|
'FormatOptions{ is_l: $f.is_l, is_w: $f.is_w, is_diff: $f.is_diff, is_verbose: $f.is_verbose,' +
|
2020-07-17 13:03:07 +02:00
|
|
|
' is_all: $f.is_all, is_worker: $f.is_worker, is_debug: $f.is_debug, is_noerror: $f.is_noerror,' +
|
|
|
|
' is_verify: $f.is_verify" }'
|
2019-12-27 17:59:04 +01:00
|
|
|
}
|
|
|
|
|
2020-04-16 15:54:17 +02:00
|
|
|
fn file_to_mod_name_and_is_module_file(file string) (string, bool) {
|
2020-04-23 01:16:58 +02:00
|
|
|
mut mod_name := 'main'
|
|
|
|
mut is_module_file := false
|
2020-12-27 16:15:26 +01:00
|
|
|
flines := read_source_lines(file) or { return mod_name, is_module_file }
|
2019-12-27 17:59:04 +01:00
|
|
|
for fline in flines {
|
|
|
|
line := fline.trim_space()
|
|
|
|
if line.starts_with('module ') {
|
|
|
|
if !line.starts_with('module main') {
|
|
|
|
is_module_file = true
|
|
|
|
mod_name = line.replace('module ', ' ').trim_space()
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-04-16 15:54:17 +02:00
|
|
|
return mod_name, is_module_file
|
2019-12-24 03:43:31 +01:00
|
|
|
}
|
2020-01-10 23:33:35 +01:00
|
|
|
|
|
|
|
fn read_source_lines(file string) ?[]string {
|
2020-12-27 16:15:26 +01:00
|
|
|
source_lines := os.read_lines(file) or { return error('can not read $file') }
|
2020-02-12 23:19:45 +01:00
|
|
|
return source_lines
|
2020-01-10 23:33:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_compile_name_of_potential_v_project(file string) string {
|
|
|
|
// This function get_compile_name_of_potential_v_project returns:
|
|
|
|
// a) the file's folder, if file is part of a v project
|
|
|
|
// b) the file itself, if the file is a standalone v program
|
2020-03-20 16:41:18 +01:00
|
|
|
pfolder := os.real_path(os.dir(file))
|
2020-01-10 23:33:35 +01:00
|
|
|
// a .v project has many 'module main' files in one folder
|
|
|
|
// if there is only one .v file, then it must be a standalone
|
2020-12-27 16:15:26 +01:00
|
|
|
all_files_in_pfolder := os.ls(pfolder) or { panic(err) }
|
2020-04-26 13:49:31 +02:00
|
|
|
mut vfiles := []string{}
|
2020-01-10 23:33:35 +01:00
|
|
|
for f in all_files_in_pfolder {
|
2020-03-09 02:23:34 +01:00
|
|
|
vf := os.join_path(pfolder, f)
|
2020-01-10 23:33:35 +01:00
|
|
|
if f.starts_with('.') || !f.ends_with('.v') || os.is_dir(vf) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
vfiles << vf
|
|
|
|
}
|
|
|
|
if vfiles.len == 1 {
|
|
|
|
return file
|
|
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////
|
|
|
|
// At this point, we know there are many .v files in the folder
|
|
|
|
// We will have to read them all, and if there are more than one
|
|
|
|
// containing `fn main` then the folder contains multiple standalone
|
|
|
|
// v programs. If only one contains `fn main` then the folder is
|
|
|
|
// a project folder, that should be compiled with `v pfolder`.
|
2020-04-23 01:16:58 +02:00
|
|
|
mut main_fns := 0
|
2020-01-10 23:33:35 +01:00
|
|
|
for f in vfiles {
|
2020-12-27 16:15:26 +01:00
|
|
|
slines := read_source_lines(f) or { panic(err) }
|
2020-01-10 23:33:35 +01:00
|
|
|
for line in slines {
|
|
|
|
if line.contains('fn main()') {
|
|
|
|
main_fns++
|
|
|
|
if main_fns > 1 {
|
|
|
|
return file
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pfolder
|
|
|
|
}
|
2020-02-09 10:08:04 +01:00
|
|
|
|
2020-04-16 15:54:17 +02:00
|
|
|
fn verror(s string) {
|
2020-04-03 17:38:41 +02:00
|
|
|
util.verror('vfmt error', s)
|
|
|
|
}
|