1use crate::{
6 BlockId, Config, HistoryPath, PipelineData, Record, ShellError, Span, Value, VarId,
7 ast::{self, Assignment, Block, Call, Expr, Expression, ExternalArgument},
8 debugger::{DebugContext, WithoutDebug},
9 engine::{
10 Argument, Closure, CommandType, EngineState, Stack, StateWorkingSet,
11 named_flags::normalize_engine_arguments,
12 },
13 eval_base::Eval,
14 ir, record,
15 shell_error::generic::GenericError,
16};
17use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
18use std::{
19 path::{Path, PathBuf},
20 sync::Arc,
21};
22
23pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Value {
27 fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
28 #[allow(deprecated)]
29 let cwd = engine_state.current_work_dir();
30
31 if path.exists() {
32 match nu_path::canonicalize_with(path, cwd) {
33 Ok(canon_path) => canon_path,
34 Err(_) => path.to_owned(),
35 }
36 } else {
37 path.to_owned()
38 }
39 }
40
41 let mut record = Record::new();
42
43 let config_home = &engine_state.config_dirs.config_home;
44 let canon_config_home = canonicalize_path(engine_state, config_home);
45
46 record.push(
47 "default-config-dir",
48 Value::string(canon_config_home.to_string_lossy(), span),
49 );
50
51 record.push(
52 "config-path",
53 Value::string(
54 canonicalize_path(engine_state, engine_state.config_dirs.config_file.as_path())
55 .to_string_lossy(),
56 span,
57 ),
58 );
59
60 record.push(
61 "env-path",
62 Value::string(
63 canonicalize_path(engine_state, engine_state.config_dirs.env_file.as_path())
64 .to_string_lossy(),
65 span,
66 ),
67 );
68
69 record.push(
70 "history-path",
71 match &engine_state.config.history.path {
72 HistoryPath::Disabled => Value::string("", span),
73 HistoryPath::Custom(custom_path) => {
74 let effective_path = if custom_path.is_dir() {
75 custom_path.join(engine_state.config.history.file_format.default_file_name())
76 } else {
77 custom_path.clone()
78 };
79 let canon_hist_path = canonicalize_path(engine_state, &effective_path);
80 Value::string(canon_hist_path.to_string_lossy(), span)
81 }
82 HistoryPath::Default => {
83 let hist_path = engine_state
86 .config
87 .history
88 .file_path(config_home)
89 .unwrap_or_else(|| {
90 config_home
91 .join(engine_state.config.history.file_format.default_file_name())
92 });
93 let canon_hist_path = canonicalize_path(engine_state, &hist_path);
94 Value::string(canon_hist_path.to_string_lossy(), span)
95 }
96 },
97 );
98
99 record.push(
100 "loginshell-path",
101 Value::string(
102 canonicalize_path(engine_state, &config_home.join("login.nu")).to_string_lossy(),
103 span,
104 ),
105 );
106
107 #[cfg(feature = "plugin")]
108 {
109 let plugin_path = engine_state
111 .plugin_path
112 .as_deref()
113 .unwrap_or_else(|| engine_state.config_dirs.plugin_file.as_path());
114 record.push(
115 "plugin-path",
116 Value::string(
117 canonicalize_path(engine_state, plugin_path).to_string_lossy(),
118 span,
119 ),
120 );
121 }
122
123 let home_dir = &engine_state.config_dirs.home_dir;
124 record.push(
125 "home-dir",
126 if home_dir.as_os_str().is_empty() {
127 Value::error(
128 ShellError::Generic(GenericError::new(
129 "setting $nu.home-dir failed",
130 "Could not get home directory",
131 span,
132 )),
133 span,
134 )
135 } else {
136 Value::string(
137 canonicalize_path(engine_state, home_dir).to_string_lossy(),
138 span,
139 )
140 },
141 );
142
143 record.push(
144 "data-dir",
145 Value::string(
146 canonicalize_path(engine_state, &engine_state.config_dirs.data_home).to_string_lossy(),
147 span,
148 ),
149 );
150
151 record.push(
152 "cache-dir",
153 Value::string(
154 canonicalize_path(engine_state, &engine_state.config_dirs.cache_home).to_string_lossy(),
155 span,
156 ),
157 );
158
159 record.push(
160 "vendor-autoload-dirs",
161 Value::list(
162 engine_state
163 .config_dirs
164 .vendor_autoload_dirs
165 .iter()
166 .map(|path| Value::string(path.to_string_lossy(), span))
167 .collect(),
168 span,
169 ),
170 );
171
172 record.push(
173 "user-autoload-dirs",
174 Value::list(
175 engine_state
176 .config_dirs
177 .user_autoload_dirs
178 .iter()
179 .map(|path| Value::string(path.to_string_lossy(), span))
180 .collect(),
181 span,
182 ),
183 );
184
185 record.push("temp-dir", {
186 let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
187 Value::string(canon_temp_path.to_string_lossy(), span)
188 });
189
190 record.push("pid", Value::int(std::process::id().into(), span));
191
192 record.push("os-info", {
193 let ver = get_kernel_version();
194 Value::record(
195 record! {
196 "name" => Value::string(get_os_name(), span),
197 "arch" => Value::string(get_os_arch(), span),
198 "family" => Value::string(get_os_family(), span),
199 "kernel_version" => Value::string(ver, span),
200 },
201 span,
202 )
203 });
204
205 record.push(
206 "startup-time",
207 Value::duration(engine_state.get_startup_time(), span),
208 );
209
210 record.push(
211 "is-interactive",
212 Value::bool(engine_state.is_interactive, span),
213 );
214
215 record.push("is-login", Value::bool(engine_state.is_login, span));
216
217 record.push(
218 "history-enabled",
219 Value::bool(engine_state.history_enabled, span),
220 );
221
222 record.push(
223 "current-exe",
224 if let Ok(current_exe) = std::env::current_exe() {
225 Value::string(current_exe.to_string_lossy(), span)
226 } else {
227 Value::error(
228 ShellError::Generic(GenericError::new(
229 "setting $nu.current-exe failed",
230 "Could not get current executable path",
231 span,
232 )),
233 span,
234 )
235 },
236 );
237
238 record.push("is-lsp", Value::bool(engine_state.is_lsp, span));
239 record.push("is-mcp", Value::bool(engine_state.is_mcp, span));
240 record.push("is-dap", Value::bool(engine_state.is_dap, span));
241
242 Value::record(record, span)
243}
244
245fn eval_const_call(
246 working_set: &StateWorkingSet,
247 call: &Call,
248 input: PipelineData,
249) -> Result<PipelineData, ShellError> {
250 let decl = working_set.get_decl(call.decl_id);
251
252 if !decl.is_const() {
253 return Err(ShellError::NotAConstCommand { span: call.head });
254 }
255
256 if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
257 return Err(ShellError::NotAConstHelp { span: call.head });
260 }
261
262 if decl.command_type() == CommandType::Keyword && decl.name() == "if" {
264 return eval_const_if(working_set, call, input);
265 }
266
267 if decl.command_type() == CommandType::Keyword && decl.name() == "const" {
269 return Ok(PipelineData::empty());
270 }
271
272 let mut stack = Stack::new();
273 let ir_call = build_const_ir_call(working_set, call, &decl.signature(), &mut stack)?;
274 let result = decl.run_const(working_set, &mut stack, &(&ir_call).into(), input);
275 ir_call.leave(&mut stack);
276 result
277}
278
279fn eval_const_call_arg(
289 working_set: &StateWorkingSet,
290 expr: &Expression,
291) -> Result<Value, ShellError> {
292 match &expr.expr {
293 Expr::Block(block_id) => Ok(Value::closure(
294 Closure {
295 block_id: *block_id,
296 captures: vec![],
297 },
298 expr.span,
299 )),
300 _ => eval_constant(working_set, expr),
301 }
302}
303
304fn build_const_ir_call(
305 working_set: &StateWorkingSet,
306 call: &Call,
307 signature: &crate::Signature,
308 stack: &mut Stack,
309) -> Result<ir::Call, ShellError> {
310 let mut builder = ir::Call::build(call.decl_id, call.head);
311
312 for arg in &call.arguments {
313 match arg {
314 ast::Argument::Positional(expr) | ast::Argument::Unknown(expr) => {
315 let val = eval_const_call_arg(working_set, expr)?;
316 builder.add_positional(stack, expr.span, val);
317 }
318 ast::Argument::Spread(expr) => {
319 let val = eval_const_call_arg(working_set, expr)?;
320 builder.add_spread(stack, expr.span, val);
321 }
322 ast::Argument::Named((long, short, maybe_expr)) => {
323 let short_name = short.as_ref().map(|s| s.item.as_str()).unwrap_or("");
324 if let Some(expr) = maybe_expr {
325 let val = eval_const_call_arg(working_set, expr)?;
326 builder.add_named(stack, &long.item, short_name, arg.span(), val);
327 } else {
328 builder.add_flag(stack, &long.item, short_name, arg.span());
329 }
330 }
331 }
332 }
333
334 for (name, expr) in &call.parser_info {
335 let data: std::sync::Arc<[u8]> = name.as_bytes().into();
336 let name_slice = ir::DataSlice {
337 start: 0,
338 len: name.len().try_into().expect("parser info name too big"),
339 };
340 builder.add_argument(
341 stack,
342 Argument::ParserInfo {
343 data,
344 name: name_slice,
345 info: Box::new(expr.clone()),
346 },
347 );
348 }
349
350 let mut ir_call = builder.finish();
351 let raw: Vec<Argument> = stack
354 .arguments
355 .drain_args(ir_call.args_base, ir_call.args_len)
356 .collect();
357 let expanded = normalize_engine_arguments(signature, raw)?;
358 ir_call.args_len = expanded.len();
359 for arg in expanded {
360 stack.arguments.push(arg);
361 }
362
363 Ok(ir_call)
364}
365
366fn eval_const_if(
368 working_set: &StateWorkingSet,
369 call: &Call,
370 input: PipelineData,
371) -> Result<PipelineData, ShellError> {
372 let mut iter = call.positional_iter();
373 let cond = iter.next().expect("checked through parser");
374 let then_expr = iter.next().expect("checked through parser");
375 let else_case = iter.next();
376
377 let then_block = then_expr
378 .as_block()
379 .ok_or_else(|| ShellError::TypeMismatch {
380 err_message: "expected block".into(),
381 span: then_expr.span,
382 })?;
383
384 if eval_constant(working_set, cond)?.as_bool()? {
385 let block = working_set.get_block(then_block);
386 eval_const_subexpression(working_set, block, input, block.span.unwrap_or(call.head))
387 } else if let Some(else_case) = else_case {
388 if let Some(else_expr) = else_case.as_keyword() {
389 if let Some(block_id) = else_expr.as_block() {
390 let block = working_set.get_block(block_id);
391 eval_const_subexpression(working_set, block, input, block.span.unwrap_or(call.head))
392 } else {
393 eval_constant_with_input(working_set, else_expr, input)
394 }
395 } else {
396 eval_constant_with_input(working_set, else_case, input)
397 }
398 } else {
399 Ok(PipelineData::empty())
400 }
401}
402
403pub fn eval_const_subexpression(
404 working_set: &StateWorkingSet,
405 block: &Block,
406 mut input: PipelineData,
407 span: Span,
408) -> Result<PipelineData, ShellError> {
409 for pipeline in block.pipelines.iter() {
410 for element in pipeline.elements.iter() {
411 if element.redirection.is_some() {
412 return Err(ShellError::NotAConstant { span });
413 }
414
415 input = eval_constant_with_input(working_set, &element.expr, input)?
416 }
417 }
418
419 Ok(input)
420}
421
422pub fn eval_constant_with_input(
423 working_set: &StateWorkingSet,
424 expr: &Expression,
425 input: PipelineData,
426) -> Result<PipelineData, ShellError> {
427 match &expr.expr {
428 Expr::Call(call) => eval_const_call(working_set, call, input),
429 Expr::Subexpression(block_id) => {
430 let block = working_set.get_block(*block_id);
431 eval_const_subexpression(working_set, block, input, expr.span(&working_set))
432 }
433 _ => eval_constant(working_set, expr).map(|v| PipelineData::value(v, None)),
434 }
435}
436
437pub fn eval_constant(
439 working_set: &StateWorkingSet,
440 expr: &Expression,
441) -> Result<Value, ShellError> {
442 <EvalConst as Eval>::eval::<WithoutDebug>(working_set, &mut (), expr)
444}
445
446struct EvalConst;
447
448impl Eval for EvalConst {
449 type State<'a> = &'a StateWorkingSet<'a>;
450
451 type MutState = ();
452
453 fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc<Config> {
454 state.get_config().clone()
455 }
456
457 fn eval_var(
458 working_set: &StateWorkingSet,
459 _: &mut (),
460 var_id: VarId,
461 span: Span,
462 ) -> Result<Value, ShellError> {
463 match working_set.get_variable(var_id).const_val.as_ref() {
464 Some(val) => Ok(val.clone()),
465 None => Err(ShellError::NotAConstant { span }),
466 }
467 }
468
469 fn eval_call<D: DebugContext>(
470 working_set: &StateWorkingSet,
471 _: &mut (),
472 call: &Call,
473 span: Span,
474 ) -> Result<Value, ShellError> {
475 eval_const_call(working_set, call, PipelineData::empty())?.into_value(span)
478 }
479
480 fn eval_external_call(
481 _: &StateWorkingSet,
482 _: &mut (),
483 _: &Expression,
484 _: &[ExternalArgument],
485 span: Span,
486 ) -> Result<Value, ShellError> {
487 Err(ShellError::NotAConstant { span })
489 }
490
491 fn eval_collect<D: DebugContext>(
492 _: &StateWorkingSet,
493 _: &mut (),
494 _var_id: VarId,
495 expr: &Expression,
496 ) -> Result<Value, ShellError> {
497 Err(ShellError::NotAConstant { span: expr.span })
498 }
499
500 fn eval_subexpression<D: DebugContext>(
501 working_set: &StateWorkingSet,
502 _: &mut (),
503 block_id: BlockId,
504 span: Span,
505 ) -> Result<Value, ShellError> {
506 if working_set
508 .parse_errors
509 .iter()
510 .any(|error| span.contains_span(error.span()))
511 {
512 return Err(ShellError::ParseErrorInConstant { span });
513 }
514 let block = working_set.get_block(block_id);
516 eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
517 }
518
519 fn regex_match(
520 _: &StateWorkingSet,
521 _op_span: Span,
522 _: &Value,
523 _: &Value,
524 _: bool,
525 expr_span: Span,
526 ) -> Result<Value, ShellError> {
527 Err(ShellError::NotAConstant { span: expr_span })
528 }
529
530 fn eval_assignment<D: DebugContext>(
531 _: &StateWorkingSet,
532 _: &mut (),
533 _: &Expression,
534 _: &Expression,
535 _: Assignment,
536 _op_span: Span,
537 expr_span: Span,
538 ) -> Result<Value, ShellError> {
539 Err(ShellError::NotAConstant { span: expr_span })
541 }
542
543 fn eval_row_condition_or_closure(
544 _: &StateWorkingSet,
545 _: &mut (),
546 _: BlockId,
547 span: Span,
548 ) -> Result<Value, ShellError> {
549 Err(ShellError::NotAConstant { span })
550 }
551
552 fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
553 Err(ShellError::NotAConstant { span })
554 }
555
556 fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
557 Err(ShellError::NotAConstant {
558 span: expr.span(&working_set),
559 })
560 }
561}