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
use std::{fmt::Debug, fs, path::Path};

use crate::corelib::rail_builtin_dictionary;
use crate::log;
use crate::rail_lib_path;
use crate::rail_machine::{RailRunResult, RailState, RunConventions};
use crate::tokens::{self, Token};

pub struct SourceConventions<'a> {
    pub lib_exts: &'a [&'a str],
    pub lib_list_exts: &'a [&'a str],
}

pub const RAIL_SOURCE_CONVENTIONS: SourceConventions = SourceConventions {
    lib_exts: &[".rail"],
    lib_list_exts: &[".txt"],
};

impl SourceConventions<'_> {
    pub fn is_lib(&self, filename: &str) -> bool {
        self.has_ext(filename, self.lib_exts)
    }

    pub fn is_lib_list(&self, filename: &str) -> bool {
        self.has_ext(filename, self.lib_list_exts)
    }

    fn has_ext(&self, filename: &str, exts: &[&str]) -> bool {
        exts.iter().any(|ext| filename.ends_with(ext))
    }
}

pub fn initial_rail_state(
    skip_stdlib: bool,
    lib_list: Option<String>,
    rc: &'static RunConventions,
) -> RailRunResult {
    let definitions = rail_builtin_dictionary();
    let state = RailState::new_main(definitions, rc);

    let state = if skip_stdlib {
        Ok(state)
    } else {
        let tokens = from_rail_stdlib(rc);
        state.run_tokens(tokens)
    };

    if let Some(lib_list) = lib_list {
        let tokens = from_lib_list(&lib_list, &RAIL_SOURCE_CONVENTIONS);
        state.and_then(|state| state.run_tokens(tokens))
    } else {
        state
    }
}

pub fn get_source_as_tokens(source: String) -> Vec<Token> {
    source.split('\n').flat_map(tokens::tokenize).collect()
}

pub fn get_source_file_as_tokens<P>(path: P) -> Vec<Token>
where
    P: AsRef<Path> + Debug,
{
    let error_msg = format!("Error reading file {:?}", path);
    let source = fs::read_to_string(path).expect(&error_msg);

    get_source_as_tokens(source)
}

pub fn from_rail_stdlib(rc: &RunConventions) -> Vec<Token> {
    let path = rail_lib_path(rc).join("rail-src/stdlib/all.txt");

    if path.is_file() {
        return from_lib_list(path, &RAIL_SOURCE_CONVENTIONS);
    }

    let message = format!(
        "Unable to load stdlib. Wanted to find it at {:?}\nDo you need to run 'railup bootstrap'?",
        path
    );
    log::warn(rc, message);

    vec![]
}

pub fn from_lib_list<P>(path: P, conventions: &SourceConventions) -> Vec<Token>
where
    P: AsRef<Path> + Debug,
{
    let path: &Path = path.as_ref();

    let base_dir = path.parent().unwrap();

    fs::read_to_string(path)
        .unwrap_or_else(|_| panic!("Unable to load library list file {:?}", path))
        .split('\n')
        .filter(|s| !s.is_empty() && !s.starts_with('#'))
        .map(|filepath| base_dir.join(filepath).to_string_lossy().to_string())
        .map(|file| {
            if conventions.is_lib(&file) {
                Some(get_source_file_as_tokens(file))
            } else if conventions.is_lib_list(&file) {
                Some(from_lib_list(file, conventions))
            } else {
                None
            }
        })
        .filter(|list| list.is_some())
        .flat_map(|list| list.unwrap())
        .collect::<Vec<_>>()
}