zkas/
main.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    fs::{read_to_string, File},
21    io::Write,
22    process::ExitCode,
23};
24
25use arg::Args;
26
27use darkfi::{
28    zkas::{Analyzer, Compiler, Lexer, Parser, ZkBinary},
29    ANSI_LOGO,
30};
31
32const ABOUT: &str =
33    concat!("zkas ", env!("CARGO_PKG_VERSION"), '\n', env!("CARGO_PKG_DESCRIPTION"));
34
35const USAGE: &str = r#"
36Usage: zkas [OPTIONS] <INPUT>
37
38Arguments:
39  <INPUT>    ZK script to compile
40
41Options:
42  -o <FILE>  Place the output into <FILE>
43  -s         Strip debug symbols
44  -p         Preprocess only; do not compile
45  -i         Interactive semantic analysis
46  -e         Examine decoded bytecode
47  -h         Print this help
48"#;
49
50fn usage() {
51    print!("{}{}\n{}", ANSI_LOGO, ABOUT, USAGE);
52}
53
54fn main() -> ExitCode {
55    let argv;
56    let mut pflag = false;
57    let mut iflag = false;
58    let mut eflag = false;
59    let mut sflag = false;
60    let mut hflag = false;
61    let mut output = String::new();
62
63    {
64        let mut args = Args::new().with_cb(|args, flag| match flag {
65            'p' => pflag = true,
66            'i' => iflag = true,
67            'e' => eflag = true,
68            's' => sflag = true,
69            'o' => output = args.eargf().to_string(),
70            _ => hflag = true,
71        });
72
73        argv = args.parse();
74    }
75
76    if hflag || argv.is_empty() {
77        usage();
78        return ExitCode::FAILURE
79    }
80
81    let filename = argv[0].as_str();
82    let source = match read_to_string(filename) {
83        Ok(v) => v,
84        Err(e) => {
85            eprintln!("Error: Failed reading from \"{}\". {}", filename, e);
86            return ExitCode::FAILURE
87        }
88    };
89
90    // Clean up tabs, and convert CRLF to LF.
91    let source = source.replace('\t', "    ").replace("\r\n", "\n");
92
93    // ANCHOR: zkas
94    // The lexer goes over the input file and separates its content into
95    // tokens that get fed into a parser.
96    let lexer = Lexer::new(filename, source.chars());
97    let tokens = match lexer.lex() {
98        Ok(v) => v,
99        Err(_) => return ExitCode::FAILURE,
100    };
101
102    // The parser goes over the tokens provided by the lexer and builds
103    // the initial AST, not caring much about the semantics, just enforcing
104    // syntax and general structure.
105    let parser = Parser::new(filename, source.chars(), tokens);
106    let (namespace, k, constants, witnesses, statements) = match parser.parse() {
107        Ok(v) => v,
108        Err(_) => return ExitCode::FAILURE,
109    };
110
111    // The analyzer goes through the initial AST provided by the parser and
112    // converts return and variable types to their correct forms, and also
113    // checks that the semantics of the ZK script are correct.
114    let mut analyzer = Analyzer::new(filename, source.chars(), constants, witnesses, statements);
115    if analyzer.analyze_types().is_err() {
116        return ExitCode::FAILURE
117    }
118
119    if iflag && analyzer.analyze_semantic().is_err() {
120        return ExitCode::FAILURE
121    }
122
123    if pflag {
124        println!("{:#?}", analyzer.constants);
125        println!("{:#?}", analyzer.witnesses);
126        println!("{:#?}", analyzer.statements);
127        println!("{:#?}", analyzer.heap);
128        return ExitCode::SUCCESS
129    }
130
131    let compiler = Compiler::new(
132        filename,
133        source.chars(),
134        namespace,
135        k,
136        analyzer.constants,
137        analyzer.witnesses,
138        analyzer.statements,
139        analyzer.literals,
140        !sflag,
141    );
142
143    let bincode = match compiler.compile() {
144        Ok(v) => v,
145        Err(_) => return ExitCode::FAILURE,
146    };
147    // ANCHOR_END: zkas
148
149    let output = if output.is_empty() { format!("{}.bin", filename) } else { output };
150
151    let mut file = match File::create(&output) {
152        Ok(v) => v,
153        Err(e) => {
154            eprintln!("Error: Failed to create \"{}\". {}", output, e);
155            return ExitCode::FAILURE
156        }
157    };
158
159    if let Err(e) = file.write_all(&bincode) {
160        eprintln!("Error: Failed to write bincode to \"{}\". {}", output, e);
161        return ExitCode::FAILURE
162    };
163
164    println!("Wrote output to {}", &output);
165
166    if eflag {
167        let zkbin = ZkBinary::decode(&bincode).unwrap();
168        println!("{:#?}", zkbin);
169    }
170
171    ExitCode::SUCCESS
172}