Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/compiler/compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
///
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
33 changes: 33 additions & 0 deletions src/compiler/format.cc
Original file line number Diff line number Diff line change
@@ -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<const char*>(unit->source()->text());
uint8* formatted = reinterpret_cast<uint8*>(strdup(src));
*formatted_size = unit->source()->size();
return formatted;
}

} // namespace toit::compiler
} // namespace toit
28 changes: 28 additions & 0 deletions src/compiler/format.h
Original file line number Diff line number Diff line change
@@ -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
34 changes: 29 additions & 5 deletions src/compiler/toitc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static void print_usage(int exit_code) {
printf(" { -o <executable> <toitfile|snapshot> | // Write executable.\n");
printf(" -w <snapshot> <toitfile|snapshot> | // Write snapshot file.\n");
printf(" --analyze <toitfiles>... // Analyze Toit files.\n");
printf(" --format <toitfiles>... // Format Toit files.\n");
printf(" --dependencies <toitfiles>... // List dependencies.\n");
printf(" }\n");
exit(exit_code);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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.
Expand All @@ -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<const char**>(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");
Expand Down Expand Up @@ -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<const char*>(source_paths, source_path_count),
Expand Down
22 changes: 22 additions & 0 deletions tools/toit.toit
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Loading