11use std:: {
2- error:: Error ,
3- fmt,
4- fmt:: Display ,
5- io,
6- io:: { Read , Write } ,
7- str:: FromStr ,
8- string:: String ,
9- time:: Instant ,
2+ error:: Error , ffi:: OsString , fmt, fmt:: Display , io, str:: FromStr , string:: String , time:: Instant ,
103} ;
114
12- use clap:: {
13- Arg ,
14- ArgAction :: { Set , SetTrue } ,
15- ArgMatches , Command ,
16- } ;
5+ use clap:: { Args , Parser , Subcommand } ;
176use fungoid:: { examples:: EXAMPLES , execution:: ExecutionState , program:: Program } ;
187use humantime:: format_duration;
198use itertools:: Itertools ;
@@ -26,60 +15,122 @@ fn main() {
2615 }
2716}
2817
29- type GenericResult = Result < ( ) , Box < dyn std:: error:: Error > > ;
30-
31- fn command ( ) -> Command {
32- Command :: new ( "fungoid" )
33- . version ( "0.2.1" )
34- . author ( "Josh Karpel <josh.karpel@gmail.com>" )
35- . about ( "A Befunge interpreter written in Rust" )
36- . subcommand (
37- Command :: new ( "run" )
38- . about ( "Execute a program" )
39- . arg (
40- Arg :: new ( "profile" )
41- . long ( "profile" )
42- . action ( SetTrue )
43- . help ( "Enable profiling" ) ,
44- )
45- . arg (
46- Arg :: new ( "trace" )
47- . long ( "trace" )
48- . action ( SetTrue )
49- . help ( "Trace program execution" ) ,
50- )
51- . arg (
52- Arg :: new ( "FILE" )
53- . action ( Set )
54- . required ( true )
55- . help ( "The file to read the program from" ) ,
56- ) ,
57- )
58- . subcommand (
59- Command :: new ( "ide" ) . about ( "Start a TUI IDE" ) . arg (
60- Arg :: new ( "FILE" )
61- . action ( Set )
62- . required ( true )
63- . help ( "The file to read the program from" ) ,
64- ) ,
65- )
66- . subcommand (
67- Command :: new ( "examples" ) . about ( "Print the names of the bundled example programs." ) ,
68- )
69- . arg_required_else_help ( true )
18+ type GenericResult < T > = Result < T , Box < dyn Error > > ;
19+
20+ #[ derive( Debug , Parser ) ]
21+ #[ command( name = "fungoid" , author, version, about) ]
22+ struct Cli {
23+ #[ command( subcommand) ]
24+ command : Commands ,
7025}
71- fn cli ( ) -> GenericResult {
72- let matches = command ( ) . get_matches ( ) ;
73-
74- if let Some ( matches) = matches. subcommand_matches ( "ide" ) {
75- ide ( matches) ?;
76- } else if let Some ( matches) = matches. subcommand_matches ( "run" ) {
77- run_program ( matches) ?;
78- } else if matches. subcommand_matches ( "examples" ) . is_some ( ) {
79- println ! ( "{}" , EXAMPLES . keys( ) . sorted( ) . join( "\n " ) )
80- }
8126
82- Ok ( ( ) )
27+ #[ derive( Debug , Subcommand ) ]
28+ enum Commands {
29+ /// Run a program
30+ #[ command( arg_required_else_help = true ) ]
31+ Run {
32+ /// The path to the file to read the program from
33+ file : OsString ,
34+ /// Enable execution tracing
35+ #[ arg( long) ]
36+ trace : bool ,
37+ /// Enable profiling
38+ #[ arg( long) ]
39+ profile : bool ,
40+ } ,
41+ /// Start the TUI IDE
42+ #[ command( arg_required_else_help = true ) ]
43+ Ide {
44+ /// The path to the file to open
45+ file : OsString ,
46+ } ,
47+ /// Interact with the bundled example programs.
48+ #[ command( arg_required_else_help = true ) ]
49+ Examples ( ExamplesArgs ) ,
50+ }
51+
52+ #[ derive( Debug , Args ) ]
53+ struct ExamplesArgs {
54+ #[ command( subcommand) ]
55+ command : ExamplesCommands ,
56+ }
57+
58+ #[ derive( Debug , Subcommand ) ]
59+ enum ExamplesCommands {
60+ /// Print the available bundled example programs
61+ #[ command( arg_required_else_help = true ) ]
62+ List ,
63+ /// Print one of the example programs to stdout
64+ #[ command( arg_required_else_help = true ) ]
65+ Print { example : String } ,
66+ /// Run one of the example programs
67+ #[ command( arg_required_else_help = true ) ]
68+ Run {
69+ /// The name of the example to run
70+ example : String ,
71+ /// Enable execution tracing
72+ #[ arg( long) ]
73+ trace : bool ,
74+ /// Enable profiling
75+ #[ arg( long) ]
76+ profile : bool ,
77+ } ,
78+ }
79+
80+ fn cli ( ) -> GenericResult < ( ) > {
81+ match Cli :: parse ( ) . command {
82+ Commands :: Run {
83+ file,
84+ trace,
85+ profile,
86+ } => {
87+ let program = Program :: from_file ( & file) ?;
88+
89+ run_program ( program, trace, profile) ?;
90+
91+ Ok ( ( ) )
92+ }
93+
94+ Commands :: Ide { file } => {
95+ let program = Program :: from_file ( & file) ?;
96+
97+ fungoid:: ide:: ide ( program) ?;
98+
99+ Ok ( ( ) )
100+ }
101+
102+ Commands :: Examples ( ExamplesArgs {
103+ command : ExamplesCommands :: List ,
104+ } ) => {
105+ println ! ( "{}" , EXAMPLES . keys( ) . sorted( ) . join( "\n " ) ) ;
106+
107+ Ok ( ( ) )
108+ }
109+
110+ Commands :: Examples ( ExamplesArgs {
111+ command : ExamplesCommands :: Print { example } ,
112+ } ) => {
113+ let program = get_example ( example. as_str ( ) ) ?;
114+ println ! ( "{}" , program) ;
115+
116+ Ok ( ( ) )
117+ }
118+
119+ Commands :: Examples ( ExamplesArgs {
120+ command :
121+ ExamplesCommands :: Run {
122+ example,
123+ trace,
124+ profile,
125+ } ,
126+ } ) => {
127+ let program = Program :: from_str ( get_example ( example. as_str ( ) ) ?) . unwrap ( ) ;
128+
129+ run_program ( program, trace, profile) ?;
130+
131+ Ok ( ( ) )
132+ }
133+ }
83134}
84135
85136#[ derive( Debug ) ]
@@ -105,61 +156,34 @@ impl Error for NoExampleFound {
105156 }
106157}
107158
108- fn load_program ( matches : & ArgMatches ) -> Result < Program , Box < dyn Error > > {
109- let file = matches. get_one :: < String > ( "FILE" ) . unwrap ( ) ;
110- if file. starts_with ( "example:" ) || file. starts_with ( "examples:" ) {
111- let ( _, e) = file. split_once ( ':' ) . unwrap ( ) ;
112- if let Some ( p) = EXAMPLES . get ( e) {
113- Ok ( Program :: from_str ( p) ?)
114- } else {
115- Err ( Box :: new ( NoExampleFound :: new ( format ! (
116- "No example named '{}'.\n Examples: {:?}" ,
117- e,
118- EXAMPLES . keys( )
119- ) ) ) )
120- }
159+ fn get_example ( example : & str ) -> GenericResult < & str > {
160+ if let Some ( program) = EXAMPLES . get ( example) {
161+ Ok ( program)
121162 } else {
122- Ok ( Program :: from_file ( file) ?)
163+ Err ( Box :: new ( NoExampleFound :: new ( format ! (
164+ "No example named '{}'.\n Examples:\n {}" ,
165+ example,
166+ EXAMPLES . keys( ) . sorted( ) . join( "\n " )
167+ ) ) ) )
123168 }
124169}
125170
126- fn ide ( matches : & ArgMatches ) -> GenericResult {
127- let program = load_program ( matches) ?;
128-
129- fungoid:: ide:: ide ( program) ?;
130-
131- Ok ( ( ) )
132- }
133-
134- fn run_program ( matches : & ArgMatches ) -> GenericResult {
135- let program = load_program ( matches) ?;
136-
171+ fn run_program ( program : Program , trace : bool , profile : bool ) -> GenericResult < ( ) > {
137172 let input = & mut io:: stdin ( ) ;
138173 let output = & mut io:: stdout ( ) ;
139- let program_state =
140- fungoid:: execution:: ExecutionState :: new ( program, matches. get_flag ( "trace" ) , input, output) ;
141-
142- run ( program_state, matches. get_flag ( "profile" ) ) ?;
174+ let mut program_state = ExecutionState :: new ( program, trace, input, output) ;
143175
144- Ok ( ( ) )
145- }
146-
147- pub fn run < R : Read , O : Write > (
148- mut program_state : ExecutionState < R , O > ,
149- profile : bool ,
150- ) -> GenericResult {
151176 let start = Instant :: now ( ) ;
152177 program_state. run ( ) ?;
153178 let duration = start. elapsed ( ) ;
154179
155- let num_seconds = 1.0e-9 * ( duration. as_nanos ( ) as f64 ) ;
156-
157180 if profile {
158181 eprintln ! (
159182 "Executed {} instructions in {} ({} instructions/second)" ,
160183 program_state. instruction_count,
161184 format_duration( duration) ,
162- ( ( program_state. instruction_count as f64 / num_seconds) as u64 ) . separated_string( )
185+ ( ( program_state. instruction_count as f64 / duration. as_secs_f64( ) ) as u64 )
186+ . separated_string( )
163187 ) ;
164188 }
165189
@@ -168,10 +192,12 @@ pub fn run<R: Read, O: Write>(
168192
169193#[ cfg( test) ]
170194mod tests {
171- use crate :: command;
195+ use clap:: CommandFactory ;
196+
197+ use crate :: Cli ;
172198
173199 #[ test]
174200 fn verify_command ( ) {
175- command ( ) . debug_assert ( ) ;
201+ Cli :: command ( ) . debug_assert ( )
176202 }
177203}
0 commit comments