mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> As we are tightening the formatting and linting setups, this was missing, now the line-length for black and ruff are the same, thereby no longer running into black issues as often. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
168 lines
6.4 KiB
Python
168 lines
6.4 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import logging
|
|
from typing import Any, List, Optional
|
|
|
|
from semantic_kernel.sk_pydantic import SKBaseModel
|
|
from semantic_kernel.template_engine.blocks.block import Block
|
|
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
|
|
from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock
|
|
from semantic_kernel.template_engine.blocks.symbols import Symbols
|
|
from semantic_kernel.template_engine.blocks.val_block import ValBlock
|
|
from semantic_kernel.template_engine.blocks.var_block import VarBlock
|
|
|
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
|
|
|
|
# BNF parsed by CodeTokenizer:
|
|
# [template] ::= "" | [variable] " " [template]
|
|
# | [value] " " [template]
|
|
# | [function-call] " " [template]
|
|
# [variable] ::= "$" [valid-name]
|
|
# [value] ::= "'" [text] "'" | '"' [text] '"'
|
|
# [function-call] ::= [function-id] | [function-id] [parameter]
|
|
# [parameter] ::= [variable] | [value]
|
|
class CodeTokenizer(SKBaseModel):
|
|
def __init__(self, log: Optional[Any] = None):
|
|
super().__init__()
|
|
|
|
if log:
|
|
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
|
|
|
def tokenize(self, text: str) -> List[Block]:
|
|
# Remove spaces, which are ignored anyway
|
|
text = text.strip() if text else ""
|
|
|
|
# Render None/empty to []
|
|
if not text or text == "":
|
|
return []
|
|
|
|
# Track what type of token we're reading
|
|
current_token_type = None
|
|
|
|
# Track the content of the current token
|
|
current_token_content = []
|
|
|
|
# Other state we need to track
|
|
text_value_delimiter = None
|
|
blocks = []
|
|
next_char = text[0]
|
|
space_separator_found = False
|
|
skip_next_char = False
|
|
|
|
# 1 char only edge case
|
|
if len(text) == 1:
|
|
if next_char == Symbols.VAR_PREFIX:
|
|
blocks.append(VarBlock(text))
|
|
elif next_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE):
|
|
blocks.append(ValBlock(text))
|
|
else:
|
|
blocks.append(FunctionIdBlock(text))
|
|
|
|
return blocks
|
|
|
|
for next_char_cursor in range(1, len(text)):
|
|
current_char = next_char
|
|
next_char = text[next_char_cursor]
|
|
|
|
if skip_next_char:
|
|
skip_next_char = False
|
|
continue
|
|
|
|
# First char is easy
|
|
if next_char_cursor == 1:
|
|
if current_char == Symbols.VAR_PREFIX:
|
|
current_token_type = BlockTypes.VARIABLE
|
|
elif current_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE):
|
|
current_token_type = BlockTypes.VALUE
|
|
text_value_delimiter = current_char
|
|
else:
|
|
current_token_type = BlockTypes.FUNCTION_ID
|
|
|
|
current_token_content.append(current_char)
|
|
continue
|
|
|
|
# While reading values between quotes
|
|
if current_token_type == BlockTypes.VALUE:
|
|
# If the current char is escaping the next special char we:
|
|
# - skip the current char (escape char)
|
|
# - add the next char (special char)
|
|
# - jump to the one after (to handle "\\" properly)
|
|
if current_char == Symbols.ESCAPE_CHAR and self._can_be_escaped(next_char):
|
|
current_token_content.append(next_char)
|
|
skip_next_char = True
|
|
continue
|
|
|
|
current_token_content.append(current_char)
|
|
|
|
# When we reach the end of the value, we add the block
|
|
if current_char == text_value_delimiter:
|
|
blocks.append(ValBlock("".join(current_token_content)))
|
|
current_token_content.clear()
|
|
current_token_type = None
|
|
space_separator_found = False
|
|
|
|
continue
|
|
|
|
# If we're not between quotes, a space signals the end of the current token
|
|
# Note: there might be multiple consecutive spaces
|
|
if self._is_blank_space(current_char):
|
|
if current_token_type == BlockTypes.VARIABLE:
|
|
blocks.append(VarBlock("".join(current_token_content)))
|
|
current_token_content.clear()
|
|
elif current_token_type == BlockTypes.FUNCTION_ID:
|
|
blocks.append(FunctionIdBlock("".join(current_token_content)))
|
|
current_token_content.clear()
|
|
|
|
space_separator_found = True
|
|
current_token_type = None
|
|
|
|
continue
|
|
|
|
# If we're not inside a quoted value and we're not processing a space
|
|
current_token_content.append(current_char)
|
|
|
|
if current_token_type is None:
|
|
if not space_separator_found:
|
|
raise ValueError("Tokens must be separated by one space least")
|
|
|
|
if current_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE):
|
|
# A quoted value starts here
|
|
current_token_type = BlockTypes.VALUE
|
|
text_value_delimiter = current_char
|
|
elif current_char == Symbols.VAR_PREFIX:
|
|
# A variable starts here
|
|
current_token_type = BlockTypes.VARIABLE
|
|
else:
|
|
# A function id starts here
|
|
current_token_type = BlockTypes.FUNCTION_ID
|
|
|
|
# Capture last token
|
|
current_token_content.append(next_char)
|
|
|
|
if current_token_type == BlockTypes.VALUE:
|
|
blocks.append(ValBlock("".join(current_token_content)))
|
|
elif current_token_type == BlockTypes.VARIABLE:
|
|
blocks.append(VarBlock("".join(current_token_content)))
|
|
elif current_token_type == BlockTypes.FUNCTION_ID:
|
|
blocks.append(FunctionIdBlock("".join(current_token_content)))
|
|
else:
|
|
raise ValueError("Tokens must be separated by one space least")
|
|
|
|
return blocks
|
|
|
|
def _is_blank_space(self, c: str) -> bool:
|
|
return c in (
|
|
Symbols.SPACE,
|
|
Symbols.NEW_LINE,
|
|
Symbols.CARRIAGE_RETURN,
|
|
Symbols.TAB,
|
|
)
|
|
|
|
def _can_be_escaped(self, c: str) -> bool:
|
|
return c in (
|
|
Symbols.DBL_QUOTE,
|
|
Symbols.SGL_QUOTE,
|
|
Symbols.ESCAPE_CHAR,
|
|
)
|