///|
/// Convert a parser error into the common diagnostic format.
fn parse_diagnostic(
error : @parser.ParseError,
file_name : String,
) -> @diagnostic.Diagnostic {
@diagnostic.Diagnostic::new(
"RTL001",
@diagnostic.Error,
error.message,
error.span,
).with_file(file_name)
}
///|
fn usage() -> String {
options_usage()
}
///|
/// Write a renderer result without adding an extra blank line.
fn emit(text : String) -> Unit {
for line in text.split("\n") {
if !line.is_empty() {
println(line)
}
}
}
///|
fn outline(design : @syntax.Design) -> String {
let builder = StringBuilder()
for mod in design.modules {
builder.write_string("module ")
builder.write_string(mod.name)
builder.write_string("\n")
for port in mod.ports {
builder.write_string(" port ")
builder.write_string(port.name)
builder.write_string("\n")
}
for signal in mod.signals {
builder.write_string(" signal ")
builder.write_string(signal.name)
builder.write_string("\n")
}
for item in mod.items {
match item {
@syntax.Instance(instance) => {
builder.write_string(" instance ")
builder.write_string(instance.instance_name)
builder.write_string(" : ")
builder.write_string(instance.module_name)
builder.write_string("\n")
}
_ => ()
}
}
}
builder.to_string()
}
///|
fn print_diagnostics(
diagnostics : Array[@diagnostic.Diagnostic],
source : @source.SourceFile,
format : String,
) -> Unit {
if format == "json" {
println(@diagnostic.render_json(diagnostics))
} else if format == "sarif" {
emit(@diagnostic.render_sarif(diagnostics, source))
} else {
emit(@diagnostic.render_text(diagnostics, source))
}
}
///|
fn read_source(path : String) -> String {
@fs.read_file_to_string(path) catch {
_ => {
println("moonbit-rtl: cannot read " + path)
@sys.exit(2)
""
}
}
}
///|
fn check_file(path : String, format : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let parsed = @parser.parse(source)
let diagnostics : Array[@diagnostic.Diagnostic] = []
for error in parsed.errors {
diagnostics.push(parse_diagnostic(error, path))
}
if parsed.errors.length() == 0 {
let index = @semantic.index(parsed.design)
for diagnostic in @lint.run(index, @lint.default_config()) {
diagnostics.push(diagnostic.with_file(path))
}
}
print_diagnostics(diagnostics, source, format)
if diagnostics.any(fn(diagnostic) { diagnostic.severity is @diagnostic.Error }) {
1
} else {
0
}
}
///|
fn outline_file(path : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let parsed = @parser.parse(source)
if parsed.errors.length() > 0 {
for error in parsed.errors {
println(parse_diagnostic(error, path).message)
}
1
} else {
emit(outline(parsed.design))
0
}
}
///|
fn graph_file(path : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let parsed = @parser.parse(source)
if parsed.errors.length() > 0 {
1
} else {
emit(@diagnostic.render_graph(@semantic.index(parsed.design)))
0
}
}
///|
fn summary_file(path : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let parsed = @parser.parse(source)
println(parsed.summary())
if parsed.errors.length() > 0 {
1
} else {
0
}
}
///|
fn ir_file(path : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let parsed = @parser.parse(source)
if parsed.errors.length() > 0 {
for error in parsed.errors {
println(parse_diagnostic(error, path).message)
}
1
} else {
println(@semantic.index(parsed.design).to_ir_json())
0
}
}
///|
fn tokens_file(path : String) -> Int {
let text = read_source(path)
let source = @source.SourceFile::new(path, text)
let tokens = @lexer.lex(source)
println(@lexer.token_summary(tokens))
for token in @lexer.without_eof(tokens) {
println(token.describe())
}
if @lexer.token_metrics(tokens).invalid > 0 {
1
} else {
0
}
}
///|
fn main {
let args = @env.args()
if args.length() < 3 {
println(usage())
@sys.exit(2)
}
let command = args[1]
let path = args[2]
let mut format = "text"
for argument in args[3:] {
if argument == "json" || argument == "--format=json" {
format = "json"
} else if argument == "sarif" || argument == "--format=sarif" {
format = "sarif"
}
}
let code = match command {
"check" => check_file(path, format)
"outline" => outline_file(path)
"graph" => graph_file(path)
"summary" => summary_file(path)
"ir" => ir_file(path)
"tokens" => tokens_file(path)
_ => {
println(usage())
2
}
}
@sys.exit(code)
}