Skip to content
Open
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
18 changes: 9 additions & 9 deletions contributing/grammar.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,18 @@ external_call → "^" external_name external_argument* ;
external_name → command_name | variable | string ;
external_argument
→ spread | expression | bare_word | string ;
internal_call → command_name argument* ;
internal_call → call_name argument* ;
argument → flag | spread | expression | bare_word ;
flag → long_flag (("=" expression) | expression)?
flag → flag_long (("=" expression) | expression)?
| short_flag (("=" expression) | expression)? ;
long_flag → "--" FLAG_NAME ;
flag_long → "--" flag_name;
short_flag → "-" SHORT_FLAGS ;
spread → "..." expression ;

command_name → string | known_command_name | IDENTIFIER ;
known_command_name
→ IDENTIFIER IDENTIFIER+ ;
command_name → string | call_name | IDENTIFIER ;
flag_name → IDENTIFIER_ALLOW_DASH ;
call_name
→ IDENTIFIER_ALLOW_DASH IDENTIFIER_ALLOW_DASH+ ;
```

**Command-name semantic requirement:** command heads are scope-sensitive. The
Expand Down Expand Up @@ -231,12 +232,11 @@ record_pattern_item
signature → "[" signature_parameter (separator? signature_parameter)*
separator? "]" ;
signature_parameter
→ rest_parameter | flag_parameter | short_only_flag | positional_parameter ;
→ rest_parameter | flag_parameter | positional_parameter ;
positional_parameter
→ IDENTIFIER "?"? type_annotation? default_value? ;
rest_parameter → "..." IDENTIFIER type_annotation? ;
flag_parameter → long_flag ("(" short_flag ")")? type_annotation? default_value? ;
short_only_flag → short_flag type_annotation? default_value? ;
flag_parameter → flag_long ("(" short_flag ")")? type_annotation? default_value? ;
default_value → "=" expression ;
io_signature → ":" "[" in_out_type (separator? in_out_type)* separator? "]"
| ":" in_out_type ;
Expand Down
198 changes: 152 additions & 46 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,16 @@ pub enum AstNode {
params: NodeId,
},
Params(ParamsId),
Param {
FlagParam {
long: NodeId,
short: Option<NodeId>,
ty: Option<NodeId>,
default: Option<NodeId>,
},
PosParam {
name: NodeId,
ty: Option<NodeId>,
default: Option<NodeId>,
},
InOutTypes(InOutTypesId),
/// Input/output type pair for a command
Expand All @@ -321,11 +328,13 @@ pub enum AstNode {
},

/// Long flag ('--' + one or more letters)
FlagLong,
FlagLong(NodeId),
/// Short flag ('-' + single letter)
FlagShort,
FlagShort(NodeId),
/// Group of short flags ('-' + more than 1 letters)
FlagShortGroup,
FlagShortGroup(NodeId),
/// Spread
Spread(NodeId),

// Expressions
Call(CallId),
Expand Down Expand Up @@ -622,7 +631,7 @@ impl Parser {
self.compiler.ast_nodes[node_id.0] = AstNode::String;
node_id
}
BarewordContext::Call => self.call(),
BarewordContext::Call => self.internal_call(),
},
},
_ => self.error("incomplete expression"),
Expand Down Expand Up @@ -725,27 +734,23 @@ impl Parser {
}
}

pub fn call(&mut self) -> NodeId {
let _span = span!();
let mut parts = vec![self.call_name()];
let mut is_head = true;
let span_start = self.position();

while self.has_tokens() {
if self.is_newline() {
break;
}
fn call_name(&mut self) -> Vec<NodeId> {
let mut parts = vec![self.identifier_allow_dash()];

if self.is_name() && is_head {
parts.push(self.name());
continue;
}
while self.has_tokens() && self.is_name() && !self.is_newline() {
parts.push(self.identifier_allow_dash());
}
parts
}

// TODO: Add flags
pub fn internal_call(&mut self) -> NodeId {
let _span = span!();
let span_start = self.position();
let mut parts = self.call_name();

is_head = false;
let arg_id = self.simple_expression(BarewordContext::String);
parts.push(arg_id);
// Arguments.
while self.has_tokens() && !self.is_newline() {
parts.push(self.argument());
}

let span_end = self.position();
Expand All @@ -758,6 +763,61 @@ impl Parser {
)
}

fn argument(&mut self) -> NodeId {
match self.tokens.peek_token() {
Token::DotDotDot => self.spread_expression(),
Token::DashDash => self.flag_long(),
Token::Dash => self.flag_short(),
_ => self.simple_expression(BarewordContext::String),
}
}

fn spread_expression(&mut self) -> NodeId {
let span_start = self.position();
self.tokens.advance();
let expression = self.simple_expression(BarewordContext::String);
let span_end = self.compiler.get_span(expression).end;
self.create_node(AstNode::Spread(expression), span_start, span_end)
}

fn flag_long(&mut self) -> NodeId {
let span_start = self.position();
if !self.is_dashdash() {
return self.error("Expect dashdash(--)");
}
self.tokens.advance();
let flag_name = self.flag_name();
let span_end = self.compiler.get_span(flag_name).end;
let result = self.create_node(AstNode::FlagLong(flag_name), span_start, span_end);

// may skip additional `=`
if self.is_equals() {
self.tokens.advance();
}
result
}

fn flag_short(&mut self) -> NodeId {
let span_start = self.position();
if !self.is_dash() {
return self.error("Expect dash(-)");
}
self.tokens.advance();
let flag_name = self.name();
let span_end = self.compiler.get_span(flag_name).end;
let result = if self.compiler.get_span_contents(flag_name).len() > 1 {
self.create_node(AstNode::FlagShortGroup(flag_name), span_start, span_end)
} else {
self.create_node(AstNode::FlagShort(flag_name), span_start, span_end)
};

// may skip additional `=`
if self.is_equals() {
self.tokens.advance();
}
result
}

pub fn list_or_table(&mut self) -> NodeId {
let _span = span!();
let span_start = self.position();
Expand Down Expand Up @@ -955,27 +1015,24 @@ impl Parser {
}
}

pub fn call_name(&mut self) -> NodeId {
let (mut token, mut span) = self.tokens.peek();

loop {
if [Token::Eof, Token::Newline].contains(&token) {
break;
}
fn flag_name(&mut self) -> NodeId {
self.identifier_allow_dash()
}

fn identifier_allow_dash(&mut self) -> NodeId {
let span = self.tokens.peek_span();
let (span_start, mut span_end) = (span.start, span.end);
while self.has_tokens() && (self.is_name() || self.is_dash()) {
span_end = self.tokens.peek_span().end;
self.tokens.advance();
let (next_token, next_span) = self.tokens.peek();

if next_span.start > span.end {
// horizontal whitespace
let next_span = self.tokens.peek_span();
if next_span.start > span_end {
// horizontal whitespace.
break;
}

token = next_token;
span.end = next_span.end;
}

self.create_node(AstNode::Name, span.start, span.end)
self.create_node(AstNode::Name, span_start, span_end)
}

pub fn has_tokens(&mut self) -> bool {
Expand Down Expand Up @@ -1160,7 +1217,21 @@ impl Parser {
continue;
}

let name = self.name();
let is_flag_param = self.is_dashdash();
let (name, short_name) =
if is_flag_param && matches!(params_context, ParamsContext::Squares) {
let result = self.flag_long();
if self.is_lparen() {
self.tokens.advance();
let short = self.flag_short();
self.rparen();
(result, Some(short))
} else {
(result, None)
}
} else {
(self.name(), None)
};

let ty = if self.is_colon() {
// We have a type
Expand All @@ -1171,15 +1242,42 @@ impl Parser {
None
};

let name_span = self.compiler.spans[name.0];
let param_span_end = if let Some(ty_id) = ty {
self.compiler.spans[ty_id.0].end
let default_val = if self.is_equals() {
// We have a default value.
self.equals();
Some(self.simple_expression(BarewordContext::String))
} else {
name_span.end
None
};

let param =
self.create_node(AstNode::Param { name, ty }, name_span.start, param_span_end);
let name_span = self.compiler.spans[name.0];
let param_span_end = default_val.map_or_else(
|| ty.map_or(name_span.end, |ty_node| self.get_span_end(ty_node)),
|default_val| self.get_span_end(default_val),
);

let param = if is_flag_param {
self.create_node(
AstNode::FlagParam {
long: name,
short: short_name,
ty,
default: default_val,
},
name_span.start,
param_span_end,
)
} else {
self.create_node(
AstNode::PosParam {
name,
ty,
default: default_val,
},
name_span.start,
param_span_end,
)
};

// output.push(self.name());
output.push(param);
Expand Down Expand Up @@ -1412,7 +1510,7 @@ impl Parser {
}

let name = match self.tokens.peek() {
(Token::Bareword, span) => self.advance_node(AstNode::Name, span),
(Token::Bareword, _) => self.identifier_allow_dash(),
(Token::DoubleQuotedString | Token::SingleQuotedString, span) => {
self.advance_node(AstNode::String, span)
}
Expand Down Expand Up @@ -1778,6 +1876,14 @@ impl Parser {
self.tokens.peek_token() == Token::LCurly
}

pub fn is_dash(&self) -> bool {
self.tokens.peek_token() == Token::Dash
}

pub fn is_dashdash(&self) -> bool {
self.tokens.peek_token() == Token::DashDash
}

pub fn is_rcurly(&mut self) -> bool {
self.tokens.peek_token() == Token::RCurly
}
Expand Down
30 changes: 22 additions & 8 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,25 @@ impl<'a> Resolver<'a> {
AstNode::Params(_) => {
let params = self.compiler.get_params(node_id);
for param in &params.nodes {
let AstNode::Param { name, ty } = self.compiler.ast_nodes[param.0] else {
panic!("param is not a param");
};
self.define_variable(name, false);
if let Some(ty) = ty {
self.resolve_node(ty);
// TODO: handle default, and maybe FlagParam.
match self.compiler.ast_nodes[param.0] {
AstNode::PosParam {
name,
ty,
default: _,
}
| AstNode::FlagParam {
long: name,
short: _,
ty,
default: _,
} => {
self.define_variable(name, false);
if let Some(ty) = ty {
self.resolve_node(ty);
}
}
_ => panic!("param is not a param"),
}
}
}
Expand Down Expand Up @@ -397,7 +410,8 @@ impl<'a> Resolver<'a> {
AstNode::RecordType { fields, .. } => {
let fields = self.compiler.get_params(fields);
for field in &fields.nodes {
if let AstNode::Param { ty: Some(ty), .. } = self.compiler.get_node(*field) {
// TODO: handle default.
if let AstNode::PosParam { ty: Some(ty), .. } = self.compiler.get_node(*field) {
self.resolve_node(*ty);
}
}
Expand All @@ -417,7 +431,7 @@ impl<'a> Resolver<'a> {
self.resolve_node(out_ty);
}
AstNode::Pipeline(pipeline_id) => self.resolve_pipeline(pipeline_id),
AstNode::Param { .. } => (/* seems unused for now */),
AstNode::PosParam { .. } => (/* seems unused for now */),
AstNode::NamedValue { .. } => (/* seems unused for now */),
// All remaining matches do not contain NodeId => there is nothing to resolve
_ => (),
Expand Down
4 changes: 1 addition & 3 deletions src/snapshots/new_nu_parser__test__node_output@alias.nu.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: src/test.rs
assertion_line: 77
expression: evaluate_example(path)
input_file: tests/alias.nu
---
Expand All @@ -10,7 +9,7 @@ input_file: tests/alias.nu
2: Alias { new_name: NodeId(0), old_name: NodeId(1) } (0 to 25)
3: Name (27 to 32) "fancy"
4: Name (33 to 38) "alias"
5: Call(CallId(0)) (33 to 38)
5: Call(CallId(0)) (27 to 38)
6: Block(BlockId(0)) (0 to 39)
==== SCOPE ====
0: Frame Scope, node_id: NodeId(6)
Expand All @@ -28,4 +27,3 @@ register_count: 0
file_count: 0
==== IR ERRORS ====
Error (NodeId 2): node Alias { new_name: NodeId(0), old_name: NodeId(1) } not suported yet

Loading
Loading