Skip to main content

plc_source/
lib.rs

1use std::{
2    fs::File,
3    io::Read,
4    path::{Path, PathBuf},
5};
6
7use encoding_rs::Encoding;
8use encoding_rs_io::DecodeReaderBytesBuilder;
9use serde::{Deserialize, Serialize};
10
11pub mod source_location;
12/// Represents the type of source a SourceContainer holds
13#[derive(Clone, Copy, Debug)]
14pub enum SourceType {
15    /// A normal text file, the content of the file could be parsed
16    Text,
17
18    /// An xml file, probably cfc
19    Xml,
20
21    /// Unknown type, probably a binary
22    Unknown,
23}
24
25/// SourceContainers offer source-code to be compiled via the load_source function.
26/// Furthermore it offers a location-String used when reporting diagnostics.
27pub trait SourceContainer: Sync + Send {
28    /// loads and returns the SourceEntry that contains the SourceCode and the path it was loaded from
29    fn load_source(&self, encoding: Option<&'static Encoding>) -> Result<SourceCode, String>;
30    /// returns the location of this source-container. Used when reporting diagnostics.
31    fn get_location(&self) -> Option<&Path>;
32
33    /// Returns the `SourceType` for the current container,
34    /// by default everything is Text unless it has the extension for a known binary/object
35    fn get_type(&self) -> SourceType {
36        if let Some(ext) = self.get_location().and_then(|it| it.extension()) {
37            match ext.to_str() {
38                Some("o") | Some("so") | Some("exe") => SourceType::Unknown,
39                //XXX: file ending vs first line? (<?xml ...)
40                Some("cfc") | Some("fbd") | Some("xml") => SourceType::Xml,
41                _ => SourceType::Text,
42            }
43        } else {
44            SourceType::Text
45        }
46    }
47
48    /// Returns a staticly available location for this source
49    fn get_location_str(&self) -> &'static str {
50        let s = self
51            .get_location()
52            .map(|it| it.to_string_lossy())
53            .map(|it| it.to_string())
54            .unwrap_or_else(|| "<internal>".into());
55        Box::leak(s.into_boxed_str())
56    }
57}
58
59/// The SourceCode unit is the smallest unit of compilation that can be passed to the compiler
60#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct SourceCode {
62    /// the source code to be compiled
63    pub source: String,
64    /// the location this code was loaded from
65    pub path: Option<PathBuf>,
66}
67
68/// tests can provide a SourceCode directly
69impl SourceContainer for SourceCode {
70    fn load_source(&self, _: Option<&'static Encoding>) -> Result<SourceCode, String> {
71        Ok(self.clone())
72    }
73
74    fn get_location(&self) -> Option<&Path> {
75        self.path.as_deref()
76    }
77}
78
79pub type BuildDescriptionSource = SourceCode;
80
81///Extension trait to create sources with names from strs, used in tests
82pub trait SourceCodeFactory {
83    fn create_source(self, path: impl Into<PathBuf>) -> SourceCode;
84}
85
86impl SourceCodeFactory for &str {
87    fn create_source(self, path: impl Into<PathBuf>) -> SourceCode {
88        SourceCode::new(self, path)
89    }
90}
91
92impl<T: AsRef<Path> + Sync + Send> SourceContainer for T {
93    fn load_source(&self, encoding: Option<&'static Encoding>) -> Result<SourceCode, String> {
94        let source_type = self.get_type();
95        if matches!(source_type, SourceType::Text | SourceType::Xml) {
96            let mut file = File::open(self).map_err(|err| err.to_string())?;
97            let source = create_source_code(&mut file, encoding)?;
98
99            Ok(SourceCode { source, path: Some(self.as_ref().to_owned()) })
100        } else {
101            Err(format!("{} is not a source file", &self.as_ref().to_string_lossy()))
102        }
103    }
104
105    fn get_location(&self) -> Option<&Path> {
106        Some(self.as_ref())
107    }
108}
109
110pub fn create_source_code<T: Read>(
111    reader: &mut T,
112    encoding: Option<&'static Encoding>,
113) -> Result<String, String> {
114    let mut buffer = String::new();
115    let mut decoder = DecodeReaderBytesBuilder::new().encoding(encoding).build(reader);
116    decoder.read_to_string(&mut buffer).map_err(|err| format!("{err}"))?;
117    Ok(buffer)
118}
119
120impl From<&str> for SourceCode {
121    fn from(src: &str) -> Self {
122        SourceCode { source: src.into(), path: Some("<internal>".into()) }
123    }
124}
125
126impl From<String> for SourceCode {
127    fn from(source: String) -> Self {
128        SourceCode { source, path: Some("<internal>".into()) }
129    }
130}
131
132impl SourceCode {
133    pub fn new(source: impl Into<String>, path: impl Into<PathBuf>) -> Self {
134        SourceCode { source: source.into(), path: Some(path.into()) }
135    }
136
137    pub fn with_path(mut self, name: impl Into<PathBuf>) -> Self {
138        self.path = Some(name.into());
139        self
140    }
141}
142
143pub trait Compilable {
144    type T: SourceContainer;
145    fn containers(self) -> Vec<Self::T>;
146}
147
148impl Compilable for &str {
149    type T = SourceCode;
150    fn containers(self) -> Vec<Self::T> {
151        let code = Self::T::from(self);
152        vec![code]
153    }
154}
155
156impl Compilable for String {
157    type T = SourceCode;
158    fn containers(self) -> Vec<Self::T> {
159        let code = self.into();
160        vec![code]
161    }
162}
163
164impl<S: SourceContainer> Compilable for Vec<S> {
165    type T = S;
166    fn containers(self) -> Vec<Self::T> {
167        self
168    }
169}
170
171impl Compilable for SourceCode {
172    type T = Self;
173
174    fn containers(self) -> Vec<Self::T> {
175        vec![self]
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use crate::create_source_code;
182
183    #[test]
184    fn windows_encoded_file_content_read() {
185        let expected = r"PROGRAM ä
186(* Cöment *)
187END_PROGRAM
188";
189        let mut source = &b"\x50\x52\x4f\x47\x52\x41\x4d\x20\xe4\x0a\x28\x2a\x20\x43\xf6\x6d\x65\x6e\x74\x20\x2a\x29\x0a\x45\x4e\x44\x5f\x50\x52\x4f\x47\x52\x41\x4d\x0a"[..];
190        // let read = std::io::Read()
191        let source = create_source_code(&mut source, Some(encoding_rs::WINDOWS_1252)).unwrap();
192
193        assert_eq!(expected, &source);
194    }
195
196    #[test]
197    fn utf_16_encoded_file_content_read() {
198        let expected = r"PROGRAM ä
199(* Cömment *)
200END_PROGRAM
201";
202
203        let mut source = &b"\xff\xfe\x50\x00\x52\x00\x4f\x00\x47\x00\x52\x00\x41\x00\x4d\x00\x20\x00\xe4\x00\x0a\x00\x28\x00\x2a\x00\x20\x00\x43\x00\xf6\x00\x6d\x00\x6d\x00\x65\x00\x6e\x00\x74\x00\x20\x00\x2a\x00\x29\x00\x0a\x00\x45\x00\x4e\x00\x44\x00\x5f\x00\x50\x00\x52\x00\x4f\x00\x47\x00\x52\x00\x41\x00\x4d\x00\x0a\x00" [..];
204
205        let source = create_source_code(&mut source, None).unwrap();
206        assert_eq!(expected, &source);
207    }
208
209    #[test]
210    fn utf_8_encoded_file_content_read() {
211        let expected = r"PROGRAM ä
212(* Cöment *)
213END_PROGRAM
214";
215
216        let mut source = &b"\x50\x52\x4f\x47\x52\x41\x4d\x20\xc3\xa4\x0a\x28\x2a\x20\x43\xc3\xb6\x6d\x65\x6e\x74\x20\x2a\x29\x0a\x45\x4e\x44\x5f\x50\x52\x4f\x47\x52\x41\x4d\x0a" [..];
217        let source = create_source_code(&mut source, None).unwrap();
218        assert_eq!(expected, &source);
219    }
220}