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
// #todo find a better name, e.g. `lang`, `sys`, `runtime`, tbh api is a good name though.

use std::path::Path;

use crate::{
    context::Context,
    error::Error,
    eval::eval,
    expr::Expr,
    lexer::{token::Token, Lexer},
    macro_expand::macro_expand,
    optimize::optimize,
    parser::Parser,
    prune::prune,
    resolver::Resolver,
};

pub const TAN_FILE_EXTENSION: &str = "tan";

pub const TAN_FILE_EMOJI_EXTENSION: &str = "👅";

pub fn has_tan_extension(path: impl AsRef<Path>) -> bool {
    let path = path.as_ref();
    if let Some(extension) = path.extension() {
        extension == TAN_FILE_EXTENSION || extension == TAN_FILE_EMOJI_EXTENSION
    } else {
        false
    }
}

// #todo argh! optimize this!!
pub fn strip_tan_extension(path: impl Into<String>) -> String {
    let path = path.into();

    if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EXTENSION}")) {
        return path.to_owned();
    } else if let Some(path) = path.strip_suffix(&format!(".{TAN_FILE_EMOJI_EXTENSION}")) {
        return path.to_owned();
    } else {
        return path;
    }
}

/// Lexes a Tan expression encoded as a text string.
pub fn lex_string(input: impl AsRef<str>) -> Result<Vec<Token>, Vec<Error>> {
    let input = input.as_ref();
    let mut lexer = Lexer::new(input);
    lexer.lex()
}

// #todo temp solution for compatibility.
// #todo remove this!
/// Parses a Tan expression encoded as a text string, returns first expression.
pub fn parse_string(input: impl AsRef<str>) -> Result<Expr, Vec<Error>> {
    let input = input.as_ref();

    let mut lexer = Lexer::new(input);
    let tokens = lexer.lex()?;

    let mut parser = Parser::new(&tokens);
    let mut expr = parser.parse()?;

    // #todo temp solution
    let expr = expr.swap_remove(0);

    Ok(expr)
}

/// Parses a Tan expression encoded as a text string, returns all expressions parsed.
pub fn parse_string_all(input: impl AsRef<str>) -> Result<Vec<Expr>, Vec<Error>> {
    let input = input.as_ref();

    let mut lexer = Lexer::new(input);
    let tokens = lexer.lex()?;

    let mut parser = Parser::new(&tokens);
    let exprs = parser.parse()?;

    Ok(exprs)
}

// #todo what is a good name?
/// Reads and resolves a Tan expression encoded as a text string.
/// Updates the environment with definitions.
pub fn resolve_string(
    input: impl AsRef<str>,
    context: &mut Context,
) -> Result<Vec<Expr>, Vec<Error>> {
    let exprs = parse_string_all(input)?;

    // #todo also resolve static-use (normal use) here!

    // #insight
    //
    // AST -> Executable pipeline:
    //
    // - macro-expand
    // - resolve
    // - optimize

    // // Nice debugging tool!
    // for ex in &exprs {
    //     for e in ex.iter() {
    //         println!("-- {e:?}");
    //     }
    // }

    let mut resolved_exprs = Vec::new();

    for expr in exprs {
        // #Insight
        // Macro expansion should be performed before resolving.

        // Prune pass

        // #insight first prune pass needed before macro_expand.

        let Some(expr) = prune(expr) else {
            // The expression is pruned (elided)
            continue;
        };

        // Expand macros.

        // #todo pass a dummy scope here? no need to polute the dyn-time environment with macro stuff.
        let expr = macro_expand(expr, context);

        // #todo temp hack until macro_expand returns multiple errors.
        let Ok(expr) = expr else {
            return Err(vec![expr.unwrap_err()]);
        };

        // #todo maybe a second `prune` pass is needed?

        let Some(expr) = expr else {
            // The expression is pruned (elided)
            // #insight elision can happen also in macro_expand!
            continue;
        };

        // Optimization pass

        // #todo should run after resolve?
        let expr = optimize(expr);

        // Resolve pass (typechecking, definitions, etc)

        // #todo should we push a new env?
        let mut resolver = Resolver::new();
        let expr = resolver.resolve(expr, context)?;

        resolved_exprs.push(expr);
    }

    Ok(resolved_exprs)
}

// #todo this implements in essence a do block. Maybe no value should be returned?
/// Evaluates a Tan expression encoded as a text string.
pub fn eval_string(input: impl AsRef<str>, context: &mut Context) -> Result<Expr, Vec<Error>> {
    let exprs = resolve_string(input, context)?;

    let mut last_value = Expr::One.into();

    for expr in exprs {
        let value = eval(&expr, context);

        let Ok(value) = value else {
            return Err(vec![value.unwrap_err()]);
        };

        last_value = value;
    }

    Ok(last_value)
}