1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
use torque_asm::*;
use log::*;
use switchboard::*;
use std::io::{Read, Write};
fn main() {
let mut args = Switchboard::from_env();
args.positional("source");
args.positional("destination");
args.positional("extension").default("tq");
args.named("no-libs");
args.named("no-project-libs");
args.named("no-env-libs");
args.named("format").default("debug");
args.named("width");
args.named("dry-run").short('n');
args.named("tree");
args.named("help").short('h');
args.named("version");
args.named("verbose").short('v');
args.raise_errors();
let source_path = args.get("source").as_path_opt().map(
|p| p.canonicalize().unwrap_or_else(|e| fatal!("{p:?}: {e:?}")));
let destination = args.get("destination").as_path_opt();
let extension = args.get("extension").as_string();
let opt_extension = Some(extension.as_str());
let no_libs = args.get("no-libs").as_bool();
let no_project_libs = args.get("no-project-libs").as_bool();
let no_env_libs = args.get("no-env-libs").as_bool();
let format = Format::from_str(args.get("format").as_str());
let width = args.get("width").as_u32_opt();
let dry_run = args.get("dry-run").as_bool();
let print_tree = args.get("tree").as_bool();
let print_help = args.get("help").as_bool();
let print_version = args.get("version").as_bool();
let verbose = args.get("verbose").as_bool();
if verbose { log::set_log_level(log::LogLevel::Info) }
if print_version {
let version = env!("CARGO_PKG_VERSION");
eprintln!("torque assembler, version {version}");
eprintln!("written by ben bridle");
std::process::exit(0);
}
if print_help {
eprintln!("\
Usage: tq [source] [destination]
Torque multi-assembler, see http://benbridle.com/torque for documentation.
Arguments:
[source] Path to a source file to assemble
[destination] Path to which output will be written
[extension] File extension to identify library files (default is 'tq')
Switches:
--format=<fmt> Format to apply to assembled bytecode (default is 'debug')
--width=<width> Force a fixed width for all assembled words
--no-project-libs Don't search for libraries in the source parent folder
--no-env-libs Don't search for libraries in the TORQUE_LIBS path variable
--no-libs Combination of --no-project-libs and --no-env-libs
--tree Display a tree visualisation of all included library files
--dry-run (-n) Assemble and show errors only, don't write any output
--help (-h) Prints help
--verbose, (-v) Print additional debug information
--version Print the assembler version and exit
Environment variables:
TORQUE_LIBS
A list of colon-separated paths which will be searched to find
Torque source code files to use as libraries when assembling a
Torque program. If a library file resolves an unresolved symbol
in the program being assembled, the library file will be merged
into the program.
Output formats:
<debug>
Print assembled words as human-readable binary literals.
<inhx>
Original 8-bit Intel hex format.
<inhx32>
Modified 16-bit Intel hex format used by Microchip.
<raw>
Assembled words are converted to big-endian bytestrings and concatenated.
Each word is padded to the nearest byte. Words must all be the same width.
<source>
Print the source file before assembly, with symbols resolved.
Created by Ben Bridle.
");
std::process::exit(0);
}
// -----------------------------------------------------------------------
let mut compiler = new_compiler();
if let Some(path) = &source_path {
info!("Reading program source from {path:?}");
compiler.root_from_path(path).unwrap_or_else(|err| fatal!("{err:?}: {path:?}"))
} else {
let mut source_code = String::new();
info!("Reading program source from standard input");
if let Err(err) = std::io::stdin().read_to_string(&mut source_code) {
fatal!("Could not read from standard input\n{err:?}");
}
compiler.root_from_string(source_code, "<standard input>")
};
if compiler.error().is_some() && !no_libs && !no_project_libs {
compiler.include_libs_from_parent(opt_extension);
}
if compiler.error().is_some() && !no_libs && !no_env_libs {
compiler.include_libs_from_path_variable("TORQUE_LIBS", opt_extension);
}
if print_tree {
compiler.hierarchy().report()
}
if let Some(error) = compiler.error() {
error.report();
std::process::exit(1);
}
let merged_source = compiler.get_compiled_source().unwrap_or_else(|error| {
error.report();
std::process::exit(1);
});
if !dry_run && format == Format::Source {
write_bytes_and_exit(merged_source.as_bytes(), destination.as_ref());
}
// -----------------------------------------------------------------------
let path = Some("<merged source>");
let syntactic = match parse_syntactic(&merged_source, path) {
Ok(tokens) => tokens,
Err(errors) => {
report_syntactic_errors(&errors, &merged_source);
std::process::exit(1);
}
};
let semantic = match parse_semantic(syntactic) {
Ok(tokens) => tokens,
Err(errors) => {
report_semantic_errors(&errors, &merged_source);
std::process::exit(1);
}
};
let intermediate = match parse_intermediate(semantic) {
Ok(tokens) => tokens,
Err(errors) => {
report_intermediate_errors(&errors, &merged_source);
std::process::exit(1);
}
};
let segments = match parse_bytecode(intermediate, width) {
Ok(segments) => segments,
Err(errors) => {
report_bytecode_errors(&errors, &merged_source);
std::process::exit(1);
}
};
if !dry_run {
let result = match format {
Format::Debug => format_debug(&segments),
Format::Inhx => format_inhx(&segments),
Format::Inhx32 => format_inhx32(&segments),
Format::Raw => format_raw(&segments, width),
Format::Source => unreachable!("Source output is handled before full assembly"),
};
match result {
Ok(bytes) => write_bytes_and_exit(&bytes, destination.as_ref()),
Err(error) => report_format_error(&error, format, &merged_source),
}
}
}
fn write_bytes_and_exit<P: AsRef<Path>>(bytes: &[u8], path: Option<&P>) -> ! {
match path {
Some(path) => match std::fs::write(path, bytes) {
Ok(_) => info!("Wrote output to path {:?}", path.as_ref()),
Err(err) => fatal!("Could not write to path {:?}\n{err:?}", path.as_ref()),
}
None => match std::io::stdout().write_all(bytes) {
Ok(_) => info!("Wrote output to standard output"),
Err(err) => fatal!("Could not write to standard output\n{err:?}"),
}
}
std::process::exit(0);
}
|