diff --git a/src/command.rs b/src/command.rs index 5372a75..9439393 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,46 +1,43 @@ pub mod parser; +mod tokenizer; use std::collections::HashMap; -use anyhow::{anyhow, bail, Context as _}; +use anyhow::{bail, Context as _}; use colored::Colorize; use wasmtime::component::Val; +use self::parser::Ident; +use self::tokenizer::TokenKind; + use super::runtime::Runtime; use super::wit::WorldResolver; -use crate::command::parser::ItemIdent; use crate::evaluator::Evaluator; use crate::wit::Expansion; -use parser::SpannedStr; pub enum Cmd<'a> { BuiltIn { - name: SpannedStr<'a>, - args: Vec>, + name: &'a str, + args: Vec>, }, Eval(parser::Expr<'a>), Assign { - ident: SpannedStr<'a>, + ident: &'a str, value: parser::Expr<'a>, }, } impl<'a> Cmd<'a> { - pub fn parse(s: &'a str) -> anyhow::Result>> { - let s = s.trim(); - if s.is_empty() { - return Ok(None); - } - - // try to parse a function - let (rest, line) = parser::Line::parse(s).map_err(|e| anyhow!("{e}"))?; - if !rest.is_empty() { - anyhow::bail!("unexpected end of input: '{rest}'"); - } + pub fn parse(input: &'a str) -> anyhow::Result>> { + let tokens = tokenizer::Token::tokenize(input)?; + let line = parser::Line::parse(tokens).map_err(|e| anyhow::anyhow!("{e}"))?; log::debug!("Parsed line: {line:?}"); match line { parser::Line::Expr(expr) => Ok(Some(Cmd::Eval(expr))), parser::Line::Assignment(ident, value) => Ok(Some(Cmd::Assign { ident, value })), - parser::Line::Builtin(name, args) => Ok(Some(Cmd::BuiltIn { name, args })), + parser::Line::BuiltIn(builtin) => Ok(Some(Cmd::BuiltIn { + name: builtin.name, + args: builtin.rest, + })), } } @@ -50,17 +47,17 @@ impl<'a> Cmd<'a> { pub fn run( self, runtime: &mut Runtime, - querier: &mut WorldResolver, + resolver: &mut WorldResolver, scope: &mut HashMap, ) -> anyhow::Result { - let mut eval = Evaluator::new(runtime, querier, scope); + let mut eval = Evaluator::new(runtime, resolver, scope); match self { Cmd::Eval(expr) => match expr { parser::Expr::Literal(l) => { let val = eval.eval_literal(l, None)?; println!("{}: {}", format_val(&val), val_as_type(&val)); } - parser::Expr::Ident(ident) => match scope.get(&*ident) { + parser::Expr::Ident(ident) => match scope.get(ident) { Some(val) => { println!("{}: {}", format_val(val), val_as_type(val)) } @@ -68,8 +65,8 @@ impl<'a> Cmd<'a> { anyhow::bail!("no identifier '{ident}' in scope") } }, - parser::Expr::FunctionCall(ident, args) => { - let results = eval.call_func(ident, args)?; + parser::Expr::FunctionCall(func) => { + let results = eval.call_func(func.ident, func.args)?; println!( "{}", results @@ -85,49 +82,61 @@ impl<'a> Cmd<'a> { println!("{}: {}", ident, val_as_type(&val)); scope.insert(ident.into(), val); } - Cmd::BuiltIn { name, args } if name == "exports" => { + Cmd::BuiltIn { + name: "exports", + args, + } => { let &[] = args.as_slice() else { bail!( "wrong number of arguments to exports function. Expected 0 got {}", args.len() ) }; - for (export_name, export) in querier.world().exports.iter() { - let export_name = querier.world_item_name(export_name); - if let Some(ty) = format_world_item(export, querier) { + for (export_name, export) in resolver.world().exports.iter() { + let export_name = resolver.world_item_name(export_name); + if let Some(ty) = format_world_item(export, resolver) { println!("{}: {ty}", export_name.bold()); } } } - Cmd::BuiltIn { name, args } if name == "imports" => { + Cmd::BuiltIn { + name: "imports", + args, + } => { let include_wasi = match args.as_slice() { [] => true, - [flag] if *flag == "--no-wasi" => false, - [flag] => { - bail!("unrecorgnized flag for imports builtin '{}'", flag) - } + [t] => match t.token() { + TokenKind::Flag("no-wasi") => false, + TokenKind::Flag(flag) => { + bail!("unrecognized flag for imports builtin '{flag}'") + } + _ => bail!("unrecognized token {}", t.input.str), + }, _ => { bail!( - "wrong number of arguments to imports function. Expected 0 got {}", + "wrong number of arguments to imports function. Expected 1 got {}", args.len() ) } }; - for (import_name, import) in querier.imports(include_wasi) { - let import_name = querier.world_item_name(import_name); - if let Some(ty) = format_world_item(import, querier) { + for (import_name, import) in resolver.imports(include_wasi) { + let import_name = resolver.world_item_name(import_name); + if let Some(ty) = format_world_item(import, resolver) { println!("{}: {ty}", import_name.bold()); } } } - Cmd::BuiltIn { name, args } if name == "type" => { + Cmd::BuiltIn { name: "type", args } => { match args.as_slice() { - &[name] => { - let types = querier.types_by_name(&*name); + &[token] => { + let TokenKind::Ident(name) = token.token() else { + bail!("unrecognized token") + }; + let types = resolver.types_by_name(name); for (interface, ty) in &types { - let typ = querier.display_wit_type_def(ty, Expansion::Expanded(1)); + let typ = resolver.display_wit_type_def(ty, Expansion::Expanded(1)); let name = &ty.name; - let interface = interface.and_then(|i| querier.interface_name(i)); + let interface = interface.and_then(|i| resolver.interface_name(i)); let ident = match (interface, name) { (Some(i), Some(n)) => format!("{i}#{n}: "), (None, Some(n)) => format!("{n}: "), @@ -142,56 +151,64 @@ impl<'a> Cmd<'a> { ), }; } - Cmd::BuiltIn { name, args } if name == "compose" => { - let &[path] = args.as_slice() else { + Cmd::BuiltIn { + name: "compose", + args, + } => { + let &[token] = args.as_slice() else { bail!( "wrong number of arguments to compose function. Expected 1 got {}", args.len() ) }; + let TokenKind::String(path) = token.token() else { + bail!("unrecognized token {}", token.input.str); + }; let adapter = std::fs::read(&*path).context("could not read path to adapter module")?; runtime.compose(&adapter)?; - *querier = WorldResolver::from_bytes(runtime.component_bytes())?; + *resolver = WorldResolver::from_bytes(runtime.component_bytes())?; } - Cmd::BuiltIn { name, args } if name == "link" => { - let &[import_ident, export_ident, component] = args.as_slice() else { - bail!("wrong number of arguments. Expected 3 got {}", args.len()) - }; - let Ok((_, import_ident)) = ItemIdent::parse((&*import_ident).into()) else { - bail!("'{import_ident}' is not a proper item identifier"); + Cmd::BuiltIn { name: "link", args } => { + let mut args = args.into_iter().collect(); + let Ok(Some(import_ident)) = Ident::try_parse(&mut args) else { + bail!("import_ident is not a proper item identifier"); }; - let Ok((_, export_ident)) = ItemIdent::parse((&*export_ident).into()) else { - bail!("'{export_ident}' is not a proper item identifier"); + let Ok(Some(export_ident)) = Ident::try_parse(&mut args) else { + bail!("export_ident is not a proper item identifier"); }; - let component_bytes = std::fs::read(component.as_str()) + let Some(TokenKind::String(component)) = args.pop_front().map(|t| t.token()) else { + bail!("component path is not a string"); + }; + let component_bytes = std::fs::read(component) .with_context(|| format!("could not read component '{component}'"))?; - runtime.stub(&querier, import_ident, export_ident, &component_bytes)?; + runtime.stub(&resolver, import_ident, export_ident, &component_bytes)?; } - Cmd::BuiltIn { name, args } if name == "inspect" => { - let &[ident] = args.as_slice() else { - bail!("wrong number of arguments. Expected 1 got {}", args.len()) - }; - let Ok((_, ident)) = ItemIdent::parse((&*ident).into()) else { - bail!("'{ident}' is not a proper item identifier"); + Cmd::BuiltIn { + name: "inspect", + args, + } => { + let mut args = args.into_iter().collect(); + let Ok(Some(ident)) = Ident::try_parse(&mut args) else { + bail!("ident is not a proper item identifier"); }; match ident { - ItemIdent::Function(ident) => { - let f = querier + Ident::Item(ident) => { + let f = resolver .exported_function(ident) - .or_else(|| querier.imported_function(ident)); + .or_else(|| resolver.imported_function(ident)); match f { - Some(f) => println!("{}", format_function(f, querier)), + Some(f) => println!("{}", format_function(f, resolver)), None => bail!("Could not find imported or exported function '{ident}'"), } } - ItemIdent::Interface(ident) => { - let i = querier + Ident::Interface(ident) => { + let i = resolver .exported_interface(ident) - .or_else(|| querier.imported_interface(ident)); + .or_else(|| resolver.imported_interface(ident)); match i { - Some(f) => println!("{}", format_interface(f, querier)), + Some(f) => println!("{}", format_interface(f, resolver)), None => { bail!("Could not find imported or exported interface '{ident}'") } @@ -199,8 +216,14 @@ impl<'a> Cmd<'a> { } } } - Cmd::BuiltIn { name, args: _ } if name == "help" => print_help(), - Cmd::BuiltIn { name, args: _ } if name == "clear" => return Ok(true), + Cmd::BuiltIn { + name: "help", + args: _, + } => print_help(), + Cmd::BuiltIn { + name: "clear", + args: _, + } => return Ok(true), Cmd::BuiltIn { name, args: _ } => { bail!("Unrecognized built-in function '{name}'") } @@ -226,22 +249,22 @@ There are also builtin functions that can be called with a preceding '.'. Suppor .inspect $item inspect an item `$item` in scope (`?` is alias for this built-in)") } -fn format_world_item(item: &wit_parser::WorldItem, querier: &WorldResolver) -> Option { +fn format_world_item(item: &wit_parser::WorldItem, resolver: &WorldResolver) -> Option { match item { - wit_parser::WorldItem::Function(f) => Some(format_function(f, querier)), + wit_parser::WorldItem::Function(f) => Some(format_function(f, resolver)), wit_parser::WorldItem::Interface(id) => { - let interface = querier.interface_by_id(*id).unwrap(); + let interface = resolver.interface_by_id(*id).unwrap(); if interface.functions.is_empty() { return None; } - let output = format_interface(interface, querier); + let output = format_interface(interface, resolver); Some(output) } wit_parser::WorldItem::Type(_) => None, } } -fn format_interface(interface: &wit_parser::Interface, querier: &WorldResolver) -> String { +fn format_interface(interface: &wit_parser::Interface, resolver: &WorldResolver) -> String { use std::fmt::Write; let mut output = String::from("{\n"); for (_, fun) in &interface.functions { @@ -249,7 +272,7 @@ fn format_interface(interface: &wit_parser::Interface, querier: &WorldResolver) &mut output, " {}: {}", fun.name.bold(), - format_function(fun, querier) + format_function(fun, resolver) ) .unwrap(); } @@ -257,16 +280,16 @@ fn format_interface(interface: &wit_parser::Interface, querier: &WorldResolver) output } -fn format_function(f: &wit_parser::Function, querier: &WorldResolver) -> String { +fn format_function(f: &wit_parser::Function, resolver: &WorldResolver) -> String { let mut params = Vec::new(); for (param_name, param_type) in &f.params { - let ty = querier.display_wit_type(param_type, Expansion::Collapsed); + let ty = resolver.display_wit_type(param_type, Expansion::Collapsed); params.push(format!("{param_name}: {}", ty.italic())); } let params = params.join(", "); let rets = match &f.results { wit_parser::Results::Anon(t) => { - let t = querier.display_wit_type(t, Expansion::Collapsed); + let t = resolver.display_wit_type(t, Expansion::Collapsed); format!(" -> {}", t.italic()) } wit_parser::Results::Named(n) if n.is_empty() => String::new(), @@ -274,7 +297,7 @@ fn format_function(f: &wit_parser::Function, querier: &WorldResolver) -> String let params = params .iter() .map(|(name, t)| { - let t = querier.display_wit_type(t, Expansion::Collapsed); + let t = resolver.display_wit_type(t, Expansion::Collapsed); format!("{name}: {t}") }) .collect::>() diff --git a/src/command/parser.rs b/src/command/parser.rs index 67001ef..f895906 100644 --- a/src/command/parser.rs +++ b/src/command/parser.rs @@ -1,168 +1,390 @@ -use std::ops::Range; +use std::collections::VecDeque; -use nom::branch::alt; -use nom::bytes::complete::tag; -use nom::character::complete::{alpha1, digit1, multispace0, multispace1}; -use nom::combinator::{cut, map, map_res, recognize}; -use nom::multi::{many0_count, separated_list0}; -use nom::sequence::{delimited, pair, preceded}; -use nom::InputTakeAtPosition; +use crate::command::tokenizer::TokenKind; + +use super::tokenizer::Token; #[derive(Debug, PartialEq)] pub enum Line<'a> { - Builtin(SpannedStr<'a>, Vec>), Expr(Expr<'a>), - Assignment(SpannedStr<'a>, Expr<'a>), + BuiltIn(BuiltIn<'a>), + Assignment(&'a str, Expr<'a>), } impl<'a> Line<'a> { - pub fn parse(input: &'a str) -> nom::IResult<&str, Line> { - let input = Span::new(input); - alt(( - map(builtin, |(name, args)| { - Line::Builtin(name.into(), args.into_iter().map(|s| s.into()).collect()) - }), - map(assignment, |(ident, expr)| { - Line::Assignment(ident.into(), expr) - }), - map(Expr::parse, Line::Expr), - ))(input) - .map_err(|e| e.map(|e| nom::error::Error::new(*e.input.fragment(), e.code))) - .map(|(rest, result)| (*rest.fragment(), result)) + pub fn parse(mut tokens: VecDeque>) -> Result, ParserError<'a>> { + let result = match BuiltIn::try_parse(&mut tokens)? { + Some(builtin) => Ok(Self::BuiltIn(builtin)), + None => match Self::try_parse_assignment(&mut tokens)? { + Some((ident, expr)) => Ok(Self::Assignment(ident, expr)), + None => match Expr::try_parse(&mut tokens)? { + Some(e) => Ok(Self::Expr(e)), + None => { + return match tokens.front() { + Some(t) => Err(ParserError::UnexpectedToken(*t)), + None => Err(ParserError::UnexpectedEndOfInput), + } + } + }, + }, + }; + if !tokens.is_empty() { + return Err(ParserError::RemainingInput); + } + result } -} -/// Used to collect span information during parsing -type Span<'a> = nom_locate::LocatedSpan<&'a str>; - -pub fn builtin(input: Span) -> nom::IResult)> { - alt((builtin_call, special_char))(input) + fn try_parse_assignment( + tokens: &mut VecDeque>, + ) -> Result)>, ParserError<'a>> { + let Some(token) = tokens.front() else { + return Ok(None); + }; + let TokenKind::Ident(ident) = token.token() else { + return Ok(None); + }; + let token = *token; + let _ = tokens.pop_front(); + if matches!(tokens.front().map(|t| t.token()), Some(TokenKind::Equal)) { + let _ = tokens.pop_front(); + match Expr::try_parse(tokens)? { + Some(e) => Ok(Some((ident, e))), + None => Err(ParserError::ExpectedExpr), + } + } else { + tokens.push_front(token); + Ok(None) + } + } } -pub fn builtin_call(input: Span) -> nom::IResult)> { - let (rest, _) = tag(".")(input)?; - let (rest, ident) = ident(rest)?; - if rest.is_empty() { - return Ok((rest, (ident, Vec::new()))); +#[derive(Debug, PartialEq)] +pub struct BuiltIn<'a> { + pub name: &'a str, + pub rest: Vec>, +} + +impl<'a> BuiltIn<'a> { + fn try_parse(tokens: &mut VecDeque>) -> Result>, ParserError<'a>> { + let Some(TokenKind::Builtin(ident)) = tokens.front().map(|t| t.token()) else { + return Ok(None); + }; + tokens.pop_front(); + Ok(Some(BuiltIn { + name: ident, + rest: tokens.drain(..).collect(), + })) } - let (rest, args) = separated_list0(multispace1, builtin_argument)(rest)?; +} - Ok((rest, (ident, args))) +#[derive(Debug, PartialEq)] +pub enum ParserError<'a> { + UnexpectedToken(Token<'a>), + UnexpectedEndOfInput, + RemainingInput, + ExpectedExpr, } -pub fn special_char(input: Span) -> nom::IResult)> { - let (rest, _) = tag("?")(input)?; - let (rest, _) = multispace0(rest)?; - let (rest, args) = separated_list0(multispace1, builtin_argument)(rest)?; - if args.is_empty() { - return Ok((rest, (Span::new("help"), Vec::new()))); +impl std::error::Error for ParserError<'_> {} + +impl std::fmt::Display for ParserError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParserError::UnexpectedToken(_) => f.write_str("unexpected token"), + ParserError::UnexpectedEndOfInput => f.write_str("unexpected end of input"), + ParserError::RemainingInput => f.write_str("remaining input"), + ParserError::ExpectedExpr => f.write_str("unexpected expression"), + } } - Ok((rest, (Span::new("inspect"), args))) } #[derive(Debug, PartialEq)] pub enum Expr<'a> { + FunctionCall(FunctionCall<'a>), + Ident(&'a str), Literal(Literal<'a>), - Ident(SpannedStr<'a>), - FunctionCall(FunctionIdent<'a>, Vec>), } impl<'a> Expr<'a> { - pub fn parse(input: Span) -> nom::IResult { - alt(( - map(function_call, |(name, args)| { - Expr::FunctionCall(name.into(), args) - }), - map(ident, |i| Expr::Ident(i.into())), - map(Literal::parse, Expr::Literal), - ))(input) + fn try_parse(input: &mut VecDeque>) -> Result>, ParserError<'a>> { + let Some(first) = input.front() else { + return Ok(None); + }; + match first.token() { + TokenKind::String(s) => { + input.pop_front(); + Ok(Some(Expr::Literal(Literal::String(s)))) + } + TokenKind::Number(n) => { + input.pop_front(); + Ok(Some(Expr::Literal(Literal::Number(n)))) + } + TokenKind::OpenBracket => { + input.pop_front(); + enum State { + ExpectExpr, + ExpectComma, + } + let mut state = State::ExpectExpr; + let mut items = vec![]; + while let Some(token) = input.front() { + match token.token() { + TokenKind::ClosedBracket => { + input.pop_front(); + return Ok(Some(Expr::Literal(Literal::List(List { items })))); + } + TokenKind::Comma if matches!(state, State::ExpectComma) => { + input.pop_front(); + state = State::ExpectExpr; + } + _ => { + let expr = Expr::try_parse(input)?; + if let Some(expr) = expr { + items.push(expr); + state = State::ExpectComma; + } else { + return Err(ParserError::UnexpectedEndOfInput); + } + } + } + } + return Err(ParserError::UnexpectedEndOfInput); + } + TokenKind::OpenBrace => { + input.pop_front(); + enum State<'a> { + ExpectIdent, + ExpectColon(&'a str), + ExpectExpr(&'a str), + ExpectComma, + } + let mut state = State::ExpectIdent; + let mut fields = vec![]; + while let Some(token) = input.front() { + match (token.token(), state) { + (TokenKind::ClosedBrace, State::ExpectComma | State::ExpectIdent) => { + input.pop_front(); + return Ok(Some(Expr::Literal(Literal::Record(Record { fields })))); + } + (TokenKind::Comma, State::ExpectComma) => { + input.pop_front(); + state = State::ExpectIdent; + } + (TokenKind::Colon, State::ExpectColon(ident)) => { + input.pop_front(); + state = State::ExpectExpr(ident); + } + (_, State::ExpectIdent) => { + let ident = Literal::parse_ident(input)?; + state = State::ExpectColon(ident); + } + (_, State::ExpectExpr(ident)) => { + let expr = Expr::try_parse(input)?; + if let Some(expr) = expr { + fields.push((ident, expr)); + state = State::ExpectComma; + } else { + return Err(ParserError::UnexpectedEndOfInput); + } + } + _ => return Err(ParserError::UnexpectedToken(*token)), + } + } + return Err(ParserError::UnexpectedEndOfInput); + } + TokenKind::Ident(_) => { + let func = FunctionCall::try_parse(input)?; + match func { + Some(f) => Ok(Some(Expr::FunctionCall(f))), + None => Ok(Some(Expr::Ident(Literal::parse_ident(input)?))), + } + } + + _ => return Ok(None), + } } } +#[derive(Debug, PartialEq)] +pub struct FunctionCall<'a> { + pub ident: ItemIdent<'a>, + pub args: Vec>, +} + +impl<'a> FunctionCall<'a> { + fn try_parse(input: &mut VecDeque>) -> Result, ParserError<'a>> { + let original = input.clone(); + let Some(function_ident) = ItemIdent::try_parse(input)? else { + return Ok(None); + }; + let next = input.front(); + if next.map(|t| t.token()) != Some(TokenKind::OpenParen) { + // If we failed to find an open paren then we need to completely bail + // on function parsing which means restoring the input state back to + // its original form. + *input = original; + return Ok(None); + } + expect_token(input, |t| t == TokenKind::OpenParen)?; + let mut args = Vec::new(); + loop { + let Some(expr) = Expr::try_parse(input)? else { + break; + }; + args.push(expr); + if input.front().map(|t| t.token()) != Some(TokenKind::Comma) { + break; + } + } + expect_token(input, |t| t == TokenKind::ClosedParen)?; + Ok(Some(FunctionCall { + ident: function_ident, + args, + })) + } +} + +fn expect_token<'a>( + input: &mut VecDeque>, + pred: impl FnOnce(TokenKind<'a>) -> bool, +) -> Result<(), ParserError<'a>> { + let Some(token) = input.pop_front() else { + return Err(ParserError::UnexpectedEndOfInput); + }; + if !pred(token.token()) { + return Err(ParserError::UnexpectedToken(token)); + } + Ok(()) +} + #[derive(Debug, PartialEq, Copy, Clone)] -pub enum ItemIdent<'a> { - Function(FunctionIdent<'a>), +pub enum Ident<'a> { + Item(ItemIdent<'a>), Interface(InterfaceIdent<'a>), } -impl<'a> ItemIdent<'a> { - pub fn parse(input: Span<'a>) -> nom::IResult, ItemIdent<'a>> { - alt(( - map(FunctionIdent::parse, |f| ItemIdent::Function(f)), - map(InterfaceIdent::parse, |i| ItemIdent::Interface(i)), - ))(input) +impl<'a> Ident<'a> { + pub(crate) fn try_parse( + input: &mut VecDeque>, + ) -> Result>, ParserError<'a>> { + match ItemIdent::try_parse(input)? { + Some(i) => Ok(Some(Self::Item(i))), + None => Ok(InterfaceIdent::try_parse(input)?.map(Self::Interface)), + } } } #[derive(Debug, PartialEq, Copy, Clone)] -pub struct FunctionIdent<'a> { +pub struct ItemIdent<'a> { pub interface: Option>, - pub function: SpannedStr<'a>, + pub item: &'a str, } -impl<'a> FunctionIdent<'a> { - pub fn parse(input: Span<'a>) -> nom::IResult, FunctionIdent<'a>> { - fn with_interface(input: Span<'_>) -> nom::IResult, FunctionIdent<'_>> { - let (rest, interface) = InterfaceIdent::parse(input)?; - let (rest, _) = tag("#")(rest)?; - let (rest, function) = cut(ident)(rest)?; - Ok(( - rest, - FunctionIdent { - interface: Some(interface), - function: function.into(), - }, - )) +impl<'a> ItemIdent<'a> { + fn try_parse(input: &mut VecDeque>) -> Result, ParserError<'a>> { + let interface = InterfaceIdent::try_parse(input)?; + match interface { + Some(i) if i.package.is_none() => { + if input.front().map(|t| t.token()) == Some(TokenKind::Hash) { + input.pop_front(); + let ident = Literal::parse_ident(input)?; + Ok(Some(ItemIdent { + interface: Some(i), + item: ident, + })) + } else { + // We parsed the function ident as the interface ident + // Map the interface ident to the function ident + Ok(Some(ItemIdent { + interface: None, + item: i.interface, + })) + } + } + Some(i) => { + // if we parse an interface id with a full package, we must + // be expecting a `#` next with the function ident + match input.pop_front() { + Some(t) if t.token() == TokenKind::Hash => { + let ident = Literal::parse_ident(input)?; + Ok(Some(ItemIdent { + interface: Some(i), + item: ident, + })) + } + Some(t) => Err(ParserError::UnexpectedToken(t)), + None => Err(ParserError::UnexpectedEndOfInput), + } + } + + None => Ok(None), } - alt(( - with_interface, - map(ident, |f| Self { - interface: None, - function: f.into(), - }), - ))(input) } } -impl std::fmt::Display for FunctionIdent<'_> { +impl std::fmt::Display for ItemIdent<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(interface) = self.interface { write!(f, "{interface}#")? } - write!(f, "{}", self.function) + write!(f, "{}", self.item) } } #[derive(Debug, PartialEq, Clone, Copy)] pub struct InterfaceIdent<'a> { - package: Option<(SpannedStr<'a>, SpannedStr<'a>)>, - interface: SpannedStr<'a>, + package: Option<(&'a str, &'a str)>, + interface: &'a str, } impl<'a> InterfaceIdent<'a> { - fn parse(input: Span<'a>) -> nom::IResult { - fn prefixed<'a>(input: Span<'a>) -> nom::IResult> { - let (rest, namespace) = ident(input)?; - let (rest, _) = tag(":")(rest)?; - let (rest, package) = cut(ident)(rest)?; - let (rest, _) = cut(tag("/"))(rest)?; - let (rest, interface) = cut(ident)(rest)?; - Ok(( - rest, - InterfaceIdent { - package: Some((namespace.into(), package.into())), - interface: interface.into(), - }, - )) + fn try_parse<'b>(input: &'b mut VecDeque>) -> Result, ParserError<'a>> { + #[derive(Debug)] + enum State<'a> { + ExpectFirst, + ExpectColon(&'a str), + ExpectSecond(&'a str), + ExpectSlash(&'a str, &'a str), + ExpectThird(&'a str, &'a str), + } + let mut state = State::ExpectFirst; + loop { + let token = input.front(); + match (token.map(|t| t.token()), state) { + (Some(TokenKind::Ident(i)), State::ExpectFirst) => { + input.pop_front(); + state = State::ExpectColon(i); + } + (Some(TokenKind::Colon), State::ExpectColon(first)) => { + input.pop_front(); + state = State::ExpectSecond(first); + } + (Some(TokenKind::Ident(second)), State::ExpectSecond(first)) => { + input.pop_front(); + state = State::ExpectSlash(first, second); + } + (Some(TokenKind::Slash), State::ExpectSlash(first, second)) => { + input.pop_front(); + state = State::ExpectThird(first, second); + } + (Some(TokenKind::Ident(third)), State::ExpectThird(first, second)) => { + input.pop_front(); + return Ok(Some(InterfaceIdent { + package: Some((first, second)), + interface: third, + })); + } + (_, State::ExpectColon(first)) => { + return Ok(Some(InterfaceIdent { + package: None, + interface: first, + })); + } + (_, State::ExpectFirst) => return Ok(None), + (Some(_), _) => return Err(ParserError::UnexpectedToken(*token.unwrap())), + _ => return Err(ParserError::UnexpectedEndOfInput), + } } - - alt(( - prefixed, - map(ident, |i| Self { - package: None, - interface: i.into(), - }), - ))(input) } } @@ -177,20 +399,24 @@ impl std::fmt::Display for InterfaceIdent<'_> { #[derive(Debug, PartialEq)] pub enum Literal<'a> { - Record(Record<'a>), + String(&'a str), + Number(usize), List(List<'a>), - String(SpannedStr<'a>), - Num(usize), + Record(Record<'a>), } impl<'a> Literal<'a> { - pub fn parse(input: Span) -> nom::IResult { - alt(( - map(number, Literal::Num), - map(Record::parse, Literal::Record), - map(List::parse, Literal::List), - map(string_literal, |s| Literal::String(s.into())), - ))(input) + fn parse_ident(input: &mut VecDeque>) -> Result<&'a str, ParserError<'a>> { + let Some(token) = input.front() else { + return Err(ParserError::UnexpectedEndOfInput); + }; + match token.token() { + TokenKind::Ident(i) => { + input.pop_front(); + Ok(i) + } + _ => Err(ParserError::UnexpectedToken(*token)), + } } } @@ -199,302 +425,251 @@ pub struct List<'a> { pub items: Vec>, } -impl<'a> List<'a> { - fn parse(input: Span<'a>) -> nom::IResult { - fn item(input: Span) -> nom::IResult> { - delimited(multispace0, Expr::parse, multispace0)(input) - } - let (rest, _) = tag("[")(input)?; - let (rest, items) = cut(separated_list0(tag(","), item))(rest)?; - let (rest, _) = cut(tag("]"))(rest)?; - Ok((rest, Self { items })) +impl<'a> From>> for List<'a> { + fn from(items: Vec>) -> Self { + Self { items } } } -fn number(input: Span) -> nom::IResult { - fn parse(input: Span) -> Result { - input.fragment().parse() - } - map_res(digit1, parse)(input) -} - #[derive(Debug, PartialEq)] pub struct Record<'a> { - pub fields: Vec<(SpannedStr<'a>, Expr<'a>)>, -} - -impl<'a> Record<'a> { - fn parse(input: Span<'a>) -> nom::IResult { - fn field(input: Span) -> nom::IResult)> { - let (rest, name) = preceded(multispace0, ident)(input)?; - let (rest, _) = tag(":")(rest)?; - let (rest, expr) = delimited(multispace0, Expr::parse, multispace0)(rest)?; - Ok((rest, (name, expr))) - } - let (rest, _) = tag("{")(input)?; - let (rest, fields) = cut(separated_list0(tag(","), field))(rest)?; - let fields = fields.into_iter().map(|(f, e)| (f.into(), e)).collect(); - let (rest, _) = cut(tag("}"))(rest)?; - Ok((rest, Self { fields })) - } -} - -fn assignment(input: Span) -> nom::IResult)> { - let (rest, ident) = ident(input)?; - let (rest, _) = delimited(multispace0, tag("="), multispace0)(rest)?; - let (r, value) = cut(Expr::parse)(rest)?; - Ok((r, (ident, value))) -} - -pub fn function_call(input: Span) -> nom::IResult, Vec>)> { - let (rest, ident) = FunctionIdent::parse(input)?; - let (rest, _) = tag("(")(rest)?; - let (rest, args) = cut(separated_list0(tag(","), Expr::parse))(rest)?; - let (rest, _) = cut(tag(")"))(rest)?; - - Ok((rest, (ident, args))) -} - -fn string_literal(input: Span) -> nom::IResult { - delimited(tag("\""), anything_but_quote, tag("\""))(input) -} - -fn builtin_argument(input: Span) -> nom::IResult { - alt(( - delimited(tag("\""), anything_but_quote, tag("\"")), - anything_but_space, - ))(input) -} - -fn anything_but_quote(input: Span) -> nom::IResult { - input.split_at_position_complete(|c| c == '"') -} - -/// Anything that is not whitespace -fn anything_but_space(input: Span) -> nom::IResult { - input.split_at_position_complete(char::is_whitespace) -} - -pub fn ident(input: Span) -> nom::IResult { - let ident_parser = recognize(pair(alpha1, many0_count(alt((alpha1, tag("-")))))); - delimited(multispace0, ident_parser, multispace0)(input) -} - -/// A view into the input str with span information -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct SpannedStr<'a> { - str: &'a str, - offset: usize, + pub fields: Vec<(&'a str, Expr<'a>)>, } -impl<'a> SpannedStr<'a> { - pub fn as_str(&self) -> &'a str { - self.str - } - - pub fn range(&self) -> Range { - self.offset..(self.offset + self.str.len()) +impl<'a> From)>> for Record<'a> { + fn from(fields: Vec<(&'a str, Expr<'a>)>) -> Self { + Self { fields } } } -impl<'a> PartialEq<&str> for SpannedStr<'a> { - fn eq(&self, other: &&str) -> bool { - self.str == *other - } -} - -impl std::fmt::Display for SpannedStr<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.str) - } -} +#[cfg(test)] +mod tests { + use std::vec; -impl From> for String { - fn from(value: SpannedStr<'_>) -> Self { - value.str.to_owned() - } -} + use crate::command::tokenizer::{SpannedStr, TokenKind}; -impl<'a> std::ops::Deref for SpannedStr<'a> { - type Target = str; + use super::*; - fn deref(&self) -> &Self::Target { - self.str + fn dummy_spanned_str() -> SpannedStr<'static> { + SpannedStr { str: "", offset: 0 } } -} -impl<'a> From> for SpannedStr<'a> { - fn from(span: Span<'a>) -> Self { - Self { - str: span.fragment(), - offset: span.location_offset(), + fn token(kind: TokenKind<'static>) -> Token<'static> { + Token { + input: dummy_spanned_str(), + token: kind, } } -} -impl<'a> From<(&'a str, usize)> for SpannedStr<'a> { - fn from((str, offset): (&'a str, usize)) -> Self { - Self { str, offset } + fn tokens(tokens: impl IntoIterator>) -> VecDeque> { + tokens.into_iter().map(token).collect() } -} -#[cfg(test)] -mod tests { - use super::*; + fn parse( + ts: impl IntoIterator>, + ) -> Result, ParserError<'static>> { + Line::parse(tokens(ts)) + } #[test] - fn function_call() { - let input = r#"my-func(my-other-func("arg"))"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); + fn parse_string_literals() { + let line = parse([TokenKind::String("hello-world")]).unwrap(); assert_eq!( - result, - Line::Expr(Expr::FunctionCall( - FunctionIdent { - interface: None, - function: ("my-func", 0).into() - }, - vec![Expr::FunctionCall( - FunctionIdent { - interface: None, - function: ("my-other-func", 8).into() - }, - vec![Expr::Literal(Literal::String(("arg", 23).into()))] - )] - )) + line, + Line::Expr(Expr::Literal(Literal::String("hello-world"))) ); } #[test] - fn namespaced_interface_function_call() { - let input = r#"wasi:cli/terminal-stderr#my-func()"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); - assert_eq!( - result, - Line::Expr(Expr::FunctionCall( - FunctionIdent { - interface: Some(InterfaceIdent { - package: Some((("wasi", 0).into(), ("cli", 5).into())), - interface: ("terminal-stderr", 9).into() - }), - function: ("my-func", 25).into() - }, - vec![] - )) - ); + fn parse_list_literals() { + let list_of_string = Line::Expr(Expr::Literal(Literal::List( + vec![Expr::Literal(Literal::String("hello-world"))].into(), + ))); + let line = parse([ + TokenKind::OpenBracket, + TokenKind::String("hello-world"), + TokenKind::ClosedBracket, + ]) + .unwrap(); + assert_eq!(line, list_of_string); + + let line = parse([ + TokenKind::OpenBracket, + TokenKind::String("hello-world"), + TokenKind::Comma, + TokenKind::ClosedBracket, + ]) + .unwrap(); + assert_eq!(line, list_of_string); + + let err = parse([ + TokenKind::OpenBracket, + TokenKind::String("hello-world"), + TokenKind::Comma, + ]) + .unwrap_err(); + assert_eq!(err, ParserError::UnexpectedEndOfInput); } #[test] - fn interface_function_call() { - let input = r#"terminal-stderr#my-func()"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); + fn parse_record_literals() { + let record = Line::Expr(Expr::Literal(Literal::Record( + vec![("foo", Expr::Literal(Literal::String("bar")))].into(), + ))); + let line = parse([ + TokenKind::OpenBrace, + TokenKind::Ident("foo"), + TokenKind::Colon, + TokenKind::String("bar"), + TokenKind::ClosedBrace, + ]) + .unwrap(); + assert_eq!(line, record); + + let line = parse([ + TokenKind::OpenBrace, + TokenKind::Ident("foo"), + TokenKind::Colon, + TokenKind::String("bar"), + TokenKind::Comma, + TokenKind::ClosedBrace, + ]) + .unwrap(); + assert_eq!(line, record); + + let err = parse([ + TokenKind::OpenBrace, + TokenKind::Ident("foo"), + TokenKind::String("bar"), + TokenKind::ClosedBrace, + ]) + .unwrap_err(); assert_eq!( - result, - Line::Expr(Expr::FunctionCall( - FunctionIdent { - interface: Some(InterfaceIdent { - package: None, - interface: ("terminal-stderr", 0).into() - }), - function: ("my-func", 16).into() - }, - vec![] - )) + err, + ParserError::UnexpectedToken(token(TokenKind::String("bar"))) ); } #[test] - fn function_call_bad_args() { - let input = r#"my-func(%^&)"#; - let result = Line::parse(input); - assert!(matches!(result, Err(nom::Err::Failure(_)))); - } - - #[test] - fn function_call_with_record() { - let input = r#"my-func({n:1, name: err("string") })"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); - - assert_eq!( - result, - Line::Expr(Expr::FunctionCall( - FunctionIdent { + fn parse_function_calls() { + let function = Line::Expr(Expr::FunctionCall(FunctionCall { + ident: ItemIdent { + interface: Some(InterfaceIdent { + package: Some(("foo", "bar")), + interface: "baz", + }), + item: "qux", + }, + args: vec![], + })); + let line = parse([ + TokenKind::Ident("foo"), + TokenKind::Colon, + TokenKind::Ident("bar"), + TokenKind::Slash, + TokenKind::Ident("baz"), + TokenKind::Hash, + TokenKind::Ident("qux"), + TokenKind::OpenParen, + TokenKind::ClosedParen, + ]) + .unwrap(); + assert_eq!(line, function); + + let function = Line::Expr(Expr::FunctionCall(FunctionCall { + ident: ItemIdent { + interface: None, + item: "qux", + }, + args: vec![], + })); + let line = parse([ + TokenKind::Ident("qux"), + TokenKind::OpenParen, + TokenKind::ClosedParen, + ]) + .unwrap(); + assert_eq!(line, function); + + let function = Line::Expr(Expr::FunctionCall(FunctionCall { + ident: ItemIdent { + interface: None, + item: "foo", + }, + args: vec![Expr::FunctionCall(FunctionCall { + ident: ItemIdent { interface: None, - function: ("my-func", 0).into() + item: "bar", }, - vec![Expr::Literal(Literal::Record(Record { - fields: vec![ - (("n", 9).into(), Expr::Literal(Literal::Num(1))), - ( - ("name", 15).into(), - Expr::FunctionCall( - FunctionIdent { - interface: None, - function: ("err", 24).into() - }, - vec![Expr::Literal(Literal::String(("string", 29).into()))] - ) - ) - ] - }))] - )) + args: vec![], + })], + })); + let line = parse([ + TokenKind::Ident("foo"), + TokenKind::OpenParen, + TokenKind::Ident("bar"), + TokenKind::OpenParen, + TokenKind::ClosedParen, + TokenKind::ClosedParen, + ]) + .unwrap(); + assert_eq!(line, function); + + let err = parse([ + TokenKind::Ident("foo"), + TokenKind::Colon, + TokenKind::Ident("bar"), + TokenKind::OpenParen, + TokenKind::ClosedParen, + ]) + .unwrap_err(); + assert_eq!( + err, + ParserError::UnexpectedToken(token(TokenKind::OpenParen)) ); + + let err = parse([ + TokenKind::Ident("foo"), + TokenKind::Colon, + TokenKind::Ident("bar"), + TokenKind::Hash, + TokenKind::Ident("baz"), + TokenKind::OpenParen, + TokenKind::ClosedParen, + ]) + .unwrap_err(); + assert_eq!(err, ParserError::UnexpectedToken(token(TokenKind::Hash))); } #[test] - fn builtin() { - let input = r#".foo bar baz"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); - assert_eq!( - result, - Line::Builtin( - ("foo", 1).into(), - vec![("bar", 5).into(), ("baz", 9).into(),] - ) - ); + fn parse_ident_expr() { + let line = parse([TokenKind::Ident("foo")]).unwrap(); + assert_eq!(line, Line::Expr(Expr::Ident("foo"))); } #[test] - fn builtin_no_args() { - let input = r#".foo"#; - let result = Line::parse(input); + fn parse_builtin() { + let line = parse([TokenKind::Builtin("foo"), TokenKind::Ident("foo")]).unwrap(); assert_eq!( - result, - Ok(("".into(), Line::Builtin(("foo", 1).into(), vec![]))) + line, + Line::BuiltIn(BuiltIn { + name: "foo", + rest: vec![token(TokenKind::Ident("foo"))] + }) ); } #[test] - fn assignment() { - let input = r#"x = "wow""#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); - + fn parse_assignment() { + let line = parse([ + TokenKind::Ident("foo"), + TokenKind::Equal, + TokenKind::String("bar"), + ]) + .unwrap(); assert_eq!( - result, - Line::Assignment( - ("x", 0).into(), - Expr::Literal(Literal::String(("wow", 5).into())) - ) + line, + Line::Assignment("foo", Expr::Literal(Literal::String("bar"))) ); } - - #[test] - fn ident_line() { - let input = r#"hello-world"#; - let (rest, result) = Line::parse(input).unwrap(); - assert!(rest.is_empty()); - assert_eq!(result, Line::Expr(Expr::Ident(("hello-world", 0).into()))); - } - - #[test] - fn nonsense_assignment() { - let input = r#"x = %&*"#; - let result = Line::parse(input); - assert!(matches!(result, Err(nom::Err::Failure(_)))); - } } diff --git a/src/command/tokenizer.rs b/src/command/tokenizer.rs new file mode 100644 index 0000000..435e802 --- /dev/null +++ b/src/command/tokenizer.rs @@ -0,0 +1,256 @@ +use std::{collections::VecDeque, fmt::Write, ops::Deref}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Token<'a> { + pub input: SpannedStr<'a>, + pub token: TokenKind<'a>, +} + +impl<'a> Token<'a> { + pub fn token(&self) -> TokenKind<'a> { + self.token + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TokenKind<'a> { + String(&'a str), + Ident(&'a str), + Builtin(&'a str), + Flag(&'a str), + Number(usize), + Equal, + OpenParen, + ClosedParen, + Slash, + Hash, + Colon, + OpenBracket, + ClosedBracket, + OpenBrace, + ClosedBrace, + Comma, + Period, +} + +impl<'a> Token<'a> { + pub fn tokenize(input: &'a str) -> Result>, TokenizeError> { + let mut tokens = VecDeque::new(); + let mut rest = SpannedStr { + str: input, + offset: 0, + }; + while !rest.is_empty() { + let (new_rest, token) = Token::next(rest)?; + if let Some(token) = token { + tokens.push_back(token); + } + rest = new_rest; + } + Ok(tokens) + } + + fn next(rest: SpannedStr<'a>) -> Result<(SpannedStr<'a>, Option>), TokenizeError> { + let mut chars = rest.chars().peekable(); + let original_offset = rest.offset; + let Some(first) = chars.next() else { + return Ok((rest, None)); + }; + let (offset, token_kind) = match first { + '"' => { + let len: usize = chars.take_while(|c| *c != '"').map(|c| c.len_utf8()).sum(); + let offset = 2 * '"'.len_utf8() + len; + let str = &rest.str[1..(offset - 1)]; + (offset, Some(TokenKind::String(str))) + } + c if c.is_ascii_alphabetic() => { + let len: usize = chars + .take_while(|c| c.is_ascii_alphabetic() || *c == '-') + .map(|c| c.len_utf8()) + .sum(); + let offset = c.len_utf8() + len; + let str = &rest.str[..offset]; + if str.ends_with('-') { + return Err(TokenizeError::UnexpectedChar( + '-', + original_offset + offset - 1, + )); + } + (offset, Some(TokenKind::Ident(str))) + } + c if c.is_ascii_digit() => { + let len: usize = chars + .take_while(|c| c.is_ascii_digit()) + .map(|c| c.len_utf8()) + .sum(); + let offset = c.len_utf8() + len; + let num = rest.str[..offset] + .parse() + .expect("failed to parse ascii digits as number"); + (offset, Some(TokenKind::Number(num))) + } + c if c.is_whitespace() => (c.len_utf8(), None), + '=' => ('='.len_utf8(), Some(TokenKind::Equal)), + '(' => ('('.len_utf8(), Some(TokenKind::OpenParen)), + ')' => (')'.len_utf8(), Some(TokenKind::ClosedParen)), + '/' => ('/'.len_utf8(), Some(TokenKind::Slash)), + '#' => ('/'.len_utf8(), Some(TokenKind::Hash)), + ':' => ('/'.len_utf8(), Some(TokenKind::Colon)), + '[' => ('['.len_utf8(), Some(TokenKind::OpenBracket)), + ']' => (']'.len_utf8(), Some(TokenKind::ClosedBracket)), + ',' => (','.len_utf8(), Some(TokenKind::Comma)), + '.' => { + if matches!(chars.peek(), Some(c) if c.is_alphabetic()) { + let len: usize = chars + .take_while(|c| c.is_ascii_alphabetic() || *c == '_') + .map(|c| c.len_utf8()) + .sum(); + let offset = '.'.len_utf8() + len; + let ident = &rest.str[1..offset]; + (offset, Some(TokenKind::Builtin(ident))) + } else { + ('.'.len_utf8(), Some(TokenKind::Period)) + } + } + '{' => ('.'.len_utf8(), Some(TokenKind::OpenBrace)), + '}' => ('.'.len_utf8(), Some(TokenKind::ClosedBrace)), + '-' if chars.peek() == Some(&'-') => { + let len: usize = chars + .skip(1) + .take_while(|c| c.is_ascii_alphabetic() || *c == '_' || *c == '-') + .map(|c| c.len_utf8()) + .sum(); + let offset = '-'.len_utf8() * 2 + len; + let ident = &rest.str[2..offset]; + (offset, Some(TokenKind::Flag(ident))) + } + _ => return Err(TokenizeError::UnexpectedChar(first, original_offset)), + }; + Ok(( + rest.offset(offset), + token_kind.map(|token| Token { + input: SpannedStr { + str: &rest.str[..offset], + offset: original_offset, + }, + token, + }), + )) + } +} + +#[derive(Debug, PartialEq)] +pub enum TokenizeError { + UnexpectedChar(char, usize), +} + +impl std::error::Error for TokenizeError {} + +impl std::fmt::Display for TokenizeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TokenizeError::UnexpectedChar(char, _) => { + f.write_str("unexpected character: ")?; + f.write_char(*char) + } + } + } +} + +/// A view into the input str with span information +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct SpannedStr<'a> { + pub str: &'a str, + pub offset: usize, +} + +impl<'a> SpannedStr<'a> { + fn offset(self, offset: usize) -> SpannedStr<'a> { + Self { + str: &self.str[offset..], + offset: self.offset + offset, + } + } +} + +impl Deref for SpannedStr<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.str + } +} + +#[cfg(test)] +mod tests { + pub use super::*; + #[test] + fn tokenize_literals() { + let input = r#" "hello-world" "#; + let tokens = Token::tokenize(input).unwrap(); + assert_eq!(tokens.len(), 1); + assert_eq!( + tokens[0], + Token { + input: SpannedStr { + str: r#""hello-world""#, + offset: 2 + }, + token: TokenKind::String("hello-world") + } + ); + + let input = " hello "; + let tokens = Token::tokenize(input).unwrap(); + assert_eq!(tokens.len(), 1); + assert_eq!( + tokens[0], + Token { + input: SpannedStr { + str: "hello", + offset: 2 + }, + token: TokenKind::Ident("hello") + } + ); + } + + #[test] + fn tokenize_ident() { + let input = " hello- "; + let err = Token::tokenize(input).unwrap_err(); + assert_eq!(err, TokenizeError::UnexpectedChar('-', 7)) + } + + #[test] + fn tokenize_assignment() { + let input = r#" hello = "world" "#; + let tokens = Token::tokenize(input) + .unwrap() + .into_iter() + .map(|t| t.token) + .collect::>(); + assert_eq!( + tokens, + vec![ + TokenKind::Ident("hello"), + TokenKind::Equal, + TokenKind::String("world"), + ] + ) + } + + #[test] + fn tokenize_builtin() { + let input = ".foo hello"; + let tokens = Token::tokenize(input) + .unwrap() + .into_iter() + .map(|t| t.token) + .collect::>(); + assert_eq!( + tokens, + vec![TokenKind::Builtin("foo"), TokenKind::Ident("hello"),] + ) + } +} diff --git a/src/evaluator.rs b/src/evaluator.rs index d581b3b..12a28a7 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -3,15 +3,11 @@ use std::collections::HashMap; use anyhow::{bail, Context}; use wasmtime::component::{self, List, Record, Val}; -use crate::{ - command::parser::{self, FunctionIdent}, - runtime::Runtime, - wit::WorldResolver, -}; +use crate::{command::parser, runtime::Runtime, wit::WorldResolver}; pub struct Evaluator<'a> { runtime: &'a mut Runtime, - querier: &'a WorldResolver, + resolver: &'a WorldResolver, scope: &'a HashMap, } @@ -19,12 +15,12 @@ impl<'a> Evaluator<'a> { /// Create a new evaluator pub fn new( runtime: &'a mut Runtime, - querier: &'a WorldResolver, + resolver: &'a WorldResolver, scope: &'a HashMap, ) -> Self { Self { runtime, - querier, + resolver, scope, } } @@ -38,7 +34,9 @@ impl<'a> Evaluator<'a> { match expr { parser::Expr::Literal(l) => self.eval_literal(l, type_hint), parser::Expr::Ident(ident) => self.resolve_ident(&*ident, type_hint), - parser::Expr::FunctionCall(ident, mut args) => { + parser::Expr::FunctionCall(func) => { + let ident = func.ident; + let mut args = func.args; log::debug!( "Checking for type constructor for {ident} #args={} type_hint={type_hint:?}", args.len() @@ -46,22 +44,20 @@ impl<'a> Evaluator<'a> { // If the preferred type has some sort of type constructor, try that first match type_hint { Some(component::Type::Option(o)) - if ident.interface.is_none() - && ident.function == "some" - && args.len() == 1 => + if ident.interface.is_none() && ident.item == "some" && args.len() == 1 => { let val = self.eval(args.remove(0), Some(&o.ty()))?; return o.new_val(Some(val)); } Some(component::Type::Result(r)) if args.len() == 1 => { if let Some(ok) = r.ok() { - if ident.interface.is_none() && ident.function == "ok" { + if ident.interface.is_none() && ident.item == "ok" { let val = self.eval(args.remove(0), Some(&ok))?; return r.new_val(Ok(Some(val))); } } if let Some(err) = r.err() { - if ident.interface.is_none() && ident.function == "err" { + if ident.interface.is_none() && ident.item == "err" { let val = self.eval(args.remove(0), Some(&err))?; return r.new_val(Err(Some(val))); } @@ -85,12 +81,12 @@ impl<'a> Evaluator<'a> { /// Call the function pub fn call_func( &mut self, - ident: FunctionIdent, + ident: parser::ItemIdent, args: Vec>, ) -> anyhow::Result> { log::debug!("Calling function: {ident} with args: {args:?}"); let func_def = self - .querier + .resolver .exported_function(ident) .with_context(|| format!("no function with name '{ident}'"))?; let mut evaled_args = Vec::with_capacity(func_def.params.len()); @@ -177,20 +173,16 @@ impl<'a> Evaluator<'a> { .map(|(index, field)| (field.name, index)) .collect::>(); // Sort the fields since wasmtime expects the fields to be in the defined order - r.fields.sort_by(|(f1, _), (f2, _)| { - types - .get(f1.as_str()) - .unwrap() - .cmp(types.get(f2.as_str()).unwrap()) - }); + r.fields + .sort_by(|(f1, _), (f2, _)| types.get(f1).unwrap().cmp(types.get(f2).unwrap())); for ((name, field_expr), field_type) in r.fields.into_iter().zip(ty.fields()) { - values.push((name.as_str(), self.eval(field_expr, Some(&field_type.ty))?)); + values.push((name, self.eval(field_expr, Some(&field_type.ty))?)); } Ok(Val::Record(Record::new(ty, values)?)) } parser::Literal::String(s) => { - let val = Val::String(s.as_str().to_owned().into()); + let val = Val::String(s.to_owned().into()); match type_hint { Some(component::Type::Result(r)) => r.new_val(match (r.ok(), r.err()) { (Some(_), _) => Ok(Some(val)), @@ -200,7 +192,7 @@ impl<'a> Evaluator<'a> { _ => Ok(val), } } - parser::Literal::Num(n) => match type_hint { + parser::Literal::Number(n) => match type_hint { Some(component::Type::U8) => Ok(Val::U8(n.try_into()?)), _ => Ok(Val::S32(n.try_into()?)), }, diff --git a/src/main.rs b/src/main.rs index 49fcba3..4ae1969 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,8 +28,8 @@ fn _main() -> anyhow::Result<()> { let cli = Cli::parse(); let component_bytes = std::fs::read(cli.component)?; - let mut querier = wit::WorldResolver::from_bytes(&component_bytes)?; - let mut runtime = runtime::Runtime::init(component_bytes, &querier, |import_name| { + let mut resolver = wit::WorldResolver::from_bytes(&component_bytes)?; + let mut runtime = runtime::Runtime::init(component_bytes, &resolver, |import_name| { print_error_prefix(); eprintln!("unimplemented import: {import_name}"); })?; @@ -38,7 +38,7 @@ fn _main() -> anyhow::Result<()> { if let Some(home) = home::home_dir() { let _ = rl.load_history(&home.join(".weplhistory")); } - let world = querier.world_name(); + let world = resolver.world_name(); println!("{}: {world}", "World".blue().bold()); let mut scope = HashMap::default(); let prompt = "> ".blue().bold().to_string(); @@ -50,7 +50,7 @@ fn _main() -> anyhow::Result<()> { let line = command::Cmd::parse(&line); match line { Ok(Some(cmd)) => { - match cmd.run(&mut runtime, &mut querier, &mut scope) { + match cmd.run(&mut runtime, &mut resolver, &mut scope) { Err(e) => { print_error_prefix(); eprintln!("{e}"); diff --git a/src/runtime.rs b/src/runtime.rs index 43a9412..b870bae 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -14,9 +14,10 @@ use wasmtime_wasi::preview2::{ WasiView, }; -use crate::command::parser::{FunctionIdent, InterfaceIdent, ItemIdent}; - -use super::wit::WorldResolver; +use crate::{ + command::parser::{self, ItemIdent}, + wit::WorldResolver, +}; pub struct Runtime { engine: Engine, @@ -30,7 +31,7 @@ pub struct Runtime { impl Runtime { pub fn init( component_bytes: Vec, - querier: &WorldResolver, + resolver: &WorldResolver, stub_import: impl Fn(&str) + Sync + Send + Clone + 'static, ) -> anyhow::Result { let engine = load_engine()?; @@ -38,13 +39,13 @@ impl Runtime { let mut linker = Linker::::new(&engine); linker.allow_shadowing(true); - let imports_wasi_cli = querier.imports_wasi_cli(); + let imports_wasi_cli = resolver.imports_wasi_cli(); if imports_wasi_cli { log::debug!("Linking with wasi"); wasmtime_wasi::preview2::command::sync::add_to_linker(&mut linker)?; } - for (import_name, import) in querier.imports(!imports_wasi_cli) { - let import_name = querier.world_item_name(import_name); + for (import_name, import) in resolver.imports(!imports_wasi_cli) { + let import_name = resolver.world_item_name(import_name); let stub_import = stub_import.clone(); match import { wit_parser::WorldItem::Function(f) => { @@ -56,7 +57,7 @@ impl Runtime { })?; } wit_parser::WorldItem::Interface(i) => { - let interface = querier.interface_by_id(*i).unwrap(); + let interface = resolver.interface_by_id(*i).unwrap(); let mut root = linker.root(); let mut instance = root.instance(&import_name)?; for (_, f) in interface.functions.iter() { @@ -68,7 +69,7 @@ impl Runtime { })?; } for (name, t) in &interface.types { - let t = querier.type_by_id(*t).unwrap(); + let t = resolver.type_by_id(*t).unwrap(); match &t.kind { wit_parser::TypeDefKind::Resource => { let ty = wasmtime::component::ResourceType::host::<()>(); @@ -97,7 +98,7 @@ impl Runtime { }) } - pub fn get_func(&mut self, ident: FunctionIdent) -> anyhow::Result { + pub fn get_func(&mut self, ident: ItemIdent) -> anyhow::Result { let func = match ident.interface { Some(i) => { let mut exports = self.instance.exports(&mut self.store); @@ -107,13 +108,13 @@ impl Runtime { .with_context(|| { format!("could not find exported instance with name '{instance_name}'") })? - .func(&ident.function) + .func(&ident.item) } None => self .instance .exports(&mut self.store) .root() - .func(&ident.function), + .func(&ident.item), }; func.with_context(|| format!("could not find function '{ident}' in instance")) } @@ -136,22 +137,22 @@ impl Runtime { /// export needed. pub fn stub( &mut self, - querier: &WorldResolver, - import_ident: ItemIdent<'_>, - export_ident: ItemIdent<'_>, + resolver: &WorldResolver, + import_ident: parser::Ident<'_>, + export_ident: parser::Ident<'_>, component_bytes: &[u8], ) -> anyhow::Result<()> { match (import_ident, export_ident) { - (ItemIdent::Function(import_ident), ItemIdent::Function(export_ident)) => { - self.stub_function(querier, import_ident, export_ident, component_bytes) + (parser::Ident::Item(import_ident), parser::Ident::Item(export_ident)) => { + self.stub_function(resolver, import_ident, export_ident, component_bytes) } - (ItemIdent::Interface(import_ident), ItemIdent::Interface(export_ident)) => { - self.stub_interface(querier, import_ident, export_ident, component_bytes) + (parser::Ident::Interface(import_ident), parser::Ident::Interface(export_ident)) => { + self.stub_interface(resolver, import_ident, export_ident, component_bytes) } - (ItemIdent::Interface(_), ItemIdent::Function(_)) => { + (parser::Ident::Interface(_), parser::Ident::Item(_)) => { anyhow::bail!("cannot satisfy interface import with a function") } - (ItemIdent::Function(_), ItemIdent::Interface(_)) => { + (parser::Ident::Item(_), parser::Ident::Interface(_)) => { anyhow::bail!("cannot satisfy function import with an interface") } } @@ -159,9 +160,9 @@ impl Runtime { pub fn stub_interface( &mut self, - querier: &WorldResolver, - import_ident: InterfaceIdent<'_>, - export_ident: InterfaceIdent<'_>, + resolver: &WorldResolver, + import_ident: parser::InterfaceIdent<'_>, + export_ident: parser::InterfaceIdent<'_>, component_bytes: &[u8], ) -> anyhow::Result<()> { let component = load_component(&self.engine, component_bytes)?; @@ -171,7 +172,7 @@ impl Runtime { let mut import_instance = root .instance(&import_ident.to_string()) .with_context(|| format!("no imported instance named '{import_ident}' found"))?; - let import = querier + let import = resolver .imported_interface(import_ident) .with_context(|| format!("no imported interface named '{import_ident}' found"))?; let other = WorldResolver::from_bytes(component_bytes)?; @@ -194,7 +195,7 @@ impl Runtime { .iter() .zip(&exported_function.params) { - if !types_equal(querier, p1, &other, p2) { + if !types_equal(resolver, p1, &other, p2) { anyhow::bail!( "different types for arg '{arg_name}' in function '{fun_name}'" ) @@ -211,13 +212,13 @@ impl Runtime { .collect::>(); for (name, ty) in is { let e = es.get(name).with_context(|| format!("exported function '{fun_name}' does not have return value '{name}'"))?; - if !types_equal(querier, ty, &other, e) { + if !types_equal(resolver, ty, &other, e) { anyhow::bail!("return value '{name}' has differing types"); } } } (wit_parser::Results::Anon(t1), wit_parser::Results::Anon(t2)) => { - if !types_equal(querier, t1, &other, t2) { + if !types_equal(resolver, t1, &other, t2) { anyhow::bail!("return types did not match for function {fun_name}"); } } @@ -254,13 +255,13 @@ impl Runtime { pub fn stub_function( &mut self, - querier: &WorldResolver, - import_ident: FunctionIdent<'_>, - export_ident: FunctionIdent<'_>, + resolver: &WorldResolver, + import_ident: parser::ItemIdent<'_>, + export_ident: parser::ItemIdent<'_>, component_bytes: &[u8], ) -> anyhow::Result<()> { // type checking - let import = querier + let import = resolver .imported_function(import_ident) .with_context(|| format!("no import with name '{import_ident}'"))?; let other = WorldResolver::from_bytes(component_bytes)?; @@ -286,15 +287,15 @@ impl Runtime { let mut instance = export .instance(&interface.to_string()) .with_context(|| format!("no export named '{interface} found'"))?; - instance.func(&export_ident.function) + instance.func(&export_ident.item) } - None => export_instance.get_func(&mut *store_lock, &export_ident.function), + None => export_instance.get_func(&mut *store_lock, &export_ident.item), } } .with_context(|| format!("no function found named '{export_ident}'"))?; let store = self.import_impls.store.clone(); - let name = import_ident.function.as_str().to_owned(); + let name = import_ident.item.to_owned(); match import_ident.interface { Some(interface) => { let mut instance = self @@ -501,29 +502,29 @@ impl WasiView for ImportImplsContext { } fn types_equal( - querier1: &WorldResolver, + resolver1: &WorldResolver, t1: &wit_parser::Type, - querier2: &WorldResolver, + resolver2: &WorldResolver, t2: &wit_parser::Type, ) -> bool { match (t1, t2) { (wit_parser::Type::Id(t1), wit_parser::Type::Id(t2)) => { - let t1 = querier1.type_by_id(*t1).unwrap(); - let t2 = querier2.type_by_id(*t2).unwrap(); - type_defs_equal(querier1, &t1.kind, querier2, &t2.kind) + let t1 = resolver1.type_by_id(*t1).unwrap(); + let t2 = resolver2.type_by_id(*t2).unwrap(); + type_defs_equal(resolver1, &t1.kind, resolver2, &t2.kind) } (wit_parser::Type::Id(t1), t2) => { - let t1 = querier1.type_by_id(*t1).unwrap(); + let t1 = resolver1.type_by_id(*t1).unwrap(); if let wit_parser::TypeDefKind::Type(t1) = &t1.kind { - types_equal(querier1, t1, querier2, t2) + types_equal(resolver1, t1, resolver2, t2) } else { false } } (t1, wit_parser::Type::Id(t2)) => { - let t2 = querier1.type_by_id(*t2).unwrap(); + let t2 = resolver1.type_by_id(*t2).unwrap(); if let wit_parser::TypeDefKind::Type(t2) = &t2.kind { - types_equal(querier1, t1, querier2, t2) + types_equal(resolver1, t1, resolver2, t2) } else { false } @@ -533,27 +534,27 @@ fn types_equal( } fn type_defs_equal( - querier1: &WorldResolver, + resolver1: &WorldResolver, t1: &wit_parser::TypeDefKind, - querier2: &WorldResolver, + resolver2: &WorldResolver, t2: &wit_parser::TypeDefKind, ) -> bool { match (t1, t2) { (wit_parser::TypeDefKind::Result(r1), wit_parser::TypeDefKind::Result(r2)) => { let oks = match (&r1.ok, &r2.ok) { (None, None) => true, - (Some(t1), Some(t2)) => types_equal(querier1, t1, querier2, t2), + (Some(t1), Some(t2)) => types_equal(resolver1, t1, resolver2, t2), _ => false, }; let errs = match (&r1.err, &r2.err) { (None, None) => true, - (Some(t1), Some(t2)) => types_equal(querier1, t1, querier2, t2), + (Some(t1), Some(t2)) => types_equal(resolver1, t1, resolver2, t2), _ => false, }; oks && errs } (wit_parser::TypeDefKind::List(t1), wit_parser::TypeDefKind::List(t2)) => { - types_equal(querier1, t1, querier2, t2) + types_equal(resolver1, t1, resolver2, t2) } (wit_parser::TypeDefKind::Variant(v1), wit_parser::TypeDefKind::Variant(v2)) => { if v1.cases.len() != v2.cases.len() { @@ -561,7 +562,7 @@ fn type_defs_equal( } v1.cases.iter().zip(v2.cases.iter()).all(|(c1, c2)| { let types_equal = match (&c1.ty, &c2.ty) { - (Some(t1), Some(t2)) => types_equal(querier1, t1, querier2, t2), + (Some(t1), Some(t2)) => types_equal(resolver1, t1, resolver2, t2), (None, None) => true, _ => false, }; diff --git a/src/wit.rs b/src/wit.rs index b0bf802..79b5b78 100644 --- a/src/wit.rs +++ b/src/wit.rs @@ -7,7 +7,7 @@ use wit_parser::{ WorldKey, }; -use crate::command::parser::{FunctionIdent, InterfaceIdent}; +use crate::command::parser; /// A resolver for a wit world. pub struct WorldResolver { @@ -38,14 +38,14 @@ impl WorldResolver { } /// Get the exported function by the given `FunctionIdent`. - pub fn exported_function(&self, ident: FunctionIdent) -> Option<&Function> { + pub fn exported_function(&self, ident: parser::ItemIdent) -> Option<&Function> { match ident.interface { Some(i) => { let interface = self.exported_interface(i)?; - interface.functions.get(ident.function.as_str()) + interface.functions.get(ident.item) } None => { - if let WorldItem::Function(f) = &self.export(ident.function.as_str())? { + if let WorldItem::Function(f) = &self.export(ident.item)? { Some(f) } else { None @@ -55,14 +55,14 @@ impl WorldResolver { } /// Get the imported function by the given `FunctionIdent`. - pub fn imported_function(&self, ident: FunctionIdent) -> Option<&Function> { + pub fn imported_function(&self, ident: parser::ItemIdent) -> Option<&Function> { match ident.interface { Some(i) => { let interface = self.imported_interface(i)?; - interface.functions.get(ident.function.as_str()) + interface.functions.get(ident.item) } None => { - if let WorldItem::Function(f) = &self.import(ident.function.as_str())? { + if let WorldItem::Function(f) = &self.import(ident.item)? { Some(f) } else { None @@ -72,12 +72,12 @@ impl WorldResolver { } /// Get the exported interface by the given `InterfaceIdent`. - pub fn exported_interface(&self, ident: InterfaceIdent) -> Option<&Interface> { + pub fn exported_interface(&self, ident: parser::InterfaceIdent) -> Option<&Interface> { self.interface_in_items(ident, self.world().exports.iter()) } /// Get the imported interface by the given `InterfaceIdent`. - pub fn imported_interface(&self, ident: InterfaceIdent) -> Option<&Interface> { + pub fn imported_interface(&self, ident: parser::InterfaceIdent) -> Option<&Interface> { self.interface_in_items(ident, self.world().imports.iter()) } @@ -342,7 +342,7 @@ impl WorldResolver { /// Get an interface by its ident from a list of world items. fn interface_in_items<'a>( &self, - ident: InterfaceIdent, + ident: parser::InterfaceIdent, items: impl Iterator, ) -> Option<&Interface> { let item = self.get_world_item_by_name(items, &ident.to_string())?;