From a9c9252e66d775c9fe67814c7a6b63c6580bf019 Mon Sep 17 00:00:00 2001 From: Christopher Gagner Date: Fri, 29 Mar 2024 23:21:53 -0400 Subject: [PATCH] Added ifdef parsing. --- moos-ivp-language-server/src/cache.rs | 464 +----------------- moos-ivp-language-server/src/main.rs | 1 + moos-ivp-language-server/src/parsers/mod.rs | 1 + .../src/parsers/nsplug.rs | 464 ++++++++++++++++++ moos-parser/src/nsplug/error.rs | 3 +- moos-parser/src/nsplug/lexer.rs | 2 +- moos-parser/src/nsplug/nsplug.lalrpop | 112 +++-- moos-parser/src/nsplug/tree.rs | 173 ++++++- 8 files changed, 700 insertions(+), 520 deletions(-) create mode 100644 moos-ivp-language-server/src/parsers/mod.rs create mode 100644 moos-ivp-language-server/src/parsers/nsplug.rs diff --git a/moos-ivp-language-server/src/cache.rs b/moos-ivp-language-server/src/cache.rs index 1c78e47..92d2287 100644 --- a/moos-ivp-language-server/src/cache.rs +++ b/moos-ivp-language-server/src/cache.rs @@ -1,27 +1,18 @@ -use std::collections::HashMap; - -use lsp_types::{ - Diagnostic, DiagnosticSeverity, SemanticToken, SemanticTokenModifier, SemanticTokens, Url, -}; -use moos_parser::{ - lexers::{self, Location, TokenMap}, - nsplug::{ - self, - error::{PlugParseError, PlugParseErrorKind}, - lexer::{State, Token}, - }, - Lexer, LinesParser, ParseError, PlugParser, -}; +use crate::parsers::nsplug; +use lsp_types::{Diagnostic, SemanticToken, SemanticTokenModifier, SemanticTokens, Url}; +use moos_parser::lexers::TokenMap; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use tracing::{debug, error, info, trace, warn}; #[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)] -struct SemanticTokenInfo { - token_type: u32, - token_modifiers: u32, +pub struct SemanticTokenInfo { + pub token_type: u32, + pub token_modifiers: u32, } -enum TokenTypes { +#[derive(Debug, Copy, Clone)] +pub enum TokenTypes { /// For tokens that represent a comment. Comment = 0, /// For tokens that represent a language keyword. @@ -149,10 +140,10 @@ impl Project { #[derive(Debug)] pub struct Document { - uri: Url, - text: String, - file_type: FileType, - semantic_tokens: TokenMap, + pub uri: Url, + pub text: String, + pub file_type: FileType, + pub semantic_tokens: TokenMap, pub diagnostics: Vec, } @@ -169,204 +160,7 @@ impl Document { pub fn refresh(&mut self) { self.clear(); - - info!("Parsing: {:?}", &self.text); - - let mut lexer = moos_parser::nsplug::lexer::Lexer::new(&self.text); - let mut state = moos_parser::nsplug::lexer::State::default(); - let result = PlugParser::new().parse(&mut state, &self.text, lexer); - - info!("Parse Results: {result:?}"); - - use moos_parser::nsplug::tree::Line::*; - use moos_parser::nsplug::tree::MacroType; - if let Ok(lines) = result { - for l in lines { - match l { - Macro { - macro_type, - comment, - line, - } => { - match macro_type { - MacroType::Define { definition, range } => { - // TODO: Add declaration, definition, and references - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::Include { path, range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::IfDef { range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::IfNotDef { range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::ElseIfDef { range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::Else { range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - MacroType::EndIf { range } => { - // - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, - }, - ); - } - } - } - Variable { variable, line } => { - // TODO: Add a reference to the variable - - match variable { - nsplug::tree::Variable::Regular { text: _, range } - | nsplug::tree::Variable::Upper { text: _, range } - | nsplug::tree::Variable::Partial { text: _, range } - | nsplug::tree::Variable::PartialUpper { text: _, range } => { - self.semantic_tokens.insert( - line, - range, - SemanticTokenInfo { - token_type: TokenTypes::Variable as u32, - token_modifiers: 0, - }, - ); - } - } - } - _ => {} - } - } - } - - let iter = self.diagnostics.iter(); - // TODO: Add new method to handle converting errors into diagnostics - // TODO: Only create diagnostics if the client supports diagnostics - // TODO: Need to handle dropped tokens - for e in state.errors { - match e.error { - ParseError::User { error } => match error.kind { - PlugParseErrorKind::InvalidConfigBlock => {} - PlugParseErrorKind::MissingNewLine => { - let d = Diagnostic::new( - lsp_types::Range { - start: error.loc_start.into(), - end: error.loc_start.into(), - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - String::from("Missing new line after application name."), - None, - None, - ); - self.diagnostics.push(d); - } - PlugParseErrorKind::MissingTrailing(c) => { - let d = Diagnostic::new( - lsp_types::Range { - start: error.loc_start.into(), - end: error.loc_end.into(), - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Missing trailing character {c:?}"), - None, - None, - ); - self.diagnostics.push(d); - } - PlugParseErrorKind::UnexpectedSymbol(c) => {} - PlugParseErrorKind::UnexpectedComment(comment) => { - let d = Diagnostic::new( - lsp_types::Range { - start: error.loc_start.into(), - end: error.loc_end.into(), - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Unexpected comment: {comment}"), - None, - None, - ); - self.diagnostics.push(d); - } - PlugParseErrorKind::UnknownMacro(_) => {} - }, - ParseError::UnrecognizedToken { token, expected } => { - let (loc_start, token, loc_end) = token; - let d = Diagnostic::new( - lsp_types::Range { - start: loc_start.into(), - end: loc_end.into(), - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Unrecognized token: {:?}. Expected: {:?}", token, expected), - None, - None, - ); - self.diagnostics.push(d); - } - _ => {} - } - } + nsplug::parse(self); } pub fn get_semantic_tokens(&self) -> SemanticTokens { @@ -391,236 +185,6 @@ impl Document { } } -/* -Token::Comment(_comment) => Some(SemanticTokenInfo { - token_type: TokenTypes::Comment as u32, - token_modifiers: 0, -}), -Token::PlugVariable(_name) | Token::PlugUpperVariable(_name) => { - Some(SemanticTokenInfo { - token_type: TokenTypes::Variable as u32, - token_modifiers: 0, - }) -} -Token::Float(_, _) | Token::Integer(_, _) => Some(SemanticTokenInfo { - token_type: TokenTypes::Number as u32, - token_modifiers: 0, -}), -Token::MacroDefine -| Token::MacroElse -| Token::MacroElseIfDef -| Token::MacroEndIf -| Token::MacroIfDef -| Token::MacroIfNotDef -| Token::MacroInclude => Some(SemanticTokenInfo { - token_type: TokenTypes::Macro as u32, - token_modifiers: 0, -}), -Token::Quote(_value) => Some(SemanticTokenInfo { - token_type: TokenTypes::String as u32, - token_modifiers: 0, -}), -Token::PartialQuote(_value, _c) => Some(SemanticTokenInfo { - token_type: TokenTypes::String as u32, - token_modifiers: 0, -}), - -Token::BlockKeyword(key) => { - // TODO: This should check the value of name for the current - // application - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Keyword as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::Key(name) => { - let mut found_keyword = false; - - if let Some(app) = &self.current_app { - if self.keywords.contains(name.to_lowercase().as_str()) { - found_keyword = true; - } - // TODO: This should check the value of name for the current - // application - } else { - if self.global_keywords.contains(name.to_lowercase().as_str()) { - found_keyword = true; - } - } - - if found_keyword { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Variable as u32, - token_modifiers_bitset: 0, - }); - - added = true; - } -} -Token::EnvVariable(name) -| Token::PlugVariable((name)) -| Token::PlugUpperVariable(name) => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Variable as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::PartialEnvVariable(value) -| Token::PartialPlugVariable(value) -| Token::PartialPlugUpperVariable(value) => { - let bracket = match token { - Token::PartialEnvVariable(_) => '}', - Token::PartialPlugVariable(_) | Token::PartialPlugUpperVariable(_) => ')', - _ => '}', - }; - let d = Diagnostic::new( - lsp_types::Range { - start: lsp_types::Position { - line: start_loc.line, - character: start_loc.index, - }, - end: lsp_types::Position { - line: end_loc.line, - character: end_loc.index, - }, - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Missing closing bracket for variable: '{}'", bracket).to_owned(), - None, - None, - ); - self.cache.diagnostics.push(d); - - self.cache.semantic_tokens.push(SemanticToken { - delta_line, - delta_start: delta_index, - length, - token_type: TokenTypes::Variable as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::Quote(value) => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line, - delta_start: delta_index, - length, - token_type: TokenTypes::String as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::PartialQuote(value, c) => { - let d = Diagnostic::new( - lsp_types::Range { - start: lsp_types::Position { - line: start_loc.line, - character: start_loc.index, - }, - end: lsp_types::Position { - line: end_loc.line, - character: end_loc.index, - }, - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Missing closing quote mark: {}", c), - None, - None, - ); - self.cache.diagnostics.push(d); - - self.cache.semantic_tokens.push(SemanticToken { - delta_line, - delta_start: delta_index, - length, - token_type: TokenTypes::String as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::UnknownMacro(value) => { - let d = Diagnostic::new( - lsp_types::Range { - start: lsp_types::Position { - line: start_loc.line, - character: start_loc.index, - }, - end: lsp_types::Position { - line: end_loc.line, - character: end_loc.index, - }, - }, - Some(DiagnosticSeverity::ERROR), - None, - None, - format!("Unknown macro: {}", value), - None, - None, - ); - self.cache.diagnostics.push(d); - - // added = true; -} -Token::Float(_, _) | Token::Integer(_, _) => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Number as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::Boolean(_, _) => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Keyword as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::DefineKeyword => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Macro as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -Token::OrOperator | Token::AndOperator => { - self.cache.semantic_tokens.push(SemanticToken { - delta_line: delta_line, - delta_start: delta_index, - length: length, - token_type: TokenTypes::Operator as u32, - token_modifiers_bitset: 0, - }); - added = true; -} -// TODO: We should differentiate between environment variables and -// regular variables -_ => {} -*/ - struct Delcaration {} struct Definition {} diff --git a/moos-ivp-language-server/src/main.rs b/moos-ivp-language-server/src/main.rs index 28733e3..88e438f 100644 --- a/moos-ivp-language-server/src/main.rs +++ b/moos-ivp-language-server/src/main.rs @@ -47,6 +47,7 @@ mod cache; mod handler; mod lsp; +mod parsers; mod tracer; mod trees; diff --git a/moos-ivp-language-server/src/parsers/mod.rs b/moos-ivp-language-server/src/parsers/mod.rs new file mode 100644 index 0000000..55a5c71 --- /dev/null +++ b/moos-ivp-language-server/src/parsers/mod.rs @@ -0,0 +1 @@ +pub mod nsplug; diff --git a/moos-ivp-language-server/src/parsers/nsplug.rs b/moos-ivp-language-server/src/parsers/nsplug.rs new file mode 100644 index 0000000..d8feb94 --- /dev/null +++ b/moos-ivp-language-server/src/parsers/nsplug.rs @@ -0,0 +1,464 @@ +use crate::cache::{Document, SemanticTokenInfo, TokenTypes}; +use lsp_types::{Diagnostic, DiagnosticSeverity}; +use moos_parser::{ + lexers::{self, Location, TokenMap, TokenRange}, + nsplug::{ + self, + error::{PlugParseError, PlugParseErrorKind}, + lexer::{State, Token}, + tree::{ + IfDefBranch, IncludePath, Line, Lines, MacroCondition, MacroDefinition, MacroType, + Quote, Values, Variable, VariableStrings, + }, + }, + ParseError, PlugParser, +}; +use tracing::{debug, error, info, trace, warn}; + +pub fn new_diagnostic( + severity: DiagnosticSeverity, + start: &Location, + end: &Location, + message: String, +) -> Diagnostic { + Diagnostic::new( + lsp_types::Range { + start: (*start).into(), + end: (*end).into(), + }, + Some(severity), + None, + None, + message, + None, + None, + ) +} + +/// Helper method to create an error Diagnostic +pub fn new_error_diagnostic(start: &Location, end: &Location, message: String) -> Diagnostic { + new_diagnostic(DiagnosticSeverity::ERROR, start, end, message) +} + +pub fn new_warning_diagnostic(start: &Location, end: &Location, message: String) -> Diagnostic { + new_diagnostic(DiagnosticSeverity::ERROR, start, end, message) +} + +pub fn parse(document: &mut Document) { + // TODO: We should be able to parse the document without cloning the text, + // but this breaks the borrow checker + let text = document.text.clone(); + let mut lexer = moos_parser::nsplug::lexer::Lexer::new(text.as_str()); + let mut state = moos_parser::nsplug::lexer::State::default(); + let result = PlugParser::new().parse(&mut state, text.as_str(), lexer); + + info!("Parse Results: {result:?}"); + + if let Ok(lines) = result { + handle_lines(document, &lines); + } + + let iter = document.diagnostics.iter(); + iter.for_each(|e| { + error!("Parse Error: {e:?}"); + }); + // TODO: Add new method to handle converting errors into diagnostics + // TODO: Only create diagnostics if the client supports diagnostics + // TODO: Need to handle dropped tokens + for e in state.errors { + match e.error { + ParseError::User { error } => match error.kind { + PlugParseErrorKind::MissingNewLine => { + let d = new_error_diagnostic( + &error.loc_start, + &error.loc_end, + String::from("Missing new line after application name."), + ); + document.diagnostics.push(d); + } + PlugParseErrorKind::MissingTrailing(c) => { + let d = new_error_diagnostic( + &error.loc_start, + &error.loc_end, + format!("Missing trailing character {c:?}"), + ); + document.diagnostics.push(d); + } + PlugParseErrorKind::UnexpectedSymbol(c) => {} + PlugParseErrorKind::UnexpectedComment(comment) => { + let d = new_error_diagnostic( + &error.loc_start, + &error.loc_end, + format!("Unexpected comment: {comment}"), + ); + document.diagnostics.push(d); + } + PlugParseErrorKind::UnknownMacro(text) => { + let d = new_error_diagnostic( + &error.loc_start, + &error.loc_end, + format!("Unknown macro: {text}"), + ); + document.diagnostics.push(d); + } + }, + ParseError::UnrecognizedToken { token, expected } => { + let (loc_start, token, loc_end) = token; + let d = new_error_diagnostic( + &loc_start, + &loc_end, + format!("Unrecognized token: {:?}. Expected: {:#?}", token, expected), + ); + document.diagnostics.push(d); + } + ParseError::UnrecognizedEOF { location, expected } => { + let d = new_error_diagnostic( + &location, + &location, + format!( + "Unrecognized end of file at {:?}. Expected: {:#?}", + location, expected + ), + ); + document.diagnostics.push(d); + } + _ => {} + } + } +} + +fn handle_lines(document: &mut Document, lines: &Lines) { + use moos_parser::nsplug::tree::Line::*; + use moos_parser::nsplug::tree::MacroType; + for l in lines { + match l { + Macro { + macro_type, + comment, + line, + } => { + let line = *line; + match macro_type { + MacroType::Define { definition, range } => { + // TODO: Add declaration, definition, and references + handle_macro_token(document, line, &range); + handle_variable_strings( + document, + line, + &definition.name, + TokenTypes::Variable, + 0, + ); + handle_values(document, line, &definition.value); + } + MacroType::Include { path, range } => { + handle_include(document, line, &path, &range); + } + + MacroType::IfDef { + condition, + branch, + body, + range, + } => { + // + handle_macro_token(document, line, &range); + handle_macro_condition(document, line, &condition); + handle_lines(document, body); + handle_ifdef_branch(document, line, branch); + } + MacroType::IfNotDef { range } => { + // + handle_macro_token(document, line, &range); + } + MacroType::ElseIfDef { range } => { + // + handle_macro_token(document, line, &range); + } + MacroType::Else { range } => { + // + handle_macro_token(document, line, &range); + } + MacroType::EndIf { range } => { + // + handle_macro_token(document, line, &range); + } + } + } + Variable { variable, line } => { + // TODO: Add a reference to the variable + + handle_variable(document, *line, &variable); + } + _ => {} + } + } +} + +fn handle_macro_token(document: &mut Document, line: u32, range: &TokenRange) { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Macro as u32, + token_modifiers: 0, + }, + ); +} + +fn handle_include(document: &mut Document, line: u32, path: &IncludePath, range: &TokenRange) { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Macro as u32, + token_modifiers: 0, + }, + ); + match path { + IncludePath::VariableStrings(values, _range) => { + handle_variable_strings(document, line, &values, TokenTypes::String, 0); + } + IncludePath::Quote(quote) => handle_quote(document, line, "e), + } +} + +fn handle_quote(document: &mut Document, line: u32, quote: &Quote) { + // Insert all of the variables first so they take priority + quote.content.iter().for_each(|v| match v { + nsplug::tree::VariableString::Variable(variable) => { + handle_variable(document, line, variable) + } + _ => {} + }); + + // Then insert one large string for the span of the quote + document.semantic_tokens.insert( + line, + quote.range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::String as u32, + token_modifiers: 0, + }, + ); +} + +fn handle_variable_strings( + document: &mut Document, + line: u32, + values: &VariableStrings, + string_type: TokenTypes, + string_modifiers: u32, +) { + for v in values { + match v { + nsplug::tree::VariableString::String(text, range) => { + handle_string(document, line, text, range, string_type, string_modifiers); + } + nsplug::tree::VariableString::Variable(variable) => { + handle_variable(document, line, variable); + } + } + } +} + +fn handle_string( + document: &mut Document, + line: u32, + _text: &str, + range: &TokenRange, + string_type: TokenTypes, + string_modifiers: u32, +) { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: string_type as u32, + token_modifiers: string_modifiers, + }, + ); +} + +fn handle_variable(document: &mut Document, line: u32, variable: &Variable) { + match variable { + nsplug::tree::Variable::Regular { text: _, range } + | nsplug::tree::Variable::Upper { text: _, range } + | nsplug::tree::Variable::Partial { text: _, range } + | nsplug::tree::Variable::PartialUpper { text: _, range } => { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Variable as u32, + token_modifiers: 0, + }, + ); + } + } +} + +fn handle_values(document: &mut Document, line: u32, values: &Values) { + for v in values { + match v { + nsplug::tree::Value::Boolean(_value, _value_str, range) => { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Keyword as u32, + token_modifiers: 0, + }, + ); + } + nsplug::tree::Value::Integer(_value, _value_str, range) => { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Number as u32, + token_modifiers: 0, + }, + ); + } + nsplug::tree::Value::Float(_value, _value_str, range) => { + document.semantic_tokens.insert( + line, + range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Number as u32, + token_modifiers: 0, + }, + ); + } + nsplug::tree::Value::String(_text, _range) => { + // TODO: Should we color these differently than a quoted string? + // For now, we'll leave these unformatted + } + nsplug::tree::Value::Quote(quote) => handle_quote(document, line, quote), + nsplug::tree::Value::Variable(variable) => handle_variable(document, line, variable), + } + } +} + +fn handle_macro_definition(document: &mut Document, line: u32, definition: &MacroDefinition) { + handle_variable_strings(document, line, &definition.name, TokenTypes::Variable, 0); + handle_values(document, line, &definition.value); +} + +fn handle_macro_condition(document: &mut Document, line: u32, condition: &MacroCondition) { + match condition { + MacroCondition::Simple(definition) => handle_macro_definition(document, line, definition), + MacroCondition::Disjunction { + operator_range, + lhs, + rhs, + } + | MacroCondition::Conjunction { + operator_range, + lhs, + rhs, + } => { + handle_macro_definition(document, line, lhs); + document.semantic_tokens.insert( + line, + operator_range.clone(), + SemanticTokenInfo { + token_type: TokenTypes::Operator as u32, + token_modifiers: 0, + }, + ); + handle_macro_condition(document, line, rhs); + } + } +} + +fn handle_ifdef_branch(document: &mut Document, _parent_line: u32, input_branch: &IfDefBranch) { + // TODO: Add folding ranges + match input_branch { + IfDefBranch::ElseIfDef { + line, + macro_range, + condition, + body, + branch, + } => { + handle_macro_token(document, *line, ¯o_range); + handle_macro_condition(document, *line, &condition); + handle_lines(document, body); + handle_ifdef_branch(document, *line, branch); + } + IfDefBranch::Else { + line, + macro_range, + body, + endif_line, + endif_macro_range, + } => { + handle_macro_token(document, *line, ¯o_range); + handle_lines(document, body); + handle_macro_token(document, *endif_line, &endif_macro_range); + } + IfDefBranch::EndIf { line, macro_range } => { + handle_macro_token(document, *line, ¯o_range); + } + } +} + +/* + +TODO: These are reminders of tokens that we should be handling. + +Token::Comment(_comment) => Some(SemanticTokenInfo { + token_type: TokenTypes::Comment as u32, + token_modifiers: 0, +}), + +Token::BlockKeyword(key) => { + // TODO: This should check the value of name for the current + // application + self.cache.semantic_tokens.push(SemanticToken { + delta_line: delta_line, + delta_start: delta_index, + length: length, + token_type: TokenTypes::Keyword as u32, + token_modifiers_bitset: 0, + }); + added = true; +} + +Token::UnknownMacro(value) => { + let d = Diagnostic::new( + lsp_types::Range { + start: lsp_types::Position { + line: start_loc.line, + character: start_loc.index, + }, + end: lsp_types::Position { + line: end_loc.line, + character: end_loc.index, + }, + }, + Some(DiagnosticSeverity::ERROR), + None, + None, + format!("Unknown macro: {}", value), + None, + None, + ); + self.cache.diagnostics.push(d); + + // added = true; +} + +Token::OrOperator | Token::AndOperator => { + self.cache.semantic_tokens.push(SemanticToken { + delta_line: delta_line, + delta_start: delta_index, + length: length, + token_type: TokenTypes::Operator as u32, + token_modifiers_bitset: 0, + }); + added = true; +} +*/ diff --git a/moos-parser/src/nsplug/error.rs b/moos-parser/src/nsplug/error.rs index c6a5f3b..12a6adf 100644 --- a/moos-parser/src/nsplug/error.rs +++ b/moos-parser/src/nsplug/error.rs @@ -62,7 +62,7 @@ impl<'input> PlugParseError<'input> { loc_start, loc_end: Location { line: loc_start.line, - index: loc_start.index + macro_name.len() as u32, + index: loc_start.index + (macro_name.len() as u32) + 1_u32, }, } } @@ -72,7 +72,6 @@ impl<'input> PlugParseError<'input> { pub enum PlugParseErrorKind<'input> { MissingTrailing(char), MissingNewLine, - InvalidConfigBlock, UnexpectedComment(&'input str), UnexpectedSymbol(char), UnknownMacro(&'input str), diff --git a/moos-parser/src/nsplug/lexer.rs b/moos-parser/src/nsplug/lexer.rs index bcc3642..4364a98 100644 --- a/moos-parser/src/nsplug/lexer.rs +++ b/moos-parser/src/nsplug/lexer.rs @@ -437,12 +437,12 @@ impl<'input> Lexer<'input> { if !unhandled.is_empty() { self.scan_value(unhandled, prev_i); has_whitespace = false; + self.push_token(i, Token::Space, i + 1); } self.previous_index = self.get_safe_index(i + 1); } self.trim_start = true; - self.push_token(i, Token::Space, i + 1); self.previous_index = self.get_safe_index(i + 1); } _ => {} diff --git a/moos-parser/src/nsplug/nsplug.lalrpop b/moos-parser/src/nsplug/nsplug.lalrpop index 8e0ccd0..d1fc13c 100644 --- a/moos-parser/src/nsplug/nsplug.lalrpop +++ b/moos-parser/src/nsplug/nsplug.lalrpop @@ -8,13 +8,14 @@ use lalrpop_util::ErrorRecovery; //grammar<'input>(input: &'input str); grammar<'s, 'input>(state: &'s mut State<'input>, input: &'input str); -pub Lines: Vec> = ; +pub Lines: tree::Lines<'input> = { + => <>.into() +} pub Line: Line<'input> = { + IfDef => <>, MacroDefine => <>, MacroInclude => <>, - //MacroElse => <>, - //MacroEndif => <>, "EOL" => Line::EndOfLine, // => Line::Comment(comment), UnknownMacro => <>, @@ -38,53 +39,12 @@ MacroDefine: Line<'input> = { } MacroDefinition: MacroDefinition<'input> = { - " " => { + " "+ => { MacroDefinition::new(name.into(), value.into()) - } -} - -MacroElse: Line<'input> = { - "EOL" => { - if !extras.is_empty() { - let e = lalrpop_util::ErrorRecovery { - error: lalrpop_util::ParseError::User { - // TODO: Need to create a more descriptive error - error: PlugParseError::new_missing_new_line(l, Location{line: r.line, index: r.index+1}) - }, - dropped_tokens: vec![] - }; - state.errors.push(e); - Line::Error - } else { - Line::Macro{ - macro_type: MacroType::Else{range: TokenRange::new_line(ml,mr).unwrap()}, - comment, - line: ml.line, - } - } - }, -} - -MacroEndif: Line<'input> = { - "EOL" => { - if !extras.is_empty() { - let e = lalrpop_util::ErrorRecovery { - error: lalrpop_util::ParseError::User { - // TODO: Need to create a more descriptive error - error: PlugParseError::new_missing_new_line(l, Location{line: r.line, index: r.index+1}) - }, - dropped_tokens: vec![] - }; - state.errors.push(e); - Line::Error - } else { - Line::Macro{ - macro_type: MacroType::EndIf{range: TokenRange::new_line(ml,mr).unwrap()}, - comment, - line: ml.line, - } - } }, + " "* => { + MacroDefinition::new(name.into(), Vec::new().into()) + } } MacroInclude: Line<'input> = { @@ -106,7 +66,6 @@ MacroInclude: Line<'input> = { }, // TODO: Need to add support for `#include filename ` - } UnknownMacro: Line<'input> = { @@ -161,6 +120,61 @@ IncludePath: tree::IncludePath<'input> = { Quote => tree::IncludePath::Quote(<>), } +IfDef: Line<'input> = { + "#ifdef" "EOL" => + Line::Macro{ + macro_type: MacroType::IfDef{ + condition, + branch: branch, + body, + range: TokenRange::new_line(ml,mr).unwrap(), + }, + comment: None, + line: ml.line, + }, +} + +IfDefBranch: tree::IfDefBranch<'input> = { + "#elseifdef" "EOL" => + tree::IfDefBranch::ElseIfDef { + line: ml.line, + macro_range: TokenRange::new_line(ml,mr).unwrap(), + condition, + body, + branch: Box::new(branch), + }, + "#else" "EOL" "#endif" => + tree::IfDefBranch::Else { + line: ml.line, + macro_range: TokenRange::new_line(ml,mr).unwrap(), + body, + endif_line: endl.line, + endif_macro_range: TokenRange::new_line(endl,endr).unwrap(), + }, + "#endif" "EOL" => + tree::IfDefBranch::EndIf { + line: ml.line, + macro_range: TokenRange::new_line(ml,mr).unwrap(), + }, +} + +MacroCondition: tree::MacroCondition<'input> = { + MacroDefinition => tree::MacroCondition::Simple(<>), + "||" => + tree::MacroCondition::Disjunction{ + operator_range: TokenRange::new_line(l,r).expect("Invalid token range while parsing `MacroCondition`"), + lhs, + rhs: Box::new(rhs), + }, + "&&" => + tree::MacroCondition::Conjunction{ + operator_range: TokenRange::new_line(l,r).expect("Invalid token range while parsing `MacroCondition`"), + lhs, + rhs: Box::new(rhs), + }, +} + + // --------------------------------------------------------------------------- // Token Definitions // --------------------------------------------------------------------------- diff --git a/moos-parser/src/nsplug/tree.rs b/moos-parser/src/nsplug/tree.rs index 4cc4160..146121a 100644 --- a/moos-parser/src/nsplug/tree.rs +++ b/moos-parser/src/nsplug/tree.rs @@ -55,7 +55,7 @@ impl<'input> TryFrom> for Variable<'input> { // Declares a new struct Values that wraps a Vec vec_wrapper!(Values, Value); -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum Variable<'input> { Regular { text: &'input str, @@ -85,12 +85,30 @@ impl<'input> ToString for Variable<'input> { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum VariableString<'input> { String(&'input str, TokenRange), Variable(Variable<'input>), } +impl<'input> VariableString<'input> { + #[inline] + pub fn is_string(&self) -> bool { + match *self { + VariableString::String(_, _) => true, + VariableString::Variable(_) => false, + } + } + + #[inline] + pub fn is_variable(&self) -> bool { + match *self { + VariableString::String(_, _) => false, + VariableString::Variable(_) => true, + } + } +} + impl<'input> ToString for VariableString<'input> { fn to_string(&self) -> String { match self { @@ -173,6 +191,9 @@ pub enum MacroType<'input> { range: TokenRange, }, IfDef { + condition: MacroCondition<'input>, + branch: IfDefBranch<'input>, + body: Lines<'input>, /// Range of the "#ifdef" range: TokenRange, }, @@ -194,21 +215,45 @@ pub enum MacroType<'input> { }, } +impl<'input> ToString for MacroType<'input> { + fn to_string(&self) -> String { + match self { + MacroType::Define { definition, range } => { + format!("#define {}", definition.to_string()) + } + MacroType::Include { path, range } => { + format!("#include {}", path.to_string()) + } + MacroType::IfDef { + condition, + branch, + body, + range, + } => { + // TODO: Need to recursively print the branch and lines + format!("#ifdef {}", condition.to_string()) + } + _ => "".to_string(), + } + } +} + #[derive(Debug)] pub struct MacroDefinition<'input> { - name: Values<'input>, - value: Values<'input>, + pub name: VariableStrings<'input>, + pub value: Values<'input>, } impl<'input> MacroDefinition<'input> { /// Create a new MacroDefinition - pub fn new(name: Values<'input>, value: Values<'input>) -> Self { + pub fn new(name: VariableStrings<'input>, value: Values<'input>) -> Self { MacroDefinition { name, value } } +} - pub fn eval() -> bool { - // TODO: Implement - false +impl<'input> ToString for MacroDefinition<'input> { + fn to_string(&self) -> String { + return format!("{} {}", self.name.to_string(), self.value.to_string()); } } @@ -217,25 +262,91 @@ pub enum MacroCondition<'input> { // Simple Definition Simple(MacroDefinition<'input>), // Disjunction Expression (a.k.a. Logical-Or) - Disjunction(Vec>), + Disjunction { + operator_range: TokenRange, + lhs: MacroDefinition<'input>, + rhs: Box>, + }, // Conjunction Expression (a.k.a. Logical-And) - Conjunction(Vec>), - // Mixture of Disjunction and Conjunction - This is an error or false - Mixed(Vec>), + Conjunction { + operator_range: TokenRange, + lhs: MacroDefinition<'input>, + rhs: Box>, + }, +} + +impl<'input> ToString for MacroCondition<'input> { + fn to_string(&self) -> String { + match self { + MacroCondition::Simple(condition) => condition.to_string(), + MacroCondition::Disjunction { + operator_range: _, + lhs, + rhs, + } => format!("{} || {}", lhs.to_string(), rhs.to_string()), + MacroCondition::Conjunction { + operator_range: _, + lhs, + rhs, + } => format!("{} && {}", lhs.to_string(), rhs.to_string()), + } + } +} + +#[derive(Debug)] +pub enum IfDefBranch<'input> { + ElseIfDef { + line: u32, + macro_range: TokenRange, + condition: MacroCondition<'input>, + body: Lines<'input>, + branch: Box>, + }, + Else { + line: u32, + macro_range: TokenRange, + body: Lines<'input>, + endif_line: u32, + endif_macro_range: TokenRange, + }, + EndIf { + line: u32, + macro_range: TokenRange, + }, +} + +impl<'input> ToString for IfDefBranch<'input> { + fn to_string(&self) -> String { + match self { + IfDefBranch::ElseIfDef { + line, + macro_range, + condition, + body, + branch, + } => { + format!("#elsifdef {}", condition.to_string()) + } + IfDefBranch::Else { + line, + macro_range, + body, + endif_line, + endif_macro_range, + } => "#else".to_string(), + IfDefBranch::EndIf { line, macro_range } => "#endif".to_string(), + } + } } #[derive(Debug)] pub enum Line<'input> { + /// NOTE: Comments are not really supported by NSPlug. We have them here + /// because they might be soon. Comment { comment: &'input str, line: u32, }, - Define { - name: &'input str, - value: Value<'input>, - comment: Option<&'input str>, - line: u32, - }, Macro { macro_type: MacroType<'input>, comment: Option<&'input str>, @@ -249,6 +360,32 @@ pub enum Line<'input> { EndOfLine, } +impl<'input> ToString for Line<'input> { + fn to_string(&self) -> String { + match self { + Line::Comment { comment, line } => { + format!("// {comment}") + } + Line::Macro { + macro_type, + comment, + line, + } => { + if let Some(comment) = comment { + format!("{} // {comment}", macro_type.to_string()) + } else { + macro_type.to_string() + } + } + Line::Variable { variable, line } => todo!(), + Line::Error => "".to_string(), + Line::EndOfLine => "".to_string(), + } + } +} + +vec_wrapper!(Lines, Line); + // ---------------------------------------------------------------------------- // Tests #[cfg(test)]