loft 0.0.1-alpha.22

Rusty embedded scripting language
Documentation
use super::*;

enum Placeholder {
    Named(String),
    Positional(Option<usize>),
}

pub fn handle_procedural_macro(name: &str, tokens: &[TokenInfo]) -> Result<Vec<TokenInfo>, Option<String>> {
    match name {
        "stringify" => {
            let content = tokens_to_string(tokens);
            let string_token = TokenInfo {
                token: Token::String(content),
                location: tokens.first().map_or_else(|| Location { line: 0, column: 0 }, |t| t.location.clone()),
            };

            Ok(vec![string_token])
        }

        "concat" => {
            let mut result = String::new();
            for token in tokens {
                if let Token::String(s) = &token.token {
                    result.push_str(s);
                }
            }

            let concat_token = TokenInfo {
                token: Token::String(result),
                location: tokens.first().map_or_else(|| Location { line: 0, column: 0 }, |t| t.location.clone()),
            };

            Ok(vec![concat_token])
        }

        "include_str" => {
            if tokens.len() != 1 {
                return Err(Some("include_str! requires exactly one file path argument".to_string()));
            }

            if let Token::String(path) = &tokens[0].token {
                match std::fs::read_to_string(path) {
                    Ok(content) => {
                        let include_token = TokenInfo {
                            token: Token::String(content),
                            location: tokens[0].location.clone(),
                        };
                        Ok(vec![include_token])
                    }
                    Err(e) => Err(Some(format!("Failed to read file '{}': {}", path, e))),
                }
            } else {
                Err(Some("include_str! argument must be a string literal".to_string()))
            }
        }

        "panic" => {
            let mut format_invocation = Vec::new();
            let first_location = tokens.first().map_or(Location { line: 0, column: 0 }, |t| t.location.clone());

            format_invocation.push(TokenInfo {
                token: Token::Identifier("format".to_string()),
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::Not,
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });

            format_invocation.extend_from_slice(tokens);
            format_invocation.push(TokenInfo {
                token: Token::RightParen,
                location: first_location.clone(),
            });

            // core::panic( format!( ... ) )
            let mut result_tokens = Vec::new();
            result_tokens.push(TokenInfo {
                token: Token::Identifier("core".to_string()),
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::DoubleColon,
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::Identifier("panic".to_string()),
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });
            result_tokens.extend(format_invocation);
            result_tokens.push(TokenInfo {
                token: Token::RightParen,
                location: first_location,
            });
            Ok(result_tokens)
        }

        "println" => {
            let mut format_invocation = Vec::new();
            let first_location = tokens.first().map_or(Location { line: 0, column: 0 }, |t| t.location.clone());

            format_invocation.push(TokenInfo {
                token: Token::Identifier("print".to_string()),
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::Not,
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });

            if tokens.is_empty() {
                format_invocation.push(TokenInfo {
                    token: Token::String("\n".to_string()),
                    location: first_location.clone(),
                });
            } else if let Some(TokenInfo { token: Token::String(s), location }) = tokens.first() {
                let mut new_format_string = s.clone();
                new_format_string.push('\n');

                format_invocation.push(TokenInfo {
                    token: Token::String(new_format_string),
                    location: location.clone(),
                });

                format_invocation.extend_from_slice(&tokens[1..]);
            } else {
                return Err(Some("println! requires a string literal as its first argument".to_string()));
            }

            format_invocation.push(TokenInfo {
                token: Token::RightParen,
                location: first_location.clone(),
            });

            Ok(format_invocation)
        }

        "print" => {
            let mut format_invocation = Vec::new();
            let first_location = tokens.first().map_or(Location { line: 0, column: 0 }, |t| t.location.clone());

            format_invocation.push(TokenInfo {
                token: Token::Identifier("format".to_string()),
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::Not,
                location: first_location.clone(),
            });

            format_invocation.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });

            format_invocation.extend_from_slice(tokens);
            format_invocation.push(TokenInfo {
                token: Token::RightParen,
                location: first_location.clone(),
            });

            // core::write( format!( ... ) )
            let mut result_tokens = Vec::new();
            result_tokens.push(TokenInfo {
                token: Token::Identifier("core".to_string()),
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::DoubleColon,
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::Identifier("write".to_string()),
                location: first_location.clone(),
            });
            result_tokens.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });
            result_tokens.extend(format_invocation);
            result_tokens.push(TokenInfo {
                token: Token::RightParen,
                location: first_location,
            });
            Ok(result_tokens)
        }

        "format" => {
            if tokens.is_empty() {
                return Ok(vec![TokenInfo {
                    token: Token::String("".to_string()),
                    location: Location { line: 0, column: 0 },
                }]);
            }

            let fmt_token = &tokens[0];
            let fmt_str = if let Token::String(ref s) = fmt_token.token {
                s.clone()
            } else {
                return Err(Some("format! requires a string literal as its first argument".to_string()));
            };

            let mut parts = Vec::new();
            let mut placeholders = Vec::new();
            let mut current_part = String::new();
            let mut chars = fmt_str.chars().peekable();

            while let Some(c) = chars.next() {
                if c == '{' {
                    match chars.peek() {
                        Some(&'}') => {
                            chars.next(); // Consume '}'
                            parts.push(std::mem::take(&mut current_part));
                            placeholders.push(Placeholder::Positional(None));
                        }
                        Some(_) => {
                            let mut content = String::with_capacity(8);
                            let mut found_closing = false;

                            while let Some(next_c) = chars.next() {
                                if next_c == '}' {
                                    found_closing = true;
                                    break;
                                }
                                content.push(next_c);
                            }

                            if !found_closing {
                                return Err(Some("Unmatched '{' in format string".to_string()));
                            }

                            parts.push(std::mem::take(&mut current_part));

                            if !content.is_empty() && content.chars().all(|c| c.is_ascii_digit()) {
                                if let Ok(index) = content.parse::<usize>() {
                                    placeholders.push(Placeholder::Positional(Some(index)));
                                } else {
                                    placeholders.push(Placeholder::Named(content));
                                }
                            } else {
                                placeholders.push(Placeholder::Named(content));
                            }
                        }
                        None => {
                            return Err(Some("Unmatched '{' in format string".to_string()));
                        }
                    }
                } else {
                    current_part.push(c);
                }
            }

            parts.push(current_part);

            let args = extract_macro_arguments(&tokens[1..])?;
            let mut named_args = std::collections::HashMap::new();

            for arg in &args {
                if let Some(Token::Identifier(name)) = arg.first().map(|t| &t.token) {
                    named_args.insert(name.clone(), arg.clone());
                }
            }

            let mut new_tokens = Vec::new();
            let first_location = fmt_token.location.clone();

            new_tokens.push(TokenInfo {
                token: Token::Identifier("core".to_string()),
                location: first_location.clone(),
            });

            new_tokens.push(TokenInfo {
                token: Token::DoubleColon,
                location: first_location.clone(),
            });

            new_tokens.push(TokenInfo {
                token: Token::Identifier("concat".to_string()),
                location: first_location.clone(),
            });

            new_tokens.push(TokenInfo {
                token: Token::LeftParen,
                location: first_location.clone(),
            });

            let mut positional_index = 0;

            for (i, part) in parts.iter().enumerate() {
                new_tokens.push(TokenInfo {
                    token: Token::String(part.clone()),
                    location: first_location.clone(),
                });

                if i < placeholders.len() {
                    match &placeholders[i] {
                        Placeholder::Positional(Some(index)) => {
                            if *index < args.len() {
                                new_tokens.push(TokenInfo {
                                    token: Token::Comma,
                                    location: first_location.clone(),
                                });
                                new_tokens.extend(args[*index].clone());
                            } else {
                                return Err(Some(format!("format! expected argument at index {}, but got {} arguments", index, args.len())));
                            }
                        }
                        Placeholder::Positional(None) => {
                            if positional_index < args.len() {
                                new_tokens.push(TokenInfo {
                                    token: Token::Comma,
                                    location: first_location.clone(),
                                });
                                new_tokens.extend(args[positional_index].clone());
                                positional_index += 1;
                            } else {
                                return Err(Some(format!("format! expected {} arguments, got {}", positional_index + 1, args.len())));
                            }
                        }
                        Placeholder::Named(name) => {
                            if let Some(arg_tokens) = named_args.get(name) {
                                new_tokens.push(TokenInfo {
                                    token: Token::Comma,
                                    location: first_location.clone(),
                                });
                                new_tokens.extend(arg_tokens.clone());
                            } else {
                                new_tokens.push(TokenInfo {
                                    token: Token::Comma,
                                    location: first_location.clone(),
                                });
                                new_tokens.push(TokenInfo {
                                    token: Token::Identifier(name.clone()),
                                    location: first_location.clone(),
                                });
                            }
                        }
                    }
                }

                if i < parts.len() - 1 {
                    new_tokens.push(TokenInfo {
                        token: Token::Comma,
                        location: first_location.clone(),
                    });
                }
            }

            new_tokens.push(TokenInfo {
                token: Token::RightParen,
                location: first_location.clone(),
            });

            Ok(new_tokens)
        }

        // add more built-in procedural macros later
        _ => Err(None),
    }
}