Add JSON functions; Modify CLI prompt

This commit is contained in:
Jeff 2024-01-26 17:14:57 -05:00
parent f6a1e641c9
commit 3d21196768
8 changed files with 157 additions and 53 deletions

1
Cargo.lock generated
View File

@ -1022,6 +1022,7 @@ dependencies = [
"enum-iterator", "enum-iterator",
"env_logger", "env_logger",
"getrandom", "getrandom",
"humantime",
"libc", "libc",
"log", "log",
"nu-ansi-term", "nu-ansi-term",

View File

@ -42,6 +42,7 @@ env_logger = "0.10"
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] } reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
crossterm = "0.27.0" crossterm = "0.27.0"
nu-ansi-term = "0.49.0" nu-ansi-term = "0.49.0"
humantime = "2.1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10" env_logger = "0.10"

View File

@ -4,8 +4,8 @@ use enum_iterator::{all, Sequence};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
built_in_functions::string_functions, AbstractTree, BuiltInFunction, Format, Function, List, built_in_functions::{json::json_functions, string::string_functions, Callable},
Map, Result, SyntaxNode, Type, Value, AbstractTree, BuiltInFunction, Format, Function, List, Map, Result, SyntaxNode, Type, Value,
}; };
static ARGS: OnceLock<Value> = OnceLock::new(); static ARGS: OnceLock<Value> = OnceLock::new();
@ -80,16 +80,18 @@ impl BuiltInValue {
Value::Map(fs_context) Value::Map(fs_context)
}), }),
BuiltInValue::Json => JSON.get_or_init(|| { BuiltInValue::Json => JSON.get_or_init(|| {
let json_context = Map::new(); let mut json_context = BTreeMap::new();
json_context for json_function in json_functions() {
.set( let key = json_function.name().to_string();
BuiltInFunction::JsonParse.name().to_string(), let value =
Value::Function(Function::BuiltIn(BuiltInFunction::JsonParse)), Value::Function(Function::BuiltIn(BuiltInFunction::Json(json_function)));
) let r#type = value.r#type();
.unwrap();
Value::Map(json_context) json_context.insert(key, (value, r#type));
}
Value::Map(Map::with_variables(json_context))
}), }),
BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)), BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)), BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),

View File

@ -0,0 +1,64 @@
use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};
use crate::{Error, Map, Result, Type, Value};
use super::Callable;
pub fn json_functions() -> impl Iterator<Item = Json> {
enum_iterator::all()
}
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Json {
Create,
CreatePretty,
Parse,
}
impl Callable for Json {
fn name(&self) -> &'static str {
match self {
Json::Create => "create",
Json::CreatePretty => "create_pretty",
Json::Parse => "parse",
}
}
fn r#type(&self) -> Type {
match self {
Json::Create => Type::function(vec![Type::Any], Type::String),
Json::CreatePretty => Type::function(vec![Type::Any], Type::String),
Json::Parse => Type::function(vec![Type::String], Type::Any),
}
}
fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
match self {
Json::Create => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string(value)?;
Ok(Value::String(json_string))
}
Json::CreatePretty => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let value = arguments.first().unwrap();
let json_string = serde_json::to_string_pretty(value)?;
Ok(Value::String(json_string))
}
Json::Parse => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?;
let json_string = arguments.first().unwrap().as_string()?;
let value = serde_json::from_str(json_string)?;
Ok(value)
}
}
}
}

View File

