Skip to content

Commit

Permalink
Merge pull request #241 from SteveL-MSFT/tree-sitter-parse
Browse files Browse the repository at this point in the history
Implement use of tree-sitter parser
  • Loading branch information
SteveL-MSFT authored Oct 26, 2023
2 parents 41542ee + 09a818c commit d6acc1d
Show file tree
Hide file tree
Showing 18 changed files with 744 additions and 102 deletions.
18 changes: 11 additions & 7 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,18 @@ New-Item -ItemType Directory $target > $null

# make sure dependencies are built first so clippy runs correctly
$windows_projects = @("pal", "ntreg", "ntstatuserror", "ntuserinfo", "registry")

# projects are in dependency order
$projects = @(
"tree-sitter-dscexpression"
"dsc_lib"
"file_lib"
"dsc"
"osinfo"
"process"
"tools/test_group_resource"
"tree-sitter-dscexpression",
"dsc_lib",
"file_lib",
"dsc",
"osinfo",
"powershellgroup",
"process",
"tools/dsctest",
"tools/test_group_resource",
"y2j"
"powershellgroup"
"resources/brew"
Expand Down
20 changes: 7 additions & 13 deletions dsc/src/resource_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,9 @@ pub fn export(dsc: &mut DscManager, resource: &str, format: &Option<OutputFormat

let mut conf = Configuration::new();

match add_resource_export_results_to_configuration(&dsc_resource, &mut conf) {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
if let Err(err) = add_resource_export_results_to_configuration(&dsc_resource, &mut conf) {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}

let json = match serde_json::to_string(&conf) {
Expand All @@ -190,13 +187,10 @@ pub fn get_resource(dsc: &mut DscManager, resource: &str) -> DscResource {
exit(EXIT_INVALID_ARGS);
}

match dsc.initialize_discovery() {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
};
if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
let resources: Vec<DscResource> = dsc.find_resource(resource).collect();
match resources.len() {
0 => {
Expand Down
34 changes: 14 additions & 20 deletions dsc/src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use dsc_lib::{
dscresources::dscresource::{ImplementedAs, Invoke},
dscresources::resource_manifest::{import_manifest, ResourceManifest},
};
use jsonschema::{JSONSchema, ValidationError};
use jsonschema::JSONSchema;
use serde_yaml::Value;
use std::process::exit;

Expand Down Expand Up @@ -272,18 +272,15 @@ pub fn validate_config(config: &str) {
},
};
let properties = resource_block["properties"].clone();
let _result: Result<(), ValidationError> = match compiled_schema.validate(&properties) {
Ok(()) => Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}

error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
},
};
let validation = compiled_schema.validate(&properties);
if let Err(err) = validation {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}
error!("Error: Resource {type_name} failed validation: {error}");
exit(EXIT_VALIDATION_FAILED);
}
}
}
}
Expand All @@ -303,13 +300,10 @@ pub fn resource(subcommand: &ResourceSubCommand, format: &Option<OutputFormat>,

match subcommand {
ResourceSubCommand::List { resource_name, description, tags } => {
match dsc.initialize_discovery() {
Ok(()) => (),
Err(err) => {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
};
if let Err(err) = dsc.initialize_discovery() {
error!("Error: {err}");
exit(EXIT_DSC_ERROR);
}
let mut write_table = false;
let mut table = Table::new(&["Type", "Version", "Requires", "Description"]);
if format.is_none() && atty::is(Stream::Stdout) {
Expand Down
6 changes: 6 additions & 0 deletions dsc_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "3.0.0-alpha.4"
edition = "2021"

[dependencies]
base64 = "0.21"
derive_builder ="0.12"
jsonschema = "0.17"
regex = "1.7"
Expand All @@ -14,6 +15,11 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
thiserror = "1.0"
chrono = "0.4.26"
tracing = "0.1.37"
tree-sitter = "~0.20.10"
tree-sitter-dscexpression = { path = "../tree-sitter-dscexpression" }

[dev-dependencies]
serde_yaml = "0.9"

[build-dependencies]
cc="*"
1 change: 1 addition & 0 deletions dsc_lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl Configurator {
pub fn new(config: &str) -> Result<Configurator, DscError> {
let mut discovery = Discovery::new()?;
discovery.initialize()?;

Ok(Configurator {
config: config.to_owned(),
discovery,
Expand Down
21 changes: 21 additions & 0 deletions dsc_lib/src/dscerror.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::str::Utf8Error;

use reqwest::StatusCode;
use thiserror::Error;
use chrono::{Local, DateTime};
use tracing::{error, warn};
use tree_sitter::LanguageError;

#[derive(Error, Debug)]
pub enum DscError {
#[error("Function boolean argument conversion error: {0}")]
BooleanConversion(#[from] std::str::ParseBoolError),

#[error("Command: Resource '{0}' [Exit code {1}] {2}")]
Command(String, i32, String),

Expand All @@ -20,6 +26,9 @@ pub enum DscError {
#[error("HTTP status: {0}")]
HttpStatus(StatusCode),

#[error("Function integer argument conversion error: {0}")]
IntegerConversion(#[from] std::num::ParseIntError),

#[error("Regex: {0}")]
Regex(#[from] regex::Error),

Expand All @@ -29,12 +38,18 @@ pub enum DscError {
#[error("Unsupported manifest version: {0}. Must be: {1}")]
InvalidManifestSchemaVersion(String, String),

#[error("Invalid function parameter count for '{0}', expected {1}, got {2}")]
InvalidFunctionParameterCount(String, usize, usize),

#[error("IO: {0}")]
Io(#[from] std::io::Error),

#[error("JSON: {0}")]
Json(#[from] serde_json::Error),

#[error("Language: {0}")]
Language(#[from] LanguageError),

#[error("Manifest: {0}\nJSON: {1}")]
Manifest(String, serde_json::Error),

Expand All @@ -53,6 +68,9 @@ pub enum DscError {
#[error("Operation: {0}")]
Operation(String),

#[error("Parser: {0}")]
Parser(String),

#[error("Resource not found: {0}")]
ResourceNotFound(String),

Expand All @@ -62,6 +80,9 @@ pub enum DscError {
#[error("No Schema: {0}")]
SchemaNotAvailable(String),

#[error("Utf-8 conversion error: {0}")]
Utf8Conversion(#[from] Utf8Error),

#[error("Unknown: {code:?} {message:?}")]
Unknown {
code: i32,
Expand Down
20 changes: 9 additions & 11 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,18 +492,16 @@ fn verify_json(resource: &ResourceManifest, cwd: &str, json: &str) -> Result<(),
},
};
let json: Value = serde_json::from_str(json)?;
let result = match compiled_schema.validate(&json) {
Ok(()) => Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}
if let Err(err) = compiled_schema.validate(&json) {
let mut error = String::new();
for e in err {
error.push_str(&format!("{e} "));
}

Err(DscError::Schema(error))
},
};
result
return Err(DscError::Schema(error));
}

Ok(())
}

fn json_to_hashmap(json: &str) -> Result<HashMap<String, String>, DscError> {
Expand Down
58 changes: 58 additions & 0 deletions dsc_lib/src/functions/base64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use base64::{Engine as _, engine::general_purpose};

use crate::DscError;
use crate::parser::functions::{FunctionArg, FunctionResult};
use super::{Function, AcceptedArgKind};

#[derive(Debug, Default)]
pub struct Base64 {}

impl Function for Base64 {
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::String]
}

fn min_args(&self) -> usize {
1
}

fn max_args(&self) -> usize {
1
}

fn invoke(&self, args: &[FunctionArg]) -> Result<FunctionResult, DscError> {
let FunctionArg::String(arg) = args.get(0).unwrap() else {
return Err(DscError::Parser("Invalid argument type".to_string()));
};
Ok(FunctionResult::String(general_purpose::STANDARD.encode(arg)))
}
}

#[cfg(test)]
mod tests {
use crate::parser::Statement;

#[test]
fn strings() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64('hello world')]").unwrap();
assert_eq!(result, "aGVsbG8gd29ybGQ=");
}

#[test]
fn numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64(123)]");
assert!(result.is_err());
}

#[test]
fn nested() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[base64(base64('hello world'))]").unwrap();
assert_eq!(result, "YUdWc2JHOGdkMjl5YkdRPQ==");
}
}
87 changes: 87 additions & 0 deletions dsc_lib/src/functions/concat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::DscError;
use crate::functions::{Function, FunctionArg, FunctionResult, AcceptedArgKind};

#[derive(Debug, Default)]
pub struct Concat {}

impl Function for Concat {
fn min_args(&self) -> usize {
2
}

fn max_args(&self) -> usize {
usize::MAX
}

fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::String, AcceptedArgKind::Integer]
}

fn invoke(&self, args: &[FunctionArg]) -> Result<FunctionResult, DscError> {
let mut result = String::new();
for arg in args {
match arg {
FunctionArg::String(value) => {
result.push_str(value);
},
FunctionArg::Integer(value) => {
result.push_str(&value.to_string());
},
_ => {
return Err(DscError::Parser("Invalid argument type".to_string()));
}
}
}
Ok(FunctionResult::String(result))
}
}

#[cfg(test)]
mod tests {
use crate::parser::Statement;

#[test]
fn strings() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', 'b')]").unwrap();
assert_eq!(result, "ab");
}

#[test]
fn strings_with_spaces() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a ', ' ', ' b')]").unwrap();
assert_eq!(result, "a b");
}

#[test]
fn numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat(1, 2)]").unwrap();
assert_eq!(result, "12");
}

#[test]
fn string_and_numbers() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', 1, 'b', 2)]").unwrap();
assert_eq!(result, "a1b2");
}

#[test]
fn nested() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a', concat('b', 'c'), 'd')]").unwrap();
assert_eq!(result, "abcd");
}

#[test]
fn invalid_one_parameter() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[concat('a')]");
assert!(result.is_err());
}
}
Loading

0 comments on commit d6acc1d

Please sign in to comment.