Splits FEEL source text into raw tokens (DMN 1.6, section 10.3.1.4).
The lexer is deliberately context-free. FEEL names may contain spaces and
symbols, and whether a-b is a name or a subtraction depends on the names in
scope, so the lexer cannot decide it. It emits words, numbers, strings and
symbols, each with its text, position, and whether white space came before it;
the parser groups them into names (see Feel.Syntax.Token). Scope names are
lexed with the same lexer, so a name in scope and the same name in an
expression produce the same tokens.
What the lexer does
- Words are maximal runs of name part characters (
Feel.Syntax.Characters), soIncomeTaxesAmountandx1are one token each. A word that equals a reserved word is flagged as a keyword;not,list,context,range,e, andEare ordinary words. - Numbers are unsigned:
-is always a separate token, and the parser turns it into a negation. The longest numeric literal wins (1.5,.5,1e-3), unless name characters follow it directly. Then a shorter literal is tried, and if none fits the whole run is a word:2ndis one word, and1.5xis the number1, a dot, and the word5x. A number is read withFeel.Value.Number.parse/1, so one with more than 34 digits is rounded to 34, and one outside the decimal128 exponent range is an error. - Strings are delimited by
"only; typographic quotes are not delimiters. The escapes are backslash followed by',",\,n,r, ort, and\uwith four hex digits or\Uwith six. A high and a low surrogate written as two\uescapes make one character. A backslash before any other character stays in the string literally, since the grammar allows a backslash as a plain character. A string cannot contain a raw line break (U+000A to U+000D). - Comments are
//to the end of the line and/* ... */(not nested). They count as white space. Comment openers win over/and*, soa//bisafollowed by a comment. - White space is the set of grammar rule 61. It wins over name characters where the two overlap.
- Symbols are
... .. . ** * + - / ' <= >= != -> < > = ( ) [ ] { } , : @, longest match first, and'or the typographic apostrophe U+2019 as the:quotesymbol.->is one token, soa->bis not a subtraction followed by a comparison. - Names in backticks (D-83) are one
:quoted_nametoken:`first name`. They are not DMN 1.6 FEEL; they say which tokens form a name without the scope having to, and the parser reports that one was used (Feel.Analysis.QuotedNames). What is between the backticks is lexed as the rest of the source is, and has to be a name in the canonical form ofFeel.Name, which the lexer checks by writing the name out and comparing it with the text:`first name`(two spaces) is not one, nor is`a - b`(it isa-b),`a,b`,`2nd`or`if`. There is no escape for a backtick.
Anything else is an error. Tokenizing stops at the first error and never raises.
Errors
The error is a list with one Feel.Diagnostic whose code is one of
:unterminated_string, :unterminated_comment, :invalid_escape,
:unexpected_character, :number_out_of_range, :invalid_encoding, :unterminated_quoted_name,
:invalid_quoted_name, or :source_too_large.
Summary
Functions
Returns the tokens of a text that is a name, written the way a name can be: a word that can start a name, then tokens that can continue it, with nothing but white space between them and around them.
Tokenizes source.
Types
@type result() :: {:ok, [Feel.Syntax.Token.t()]} | {:error, [Feel.Diagnostic.t(), ...]}
The result of tokenizing.
Functions
@spec name_tokens(String.t()) :: {:ok, [Feel.Syntax.Token.t(), ...]} | :error
Returns the tokens of a text that is a name, written the way a name can be: a word that can start a name, then tokens that can continue it, with nothing but white space between them and around them.
A string used as a context key is a name when this gives its tokens (D-213), and the key is then the canonical
name of the tokens (Feel.Name.join/1). The parser and the analyzer both decide it here. A comment is no
white space for this: the lexer drops it, so the text would not be what it is read as. Anything the lexer
rejects is no name.
iex> {:ok, tokens} = Feel.Syntax.Lexer.name_tokens(" first name ")
iex> Enum.map(tokens, & &1.text)
["first", "name"]
iex> Feel.Syntax.Lexer.name_tokens("a - b = c")
:error
iex> Feel.Syntax.Lexer.name_tokens("a /* c */ b")
:error
@spec tokenize(String.t(), [{:limits, Feel.Limits.t()}]) :: result()
Tokenizes source.
Options
:limits- aFeel.Limits.t/0; only:max_source_bytesis used here.
Examples
iex> {:ok, tokens} = Feel.Syntax.Lexer.tokenize("price * 2")
iex> Enum.map(tokens, &{&1.kind, &1.text})
[word: "price", star: "*", number: "2"]