Skip to content

Commit

Permalink
Merge branch 'topic/interpolated_strings' into 'master'
Browse files Browse the repository at this point in the history
Implementation of string interpolation

Closes #1037

See merge request eng/libadalang/libadalang!1645
  • Loading branch information
raph-amiard committed May 28, 2024
2 parents 88aef2c + 2a442f2 commit dcecb88
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 5 deletions.
87 changes: 87 additions & 0 deletions ada/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -7522,6 +7522,19 @@ def allows_universal_real():
default_val=False
)

@langkit_property(predicate_error="No inferable type for expression")
def is_not_any_type():
"""
Predicate that'll check that this type is not null. Meant to be used in
equations, where we know that the expression's type cannot be
determined if there is no expected type, as in::

Predicate(BaseTypeDecl.is_not_any_type,
Self.expected_type_var,
error_location=Self)
"""
return Not(Self.is_null)

@langkit_property(dynamic_vars=[default_origin()],
predicate_error="$Self does not allow string literals")
def allows_string_literal():
Expand Down Expand Up @@ -19579,10 +19592,84 @@ def xref_equation():
# case we don't want to constrain its type.
Self.parent.is_a(Name),
Entity.super(),

Bind(Self.expected_type_var, Self.type_var)
& Predicate(BaseTypeDecl.is_not_any_type,
Self.expected_type_var,
error_location=Self)
& Predicate(BaseTypeDecl.allows_string_literal,
Self.expected_type_var,
error_location=Self)
)


@abstract
class FormatStringTokNode(AdaNode):
"""
Node holding a format string token.
"""
token_node = True


class FormatStringTokStart(FormatStringTokNode):
"""
Node holding a formatting "start" token.
"""
pass


class FormatStringTokMid(FormatStringTokNode):
"""
Node holding a formatting "middle" token.
"""
pass


class FormatStringTokEnd(FormatStringTokNode):
"""
Node holding a formatting "end" token.
"""
pass


class FormatStringChunk(AdaNode):
"""
Chunk of a format string literal.
"""
expr = Field(type=T.Expr)
string_tok = Field(type=T.FormatStringTokNode)

@langkit_property()
def xref_equation():
return And(
Entity.expr.sub_equation,

# The RFC specifies that interpolated string expressions can be "of
# any type", so we explicitly bind the expected type to null.
Bind(Self.expr.expected_type_var, No(T.AdaNode))
)


class FormatStringLiteral(Expr):
"""
Interpolated string expression.

See :gnat_rm:`string-interpolation` for more details.
"""

opening_chunk = Field(type=T.FormatStringTokStart)
mid_exprs = Field(type=T.FormatStringChunk.list)
trailing_expr = Field(type=T.FormatStringChunk)

@langkit_property()
def xref_equation():
return (
Predicate(BaseTypeDecl.allows_string_literal,
Self.expected_type_var,
error_location=Self)
& Bind(Self.expected_type_var, Self.type_var)
& Entity.trailing_expr.sub_equation
& Entity.mid_exprs.logic_all(lambda m: m.expr.sub_equation)
)


Expand Down
7 changes: 5 additions & 2 deletions ada/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
from langkit.documentation import PassthroughNode


# Passthrough the ada:rmlink role, that we want to be handled by
# sphinxcontrib-adadomain in generated doc.
# Passthrough some roles that we want to be handled during doc generation
docutils.parsers.rst.roles.register_local_role(
"rmlink", PassthroughNode.role_fn
)

docutils.parsers.rst.roles.register_local_role(
"gnat_rm", PassthroughNode.role_fn
)

libadalang_docs = {
'libadalang.gpr_project': """
Loaded GPR project file.
Expand Down
8 changes: 8 additions & 0 deletions ada/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,13 @@ def end_named_block():
char_literal=CharLiteral(L.Char),
string_literal=StringLiteral(L.String),

format_string_literal=FormatStringLiteral(
FormatStringTokStart(L.FormatStringStart),
List(FormatStringChunk(A.expr, FormatStringTokMid(L.FormatStringMid)),
empty_valid=True),
FormatStringChunk(A.expr, FormatStringTokEnd(L.FormatStringEnd))
),

defining_id=DefiningName(A.identifier),

dec_literal=RealLiteral(L.Decimal),
Expand Down Expand Up @@ -1365,6 +1372,7 @@ def end_named_block():
),

primary=Or(A.num_literal, A.null_literal,
A.format_string_literal,
A.name, A.allocator,
A.conditional_expr,
A.raise_expr,
Expand Down
10 changes: 10 additions & 0 deletions ada/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class Token(LexerToken):
# String and char literals
String = WithText()
Char = WithSymbol()
FormatStringStart = WithText()
FormatStringMid = WithText()
FormatStringEnd = WithText()

With = WithText()
Decimal = WithText()
Expand All @@ -131,6 +134,9 @@ class Token(LexerToken):
ada_lexer.add_patterns(
('bracket_char', r'(\[\"[0-9a-fA-F]+\"\])'),
('p_string', r'\"(\"\"|{bracket_char}|[^\n\"])*\"'),
('p_format_string_start', r'f\"(\"\"|{bracket_char}|[^\n\"\{])*\{'),
('p_format_string_mid', r'\}(\"\"|{bracket_char}|[^\n\"\{])*\{'),
('p_format_string_end', r'\}(\"\"|{bracket_char}|[^\n\"\{])*\"'),
('p_percent_string', r'%(%%|{bracket_char}|[^\n%])*%'),

('digit', r"[0-9]"),
Expand Down Expand Up @@ -288,6 +294,10 @@ class Token(LexerToken):
(Pattern('{p_string}'), Token.String),
(Pattern('{p_percent_string}'), Token.String),

(Pattern('{p_format_string_start}'), Token.FormatStringStart),
(Pattern('{p_format_string_mid}'), Token.FormatStringMid),
(Pattern('{p_format_string_end}'), Token.FormatStringEnd),

# Identifiers
(Pattern('{identifier}'), Token.Identifier),

Expand Down
2 changes: 2 additions & 0 deletions contrib/highlight/highlighter.adb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ package body Highlighter is
Ada_Pipe | Ada_Assign => Punctuation_Special,
Ada_Label_Start .. Ada_Label_End => Label_Name,
Ada_String => String_Literal,
Ada_Format_String_Start
.. Ada_Format_String_End => String_Literal,
Ada_Target => Operator,
Ada_Char => Character_Literal,
Ada_With => Keyword,
Expand Down
Loading

0 comments on commit dcecb88

Please sign in to comment.