Showing preview only (404K chars total). Download the full file or copy to clipboard to get everything.
Repository: tevelee/Eval
Branch: master
Commit: df80bd880d56
Files: 98
Total size: 374.9 KB
Directory structure:
gitextract_vfh2ubbu/
├── .codecov.yml
├── .github/
│ └── .config.yml
├── .gitignore
├── .jazzy.yml
├── .swift-version
├── .swiftlint.yml
├── .travis.yml
├── .version
├── CONTRIBUTING.md
├── Dangerfile
├── Documentation/
│ ├── Example projects.md
│ ├── Interpreter engine details.md
│ ├── Strongly-typed evaluator.md
│ ├── Template evaluator.md
│ └── Tips & Tricks.md
├── Eval.playground/
│ ├── Contents.swift
│ ├── Sources/
│ │ ├── Helpers.swift
│ │ └── TypesAndFunctions.swift
│ ├── contents.xcplayground
│ ├── playground.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── Playground.xcscheme
├── Eval.podspec
├── Eval.xcodeproj/
│ ├── EvalTests_Info.plist
│ ├── Eval_Info.plist
│ ├── project.pbxproj
│ └── xcshareddata/
│ └── xcschemes/
│ ├── Eval-Package.xcscheme
│ └── xcschememanagement.plist
├── Eval.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── IDEWorkspaceChecks.plist
├── Examples/
│ ├── .swiftlint.yml
│ ├── AttributedStringExample/
│ │ ├── .gitignore
│ │ ├── Package.swift
│ │ ├── README.md
│ │ ├── Sources/
│ │ │ └── AttributedStringExample/
│ │ │ └── TemplateExample.swift
│ │ └── Tests/
│ │ ├── .swiftlint.yml
│ │ ├── AttributedStringExampleTests/
│ │ │ └── AttributedStringExampleTests.swift
│ │ └── LinuxMain.swift
│ ├── ColorParserExample/
│ │ ├── .gitignore
│ │ ├── Package.swift
│ │ ├── README.md
│ │ ├── Sources/
│ │ │ └── ColorParserExample/
│ │ │ └── ColorParserExample.swift
│ │ └── Tests/
│ │ ├── .swiftlint.yml
│ │ ├── ColorParserExampleTests/
│ │ │ └── ColorParserExampleTests.swift
│ │ └── LinuxMain.swift
│ └── TemplateExample/
│ ├── .gitignore
│ ├── Package.swift
│ ├── README.md
│ ├── Sources/
│ │ └── TemplateExample/
│ │ └── TemplateExample.swift
│ └── Tests/
│ ├── .swiftlint.yml
│ ├── LinuxMain.swift
│ └── TemplateExampleTests/
│ ├── TemplateExampleComponentTests.swift
│ ├── TemplateExampleTests.swift
│ ├── import.txt
│ └── template.txt
├── Gemfile
├── LICENSE.txt
├── Package.swift
├── README.md
├── Scripts/
│ ├── .gitignore
│ ├── .swiftlint.yml
│ ├── Package.swift
│ ├── Sources/
│ │ └── Automation/
│ │ ├── Error.swift
│ │ ├── Eval.swift
│ │ ├── Shell.swift
│ │ ├── Travis.swift
│ │ └── main.swift
│ ├── ci.sh
│ └── git_auth.sh
├── Sources/
│ └── Eval/
│ ├── Common.swift
│ ├── Elements.swift
│ ├── TemplateInterpreter.swift
│ ├── TypedInterpreter.swift
│ └── Utilities/
│ ├── MatchResult.swift
│ ├── Matcher.swift
│ ├── Pattern.swift
│ └── Utils.swift
├── Tests/
│ ├── .swiftlint.yml
│ ├── EvalTests/
│ │ ├── IntegrationTests/
│ │ │ ├── InterpreterTests.swift
│ │ │ ├── PerformanceTest.swift
│ │ │ ├── Suffix.swift
│ │ │ └── TemplateTests.swift
│ │ ├── UnitTests/
│ │ │ ├── DataTypeTests.swift
│ │ │ ├── FunctionTests.swift
│ │ │ ├── InterpreterContextTests.swift
│ │ │ ├── KeywordTests.swift
│ │ │ ├── LiteralTests.swift
│ │ │ ├── MatchResultTests.swift
│ │ │ ├── MatchStatementTests.swift
│ │ │ ├── MatcherTests.swift
│ │ │ ├── PatternTests.swift
│ │ │ ├── TemplateInterpreterTests.swift
│ │ │ ├── TypedInterpreterTests.swift
│ │ │ ├── UtilTests.swift
│ │ │ ├── VariableProcessor.swift
│ │ │ └── VariableTests.swift
│ │ └── Utils.swift
│ └── LinuxMain.swift
└── github_rsa.enc
================================================
FILE CONTENTS
================================================
================================================
FILE: .codecov.yml
================================================
coverage:
ignore:
- Applications/Xcode.app/.*
- build/.*
- .build/.*
- Documentation/.*
- Example/Pods/.*
- Scripts/.*
- vendor/.*
precision: 2
round: down
range: "80...100"
status:
patch: yes
project: yes
changes: no
================================================
FILE: .github/.config.yml
================================================
updateDocsComment: >
Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would update some of our documentation based on your changes.
requestInfoReplyComment: >
We would appreciate it if you could provide us with more info about this issue/pr!
requestInfoLabelToAdd: request-more-info
newPRWelcomeComment: >
Thanks so much for opening your first PR here!
firstPRMergeComment: >
Congrats on merging your first pull request here! :tada: How awesome!
newIssueWelcomeComment: >
Thanks for opening this issue, a maintainer will get back to you shortly!
sentimentBotToxicityThreshold: .7
sentimentBotReplyComment: >
Please be sure to review the code of conduct and be respectful of other users
lockThreads:
toxicityThreshold: .7
numComments: 2
setTimeInHours: 72
replyComment: >
This thread is being locked due to exceeding the toxicity minimums
================================================
FILE: .gitignore
================================================
.DS_Store
/.build
build
/Packages
xcuserdata
.xcuserstate
Pods
Package.resolved
docs
gh-pages
Documentation/Output
================================================
FILE: .jazzy.yml
================================================
author: Laszlo Teveli
author_url: https://tevelee.github.io
readme: README.md
documentation: Documentation/*.md
abstract: Documentation/Sections/*.md
module: Eval
xcodebuild_arguments: [-scheme,Eval-Package]
output: Documentation/Output
theme: apple
github_url: https://github.com/tevelee/Eval
github_file_prefix: https://github.com/tevelee/Eval/tree/master
root_url: https://tevelee.github.io/Eval
clean: true
min_acl: internal
framework_root: Sources
source_directory: .
================================================
FILE: .swift-version
================================================
5.0
================================================
FILE: .swiftlint.yml
================================================
reporter: "xcode"
opt_in_rules:
- array_init
- attributes
- block_based_kvo
- class_delegate_protocol
- closing_brace
- closure_end_indentation
- closure_parameter_position
- closure_spacing
- colon
- comma
- compiler_protocol_init
# - conditional_returns_on_newline
- contains_over_first_not_nil
- control_statement
- custom_rules
- cyclomatic_complexity
- discarded_notification_center_observer
- discouraged_direct_init
- discouraged_object_literal
- dynamic_inline
- empty_count
- empty_enum_arguments
- empty_parameters
- empty_parentheses_with_trailing_closure
# - explicit_acl
- explicit_enum_raw_value
- explicit_init
- explicit_top_level_acl
# - explicit_type_interface
- extension_access_modifier
- fallthrough
- fatal_error_message
- file_header
- file_length
- first_where
- for_where
- force_cast
- force_try
- force_unwrapping
- function_body_length
- function_parameter_count
- generic_type_name
- identifier_name
- implicit_getter
- implicit_return
- implicitly_unwrapped_optional
- is_disjoint
- joined_default_parameter
- large_tuple
- leading_whitespace
- legacy_cggeometry_functions
- legacy_constant
- legacy_constructor
- legacy_nsgeometry_functions
- let_var_whitespace
- line_length
- literal_expression_end_indentation
- mark
- multiline_arguments
- multiline_parameters
- multiple_closures_with_trailing_closure
- nesting
- nimble_operator
# - no_extension_access_modifier
# - no_grouping_extension
- notification_center_detachment
- number_separator
- object_literal
- opening_brace
- operator_usage_whitespace
- operator_whitespace
- overridden_super_call
- override_in_extension
- pattern_matching_keywords
- prefixed_toplevel_constant
- private_action
- private_outlet
- private_over_fileprivate
- private_unit_test
- prohibited_super_call
- protocol_property_accessors_order
- quick_discouraged_call
- quick_discouraged_focused_test
- quick_discouraged_pending_test
- redundant_discardable_let
- redundant_nil_coalescing
- redundant_optional_initialization
- redundant_string_enum_value
- redundant_void_return
- required_enum_case
- return_arrow_whitespace
- shorthand_operator
- single_test_class
- sorted_first_last
- sorted_imports
- statement_position
- strict_fileprivate
- superfluous_disable_command
- switch_case_alignment
- switch_case_on_newline
- syntactic_sugar
- todo
- trailing_closure
- trailing_comma
- trailing_newline
- trailing_semicolon
- trailing_whitespace
- type_body_length
- type_name
- unneeded_break_in_switch
- unneeded_parentheses_in_closure_argument
- unused_closure_parameter
- unused_enumerated
- unused_optional_binding
- valid_ibinspectable
- vertical_parameter_alignment
- vertical_parameter_alignment_on_call
- vertical_whitespace
- void_return
- weak_delegate
- xctfail_message
- yoda_condition
included:
- Sources
- Tests
- Examples/AttributedStringExample/Sources
- Examples/AttributedStringExample/Tests
- Examples/ColorParserExample/Sources
- Examples/ColorParserExample/Tests
- Examples/TemplateExample/Sources
- Examples/TemplateExample/Tests
- Scripts/Sources
force_cast: error
force_try: error
line_length: 320
type_body_length:
- 300
- 400
file_length:
warning: 500
error: 1000
type_name:
min_length: 4
max_length: 25
identifier_name:
min_length: 3
max_length: 35
file_header:
severity: error
required_string: |
/*
* Copyright (c) 2018 Laszlo Teveli.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
================================================
FILE: .travis.yml
================================================
osx_image: xcode11.3
language: swift
sudo: true
env:
global:
- EXPANDED_CODE_SIGN_IDENTITY="-"
- EXPANDED_CODE_SIGN_IDENTITY_NAME="-"
- EXPANDED_PROVISIONING_PROFILE="-"
before_script:
- sh Scripts/git_auth.sh
script:
- travis_retry Scripts/ci.sh
- sleep 3
================================================
FILE: .version
================================================
1.5.0
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Eval
## 👍🎉 First off, thanks for taking the time to contribute! 🎉👍
**You are more than welcomed to do so!**
The following is a set of guidelines and best practices for contributing to Eval. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a Pull Request.
## Code of Conduct
Nothing serious, all I ask is to be respectful and as helpful as you would expect others commnicating with you.
## I don't want to read this whole thing, I just have a question!
The easiest channel is Twitter. You can reach out to me `@tevelee`, or feel free to write a mail at `tevelee [at] gmail [dot] com`.
If you have bigger concerns, feel free to write a GitHub issue.
## What should I know before I get started?
First of all, please read the [documentation pages](Documentation), and feel free to check out the examples.
There are a few things you need to be aware of when contributing:
* The repository is only opened to a very selected set of contributors. If you need any code modifications, you need to fork and create a Pull Request.
* There is a CI up and running
* I use SwiftLint with build-in rules to keep the code as nice as possible
* There is Danger configured with some rules, auto-checking contributions before I do
* I intend to keep the code test coverage as high as possible. Please be mindful about this when contributing
# How Can I Contribute?
## Reporting Bugs or Suggesting Enhancements
Feel free to use the same channels as I described in the README:
The easiest channel is Twitter. You can reach out to me `@tevelee`, or feel free to write a mail at `tevelee [at] gmail [dot] com`.
If you have bigger concerns, feel free to write a GitHub issue.
## Pull Requests
### Git Commit Messages
Please use git rebase keep your number commits as low as possible.
### Documentation Styleguide
I use a standard style of markdown pages in the [README.md](README.md) and in the [Documentation folder](Documentation).
I also document the code, most importantly the public interfaces. I intend to keep the line documentation coverage of the publicly available methods and classes a 100%.
### Issue and Pull Request Labels
No rules you need to be aware of
================================================
FILE: Dangerfile
================================================
# Sometimes it's a README fix, or something like that - which isn't relevant for
# including in a project's CHANGELOG for example
not_declared_trivial = !(github.pr_title.include? "#trivial")
has_app_changes = !git.modified_files.grep(/Sources/).empty?
# ENSURE THAT LABELS HAVE BEEN USED ON THE PR
fail "Please add labels to this PR" if github.pr_labels.empty?
# Mainly to encourage writing up some reasoning about the PR, rather than just leaving a title
if github.pr_body.length < 5
fail "Please provide a summary in the Pull Request description"
end
# Pay extra attention if external contributors modify certain files
if git.modified_files.include?("LICENSE.txt")
fail "External contributor has edited the LICENSE.txt"
end
if git.modified_files.include?("Gemfile") or git.modified_files.include?("Gemfile.lock")
warn "External contributor has edited the Gemfile and/or Gemfile.lock"
end
if git.modified_files.include?("Eval.podspec") or git.modified_files.include?("Package.swift")
warn "External contributor has edited the Eval.podspec and/or Package.swift"
end
# Make it more obvious that a PR is a work in progress and shouldn't be merged yet
warn("PR is classed as Work in Progress") if github.pr_title.include? "WIP"
# Warn when there is a big PR
warn("Big PR, try to keep changes smaller if you can") if git.lines_of_code > 500
# Changelog entries are required for changes to library files.
no_changelog_entry = !git.modified_files.include?("Changelog.md")
if has_app_changes && no_changelog_entry && not_declared_trivial
#warn("Any changes to library code should be reflected in the Changelog. Please consider adding a note there")
end
# Added (or removed) library files need to be added (or removed) from the Carthage Xcode project to avoid breaking things for our Carthage users.
added_swift_library_files = !(git.added_files.grep(/Sources.*\.swift/).empty?)
deleted_swift_library_files = !(git.deleted_files.grep(/Sources.*\.swift/).empty?)
modified_carthage_xcode_project = !(git.modified_files.grep(/Eval\.xcodeproj/).empty?)
if (added_swift_library_files || deleted_swift_library_files) && !modified_carthage_xcode_project
warn("Added or removed library files require the Carthage Xcode project to be updated")
end
missing_doc_changes = git.modified_files.grep(/Documentation/).empty?
doc_changes_recommended = git.insertions > 15
if has_app_changes && missing_doc_changes && doc_changes_recommended && not_declared_trivial
warn("Consider adding supporting documentation to this change. Documentation can be found in the `Documentation` directory.")
end
# Warn when library files has been updated but not tests.
tests_updated = !git.modified_files.grep(/Tests/).empty?
if has_app_changes && !tests_updated
warn("The library files were changed, but the tests remained unmodified. Consider updating or adding to the tests to match the library changes.")
end
# Give inline build results (compile and link time warnings and errors)
xcode_summary.report 'build/tests/summary.json' if File.file?('build/tests/summary.json')
xcode_summary.report 'build/example/summary.json' if File.file?('build/example/summary.json')
# Run SwiftLint
swiftlint.lint_files
#swiftlint.lint_files inline_mode: true
================================================
FILE: Documentation/Example projects.md
================================================
# Example projects
I included a few use-cases, which bring significant improvements on how things are processed before - at least in my previous projects.
### [Template language](https://github.com/tevelee/Eval/blob/master/Examples/TemplateExample/Tests/TemplateExampleTests/TemplateExampleTests.swift)
I was able to create a full-blown template language, completely, using this framework and nothing else. It's almost like a competitor of the one I mentioned ([Twig](https://github.com/twigphp/Twig)). This is the most advanced example of them all!
I created a standard library with all the possible operators you can imagine. With helpers, each operator is a small, one-liner addition. Added the important data types, such as arrays, strings, numbers, booleans, dates, etc., and a few functions, to be more awesome. [Take a look for inspiration!](https://github.com/tevelee/Eval/blob/master/Examples/TemplateExample/Sources/TemplateExample/TemplateExample.swift)
Together, it makes an excellent addition to my model-object generation project, and **REALLY useful for server-side Swift development as well**!
### [Attributed string parser](https://github.com/tevelee/Eval/blob/master/Examples/AttributedStringExample/Tests/AttributedStringExampleTests/AttributedStringExampleTests.swift)
I created another small example, parsing attribtuted strings from simple expressions using XML style tags, such as bold, italic, underlined, colored, etc.
With just a few operators, this solution can deliver attributed strings from basic APIs, which otherwise would be hard to manage.
My connected project is an iOS application, using the Spotify [HUB framework](https://github.com/spotify/HubFramework), in which I can now provide rich strings with my view-models and parse them from the JSON string results.
### [Color parser](https://github.com/tevelee/Eval/blob/master/Examples/ColorParserExample/Tests/ColorParserExampleTests/ColorParserExampleTests.swift)
A color parser is also used by the BFF (Backend For Frontend, not 👭) project I mentioned before. It can parse Swift Color objects from many different styles of strings, such as `#ffddee`, or `red`, or `rgba(1,0.5,0.4,1)`. I included this basic example in the repository as well.
================================================
FILE: Documentation/Interpreter engine details.md
================================================
# Technical details
## Interpreter engine
TBD
## Template engine
TBD
================================================
FILE: Documentation/Strongly-typed evaluator.md
================================================
# Strongly typed evaluator
This kind of evaluator interprets its input as one function. It searches for the one with the highest precedence and works its way down from that.
Ocassionally, more functions are present in the same expression. In this case, it goes recursively, all the way down to the most basic elements: variables or literals, which are trivial to evaluate.
## Creation
The way to create typed interpreters is the following:
```swift
let interpreter = TypedInterpreter(dataTypes: [number, string, boolean, array, date],
functions: [concat, add, multiply, substract, divide],
context: Context(variables: ["example": 1]))
```
First, you'll need the data types you are going to work with. These are a smaller subsets of the build in Swift data types, you can map them to existing types (Swift types of the ones of your own)
The second parameter are the functions you can apply on the above listed data types. All the functions - regardless of the grouping of the data types - should be listed here.
Typically, in case of numbers, these are numeric operators. In case of string, these can be concatenation, getters, slicing, etc.
Or, these can be complex things, such as parentheses, data factories, or high level functional operations, such as filter, map or reduce.
And lastly, an optional context object, if you have any global variables.
Variables in the context can be expression specific, or global that apply to every evaluation session.
## Data types
Data types map the outside world to the inside of the expression. They map existing types to inner data types.
They don't restrict any behaviour, so these types can either be built-in Swift types, such as String, Array, Date; or they can be your custom classes, srtucts, or enums.
Let's see it in action:
```swift
let number = DataType(type: Double.self, literals: [numberLiteral, piConstant]) { String(describing: $0) }
```
If has a type, that is the existing type of the sorrounding program. The literals, which can tell the framework whether a given string can be converted to a given type.
Typical example is the `String` literal, which encloses something between quotes: `'like this'`. The can also be constants, for example `pi` for numbers of `true` for booleans.
The last parameter is a `print` closure. It tells the framework how to render the given type when needed. Typically used while debugging, or when templates use the `print` statement.
In summary: literals provide the input, print provides the output of a mapped type.
### Literals
Let's check out some literals a bit more deeply.
The block used for literals have two parameters: the input string, and an interpreter.
Most of the times, only the input is enough to recognise things, like numbers:
```swift
Literal { value, _ in Double(value) }
```
Arrays, on the other hand, should process their content (Comma `,` separated values between brackets `[` `]`). For this, the second parameter, the interpreter can be used.
#### Constants
Literals are the perfect place to recognise constants, such as:
```swift
Literal("pi", convertsTo: Double.pi)
```
or
```swift
[Literal("false", convertsTo: false)
```
Of course, there are multiple ways to represent them (for example, as a single keyword function pattern), but this seems like a place where they can be most closely connected to their type.
The `convertsTo` parameter of `Literal`s are `autoclosure` parameters, which means, that they are going to be processed lazily.
```swift
Literal("now", convertsTo: Date())
```
The `now` string is going to be expressed as the current timestamp at the time of the evaluation, not the time of the initialisation.
## Functions
Similarly to templates, typed interpreters use the same building blocks to build up their patterns: `Keyword`s and `Variable`s.
### Keywords
`Keyword`s are the most basic elements of a pattern; they represent simple, static `String`s. You can chain them, for example `Keyword("<") + Keyword("br/") + Keyword(>}")`, or simply merge them `Keyword("<br/>")`. Logically, these two are the same, but the former accepts any number of whitespaces between the tags, while the latter allows none, as it is a strict match.
Most of the time though, you are going to need to handle placeholders, varying number of elements. That's where `Variable`s come into place.
### Variables
Let's check out the following, really straightforward pattern:
```swift
Function(Keyword("(") + Variable<Any>("body") + Keyword(")")) { variables, _, _ in
return variables["body"]
}
```
Something between two enclosing parentheses `(`, `)`. The middle tag is a `Variable`, which means that its value is going to be passed through in the block, using its name. Let's imagine the following input: `(5)`. Here, the `variables` dictionary is going to have `5` under the key `body`.
#### Generics
Since its value is going to be processed, there is a generic sign as well, signalling that this current `Variable` accepts `Any` kind of data, no transfer is needed. Let's imagine if we wrote `Variable<String>` instead. In this case, `5` would not match to the pattern, it would be intact. But, for example, `('Hello')` would do.
Let check out a `+` operator. This could equally mean addition for numeric types
```swift
Function(Variable<Double>("lhs") + Keyword("+") + Variable<Double>("rhs")) { arguments,_,_ in
guard let lhs = arguments["lhs"] as? Double, let rhs = arguments["rhs"] as? Double else { return nil }
return lhs + rhs
}
```
or concatenation for strings
```swift
Function(Variable<String>("lhs") + Keyword("+") + Variable<String>("rhs")) { arguments,_,_ in
guard let lhs = arguments["lhs"] as? String, let rhs = arguments["rhs"] as? String else { return nil }
return lhs + rhs
}
```
Since the interpreter is strongly typed, always the appropriate one is going to be selected by the framework.
#### Evaluation
Variables also have optional properties, such as `interpreted`, `shortest`, or `acceptsNilValue`. They might also have a `map` block, which by default is `nil`.
* `interpreted` tells the framework, that its value should be evaluated. This is true, by default. But, the option exists to modify this to false. In that case, `(2 + 3)` would not generate the number `5` under the `body` key, but `2 + 3` as a `String`.
* `shortest` signals the "strength" of the matching operation. By default it's false, we need the most possible characters. The only scenario where this could get tricky is if the last element of a pattern is a `Variable`. In that case, the preferred setting is `false`, so we need the largest possible match!
Let's find out why! A general addition operator (which looks like this `Variable<Double>("lhs") + Keyword("+") + Variable<Double>("rhs")`) would recognise the pattern `12 + 34`, but it also matches to `12 + 3`. What's what shortest means, the shortest match, in this case, is `12 + 3`, which - semantically - is an incorrect match.
But don't worry, the framework already knows about this, so it sets the right value for your variables, even in the last place!
* `acceptsNilValue` informs the framework if `nil` should be accepted by the pattern. For example, `1 + '5'` with the previous example (`Double + Double`) would not match. But, if the `acceptsNilValue` is defined, then the block would trigger, with `{'lhs': 1, 'rhs': nil}`, so you can decide by your own logic what to do in this case.
* Finally, the `map` block can be used to further transform the value of your `Variable` before calling the block on the `Pattern`. Since map is a trailing closure, it's quite easy to add. For example, `Variable<Int>("example") { Double($0) }` would recognise only `Int` values, but would transform them to `Double` instances when providing them in the `variables` dictionary. This map can also return `nil` values but depends on your logic if you want to accept them or not. Side note: the previous map generates a `Variable<Double>` kind of variable instance.
### Specialised elements
#### Open & Close Keyword
Parentheses are quite common in expressions. They are often embedded in each other. Embedding is a nasty problem of interpreters, as `(a * (b + c))` would logically be evaluated with `(b + c)` first, and the rest afterwards.
But, an algorithm, by default, would interpret things linearly, disregarding the semantics: `(a * (b + c))` would be the match for the first if statement, with a totally invalid `a * (b + c` data, until the first match.
This, of course, needs to be solved, but it's not that easy as it first looks! Some edge cases would not work unless we somehow try to connect them together. For this reason, I added two special elements: `OpenKeyword` and `CloseKeyword`. These work exactly the same way as normal `Keyword`s do, but add a bit more semantics to the framework: these two should be connected together, and therefore embedding them should not be a problem as they come in pairs.
The previous parentheses statement should - correctly - look like this:
```swift
Function(OpenVariable<String>("lhs") + Keyword("+") + CloseVariable<String>("rhs")) { arguments,_,_ in
guard let lhs = arguments["lhs"] as? String, let rhs = arguments["rhs"] as? String else { return nil }
return lhs + rhs
}
```
By using the `OpenKeyword` and `CloseKeyword` types, these become connected, so embedding parentheses in an expression shouldn't be a problem.
After this match is defined, they can be embedded in each other as deeply as needed.
#### Multiple Patterns in one Function
This is a rarely used pattern, but `Function`s consists of an array of `Pattern` elements.
Usually, one `Function` does only one operation. Unless this is true, grouping multiple `Pattern`s into one `Function` allows semantical grouping of opeartors.
For example a Boolean negation can be expressed in multiple ways: `not(true)` or `!true`. In this case, semantically both expressions do the same thing, therefore it might be a good practice to use one `Function` with two `Pattern`s for this.
## Context
You can also pass contextual values, which - for now - equal to variables.
```swift
expression.evaluate("1 + var", context: Context(variables: ["var": 2]))
```
The reason that the variables are encapsulated in a context is that context is a class, while variables are mutable `var` struct properties on that object. With this construction the context reference can be passed around to multiple interpreter instances, but keeps the copy-on-write (🐮) behaviour of the modification.
Context defined during the initialisation apply to every evaluation performed with the given interpreter, while the ones passed to the `evaluate` method only apply to that specific expression instance.
If some patterns modify the context, they have the option to modify the general context (for long term settings, such as `value++`), or the local one (for example, the interation variable of a `for` loop).
### Order of statements define precedence
The earlier a pattern is represent in the array of `functions`, the higher precedence it gets.
Practically, if there is an addition function and a multiplication one, the multiplication should be defined earlier (as it has higher precedence), because both are going to match the following expression:
`1 * 2 + 3`, but if addition goes first, then the evaluation would process `1 * 2` on `lhs` and `3` on `rhs`, which - of course - is incorrect.
Typically, parentheses and higher precedence operators should go earlier in the array.
================================================
FILE: Documentation/Template evaluator.md
================================================
# Template evaluator
The logic of the interpreter is fairly easy: it goes over the input character by character and replaces any patterns it can find.
## Creation
The way to create template interpreters is the following:
```swift
let template = StringTemplateInterpreter(statements: [ifStatement, printStatement],
interpreter: interpreter,
context: Context(variables: ["example": 1]))
```
First, you'll need the statements that you aim to recognise.
Then, you'll need a typed interpreter, so that you can evaluate strongly typed expressions.
And lastly, an optional context object, if you have any global variables.
Variables in the context can be expression specific, or global that apply to every evaluation session.
The template interpreter and the given typed interpreter don't share the same context. Apart from the containment dependency, they don't have any logical connection. The reason for this is that templates need a special context feeding the template content, but typed interpreters might work with totally different data types. It is totally up to the developer how they want their context to be managed. Since the context is a class, its reference can be passed around, so it's quite straightforward to have them share the same context object - if needed.
## Statement examples
### Keywords
`Keyword`s are the most basic elements of a pattern; they represent simple, static `String`s. You can chain them, for example `Keyword("{%") + Keyword("if") + Keyword("%}")`, or simply merge them `Keyword("{% if %}")`. Logically, these two are the same, but the former accepts any number of whitespaces between the tags, while the latter allows only one, as it is a strict match.
Most of the time though, you are going to need to handle placeholders, varying number of elements. That's where `Variable`s come into place.
### Variables
Let's check out the following, really straightforward pattern:
```swift
Pattern(Keyword("{{") + Variable<Any>("body") + Keyword("}}")) { variables, interpreter, _ in
guard let body = variables["body"] else { return nil }
return interpreter.typedInterpreter.print(body)
}
```
Something between two enclosing parentheses `{{`, `}}`. The middle tag is a `Variable`, which means that its value is going to be passed through in the block, using its name. Let's imagine the following input: `The winner is: {{ 5 }}`. Here, the `variables` dictionary is going to have `5` under the key `body`.
#### Generics
Since its value is going to be processed, there is a generic sign as well, signalling that this current `Variable` accepts `Any` kind of data, no transfer is needed. Let's imagine if we wrote `Variable<String>` instead. In this case, `5` would not match to the template, it would be intact. But, for example, `{{ 'Hello' }}` would do.
#### Evaluation
Variables also have optional properties, such as `interpreted`, `shortest`, or `acceptsNilValue`. They might also have a `map` block, which by default is `nil`.
* `interpreted` tells the framework, that its value should be evaluated. This is true, by default. But, the option exists to modify this to false. In that case, `{{ 2 + 3 }}` would not generate the number `5` under the `body` key, but `2 + 3` as a `String`.
* `shortest` signals the "strength" of the matching operation. By default it's false, we need the most possible characters. The only scenario where this could get tricky is if the last element of a pattern is a `Variable`. In that case, the preferred setting is `false`, so we need the largest possible match!
Let's find out why! A general addition operator (which looks like this `Variable<Double>("lhs") + Keyword("+") + Variable<Double>("rhs")`) would recognise the pattern `12 + 34`, but it also matches to `12 + 3`. What's what shortest means, the shortest match, in this case, is `12 + 3`, which - semantically - is an incorrect match.
But don't worry, the framework already knows about this, so it sets the right value for your variables, even in the last place!
* `acceptsNilValue` informs the framework if `nil` should be accepted by the pattern. For example, `1 + '5'` with the previous example (`Double + Double`) would not match. But, if the `acceptsNilValue` is defined, then the block would trigger, with `{'lhs': 1, 'rhs': nil}`, so you can decide by your own logic what to do in this case.
* Finally, the `map` block can be used to further transform the value of your `Variable` before calling the block on the `Pattern`. Since map is a trailing closure, it's quite easy to add. For example, `Variable<Int>("example") { Double($0) }` would recognise only `Int` values, but would transform them to `Double` instances when providing them in the `variables` dictionary. This map can also return `nil` values but depends on your logic if you want to accept them or not. Side note: the previous map generates a `Variable<Double>` kind of variable instance.
### Specialised elements
#### Template Variable
By default, `Variable` instances use typed interpreters to evaluate their value. Sometimes though, they should be processed with the template interpreter. A good example is the `if` statement:
```swift
Pattern(Keyword("{%") + Keyword("if") + Variable<Bool>("condition") + Keyword("%}") + TemplateVariable("body") + Keyword("{% endif %}")) { variables, interpreter, _ in
guard let condition = variables["condition"] as? Bool, let body = variables["body"] as? String else { return nil }
if condition {
return body
}
return nil
}
```
This statement has two semantically different kinds of variable, but they both are just placeholders. The first (`condition`) is an interpreted variable, which at the end returns a `Boolean` value.
The second one is a bit different; it should not be evaluated the same way as `condition`. We need to further evaluate the enclosed template, that's why this variable
1. Should not be interpreted
2. Should be evaluated using the template interpreter, not the typed interpreter
That's why there's a subclass called `TemplateVariable`, which forces these two options when initialised. It DOES evaluate its content but uses the template interpreter to do so.
A quick example: `Header ... {% if x > 0 %}Number of results: {{ x }} {% endif %} ... Footer`
Here, `x > 0` is a `Boolean` expression, but the body between the `if`, and `endif` tags is a template, such as the whole expression.
#### Open & Close Keyword
`if` statements are quite common in templates. They are often chained and embedded in each other. Embedding is a nasty problem of interpreters, as `{% if %}a{% if %}b{% endif %}c{% endif %}` would logically be evaluated with `{% if %}b{% endif %}` first, and the rest afterwards.
But, an algorithm, by default, would interpret things linearly, disregarding the semantics: `{% if %}a{% if %}b{% endif %}` would be the match for the first if statement, with a totally invalid `a{% if %}b` data.
This, of course, needs to be solved, but it's not that easy as it first looks! Some edge cases would not work unless we somehow try to connect them together. For this reason, I added two special elements: `OpenKeyword` and `CloseKeyword`. These work exactly the same way as normal `Keyword`s do, but add a bit more semantics to the framework: these two should be connected together, and therefore embedding them should not be a problem as they come in pairs.
The previous `if` statement, now with an `else` block should - correctly - look like this:
```swift
Pattern(OpenKeyword("{% "if") + Variable<Bool>("condition") + Keyword("%}") + TemplateVariable("body") + Keyword("{% else %}") + TemplateVariable("else") + CloseKeyword("{% endif %}")) { variables, interpreter, _ in
guard let condition = variables["condition"] as? Bool, let body = variables["body"] as? String else { return nil }
if condition {
return body
} else {
return variables["else"] as? String
}
}
```
By using the `OpenKeyword` and `CloseKeyword` types, these become connected, so embedding `if` statements in a template shouldn't be a problem.
Similarly, this works for the `print` statement from an earlier example:
```swift
Pattern(OpenKeyword("{{") + Variable<Any>("body") + CloseKeyword("}}")) { variables, interpreter, _ in
guard let body = variables["body"] else { return nil }
return interpreter.typedInterpreter.print(body)
}
```
## Evaluation
The evaluation of the templates happens with the `evaluate` function on the interpreter:
```swift
template.evaluate("{{ 1 + 2 }}")
```
The result of the evaluation - in case of templates - is always a `String`. In the result you shouldn't see any template elements, because they were recognised, processed, and replaced during the evaluation by the interpreter.
### Context
You can also pass contextual values, which - for now - equal to variables.
```swift
template.evaluate("{{ 1 + var }}", context: Context(variables: ["var": 2]))
```
The reason that the variables are encapsulated in a context is that context is a class, while variables are mutable `var` struct properties on that object. With this construction the context reference can be passed around to multiple interpreter instances, but keeps the copy-on-write (🐮) behaviour of the modification.
Context defined during the initialisation apply to every evaluation performed with the given interpreter, while the ones passed to the `evaluate` method only apply to that specific expression instance.
If some patterns modify the context, they have the option to modify the general context (for long term settings), or the local one (for example, the interation variable of a `for` loop).
### Order of statements define precedence
The earlier a pattern is represent in the array of `statements`, the higher precedence it gets.
Practically, if there is an `if` statement and an `if-else` one, the `if-else` should be defined earlier, because both are going to match the following expression:
`{% if x < 0 %}A{% else %}B{% endif %}`, but if `if` goes first, then the output - and the `body` of the `if` statement - is going to be processed as `A{% else %}B`.
Typically, parentheses and richer type of expressions should go earlier in the array.
================================================
FILE: Documentation/Tips & Tricks.md
================================================
# Tips & Tricks
The following sections provide handy Tips and Tricks to help you effectively build up your own interpreter using custom operators and data types.
## Get inspired by checking out the examples
There are quite a few operators and data types available in the [TemplateLanguage Example](https://github.com/tevelee/Eval/blob/master/Examples/TemplateExample/Sources/TemplateExample/TemplateExample.swift#L114-L150) project, under the StandardLibrary class
Also, there are quite a few expressions available [in some of the unit tests](https://github.com/tevelee/Eval/blob/master/Tests/EvalTests/IntegrationTests/InterpreterTests.swift#L47-L86) as well.
## Use helper functions to define operators
It's a lot readable to define operators in a one-liner expression, rather than using long patterns:
```swift
infixOperator("+") { (lhs: String, rhs: String) in lhs + rhs }
```
```swift
suffixOperator("is odd") { (value: Double) in Int(value) % 2 == 1 }
```
```swift
prefixOperator("!") { (value: Bool) in !value }
```
You can find a few helpers [in the examples](https://github.com/tevelee/Eval/tree/master/Examples/TemplateExample/Sources/TemplateExample/TemplateExample.swift#L331-L412). Feel free to use them!
## Be mindful about precedence
#### Template expressions
The earlier a pattern is represent in the array of `statements`, the higher precedence it gets.
Practically, if there is an `if` statement and an `if-else` one, the `if-else` should be defined earlier, because both are going to match the following expression:
`{% if x < 0 %}A{% else %}B{% endif %}`, but if `if` goes first, then the output - and the `body` of the `if` statement - is going to be processed as `A{% else %}B`.
Typically, parentheses and richer type of expressions should go earlier in the array.
#### Typed expressions
The earlier a pattern is represent in the array of `functions`, the higher precedence it gets.
Practically, if there is an addition function and a multiplication one, the multiplication should be defined earlier (as it has higher precedence), because both are going to match the following expression:
`1 * 2 + 3`, but if addition goes first, then the evaluation would process `1 * 2` on `lhs` and `3` on `rhs`, which - of course - is incorrect.
Typically, parentheses and higher precedence operators should go earlier in the array.
## Use Any for generics: `Variable<Any>`
If you are not sure about the allowed input type of your expressions, or you just want to defer that decision until your match is ran and your hit the block in the pattern, feel free to use `Variable<Any>("name")` in your patterns.
It makes life a lot easier, than definig functions for each type.
## Use map on `Variable`s for pre-filtering
Before processing Variable values, there is an option to pre-filter or modify them before it hits the match block.
Examples include data type conversion and other types of validation.
## Use `OpenKeyword` and `CloseKeyword` for embedding parentheses
Embedding is a common issue with interpreters and compilers. In order to provide some extra semantics to the engine, please use the `OpenKeyword("[")` and `OpenKeyword("]")` options, when defining `Keyword`s that come in pairs.
## Share context between `StringTemplateInterpreter` and `TypedInterpreter`
If you use template interpreters, they need a typed interpreter to hold. Both interpreters have `context` variables, so if you are not being careful enough, it can cause headaches.
Since `Context` is a class, its reference can be passed around and used in multiple places.
The reason that the variables are encapsulated in a context is that context is a class, while variables are mutable `var` struct properties on that object. With this construction the context reference can be passed around to multiple interpreter instances, but keeps the copy-on-write (🐮) behaviour of the modification.
Context defined during the initialisation apply to every evaluation performed with the given interpreter, while the ones passed to the `evaluate` method only apply to that specific expression instance.
## Define constants in `Literal`s
The frameworks allows multiple ways to express static strings and convert them.
I believe the best place to put constants are in the `Literal`s of `DataType`s.
Use the `Literal("YES", convertsTo: true)` `Literal` initialiser for easy definition.
The `convertsTo` parameter of `Literal`s are `autoclosure` parameters, which means, that they are going to be processed lazily.
```swift
Literal("now", convertsTo: Date())
```
The `now` string is going to be expressed as the current timestamp at the time of the evaluation, not the time of the initialisation.
## Map any function signatures from Swift, dynamically
The framework is really lightweight and not really restrictive in regards of how to parse your expressions. Free your mind, and do stuff dynamically.
```swift
Function(Variable<Any>("lhs") + Keyword(".") + Variable<String>("rhs", interpreted: false)) { (arguments,_,_) -> Double? in
if let lhs = arguments["lhs"] as? NSObjectProtocol,
let rhs = arguments["rhs"] as? String,
let result = lhs.perform(Selector(rhs)) {
return Double(Int(bitPattern: result.toOpaque()))
}
return nil
}
])
```
Perform any method call of any type and maybe process their output as well. It's not the safest way to go with it, but this is just an example.
This opens up the way of running almost any arbitrary code on Apple platforms, from any backend. But, this does it in a very controlled way, as you must define a set of data types and functions that apply, unless you call them dynamically at runtime.
## Experiment with your expressions!
It's quite easy to add new operators, functions, and data types. I suggest not to think about them too long, just dare to experpiment with them, what's possible and what is not.
You can always add new types or functions if you need extra functionality. The options are practically endless!
## Debugging tips
#### If an expression haven't been matched
* It's common, that some validation caught the value
* Print your expressions or put breakpoints into the affected match blocks or variable map blocks
#### If you see weird output
* Play with the order of the newly added opeartions.
* Incorrect precedence can turn expressions upside down
The framework is still in an early stage, so debugging helpers will follow in upcoming releases. Please stay tuned!
## Validate your expressions before putting them out in production code
Not every expression work out of the box as you might expect. Operators and functions depend on each other, especially in terms of precedence. If one pattern was recognised before the other one, your code might not run as you expected.
Pro Tip: Write unit tests to validate expressions. Feel free to use `as!` operator to force-cast the result expressions in tests, but only in tests. It's not a problem is tests crash, you can fix it right away, but it's not okay in production.
================================================
FILE: Eval.playground/Contents.swift
================================================
//: Playground - noun: a place where people can play
import Foundation
import Eval
let context = InterpreterContext()
let interpreter = TypedInterpreter(dataTypes: [numberDataType, stringDataType, arrayDataType, booleanDataType, dateDataType],
functions: [parentheses, multipication, addition, lessThan],
context: context)
let template = TemplateInterpreter(statements: [ifStatement, printStatement],
interpreter: interpreter,
context: context)
interpreter.evaluate("2 + 3 * 4")
template.evaluate("{% if 10 < 21 %}Hello{% endif %} {{ name }}!", context: InterpreterContext(variables: ["name": "Eval"]))
================================================
FILE: Eval.playground/Sources/Helpers.swift
================================================
import Foundation
import Eval
public func infixOperator<A,B,T>(_ symbol: String, body: @escaping (A, B) -> T) -> Function<T?> {
return Function([Variable<A>("lhs", shortest: true), Keyword(symbol), Variable<B>("rhs", shortest: false)]) { arguments,_,_ in
guard let lhs = arguments["lhs"] as? A, let rhs = arguments["rhs"] as? B else { return nil }
return body(lhs, rhs)
}
}
public func prefixOperator<A,T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T?> {
return Function([Keyword(symbol), Variable<A>("value", shortest: false)]) { arguments,_,_ in
guard let value = arguments["value"] as? A else { return nil }
return body(value)
}
}
public func suffixOperator<A,T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T?> {
return Function([Variable<A>("value", shortest: true), Keyword(symbol)]) { arguments,_,_ in
guard let value = arguments["value"] as? A else { return nil }
return body(value)
}
}
public func function<T>(_ name: String, body: @escaping ([Any]) -> T?) -> Function<T> {
return Function([Keyword(name), Keyword("("), Variable<String>("arguments", shortest: true, interpreted: false), Keyword(")")]) { variables, interpreter, _ in
guard let arguments = variables["arguments"] as? String else { return nil }
let interpretedArguments = arguments.split(separator: ",").flatMap { interpreter.evaluate(String($0).trimmingCharacters(in: .whitespacesAndNewlines)) }
return body(interpretedArguments)
}
}
public func functionWithNamedParameters<T>(_ name: String, body: @escaping ([String: Any]) -> T?) -> Function<T> {
return Function([Keyword(name), Keyword("("), Variable<String>("arguments", shortest: true, interpreted: false), Keyword(")")]) { variables, interpreter, _ in
guard let arguments = variables["arguments"] as? String else { return nil }
var interpretedArguments: [String: Any] = [:]
for argument in arguments.split(separator: ",") {
let parts = String(argument).trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=")
if let key = parts.first, let value = parts.last {
interpretedArguments[String(key)] = interpreter.evaluate(String(value))
}
}
return body(interpretedArguments)
}
}
public func objectFunction<O,T>(_ name: String, body: @escaping (O) -> T?) -> Function<T> {
return Function([Variable<O>("lhs", shortest: true), Keyword("."), Variable<String>("rhs", shortest: false, interpreted: false) { value,_ in
guard let value = value as? String, value == name else { return nil }
return value
}]) { variables, interpreter, _ in
guard let object = variables["lhs"] as? O, variables["rhs"] != nil else { return nil }
return body(object)
}
}
public func objectFunctionWithParameters<O,T>(_ name: String, body: @escaping (O, [Any]) -> T?) -> Function<T> {
return Function([Variable<O>("lhs", shortest: true), Keyword("."), Variable<String>("rhs", interpreted: false) { value,_ in
guard let value = value as? String, value == name else { return nil }
return value
}, Keyword("("), Variable<String>("arguments", interpreted: false), Keyword(")")]) { variables, interpreter, _ in
guard let object = variables["lhs"] as? O, variables["rhs"] != nil, let arguments = variables["arguments"] as? String else { return nil }
let interpretedArguments = arguments.split(separator: ",").flatMap { interpreter.evaluate(String($0).trimmingCharacters(in: .whitespacesAndNewlines)) }
return body(object, interpretedArguments)
}
}
public func objectFunctionWithNamedParameters<O,T>(_ name: String, body: @escaping (O, [String: Any]) -> T?) -> Function<T> {
return Function([Variable<O>("lhs", shortest: true), Keyword("."), Variable<String>("rhs", interpreted: false) { value,_ in
guard let value = value as? String, value == name else { return nil }
return value
}, Keyword("("), Variable<String>("arguments", interpreted: false), Keyword(")")]) { variables, interpreter, _ in
guard let object = variables["lhs"] as? O, variables["rhs"] != nil, let arguments = variables["arguments"] as? String else { return nil }
var interpretedArguments: [String: Any] = [:]
for argument in arguments.split(separator: ",") {
let parts = String(argument).trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=")
if let key = parts.first, let value = parts.last {
interpretedArguments[String(key)] = interpreter.evaluate(String(value))
}
}
return body(object, interpretedArguments)
}
}
================================================
FILE: Eval.playground/Sources/TypesAndFunctions.swift
================================================
import Foundation
import Eval
//MARK: Double
public let numberDataType = DataType(type: Double.self, literals:[
Literal { Double($0.value) },
Literal("pi", convertsTo: Double.pi)
]) { value, _ in String(describing: value) }
//MARK: Bool
public let booleanDataType = DataType(type: Bool.self, literals: [
Literal("false", convertsTo: false),
Literal("true", convertsTo: true)
]) { $0.value ? "true" : "false" }
//MARK: String
let singleQuotesLiteral = Literal { (input, _) -> String? in
guard let first = input.first, let last = input.last, first == last, first == "'" else { return nil }
let trimmed = input.trimmingCharacters(in: CharacterSet(charactersIn: "'"))
return trimmed.contains("'") ? nil : trimmed
}
public let stringDataType = DataType(type: String.self, literals: [singleQuotesLiteral]) { $0.value }
//MARK: Date
public let dateDataType = DataType(type: Date.self, literals: [Literal<Date>("now", convertsTo: Date())]) {
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.string(from: $0)
}
//MARK: Array
let arrayLiteral = Literal { (input, interpreter) -> [CustomStringConvertible]? in
guard let first = input.first, let last = input.last, first == "[", last == "]" else { return nil }
return input
.trimmingCharacters(in: CharacterSet(charactersIn: "[]"))
.split(separator: ",")
.map{ $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.map{ interpreter.evaluate(String($0)) as? CustomStringConvertible ?? String($0) }
}
public let arrayDataType = DataType(type: [CustomStringConvertible].self, literals: [arrayLiteral]) { $0.value.map{ $0.description }.joined(separator: ",") }
//MARK: Operators
public let max = objectFunction("max") { (object: [Double]) -> Double? in object.max() }
public let min = objectFunction("min") { (object: [Double]) -> Double? in object.min() }
public let formatDate = objectFunctionWithParameters("format") { (object: Date, arguments: [Any]) -> String? in
guard let format = arguments.first as? String else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .gregorian)
dateFormatter.dateFormat = format
return dateFormatter.string(from: object)
}
public let not = prefixOperator("!") { (value: Bool) in !value }
public let dateFactory = function("Date") { (arguments: [Any]) -> Date? in
guard let arguments = arguments as? [Double], arguments.count >= 3 else { return nil }
var components = DateComponents()
components.calendar = Calendar(identifier: .gregorian)
components.year = Int(arguments[0])
components.month = Int(arguments[1])
components.day = Int(arguments[2])
components.hour = arguments.count > 3 ? Int(arguments[3]) : 0
components.minute = arguments.count > 4 ? Int(arguments[4]) : 0
components.second = arguments.count > 5 ? Int(arguments[5]) : 0
return components.date
}
public let parentheses = Function([Keyword("("), Variable<Any>("body"), Keyword(")")]) { $0.variables["body"] }
public let addition = infixOperator("+") { (lhs: Double, rhs: Double) in lhs + rhs }
public let multipication = infixOperator("*") { (lhs: Double, rhs: Double) in lhs * rhs }
public let concat = infixOperator("+") { (lhs: String, rhs: String) in lhs + rhs }
public let inNumberArray = infixOperator("in") { (lhs: Double, rhs: [Double]) in rhs.contains(lhs) }
public let inStringArray = infixOperator("in") { (lhs: String, rhs: [String]) in rhs.contains(lhs) }
public let range = infixOperator("...") { (lhs: Double, rhs: Double) in CountableClosedRange(uncheckedBounds: (lower: Int(lhs), upper: Int(rhs))).map { Double($0) } }
public let prefix = infixOperator("starts with") { (lhs: String, rhs: String) in lhs.hasPrefix(lhs) }
public let isOdd = suffixOperator("is odd") { (value: Double) in Int(value) % 2 == 1 }
public let isEven = suffixOperator("is even") { (value: Double) in Int(value) % 2 == 0 }
public let lessThan = infixOperator("<") { (lhs: Double, rhs: Double) in lhs < rhs }
public let greaterThan = infixOperator(">") { (lhs: Double, rhs: Double) in lhs > rhs }
public let equals = infixOperator("==") { (lhs: Double, rhs: Double) in lhs == rhs }
//MARK: Template elements
public let ifStatement = Matcher([Keyword("{%"), Keyword("if"), Variable<Bool>("condition"), Keyword("%}"), TemplateVariable("body"), Keyword("{%"), Keyword("endif"), Keyword("%}")]) { (variables, interpreter: StringTemplateInterpreter, _) -> String? in
guard let condition = variables["condition"] as? Bool, let body = variables["body"] as? String else { return nil }
if condition {
return body
}
return nil
}
public let printStatement = Matcher([Keyword("{{"), Variable<Any>("body"), Keyword("}}")]) { (variables, interpreter: StringTemplateInterpreter, _) -> String? in
guard let body = variables["body"] else { return nil }
return interpreter.typedInterpreter.print(body)
}
================================================
FILE: Eval.playground/contents.xcplayground
================================================
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos' display-mode='raw'>
<timeline fileName='timeline.xctimeline'/>
</playground>
================================================
FILE: Eval.playground/playground.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: Eval.playground/xcshareddata/xcschemes/Playground.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::Eval"
BuildableName = "Eval.framework"
BlueprintName = "Eval"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::Eval"
BuildableName = "Eval.framework"
BlueprintName = "Eval"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::Eval"
BuildableName = "Eval.framework"
BlueprintName = "Eval"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: Eval.podspec
================================================
Pod::Spec.new do |s|
s.name = "Eval"
s.version = "1.5.0"
s.summary = "Eval is a lightweight interpreter framework written in Swift, evaluating expressions at runtime"
s.description = <<-DESC
Eval is a lightweight interpreter framework written in Swift, for 📱iOS, 🖥 macOS, and 🐧Linux platforms.
It evaluates expressions at runtime, with operators and data types you define.
DESC
s.homepage = "https://tevelee.github.io/Eval/"
s.license = { :type => "Apache 2.0", :file => "LICENSE.txt" }
s.author = { "Laszlo Teveli" => "tevelee@gmail.com" }
s.social_media_url = "http://twitter.com/tevelee"
s.source = { :git => "https://github.com/tevelee/Eval.git", :tag => "#{s.version}" }
s.source_files = "Sources/**/*.{h,swift}"
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
s.watchos.deployment_target = "2.0"
s.tvos.deployment_target = "9.0"
end
================================================
FILE: Eval.xcodeproj/EvalTests_Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
================================================
FILE: Eval.xcodeproj/Eval_Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
================================================
FILE: Eval.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXAggregateTarget section */
"Eval::EvalPackageTests::ProductTarget" /* EvalPackageTests */ = {
isa = PBXAggregateTarget;
buildConfigurationList = OBJ_67 /* Build configuration list for PBXAggregateTarget "EvalPackageTests" */;
buildPhases = (
);
dependencies = (
OBJ_70 /* PBXTargetDependency */,
);
name = EvalPackageTests;
productName = EvalPackageTests;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
3AED32EA232D382D00FA2596 /* PerformanceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED32E9232D382D00FA2596 /* PerformanceTest.swift */; };
3AED32ED232D3D4D00FA2596 /* Suffix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AED32EB232D3D3500FA2596 /* Suffix.swift */; };
OBJ_51 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Common.swift */; };
OBJ_52 /* Elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Elements.swift */; };
OBJ_53 /* TemplateInterpreter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* TemplateInterpreter.swift */; };
OBJ_54 /* TypedInterpreter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* TypedInterpreter.swift */; };
OBJ_55 /* MatchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* MatchResult.swift */; };
OBJ_56 /* Matcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Matcher.swift */; };
OBJ_57 /* Pattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Pattern.swift */; };
OBJ_58 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Utils.swift */; };
OBJ_65 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
OBJ_76 /* InterpreterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* InterpreterTests.swift */; };
OBJ_77 /* TemplateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* TemplateTests.swift */; };
OBJ_78 /* DataTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* DataTypeTests.swift */; };
OBJ_79 /* FunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* FunctionTests.swift */; };
OBJ_80 /* InterpreterContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* InterpreterContextTests.swift */; };
OBJ_81 /* KeywordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* KeywordTests.swift */; };
OBJ_82 /* LiteralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* LiteralTests.swift */; };
OBJ_83 /* MatchResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* MatchResultTests.swift */; };
OBJ_84 /* MatchStatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* MatchStatementTests.swift */; };
OBJ_85 /* MatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* MatcherTests.swift */; };
OBJ_86 /* PatternTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* PatternTests.swift */; };
OBJ_87 /* TemplateInterpreterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* TemplateInterpreterTests.swift */; };
OBJ_88 /* TypedInterpreterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* TypedInterpreterTests.swift */; };
OBJ_89 /* UtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* UtilTests.swift */; };
OBJ_90 /* VariableProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* VariableProcessor.swift */; };
OBJ_91 /* VariableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* VariableTests.swift */; };
OBJ_92 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* Utils.swift */; };
OBJ_94 /* Eval.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Eval::Eval::Product" /* Eval.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
3A0B80062294367A008925A6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = "Eval::Eval";
remoteInfo = Eval;
};
3A0B80072294367B008925A6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = "Eval::EvalTests";
remoteInfo = EvalTests;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
3AED32E9232D382D00FA2596 /* PerformanceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTest.swift; sourceTree = "<group>"; };
3AED32EB232D3D3500FA2596 /* Suffix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Suffix.swift; sourceTree = "<group>"; };
"Eval::Eval::Product" /* Eval.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Eval.framework; sourceTree = BUILT_PRODUCTS_DIR; };
"Eval::EvalTests::Product" /* EvalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = EvalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
OBJ_10 /* Elements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Elements.swift; sourceTree = "<group>"; };
OBJ_11 /* TemplateInterpreter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateInterpreter.swift; sourceTree = "<group>"; };
OBJ_12 /* TypedInterpreter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedInterpreter.swift; sourceTree = "<group>"; };
OBJ_14 /* MatchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchResult.swift; sourceTree = "<group>"; };
OBJ_15 /* Matcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Matcher.swift; sourceTree = "<group>"; };
OBJ_16 /* Pattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pattern.swift; sourceTree = "<group>"; };
OBJ_17 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
OBJ_21 /* InterpreterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpreterTests.swift; sourceTree = "<group>"; };
OBJ_22 /* TemplateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateTests.swift; sourceTree = "<group>"; };
OBJ_24 /* DataTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypeTests.swift; sourceTree = "<group>"; };
OBJ_25 /* FunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FunctionTests.swift; sourceTree = "<group>"; };
OBJ_26 /* InterpreterContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpreterContextTests.swift; sourceTree = "<group>"; };
OBJ_27 /* KeywordTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordTests.swift; sourceTree = "<group>"; };
OBJ_28 /* LiteralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiteralTests.swift; sourceTree = "<group>"; };
OBJ_29 /* MatchResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchResultTests.swift; sourceTree = "<group>"; };
OBJ_30 /* MatchStatementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchStatementTests.swift; sourceTree = "<group>"; };
OBJ_31 /* MatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatcherTests.swift; sourceTree = "<group>"; };
OBJ_32 /* PatternTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatternTests.swift; sourceTree = "<group>"; };
OBJ_33 /* TemplateInterpreterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateInterpreterTests.swift; sourceTree = "<group>"; };
OBJ_34 /* TypedInterpreterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedInterpreterTests.swift; sourceTree = "<group>"; };
OBJ_35 /* UtilTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilTests.swift; sourceTree = "<group>"; };
OBJ_36 /* VariableProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariableProcessor.swift; sourceTree = "<group>"; };
OBJ_37 /* VariableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariableTests.swift; sourceTree = "<group>"; };
OBJ_38 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
OBJ_39 /* Documentation */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Documentation; sourceTree = SOURCE_ROOT; };
OBJ_40 /* Eval.xcworkspace */ = {isa = PBXFileReference; lastKnownFileType = wrapper.workspace; path = Eval.xcworkspace; sourceTree = SOURCE_ROOT; };
OBJ_41 /* Examples */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Examples; sourceTree = SOURCE_ROOT; };
OBJ_42 /* Scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Scripts; sourceTree = SOURCE_ROOT; };
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
OBJ_9 /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
OBJ_59 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
OBJ_93 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 0;
files = (
OBJ_94 /* Eval.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
OBJ_13 /* Utilities */ = {
isa = PBXGroup;
children = (
OBJ_14 /* MatchResult.swift */,
OBJ_15 /* Matcher.swift */,
OBJ_16 /* Pattern.swift */,
OBJ_17 /* Utils.swift */,
);
path = Utilities;
sourceTree = "<group>";
};
OBJ_18 /* Tests */ = {
isa = PBXGroup;
children = (
OBJ_19 /* EvalTests */,
);
name = Tests;
sourceTree = SOURCE_ROOT;
};
OBJ_19 /* EvalTests */ = {
isa = PBXGroup;
children = (
OBJ_20 /* IntegrationTests */,
OBJ_23 /* UnitTests */,
OBJ_38 /* Utils.swift */,
);
name = EvalTests;
path = Tests/EvalTests;
sourceTree = SOURCE_ROOT;
};
OBJ_20 /* IntegrationTests */ = {
isa = PBXGroup;
children = (
OBJ_21 /* InterpreterTests.swift */,
OBJ_22 /* TemplateTests.swift */,
3AED32E9232D382D00FA2596 /* PerformanceTest.swift */,
3AED32EB232D3D3500FA2596 /* Suffix.swift */,
);
path = IntegrationTests;
sourceTree = "<group>";
};
OBJ_23 /* UnitTests */ = {
isa = PBXGroup;
children = (
OBJ_24 /* DataTypeTests.swift */,
OBJ_25 /* FunctionTests.swift */,
OBJ_26 /* InterpreterContextTests.swift */,
OBJ_27 /* KeywordTests.swift */,
OBJ_28 /* LiteralTests.swift */,
OBJ_29 /* MatchResultTests.swift */,
OBJ_30 /* MatchStatementTests.swift */,
OBJ_31 /* MatcherTests.swift */,
OBJ_32 /* PatternTests.swift */,
OBJ_33 /* TemplateInterpreterTests.swift */,
OBJ_34 /* TypedInterpreterTests.swift */,
OBJ_35 /* UtilTests.swift */,
OBJ_36 /* VariableProcessor.swift */,
OBJ_37 /* VariableTests.swift */,
);
path = UnitTests;
sourceTree = "<group>";
};
OBJ_43 /* Products */ = {
isa = PBXGroup;
children = (
"Eval::EvalTests::Product" /* EvalTests.xctest */,
"Eval::Eval::Product" /* Eval.framework */,
);
name = Products;
sourceTree = BUILT_PRODUCTS_DIR;
};
OBJ_5 = {
isa = PBXGroup;
children = (
OBJ_6 /* Package.swift */,
OBJ_7 /* Sources */,
OBJ_18 /* Tests */,
OBJ_39 /* Documentation */,
OBJ_40 /* Eval.xcworkspace */,
OBJ_41 /* Examples */,
OBJ_42 /* Scripts */,
OBJ_43 /* Products */,
);
sourceTree = "<group>";
};
OBJ_7 /* Sources */ = {
isa = PBXGroup;
children = (
OBJ_8 /* Eval */,
);
name = Sources;
sourceTree = SOURCE_ROOT;
};
OBJ_8 /* Eval */ = {
isa = PBXGroup;
children = (
OBJ_9 /* Common.swift */,
OBJ_10 /* Elements.swift */,
OBJ_11 /* TemplateInterpreter.swift */,
OBJ_12 /* TypedInterpreter.swift */,
OBJ_13 /* Utilities */,
);
name = Eval;
path = Sources/Eval;
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
"Eval::Eval" /* Eval */ = {
isa = PBXNativeTarget;
buildConfigurationList = OBJ_47 /* Build configuration list for PBXNativeTarget "Eval" */;
buildPhases = (
OBJ_50 /* Sources */,
OBJ_59 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Eval;
productName = Eval;
productReference = "Eval::Eval::Product" /* Eval.framework */;
productType = "com.apple.product-type.framework";
};
"Eval::EvalTests" /* EvalTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = OBJ_72 /* Build configuration list for PBXNativeTarget "EvalTests" */;
buildPhases = (
OBJ_75 /* Sources */,
OBJ_93 /* Frameworks */,
);
buildRules = (
);
dependencies = (
OBJ_95 /* PBXTargetDependency */,
);
name = EvalTests;
productName = EvalTests;
productReference = "Eval::EvalTests::Product" /* EvalTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
"Eval::SwiftPMPackageDescription" /* EvalPackageDescription */ = {
isa = PBXNativeTarget;
buildConfigurationList = OBJ_61 /* Build configuration list for PBXNativeTarget "EvalPackageDescription" */;
buildPhases = (
OBJ_64 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = EvalPackageDescription;
productName = EvalPackageDescription;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
OBJ_1 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
TargetAttributes = {
"Eval::EvalTests" = {
LastSwiftMigration = 1020;
};
};
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Eval" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = OBJ_5;
productRefGroup = OBJ_43 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
"Eval::Eval" /* Eval */,
"Eval::SwiftPMPackageDescription" /* EvalPackageDescription */,
"Eval::EvalPackageTests::ProductTarget" /* EvalPackageTests */,
"Eval::EvalTests" /* EvalTests */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
OBJ_50 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 0;
files = (
OBJ_51 /* Common.swift in Sources */,
OBJ_52 /* Elements.swift in Sources */,
OBJ_53 /* TemplateInterpreter.swift in Sources */,
OBJ_54 /* TypedInterpreter.swift in Sources */,
OBJ_55 /* MatchResult.swift in Sources */,
OBJ_56 /* Matcher.swift in Sources */,
OBJ_57 /* Pattern.swift in Sources */,
OBJ_58 /* Utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
OBJ_64 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 0;
files = (
OBJ_65 /* Package.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
OBJ_75 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 0;
files = (
OBJ_76 /* InterpreterTests.swift in Sources */,
OBJ_77 /* TemplateTests.swift in Sources */,
OBJ_78 /* DataTypeTests.swift in Sources */,
3AED32ED232D3D4D00FA2596 /* Suffix.swift in Sources */,
OBJ_79 /* FunctionTests.swift in Sources */,
OBJ_80 /* InterpreterContextTests.swift in Sources */,
OBJ_81 /* KeywordTests.swift in Sources */,
3AED32EA232D382D00FA2596 /* PerformanceTest.swift in Sources */,
OBJ_82 /* LiteralTests.swift in Sources */,
OBJ_83 /* MatchResultTests.swift in Sources */,
OBJ_84 /* MatchStatementTests.swift in Sources */,
OBJ_85 /* MatcherTests.swift in Sources */,
OBJ_86 /* PatternTests.swift in Sources */,
OBJ_87 /* TemplateInterpreterTests.swift in Sources */,
OBJ_88 /* TypedInterpreterTests.swift in Sources */,
OBJ_89 /* UtilTests.swift in Sources */,
OBJ_90 /* VariableProcessor.swift in Sources */,
OBJ_91 /* VariableTests.swift in Sources */,
OBJ_92 /* Utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
OBJ_70 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = "Eval::EvalTests" /* EvalTests */;
targetProxy = 3A0B80072294367B008925A6 /* PBXContainerItemProxy */;
};
OBJ_95 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = "Eval::Eval" /* Eval */;
targetProxy = 3A0B80062294367A008925A6 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
OBJ_3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-DXcode";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE DEBUG";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
USE_HEADERMAP = NO;
};
name = Debug;
};
OBJ_4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = s;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_SWIFT_FLAGS = "-DXcode";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
USE_HEADERMAP = NO;
};
name = Release;
};
OBJ_48 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Eval.xcodeproj/Eval_Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = Eval;
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 5.0;
TARGET_NAME = Eval;
};
name = Debug;
};
OBJ_49 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Eval.xcodeproj/Eval_Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = Eval;
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 5.0;
TARGET_NAME = Eval;
};
name = Release;
};
OBJ_62 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
LD = /usr/bin/true;
OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
OBJ_63 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
LD = /usr/bin/true;
OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
SWIFT_VERSION = 5.0;
};
name = Release;
};
OBJ_68 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Debug;
};
OBJ_69 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Release;
};
OBJ_73 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ENABLE_MODULES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Eval.xcodeproj/EvalTests_Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 5.0;
TARGET_NAME = EvalTests;
};
name = Debug;
};
OBJ_74 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ENABLE_MODULES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
);
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Eval.xcodeproj/EvalTests_Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
SWIFT_VERSION = 5.0;
TARGET_NAME = EvalTests;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
OBJ_2 /* Build configuration list for PBXProject "Eval" */ = {
isa = XCConfigurationList;
buildConfigurations = (
OBJ_3 /* Debug */,
OBJ_4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
OBJ_47 /* Build configuration list for PBXNativeTarget "Eval" */ = {
isa = XCConfigurationList;
buildConfigurations = (
OBJ_48 /* Debug */,
OBJ_49 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
OBJ_61 /* Build configuration list for PBXNativeTarget "EvalPackageDescription" */ = {
isa = XCConfigurationList;
buildConfigurations = (
OBJ_62 /* Debug */,
OBJ_63 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
OBJ_67 /* Build configuration list for PBXAggregateTarget "EvalPackageTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
OBJ_68 /* Debug */,
OBJ_69 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
OBJ_72 /* Build configuration list for PBXNativeTarget "EvalTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
OBJ_73 /* Debug */,
OBJ_74 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = OBJ_1 /* Project object */;
}
================================================
FILE: Eval.xcodeproj/xcshareddata/xcschemes/Eval-Package.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::Eval"
BuildableName = "Eval.framework"
BlueprintName = "Eval"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::EvalTests"
BuildableName = "EvalTests.xctest"
BlueprintName = "EvalTests"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Eval::Eval"
BuildableName = "Eval.framework"
BlueprintName = "Eval"
ReferencedContainer = "container:Eval.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: Eval.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Eval-Package.xcscheme</key>
<dict></dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict></dict>
</dict>
</plist>
================================================
FILE: Eval.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Eval.playground">
</FileRef>
<FileRef
location = "container:Eval.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: Eval.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: Examples/.swiftlint.yml
================================================
disabled_rules:
- file_header
================================================
FILE: Examples/AttributedStringExample/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
================================================
FILE: Examples/AttributedStringExample/Package.swift
================================================
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "AttributedStringExample",
products: [
.library(
name: "AttributedStringExample",
targets: ["AttributedStringExample"])
],
dependencies: [
.package(url: "../../", from: "1.4.0")
],
targets: [
.target(
name: "AttributedStringExample",
dependencies: ["Eval"]),
.testTarget(
name: "AttributedStringExampleTests",
dependencies: ["AttributedStringExample"])
]
)
================================================
FILE: Examples/AttributedStringExample/README.md
================================================
# TemplateExample
A description of this package.
================================================
FILE: Examples/AttributedStringExample/Sources/AttributedStringExample/TemplateExample.swift
================================================
import AppKit
@_exported import Eval
import Foundation
@_exported import class Eval.Pattern
// swiftlint:disable:next type_name
public class AttributedStringTemplateInterpreter: TemplateInterpreter<NSAttributedString> {
typealias EvaluatedType = NSAttributedString
override public func evaluate(_ expression: String, context: Context = Context()) -> NSAttributedString {
return evaluate(expression, context: context, reducer: (initialValue: NSAttributedString(), reduceValue: { existing, next in
existing.appending(next)
}, reduceCharacter: { existing, next in
existing.appending(NSAttributedString(string: String(next)))
}))
}
}
// swiftlint:disable:next type_name
public class AttributedStringInterpreter: EvaluatorWithLocalContext {
public typealias EvaluatedType = NSAttributedString
let interpreter: AttributedStringTemplateInterpreter
init() {
let context = Context()
let center = NSMutableParagraphStyle()
center.alignment = .center
interpreter = AttributedStringTemplateInterpreter(statements: [AttributedStringInterpreter.attributeMatcher(name: "bold", attributes: [.font: NSFont.boldSystemFont(ofSize: 12)]),
AttributedStringInterpreter.attributeMatcher(name: "red", attributes: [.foregroundColor: NSColor.red]),
AttributedStringInterpreter.attributeMatcher(name: "center", attributes: [.paragraphStyle: center])],
interpreter: TypedInterpreter(context: context),
context: context)
}
public func evaluate(_ expression: String) -> AttributedStringInterpreter.EvaluatedType {
return interpreter.evaluate(expression)
}
public func evaluate(_ expression: String, context: Context) -> AttributedStringInterpreter.EvaluatedType {
return interpreter.evaluate(expression, context: context)
}
static func attributeMatcher(name: String, attributes: [NSAttributedString.Key: Any]) -> Pattern<NSAttributedString, TemplateInterpreter<NSAttributedString>> {
return Pattern([OpenKeyword("<\(name)>"), GenericVariable<String, AttributedStringTemplateInterpreter>("body", options: .notInterpreted), CloseKeyword("</\(name)>")]) {
guard let body = $0.variables["body"] as? String else { return nil }
return NSAttributedString(string: body, attributes: attributes)
}
}
}
public extension NSAttributedString {
func appending(_ other: NSAttributedString) -> NSAttributedString {
let mutable = NSMutableAttributedString(attributedString: self)
mutable.append(other)
return mutable
}
}
================================================
FILE: Examples/AttributedStringExample/Tests/.swiftlint.yml
================================================
disabled_rules:
- force_cast
- force_try
- type_name
- file_header
- explicit_top_level_acl
================================================
FILE: Examples/AttributedStringExample/Tests/AttributedStringExampleTests/AttributedStringExampleTests.swift
================================================
@testable import AttributedStringExample
import Eval
import XCTest
class AttributedStringExampleTests: XCTestCase {
let interpreter: AttributedStringInterpreter = AttributedStringInterpreter()
func testExample() {
let interpreter = AttributedStringInterpreter()
XCTAssertEqual(interpreter.evaluate("<bold>Hello</bold>"), NSAttributedString(string: "Hello", attributes: [.font: NSFont.boldSystemFont(ofSize: 12)]))
XCTAssertEqual(interpreter.evaluate("It's <red>red</red>"), NSAttributedString(string: "It's ").appending(NSAttributedString(string: "red", attributes: [.foregroundColor: NSColor.red])))
let style = interpreter.evaluate("<center>Centered text</center>").attribute(.paragraphStyle, at: 0, effectiveRange: nil) as! NSParagraphStyle
XCTAssertEqual(style.alignment, .center)
}
}
================================================
FILE: Examples/AttributedStringExample/Tests/LinuxMain.swift
================================================
@testable import TemplateExampleTests
import XCTest
XCTMain([
testCase(TemplateExampleTests.allTests)
])
================================================
FILE: Examples/ColorParserExample/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
================================================
FILE: Examples/ColorParserExample/Package.swift
================================================
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "ColorParserExample",
products: [
.library(
name: "ColorParserExample",
targets: ["ColorParserExample"])
],
dependencies: [
.package(url: "../../", from: "1.4.0")
],
targets: [
.target(
name: "ColorParserExample",
dependencies: ["Eval"]),
.testTarget(
name: "ColorParserExampleTests",
dependencies: ["ColorParserExample"])
]
)
================================================
FILE: Examples/ColorParserExample/README.md
================================================
# TemplateExample
A description of this package.
================================================
FILE: Examples/ColorParserExample/Sources/ColorParserExample/ColorParserExample.swift
================================================
import AppKit
@_exported import Eval
@_exported import class Eval.Pattern
import Foundation
public class ColorParser: EvaluatorWithLocalContext {
let interpreter: TypedInterpreter
init() {
interpreter = TypedInterpreter(dataTypes: [ColorParser.colorDataType()], functions: [ColorParser.mixFunction()])
}
static func colorDataType() -> DataType<NSColor> {
let hex = Literal<NSColor> {
guard $0.value.first == "#", $0.value.count == 7,
let red = Int($0.value[1...2], radix: 16),
let green = Int($0.value[3...4], radix: 16),
let blue = Int($0.value[5...6], radix: 16) else { return nil }
return NSColor(calibratedRed: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: 1)
}
let red = Literal("red", convertsTo: NSColor.red)
return DataType(type: NSColor.self, literals: [hex, red]) { $0.value.description }
}
static func mixFunction() -> Function<NSColor> {
return Function([Variable<NSColor>("lhs"), Keyword("mixed with"), Variable<NSColor>("rhs")]) {
guard let lhs = $0.variables["lhs"] as? NSColor, let rhs = $0.variables["rhs"] as? NSColor else { return nil }
return lhs.blend(with: rhs)
}
}
public func evaluate(_ expression: String) -> Any? {
return interpreter.evaluate(expression)
}
public func evaluate(_ expression: String, context: Context) -> Any? {
return interpreter.evaluate(expression, context: context)
}
}
extension String {
subscript (range: CountableClosedRange<Int>) -> Substring {
return self[index(startIndex, offsetBy: range.lowerBound) ..< index(startIndex, offsetBy: range.upperBound)]
}
}
extension NSColor {
func blend(with other: NSColor, using factor: CGFloat = 0.5) -> NSColor {
let inverseFactor = 1.0 - factor
var leftRed: CGFloat = 0
var leftGreen: CGFloat = 0
var leftBlue: CGFloat = 0
var leftAlpha: CGFloat = 0
getRed(&leftRed, green: &leftGreen, blue: &leftBlue, alpha: &leftAlpha)
var rightRed: CGFloat = 0
var rightGreen: CGFloat = 0
var rightBlue: CGFloat = 0
var rightAlpha: CGFloat = 0
other.getRed(&rightRed, green: &rightGreen, blue: &rightBlue, alpha: &rightAlpha)
return NSColor(calibratedRed: leftRed * factor + rightRed * inverseFactor,
green: leftGreen * factor + rightGreen * inverseFactor,
blue: leftBlue * factor + rightBlue * inverseFactor,
alpha: leftAlpha * factor + rightAlpha * inverseFactor)
}
}
================================================
FILE: Examples/ColorParserExample/Tests/.swiftlint.yml
================================================
disabled_rules:
- force_cast
- force_try
- type_name
- file_header
- explicit_top_level_acl
================================================
FILE: Examples/ColorParserExample/Tests/ColorParserExampleTests/ColorParserExampleTests.swift
================================================
@testable import ColorParserExample
import Eval
import XCTest
class ColorParserExampleTests: XCTestCase {
let colorParser: ColorParser = ColorParser()
func testExample() {
XCTAssertEqual(colorParser.evaluate("#00ff00") as! NSColor, NSColor(calibratedRed: 0, green: 1, blue: 0, alpha: 1))
XCTAssertEqual(colorParser.evaluate("red") as! NSColor, .red)
XCTAssertEqual(colorParser.evaluate("#ff0000 mixed with #0000ff") as! NSColor, NSColor(calibratedRed: 0.5, green: 0, blue: 0.5, alpha: 1))
}
}
================================================
FILE: Examples/ColorParserExample/Tests/LinuxMain.swift
================================================
@testable import TemplateExampleTests
import XCTest
XCTMain([
testCase(TemplateExampleTests.allTests)
])
================================================
FILE: Examples/TemplateExample/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
================================================
FILE: Examples/TemplateExample/Package.swift
================================================
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "TemplateExample",
products: [
.library(
name: "TemplateExample",
targets: ["TemplateExample"])
],
dependencies: [
.package(url: "../../", from: "1.4.0")
],
targets: [
.target(
name: "TemplateExample",
dependencies: ["Eval"]),
.testTarget(
name: "TemplateExampleTests",
dependencies: ["TemplateExample"])
]
)
================================================
FILE: Examples/TemplateExample/README.md
================================================
# TemplateExample
A description of this package.
================================================
FILE: Examples/TemplateExample/Sources/TemplateExample/TemplateExample.swift
================================================
@_exported import Eval
@_exported import class Eval.Pattern
import Foundation
public class TemplateLanguage: EvaluatorWithLocalContext {
public typealias EvaluatedType = String
let language: StringTemplateInterpreter
let macroReplacer: StringTemplateInterpreter
init(dataTypes: [DataTypeProtocol] = StandardLibrary.dataTypes,
functions: [FunctionProtocol] = StandardLibrary.functions,
templates: [Pattern<String, TemplateInterpreter<String>>] = TemplateLibrary.templates,
context: Context = Context()) {
TemplateLanguage.preprocess(context)
let interpreter = TypedInterpreter(dataTypes: dataTypes, functions: functions, context: context)
let language = StringTemplateInterpreter(statements: templates, interpreter: interpreter, context: context)
self.language = language
let block = Pattern<String, TemplateInterpreter<String>>([OpenKeyword("{{{"), TemplateVariable("name", options: .notInterpreted), CloseKeyword("}}}")]) {
guard let name = $0.variables["name"] as? String else { return nil }
return language.context.blocks[name]?.last?(language.context)
}
macroReplacer = StringTemplateInterpreter(statements: [block])
}
public func evaluate(_ expression: String) -> String {
return evaluate(expression, context: Context())
}
public func evaluate(_ expression: String, context: Context) -> String {
TemplateLanguage.preprocess(context)
let input = replaceWhitespaces(expression)
let result = language.evaluate(input, context: context)
let finalResult = macroReplacer.evaluate(result)
return finalResult.contains(TemplateLibrary.tagPrefix) ? language.evaluate(finalResult, context: context) : finalResult
}
public func evaluate(template from: URL) throws -> String {
let expression = try String(contentsOf: from)
return evaluate(expression)
}
public func evaluate(template from: URL, context: Context) throws -> String {
let expression = try String(contentsOf: from)
return evaluate(expression, context: context)
}
static func preprocess(_ context: Context) {
context.variables = context.variables.mapValues { value in
convert(value) {
if let integerValue = $0 as? Int {
return Double(integerValue)
}
return $0
}
}
}
static func convert(_ value: Any, recursively: Bool = true, convert: @escaping (Any) -> Any) -> Any {
if recursively, let array = value as? [Any] {
return array.map { convert($0) }
}
if recursively, let dictionary = value as? [String: Any] {
return dictionary.mapValues { convert($0) }
}
return convert(value)
}
func replaceWhitespaces(_ input: String) -> String {
let tag = "{-}"
var input = input
repeat {
if var range = input.range(of: tag) {
searchForward: while true {
if range.upperBound < input.index(before: input.endIndex) {
let nextIndex = range.upperBound
if let unicodeScalar = input[nextIndex].unicodeScalars.first,
CharacterSet.whitespacesAndNewlines.contains(unicodeScalar) {
range = Range(uncheckedBounds: (lower: range.lowerBound, upper: input.index(after: range.upperBound)))
} else {
break searchForward
}
} else {
break searchForward
}
}
searchBackward: while true {
if range.lowerBound > input.startIndex {
let nextIndex = input.index(before: range.lowerBound)
if let unicodeScalar = input[nextIndex].unicodeScalars.first,
CharacterSet.whitespacesAndNewlines.contains(unicodeScalar) {
range = Range(uncheckedBounds: (lower: input.index(before: range.lowerBound), upper: range.upperBound))
} else {
break searchBackward
}
} else {
break searchBackward
}
}
input.replaceSubrange(range, with: "")
}
} while input.contains(tag)
return input
}
}
internal typealias Macro = (arguments: [String], body: String)
internal typealias BlockRenderer = (_ context: Context) -> String
extension Context {
static let macrosKey: String = "__macros"
var macros: [String: Macro] {
get {
return variables[Context.macrosKey] as? [String: Macro] ?? [:]
}
set {
variables[Context.macrosKey] = macros.merging(newValue) { _, new in new }
}
}
static let blocksKey: String = "__blocks"
var blocks: [String: [BlockRenderer]] {
get {
return variables[Context.blocksKey] as? [String: [BlockRenderer]] ?? [:]
}
set {
variables[Context.blocksKey] = blocks.merging(newValue) { _, new in new }
}
}
}
public class TemplateLibrary {
public static var standardLibrary: StandardLibrary = StandardLibrary()
public static var templates: [Pattern<String, TemplateInterpreter<String>>] {
return [
ifElseStatement,
ifStatement,
printStatement,
forInStatement,
setUsingBodyStatement,
setStatement,
blockStatement,
macroStatement,
commentStatement,
importStatement,
spacelessStatement
]
}
public static var tagPrefix: String = "{%"
public static var tagSuffix: String = "%}"
public static var ifStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([Keyword(tagPrefix + " if"), Variable<Bool>("condition"), Keyword(tagSuffix), TemplateVariable("body", options: .notTrimmed) {
guard let content = $0.value as? String, !content.contains(tagPrefix + " else " + tagSuffix) else { return nil }
return content
}, Keyword("{%"), Keyword("endif"), Keyword("%}")]) {
guard let condition = $0.variables["condition"] as? Bool, let body = $0.variables["body"] as? String else { return nil }
return condition ? body : ""
}
}
public static var ifElseStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " if"), Variable<Bool>("condition"), Keyword(tagSuffix), TemplateVariable("body", options: .notTrimmed) {
guard let content = $0.value as? String, !content.contains(tagPrefix + " else " + tagSuffix) else { return nil }
return content
}, Keyword(tagPrefix + " else " + tagSuffix), TemplateVariable("else", options: .notTrimmed) {
guard let content = $0.value as? String, !content.contains(tagPrefix + " else " + tagSuffix) else { return nil }
return content
}, CloseKeyword(tagPrefix + " endif " + tagSuffix)]) {
guard let condition = $0.variables["condition"] as? Bool, let body = $0.variables["body"] as? String else { return nil }
return condition ? body : $0.variables["else"] as? String
}
}
public static var printStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword("{{"), Variable<Any>("body"), CloseKeyword("}}")]) {
guard let body = $0.variables["body"] else { return nil }
return $0.interpreter.typedInterpreter.print(body)
}
}
public static var forInStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " for"),
GenericVariable<String, StringTemplateInterpreter>("variable", options: .notInterpreted), Keyword("in"),
Variable<[Any]>("items"),
Keyword(tagSuffix),
GenericVariable<String, StringTemplateInterpreter>("body", options: [.notInterpreted, .notTrimmed]),
CloseKeyword(tagPrefix + " endfor " + tagSuffix)]) {
guard let variableName = $0.variables["variable"] as? String,
let items = $0.variables["items"] as? [Any],
let body = $0.variables["body"] as? String else { return nil }
var result = ""
$0.context.push()
$0.context.variables["__loop"] = items
for (index, item) in items.enumerated() {
$0.context.variables["__first"] = index == items.startIndex
$0.context.variables["__last"] = index == items.index(before: items.endIndex)
$0.context.variables[variableName] = item
result += $0.interpreter.evaluate(body, context: $0.context)
}
$0.context.pop()
return result
}
}
public static var setStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " set"), TemplateVariable("variable"), Keyword(tagSuffix), TemplateVariable("body"), CloseKeyword(tagPrefix + " endset " + tagSuffix)]) {
guard let variableName = $0.variables["variable"] as? String, let body = $0.variables["body"] as? String else { return nil }
$0.interpreter.context.variables[variableName] = body
return ""
}
}
public static var setUsingBodyStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " set"), TemplateVariable("variable"), Keyword("="), Variable<Any>("value"), CloseKeyword(tagSuffix)]) {
guard let variableName = $0.variables["variable"] as? String else { return nil }
$0.interpreter.context.variables[variableName] = $0.variables["value"]
return ""
}
}
public static var blockStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " block"),
GenericVariable<String, StringTemplateInterpreter>("name", options: .notInterpreted),
Keyword(tagSuffix),
GenericVariable<String, StringTemplateInterpreter>("body", options: .notInterpreted),
CloseKeyword(tagPrefix + " endblock " + tagSuffix)]) { match in
guard let name = match.variables["name"] as? String, let body = match.variables["body"] as? String else { return nil }
let block: BlockRenderer = { context in
context.push()
context.merge(with: match.context) { existing, _ in existing }
context.variables["__block"] = name
if let last = context.blocks[name] {
context.blocks[name] = Array(last.dropLast())
}
let result = match.interpreter.evaluate(body, context: context)
context.pop()
return result
}
if let last = match.interpreter.context.blocks[name] {
match.interpreter.context.blocks[name] = last + [block]
return ""
} else {
match.interpreter.context.blocks[name] = [block]
return "{{{\(name)}}}"
}
}
}
public static var macroStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " macro"), GenericVariable<String, StringTemplateInterpreter>("name", options: .notInterpreted), Keyword("("), GenericVariable<[String], StringTemplateInterpreter>("arguments", options: .notInterpreted) {
guard let arguments = $0.value as? String else { return nil }
return arguments.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
}, Keyword(")"), Keyword(tagSuffix), GenericVariable<String, StringTemplateInterpreter>("body", options: .notInterpreted), CloseKeyword(tagPrefix + " endmacro " + tagSuffix)]) {
guard let name = $0.variables["name"] as? String,
let arguments = $0.variables["arguments"] as? [String],
let body = $0.variables["body"] as? String else { return nil }
$0.interpreter.context.macros[name] = (arguments: arguments, body: body)
return ""
}
}
public static var commentStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword("{#"), GenericVariable<String, StringTemplateInterpreter>("body", options: .notInterpreted), CloseKeyword("#}")]) { _ in "" }
}
public static var importStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " import"), Variable<String>("file"), CloseKeyword(tagSuffix)]) {
guard let file = $0.variables["file"] as? String,
let url = Bundle.allBundles.compactMap({ $0.url(forResource: file, withExtension: nil) }).first,
let expression = try? String(contentsOf: url) else { return nil }
return $0.interpreter.evaluate(expression, context: $0.context)
}
}
public static var spacelessStatement: Pattern<String, TemplateInterpreter<String>> {
return Pattern([OpenKeyword(tagPrefix + " spaceless " + tagSuffix), TemplateVariable("body"), CloseKeyword(tagPrefix + " endspaceless " + tagSuffix)]) {
guard let body = $0.variables["body"] as? String else { return nil }
return body.self.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }.joined()
}
}
}
// swiftlint:disable:next type_body_length
public class StandardLibrary {
public static var dataTypes: [DataTypeProtocol] {
return [
stringType,
booleanType,
arrayType,
dictionaryType,
dateType,
numericType,
emptyType
]
}
public static var functions: [FunctionProtocol] {
return [
parentheses,
macro,
blockParent,
ternaryOperator,
rangeFunction,
rangeOfStringFunction,
rangeBySteps,
loopIsFirst,
loopIsLast,
loopIsNotFirst,
loopIsNotLast,
startsWithOperator,
endsWithOperator,
containsOperator,
matchesOperator,
capitalise,
lowercase,
uppercase,
lowercaseFirst,
uppercaseFirst,
trim,
urlEncode,
urlDecode,
escape,
nl2br,
stringConcatenationOperator,
multiplicationOperator,
divisionOperator,
additionOperator,
subtractionOperator,
moduloOperator,
powOperator,
lessThanOperator,
lessThanOrEqualsOperator,
moreThanOperator,
moreThanOrEqualsOperator,
equalsOperator,
notEqualsOperator,
stringEqualsOperator,
stringNotEqualsOperator,
inNumericArrayOperator,
inStringArrayOperator,
incrementOperator,
decrementOperator,
negationOperator,
notOperator,
orOperator,
andOperator,
absoluteValue,
defaultValue,
isEvenOperator,
isOddOperator,
minFunction,
maxFunction,
sumFunction,
sqrtFunction,
roundFunction,
averageFunction,
arraySubscript,
arrayCountFunction,
arrayMapFunction,
arrayFilterFunction,
arraySortFunction,
arrayReverseFunction,
arrayMinFunction,
arrayMaxFunction,
arrayFirstFunction,
arrayLastFunction,
arrayJoinFunction,
arraySplitFunction,
arrayMergeFunction,
arraySumFunction,
arrayAverageFunction,
dictionarySubscript,
dictionaryCountFunction,
dictionaryFilterFunction,
dictionaryKeys,
dictionaryValues,
dateFactory,
dateFormat,
stringFactory
]
}
// MARK: Types
public static var numericType: DataType<Double> {
let numberLiteral = Literal { Double($0.value) }
let piLiteral = Literal("pi", convertsTo: Double.pi)
return DataType(type: Double.self, literals: [numberLiteral, piLiteral]) { String(format: "%g", $0.value) }
}
public static var stringType: DataType<String> {
let singleQuotesLiteral = literal(opening: "'", closing: "'") { $0.value }
return DataType(type: String.self, literals: [singleQuotesLiteral]) { $0.value }
}
public static var dateType: DataType<Date> {
let dateFormatter = DateFormatter(with: "yyyy-MM-dd HH:mm:ss")
let now = Literal<Date>("now", convertsTo: Date())
return DataType(type: Date.self, literals: [now]) { dateFormatter.string(from: $0.value) }
}
public static var arrayType: DataType<[CustomStringConvertible]> {
let arrayLiteral = literal(opening: "[", closing: "]") { literal -> [CustomStringConvertible]? in
literal.value
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.map { literal.interpreter.evaluate(String($0)) as? CustomStringConvertible ?? String($0) }
}
return DataType(type: [CustomStringConvertible].self, literals: [arrayLiteral]) { dataType in dataType.value.map { dataType.printer.print($0) }.joined(separator: ",") }
}
public static var dictionaryType: DataType<[String: CustomStringConvertible?]> {
let dictionaryLiteral = literal(opening: "{", closing: "}") { body -> [String: CustomStringConvertible?]? in
let values = body.value
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
let parsedValues : [(key: String, value: CustomStringConvertible?)] = values
.map { $0.split(separator: ":").map { body.interpreter.evaluate(String($0)) } }
.compactMap {
guard let first = $0.first, let key = first as? String, let value = $0.last else { return nil }
return (key: key, value: value as? CustomStringConvertible)
}
return Dictionary(grouping: parsedValues) { $0.key }.mapValues { $0.first?.value }
}
return DataType(type: [String: CustomStringConvertible?].self, literals: [dictionaryLiteral]) { dataType in
let items = dataType.value.map { key, value in
if let value = value {
return "\(dataType.printer.print(key)): \(dataType.printer.print(value))"
} else {
return "\(dataType.printer.print(key)): nil"
}
}.sorted().joined(separator: ", ")
return "[\(items)]"
}
}
public static var booleanType: DataType<Bool> {
let trueLiteral = Literal("true", convertsTo: true)
let falseLiteral = Literal("false", convertsTo: false)
return DataType(type: Bool.self, literals: [trueLiteral, falseLiteral]) { $0.value ? "true" : "false" }
}
public static var emptyType: DataType<Any?> {
let nullLiteral = Literal<Any?>("null", convertsTo: nil)
let nilLiteral = Literal<Any?>("nil", convertsTo: nil)
return DataType<Any?>(type: Any?.self, literals: [nullLiteral, nilLiteral]) { _ in "null" }
}
// MARK: Functions
public static var parentheses: Function<Double> {
return Function([OpenKeyword("("), Variable<Double>("body"), CloseKeyword(")")]) { $0.variables["body"] as? Double }
}
public static var macro: Function<Any> {
return Function([Variable<String>("name", options: .notInterpreted) {
guard let value = $0.value as? String else { return nil }
return $0.interpreter.context.macros.keys.contains(value) ? value : nil
}, Keyword("("), Variable<String>("arguments", options: .notInterpreted), Keyword(")")]) { match in
guard let arguments = match.variables["arguments"] as? String,
let name = match.variables["name"] as? String,
let macro = match.interpreter.context.macros[name.trimmingCharacters(in: .whitespacesAndNewlines)] else { return nil }
let interpretedArguments = arguments.split(separator: ",").compactMap { match.interpreter.evaluate(String($0).trimmingCharacters(in: .whitespacesAndNewlines)) }
match.context.push()
for (key, value) in zip(macro.arguments, interpretedArguments) {
match.context.variables[key] = value
}
let result = match.interpreter.evaluate(macro.body, context: match.context)
match.context.pop()
return result
}
}
public static var blockParent: Function<Any> {
return Function([Keyword("parent"), Keyword("("), Variable<String>("arguments", options: .notInterpreted), Keyword(")")]) {
guard let arguments = $0.variables["arguments"] as? String else { return nil }
var interpretedArguments: [String: Any] = [:]
for argument in arguments.split(separator: ",") {
let parts = String(argument).trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=")
if let key = parts.first, let value = parts.last {
interpretedArguments[String(key)] = $0.interpreter.evaluate(String(value))
}
}
guard let name = $0.context.variables["__block"] as? String, let block = $0.context.blocks[name]?.last else { return nil }
$0.context.push()
$0.context.variables.merge(interpretedArguments) { _, new in new }
let result = block($0.context)
$0.context.pop()
return result
}
}
public static var ternaryOperator: Function<Any> {
return Function([Variable<Bool>("condition"), Keyword("?"), Variable<Any>("body"), Keyword(": "), Variable<Any>("else")]) {
guard let condition = $0.variables["condition"] as? Bool else { return nil }
return condition ? $0.variables["body"] : $0.variables["else"]
}
}
public static var rangeFunction: Function<[Double]> {
return infixOperator("...") { (lhs: Double, rhs: Double) in
CountableClosedRange(uncheckedBounds: (lower: Int(lhs), upper: Int(rhs))).map { Double($0) }
}
}
public static var rangeOfStringFunction: Function<[String]> {
return infixOperator("...") { (lhs: String, rhs: String) in
CountableClosedRange(uncheckedBounds: (lower: Character(lhs), upper: Character(rhs))).map { String($0) }
}
}
public static var startsWithOperator: Function<Bool> {
return infixOperator("starts with") { (lhs: String, rhs: String) in lhs.hasPrefix(rhs) }
}
public static var endsWithOperator: Function<Bool> {
return infixOperator("ends with") { (lhs: String, rhs: String) in lhs.hasSuffix(rhs) }
}
public static var containsOperator: Function<Bool> {
return infixOperator("contains") { (lhs: String, rhs: String) in lhs.contains(rhs) }
}
public static var matchesOperator: Function<Bool> {
return infixOperator("matches") { (lhs: String, rhs: String) in
if let regex = try? NSRegularExpression(pattern: rhs) {
let matches = regex.numberOfMatches(in: lhs, range: NSRange(lhs.startIndex..., in: lhs))
return matches > 0
}
return false
}
}
public static var capitalise: Function<String> {
return objectFunction("capitalise") { (value: String) -> String? in value.capitalized }
}
public static var lowercase: Function<String> {
return objectFunction("lower") { (value: String) -> String? in value.lowercased() }
}
public static var uppercase: Function<String> {
return objectFunction("upper") { (value: String) -> String? in value.uppercased() }
}
public static var lowercaseFirst: Function<String> {
return objectFunction("lowerFirst") { (value: String) -> String? in
guard let first = value.first else { return nil }
return String(first).lowercased() + value[value.index(value.startIndex, offsetBy: 1)...]
}
}
public static var uppercaseFirst: Function<String> {
return objectFunction("upperFirst") { (value: String) -> String? in
guard let first = value.first else { return nil }
return String(first).uppercased() + value[value.index(value.startIndex, offsetBy: 1)...]
}
}
public static var trim: Function<String> {
return objectFunction("trim") { (value: String) -> String? in value.trimmingCharacters(in: .whitespacesAndNewlines) }
}
public static var urlEncode: Function<String> {
return objectFunction("urlEncode") { (value: String) -> String? in value.addingPercentEncoding(withAllowedCharacters: .alphanumerics) }
}
public static var urlDecode: Function<String> {
return objectFunction("urlDecode") { (value: String) -> String? in value.removingPercentEncoding }
}
public static var escape: Function<String> {
return objectFunction("escape") { (value: String) -> String? in value.html }
}
public static var nl2br: Function<String> {
return objectFunction("nl2br") { (value: String) -> String? in value
.replacingOccurrences(of: "\r\n", with: "<br/>")
.replacingOccurrences(of: "\n", with: "<br/>")
}
}
public static var stringConcatenationOperator: Function<String> {
return infixOperator("+") { (lhs: String, rhs: String) in lhs + rhs }
}
public static var additionOperator: Function<Double> {
return infixOperator("+") { (lhs: Double, rhs: Double) in lhs + rhs }
}
public static var subtractionOperator: Function<Double> {
return infixOperator("-") { (lhs: Double, rhs: Double) in lhs - rhs }
}
public static var multiplicationOperator: Function<Double> {
return infixOperator("*") { (lhs: Double, rhs: Double) in lhs * rhs }
}
public static var divisionOperator: Function<Double> {
return infixOperator("/") { (lhs: Double, rhs: Double) in lhs / rhs }
}
public static var moduloOperator: Function<Double> {
return infixOperator("%") { (lhs: Double, rhs: Double) in Double(Int(lhs) % Int(rhs)) }
}
public static var powOperator: Function<Double> {
return infixOperator("**") { (lhs: Double, rhs: Double) in pow(lhs, rhs) }
}
public static var lessThanOperator: Function<Bool> {
return infixOperator("<") { (lhs: Double, rhs: Double) in lhs < rhs }
}
public static var moreThanOperator: Function<Bool> {
return infixOperator("<=") { (lhs: Double, rhs: Double) in lhs <= rhs }
}
public static var lessThanOrEqualsOperator: Function<Bool> {
return infixOperator(">") { (lhs: Double, rhs: Double) in lhs > rhs }
}
public static var moreThanOrEqualsOperator: Function<Bool> {
return infixOperator(">=") { (lhs: Double, rhs: Double) in lhs >= rhs }
}
public static var equalsOperator: Function<Bool> {
return infixOperator("==") { (lhs: Double, rhs: Double) in lhs == rhs }
}
public static var notEqualsOperator: Function<Bool> {
return infixOperator("!=") { (lhs: Double, rhs: Double) in lhs != rhs }
}
public static var stringEqualsOperator: Function<Bool> {
return infixOperator("==") { (lhs: String, rhs: String) in lhs == rhs }
}
public static var stringNotEqualsOperator: Function<Bool> {
return infixOperator("!=") { (lhs: String, rhs: String) in lhs != rhs }
}
public static var inStringArrayOperator: Function<Bool> {
return infixOperator("in") { (lhs: String, rhs: [String]) in rhs.contains(lhs) }
}
public static var inNumericArrayOperator: Function<Bool> {
return infixOperator("in") { (lhs: Double, rhs: [Double]) in rhs.contains(lhs) }
}
public static var negationOperator: Function<Bool> {
return prefixOperator("!") { (expression: Bool) in !expression }
}
public static var notOperator: Function<Bool> {
return prefixOperator("not") { (expression: Bool) in !expression }
}
public static var andOperator: Function<Bool> {
return infixOperator("and") { (lhs: Bool, rhs: Bool) in lhs && rhs }
}
public static var orOperator: Function<Bool> {
return infixOperator("or") { (lhs: Bool, rhs: Bool) in lhs || rhs }
}
public static var absoluteValue: Function<Double> {
return objectFunction("abs") { (value: Double) -> Double? in abs(value) }
}
public static var defaultValue: Function<Any> {
return Function([Variable<Any>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "default" else { return nil }
return value
}, Keyword("("), Variable<Any>("fallback"), Keyword(")")], options: .backwardMatch) {
guard let value = $0.variables["lhs"], $0.variables["rhs"] != nil else { return nil }
return isNilOrWrappedNil(value: value) ? $0.variables["fallback"] : value
}
}
public static var incrementOperator: Function<Double> {
return suffixOperator("++") { (expression: Double) in expression + 1 }
}
public static var decrementOperator: Function<Double> {
return suffixOperator("--") { (expression: Double) in expression - 1 }
}
public static var isEvenOperator: Function<Bool> {
return suffixOperator("is even") { (expression: Double) in Int(expression) % 2 == 0 }
}
public static var isOddOperator: Function<Bool> {
return suffixOperator("is odd") { (expression: Double) in abs(Int(expression) % 2) == 1 }
}
public static var minFunction: Function<Double> {
return function("min") { (arguments: [Any]) -> Double? in
guard let arguments = arguments as? [Double] else { return nil }
return arguments.min()
}
}
public static var maxFunction: Function<Double> {
return function("max") { (arguments: [Any]) -> Double? in
guard let arguments = arguments as? [Double] else { return nil }
return arguments.max()
}
}
public static var arraySortFunction: Function<[Double]> {
return objectFunction("sort") { (object: [Double]) -> [Double]? in object.sorted() }
}
public static var arrayReverseFunction: Function<[Double]> {
return objectFunction("reverse") { (object: [Double]) -> [Double]? in object.reversed() }
}
public static var arrayMinFunction: Function<Double> {
return objectFunction("min") { (object: [Double]) -> Double? in object.min() }
}
public static var arrayMaxFunction: Function<Double> {
return objectFunction("max") { (object: [Double]) -> Double? in object.max() }
}
public static var arrayFirstFunction: Function<Double> {
return objectFunction("first") { (object: [Double]) -> Double? in object.first }
}
public static var arrayLastFunction: Function<Double> {
return objectFunction("last") { (object: [Double]) -> Double? in object.last }
}
public static var arrayJoinFunction: Function<String> {
return objectFunctionWithParameters("join") { (object: [String], arguments: [Any]) -> String? in
guard let separator = arguments.first as? String else { return nil }
return object.joined(separator: separator)
}
}
public static var arraySplitFunction: Function<[String]> {
return Function([Variable<String>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "split" else { return nil }
return value
}, Keyword("("), Variable<String>("separator"), Keyword(")")]) {
guard let object = $0.variables["lhs"] as? String, $0.variables["rhs"] != nil, let separator = $0.variables["separator"] as? String else { return nil }
return object.split(separator: Character(separator)).map { String($0) }
}
}
public static var arrayMergeFunction: Function<[Any]> {
return Function([Variable<[Any]>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "merge" else { return nil }
return value
}, Keyword("("), Variable<[Any]>("other"), Keyword(")")]) {
guard let object = $0.variables["lhs"] as? [Any], $0.variables["rhs"] != nil, let other = $0.variables["other"] as? [Any] else { return nil }
return object + other
}
}
public static var arraySumFunction: Function<Double> {
return objectFunction("sum") { (object: [Double]) -> Double? in object.reduce(0, +) }
}
public static var arrayAverageFunction: Function<Double> {
return objectFunction("avg") { (object: [Double]) -> Double? in object.reduce(0, +) / Double(object.count) }
}
public static var arrayCountFunction: Function<Double> {
return objectFunction("count") { (object: [Double]) -> Double? in Double(object.count) }
}
public static var dictionaryCountFunction: Function<Double> {
return objectFunction("count") { (object: [String: Any]) -> Double? in Double(object.count) }
}
public static var arrayMapFunction: Function<[Any]> {
return Function([Variable<[Any]>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "map" else { return nil }
return value
}, Keyword("("), Variable<String>("variable", options: .notInterpreted), Keyword("=>"), Variable<Any>("body", options: .notInterpreted), Keyword(")")]) { match in
guard let object = match.variables["lhs"] as? [Any], match.variables["rhs"] != nil,
let variable = match.variables["variable"] as? String,
let body = match.variables["body"] as? String else { return nil }
match.context.push()
let result: [Any] = object.compactMap { item in
match.context.variables[variable] = item
return match.interpreter.evaluate(body, context: match.context)
}
match.context.pop()
return result
}
}
public static var arrayFilterFunction: Function<[Any]> {
return Function([Variable<[Any]>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "filter" else { return nil }
return value
}, Keyword("("), Variable<String>("variable", options: .notInterpreted), Keyword("=>"), Variable<Any>("body", options: .notInterpreted), Keyword(")")]) { match in
guard let object = match.variables["lhs"] as? [Any], match.variables["rhs"] != nil,
let variable = match.variables["variable"] as? String,
let body = match.variables["body"] as? String else { return nil }
match.context.push()
let result: [Any] = object.filter { item in
match.context.variables[variable] = item
if let result = match.interpreter.evaluate(body, context: match.context) as? Bool {
return result
}
return false
}
match.context.pop()
return result
}
}
public static var dictionaryFilterFunction: Function<[String: Any]> {
return Function([Variable<[String: Any]>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == "filter" else { return nil }
return value
}, Keyword("("), Variable<String>("key", options: .notInterpreted), Keyword(","), Variable<String>("value", options: .notInterpreted), Keyword("=>"), Variable<Any>("body", options: .notInterpreted), Keyword(")")]) { match in
guard let object = match.variables["lhs"] as? [String: Any], match.variables["rhs"] != nil,
let keyVariable = match.variables["key"] as? String,
let valueVariable = match.variables["value"] as? String,
let body = match.variables["body"] as? String else { return nil }
match.context.push()
let result: [String: Any] = object.filter { key, value in
match.context.variables[keyVariable] = key
match.context.variables[valueVariable] = value
if let result = match.interpreter.evaluate(body, context: match.context) as? Bool {
return result
}
return false
}
match.context.pop()
return result
}
}
public static var sumFunction: Function<Double> {
return function("sum") { (arguments: [Any]) -> Double? in
guard let arguments = arguments as? [Double] else { return nil }
return arguments.reduce(0, +)
}
}
public static var averageFunction: Function<Double> {
return function("avg") { (arguments: [Any]) -> Double? in
guard let arguments = arguments as? [Double] else { return nil }
return arguments.reduce(0, +) / Double(arguments.count)
}
}
public static var sqrtFunction: Function<Double> {
return function("sqrt") { (arguments: [Any]) -> Double? in
guard let value = arguments.first as? Double else { return nil }
return sqrt(value)
}
}
public static var roundFunction: Function<Double> {
return function("round") { (arguments: [Any]) -> Double? in
guard let value = arguments.first as? Double else { return nil }
return round(value)
}
}
public static var dateFactory: Function<Date?> {
return function("Date") { (arguments: [Any]) -> Date? in
guard let arguments = arguments as? [Double], arguments.count >= 3 else { return nil }
var components = DateComponents()
components.calendar = Calendar(identifier: .gregorian)
components.year = Int(arguments[0])
components.month = Int(arguments[1])
components.day = Int(arguments[2])
components.hour = arguments.count > 3 ? Int(arguments[3]) : 0
components.minute = arguments.count > 4 ? Int(arguments[4]) : 0
components.second = arguments.count > 5 ? Int(arguments[5]) : 0
return components.date
}
}
public static var stringFactory: Function<String?> {
return function("String") { (arguments: [Any]) -> String? in
guard let argument = arguments.first as? Double else { return nil }
return String(format: "%g", argument)
}
}
public static var rangeBySteps: Function<[Double]> {
return functionWithNamedParameters("range") { (arguments: [String: Any]) -> [Double]? in
guard let start = arguments["start"] as? Double, let end = arguments["end"] as? Double, let step = arguments["step"] as? Double else { return nil }
var result = [start]
var value = start
while value <= end - step {
value += step
result.append(value)
}
return result
}
}
public static var loopIsFirst: Function<Bool?> {
return Function([Variable<Any>("value"), Keyword("is first")]) {
$0.context.variables["__first"] as? Bool
}
}
public static var loopIsLast: Function<Bool?> {
return Function([Variable<Any>("value"), Keyword("is last")]) {
$0.context.variables["__last"] as? Bool
}
}
public static var loopIsNotFirst: Function<Bool?> {
return Function([Variable<Any>("value"), Keyword("is not first")]) {
guard let isFirst = $0.context.variables["__first"] as? Bool else { return nil }
return !isFirst
}
}
public static var loopIsNotLast: Function<Bool?> {
return Function([Variable<Any>("value"), Keyword("is not last")]) {
guard let isLast = $0.context.variables["__last"] as? Bool else { return nil }
return !isLast
}
}
public static var dateFormat: Function<String> {
return objectFunctionWithParameters("format") { (object: Date, arguments: [Any]) -> String? in
guard let format = arguments.first as? String else { return nil }
let dateFormatter = DateFormatter(with: format)
return dateFormatter.string(from: object)
}
}
public static var arraySubscript: Function<Any?> {
return Function([Variable<[Any]>("array"), Keyword("."), Variable<Double>("index")]) {
guard let array = $0.variables["array"] as? [Any], let index = $0.variables["index"] as? Double, index > 0, Int(index) < array.count else { return nil }
return array[Int(index)]
}
}
public static var dictionarySubscript: Function<Any?> {
return Function([Variable<[String: Any]>("dictionary"), Keyword("."), Variable<String>("key", options: .notInterpreted)]) {
guard let dictionary = $0.variables["dictionary"] as? [String: Any], let key = $0.variables["key"] as? String else { return nil }
return dictionary[key]
}
}
public static var dictionaryKeys: Function<[String]> {
return objectFunction("keys") { (object: [String: Any?]) -> [String] in
object.keys.sorted()
}
}
public static var dictionaryValues: Function<[Any?]> {
return objectFunction("values") { (object: [String: Any?]) -> [Any?] in
if let values = object as? [String: Double] {
return values.values.sorted()
}
if let values = object as? [String: String] {
return values.values.sorted()
}
return Array(object.values)
}
}
public static var methodCallWithIntResult: Function<Double> {
return Function([Variable<Any>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted)]) {
if let lhs = $0.variables["lhs"] as? NSObjectProtocol,
let rhs = $0.variables["rhs"] as? String,
let result = lhs.perform(Selector(rhs)) {
return Double(Int(bitPattern: result.toOpaque()))
}
return nil
}
}
// MARK: Literal helpers
public static func literal<T>(opening: String, closing: String, convert: @escaping (_ literal: LiteralBody) -> T?) -> Literal<T> {
return Literal { literal -> T? in
guard literal.value.hasPrefix(opening), literal.value.hasSuffix(closing), literal.value.count > 1 else { return nil }
let inputWithoutOpening = String(literal.value.suffix(from: literal.value.index(literal.value.startIndex, offsetBy: opening.count)))
let inputWithoutSides = String(inputWithoutOpening.prefix(upTo: inputWithoutOpening.index(inputWithoutOpening.endIndex, offsetBy: -closing.count)))
guard !inputWithoutSides.contains(opening) && !inputWithoutSides.contains(closing) else { return nil }
return convert(LiteralBody(value: inputWithoutSides, interpreter: literal.interpreter))
}
}
// MARK: Operator helpers
public static func infixOperator<A, B, T>(_ symbol: String, body: @escaping (A, B) -> T) -> Function<T> {
return Function([Variable<A>("lhs"), Keyword(symbol), Variable<B>("rhs")], options: .backwardMatch) {
guard let lhs = $0.variables["lhs"] as? A, let rhs = $0.variables["rhs"] as? B else { return nil }
return body(lhs, rhs)
}
}
public static func prefixOperator<A, T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T> {
return Function([Keyword(symbol), Variable<A>("value")]) {
guard let value = $0.variables["value"] as? A else { return nil }
return body(value)
}
}
public static func suffixOperator<A, T>(_ symbol: String, body: @escaping (A) -> T) -> Function<T> {
return Function([Variable<A>("value"), Keyword(symbol)]) {
guard let value = $0.variables["value"] as? A else { return nil }
return body(value)
}
}
// MARK: Function helpers
public static func function<T>(_ name: String, body: @escaping ([Any]) -> T?) -> Function<T> {
return Function([Keyword(name), OpenKeyword("("), Variable<String>("arguments", options: .notInterpreted), CloseKeyword(")")]) { match in
guard let arguments = match.variables["arguments"] as? String else { return nil }
let interpretedArguments = arguments.split(separator: ",").compactMap { match.interpreter.evaluate(String($0).trimmingCharacters(in: .whitespacesAndNewlines)) }
return body(interpretedArguments)
}
}
public static func functionWithNamedParameters<T>(_ name: String, body: @escaping ([String: Any]) -> T?) -> Function<T> {
return Function([Keyword(name), OpenKeyword("("), Variable<String>("arguments", options: .notInterpreted), CloseKeyword(")")]) {
guard let arguments = $0.variables["arguments"] as? String else { return nil }
var interpretedArguments: [String: Any] = [:]
for argument in arguments.split(separator: ",") {
let parts = String(argument).trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=")
if let key = parts.first, let value = parts.last {
interpretedArguments[String(key)] = $0.interpreter.evaluate(String(value))
}
}
return body(interpretedArguments)
}
}
public static func objectFunction<O, T>(_ name: String, body: @escaping (O) -> T?) -> Function<T> {
return Function([Variable<O>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == name else { return nil }
return value
}], options: .backwardMatch) {
guard let object = $0.variables["lhs"] as? O, $0.variables["rhs"] != nil else { return nil }
return body(object)
}
}
public static func objectFunctionWithParameters<O, T>(_ name: String, body: @escaping (O, [Any]) -> T?) -> Function<T> {
return Function([Variable<O>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == name else { return nil }
return value
}, Keyword("("), Variable<String>("arguments", options: .notInterpreted), Keyword(")")]) { match in
guard let object = match.variables["lhs"] as? O, match.variables["rhs"] != nil, let arguments = match.variables["arguments"] as? String else { return nil }
let interpretedArguments = arguments.split(separator: ",").compactMap { match.interpreter.evaluate(String($0).trimmingCharacters(in: .whitespacesAndNewlines)) }
return body(object, interpretedArguments)
}
}
public static func objectFunctionWithNamedParameters<O, T>(_ name: String, body: @escaping (O, [String: Any]) -> T?) -> Function<T> {
return Function([Variable<O>("lhs"), Keyword("."), Variable<String>("rhs", options: .notInterpreted) {
guard let value = $0.value as? String, value == name else { return nil }
return value
}, OpenKeyword("("), Variable<String>("arguments", options: .notInterpreted), CloseKeyword(")")]) { match in
guard let object = match.variables["lhs"] as? O, match.variables["rhs"] != nil, let arguments = match.variables["arguments"] as? String else { return nil }
var interpretedArguments: [String: Any] = [:]
for argument in arguments.split(separator: ",") {
let parts = String(argument).trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=")
if let key = parts.first, let value = parts.last {
interpretedArguments[String(key)] = match.interpreter.evaluate(String(value))
}
}
return body(object, interpretedArguments)
}
}
}
public extension DateFormatter {
convenience init(with format: String) {
self.init()
self.calendar = Calendar(identifier: .gregorian)
self.dateFormat = format
}
}
extension Character: Strideable {
public typealias Stride = Int
var value: UInt32 {
return unicodeScalars.first?.value ?? 0
}
public func distance(to other: Character) -> Int {
return Int(other.value) - Int(self.value)
}
public func advanced(by offset: Int) -> Character {
let advancedValue = offset + Int(self.value)
guard let advancedScalar = UnicodeScalar(advancedValue) else {
fatalError("\(String(advancedValue, radix: 16)) does not represent a valid unicode scalar value.")
}
return Character(advancedScalar)
}
}
extension String {
static let enc: [Character: String] =
[" ": " ", " ": " ", " ": " ", " ": " ", "‾": "‾", "–": "–", "—": "—",
"¡": "¡", "¿": "¿", "…": "…", "·": "·", "'": "'", "‘": "‘", "’": "’",
"‚": "‚", "‹": "‹", "›": "›", "": "‎", "": "‏", "": "­", "": "‍", "": "‌",
"\"": """, "“": "“", "”": "”", "„": "„", "«": "«", "»": "»", "⌈": "⌈",
"⌉": "⌉", "⌊": "⌊", "⌋": "⌋", "〈": "⟨", "〉": "⟩", "§": "§", "¶": "¶",
"&": "&", "‰": "‰", "†": "†", "‡": "‡", "•": "•", "′": "′", "″": "″",
"´": "´", "˜": "˜", "¯": "¯", "¨": "¨", "¸": "¸", "ˆ": "ˆ", "°": "°",
"©": "©", "®": "®", "℘": "℘", "←": "←", "→": "→", "↑": "↑", "↓": "↓",
"↔": "↔", "↵": "↵", "⇐": "⇐", "⇑": "⇑", "⇒": "⇒", "⇓": "⇓", "⇔": "⇔",
"∀": "∀", "∂": "∂", "∃": "∃", "∅": "∅", "∇": "∇", "∈": "∈", "∉": "∉",
"∋": "∋", "∏": "∏", "∑": "∑", "±": "±", "÷": "÷", "×": "×", "<": "<", "≠": "≠",
">": ">", "¬": "¬", "¦": "¦", "−": "−", "⁄": "⁄", "∗": "∗", "√": "√",
"∝": "∝", "∞": "∞", "∠": "∠", "∧": "∧", "∨": "∨", "∩": "∩", "∪": "∪", "∫": "∫",
"∴": "∴", "∼": "∼", "≅": "≅", "≈": "≈", "≡": "≡", "≤": "≤", "≥": "≥", "⊄": "⊄",
"⊂": "⊂", "⊃": "⊃", "⊆": "⊆", "⊇": "⊇", "⊕": "⊕", "⊗": "⊗", "⊥": "⊥",
"⋅": "⋅", "◊": "◊", "♠": "♠", "♣": "♣", "♥": "♥", "♦": "♦", "¤": "¤",
"¢": "¢", "£": "£", "¥": "¥", "€": "€", "¹": "¹", "½": "½", "¼": "¼",
"²": "²", "³": "³", "¾": "¾", "á": "á", "Á": "Á", "â": "â", "Â": "Â",
"à": "à", "À": "À", "å": "å", "Å": "Å", "ã": "ã", "Ã": "Ã", "ä": "ä",
"Ä": "Ä", "ª": "ª", "æ": "æ", "Æ": "Æ", "ç": "ç", "Ç": "Ç", "ð": "ð",
"Ð": "Ð", "é": "é", "É": "É", "ê": "ê", "Ê": "Ê", "è": "è", "È": "È",
"ë": "ë", "Ë": "Ë", "ƒ": "ƒ", "í": "í", "Í": "Í", "î": "î", "Î": "Î",
"ì": "ì", "Ì": "Ì", "ℑ": "ℑ", "ï": "ï", "Ï": "Ï", "ñ": "ñ", "Ñ": "Ñ",
"ó": "ó", "Ó": "Ó", "ô": "ô", "Ô": "Ô", "ò": "ò", "Ò": "Ò", "º": "º",
"ø": "ø", "Ø": "Ø", "õ": "õ", "Õ": "Õ", "ö": "ö", "Ö": "Ö", "œ": "œ", "Œ": "Œ", "ℜ": "ℜ", "š": "š", "Š": "Š", "ß": "ß", "™": "™", "ú": "ú",
"Ú": "Ú", "û": "û", "Û": "Û", "ù": "ù", "Ù": "Ù", "ü": "ü", "Ü": "Ü",
"ý": "ý", "Ý": "Ý", "ÿ": "ÿ", "Ÿ": "Ÿ", "þ": "þ", "Þ": "Þ", "α": "α",
"Α": "Α", "β": "β", "Β": "Β", "γ": "γ", "Γ": "Γ", "δ": "δ", "Δ": "Δ",
"ε": "ε", "Ε": "Ε", "ζ": "ζ", "Ζ": "Ζ", "η": "η", "Η": "Η", "θ": "θ",
"Θ": "Θ", "ϑ": "ϑ", "ι": "ι", "Ι": "Ι", "κ": "κ", "Κ": "Κ", "λ": "λ",
"Λ": "Λ", "µ": "µ", "μ": "μ", "Μ": "Μ", "ν": "ν", "Ν": "Ν", "ξ": "ξ", "Ξ": "Ξ",
"ο": "ο", "Ο": "Ο", "π": "π", "Π": "Π", "ϖ": "ϖ", "ρ": "ρ", "Ρ": "Ρ",
"σ": "σ", "Σ": "Σ", "ς": "ς", "τ": "τ", "Τ": "Τ", "ϒ": "ϒ", "υ": "υ",
"Υ": "Υ", "φ": "φ", "Φ": "Φ", "χ": "χ", "Χ": "Χ", "ψ": "ψ", "Ψ": "Ψ",
"ω": "ω", "Ω": "Ω", "ℵ": "ℵ"]
var html: String {
var html = ""
for character in self {
if let entity = String.enc[character] {
html.append(entity)
} else {
html.append(character)
}
}
return html
}
}
internal func isNilOrWrappedNil(value: Any) -> Bool {
let mirror = Mirror(reflecting: value)
if mirror.displayStyle == .optional {
if let first = mirror.children.first {
return isNilOrWrappedNil(value: first.value)
} else {
return true
}
}
return false
}
// swiftlint:disable:this file_length
================================================
FILE: Examples/TemplateExample/Tests/.swiftlint.yml
================================================
disabled_rules:
- force_cast
- force_try
- force_unwrapping
- type_name
- file_header
- explicit_top_level_acl
================================================
FILE: Examples/TemplateExample/Tests/LinuxMain.swift
================================================
@testable import TemplateExampleTests
import XCTest
XCTMain([
testCase(TemplateExampleTests.allTests)
])
================================================
FILE: Examples/TemplateExample/Tests/TemplateExampleTests/TemplateExampleComponentTests.swift
================================================
import Eval
@testable import TemplateExample
import XCTest
class TemplateExampleComponentTests: XCTestCase {
let interpreter: TemplateLanguage = TemplateLanguage()
func testComplexExample() {
XCTAssertEqual(eval(
"""
{% if greet %}Hello{% else %}Bye{% endif %} {{ name }}!
{% set works = true %}
{% for i in [3,2,1] %}{{ i }}, {% endfor %}go!
This template engine {% if !works %}does not {% endif %}work{% if works %}s{% endif %}!
""", ["greet": true, "name": "Laszlo"]),
"""
Hello Laszlo!
3, 2, 1, go!
This template engine works!
""")
}
// MARK: Helpers
func eval(_ template: String, _ variables: [String: Any] = [:]) -> String {
let context = Context(variables: variables)
let result = interpreter.evaluate(template, context: context)
if !context.debugInfo.isEmpty {
print(context.debugInfo)
}
return result
}
}
================================================
FILE: Examples/TemplateExample/Tests/TemplateExampleTests/TemplateExampleTests.swift
================================================
import Eval
@testable import TemplateExample
import XCTest
class TemplateExampleTests: XCTestCase {
let interpreter: TemplateLanguage = TemplateLanguage()
// MARK: Statements
func testIfElseStatement() {
XCTAssertEqual(eval("{% if x in [1,2,3] %}Hello{% else %}Bye{% endif %} {{ name }}!", ["x": 2, "name": "Teve"]), "Hello Teve!")
}
func testIfStatement() {
XCTAssertEqual(eval("{% if true %}Hello{% endif %} {{ name }}!", ["name": "Teve"]), "Hello Teve!")
}
func testEmbeddedIfStatement() {
XCTAssertEqual(eval("Result: {% if x > 1 %}{% if x < 5 %}1<x<5{% endif %}{% endif %}", ["x": 2]), "Result: 1<x<5")
XCTAssertEqual(eval("Result: {% if x > 1 %}{% if x < 5 %}1<x<5{% endif %}{% else %}x<=1{% endif %}", ["x": 2]), "Result: 1<x<5")
XCTAssertEqual(eval("Result: {% if x >= 5 %}x>=5{% else %}{% if x > 1 %}1<x<5{% endif %}{% endif %}", ["x": 2]), "Result: 1<x<5")
XCTAssertEqual(eval("Result: {% if x > 1 %}{% if x < 5 %}1<x<5{% else %}x>=5{% endif %}{% else %}x<=1{% endif %}", ["x": 2]), "Result: 1<x<5")
XCTAssertEqual(eval("Result: {% if x >= 5 %}x>=5{% else %}{% if x > 1 %}1<x<5{% else %}x<=1{% endif %}{% endif %}", ["x": 2]), "Result: 1<x<5")
}
func testPrintStatement() {
XCTAssertEqual(eval("{{ x }}", ["x": "Yo"]), "Yo")
XCTAssertEqual(eval("{{ x + 1 }}", ["x": 5]), "6")
}
func testSetStatement() {
_ = eval("{% set x = 4.0 %}")
XCTAssertEqual(eval("{{ x }}"), "4")
}
func testSetWithBodyStatement() {
_ = eval("{% set x %}this{% endset %}")
XCTAssertEqual(eval("Check {{ x }} out"), "Check this out")
}
func testForInStatement() {
XCTAssertEqual(eval("{% for i in [1,2,3] %}a{% endfor %}"), "aaa")
XCTAssertEqual(eval("{% for i in x %}{{i*2}} {% endfor %}", ["x": [1, 2, 3]]), "2 4 6 ")
XCTAssertEqual(eval("{% for i in [1,2,3] %}{{i * 2}} {% endfor %}"), "2 4 6 ")
XCTAssertEqual(eval("{% for i in [1,2,3] %}{% if i is not first %}, {% endif %}{{i * 2}}{% endfor %}"), "2, 4, 6")
XCTAssertEqual(eval("{% for i in [1,2,3] %}{{i * 2}}{% if i is not last %}, {% endif %}{% endfor %}"), "2, 4, 6")
XCTAssertEqual(eval("{% for i in [1,2,3] %}{% if i is first %}^{% endif %}{{i}}{% if i is last %}${% endif %}{% endfor %}"), "^123$")
}
func testCommentStatement() {
XCTAssertEqual(eval("Personal {# random comment #}Computer"), "Personal Computer")
}
func testMacroStatement() {
XCTAssertEqual(eval("{% macro double(value) %}value * 2{% endmacro %}{{ double(4) }}"), "8")
XCTAssertEqual(eval("{% macro concat(a, b) %}a + b{% endmacro %}{{ concat('Hello ', 'World!') }}"), "Hello World!")
}
func testBlockStatement() {
XCTAssertEqual(eval("Title: {% block title1 %}Original{% endblock %}."), "Title: Original.")
XCTAssertEqual(eval("Title: {% block title2 %}Original{% endblock %}.{% block title2 %}Other{% endblock %}"), "Title: Other.")
XCTAssertEqual(eval("Title: {% block title3 %}Original{% endblock %}.{% block title3 %}{{ parent() }} 2{% endblock %}"), "Title: Original 2.")
XCTAssertEqual(eval("Title: {% block title4 %}Original{% endblock %}.{% block title4 %}{{ parent() }} 2{% endblock %}{% block title4 %}{{ parent() }}.1{% endblock %}"), "Title: Original 2.1.")
XCTAssertEqual(eval("{% block title5 %}Hello {{name}}{% endblock %}{% block title5 %}{{ parent() }}!{% endblock %}", ["name": "George"]), "Hello George!")
XCTAssertEqual(eval("{% block title6 %}Hello {{name}}{% endblock %}{% block title6 %}{{ parent(name='Laszlo') }}!{% endblock %}", ["name": "Geroge"]), "Hello Laszlo!")
}
func testSpaceElimination() {
XCTAssertEqual(eval("asd {-} jkl"), "asdjkl")
XCTAssertEqual(eval("{-} jkl"), "jkl")
XCTAssertEqual(eval("asd {-}"), "asd")
XCTAssertEqual(eval("asd {-}{% if true %} Hello {% endif %} "), "asd Hello ")
XCTAssertEqual(eval("asd {-}{% if true %}{-} Hello {% endif %} "), "asdHello ")
XCTAssertEqual(eval("asd {% if true %} Hello {-} {% endif %} "), "asd Hello ")
}
// MARK: Data types
func testString() {
XCTAssertEqual(eval("{{ 'hello' }}"), "hello")
XCTAssertEqual(eval("{{ String(1) }}"), "1")
}
func testBoolean() {
XCTAssertEqual(eval("{{ true }}"), "true")
XCTAssertEqual(eval("{{ false }}"), "false")
XCTAssertEqual(eval("{{ 1 < 2 }}"), "true")
}
func testDate() {
XCTAssertEqual(eval("{{ Date(2018,12,13).format('dd/MM/yy') }}"), "13/12/18")
}
func testInteger() {
XCTAssertEqual(eval("{{ 1 }}"), "1")
}
func testDouble() {
XCTAssertEqual(eval("{{ 2.5 }}"), "2.5")
}
func testDictionary() {
XCTAssertEqual(eval("{{ {'a': 1, 'b': 2} }}"), "[a: 1, b: 2]")
XCTAssertEqual(eval("{{ {} }}"), "[]")
}
func testArray() {
XCTAssertEqual(eval("{{ [1,2,3] }}"), "1,2,3")
XCTAssertEqual(eval("{{ [] }}"), "")
}
func testEmpty() {
XCTAssertEqual(eval("{{ null }}"), "null")
XCTAssertEqual(eval("{{ nil }}"), "null")
XCTAssertEqual(eval("{{ [].0 }}"), "null")
}
// MARK: Functions and operators
func testParentheses() {
XCTAssertEqual(eval("{{ ( 1 + 2 ) * 3 }}"), "9")
XCTAssertEqual(eval("{{ ( (9/3) + 2 ) * 3 }}"), "15")
XCTAssertEqual(eval("{{ (((2))) }}"), "2")
}
func testTernary() {
XCTAssertEqual(eval("{{ true ? 1 : 2 }}"), "1")
XCTAssertEqual(eval("{{ false ? 1 : 2 }}"), "2")
}
func testRange() {
XCTAssertEqual(eval("{{ 1...3 }}"), "1,2,3")
XCTAssertEqual(eval("{{ 'a'...'c' }}"), "a,b,c")
}
func testRangeBySteps() {
XCTAssertEqual(eval("{{ range(start=1, end=7, step=2) }}"), "1,3,5,7")
}
func testStartsWith() {
XCTAssertEqual(eval("{{ 'Hello' starts with 'H' }}"), "true")
XCTAssertEqual(eval("{{ 'Hello' starts with 'Hell' }}"), "true")
XCTAssertEqual(eval("{{ 'Hello' starts with 'Yo' }}"), "false")
}
func testEndsWith() {
XCTAssertEqual(eval("{{ 'Hello' ends with 'o' }}"), "true")
XCTAssertEqual(eval("{{ 'Hello' ends with 'ello' }}"), "true")
XCTAssertEqual(eval("{{ 'Hello' ends with 'Yo' }}"), "false")
}
func testContains() {
XCTAssertEqual(eval("{{ 'Partly' contains 'art' }}"), "true")
XCTAssertEqual(eval("{{ 'Hello' contains 'art' }}"), "false")
}
func testMatches() {
XCTAssertEqual(eval("{{ 'Partly' matches '[A-Z]art[a-z]{2}' }}"), "true")
XCTAssertEqual(eval("{{ 'Partly' matches '\\d+' }}"), "false")
}
func testConcat() {
XCTAssertEqual(eval("{{ 'This' + ' is ' + 'Sparta' }}"), "This is Sparta")
}
func testAddition() {
XCTAssertEqual(eval("{{ 1 + 2 }}"), "3")
XCTAssertEqual(eval("{{ 1 + 2 + 3 }}"), "6")
}
func testSubstraction() {
XCTAssertEqual(eval("{{ 5 - 2 }}"), "3")
XCTAssertEqual(eval("{{ 5 - 2 - 3 }}"), "0")
}
func testMultiplication() {
XCTAssertEqual(eval("{{ 5 * 2 }}"), "10")
XCTAssertEqual(eval("{{ 5 * 2 * 3 }}"), "30")
}
func testDivision() {
XCTAssertEqual(eval("{{ 5 / 5 }}"), "1")
XCTAssertEqual(eval("{{ 144 / 12 / 4 }}"), "3")
}
func testPow() {
XCTAssertEqual(eval("{{ 2 ** 5 }}"), "32")
}
func testNumericPrecedence() {
XCTAssertEqual(eval("{{ 4 + 2 * 3 }}"), "10")
XCTAssertEqual(eval("{{ 4 - 2 * 3 }}"), "-2")
XCTAssertEqual(eval("{{ 4 * 3 / 2 + 2 - 8 }}"), "0")
}
func testLessThan() {
XCTAssertEqual(eval("{{ 2 < 3 }}"), "true")
XCTAssertEqual(eval("{{ 3 < 2 }}"), "false")
XCTAssertEqual(eval("{{ 2 < 2 }}"), "false")
}
func testLessThanOrEqual() {
XCTAssertEqual(eval("{{ 2 <= 3 }}"), "true")
XCTAssertEqual(eval("{{ 3 <= 2 }}"), "false")
XCTAssertEqual(eval("{{ 2 <= 2 }}"), "true")
}
func testGreaterThan() {
XCTAssertEqual(eval("{{ 2 > 3 }}"), "false")
XCTAssertEqual(eval("{{ 3 > 2 }}"), "true")
XCTAssertEqual(eval("{{ 2 > 2 }}"), "false")
}
func testGreaterThanOrEqual() {
XCTAssertEqual(eval("{{ 2 >= 3 }}"), "false")
XCTAssertEqual(eval("{{ 3 >= 2 }}"), "true")
XCTAssertEqual(eval("{{ 2 >= 2 }}"), "true")
}
func testEquals() {
XCTAssertEqual(eval("{{ 2 == 3 }}"), "false")
XCTAssertEqual(eval("{{ 2 == 2 }}"), "true")
}
func testNotEquals() {
XCTAssertEqual(eval("{{ 2 != 2 }}"), "false")
XCTAssertEqual(eval("{{ 2 != 3 }}"), "true")
}
func testInNumericArray() {
XCTAssertEqual(eval("{{ 2 in [1,2,3] }}"), "true")
XCTAssertEqual(eval("{{ 5 in [1,2,3] }}"), "false")
}
func testInStringArray() {
XCTAssertEqual(eval("{{ 'a' in ['a', 'b', 'c'] }}"), "true")
XCTAssertEqual(eval("{{ 'z' in ['a', 'b', 'c'] }}"), "false")
}
func testIncrement() {
XCTAssertEqual(eval("{{ 2++ }}"), "3")
XCTAssertEqual(eval("{{ -1++ }}"), "0")
}
func testDecrement() {
XCTAssertEqual(eval("{{ 7-- }}"), "6")
XCTAssertEqual(eval("{{ -7-- }}"), "-8")
}
func testNegation() {
XCTAssertEqual(eval("{{ not true }}"), "false")
XCTAssertEqual(eval("{{ not false }}"), "true")
XCTAssertEqual(eval("{{ !true }}"), "false")
XCTAssertEqual(eval("{{ !false }}"), "true")
}
func testAnd() {
XCTAssertEqual(eval("{{ true and true }}"), "true")
XCTAssertEqual(eval("{{ false and false }}"), "false")
XCTAssertEqual(eval("{{ true and false }}"), "false")
XCTAssertEqual(eval("{{ false and true }}"), "false")
}
func testOr() {
XCTAssertEqual(eval("{{ true or true }}"), "true")
XCTAssertEqual(eval("{{ false or false }}"), "false")
XCTAssertEqual(eval("{{ true or false }}"), "true")
XCTAssertEqual(eval("{{ false or true }}"), "true")
}
func testIsEven() {
XCTAssertEqual(eval("{{ 8 is even }}"), "true")
XCTAssertEqual(eval("{{ 1 is even }}"), "false")
XCTAssertEqual(eval("{{ -1 is even }}"), "false")
}
func testIsOdd() {
XCTAssertEqual(eval("{{ 8 is odd }}"), "false")
XCTAssertEqual(eval("{{ 1 is odd }}"), "true")
XCTAssertEqual(eval("{{ -1 is odd }}"), "true")
}
func testMax() {
XCTAssertEqual(eval("{{ [5,3,7,1].max }}"), "7")
XCTAssertEqual(eval("{{ max(5,3,7,1) }}"), "7")
XCTAssertEqual(eval("{{ [-5,-3,-7,-1].max }}"), "-1")
XCTAssertEqual(eval("{{ max(-5,-3,-7,-1) }}"), "-1")
}
func testMin() {
XCTAssertEqual(eval("{{ [5,3,7,1].min }}"), "1")
XCTAssertEqual(eval("{{ min(5,3,7,1) }}"), "1")
XCTAssertEqual(eval("{{ [-5,-3,-7,-1].min }}"), "-7")
XCTAssertEqual(eval("{{ min(-5,-3,-7,-1) }}"), "-7")
}
func testCount() {
XCTAssertEqual(eval("{{ [5,3,7,1].count }}"), "4")
XCTAssertEqual(eval("{{ [].count }}"), "0")
XCTAssertEqual(eval("{{ {'a': 5, 'b': 2}.count }}"), "2")
XCTAssertEqual(eval("{{ {}.count }}"), "0")
}
func testAverage() {
XCTAssertEqual(eval("{{ [1,2,3,4].avg }}"), "2.5")
XCTAssertEqual(eval("{{ [2,2].avg }}"), "2")
XCTAssertEqual(eval("{{ avg(1,2,3,4) }}"), "2.5")
XCTAssertEqual(eval("{{ avg(2,2) }}"), "2")
}
func testSum() {
XCTAssertEqual(eval("{{ [1,2,3,4].sum }}"), "10")
XCTAssertEqual(eval("{{ sum(1,2,3,4) }}"), "10")
}
func testSqrt() {
XCTAssertEqual(eval("{{ sqrt(225) }}"), "15")
XCTAssertEqual(eval("{{ sqrt(4) }}"), "2")
}
func testFirst() {
XCTAssertEqual(eval("{{ [1,2,3].first }}"), "1")
XCTAssertEqual(eval("{{ [].first }}"), "null")
}
func testLast() {
XCTAssertEqual(eval("{{ [1,2,3].last }}"), "3")
XCTAssertEqual(eval("{{ [].last }}"), "null")
}
func testDefault() {
XCTAssertEqual(eval("{{ null.default('fallback') }}"), "fallback")
XCTAssertEqual(eval("{{ array.last.default('none') }}", ["array": [1]]), "1")
XCTAssertEqual(eval("{{ array.last.default('none') }}", ["array": []]), "none")
XCTAssertEqual(eval("{{ array.last.default(2) }}", ["array": []]), "2")
}
func testJoin() {
XCTAssertEqual(eval("{{ ['1','2','3'].join('-') }}"), "1-2-3")
XCTAssertEqual(eval("{{ [].join('-') }}"), "")
}
func testSplit() {
XCTAssertEqual(eval("{{ 'a,b,c'.split(',') }}"), "a,b,c")
XCTAssertEqual(eval("{{ 'a'.split('-') }}"), "a")
}
func testMerge() {
XCTAssertEqual(eval("{{ [1,2,3].merge([4,5]) }}"), "1,2,3,4,5")
XCTAssertEqual(eval("{{ [].merge([1]) }}"), "1")
}
func testArraySubscript() {
XCTAssertEqual(eval("{{ array.1 }}", ["array": [1, 2, 3]]), "2")
XCTAssertEqual(eval("{{ [1,2,3].1 }}"), "2")
XCTAssertEqual(eval("{{ ['a', 'b', 'c'].1 }}"), "b")
}
func testArrayMap() {
XCTAssertEqual(eval("{{ [1,2,3].map(i => i * 2) }}"), "2,4,6")
}
func testArrayFilter() {
XCTAssertEqual(eval("{{ [1,2,3].filter(i => i % 2 == 1) }}"), "1,3")
}
func testDictionaryFilter() {
XCTAssertEqual(eval("{{ {'a': 1, 'b': 2}.filter(k,v => k == 'a') }}"), "[a: 1]")
}
func testDictionarySubscript() {
XCTAssertEqual(eval("{{ dict.b }}", ["dict": ["a": 1, "b": 2]]), "2")
XCTAssertEqual(eval("{{ {'a': 1, 'b': 2}.b }}"), "2")
}
func testDictionaryKeys() {
XCTAssertEqual(eval("{{ {'a': 1, 'b': 2}.keys }}"), "a,b")
}
func testDictionaryValues() {
XCTAssertEqual(eval("{{ {'a': 1, 'b': 2}.values }}"), "1,2")
}
func testAbsolute() {
XCTAssertEqual(eval("{{ 1.abs }}"), "1")
XCTAssertEqual(eval("{{ -1.abs }}"), "1")
}
func testRound() {
XCTAssertEqual(eval("{{ round(2.5) }}"), "3")
XCTAssertEqual(eval("{{ round(1.2) }}"), "1")
}
func testTrim() {
XCTAssertEqual(eval("{{ ' a '.trim }}"), "a")
}
func testEscape() {
XCTAssertEqual(eval("{{ ' ?&:/'.escape }}"), " ?&:/")
}
func testUrlEncode() {
XCTAssertEqual(eval("{{ ' ?&:/'.urlEncode }}"), "%20%3F%26%3A%2F")
}
func testUrlDecode() {
XCTAssertEqual(eval("{{ '%20%3F%26%3A%2F'.urlDecode }}"), " ?&:/")
}
func testNl2br() {
XCTAssertEqual(eval("{{ 'a\nb'.nl2br }}"), "a<br/>b")
}
func testCapitalise() {
XCTAssertEqual(eval("{{ 'hello there'.capitalise }}"), "Hello There")
}
func testUpper() {
XCTAssertEqual(eval("{{ 'hello there'.upper }}"), "HELLO THERE")
}
func testLower() {
XCTAssertEqual(eval("{{ 'HELLO THERE'.lower }}"), "hello there")
}
func testUpperFirst() {
XCTAssertEqual(eval("{{ 'hello there'.upperFirst }}"), "Hello there")
}
func testLowerFirst() {
XCTAssertEqual(eval("{{ 'HELLO THERE'.lowerFirst }}"), "hELLO THERE")
}
func testUpperCapitalise() {
XCTAssertEqual(eval("{{ 'hello there'.capitalise.upperFirst }}"), "Hello There")
}
func testLowerCapitalise() {
XCTAssertEqual(eval("{{ 'HELLO THERE'.capitalise.lowerFirst }}"), "hello There")
}
// MARK: Whitespace truncation
func testSpacelessTag() {
XCTAssertEqual(eval("{% spaceless %} {% if true %} Hello {% endif %} {% endspaceless %}"), "Hello")
}
// MARK: Template file
func testTemplateFile() {
let result = try! interpreter.evaluate(template: Bundle(for: type(of: self)).url(forResource: "template", withExtension: "txt")!, context: Context(variables: ["name": "Laszlo"]))
XCTAssertEqual(result, "Hello Laszlo!")
}
func testTemplateWithImportFile() {
let result = try! interpreter.evaluate(template: Bundle(for: type(of: self)).url(forResource: "import", withExtension: "txt")!, context: Context(variables: ["name": "Laszlo"]))
XCTAssertEqual(result, "Hello Laszlo!\nBye!")
}
// MARK: Helpers
func eval(_ template: String, _ variables: [String: Any] = [:]) -> String {
let context = Context(variables: variables)
let result = interpreter.evaluate(template, context: context)
if !context.debugInfo.isEmpty {
// print(context.debugInfo)
}
return result
}
}
================================================
FILE: Examples/TemplateExample/Tests/TemplateExampleTests/import.txt
================================================
{% import 'template.txt' %}
Bye!
================================================
FILE: Examples/TemplateExample/Tests/TemplateExampleTests/template.txt
================================================
Hello {{name}}!
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
gem 'jazzy', '>=0.9'
gem 'xcpretty'
gem 'xcpretty-json-formatter'
gem 'cocoapods'
gem 'danger'
gem 'danger-auto_label'
gem 'danger-commit_lint'
gem 'danger-mention'
gem 'danger-pronto'
gem 'danger-prose'
gem 'danger-shellcheck'
# gem 'danger-slather'
gem 'danger-swiftlint'
gem 'danger-tailor'
gem 'danger-welcome_message'
gem 'danger-xcode_summary'
gem 'danger-xcodebuild'
gem 'danger-xcov'
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "Eval",
products: [
.library(
name: "Eval",
targets: ["Eval"]),
],
dependencies: [
],
targets: [
.target(
name: "Eval",
dependencies: []),
.testTarget(
name: "EvalTests",
dependencies: ["Eval"]),
]
)
================================================
FILE: README.md
================================================
# { Eval }
[](https://travis-ci.org/tevelee/Eval)
[]()
[]()
[](https://tevelee.github.io/Eval)
[](https://codecov.io/gh/tevelee/Eval)
[]()
[](https://github.com/tevelee/Eval/tree/master/LICENSE.txt)
##### Dependency Managers
[](http://cocoapods.org/pods/Eval)
[](https://github.com/apple/swift-package-manager)
[](https://github.com/Carthage/Carthage)
---
- [👨🏻💻 About](#-about)
- [📈 Getting Started](#-getting-started)
- [🤓 Short Example](#-short-example)
- [⚡️ Installation](#%EF%B8%8F-installation)
- [⁉️ How does it work?](#%EF%B8%8F-how-does-it-work)
- [🏃🏻 Status](#-status)
- [💡 Motivation](#-motivation)
- [📚 Examples](#-examples)
- [🙋 Contribution](#-contribution)
- [👀 Details](#-details)
- [👤 Author](#-author)
- [⚖️ License](#%EF%B8%8F-license)
## 👨🏻💻 About
**Eval** is a lightweight interpreter framework written in <img src="http://www.swiftapplications.com/wp-content/uploads/2016/04/swift-logo.png" width="16"> Swift, for 📱iOS, 🖥 macOS, and 🐧Linux platforms.
It evaluates expressions at runtime, with operators and data types you define.
🍏 Pros | 🍎 Cons
------- | --------
🐥 Lightweight - the whole engine is really just a few hundred lines of code | 🤓 Creating custom operators and data types, on the other hand, can take a few extra lines - depending on your needs
✅ Easy to use API - create new language elements in just a matter of seconds | ♻️ The evaluated result of the expressions must be strongly typed, so you can only accept what type you expect the result is going to be
🎢 Fun - Since it is really easy to play with, it's joyful to add - even complex - language features | -
🚀 Fast execution - I'm trying to optimise as much as possible. Has its limitations though | 🌧 Since it is a really generic concept, some optimisations cannot be made, compared to native interpreters
The framework currently supports two different types of execution modes:
- **Strongly typed expressions**: like a programming language
- **Template languages**: evaluating expressions in arbitrary string environments
*Let's see just a few examples:*
It's extremely easy to formulate expressions (and evaluate them at runtime), like
- `5 in 1...3` evaluates to `false` Bool type
- `'Eval' starts with 'E'` evaluates to `true` Bool type
- `'b' in ['a','c','d']` evaluates to `false` Bool type
- `x < 2 ? 'a' : 'b'` evaluates to `"a"` or `"b"` String type, based on the `x` Int input variable
- `Date(2018, 12, 13).format('yyyy-MM-dd')` evaluates to `"2018-12-13"` string
- `'hello'.length` evaluates to `5` Integer
- `now` evaluates to `Date()`
And templates, such as
- `{% if name != nil %}Hello{% else %}Bye{% endif %} {{ name|default('user') }}!`, whose output is `Hello Adam!` or `Bye User!`
- `Sequence: {% for i in 1...5 %}{{ 2 * i }} {% endfor %}` which is `2 4 6 8 10 `
And so on... The result of these expressions depends on the content, determined by the evaluation. It can be any type which is returned by the functions (String, [Double], Date, or even custom types of your own.)
You can find various ways of usage in the examples section below.
## 🏃🏻 Status
- [x] Library implementation
- [x] API finalisation
- [x] Swift Package Manager support
- [x] Initial documentation
- [x] Example project (template engine)
- [x] CocoaPods support
- [x] CI
- [x] Code test-coverage
- [x] v1.0
- [x] Fully detailed documentation
- [x] Contribution guides
- [x] Further example projects
- [x] Debugging helpers
- [x] v1.1
This is a really early stage of the project, I'm still deep in the process of all the open-sourcing related tasks, such as firing up a CI, creating a beautiful documentation page, managing administrative tasks around stability.
Please stay tuned for the updates!
## 📈 Getting started
For the expressions to work, you'll need to create an interpreter instance, providing your data types and expressions you aim to support, and maybe some input variables - if you need any.
```swift
let interpreter = TypedInterpreter(dataTypes: [number, string, boolean, array, date],
functions: [multipication, addition, ternary],
context: Context(variables: ["x": 2.0]))
```
And call it with a string expression, as follows.
```swift
let result = interpreter.evaluate("2 * x + 1") as? Double
```
### 🤓 Short example
Let's check out a fairly complex example, and build it from scratch! Let's implement a language which can parse the following expression:
```swift
x != 0 ? 5 * x : pi + 1
```
There's a ternary operator `?:` in there, which we will need. Also, supporting number literals (`0`, `5`, and `1`) and boolean types (`true/false`). There's also a not equal operator `!=` and a `pi` constant. Let's not forget about the addition `+` and multiplication `*` as well!
First, here are the data types.
```swift
let numberLiteral = Literal { value,_ in Double(value) } //Converts every number literal, if it can be represented with a Double instance
let piConstant = Literal("pi", convertsTo: Double.pi)
let number = DataType(type: Double.self, literals: [numberLiteral, piConstant]) { String(describing: $0) }
```
```swift
let trueLiteral = Literal("true", convertsTo: true)
let falseLiteral = Literal("false", convertsTo: false)
let boolean = DataType(type: Bool.self, literals: [trueLiteral, falseLiteral]) { $0 ? "true" : "false" }
```
(The last parameter, expressed as a block, tells the framework how to formulise this type of data as a String for debug messages or other purposes)
Now, let's build the operators:
```swift
let multiplication = Function<Double>(Variable<Double>("lhs") + Keyword("*") + Variable<Double>("rhs")) { arguments in
guard let lhs = arguments["lh
gitextract_vfh2ubbu/ ├── .codecov.yml ├── .github/ │ └── .config.yml ├── .gitignore ├── .jazzy.yml ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── .version ├── CONTRIBUTING.md ├── Dangerfile ├── Documentation/ │ ├── Example projects.md │ ├── Interpreter engine details.md │ ├── Strongly-typed evaluator.md │ ├── Template evaluator.md │ └── Tips & Tricks.md ├── Eval.playground/ │ ├── Contents.swift │ ├── Sources/ │ │ ├── Helpers.swift │ │ └── TypesAndFunctions.swift │ ├── contents.xcplayground │ ├── playground.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── Playground.xcscheme ├── Eval.podspec ├── Eval.xcodeproj/ │ ├── EvalTests_Info.plist │ ├── Eval_Info.plist │ ├── project.pbxproj │ └── xcshareddata/ │ └── xcschemes/ │ ├── Eval-Package.xcscheme │ └── xcschememanagement.plist ├── Eval.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── Examples/ │ ├── .swiftlint.yml │ ├── AttributedStringExample/ │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources/ │ │ │ └── AttributedStringExample/ │ │ │ └── TemplateExample.swift │ │ └── Tests/ │ │ ├── .swiftlint.yml │ │ ├── AttributedStringExampleTests/ │ │ │ └── AttributedStringExampleTests.swift │ │ └── LinuxMain.swift │ ├── ColorParserExample/ │ │ ├── .gitignore │ │ ├── Package.swift │ │ ├── README.md │ │ ├── Sources/ │ │ │ └── ColorParserExample/ │ │ │ └── ColorParserExample.swift │ │ └── Tests/ │ │ ├── .swiftlint.yml │ │ ├── ColorParserExampleTests/ │ │ │ └── ColorParserExampleTests.swift │ │ └── LinuxMain.swift │ └── TemplateExample/ │ ├── .gitignore │ ├── Package.swift │ ├── README.md │ ├── Sources/ │ │ └── TemplateExample/ │ │ └── TemplateExample.swift │ └── Tests/ │ ├── .swiftlint.yml │ ├── LinuxMain.swift │ └── TemplateExampleTests/ │ ├── TemplateExampleComponentTests.swift │ ├── TemplateExampleTests.swift │ ├── import.txt │ └── template.txt ├── Gemfile ├── LICENSE.txt ├── Package.swift ├── README.md ├── Scripts/ │ ├── .gitignore │ ├── .swiftlint.yml │ ├── Package.swift │ ├── Sources/ │ │ └── Automation/ │ │ ├── Error.swift │ │ ├── Eval.swift │ │ ├── Shell.swift │ │ ├── Travis.swift │ │ └── main.swift │ ├── ci.sh │ └── git_auth.sh ├── Sources/ │ └── Eval/ │ ├── Common.swift │ ├── Elements.swift │ ├── TemplateInterpreter.swift │ ├── TypedInterpreter.swift │ └── Utilities/ │ ├── MatchResult.swift │ ├── Matcher.swift │ ├── Pattern.swift │ └── Utils.swift ├── Tests/ │ ├── .swiftlint.yml │ ├── EvalTests/ │ │ ├── IntegrationTests/ │ │ │ ├── InterpreterTests.swift │ │ │ ├── PerformanceTest.swift │ │ │ ├── Suffix.swift │ │ │ └── TemplateTests.swift │ │ ├── UnitTests/ │ │ │ ├── DataTypeTests.swift │ │ │ ├── FunctionTests.swift │ │ │ ├── InterpreterContextTests.swift │ │ │ ├── KeywordTests.swift │ │ │ ├── LiteralTests.swift │ │ │ ├── MatchResultTests.swift │ │ │ ├── MatchStatementTests.swift │ │ │ ├── MatcherTests.swift │ │ │ ├── PatternTests.swift │ │ │ ├── TemplateInterpreterTests.swift │ │ │ ├── TypedInterpreterTests.swift │ │ │ ├── UtilTests.swift │ │ │ ├── VariableProcessor.swift │ │ │ └── VariableTests.swift │ │ └── Utils.swift │ └── LinuxMain.swift └── github_rsa.enc
Condensed preview — 98 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (407K chars).
[
{
"path": ".codecov.yml",
"chars": 253,
"preview": "coverage:\n ignore:\n - Applications/Xcode.app/.*\n - build/.*\n - .build/.*\n - Documentation/.*\n - Example/Pods/.*\n "
},
{
"path": ".github/.config.yml",
"chars": 920,
"preview": "updateDocsComment: >\n Thanks for opening this pull request! The maintainers of this repository would appreciate it if y"
},
{
"path": ".gitignore",
"chars": 117,
"preview": ".DS_Store\n/.build\nbuild\n/Packages\n\nxcuserdata\n.xcuserstate\n\nPods\nPackage.resolved\n\ndocs\ngh-pages\nDocumentation/Output"
},
{
"path": ".jazzy.yml",
"chars": 479,
"preview": "author: Laszlo Teveli\nauthor_url: https://tevelee.github.io\nreadme: README.md\n\ndocumentation: Documentation/*.md\nabstrac"
},
{
"path": ".swift-version",
"chars": 3,
"preview": "5.0"
},
{
"path": ".swiftlint.yml",
"chars": 4513,
"preview": "reporter: \"xcode\"\nopt_in_rules:\n - array_init\n - attributes\n - block_based_kvo\n - class_delegate_protocol\n - closin"
},
{
"path": ".travis.yml",
"chars": 271,
"preview": "osx_image: xcode11.3\nlanguage: swift\nsudo: true\nenv:\n global:\n - EXPANDED_CODE_SIGN_IDENTITY=\"-\"\n - EXPANDED_CODE"
},
{
"path": ".version",
"chars": 6,
"preview": "1.5.0\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 2260,
"preview": "# Contributing to Eval\n\n## 👍🎉 First off, thanks for taking the time to contribute! 🎉👍\n\n**You are more than welcomed to d"
},
{
"path": "Dangerfile",
"chars": 3233,
"preview": "# Sometimes it's a README fix, or something like that - which isn't relevant for\n# including in a project's CHANGELOG fo"
},
{
"path": "Documentation/Example projects.md",
"chars": 2243,
"preview": "# Example projects\n\nI included a few use-cases, which bring significant improvements on how things are processed before "
},
{
"path": "Documentation/Interpreter engine details.md",
"chars": 73,
"preview": "# Technical details\n\n## Interpreter engine\n\nTBD\n\n## Template engine \n\nTBD"
},
{
"path": "Documentation/Strongly-typed evaluator.md",
"chars": 11653,
"preview": "# Strongly typed evaluator\n\nThis kind of evaluator interprets its input as one function. It searches for the one with th"
},
{
"path": "Documentation/Template evaluator.md",
"chars": 10248,
"preview": "# Template evaluator\n\nThe logic of the interpreter is fairly easy: it goes over the input character by character and rep"
},
{
"path": "Documentation/Tips & Tricks.md",
"chars": 7071,
"preview": "# Tips & Tricks\n\nThe following sections provide handy Tips and Tricks to help you effectively build up your own interpre"
},
{
"path": "Eval.playground/Contents.swift",
"chars": 748,
"preview": "//: Playground - noun: a place where people can play\nimport Foundation\nimport Eval\n\nlet context = InterpreterContext()\n\n"
},
{
"path": "Eval.playground/Sources/Helpers.swift",
"chars": 4767,
"preview": "import Foundation\nimport Eval\n\npublic func infixOperator<A,B,T>(_ symbol: String, body: @escaping (A, B) -> T) -> Functi"
},
{
"path": "Eval.playground/Sources/TypesAndFunctions.swift",
"chars": 5077,
"preview": "import Foundation\nimport Eval\n\n//MARK: Double\n\npublic let numberDataType = DataType(type: Double.self, literals:[\n "
},
{
"path": "Eval.playground/contents.xcplayground",
"chars": 186,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<playground version='5.0' target-platform='macos' display-mode='"
},
{
"path": "Eval.playground/playground.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "Eval.playground/xcshareddata/xcschemes/Playground.xcscheme",
"chars": 2781,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1020\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Eval.podspec",
"chars": 907,
"preview": "Pod::Spec.new do |s|\n s.name = \"Eval\"\n s.version = \"1.5.0\"\n s.summary = \"Eval is a lightweight interpreter framework "
},
{
"path": "Eval.xcodeproj/EvalTests_Info.plist",
"chars": 723,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<plist version=\"1.0\">\n<dict>\n <key>CFBundleDevelopmentRegion</key>\n <string>en<"
},
{
"path": "Eval.xcodeproj/Eval_Info.plist",
"chars": 723,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<plist version=\"1.0\">\n<dict>\n <key>CFBundleDevelopmentRegion</key>\n <string>en<"
},
{
"path": "Eval.xcodeproj/project.pbxproj",
"chars": 27041,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXAggregateTarget sec"
},
{
"path": "Eval.xcodeproj/xcshareddata/xcschemes/Eval-Package.xcscheme",
"chars": 2967,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1020\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Eval.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist",
"chars": 247,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<plist version=\"1.0\">\n<dict>\n <key>SchemeUserState</key>\n <dict>\n <key>Eval-"
},
{
"path": "Eval.xcworkspace/contents.xcworkspacedata",
"chars": 222,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Eval.playgroun"
},
{
"path": "Eval.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Examples/.swiftlint.yml",
"chars": 31,
"preview": "disabled_rules:\n - file_header"
},
{
"path": "Examples/AttributedStringExample/.gitignore",
"chars": 40,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj"
},
{
"path": "Examples/AttributedStringExample/Package.swift",
"chars": 573,
"preview": "// swift-tools-version:4.2\n\nimport PackageDescription\n\nlet package = Package(\n name: \"AttributedStringExample\",\n p"
},
{
"path": "Examples/AttributedStringExample/README.md",
"chars": 50,
"preview": "# TemplateExample\n\nA description of this package.\n"
},
{
"path": "Examples/AttributedStringExample/Sources/AttributedStringExample/TemplateExample.swift",
"chars": 2862,
"preview": "import AppKit\n@_exported import Eval\nimport Foundation\n@_exported import class Eval.Pattern\n\n// swiftlint:disable:next t"
},
{
"path": "Examples/AttributedStringExample/Tests/.swiftlint.yml",
"chars": 101,
"preview": "disabled_rules:\n - force_cast\n - force_try\n - type_name\n - file_header\n - explicit_top_level_acl"
},
{
"path": "Examples/AttributedStringExample/Tests/AttributedStringExampleTests/AttributedStringExampleTests.swift",
"chars": 846,
"preview": "@testable import AttributedStringExample\nimport Eval\nimport XCTest\n\nclass AttributedStringExampleTests: XCTestCase {\n "
},
{
"path": "Examples/AttributedStringExample/Tests/LinuxMain.swift",
"chars": 110,
"preview": "@testable import TemplateExampleTests\nimport XCTest\n\nXCTMain([\n testCase(TemplateExampleTests.allTests)\n])\n"
},
{
"path": "Examples/ColorParserExample/.gitignore",
"chars": 40,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj"
},
{
"path": "Examples/ColorParserExample/Package.swift",
"chars": 543,
"preview": "// swift-tools-version:4.2\n\nimport PackageDescription\n\nlet package = Package(\n name: \"ColorParserExample\",\n produc"
},
{
"path": "Examples/ColorParserExample/README.md",
"chars": 50,
"preview": "# TemplateExample\n\nA description of this package.\n"
},
{
"path": "Examples/ColorParserExample/Sources/ColorParserExample/ColorParserExample.swift",
"chars": 2668,
"preview": "import AppKit\n@_exported import Eval\n@_exported import class Eval.Pattern\nimport Foundation\n\npublic class ColorParser: E"
},
{
"path": "Examples/ColorParserExample/Tests/.swiftlint.yml",
"chars": 101,
"preview": "disabled_rules:\n - force_cast\n - force_try\n - type_name\n - file_header\n - explicit_top_level_acl"
},
{
"path": "Examples/ColorParserExample/Tests/ColorParserExampleTests/ColorParserExampleTests.swift",
"chars": 531,
"preview": "@testable import ColorParserExample\nimport Eval\nimport XCTest\n\nclass ColorParserExampleTests: XCTestCase {\n let color"
},
{
"path": "Examples/ColorParserExample/Tests/LinuxMain.swift",
"chars": 110,
"preview": "@testable import TemplateExampleTests\nimport XCTest\n\nXCTMain([\n testCase(TemplateExampleTests.allTests)\n])\n"
},
{
"path": "Examples/TemplateExample/.gitignore",
"chars": 41,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\n"
},
{
"path": "Examples/TemplateExample/Package.swift",
"chars": 525,
"preview": "// swift-tools-version:4.2\n\nimport PackageDescription\n\nlet package = Package(\n name: \"TemplateExample\",\n products:"
},
{
"path": "Examples/TemplateExample/README.md",
"chars": 50,
"preview": "# TemplateExample\n\nA description of this package.\n"
},
{
"path": "Examples/TemplateExample/Sources/TemplateExample/TemplateExample.swift",
"chars": 55425,
"preview": "@_exported import Eval\n@_exported import class Eval.Pattern\nimport Foundation\n\npublic class TemplateLanguage: EvaluatorW"
},
{
"path": "Examples/TemplateExample/Tests/.swiftlint.yml",
"chars": 122,
"preview": "disabled_rules:\n - force_cast\n - force_try\n - force_unwrapping\n - type_name\n - file_header\n - explicit_top_level_a"
},
{
"path": "Examples/TemplateExample/Tests/LinuxMain.swift",
"chars": 110,
"preview": "@testable import TemplateExampleTests\nimport XCTest\n\nXCTMain([\n testCase(TemplateExampleTests.allTests)\n])\n"
},
{
"path": "Examples/TemplateExample/Tests/TemplateExampleTests/TemplateExampleComponentTests.swift",
"chars": 905,
"preview": "import Eval\n@testable import TemplateExample\nimport XCTest\n\nclass TemplateExampleComponentTests: XCTestCase {\n let in"
},
{
"path": "Examples/TemplateExample/Tests/TemplateExampleTests/TemplateExampleTests.swift",
"chars": 16727,
"preview": "import Eval\n@testable import TemplateExample\nimport XCTest\n\nclass TemplateExampleTests: XCTestCase {\n let interpreter"
},
{
"path": "Examples/TemplateExample/Tests/TemplateExampleTests/import.txt",
"chars": 32,
"preview": "{% import 'template.txt' %}\nBye!"
},
{
"path": "Examples/TemplateExample/Tests/TemplateExampleTests/template.txt",
"chars": 15,
"preview": "Hello {{name}}!"
},
{
"path": "Gemfile",
"chars": 425,
"preview": "source 'https://rubygems.org'\n\ngem 'jazzy', '>=0.9'\n\ngem 'xcpretty'\ngem 'xcpretty-json-formatter'\n\ngem 'cocoapods'\n\ngem "
},
{
"path": "LICENSE.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Package.swift",
"chars": 408,
"preview": "// swift-tools-version:5.0\n\nimport PackageDescription\n\nlet package = Package(\n name: \"Eval\",\n products: [\n "
},
{
"path": "README.md",
"chars": 17287,
"preview": "# { Eval }\n\n[](https://travis-ci.org/tevelee/Ev"
},
{
"path": "Scripts/.gitignore",
"chars": 41,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\n"
},
{
"path": "Scripts/.swiftlint.yml",
"chars": 114,
"preview": "disabled_rules:\n - force_try\n - file_header\n - file_length\n - explicit_top_level_acl\n - function_body_length\n"
},
{
"path": "Scripts/Package.swift",
"chars": 330,
"preview": "// swift-tools-version:4.2\n\nimport PackageDescription\n\nlet package = Package(\n name: \"Automation\",\n dependencies: "
},
{
"path": "Scripts/Sources/Automation/Error.swift",
"chars": 165,
"preview": "import Foundation\n\nenum CIError: Error {\n case invalidExitCode(statusCode: Int32, errorOutput: String?)\n case time"
},
{
"path": "Scripts/Sources/Automation/Eval.swift",
"chars": 14065,
"preview": "import Foundation\nimport PathKit\nimport xcproj\n\nclass Eval {\n static func main() {\n print(\"💁🏻♂️ Job type: \\(T"
},
{
"path": "Scripts/Sources/Automation/Shell.swift",
"chars": 4254,
"preview": "import Foundation\n\nclass Shell {\n static func executeAndPrint(_ command: String, timeout: Double = 10, allowFailure: "
},
{
"path": "Scripts/Sources/Automation/Travis.swift",
"chars": 2214,
"preview": "import Foundation\n\nclass TravisCI {\n enum JobType: CustomStringConvertible {\n case local\n case travisAP"
},
{
"path": "Scripts/Sources/Automation/main.swift",
"chars": 31,
"preview": "import Foundation\n\nEval.main()\n"
},
{
"path": "Scripts/ci.sh",
"chars": 228,
"preview": "#!/bin/bash\n\necho \"🤖 Assembling automation process\"\n\nroot=`git rev-parse --show-toplevel`\ncd \"$root/Scripts\"\nswift build"
},
{
"path": "Scripts/git_auth.sh",
"chars": 319,
"preview": "#!/bin/bash\n\nopenssl aes-256-cbc -K $encrypted_f50468713ad3_key -iv $encrypted_f50468713ad3_iv -in github_rsa.enc -out g"
},
{
"path": "Sources/Eval/Common.swift",
"chars": 10915,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/Elements.swift",
"chars": 12152,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/TemplateInterpreter.swift",
"chars": 9820,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/TypedInterpreter.swift",
"chars": 19398,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/Utilities/MatchResult.swift",
"chars": 4245,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/Utilities/Matcher.swift",
"chars": 15488,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/Utilities/Pattern.swift",
"chars": 9785,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Sources/Eval/Utilities/Utils.swift",
"chars": 2136,
"preview": "/*\n * Copyright (c) 2018 Laszlo Teveli.\n *\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more c"
},
{
"path": "Tests/.swiftlint.yml",
"chars": 108,
"preview": "disabled_rules:\n - force_cast\n - force_unwrapping\n - file_header\n - type_name\n - explicit_top_level_acl"
},
{
"path": "Tests/EvalTests/IntegrationTests/InterpreterTests.swift",
"chars": 18832,
"preview": "@testable import Eval\nimport class Eval.Pattern\nimport XCTest\n\nclass InterpreterTests: XCTestCase {\n // swiftlint:dis"
},
{
"path": "Tests/EvalTests/IntegrationTests/PerformanceTest.swift",
"chars": 2948,
"preview": "//\n// PerformanceTest.swift\n// EvalTests\n//\n// Created by László Teveli on 2019. 09. 14..\n//\n\nimport XCTest\n@testable"
},
{
"path": "Tests/EvalTests/IntegrationTests/Suffix.swift",
"chars": 3935,
"preview": "//\n// Suffix.swift\n// Eval\n//\n// Created by László Teveli on 2019. 09. 14..\n//\n\nimport Foundation\nimport XCTest\n@test"
},
{
"path": "Tests/EvalTests/IntegrationTests/TemplateTests.swift",
"chars": 5360,
"preview": "@testable import Eval\nimport class Eval.Pattern\nimport XCTest\n\nclass TemplateTests: XCTestCase {\n func test_flow() {\n"
},
{
"path": "Tests/EvalTests/UnitTests/DataTypeTests.swift",
"chars": 1483,
"preview": "@testable import Eval\nimport XCTest\n\nclass DataTypeTests: XCTestCase {\n\n // MARK: init\n\n func test_whenInitialised"
},
{
"path": "Tests/EvalTests/UnitTests/FunctionTests.swift",
"chars": 1354,
"preview": "@testable import Eval\nimport XCTest\nimport class Eval.Pattern\n\nclass FunctionTests: XCTestCase {\n\n // MARK: init\n\n "
},
{
"path": "Tests/EvalTests/UnitTests/InterpreterContextTests.swift",
"chars": 3540,
"preview": "@testable import Eval\nimport XCTest\n\nclass InterpreterContextTests: XCTestCase {\n\n // MARK: init\n\n func test_whenC"
},
{
"path": "Tests/EvalTests/UnitTests/KeywordTests.swift",
"chars": 5723,
"preview": "@testable import Eval\nimport XCTest\n\nclass KeywordTests: XCTestCase {\n\n // MARK: Initialisation\n\n func test_whenKe"
},
{
"path": "Tests/EvalTests/UnitTests/LiteralTests.swift",
"chars": 793,
"preview": "@testable import Eval\nimport XCTest\n\nclass LiteralTests: XCTestCase {\n\n // MARK: init\n\n func test_whenInitialisedW"
},
{
"path": "Tests/EvalTests/UnitTests/MatchResultTests.swift",
"chars": 3725,
"preview": "@testable import Eval\nimport XCTest\n\nclass MatchResultTests: XCTestCase {\n\n // MARK: isMatch\n\n func test_whenMatch"
},
{
"path": "Tests/EvalTests/UnitTests/MatchStatementTests.swift",
"chars": 2382,
"preview": "@testable import Eval\nimport XCTest\nimport class Eval.Pattern\n\nclass MatchStatementTests: XCTestCase {\n\n func test_wh"
},
{
"path": "Tests/EvalTests/UnitTests/MatcherTests.swift",
"chars": 3612,
"preview": "@testable import Eval\nimport XCTest\n\nclass MatcherTests: XCTestCase {\n\n // MARK: isEmbedded\n\n func test_whenEmbedd"
},
{
"path": "Tests/EvalTests/UnitTests/PatternTests.swift",
"chars": 6633,
"preview": "@testable import Eval\nimport XCTest\nimport class Eval.Pattern\n\nclass PatternTests: XCTestCase {\n\n // MARK: init\n\n "
},
{
"path": "Tests/EvalTests/UnitTests/TemplateInterpreterTests.swift",
"chars": 4957,
"preview": "@testable import Eval\nimport XCTest\nimport class Eval.Pattern\n\nclass StringTemplateInterpreterTests: XCTestCase {\n\n /"
},
{
"path": "Tests/EvalTests/UnitTests/TypedInterpreterTests.swift",
"chars": 4188,
"preview": "@testable import Eval\nimport XCTest\n\nclass TypedInterpreterTests: XCTestCase {\n\n // MARK: init\n\n func test_whenIni"
},
{
"path": "Tests/EvalTests/UnitTests/UtilTests.swift",
"chars": 1507,
"preview": "@testable import Eval\nimport XCTest\n\nclass UtilTests: XCTestCase {\n\n // MARK: plus operator on Elements\n\n class Du"
},
{
"path": "Tests/EvalTests/UnitTests/VariableProcessor.swift",
"chars": 1665,
"preview": "@testable import Eval\nimport XCTest\n\nclass VariableProcessorTests: XCTestCase {\n\n // init\n\n func test_whenInitiali"
},
{
"path": "Tests/EvalTests/UnitTests/VariableTests.swift",
"chars": 2722,
"preview": "@testable import Eval\nimport XCTest\n\nclass VariableTests: XCTestCase {\n\n // MARK: init\n\n func test_whenInitialised"
},
{
"path": "Tests/EvalTests/Utils.swift",
"chars": 445,
"preview": "import Eval\nimport Foundation\n\nclass DummyInterpreter: Interpreter {\n typealias VariableEvaluator = DummyInterpreter\n"
},
{
"path": "Tests/LinuxMain.swift",
"chars": 102,
"preview": "@testable import InterpreterTests\nimport XCTest\n\nXCTMain([\n testCase(InterpreterTests.allTests)\n])\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the tevelee/Eval GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 98 files (374.9 KB), approximately 92.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.