Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,930 changes: 1,768 additions & 162 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
description = "Chap is an easy to learn, interpretive, scripting language written in Rust"
license = "GPL-3.0"
readme = "README.md"
# documentation = "https://docs.rs/chap" # if not set docs.rs will generate one and put it in place
# documentation = "https://docs.rs/chap" # if not set docs.rs will generate one and put it in place
homepage = "https://crates.io/crates/chap"
repository = "https://github.com/ali77gh/chap"
categories = ["wasm", "command-line-interface", "compilers"]
Expand All @@ -20,16 +20,18 @@ path = "src/lib.rs"
name = "chap"
path = "src/main.rs"


[dependencies]

[target.'cfg(not(target_family = "wasm"))'.dependencies]
rustyline = "12.0.0"
rand = "0.8.5"
reqwest = {version = "0.12.24", features = ["blocking", "rustls-tls"]}

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
overflow-checks=false
panic = "abort"

[dependencies]
serde_json = {version = "1.0.145"}
serde = { version = "1.0", features = ["derive"] }
4 changes: 4 additions & 0 deletions src/builtin_function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod debugger;
mod delay;
mod list;
mod math;
mod net;
mod random;
mod std_io;
mod strings;
Expand Down Expand Up @@ -101,6 +102,8 @@ pub fn function_match(function_name: &str) -> Option<BuiltinFunction> {
"tostring" | "tostr" => Some(type_conversion::to_string::to_string),
"tofloat" => Some(type_conversion::to_float::to_float),
"toint" => Some(type_conversion::to_int::to_int),
"fromjson" => Some(type_conversion::from_json::from_json),
"tojson" => Some(type_conversion::to_json::to_json),
"now" | "nowsec" | "unixtime" => Some(date_time::now::now_sec),
"waitmil" | "waitmillis" => Some(delay::wait_millis::wait_millis),
"waitsec" | "waitseconds" => Some(delay::wait_second::wait_second),
Expand All @@ -110,6 +113,7 @@ pub fn function_match(function_name: &str) -> Option<BuiltinFunction> {
"input" | "stdin" => Some(std_io::input::input),
"exit" | "quit" | "kill" | "end" => Some(std_io::exit::exit),
"pass" | "nop" | "noop" => Some(pass::pass),
"httpget" => Some(net::http_get::http_get),

// random functions not working in wasm
"randomnumber" | "randnum" => Some(random::random_number::random_number),
Expand Down
117 changes: 117 additions & 0 deletions src/builtin_function/net/http_get.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::str::FromStr;

use reqwest::{
blocking::Client,
header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT},
};

use crate::{
builtin_function::utils::{param_to_datatype, returns},
common::{
data_type::DataType,
errors::{ChapError, Result},
executable::ExecutableLine,
},
runtime::Runtime,
};

#[cfg(not(target_family = "wasm"))]
pub fn http_get(runtime: &mut Runtime, executable: &ExecutableLine) -> Result<()> {
let p1 = param_to_datatype(runtime, executable.params.first(), executable.line_number)?;
let p2 = if let Some(_) = executable.params.get(1) {
Some(param_to_datatype(
runtime,
executable.params.get(1),
executable.line_number,
)?)
} else {
None
};

match p1 {
DataType::String(url) => {
let mut headers = HeaderMap::new();

headers.insert(USER_AGENT, HeaderValue::from_static("Chap"));

if let Some(p2) = p2 {
if let DataType::Map(p2) = p2 {
for (key, value) in p2 {
let header = HeaderName::from_str(key).map_err(|_| {
ChapError::runtime_with_msg(
executable.line_number,
format!("invalid header name: {}", key),
)
})?;

let value = if let DataType::String(value) = value {
value
} else {
return Err(ChapError::runtime_with_msg(
executable.line_number,
format!("header value must me a string, got {}", value.type_name()),
));
};

let value = HeaderValue::from_str(value).map_err(|_| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to initialize TLS backend"),
)
})?;

headers.insert(header, value);
}
} else {
return Err(ChapError::runtime_with_msg(
executable.line_number,
format!(
"second parameter for headers must be a map, got {}",
p2.type_name()
),
));
}
}

let client = Client::builder()
.default_headers(headers)
.build()
.map_err(|_| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to initialize TLS backend"),
)
})?;

