diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-04-26 13:00:51 +1200 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-04-26 13:00:51 +1200 |
commit | e5447e2568e24db9a5218bbe452b856266ca39ae (patch) | |
tree | e5425bf49fb7ff93b8c46a80b8742a85ecf19fe4 /src/stages/intermediate.rs | |
parent | 83e5107684a37aa825d626c19a2af7fd6bfc231a (diff) | |
download | torque-asm-e5447e2568e24db9a5218bbe452b856266ca39ae.zip |
Implement first-class string literals
This feature promotes strings to a first-class type in the language.
If a string is passed to an invocation via the new string-type argument,
the string will be passed as a whole value. String arguments can still
be passed to an invocation via an integer-type argument, in which case
they'll be broken apart into individual characters with the macro being
invoked once per character.
String-type macro arguments are declared like "name".
Diffstat (limited to 'src/stages/intermediate.rs')
-rw-r--r-- | src/stages/intermediate.rs | 145 |
1 files changed, 99 insertions, 46 deletions
diff --git a/src/stages/intermediate.rs b/src/stages/intermediate.rs index c28426c..c4bb74d 100644 --- a/src/stages/intermediate.rs +++ b/src/stages/intermediate.rs @@ -84,6 +84,13 @@ impl IntermediateParser { ArgumentType::Block => { IntermediateValue::Block(Vec::new()) } + ArgumentType::String => { + let string = String::new(); + let chars = Vec::new(); + let literal = StringLiteral { string, chars }; + let tracked = Tracked::from(literal, null.clone()); + IntermediateValue::String(tracked) + } }; let tracked = Tracked::from(value, null.clone()); arguments.insert(argument.name.clone(), tracked); @@ -173,6 +180,11 @@ impl<'a> Environment<'a> { let value = IntermediateValue::Block(tokens); Some(Tracked::from(value, source.clone())) } + MacroDefinitionBody::String(string) => { + let string = self.parse_string_token(string, &source)?; + let integer = IntermediateValue::String(string); + Some(Tracked::from(integer, source.clone())) + } } } @@ -279,10 +291,21 @@ impl<'a> Environment<'a> { } } + fn parse_string_token(&mut self, string: &StringToken, source: &SourceSpan) -> Option<Tracked<StringLiteral>> { + match string { + StringToken::StringLiteral(literal) => { + Some(Tracked::from(literal.clone(), source.clone())) + } + StringToken::Invocation(invocation) => { + self.parse_string_invocation(&invocation, source) + } + } + } + fn parse_integer_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<IntermediateInteger>> { match self.parse_invocation(invocation, source)?.value { IntermediateValue::Integer(integer) => Some(integer), - IntermediateValue::Block(_) => { + IntermediateValue::Block(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedInteger; self.errors.push(Tracked::from(error, source.clone())); None @@ -293,7 +316,7 @@ impl<'a> Environment<'a> { fn parse_block_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Vec<Tracked<IntermediateToken>>> { match self.parse_invocation(invocation, source)?.value { IntermediateValue::Block(tokens) => Some(tokens), - IntermediateValue::Integer(_) => { + IntermediateValue::Integer(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedBlock; self.errors.push(Tracked::from(error, source.clone())); None @@ -301,9 +324,21 @@ impl<'a> Environment<'a> { } } + fn parse_string_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<StringLiteral>> { + match self.parse_invocation(invocation, source)?.value { + IntermediateValue::String(literal) => Some(literal), + IntermediateValue::Integer(_) | IntermediateValue::Block(_) => { + let error = IntermediateError::ExpectedString; + self.errors.push(Tracked::from(error, source.clone())); + None + } + } + } + fn parse_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) -> Option<Tracked<IntermediateValue>> { let received_count = invocation.arguments.len(); if let Some(argument) = self.arguments.get(&invocation.name) { + // This invocation is a macro argument if received_count != 0 { let error = IntermediateError::IncorrectArgumentCount(0, received_count); self.errors.push(Tracked::from(error, source.clone())); @@ -312,6 +347,7 @@ impl<'a> Environment<'a> { Some(argument.clone()) } } else if let Some(label_name) = self.label_names.get(&invocation.name) { + // This invocation is a label reference if received_count != 0 { let error = IntermediateError::IncorrectArgumentCount(0, received_count); self.errors.push(Tracked::from(error, source.clone())); @@ -335,48 +371,9 @@ impl<'a> Environment<'a> { // Gather and type-check the provided arguments. let mut arguments = Vec::new(); for (i, argument) in invocation.arguments.iter().enumerate() { - let received_type = match &argument.value { - InvocationArgument::String(string) => { - let mut values = Vec::new(); - for c in &string.chars { - let integer = IntermediateInteger::Integer(**c); - let tracked = Tracked::from(integer, c.source.clone()); - values.push(IntermediateValue::Integer(tracked)); - } - arguments.push(RepeatedArgument::List(values)); - ArgumentType::Integer - } - InvocationArgument::IntegerToken(integer) => { - let tracked = self.parse_integer_token(&integer, &argument.source)?; - let value = IntermediateValue::Integer(tracked); - arguments.push(RepeatedArgument::Loop(value)); - ArgumentType::Integer - } - InvocationArgument::BlockToken(block) => { - let tokens = self.parse_block_token(&block, &argument.source); - let value = IntermediateValue::Block(tokens); - arguments.push(RepeatedArgument::Loop(value)); - ArgumentType::Block - } - InvocationArgument::Invocation(invocation) => { - let value = self.parse_invocation(&invocation, &argument.source)?; - let received_type = match &value.value { - IntermediateValue::Integer(_) => ArgumentType::Integer, - IntermediateValue::Block(_) => ArgumentType::Block, - }; - arguments.push(RepeatedArgument::Loop(value.value)); - received_type - } - }; - let expected_type = match received_type { - ArgumentType::Integer => ArgumentType::Block, - ArgumentType::Block => ArgumentType::Integer, - }; - if definition.arguments[i].variant != received_type { - let error = IntermediateError::IncorrectArgumentType(expected_type, received_type); - self.errors.push(Tracked::from(error, argument.source.clone())); - return None; - } + let expected_type = definition.arguments[i].variant; + let received_value = self.parse_invocation_argument(argument, expected_type)?; + arguments.push(received_value); } // Invoke the invocation multiple times. let repetitions = arguments.iter().map(|a| a.len()).max().unwrap_or(1); @@ -407,6 +404,7 @@ impl<'a> Environment<'a> { unreachable!("Uncaught duplicate macro argument name '{name}'"); }; } + // Invoke the macro once. let mut env = Environment { label_names: &self.label_names, macro_names: &self.macro_names, @@ -415,16 +413,19 @@ impl<'a> Environment<'a> { errors: &mut self.errors, id: next_id!(), }; + // Save the result of this macro invocation. values.push(env.parse_macro_definition_body(&definition.body, source)?); } if values.len() == 1 { + // If the macro was invoked once, return the value. values.pop() } else { - // Flatten all values into a list of block tokens. + // If the macro was invoked multiple times, create a list of + // block tokens from the returned values. let mut block = Vec::new(); for value in values { match value.value { - IntermediateValue::Integer(_) => { + IntermediateValue::Integer(_) | IntermediateValue::String(_) => { let error = IntermediateError::ExpectedBlock; self.errors.push(Tracked::from(error, value.source)); return None; @@ -447,6 +448,58 @@ impl<'a> Environment<'a> { } } + fn parse_invocation_argument(&mut self, argument: &Tracked<InvocationArgument>, expected_type: ArgumentType) -> Option<RepeatedArgument> { + let source = &argument.source; + let (received_value, received_type) = match &argument.value { + InvocationArgument::StringToken(string) => { + let string = self.parse_string_token(string, source)?; + self.parse_invocation_string_argument(string, expected_type)? + } + InvocationArgument::IntegerToken(integer) => { + let tracked = self.parse_integer_token(&integer, &argument.source)?; + let value = IntermediateValue::Integer(tracked); + (RepeatedArgument::Loop(value), ArgumentType::Integer) + } + InvocationArgument::BlockToken(block) => { + let tokens = self.parse_block_token(&block, &argument.source); + let value = IntermediateValue::Block(tokens); + (RepeatedArgument::Loop(value), ArgumentType::Block) + } + InvocationArgument::Invocation(invocation) => { + let value = self.parse_invocation(&invocation, &argument.source)?; + match value.value { + IntermediateValue::Integer(_) => + (RepeatedArgument::Loop(value.value), ArgumentType::Integer), + IntermediateValue::Block(_) => + (RepeatedArgument::Loop(value.value), ArgumentType::Block), + IntermediateValue::String(string) => + self.parse_invocation_string_argument(string, expected_type)? + } + } + }; + if expected_type != received_type { + let error = IntermediateError::IncorrectArgumentType(expected_type, received_type); + self.errors.push(Tracked::from(error, argument.source.clone())); + return None; + } + return Some(received_value); + } + + fn parse_invocation_string_argument(&mut self, string: Tracked<StringLiteral>, expected_type: ArgumentType) -> Option<(RepeatedArgument, ArgumentType)> { + if let ArgumentType::Integer = expected_type { + let mut values = Vec::new(); + for c in &string.chars { + let integer = IntermediateInteger::Integer(**c); + let tracked = Tracked::from(integer, c.source.clone()); + values.push(IntermediateValue::Integer(tracked)); + } + Some((RepeatedArgument::List(values), ArgumentType::Integer)) + } else { + let value = IntermediateValue::String(string); + Some((RepeatedArgument::Loop(value), ArgumentType::String)) + } + } + fn parse_expression(&mut self, expression: &Expression, source: &SourceSpan) -> Option<Tracked<IntermediateInteger>> { let mut intermediate = Vec::new(); let mut error = false; |