@ -1,4 +1,5 @@
mod string; pub mod json;
pub mod string;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
@ -10,13 +11,19 @@ use serde::{Deserialize, Serialize};
use crate::{Error, Format, Map, Result, Type, Value}; use crate::{Error, Format, Map, Result, Type, Value};
pub use string::{string_functions, StringFunction}; use self::{json::Json, string::StringFunction};
pub trait Callable {
fn name(&self) -> &'static str;
fn r#type(&self) -> Type;
fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result<Value>;
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltInFunction { pub enum BuiltInFunction {
AssertEqual, AssertEqual,
FsRead, FsRead,
JsonParse, Json(Json),
Length, Length,
Output, Output,
RandomBoolean, RandomBoolean,
@ -26,12 +33,12 @@ pub enum BuiltInFunction {
String(StringFunction), String(StringFunction),
} }
impl BuiltInFunction { impl Callable for BuiltInFunction {
pub fn name(&self) -> &'static str { fn name(&self) -> &'static str {
match self { match self {
BuiltInFunction::AssertEqual => "assert_equal", BuiltInFunction::AssertEqual => "assert_equal",
BuiltInFunction::FsRead => "read", BuiltInFunction::FsRead => "read",
BuiltInFunction::JsonParse => "parse", BuiltInFunction::Json(json_function) => json_function.name(),
BuiltInFunction::Length => "length", BuiltInFunction::Length => "length",
BuiltInFunction::Output => "output", BuiltInFunction::Output => "output",
BuiltInFunction::RandomBoolean => "boolean", BuiltInFunction::RandomBoolean => "boolean",
@ -42,11 +49,11 @@ impl BuiltInFunction {
} }
} }
pub fn r#type(&self) -> Type { fn r#type(&self) -> Type {
match self { match self {
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String), BuiltInFunction::FsRead => Type::function(vec![Type::String], Type::String),
BuiltInFunction::JsonParse => Type::function(vec![Type::String], Type::Any), BuiltInFunction::Json(json_function) => json_function.r#type(),
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer), BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None), BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean), BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
@ -57,7 +64,7 @@ impl BuiltInFunction {
} }
} }
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> { fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
match self { match self {
BuiltInFunction::AssertEqual => { BuiltInFunction::AssertEqual => {
Error::expect_argument_amount(self.name(), 2, arguments.len())?; Error::expect_argument_amount(self.name(), 2, arguments.len())?;
@ -75,13 +82,8 @@ impl BuiltInFunction {
Ok(Value::string(file_content)) Ok(Value::string(file_content))
} }
BuiltInFunction::JsonParse => { BuiltInFunction::Json(json_function) => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?; json_function.call(arguments, _source, _outer_context)
let string = arguments.first().unwrap().as_string()?;
let value = serde_json::from_str(string)?;
Ok(value)
} }
BuiltInFunction::Length => { BuiltInFunction::Length => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?; Error::expect_argument_amount(self.name(), 1, arguments.len())?;

View File

@ -1,10 +1,12 @@
use enum_iterator::{all, Sequence}; use enum_iterator::Sequence;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Error, List, Map, Result, Type, Value}; use crate::{Error, List, Map, Result, Type, Value};
use super::Callable;
pub fn string_functions() -> impl Iterator<Item = StringFunction> { pub fn string_functions() -> impl Iterator<Item = StringFunction> {
all() enum_iterator::all()
} }
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@ -41,8 +43,8 @@ pub enum StringFunction {
Truncate, Truncate,
} }
impl StringFunction { impl Callable for StringFunction {
pub fn name(&self) -> &'static str { fn name(&self) -> &'static str {
match self { match self {
StringFunction::AsBytes => "as_bytes", StringFunction::AsBytes => "as_bytes",
StringFunction::EndsWith => "ends_with", StringFunction::EndsWith => "ends_with",
@ -77,7 +79,7 @@ impl StringFunction {
} }
} }
pub fn r#type(&self) -> Type { fn r#type(&self) -> Type {
match self { match self {
StringFunction::AsBytes => { StringFunction::AsBytes => {
Type::function(vec![Type::String], Type::list(Type::Integer)) Type::function(vec![Type::String], Type::list(Type::Integer))
@ -163,7 +165,7 @@ impl StringFunction {
} }
} }
pub fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> { fn call(&self, arguments: &[Value], _source: &str, _outer_context: &Map) -> Result<Value> {
let value = match self { let value = match self {
StringFunction::AsBytes => { StringFunction::AsBytes => {
Error::expect_argument_amount(self.name(), 1, arguments.len())?; Error::expect_argument_amount(self.name(), 1, arguments.len())?;

View File

@ -4,11 +4,12 @@ use clap::{Parser, Subcommand};
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::Style; use nu_ansi_term::Style;
use reedline::{ use reedline::{
default_emacs_keybindings, DefaultPrompt, DefaultPromptSegment, EditCommand, Emacs, default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultHinter, EditCommand, Emacs,
Highlighter, Reedline, ReedlineEvent, Signal, SqliteBackedHistory, StyledText, Highlighter, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, SqliteBackedHistory,
StyledText,
}; };
use std::{fs::read_to_string, path::PathBuf}; use std::{borrow::Cow, fs::read_to_string, path::PathBuf, time::SystemTime};
use dust_lang::{built_in_values, Interpreter, Map, Result, Value}; use dust_lang::{built_in_values, Interpreter, Map, Result, Value};
@ -170,12 +171,41 @@ impl Highlighter for DustHighlighter {
} }
} }
struct DustPrompt;
impl Prompt for DustPrompt {
fn render_prompt_left(&self) -> Cow<str> {
let path = std::env::current_dir()
.map(|path| path.file_name().unwrap().to_string_lossy().to_string())
.unwrap_or_else(|_| "No workdir".to_string());
Cow::Owned(path)
}
fn render_prompt_right(&self) -> Cow<str> {
let time = humantime::format_rfc3339_seconds(SystemTime::now()).to_string();
Cow::Owned(time)
}
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
Cow::Borrowed(" > ")
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
Cow::Borrowed(" > ")
}
fn render_prompt_history_search_indicator(
&self,
_history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
Cow::Borrowed(" ? ")
}
}
fn run_shell(context: Map) -> Result<()> { fn run_shell(context: Map) -> Result<()> {
let mut interpreter = Interpreter::new(context.clone()); let mut interpreter = Interpreter::new(context.clone());
let mut prompt = DefaultPrompt::default();
prompt.left_prompt = DefaultPromptSegment::Basic(">".to_string());
let mut keybindings = default_emacs_keybindings(); let mut keybindings = default_emacs_keybindings();
keybindings.add_binding( keybindings.add_binding(
@ -184,7 +214,7 @@ fn run_shell(context: Map) -> Result<()> {
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]), ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
); );
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::CONTROL, KeyModifiers::NONE,
KeyCode::Tab, KeyCode::Tab,
ReedlineEvent::UntilFound(vec![ ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()), ReedlineEvent::Menu("completion_menu".to_string()),
@ -192,7 +222,7 @@ fn run_shell(context: Map) -> Result<()> {
]), ]),
); );
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::NONE, KeyModifiers::CONTROL,
KeyCode::Tab, KeyCode::Tab,
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]), ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
); );
@ -202,10 +232,20 @@ fn run_shell(context: Map) -> Result<()> {
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None) SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
.expect("Error loading history."), .expect("Error loading history."),
); );
let hinter = Box::new(DefaultHinter::default());
let mut line_editor = Reedline::create() let mut line_editor = Reedline::create()
.with_edit_mode(edit_mode) .with_edit_mode(edit_mode)
.with_history(history) .with_history(history)
.with_highlighter(Box::new(DustHighlighter::new(context))); .with_highlighter(Box::new(DustHighlighter::new(context)))
.with_hinter(hinter)
.with_menu(ReedlineMenu::WithCompleter {
menu: Box::new(ColumnarMenu::default().with_name("completion_menu")),
completer: Box::new(DefaultCompleter::new_with_wordlen(
vec!["test".to_string()],
2,
)),
});
let prompt = DustPrompt;
loop { loop {
let sig = line_editor.read_line(&prompt); let sig = line_editor.read_line(&prompt);

View File

@ -5,7 +5,9 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{BuiltInFunction, Format, FunctionNode, Map, Result, Type, Value}; use crate::{
built_in_functions::Callable, BuiltInFunction, Format, FunctionNode, Map, Result, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum Function { pub enum Function {
@ -20,16 +22,6 @@ impl Function {
built_in_function.call(arguments, source, outer_context) built_in_function.call(arguments, source, outer_context)
} }
Function::ContextDefined(function_node) => { Function::ContextDefined(function_node) => {
function_node.set(
"self".to_string(),
Value::Function(Function::ContextDefined(Arc::new(FunctionNode::new(
function_node.parameters().clone(),
function_node.body().clone(),
function_node.r#type().clone(),
*function_node.syntax_position(),
)))),
)?;
function_node.call(arguments, source, outer_context) function_node.call(arguments, source, outer_context)
} }
} }