let resp = client.get(url).send().map_err(|e| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to send GET HTTP request: {}", e),
)
})?;

let body = resp.text().map_err(|e| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to read response body: {}", e),
)
})?;

returns(runtime, executable, DataType::String(body))
}
_ => {
return Err(ChapError::runtime_with_msg(
executable.line_number,
format!("first parameter must be a string, got {}", p1.type_name()),
));
}
}
}

#[cfg(target_family = "wasm")]
pub fn http_get(runtime: &mut Runtime, executable: &ExecutableLine) -> Result<()> {
Err(ChapError::runtime_with_msg(
executable.line_number,
"http_get not supported in wasm".to_string(),
))
}
1 change: 1 addition & 0 deletions src/builtin_function/net/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod http_get;
77 changes: 77 additions & 0 deletions src/builtin_function/type_conversion/from_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::collections::HashMap;

use serde_json::Value;

use crate::builtin_function::utils::{param_to_datatype, returns};
use crate::common::data_type::DataType;
use crate::common::errors::{ChapError, Result};
use crate::{common::executable::ExecutableLine, runtime::Runtime};

pub fn from_json(runtime: &mut Runtime, executable: &ExecutableLine) -> Result<()> {
let p1 = param_to_datatype(runtime, executable.params.first(), executable.line_number)?;

match p1 {
DataType::String(s) => {
let parsed: Value = serde_json::from_str(s).map_err(|_| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to parse string to map"),
)
})?;

let result = DataType::try_from(parsed)
.map_err(|e| ChapError::runtime_with_msg(executable.line_number, e))?;

returns(runtime, executable, result)
}
_ => {
return Err(ChapError::runtime_with_msg(
executable.line_number,
format!("can not convert {} to map", p1.type_name()),
));
}
}
}

impl TryFrom<Value> for DataType {
type Error = String;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
match value {
Value::String(s) => Ok(DataType::String(s)),
Value::Number(s) => {
if s.is_f64() {
// safe to use here, as we checked for being a float
Ok(DataType::Float(s.as_f64().unwrap()))
} else {
// safe to use here, as we checked for being an integer
// right now Chap does not support i64, so we cast to i32
Ok(DataType::Int(s.as_i64().unwrap() as i32))
}
}
Value::Bool(b) => Ok(DataType::Bool(b)),
Value::Array(a) => {
let mut list = vec![];

for item in a {
list.push(DataType::try_from(item)?);
}

Ok(DataType::List(list))
}
Value::Object(o) => {
let mut map = HashMap::new();

for (key, value) in o {
map.insert(key, DataType::try_from(value)?);
}

Ok(DataType::Map(map))
}
Value::Null => {
// right now Chap doesnt support Null as a data type, so we just cast it to a string
Ok(DataType::String("null".to_string()))
}
_ => Err("unsupported type in source json string".to_string()),
}
}
}
2 changes: 2 additions & 0 deletions src/builtin_function/type_conversion/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod from_json;
pub mod to_float;
pub mod to_int;
pub mod to_json;
pub mod to_string;
25 changes: 25 additions & 0 deletions src/builtin_function/type_conversion/to_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::{
builtin_function::utils::{param_to_datatype, returns},
common::{
data_type::DataType,
errors::{ChapError, Result},
executable::ExecutableLine,
},
runtime::Runtime,
};

pub fn to_json(runtime: &mut Runtime, executable: &ExecutableLine) -> Result<()> {
let p1 = param_to_datatype(runtime, executable.params.first(), executable.line_number)?;

// need to handle null values properly, right now
// Chap doesnt support Null value, so we case it
// to a string
let parsed = serde_json::to_string(p1).map_err(|_| {
ChapError::runtime_with_msg(
executable.line_number,
format!("failed to parse map to json string"),
)
})?;

returns(runtime, executable, DataType::String(parsed))
}
5 changes: 4 additions & 1 deletion src/common/data_type.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{collections::HashMap, fmt::Display};

#[derive(PartialEq, Debug, Clone)]
use serde::{Deserialize, Serialize};

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DataType {
String(String),
Int(i32),
Expand Down