diff --git a/src/compiler/compiler.cc b/src/compiler/compiler.cc index 9d5a7ec8d..d8c4c329a 100644 --- a/src/compiler/compiler.cc +++ b/src/compiler/compiler.cc @@ -35,6 +35,7 @@ #include "filesystem_hybrid.h" #include "filesystem_local.h" #include "filesystem_lsp.h" +#include "format.h" #include "lambda.h" #include "list.h" #include "lsp/lsp.h" @@ -750,6 +751,52 @@ SnapshotBundle Compiler::compile(const char* source_path, return result; } +void Compiler::format(const char* source_path, + const char* out_path) { + FilesystemHybrid fs(source_path); + SourceManager source_manager(&fs); + PathBuilder builder(&fs); + if (fs.is_absolute(source_path)) { + builder.join(source_path); + } else { + builder.join(fs.relative_anchor(source_path)); + builder.join(source_path); + } + builder.canonicalize(); + + bool show_package_warnings; + bool print_diagnostics_on_stdout; + AnalysisDiagnostics diagnostics(&source_manager, + show_package_warnings=false, + print_diagnostics_on_stdout=true); + + auto load_result = source_manager.load_file(builder.buffer(), Package::invalid()); + if (load_result.status != SourceManager::LoadResult::OK) { + load_result.report_error(&diagnostics); + exit(1); + } + Source* source = load_result.source; + + SymbolCanonicalizer symbols; + Scanner scanner(source, &symbols, &diagnostics); + Parser parser(source, &scanner, &diagnostics); + ast::Unit* unit = parser.parse_unit(); + + int formatted_size; + uint8* formatted = format_unit(unit, &formatted_size); + + // TODO(florian): if the out_path is different we should check whether the + // file exists, and, if yes, if the content has changed. + if (strcmp(source_path, out_path) != 0 || + (formatted_size != unit->source()->size() && + memcmp(formatted, unit->source()->text(), formatted_size) != 0)) { + FILE* file_out = fopen(out_path, "wb"); + fwrite(formatted, 1, formatted_size, file_out); + fclose(file_out); + } + free(formatted); +} + /// Returns the offset in the source for the given line and column number. /// The column_number is in UTF-16 and needs to be adjusted to UTF-8. /// diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h index 533ef6212..3c6d5881e 100644 --- a/src/compiler/compiler.h +++ b/src/compiler/compiler.h @@ -102,6 +102,9 @@ class Compiler { const char* out_path, const Configuration& config); + void format(const char* source_path, + const char* out_path); + private: /// Analyzes the given sources. /// diff --git a/src/compiler/format.cc b/src/compiler/format.cc new file mode 100644 index 000000000..c1fb06362 --- /dev/null +++ b/src/compiler/format.cc @@ -0,0 +1,33 @@ +// Copyright (C) 2025 Toit contributors. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 only. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The license can be found in the file `LICENSE` in the top level +// directory of this repository. + +#include "../top.h" +#include "format.h" +#include "ast.h" + +namespace toit { +namespace compiler { + +/// Formats the given unit and returns the formatted version. +/// The returned string must be freed. +uint8* format_unit(ast::Unit* unit, int* formatted_size) { + const char* src = reinterpret_cast(unit->source()->text()); + uint8* formatted = reinterpret_cast(strdup(src)); + *formatted_size = unit->source()->size(); + return formatted; +} + +} // namespace toit::compiler +} // namespace toit diff --git a/src/compiler/format.h b/src/compiler/format.h new file mode 100644 index 000000000..50f53aeec --- /dev/null +++ b/src/compiler/format.h @@ -0,0 +1,28 @@ +// Copyright (C) 2025 Toit contributors. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 only. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// The license can be found in the file `LICENSE` in the top level +// directory of this repository. + +#pragma once + +namespace toit { +namespace compiler { + +namespace ast { +class Unit; +} + +uint8* format_unit(ast::Unit* unit, int* formatted_size); + +} // namespace toit::compiler +} // namespace toit diff --git a/src/compiler/toitc.cc b/src/compiler/toitc.cc index 07b88885e..863cbedf8 100644 --- a/src/compiler/toitc.cc +++ b/src/compiler/toitc.cc @@ -46,6 +46,7 @@ static void print_usage(int exit_code) { printf(" { -o | // Write executable.\n"); printf(" -w | // Write snapshot file.\n"); printf(" --analyze ... // Analyze Toit files.\n"); + printf(" --format ... // Format Toit files.\n"); printf(" --dependencies ... // List dependencies.\n"); printf(" }\n"); exit(exit_code); @@ -104,6 +105,7 @@ int main(int argc, char **argv) { auto dep_format = compiler::Compiler::DepFormat::none; bool for_language_server = false; bool for_analysis = false; + bool for_format = false; bool for_dependencies = false; const char* vessels_root = null; const char* cross_os = null; @@ -245,6 +247,10 @@ int main(int argc, char **argv) { for_analysis = strcmp(argv[processed_args], "--analyze") == 0; processed_args++; ways_to_run++; + } else if (strcmp(argv[processed_args], "--format") == 0) { + for_format = true; + processed_args++; + ways_to_run++; } else if (strcmp(argv[processed_args], "--dependencies") == 0) { for_dependencies = true; processed_args++; @@ -259,7 +265,7 @@ int main(int argc, char **argv) { } else { if (strcmp(argv[processed_args], "--") == 0) processed_args++; if (ways_to_run == 0) { - ASSERT(!for_analysis && !for_dependencies); // Otherwise ways_to_run would be 1. + ASSERT(!for_format && !for_analysis && !for_dependencies); // Otherwise ways_to_run would be 1. if (processed_args == argc) { fprintf(stderr, "Missing toit-file, snapshot, or string-expression\n"); print_usage(1); @@ -280,7 +286,7 @@ int main(int argc, char **argv) { ASSERT(direct_script != null); fprintf(stderr, "Can't analyze string expressions\n"); } else { - fprintf(stderr, "Toit-file, snapshot, or string-expressions are exclusive\n"); + fprintf(stderr, "Toit-file, snapshot, format, or string-expressions are exclusive\n"); } print_usage(1); } @@ -317,9 +323,17 @@ int main(int argc, char **argv) { } } else { if (args[0] == NULL) { - fprintf(stderr, - "Missing toit-files to '%s'\n", - for_analysis ? "--analyze" : "--dependencies"); + const char* action; + if (for_analysis) { + action = "--analyze"; + } else if (for_dependencies) { + action = "--dependencies"; + } else if (for_format) { + action = "--format"; + } else { + UNREACHABLE(); + } + fprintf(stderr, "Missing toit-files to '%s'\n", action); print_usage(1); } // Add all remaining arguments to the `--analyze`|`--dependencies` as source paths. @@ -329,6 +343,10 @@ int main(int argc, char **argv) { // to the end of the argv list. args = &argv[argc]; } + } else if (for_format) { + // Add all remaining arguments to the `--analyze`|`--dependencies` as source paths. + source_paths = const_cast(args); + source_path_count = argc - processed_args; } if ((dep_file == null) != (dep_format == compiler::Compiler::DepFormat::none)) { fprintf(stderr, "When writing dependencies, both '--dependency-file' and '--dependency-format' must be provided\n"); @@ -358,6 +376,12 @@ int main(int argc, char **argv) { if (for_language_server) { compiler::Compiler compiler; compiler.language_server(compiler_config); + } else if (for_format) { + compiler::Compiler compiler; + for (int i = 0; i < source_path_count; i++) { + auto source_path = source_paths[i]; + compiler.format(source_path, source_path); + } } else if (for_analysis || for_dependencies) { compiler::Compiler compiler; compiler.analyze(List(source_paths, source_path_count), diff --git a/tools/toit.toit b/tools/toit.toit index ee5a3378f..164efdecd 100644 --- a/tools/toit.toit +++ b/tools/toit.toit @@ -150,6 +150,19 @@ main args/List: --run=:: compile-or-analyze-or-run --command="analyze" it root-command.add analyze-command + format-command := cli.Command "format" + --help=""" + Format the given Toit source file(s).""" + --rest=[ + cli.Option "source" + --help="The source file to format." + --required + --multi, + ] + --hidden // Until the feature is correctly implemented. + --run=:: format-sources it + root-command.add format-command + compile-command := cli.Command "compile" --help=""" Compile the given Toit source file to a Toit binary or a Toit snapshot.""" @@ -688,3 +701,12 @@ run-pkg-command command/List arg-names/List rest-args/List invocation/cli.Invoca exit-code := run sdk-dir "toit.pkg" args exit exit-code + +format-sources invocation/cli.Invocation: + sdk-dir := invocation["sdk-dir"] + sources := invocation["source"] + + args := ["--format"] + sources + + exit-code := run sdk-dir "toit.compile" args + exit exit-code