Showing preview only (242K chars total). Download the full file or copy to clipboard to get everything.
Repository: SwiftFiddle/swiftregex
Branch: main
Commit: b1b10fef9496
Files: 73
Total size: 223.2 KB
Directory structure:
gitextract_upkxoslv/
├── .github/
│ ├── FUNDING.yml
│ ├── renovate.json
│ └── workflows/
│ ├── codeql-analysis.yml
│ └── test.yml
├── .gitignore
├── .swift-format
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ ├── App.xcscheme
│ ├── DSLConverter.xcscheme
│ ├── ExpressionParser.xcscheme
│ ├── Matcher.xcscheme
│ └── swiftregex-Package.xcscheme
├── .vscode/
│ ├── launch.json
│ └── settings.json
├── DEPLOYMENT.md
├── Dockerfile
├── LICENSE
├── Package.resolved
├── Package.swift
├── Public/
│ ├── css/
│ │ ├── common.css
│ │ └── highlight.css
│ ├── error.html
│ ├── favicons/
│ │ ├── browserconfig.xml
│ │ └── site.webmanifest
│ ├── index.html
│ ├── index.js
│ ├── js/
│ │ ├── app.js
│ │ ├── docs/
│ │ │ └── reference.js
│ │ ├── misc/
│ │ │ ├── icons.js
│ │ │ └── utils.js
│ │ ├── runner.js
│ │ ├── state/
│ │ │ ├── decoder.js
│ │ │ ├── encoder.js
│ │ │ └── worker.js
│ │ └── views/
│ │ ├── debugger_highlighter.js
│ │ ├── debugger_text.js
│ │ ├── dsl_editor.js
│ │ ├── dsl_highlighter.js
│ │ ├── dsl_view.js
│ │ ├── editor.js
│ │ ├── error_message.js
│ │ ├── expression_field.js
│ │ ├── expression_highlighter.js
│ │ ├── match_options.js
│ │ ├── test_editor.js
│ │ └── test_highlighter.js
│ ├── robots.txt
│ └── scss/
│ └── default.scss
├── README.md
├── SECURITY.md
├── Sources/
│ ├── App/
│ │ ├── Debugger/
│ │ │ ├── Context.swift
│ │ │ ├── Debugger.swift
│ │ │ └── Executor.swift
│ │ ├── Middlewares/
│ │ │ ├── CommonErrorMiddleware.swift
│ │ │ └── CustomHeaderMiddleware.swift
│ │ ├── Models/
│ │ │ ├── ExecRequest.swift
│ │ │ └── ResultResponse.swift
│ │ ├── configure.swift
│ │ ├── entrypoint.swift
│ │ └── routes.swift
│ ├── DSLConverter/
│ │ ├── DSLConverter.swift
│ │ └── Main.swift
│ ├── ExpressionParser/
│ │ ├── ExpressionParser.swift
│ │ └── Main.swift
│ └── Matcher/
│ ├── Main.swift
│ └── Matcher.swift
├── Tests/
│ └── RegexTests/
│ ├── ConverterTests.swift
│ ├── ExpressionParserTests.swift
│ └── MatcherTests.swift
├── package.json
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: kishikawakatsumi
================================================
FILE: .github/renovate.json
================================================
{
"extends": [
"config:recommended"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true
}
]
}
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
schedule:
- cron: "37 13 * * 1"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
================================================
FILE: .github/workflows/test.yml
================================================
name: CI
on:
pull_request:
branches: [main]
workflow_dispatch:
env:
FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOME_TOKEN }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build
run: |
set -ex
docker build --rm --no-cache --build-arg FONTAWESOME_TOKEN=${{ env.FONTAWESOME_TOKEN }} .
================================================
FILE: .gitignore
================================================
### Generated by gibo (https://github.com/simonwhitaker/gibo)
### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Swift.gitignore
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
================================================
FILE: .swift-format
================================================
{
"version": 1,
"lineLength": 10000,
"indentation": {
"spaces": 2
}
}
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/App.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "App"
BuildableName = "App"
BlueprintName = "App"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</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">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "App"
BuildableName = "App"
BlueprintName = "App"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "App"
BuildableName = "App"
BlueprintName = "App"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/DSLConverter.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DSLConverter"
BuildableName = "DSLConverter"
BlueprintName = "DSLConverter"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</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">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DSLConverter"
BuildableName = "DSLConverter"
BlueprintName = "DSLConverter"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DSLConverter"
BuildableName = "DSLConverter"
BlueprintName = "DSLConverter"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/ExpressionParser.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ExpressionParser"
BuildableName = "ExpressionParser"
BlueprintName = "ExpressionParser"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</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">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ExpressionParser"
BuildableName = "ExpressionParser"
BlueprintName = "ExpressionParser"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ExpressionParser"
BuildableName = "ExpressionParser"
BlueprintName = "ExpressionParser"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/Matcher.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Matcher"
BuildableName = "Matcher"
BlueprintName = "Matcher"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</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">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Matcher"
BuildableName = "Matcher"
BlueprintName = "Matcher"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Matcher"
BuildableName = "Matcher"
BlueprintName = "Matcher"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/swiftregex-Package.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "App"
BuildableName = "App"
BlueprintName = "App"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DSLConverter"
BuildableName = "DSLConverter"
BlueprintName = "DSLConverter"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ExpressionParser"
BuildableName = "ExpressionParser"
BlueprintName = "ExpressionParser"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Matcher"
BuildableName = "Matcher"
BlueprintName = "Matcher"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "RegexTests"
BuildableName = "RegexTests"
BlueprintName = "RegexTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</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 = "App"
BuildableName = "App"
BlueprintName = "App"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Matcher"
BuildableName = "Matcher"
BlueprintName = "Matcher"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: .vscode/launch.json
================================================
{
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug DSLParser",
"program": "${workspaceFolder:swiftregex}/.build/debug/DSLParser",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug DSLParser"
},
{
"type": "lldb",
"request": "launch",
"name": "Release DSLParser",
"program": "${workspaceFolder:swiftregex}/.build/release/DSLParser",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release DSLParser"
},
{
"type": "swift",
"request": "launch",
"name": "Debug DSLConverter",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug DSLConverter",
"target": "DSLConverter",
"configuration": "debug"
},
{
"type": "swift",
"request": "launch",
"name": "Release DSLConverter",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release DSLConverter",
"target": "DSLConverter",
"configuration": "release"
},
{
"type": "swift",
"request": "launch",
"name": "Debug ExpressionParser",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug ExpressionParser",
"target": "ExpressionParser",
"configuration": "debug"
},
{
"type": "swift",
"request": "launch",
"name": "Release ExpressionParser",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release ExpressionParser",
"target": "ExpressionParser",
"configuration": "release"
},
{
"type": "swift",
"request": "launch",
"name": "Debug Matcher",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug Matcher",
"target": "Matcher",
"configuration": "debug"
},
{
"type": "swift",
"request": "launch",
"name": "Release Matcher",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release Matcher",
"target": "Matcher",
"configuration": "release"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug PatternConverter",
"program": ".build/debug/PatternConverter",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug PatternConverter"
},
{
"type": "lldb",
"request": "launch",
"name": "Release PatternConverter",
"program": ".build/release/PatternConverter",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release PatternConverter"
},
{
"type": "swift",
"request": "launch",
"sourceLanguages": ["swift"],
"name": "Debug App",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Debug App",
"target": "App",
"configuration": "debug"
},
{
"type": "swift",
"request": "launch",
"sourceLanguages": ["swift"],
"name": "Release App",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"preLaunchTask": "swift: Build Release App",
"target": "App",
"configuration": "release"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"name": "Debug Debugger",
"program": "${workspaceFolder:swiftregex}/.build/debug/Debugger",
"preLaunchTask": "swift: Build Debug Debugger"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swiftregex}",
"name": "Release Debugger",
"program": "${workspaceFolder:swiftregex}/.build/release/Debugger",
"preLaunchTask": "swift: Build Release Debugger"
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB",
"lldb.launch.expressions": "native"
}
================================================
FILE: DEPLOYMENT.md
================================================
# Deployment Instructions
## Prerequisites
Before deploying, make sure you have the following software installed on your machine:
- Node.js (v14 or newer)
- Docker (v20.10 or newer)
The following environment variables are used for deployment:
- `FONTAWESOME_TOKEN`: This token is used for authentication with the FontAwesome service. You need to obtain a valid token from your FontAwesome account and use it here. Please make sure not to expose this token publicly.
## Local Deployment
### Steps:
1. Install the dependencies:
```bash
npm install
```
2. Run Webpack to build the project:
```bash
npm run prod
```
3. Run the application:
```bash
swift run
```
You should now be able to see the application running at `localhost:8080`.
## Production Deployment
For deploying to production, we recommend using [Render](https://render.com/). Render is a platform that allows you to deploy your application to the cloud with ease. It also provides a free tier that is sufficient for deploying this application.
================================================
FILE: Dockerfile
================================================
FROM node:lts-slim as node
WORKDIR /build
ARG FONTAWESOME_TOKEN
COPY package*.json ./
RUN echo "@fortawesome:registry=https://npm.fontawesome.com/\n//npm.fontawesome.com/:_authToken=${FONTAWESOME_TOKEN}" > ./.npmrc \
&& npm ci \
&& rm -f ./.npmrc
COPY webpack.*.js ./
COPY Public ./Public/
RUN npx webpack --config webpack.prod.js
FROM swiftlang/swift:nightly-main-jammy as swift
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update && apt-get -q dist-upgrade -y \
&& apt-get install -y --no-install-recommends libsqlite3-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY --from=node /build /build
COPY ./Package.* ./
RUN swift package resolve
COPY . .
RUN swift build -c release -Xswiftc -DPROCESSOR_MEASUREMENTS_ENABLED -Xswiftc -enable-testing
WORKDIR /staging
RUN BIN_PATH="$(swift build --package-path /build -c release --show-bin-path)" \
&& cp "$BIN_PATH/App" ./ \
&& cp "$BIN_PATH/DSLConverter" ./ \
&& cp "$BIN_PATH/ExpressionParser" ./ \
&& cp "$BIN_PATH/Matcher" ./ \
&& find -L "$BIN_PATH/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true
FROM swiftlang/swift:nightly-main-jammy
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
ca-certificates \
tzdata \
&& rm -r /var/lib/apt/lists/*
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor
WORKDIR /app
COPY --from=swift --chown=vapor:vapor /staging /app
USER vapor:vapor
EXPOSE 8080
ENTRYPOINT ["./App"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Kishikawa Katsumi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.resolved
================================================
{
"pins" : [
{
"identity" : "async-http-client",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/async-http-client.git",
"state" : {
"revision" : "3a5b74a58782c3b4c1f0bc75e9b67b10c2494e8f",
"version" : "1.33.1"
}
},
{
"identity" : "async-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/async-kit.git",
"state" : {
"revision" : "6bbb83cbf9d886623a967a965c8fb1b73e6566f9",
"version" : "1.22.0"
}
},
{
"identity" : "console-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/console-kit.git",
"state" : {
"revision" : "32ad16dfc7677b927b225595ed18f3debb32f577",
"version" : "4.16.0"
}
},
{
"identity" : "leaf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/leaf.git",
"state" : {
"revision" : "b70a6108e4917f338f6b8848407bf655aa7e405f",
"version" : "4.5.1"
}
},
{
"identity" : "leaf-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/leaf-kit.git",
"state" : {
"revision" : "6044b844caa858a0c5f2505ac166f5a057c990dc",
"version" : "1.14.2"
}
},
{
"identity" : "multipart-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/multipart-kit.git",
"state" : {
"revision" : "3498e60218e6003894ff95192d756e238c01f44e",
"version" : "4.7.1"
}
},
{
"identity" : "routing-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/routing-kit.git",
"state" : {
"revision" : "1a10ccea61e4248effd23b6e814999ce7bdf0ee0",
"version" : "4.9.3"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
"version" : "1.2.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "626b5b7b2f45e1b0b1c6f4a309296d1d21d7311b",
"version" : "1.7.1"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "9f542610331815e29cc3821d3b6f488db8715517",
"version" : "1.6.0"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms.git",
"state" : {
"revision" : "9d349bcc328ac3c31ce40e746b5882742a0d1272",
"version" : "1.1.3"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
"version" : "1.3.0"
}
},
{
"identity" : "swift-certificates",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-certificates.git",
"state" : {
"revision" : "24ccdeeeed4dfaae7955fcac9dbf5489ed4f1a25",
"version" : "1.18.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
"version" : "1.4.1"
}
},
{
"identity" : "swift-configuration",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-configuration.git",
"state" : {
"revision" : "be76c4ad929eb6c4bcaf3351799f2adf9e6848a9",
"version" : "1.2.0"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "bb4ba815dab96d4edc1e0b86d7b9acf9ff973a84",
"version" : "4.3.1"
}
},
{
"identity" : "swift-distributed-tracing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-distributed-tracing.git",
"state" : {
"revision" : "dc4030184203ffafbb2ec614352487235d747fe0",
"version" : "1.4.1"
}
},
{
"identity" : "swift-experimental-string-processing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-experimental-string-processing.git",
"state" : {
"branch" : "main",
"revision" : "6a693bb139fa9e4ddf522f80fb7026b3abb0cf1d"
}
},
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b",
"version" : "1.6.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types.git",
"state" : {
"revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
"version" : "1.5.1"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "8c0f217f01000dd30f60d6e536569ad4e74291f9",
"version" : "1.11.0"
}
},
{
"identity" : "swift-metrics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-metrics.git",
"state" : {
"revision" : "f17c111cec972c2a4922cef38cf64f76f7e87886",
"version" : "2.8.0"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "558f24a4647193b5a0e2104031b71c55d31ff83a",
"version" : "2.97.1"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "abcf5312eb8ed2fb11916078aef7c46b06f20813",
"version" : "1.33.0"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "6d8d596f0a9bfebb925733003731fe2d749b7e02",
"version" : "1.42.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "df9c3406028e3297246e6e7081977a167318b692",
"version" : "2.36.1"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "60c3e187154421171721c1a38e800b390680fb5d",
"version" : "1.26.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics.git",
"state" : {
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
"version" : "1.1.1"
}
},
{
"identity" : "swift-service-context",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-service-context.git",
"state" : {
"revision" : "d0997351b0c7779017f88e7a93bc30a1878d7f29",
"version" : "1.3.0"
}
},
{
"identity" : "swift-service-lifecycle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/swift-service-lifecycle.git",
"state" : {
"revision" : "9829955b385e5bb88128b73f1b8389e9b9c3191a",
"version" : "2.11.0"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
"version" : "1.6.4"
}
},
{
"identity" : "vapor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/vapor.git",
"state" : {
"revision" : "cfd8f434843ac7850e2d97f46c1aa5ddb906cf1c",
"version" : "4.121.4"
}
},
{
"identity" : "websocket-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/websocket-kit.git",
"state" : {
"revision" : "90bbbdab3ede12c803cfbe91646f291c092517a3",
"version" : "2.16.2"
}
}
],
"version" : 2
}
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "swiftregex",
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-experimental-string-processing.git", branch: "main"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.121.4"),
.package(url: "https://github.com/vapor/leaf.git", from: "4.5.1"),
],
targets: [
.executableTarget(
name: "DSLConverter",
dependencies: [
.product(name: "_StringProcessing", package: "swift-experimental-string-processing"),
.product(name: "_RegexParser", package: "swift-experimental-string-processing"),
],
swiftSettings: [
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"]),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)),
]
),
.executableTarget(
name: "ExpressionParser",
dependencies: [
.product(name: "_StringProcessing", package: "swift-experimental-string-processing"),
.product(name: "_RegexParser", package: "swift-experimental-string-processing"),
],
swiftSettings: [
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"]),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)),
]
),
.executableTarget(
name: "Matcher",
dependencies: [
.product(name: "_StringProcessing", package: "swift-experimental-string-processing"),
.product(name: "_RegexParser", package: "swift-experimental-string-processing"),
],
swiftSettings: [
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"]),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)),
]
),
.executableTarget(
name: "App",
dependencies: [
.product(name: "_StringProcessing", package: "swift-experimental-string-processing"),
.product(name: "_RegexParser", package: "swift-experimental-string-processing"),
.product(name: "Vapor", package: "vapor"),
.product(name: "Leaf", package: "leaf"),
],
swiftSettings: [
.unsafeFlags(["-Xfrontend", "-disable-availability-checking"]),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)),
]
),
.testTarget(
name: "RegexTests", dependencies: [
.target(name: "DSLConverter"),
.target(name: "ExpressionParser"),
.target(name: "Matcher"),
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
]
)
]
)
================================================
FILE: Public/css/common.css
================================================
.active-tick .checkable::after {
font-family: "Font Awesome 6 Pro";
content: "\f00c";
color: #0d6efd;
display: none;
}
.active-tick svg {
color: #0d6efd;
float: right;
margin-top: 4px;
margin-left: 12px;
}
.dropdown-item .text-muted {
width: 86%;
font-size: 80%;
}
.error-message {
font-family: arial;
font-size: 90%;
background: rgba(255, 0, 0, 0.2);
}
.CodeMirror * {
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
max-height: 90vh;
}
.CodeMirror-line .cm-space::before,
.CodeMirror-line .cm-special::before {
color: rgba(127, 127, 127, 0.33);
content: "␣";
position: absolute;
}
.CodeMirror-line .cm-special::before {
color: #d22;
}
.CodeMirror-line .cm-tab {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REZENEEyN0Q3NTc0MTFFNzlFMTZGQ0Q1MEREODEyREEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REZENEEyN0U3NTc0MTFFNzlFMTZGQ0Q1MEREODEyREEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpERkQ0QTI3Qjc1NzQxMUU3OUUxNkZDRDUwREQ4MTJEQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpERkQ0QTI3Qzc1NzQxMUU3OUUxNkZDRDUwREQ4MTJEQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgWEz28AAABVSURBVHja7JVBCgAgCAQt+rf6cstjYN4iFxoQxJMjos3MCJlO4HyB26iqZLXyAswskQTUCmUSI7OruE4usxX9jCKELKK8w04e6Qqdmnfa/8SPmQIMANcZrZCVJGBIAAAAAElFTkSuQmCC);
background-position: 100%;
background-repeat: no-repeat;
}
.editor.multiline .CodeMirror-line:not(:last-child) > span:after {
pointer-events: none;
color: rgba(127, 127, 127, 0.33);
content: "¬";
}
.dropdown-menu {
min-width: 200px;
}
.button-circle {
width: 2rem;
height: 2rem;
}
.btn:focus {
outline: none;
box-shadow: none;
}
#match-count {
font-size: 90%;
}
#debugger-button {
background-color: rgba(51, 51, 51, 0.05);
border-radius: 15px;
border-width: 1px;
border-color: #dee2e6;
border-style: solid;
color: #333333;
cursor: pointer;
display: inline-block;
list-style: none;
margin: 0 6px;
padding: 4px 16px;
text-align: center;
transition: all 200ms;
vertical-align: baseline;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
font-size: 90%;
font-weight: 600;
}
#debugger-button:hover {
background-color: #2222221a;
}
#debugger-button:disabled {
background-color: #e9ecef;
color: #6c757d;
cursor: not-allowed;
}
#debugger-regex {
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
}
@media (min-width: 576px) {
.root-container {
height: 100%;
}
}
================================================
FILE: Public/css/highlight.css
================================================
.exp-related {
border-bottom: solid 1px rgba(16, 17, 18, 0.3);
border-top: solid 1px rgba(16, 17, 18, 0.3);
margin-bottom: -1px;
margin-top: -1px;
}
.exp-related-left {
border-left: solid 1px rgba(16, 17, 18, 0.3);
margin-left: -1px;
}
.exp-related-right {
border-right: solid 1px rgba(16, 17, 18, 0.3);
margin-right: -1px;
}
.exp-selected {
border-top: solid 2px rgba(16, 17, 18, 0.3);
border-bottom: solid 2px rgba(16, 17, 18, 0.3);
}
.exp-selected-left {
border-left: solid 2px rgba(16, 17, 18, 0.3);
margin-left: -2px;
}
.exp-selected-right {
border-right: solid 2px rgba(16, 17, 18, 0.3);
margin-right: -2px;
}
.exp-error {
border-bottom: solid 2px #d22;
}
.exp-warning {
border-bottom: dotted 2px #d22;
}
.exp-char {
color: #101112;
}
.exp-decorator {
color: #b7bcc0;
}
.exp-esc {
color: #c0c;
}
.exp-lazy,
.exp-possessive,
.exp-quant {
color: #58f;
}
.exp-alt {
color: #0a0;
}
.exp-anchor {
color: #840;
}
.exp-group,
.exp-lookaround,
.exp-ref {
color: #0a0;
}
.exp-charclass,
.exp-set,
.exp-subst {
color: #d70;
}
.exp-group-0 {
background: rgba(0, 238, 0, 0.11);
}
.exp-group-1 {
background: rgba(0, 238, 0, 0.22);
}
.exp-group-2 {
background: rgba(0, 238, 0, 0.33);
}
.exp-group-3 {
background: rgba(0, 238, 0, 0.44);
}
.exp-group-set {
background: rgba(255, 238, 0, 0.3);
}
.exp-comment {
color: #b7bcc0;
background: rgba(16, 17, 18, 0.05);
font-style: italic;
border-bottom: solid 3px #d7dadc;
}
.exp-special {
color: #c0c;
}
.exp-syntax-error {
border-top: solid 2px #d22;
}
span.match-char {
background: rgba(112, 176, 224, 0.5);
color: #101112;
border-right: solid 1px #ffffff;
border-left: solid 1px #ffffff;
margin-right: -2px;
}
span.match-left {
border-left: solid 2px rgba(255, 0, 0, 0.2);
margin-left: -2px;
}
span.match-right {
border-right: solid 2px rgba(255, 0, 0, 0.2);
margin-right: -2px;
}
span.error {
color: #d22;
font-weight: 700;
}
span.error.warning {
color: #d22;
}
span.highlight {
background: rgba(255, 128, 0, 0.5);
color: #101112;
}
span.debuggermatch {
background: rgba(112, 176, 224, 0.5);
color: #101112;
border-right: solid 1px rgba(112, 176, 224, 0.5);
border-left: solid 1px rgba(112, 176, 224, 0.5);
}
span.debuggerbacktrack {
background: rgba(255, 0, 0, 0.2);
}
================================================
FILE: Public/error.html
================================================
<!doctype html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="twitter:card" content="summary" />
<meta property="twitter:image" content="https://swift-format.com/images/ogp_image.png" />
<meta property="og:image" content="https://swift-format.com/images/ogp_image.png" />
<meta property="og:title" content="Online Swift code formatter">
<meta property="og:description"
content="Format a Swift code, making it readable and pretty, with the proper indentation." />
<meta name="description" content="Format a Swift code, making it readable and pretty, with the proper indentation." />
<meta property="og:site_name" content="Swift Formatter - Online Swift code formatter" />
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png">
<link rel="manifest" href="/favicons/site.webmanifest">
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/favicons/favicon.ico">
<title>#(title) | #(status) - #(error)</title>
<style type="text/css">
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article,
aside,
footer,
header,
nav,
section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption,
figure,
main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active,
a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b,
strong {
font-weight: inherit
}
b,
strong {
font-weight: bolder
}
code,
kbd,
samp {
font-family: monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio,
video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button,
input,
optgroup,
select,
textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button,
input {
overflow: visible
}
button,
select {
text-transform: none
}
[type=reset],
[type=submit],
button,
html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring,
[type=reset]:-moz-focusring,
[type=submit]:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox],
[type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button,
[type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button,
[type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details,
menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body,
html {
width: 100%;
height: 100%;
background-color: white
}
body {
color: black;
text-align: center;
padding: 0;
min-height: 100%;
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}
</style>
</head>
<body>
<div class="cover">
<h1>#(error) <small>Error #(status)</small></h1>
<p class="lead">#(reason)</p>
</div>
</body>
</html>
================================================
FILE: Public/favicons/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#2d89ef</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: Public/favicons/site.webmanifest
================================================
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
================================================
FILE: Public/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="twitter:card" content="summary" />
<meta property="twitter:image" content="https://swiftregex.com/images/ogp_image.png" />
<meta property="og:image" content="https://swiftregex.com/images/ogp_image.png" />
<meta property="og:title" content="Swift Regex: Learn, build and test Swift Regex">
<meta property="og:description"
content="Regular Expression Tester with highlighting for Swift Regex. Quickly test and debug your regex and Regex Builder." />
<meta name="description"
content="Swift Regex is an online tool to learn, build and test Swift Regex Regex and Regex Builder." />
<meta property="og:site_name" content="Swift Regex: Learn, build and test Swift Regex and Regex Builder." />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#2d89ef">
<meta name="theme-color" content="#ffffff">
<title>Swift Regex</title>
</head>
<body class="text-dark bg-light vh-100">
<div class="container-fluid d-flex flex-column h-100">
<div class="row">
<div class="col-md-6">
<h1 class="h5 my-2">
<a class="text-decoration-none" href="/">
<span class="fa-brands fa-swift fa-lg text-primary"></span>
<span class="ms-2 me-1 text-dark">Swift Regex</span>
</a>
</h1>
</div>
</div>
<div class="root-container">
<div class="d-flex flex-column h-100">
<div class="row flex-grow-1">
<div class="col-md-6 d-flex flex-column">
<div class="h6 mt-1 mb-2">Regular Expression</div>
<div class="border">
<div id="expression-field-container" class="d-flex flex-row-reverse">
<div class="btn-group">
<button class="btn btn-link btn-sm dropdown-toggle text-decoration-none bg-white" type="button"
data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
<span class="fa-solid fa-flag fa-sm"></span>
</button>
<ul class="dropdown-menu">
<li class="match-options-item active-tick" data-value="g">
<a class="dropdown-item" href="#">
<div class="checkable">
<span class="fw-bolder">g</span><span>lobal</span>
</div>
<div class="text-wrap text-muted">
Don't return after first match
</div>
</a>
</li>
<li class="match-options-item active-tick" data-value="m">
<a class="dropdown-item" href="#">
<div class="checkable">
<span class="fw-bolder">m</span>ultiline
</div>
<div class="text-wrap text-muted">
^ and $ match the start/end of line
</div>
</a>
</li>
<li class="match-options-item" data-value="i">
<a class="dropdown-item" href="#">
<div class="checkable">
case <span class="fw-bolder">i</span>nsensitive
</div>
<div class="text-wrap text-muted">
Case insensitive match
</div>
</a>
</li>
<li class="match-options-item" data-value="s">
<a class="dropdown-item" href="#">
<div class="checkable">
<span class="fw-bolder">s</span>ingle line
</div>
<div class="text-wrap text-muted">
Dot matches newline
</div>
</a>
</li>
<li class="match-options-item" data-value="asciiOnlyWordCharacters">
<a class="dropdown-item" href="#">
<div class="checkable">
ASCII only word
</div>
<div class="text-wrap text-muted">
Match only ASCII characters as word characters
</div>
</a>
</li>
<li class="match-options-item" data-value="asciiOnlyDigits">
<a class="dropdown-item" href="#">
<div class="checkable">
ASCII only digit
</div>
<div class="text-wrap text-muted">
Match only ASCII characters as digits
</div>
</a>
</li>
<li class="match-options-item" data-value="asciiOnlyWhitespace">
<a class="dropdown-item" href="#">
<div class="checkable">
ASCII only space
</div>
<div class="text-wrap text-muted">
Match only ASCII characters as space characters
</div>
</a>
</li>
<li class="match-options-item" data-value="asciiOnlyCharacterClasses">
<a class="dropdown-item" href="#">
<div class="checkable">
ASCII only POSIX properties
</div>
<div class="text-wrap text-muted">
Match only ASCII characters when matching character classes
</div>
</a>
</li>
</ul>
</div>
<div id="expression-field-error" class="bg-white d-none">
<span class="fa-solid fa-octagon-xmark text-danger align-middle p-2"></span>
</div>
</div>
</div>
<div class="row justify-content-between">
<div class="col">
<div class="h6 mt-4 mb-2">Test String</div>
</div>
<div class="col align-self-center">
<div class="text-end mt-2">
<button id="debugger-button" class="px-3" data-bs-toggle="modal" data-bs-target="#debugger-modal"
disabled>
<div data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-container="#debugger-button"
title="Debugger" aria-label="Debugger" style="display: inline-block;">
<span class="fa-solid fa-stethoscope"></span>
</div>
</button>
<span id="match-count" class="text-bg-light border px-3 py-1">no match</span>
</div>
</div>
</div>
<div class="flex-grow-1 border">
<div class="test-editor-container editor multiline h-100"></div>
</div>
</div>
<div class="col-md-6 d-flex flex-column">
<div class="h6 mt-1 mb-2">Builder DSL</div>
<div class="flex-grow-1 border">
<div id="dsl-view-container" class="h-100"></div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="debugger-modal" tabindex="-1" aria-labelledby="debugger-modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-xl">
<div class="modal-content">
<div class="modal-header py-2">
<h1 class="modal-title fs-5" id="debugger-modal-title">Regex Debugger</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="d-flex">
<div class="me-4" style="max-width: 40%;">
<div style="font-weight: 600;">Metrics</div>
<div class="border my-2" style="font-size: 90%;">
<table class="table table-borderless table-sm my-0">
<thead>
<tr>
<th>Cycle Count</th>
<th>Resets</th>
<th>Backtracks</th>
</tr>
</thead>
<tbody>
<tr class="font-monospace">
<td id="debugger-total-cycle-count">0</td>
<td id="debugger-resets">0</td>
<td id="debugger-backtracks">0</td>
</tr>
</tbody>
</table>
</div>
<div style="overflow: auto;">
<div class="my-2" style="font-weight: 600;">Instructions</div>
<div class="my-2" style="font-size: 90%;">
<table class="border table table-borderless table-sm font-monospace">
<tbody id="debugger-instructions">
</tbody>
</table>
</div>
</div>
</div>
<div class="flex-grow-1">
<label for="debugger-step-range" class="form-label" style="font-weight: 600;">Match Steps</label>
<div class="d-flex">
<input id="debugger-step-range" type="range" class="form-range flex-grow-1" value="1" min="1"
max="100">
<div class="ms-3">
<span id="debugger-step-range-max" class="border px-3 py-1"
style="font-size: 80%; font-weight: 600;">100</span>
</div>
</div>
<p class="mb-0" style="text-align: center">
<button id="debugger-go-start" class="btn btn-sm btn-outline-secondary rounded-circle button-circle">
<i class="fa-solid fa-backward-step fa-lg"></i>
</button>
<button id="debugger-step-backward"
class="btn btn-sm btn-outline-secondary rounded-circle button-circle">
<i class="fa-solid fa-caret-left fa-lg"></i>
</button>
<button id="debugger-step-forward"
class="btn btn-sm btn-outline-secondary rounded-circle button-circle">
<i class="fa-solid fa-caret-right fa-lg"></i>
</button>
<button id="debugger-go-end" class="btn btn-sm btn-outline-secondary rounded-circle button-circle">
<i class="fa-solid fa-forward-step fa-lg"></i>
</button>
</p>
<div class="my-1" style="font-weight: 600;">Match Step <span id="debugger-match-step"></span></div>
<div class="mb-3">
<input id="debugger-regex" type="text" class="form-control form-control-sm border shadow-none"
style="border-radius: 0;" readonly>
</div>
<div class="border my-3">
<div id="debugger-text-container" class="editor multiline"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="row row-cols-1 g-0">
<div class="col p-1 text-center">
<div class="d-inline-block mx-2">
<a class="text-reset text-decoration-none small" href="https://status.swiftregex.com/" target="_blank"
rel="nofollow noopener noreferrer"><span class="fa-light fa-monitor-heart-rate"></span><span
class="mx-2">System
Status</span></a>
</div>
<div class="d-inline-block mx-2">
<a class="text-reset text-decoration-none small" href="https://github.com/swiftfiddle/swiftregex/issues/new"
target="_blank" rel="nofollow noopener noreferrer"><span class="fa-regular fa-message-smile"></span><span
class="mx-2">Feedback</span></a>
</div>
<div class="d-inline-block mx-2">
<a class="text-reset text-decoration-none small" href="https://github.com/swiftfiddle/swiftregex"
target="_blank" rel="nofollow noopener noreferrer"><span class="fa-brands fa-github"></span><span
class="mx-2">Source
Code</span></a>
</div>
<div class="d-inline-block mx-2">
<a class="text-reset text-decoration-none small" href="https://hachyderm.io/@kishikawakatsumi" target="_blank"
rel="nofollow noopener noreferrer"><span class="fa-regular fa-at"></span><span
class="mx-2">Creator</span></a>
</div>
<div class="d-inline-block mx-2">
<a class="text-reset text-decoration-none small" href="https://github.com/sponsors/kishikawakatsumi"
target="_blank" rel="nofollow noopener noreferrer">
<span class="fa-solid fa-heart" style="color: #bf3989;"></span><span class="mx-2">Donate</span></a>
</div>
</div>
</footer>
</div>
</body>
</html>
================================================
FILE: Public/index.js
================================================
"use strict";
import "./scss/default.scss";
import "codemirror/lib/codemirror.css";
import "tippy.js/dist/tippy.css";
import "./css/common.css";
import "./css/highlight.css";
import "./js/misc/icons";
import "bootstrap";
import { App } from "./js/app";
new App();
================================================
FILE: Public/js/app.js
================================================
"use strict";
import { Tooltip } from "bootstrap";
import { ExpressionField } from "./views/expression_field";
import { MatchOptions } from "./views/match_options";
import { TestEditor } from "./views/test_editor";
import { DSLView } from "./views/dsl_view";
import { DSLEditor } from "./views/dsl_editor";
import { DebuggerText } from "./views/debugger_text";
import { Runner } from "./runner";
export class App {
constructor() {
this.init();
}
init() {
[].slice
.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
.map((trigger) => {
return new Tooltip(trigger);
});
this.expressionField = new ExpressionField(
document.getElementById("expression-field-container"),
);
this.expressionField.addEventListener("change", () =>
this.onExpressionFieldChange(),
);
this.matchOptions = new MatchOptions();
this.matchOptions.addEventListener("change", () =>
this.onExpressionFieldChange(),
);
this.patternTestEditor = new TestEditor(
document.querySelector(".test-editor-container"),
);
this.patternTestEditor.addEventListener("change", () =>
this.onPatternTestEditorChange(),
);
this.debuggerText = new DebuggerText(
document.getElementById("debugger-text-container"),
);
this.debuggerGoStartButton = document.getElementById("debugger-go-start");
this.debuggerGoStartButton.addEventListener("click", () => {
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.value = 1;
this.onDebuggerStepChange();
});
this.debuggerStepBackwardButton = document.getElementById(
"debugger-step-backward",
);
this.debuggerStepBackwardButton.addEventListener("click", () => {
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.value = Math.max(1, parseInt(matchStepRange.value) - 1);
this.onDebuggerStepChange();
});
this.debuggerStepForwardButton = document.getElementById(
"debugger-step-forward",
);
this.debuggerStepForwardButton.addEventListener("click", () => {
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.value = Math.min(
parseInt(matchStepRange.value) + 1,
parseInt(matchStepRange.max),
);
this.onDebuggerStepChange();
});
this.debuggerGoEndButton = document.getElementById("debugger-go-end");
this.debuggerGoEndButton.addEventListener("click", () => {
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.value = matchStepRange.max;
this.onDebuggerStepChange();
});
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.addEventListener("input", () => {
this.onDebuggerStepChange();
});
this.debuggerModal = document.getElementById("debugger-modal");
this.debuggerModal.addEventListener("shown.bs.modal", () =>
this.launchDebugger(),
);
this.dslView = new DSLView(document.getElementById("dsl-view-container"));
this.runner = new Runner();
this.runner.onready = this.onRunnerReady.bind(this);
this.runner.onresponse = this.onRunnerResponse.bind(this);
this.stateProxy = {
builder: "",
text2: "",
};
if (window.Worker) {
this.stateRestorationWorker = new Worker(
new URL("./state/worker.js", import.meta.url),
);
if (window.location.search) {
this.decodeState();
} else {
this.expressionField.setDefaultValue();
this.patternTestEditor.setDefaultValue();
this.stateProxy.builder = DSLEditor.defaultValue;
this.stateProxy.text2 = TestEditor.defaultValue;
}
this.startStateRestoration();
}
}
startStateRestoration() {
if (!this.stateRestorationWorker) {
return;
}
const debounce = (() => {
const timers = {};
return function (callback, delay, id) {
delay = delay || 400;
id = id || "duplicated event";
if (timers[id]) {
clearTimeout(timers[id]);
}
timers[id] = setTimeout(callback, delay);
};
})();
this.stateRestorationWorker.onmessage = (e) => {
if (e.data && e.data.type === "encode") {
debounce(
() => {
history.replaceState(null, "", e.data.value);
},
400,
"update_location",
);
}
if (e.data && e.data.type === "decode") {
const expressionField = this.expressionField;
const matchOptions = this.matchOptions;
const patternTestEditor = this.patternTestEditor;
if (expressionField) {
expressionField.value = e.data.value.pattern;
}
if (matchOptions) {
matchOptions.value = e.data.value.options;
}
if (patternTestEditor) {
patternTestEditor.value = e.data.value.text1;
}
}
};
}
encodeState() {
if (!this.stateRestorationWorker) {
return;
}
const expressionField = this.expressionField;
const matchOptions = this.matchOptions;
const patternTestEditor = this.patternTestEditor;
this.stateRestorationWorker.postMessage({
type: "encode",
value: {
pattern: expressionField ? expressionField.value : "",
options: matchOptions ? matchOptions.value : [],
text1: patternTestEditor ? patternTestEditor.value : "",
},
});
}
decodeState() {
if (!this.stateRestorationWorker) {
return;
}
this.stateRestorationWorker.postMessage({
type: "decode",
value: window.location.search,
});
}
updateMatchCount(count, id) {
const matchCount = document.getElementById(id);
if (count > 1) {
matchCount.textContent = `${count} matches`;
} else if (count > 0) {
matchCount.textContent = "1 match";
} else {
matchCount.textContent = "no match";
}
}
launchDebugger() {
const expressionField = this.expressionField;
const patternTestEditor = this.patternTestEditor;
const expression = expressionField.value;
const text = patternTestEditor.value;
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.value = 1;
matchStepRange.min = 1;
const matchStep = document.getElementById("debugger-match-step");
matchStep.textContent = "1";
const debuggerPattern = document.getElementById("debugger-regex");
debuggerPattern.value = expression;
this.debuggerText.value = text;
this.onDebuggerStepChange();
}
onExpressionFieldChange() {
if (!this.expressionField.value) {
this.expressionField.tokens = [];
this.expressionField.error = null;
this.dslView.value = "";
this.dslView.error = null;
this.updateMatchCount(0, "match-count");
return;
}
this.run();
this.encodeState();
}
run() {
const methods = ["parseExpression", "convertToDSL", "match"];
const params = {
pattern: this.expressionField.value,
text: this.patternTestEditor.value,
matchOptions: this.matchOptions.value,
};
if (this.runner.isReady) {
for (const method of methods) {
this.runner.run({
method: method,
...params,
});
}
} else {
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
for (const method of methods) {
const body = JSON.stringify({
method: method,
...params,
});
fetch(`/api/rest/${method}`, { method: "POST", headers, body })
.then((response) => {
return response.json();
})
.then((response) => {
this.onRunnerResponse(response);
});
}
}
}
onMatchOptionsChange() {
this.onPatternTestEditorChange();
}
onPatternTestEditorChange() {
const method = "match";
const params = {
method,
pattern: this.expressionField.value,
text: this.patternTestEditor.value,
matchOptions: this.matchOptions.value,
};
if (this.runner.isReady) {
this.runner.run(params);
} else {
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
const body = JSON.stringify(params);
fetch(`/api/rest/${method}`, { method: "POST", headers, body })
.then((response) => {
return response.json();
})
.then((response) => {
this.onRunnerResponse(response);
});
}
this.encodeState();
}
onDebuggerStepChange() {
const method = "debug";
const params = {
method,
pattern: document.getElementById("debugger-regex").value,
text: this.debuggerText.value,
matchOptions: this.matchOptions.value,
step: document.getElementById("debugger-step-range").value,
};
if (this.runner.isReady) {
this.runner.run(params);
} else {
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
const body = JSON.stringify(params);
fetch(`/api/rest/${method}`, { method: "POST", headers, body })
.then((response) => {
return response.json();
})
.then((response) => {
this.onRunnerResponse(response);
});
}
}
onRunnerReady() {
const value = this.expressionField.value;
if (value) {
this.onExpressionFieldChange();
}
}
onRunnerResponse(response) {
switch (response.method) {
case "parseExpression":
if (response.result) {
const tokens = JSON.parse(response.result);
this.expressionField.tokens = tokens;
} else {
this.expressionField.tokens = [];
}
if (response.error) {
try {
const error = JSON.parse(response.error);
if (error) {
this.expressionField.error = error;
}
} catch (e) {
this.expressionField.error = response.error;
}
} else {
this.expressionField.error = null;
}
break;
case "convertToDSL":
if (response.result) {
this.dslView.value = JSON.parse(response.result);
}
if (response.error) {
try {
const error = JSON.parse(response.error);
if (error) {
this.dslView.error = error;
}
} catch (e) {
this.dslView.error = response.error;
}
} else {
this.dslView.error = null;
}
break;
case "match":
const debuggerButton = document.getElementById("debugger-button");
if (response.result) {
const matches = JSON.parse(response.result);
this.patternTestEditor.matches = matches;
this.updateMatchCount(matches.length, "match-count");
debuggerButton.disabled = matches.length === 0;
} else {
this.patternTestEditor.matches = [];
this.updateMatchCount(0, "match-count");
debuggerButton.disabled = true;
}
this.patternTestEditor.error = response.error;
break;
case "debug":
if (response.result) {
const metrics = JSON.parse(response.result);
const matchStep = document.getElementById("debugger-match-step");
matchStep.textContent = metrics.step;
const matchStepRange = document.getElementById("debugger-step-range");
matchStepRange.max = metrics.stepCount;
const matchStepRangeMax = document.getElementById(
"debugger-step-range-max",
);
matchStepRangeMax.textContent = metrics.stepCount;
const instructions = document.getElementById("debugger-instructions");
instructions.innerHTML = "";
metrics.instructions.forEach((instruction, i) => {
const tr = document.createElement("tr");
if (i === metrics.programCounter) {
tr.classList.add("table-primary");
}
const programCounter = document.createElement("td");
programCounter.style =
"width: 1%; text-align: right; white-space: nowrap; padding-left: 1em; padding-right: 1em;";
programCounter.textContent = i + 1;
tr.appendChild(programCounter);
const inst = document.createElement("td");
inst.style = "white-space: nowrap;";
inst.textContent = instruction;
tr.appendChild(inst);
instructions.appendChild(tr);
});
const totalCycleCount = document.getElementById(
"debugger-total-cycle-count",
);
totalCycleCount.textContent = metrics.totalCycleCount;
const resets = document.getElementById("debugger-resets");
resets.textContent = metrics.resets;
const backtracks = document.getElementById("debugger-backtracks");
const previousBacktracks = Number(backtracks.textContent);
backtracks.textContent = metrics.backtracks;
this.debuggerText.highlighter.draw(
metrics.traces,
previousBacktracks < metrics.backtracks ? metrics.failure : null,
);
}
break;
}
}
}
================================================
FILE: Public/js/docs/reference.js
================================================
"use strict";
export class Reference {
static get(category, key) {
return references[category][key];
}
}
const references = {
charclasses: {
label: "Character classes",
desc: "Character classes match a character from a specific set. There are a number of predefined character classes and you can also define your own sets.",
set: {
title: "Character set",
detail: "Match any character in the set.",
},
setnot: {
title: "Negated set",
detail: "Match any character that is not in the set.",
},
range: {
title: "Range",
detail:
"Matches a character in the range {{getChar(prev)}} to {{getChar(next)}} (char code {{prev.code}} to {{next.code}}). {{getInsensitive()}}",
},
posixcharclass: {
title: "POSIX class",
detail:
"Matches any character in the specified POSIX class. Must be in a character set. For example, <code>[[:alnum:]$]</code> will match alphanumeric characters and <code>$</code>.",
},
dot: {
title: "Dot",
detail: "Matches any character {{getDotAll()}}.",
},
matchanyset: {
title: "Match any",
detail:
"A character set that can be used to match any character, including line breaks, without the dotall flag (<code>s</code>)." +
"<p>An alternative is <code>[^]</code>, but it is not supported in all browsers.</p>",
},
unicodegrapheme: {
title: "Unicode grapheme",
detail: "Matches any single unicode grapheme (ie. character).",
},
word: {
title: "Word",
detail: "Matches any word character (alphanumeric & underscore).",
},
notword: {
title: "Not word",
detail:
"Matches any character that is not a word character (alphanumeric & underscore).",
},
digit: {
title: "Digit",
detail: "Matches any digit character (0-9).",
},
notdigit: {
title: "Not digit",
detail: "Matches any character that is not a digit character (0-9).",
},
whitespace: {
title: "Whitespace",
detail: "Matches any whitespace character (spaces, tabs, line breaks).",
},
notwhitespace: {
title: "Not whitespace",
detail:
"Matches any character that is not a whitespace character (spaces, tabs, line breaks).",
},
hwhitespace: {
title: "Horizontal whitespace",
detail: "Matches any horizontal whitespace character (spaces, tabs).",
},
nothwhitespace: {
title: "Not horizontal whitespace",
detail:
"Matches any character that is not a horizontal whitespace character (spaces, tabs).",
},
vwhitespace: {
title: "Vertical whitespace",
detail: "Matches any vertical whitespace character (line breaks).",
},
notvwhitespace: {
title: "Not vertical whitespace",
detail:
"Matches any character that is not a vertical whitespace character (line breaks).",
},
linebreak: {
title: "Line break",
detail:
"Matches any line break character, including the CRLF pair, and CR / LF individually.",
},
notlinebreak: {
title: "Not line break",
detail: "Matches any character that is not a line break.",
},
unicodecat: {
title: "Unicode category",
detail:
"Matches any character in the '{{getUniCat()}}' unicode category.",
},
notunicodecat: {
title: "Not unicode category",
detail:
"Matches any character that is not in the '{{getUniCat()}}' unicode category.",
},
unicodescript: {
title: "Unicode script",
detail: "Matches any character in the '{{value}}' unicode script.",
},
notunicodescript: {
title: "Not unicode script",
detail:
"Matches any character that is not in the '{{value}}' unicode script.",
},
binary: {
title: "Unicode property escapes",
detail:
"Allows for matching characters based on their Unicode properties.",
},
script: {
title: "Script",
detail: "No overview available.",
},
scriptextension: {
title: "Script extension",
detail: "No overview available.",
},
named: {
title: "Named",
detail: "No overview available.",
},
numerictype: {
title: "Numeric type",
detail: "No overview available.",
},
numericvalue: {
title: "Numeric value",
detail: "No overview available.",
},
mapping: {
title: "Mapping",
detail: "No overview available.",
},
ccc: {
title: "Custom character class",
detail: "No overview available.",
},
age: {
title: "Age",
detail: "No overview available.",
},
block: {
title: "Block",
detail: "No overview available.",
},
pcrespecial: {
title: "PCRE special",
detail: "No overview available.",
},
javaspecial: {
title: "Java special",
detail: "No overview available.",
},
graphemecluster: {
title: "Grapheme cluster",
detail: "No overview available.",
},
trueanychar: {
title: "Any character",
detail: "Equivalent to (?m:.)",
},
textsegment: {
title: "Text segment",
detail: `Equivalent to (?>\O(?:\Y\O)*)`,
},
nottextsegment: {
title: "Not text segment",
detail: "Text segment non-boundary",
},
keyboardcontrol: {
title: "Control char",
detail: "No overview available.",
},
keyboardmeta: {
title: "Meta",
detail: "No overview available.",
},
keyboardmetacontrol: {
title: "Meta control char",
detail: "No overview available.",
},
namedcharacter: {
title: "Named character",
detail: "No overview available.",
},
subpattern: {
title: "Subpattern",
detail: "No overview available.",
},
callout: {
title: "Callout",
detail: "No overview available.",
},
accept: {
title: "Backtracking control",
detail: `This verb causes the match to end successfully, skipping the remainder of the pattern. When inside a recursion, only the innermost pattern is ended immediately.`,
},
fail: {
title: "Backtracking control",
detail: `This verb causes the match to fail, forcing backtracking to occur. It is equivalent to (?!) but easier to read. The Perl documentation notes that it is probably useful only when combined with (?{}) or (??{}).`,
},
mark: {
title: "Backtracking control",
detail: "No overview available.",
},
commit: {
title: "Backtracking control",
detail: `This verb causes the whole match to fail outright if the rest of the pattern does not match. Even if the pattern is unanchored, no further attempts to find a match by advancing the start point take place. Once (*COMMIT) has been passed, re:run/3 is committed to finding a match at the current starting point, or not at all.`,
},
prune: {
title: "Backtracking control",
detail: `acktracking cannot cross (*PRUNE). In simple cases, the use of (*PRUNE) is just an alternative to an atomic group or possessive quantifier, but there are some uses of (*PRUNE) that cannot be expressed in any other way.`,
},
skip: {
title: "Backtracking control",
detail: `This verb is like (*PRUNE), except that if the pattern is unanchored, the "bumpalong" advance is not to the next character, but to the position in the subject where (*SKIP) was encountered. (*SKIP) signifies that whatever text was matched leading up to it cannot be part of a successful match.`,
},
then: {
title: "Backtracking control",
detail: `This verb causes a skip to the next alternation if the rest of the pattern does not match. That is, it cancels pending backtracking, but only within the current alternation.`,
},
},
anchors: {
label: "Anchors",
desc: "Anchors are unique in that they match a position within a string, not a character.",
bos: {
title: "Beginning of string",
detail: "Matches the beginning of the string.",
},
eos: {
title: "End of string",
detail: "Matches the end of the string.",
},
abseos: {
title: "Strict end of string",
detail:
"Matches the end of the string. Unlike <code>$</code> or <code>\\Z</code>, it does not allow for a trailing newline.",
},
bof: {
title: "Beginning",
detail:
"Matches the beginning of the string, or the beginning of a line if the multiline flag (<code>m</code>) is enabled.",
},
eof: {
title: "End",
detail:
"Matches the end of the string, or the end of a line if the multiline flag (<code>m</code>) is enabled.",
},
wordboundary: {
title: "Word boundary",
detail:
"Matches a word boundary position between a word character and non-word character or position (start / end of string).",
},
notwordboundary: {
title: "Not word boundary",
detail: "Matches any position that is not a word boundary.",
},
prevmatchend: {
title: "Previous match end",
detail: "Matches the end position of the previous match.",
},
invalid: {
title: "Invalid character class",
detail: "No overview available.",
},
},
escchars: {
label: "Escaped characters",
desc: "Escape sequences can be used to insert reserved, special, and unicode characters. All escaped characters begin with the <code>\\</code> character.",
reservedchar: {
title: "Reserved characters",
detail:
"The following character have special meaning, and should be preceded by a <code>\\</code> (backslash) to represent a literal character:" +
"<p><code>{{getEscChars()}}</code></p>" +
"<p>Within a character set, only <code>\\</code>, <code>-</code>, and <code>]</code> need to be escaped.</p>",
},
escoctal: {
title: "Octal escape",
detail: "Octal escaped character in the form <code>\\000</code>.",
},
eschexadecimal: {
title: "Hexadecimal escape",
detail: "Hexadecimal escaped character in the form <code>\\xFF</code>.",
},
escunicodeu: {
title: "Unicode escape",
detail: "Unicode escaped character in the form <code>\\uFFFF</code>",
},
escunicodeub: {
title: "Extended unicode escape",
detail: "Unicode escaped character in the form <code>\\u{FFFF}</code>.",
},
escunicodexb: {
title: "Unicode escape",
detail: "Unicode escaped character in the form <code>\\x{FF}</code>.",
},
esccontrolchar: {
title: "Control character escape",
detail: "Escaped control character in the form <code>\\cZ</code>.",
},
escsequence: {
title: "Escape sequence",
detail: "Matches the literal string '{{value}}'.",
},
},
groups: {
label: "Groups & References",
desc: "Groups allow you to combine a sequence of tokens to operate on them together. Capture groups can be referenced by a backreference and accessed separately in the results.",
group: {
title: "Capturing group #{{group.num}}",
detail:
"Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference.",
},
namedgroup: {
title: "Named capturing group",
detail: "Creates a capturing group named '{{name}}'.",
},
namedref: {
title: "Named reference",
detail:
"Matches the results of the capture group named '{{group.name}}'.",
},
numref: {
title: "Numeric reference",
detail: "Matches the results of capture group #{{group.num}}.",
},
branchreset: {
title: "Branch reset group",
detail: "Define alternative groups that share the same group numbers.",
},
noncapgroup: {
title: "Non-capturing group",
detail:
"Groups multiple tokens together without creating a capture group.",
},
atomic: {
title: "Atomic group",
detail:
"Non-capturing group that discards backtracking positions once matched.",
},
define: {
title: "Define",
detail:
"Used to define named groups for use as subroutines without including them in the match.",
},
numsubroutine: {
title: "Numeric subroutine",
detail: "Matches the expression in capture group #{{group.num}}.",
},
namedsubroutine: {
title: "Named subroutine",
detail:
"Matches the expression in the capture group named '{{group.name}}'.",
},
balancedcapture: {
title: "Balancing group",
detail:
"This allows nested constructs to be matched, such as parentheses or HTML tags. The previously defined group to balance against is specified by previous. Captures subpattern as a named group specified by name, or name can be omitted to capture as an unnamed group.",
},
absentfunction: {
title: "Absent function",
detail: "No overview available.",
},
},
lookaround: {
label: "Lookaround",
desc:
"Lookaround lets you match a group before (lookbehind) or after (lookahead) your main pattern without including it in the result." +
"<p>Negative lookarounds specify a group that can NOT match before or after the pattern.</p>",
poslookahead: {
title: "Positive lookahead",
detail:
"Matches a group after the main expression without including it in the result.",
},
neglookahead: {
title: "Negative lookahead",
detail:
"Specifies a group that can not match after the main expression (if it matches, the result is discarded).",
},
poslookbehind: {
title: "Positive lookbehind",
detail:
"Matches a group before the main expression without including it in the result.",
},
neglookbehind: {
title: "Negative lookbehind",
detail:
"Specifies a group that can not match before the main expression (if it matches, the result is discarded).",
},
keepout: {
title: "Keep out",
detail:
"Keep text matched so far out of the returned match, essentially discarding the match up to this point.",
},
nonatomicposlookahead: {
title: "Non-atomic Positive lookahead",
detail: "No overview available.",
},
nonatomicposlookbehind: {
title: "Non-atomic Positive lookbehind",
detail: "No overview available.",
},
scriptrun: {
title: "Script run",
detail: "No overview available.",
},
atomicscriptrun: {
title: "Atomic script run",
detail: "No overview available.",
},
changematchingoptions: {
title: "Change matching options",
detail: "No overview available.",
},
},
quants: {
label: "Quantifiers & Alternation",
desc:
"Quantifiers indicate that the preceding token must be matched a certain number of times. By default, quantifiers are greedy, and will match as many characters as possible." +
"<hr/>Alternation acts like a boolean OR, matching one sequence or another.",
plus: {
title: "Plus",
detail: "Matches 1 or more of the preceding token.",
},
star: {
title: "Star",
detail: "Matches 0 or more of the preceding token.",
},
quant: {
title: "Quantifier",
detail: "Match {{getQuant()}} of the preceding token.",
},
opt: {
title: "Optional",
detail:
"Matches 0 or 1 of the preceding token, effectively making it optional.",
},
lazy: {
title: "Lazy",
detail:
"Makes the preceding quantifier {{getLazy()}}, causing it to match as {{getLazyFew()}} characters as possible.",
},
possessive: {
title: "Possessive",
detail:
"Makes the preceding quantifier possessive. It will match as many characters as possible, and will not release them to match subsequent tokens.",
},
alt: {
title: "Alternation",
detail:
"Acts like a boolean OR. Matches the expression before or after the <code>|</code>.",
},
},
other: {
label: "Special",
desc: "Tokens that don't quite fit anywhere else.",
comment: {
title: "Comment",
detail:
"Allows you to insert a comment into your expression that is ignored when finding a match.",
},
conditional: {
title: "Conditional",
detail:
"Conditionally matches one of two options based on whether a lookaround is matched.",
},
conditionalgroup: {
title: "Group conditional",
detail:
"Conditionally matches one of two options based on whether group '{{name}}' matched.",
},
recursion: {
title: "Recursion",
detail:
"Attempts to match the full expression again at the current position.",
},
mode: {
title: "Mode modifier",
detail: "{{~getDesc()}}{{~getModes()}}",
},
interpolation: {
title: "Interpolation",
detail: "No overview available.",
},
},
subst: {
label: "Substitution",
desc: "These tokens are used in a substitution string to insert different parts of the match.",
"subst_$&match": {
title: "Match",
detail: "Inserts the matched text.",
},
subst_0match: {
title: "Match",
detail: "Inserts the matched text.",
},
subst_group: {
title: "Capture group",
detail: "Inserts the results of capture group #{{group.num}}.",
},
subst_$before: {
title: "Before match",
detail:
"Inserts the portion of the source string that precedes the match.",
},
subst_$after: {
title: "After match",
detail:
"Inserts the portion of the source string that follows the match.",
},
subst_$esc: {
title: "Escaped $",
detail: "Inserts a dollar sign character ($).",
},
subst_esc: {
title: "Escaped characters",
detail:
"For convenience, these escaped characters are supported in the Replace string in RegExr: <code>\\n</code>, <code>\\r</code>, <code>\\t</code>, <code>\\\\</code>, and unicode escapes <code>\\uFFFF</code>. This may vary in your deploy environment.",
},
},
flags: {
label: "Flags",
desc: "Expression flags change how the expression is interpreted. Flags follow the closing forward slash of the expression (ex. <code>/.+/igm</code> ).",
caseinsensitive: {
title: "Ignore case",
detail: "Makes the whole expression case-insensitive.",
},
global: {
title: "Global search",
detail:
"Retain the index of the last match, allowing iterative searches.",
},
multiline: {
title: "Multiline",
detail:
"Beginning/end anchors (<b>^</b>/<b>$</b>) will match the start/end of a line.",
},
unicode: {
title: "Unicode",
detail: "Enables <code>\\x{FFFFF}</code> unicode escapes.",
},
sticky: {
title: "Sticky",
detail:
"The expression will only match from its lastIndex position and ignores the global (<code>g</code>) flag if set.",
},
dotall: {
title: "Dot all",
detail:
"Dot (<code>.</code>) will match any character, including newline.",
},
extended: {
title: "Extended",
detail:
"Literal whitespace characters are ignored, except in character sets.",
},
ungreedy: {
title: "Ungreedy",
detail: "Makes quantifiers ungreedy (lazy) by default.",
},
},
misc: {
label: "Miscellaneous",
desc: "No overview available.",
ignorews: {
title: "Ignored whitespace",
detail:
"Whitespace character ignored due to the e<b>x</b>tended flag or mode.",
},
extnumref: {
title: "Numeric reference",
detail: "Matches the results of capture group #{{group.num}}.",
},
char: {
title: "Character",
detail:
"Matches a {{getChar()}} character (char code {{code}}). {{getInsensitive()}}",
},
escchar: {
title: "Escaped character",
detail: "Matches a {{getChar()}} character (char code {{code}}).",
},
open: {
title: "Open",
detail: "Indicates the start of a regular expression.",
},
close: {
title: "Close",
detail:
"Indicates the end of a regular expression and the start of expression flags.",
},
condition: {
title: "Condition",
detail:
"The lookaround to match in resolving the enclosing conditional statement. See 'conditional' in the Reference for info.",
},
conditionalelse: {
title: "Conditional else",
detail: "Delimits the 'else' portion of the conditional.",
},
ERROR: {
title: "Error",
detail:
"Errors in the expression are underlined in red. Roll over errors for more info.",
},
PREG_INTERNAL_ERROR: {
title: "Internal error",
detail: "Internal PCRE error",
},
PREG_BACKTRACK_LIMIT_ERROR: {
title: "Backtrack limit error",
detail: "Backtrack limit was exhausted.",
},
PREG_RECURSION_LIMIT_ERROR: {
title: "Recursion limit error",
detail: "Recursion limit was exhausted",
},
PREG_BAD_UTF8_ERROR: {
title: "Bad UTF8 error",
detail: "Malformed UTF-8 data",
},
PREG_BAD_UTF8_OFFSET_ERROR: {
title: "Bad UTF8 offset error",
detail: "Malformed UTF-8 data",
},
any: {
title: "Any",
detail: "No overview available.",
},
assigned: {
title: "Assigned",
detail: "No overview available.",
},
ascii: {
title: "Unicode property",
detail: "Matches any characters in the ASCII script extension.",
},
},
errors: {
groupopen: "Unmatched opening parenthesis.",
groupclose: "Unmatched closing parenthesis.",
setopen: "Unmatched opening square bracket.",
rangerev:
"Range values reversed. Start char code is greater than end char code.",
quanttarg: "Invalid target for quantifier.",
quantrev: "Quantifier minimum is greater than maximum.",
esccharopen: "Dangling backslash.",
esccharbad: "Unrecognized or malformed escape character.",
unicodebad: "Unrecognized unicode category or script.",
posixcharclassbad: "Unrecognized POSIX character class.",
posixcharclassnoset: "POSIX character class must be in a character set.",
notsupported:
'The "{{~getLabel()}}" feature is not supported in this flavor of RegEx.',
fwdslash:
"Unescaped forward slash. This may cause issues if copying/pasting this expression into code.",
esccharbad: "Invalid escape sequence.",
servercomm: "An error occurred while communicating with the server.",
extraelse: "Extra else in conditional group.",
unmatchedref: 'Reference to non-existent group "{{name}}".',
modebad: 'Unrecognized mode flag "<code>{{errmode}}</code>".',
badname: "Group name can not start with a digit.",
dupname: "Duplicate group name.",
branchreseterr:
"<b>Branch Reset.</b> Results will be ok, but RegExr's parser does not number branch reset groups correctly. Coming soon!",
timeout: "The expression took longer than 250ms to execute.", // TODO: can we couple this to the help content somehow?
// warnings:
jsfuture:
'The "{{~getLabel()}}" feature may not be supported in all browsers.',
infinite:
"The expression can return empty matches, and may match infinitely in some use cases.", // TODO: can we couple this to the help content somehow?
},
empty: {
empty: {
title: "Empty",
detail: "No overview available.",
},
},
};
================================================
FILE: Public/js/misc/icons.js
================================================
"use strict";
import { config, library, dom } from "@fortawesome/fontawesome-svg-core";
import {
faFlag,
faOctagonXmark,
faStethoscope,
faHeart,
faBackwardStep,
faForwardStep,
faCaretLeft,
faCaretRight,
} from "@fortawesome/pro-solid-svg-icons";
import {
faAt,
faCheck,
faCommentAltSmile,
} from "@fortawesome/pro-regular-svg-icons";
import { faMonitorHeartRate } from "@fortawesome/pro-light-svg-icons";
import { faSwift, faGithub } from "@fortawesome/free-brands-svg-icons";
config.searchPseudoElements = true;
library.add(
faFlag,
faOctagonXmark,
faStethoscope,
faHeart,
faBackwardStep,
faForwardStep,
faCaretLeft,
faCaretRight,
faAt,
faCheck,
faCommentAltSmile,
faMonitorHeartRate,
faSwift,
faGithub
);
dom.watch();
================================================
FILE: Public/js/misc/utils.js
================================================
"use strict";
const Utils = {};
export default Utils;
Utils.copy = function (target, source) {
for (let n in source) {
target[n] = source[n];
}
return target;
};
Utils.clone = function (o) {
// this seems hacky, but it's the fastest, easiest approach for now:
return JSON.parse(JSON.stringify(o));
};
Utils.htmlSafe = function (str) {
return str == null
? ""
: ("" + str).replace(/&/g, "&").replace(/</g, "<");
};
Utils.shorten = function (str, length, htmlSafe, tag = "") {
if (!str) {
return str;
}
let b = length > 0 && str.length > length;
if (b) {
str = str.substr(0, length - 1);
}
if (htmlSafe) {
str = Utils.htmlSafe(str);
}
return !b
? str
: str + (tag && "<" + tag + ">") + "\u2026" + (tag && "</" + tag + ">");
};
Utils.unescSubstStr = function (str) {
if (!str) {
return "";
}
return str.replace(
Utils.SUBST_ESC_RE,
(a, b, c) =>
Utils.SUBST_ESC_CHARS[b] || String.fromCharCode(parseInt(c, 16))
);
};
Utils.getRegExp = function (str) {
// returns a JS RegExp object.
let match = str.match(/^\/(.+)\/([a-z]+)?$/),
regex = null;
try {
regex = match ? new RegExp(match[1], match[2] || "") : new RegExp(str, "g");
} catch (e) {}
return regex;
};
Utils.decomposeRegEx = function (str, delim = "/") {
let re = new RegExp("^" + delim + "(.*)" + delim + "([igmsuUxy]*)$");
let match = re.exec(str);
if (match) {
return { source: match[1], flags: match[2] };
} else {
return { source: str, flags: "g" };
}
};
Utils.isMac = function () {
return !!navigator.userAgent.match(/Mac\sOS/i);
};
Utils.getCtrlKey = function () {
return Utils.isMac() ? "cmd" : "ctrl";
};
Utils.now = function () {
return window.performance ? performance.now() : Date.now();
};
Utils.getUrlParams = function () {
let match,
re = /([^&=]+)=?([^&]*)/g,
params = {};
let url = window.location.search.substr(1).replace(/\+/g, " ");
while ((match = re.exec(url))) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
};
let deferIds = {};
Utils.defer = function (f, id, t = 1) {
clearTimeout(deferIds[id]);
if (f === null) {
delete deferIds[id];
return;
}
deferIds[id] = setTimeout(() => {
delete deferIds[id];
f();
}, t);
};
Utils.getHashCode = function (s) {
let hash = 0,
l = s.length,
i;
for (i = 0; i < l; i++) {
hash = ((hash << 5) - hash + s.charCodeAt(i)) | 0;
}
return hash;
};
Utils.getPatternURL = function (pattern) {
let a = Utils.isLocal ? "?id=" : "/";
let url = window.location.origin,
id = (pattern && pattern.id) || "";
return url + a + id;
};
Utils.isLocal = window.location.hostname === "localhost";
Utils.getPatternURLStr = function (pattern) {
if (!pattern || !pattern.id) {
return null;
}
let a = Utils.isLocal ? "?id=" : "/";
let url = window.location.host,
id = pattern.id;
return url + a + id;
};
Utils.SUBST_ESC_CHARS = {
// this is just the list supported in Replace. Others: b, f, ", etc.
n: "\n",
r: "\r",
t: "\t",
"\\": "\\",
};
Utils.SUBST_ESC_RE = /\\([nrt\\]|u([A-Z0-9]{4}))/gi;
================================================
FILE: Public/js/runner.js
================================================
"use strict";
import ReconnectingWebSocket from "reconnecting-websocket";
export class Runner {
constructor() {
this.connection = this.createConnection(this.endpoint());
this.onconnect = () => {};
this.onready = () => {};
this.onresponse = () => {};
}
get isReady() {
return this.connection.readyState === 1;
}
run(request) {
const encoder = new TextEncoder();
this.connection.send(encoder.encode(JSON.stringify(request)));
}
createConnection(endpoint) {
if (
this.connection &&
(this.connection.readyState === 0 || this.connection.readyState === 1)
) {
return this.connection;
}
const connection = new ReconnectingWebSocket(endpoint, [], {
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
reconnectionDelayGrowFactor: 1.3,
connectionTimeout: 10000,
maxRetries: Infinity,
debug: false,
});
connection.bufferType = "arraybuffer";
connection.onopen = () => {
this.onconnect();
this.onready();
};
connection.onerror = (event) => {
connection.close();
};
connection.onmessage = (event) => {
if (event.data.trim()) {
this.onresponse(JSON.parse(event.data));
}
};
return connection;
}
endpoint() {
let endpoint;
if (window.location.protocol === "https:") {
endpoint = "wss:";
} else {
endpoint = "ws:";
}
endpoint += "//" + window.location.host;
endpoint += window.location.pathname + "api/ws";
return endpoint;
}
}
================================================
FILE: Public/js/state/decoder.js
================================================
"use strict";
import { ungzip } from "pako";
export class Decoder {
static decode(string) {
const base64 = decodeURIComponent(string);
const gziped = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
const json = ungzip(gziped, { to: "string" });
return JSON.parse(json);
}
}
================================================
FILE: Public/js/state/encoder.js
================================================
"use strict";
import { gzip } from "pako";
export class Encoder {
static encode(data) {
const json = JSON.stringify(data);
const gziped = gzip(json);
const base64 = btoa(String.fromCharCode(...gziped));
return encodeURIComponent(base64);
}
}
================================================
FILE: Public/js/state/worker.js
================================================
"use strict";
import { Decoder } from "./decoder.js";
import { Encoder } from "./encoder.js";
onmessage = (e) => {
if (!e.data || !e.data.type || !e.data.value) {
return;
}
switch (e.data.type) {
case "decode": {
const searchParams = new URLSearchParams(e.data.value);
const query = Object.fromEntries(searchParams.entries());
if (!query.s) {
return;
}
try {
const data = Decoder.decode(query.s);
if (!data) {
return;
}
const pattern = data.p;
const options = data.o;
const text1 = data.t1;
const builder = data.b;
const text2 = data.t2;
postMessage({
type: e.data.type,
value: {
pattern,
options,
text1,
builder,
text2,
},
});
} catch (error) {}
break;
}
case "encode": {
postMessage({
type: e.data.type,
value: `?s=${Encoder.encode({
p: e.data.value.pattern,
o: e.data.value.options,
t1: e.data.value.text1,
b: e.data.value.builder,
t2: e.data.value.text2,
})}`,
});
break;
}
}
};
================================================
FILE: Public/js/views/debugger_highlighter.js
================================================
"use strict";
import Editor from "./editor";
export default class DebuggerHighlighter {
constructor(editor) {
this.editor = editor;
this.activeMarks = [];
this.widgets = [];
}
draw(traces, backtrack) {
this.clear();
const editor = this.editor;
editor.operation(() => {
const doc = editor.getDoc();
const marks = this.activeMarks;
const defaultTextHeight = editor.defaultTextHeight();
for (const trace of traces) {
const className = "debuggermatch";
if (trace.location.start !== trace.location.end) {
const location = Editor.calcRangePos(
this.editor,
trace.location.start,
trace.location.end - trace.location.start
);
marks.push(
doc.markText(location.startPos, location.endPos, {
className: className,
})
);
} else {
const pos = doc.posFromIndex(trace.location.start);
const widget = document.createElement("span");
widget.className = className;
widget.style.height = `${defaultTextHeight * 1.5}px`;
widget.style.width = "1px";
widget.style.zIndex = "10";
this.editor.addWidget(pos, widget);
const coords = editor.charCoords(pos, "local");
widget.style.left = `${coords.left}px`;
widget.style.top = `${coords.top + 2}px`;
this.widgets.push(widget);
}
}
if (backtrack) {
const pos = doc.posFromIndex(backtrack.start);
const widget = document.createElement("span");
widget.className = "debuggerbacktrack";
widget.style.height = `${defaultTextHeight * 1.5}px`;
widget.style.width = `${editor.defaultCharWidth()}px`;
widget.style.zIndex = "10";
this.editor.addWidget(pos, widget);
const coords = editor.charCoords(pos, "local");
widget.style.left = `${coords.left}px`;
widget.style.top = `${coords.top + 2}px`;
this.widgets.push(widget);
}
});
}
clear() {
this.editor.operation(() => {
let marks = this.activeMarks;
for (var i = 0, l = marks.length; i < l; i++) {
marks[i].clear();
}
marks.length = 0;
for (const widget of this.widgets) {
widget.parentNode.removeChild(widget);
}
this.widgets.length = 0;
});
}
}
================================================
FILE: Public/js/views/debugger_text.js
================================================
"use strict";
import Editor from "./editor";
import DebuggerHighlighter from "./debugger_highlighter";
export class DebuggerText {
constructor(container) {
this.container = container;
this.init(container);
}
get value() {
return this.editor.getValue();
}
set value(val) {
this.editor.setValue(val);
}
init(container) {
const editor = Editor.create(
container,
{
lineWrapping: true,
screenReaderLabel: "Debugger Test View",
readOnly: true,
},
"100%",
"100%"
);
this.editor = editor;
this.highlighter = new DebuggerHighlighter(editor);
}
}
================================================
FILE: Public/js/views/dsl_editor.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import Editor from "./editor";
import ErrorMessage from "./error_message";
import Utils from "../misc/utils";
const defaultValue = `Regex {
Capture {
ChoiceOf {
"CREDIT"
"DEBIT"
}
}
OneOrMore(.whitespace)
Capture {
Regex {
Repeat(1...2) {
One(.digit)
}
"/"
Repeat(1...2) {
One(.digit)
}
"/"
Repeat(count: 4) {
One(.digit)
}
}
}
}
`;
export class DSLEditor extends EventDispatcher {
static defaultValue = defaultValue;
constructor(container) {
super();
this.container = container;
this.init(container);
}
get value() {
return this.editor.getValue();
}
set value(val) {
this.editor.setValue(val);
}
set error(error) {
const editor = this.editor;
const widgets = this.widgets;
editor.operation(function () {
for (const widget of widgets) {
editor.removeLineWidget(widget);
}
widgets.length = 0;
if (!error) {
return;
}
widgets.push(
editor.addLineWidget(0, ErrorMessage.create(error), {
coverGutter: false,
noHScroll: true,
above: true,
})
);
});
}
init(container) {
this.editor = Editor.create(
container,
{
lineNumbers: true,
lineWrapping: false,
matchBrackets: true,
mode: "swift",
screenReaderLabel: "Build DSL Editor",
},
"100%",
"100%"
);
this.editor.setValue(defaultValue);
this.editor.setCursor(this.editor.lineCount(), 0);
this.editor.on("change", (editor, event) =>
this.onEditorChange(editor, event)
);
this.widgets = [];
}
setDefaultValue() {
this.editor.setValue(defaultValue);
}
deferUpdate() {
Utils.defer(() => this.update(), "DSLEditor.update");
}
update() {
this.dispatchEvent("change");
}
onEditorChange(editor, event) {
this.deferUpdate();
}
}
================================================
FILE: Public/js/views/dsl_highlighter.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import Editor from "./editor";
export class DSLHighlighter extends EventDispatcher {
constructor(editor) {
super();
this.editor = editor;
this.activeMarks = [];
}
clear() {
this.editor.operation(() => {
let marks = this.activeMarks;
for (var i = 0, l = marks.length; i < l; i++) {
marks[i].clear();
}
marks.length = 0;
});
}
draw(tokens) {
const editor = this.editor;
this.clear();
editor.operation(() => {
const doc = editor.getDoc();
const marks = this.activeMarks;
for (const token of tokens) {
const className = "highlight";
const location = Editor.calcRangePos(
this.editor,
token.sourceLocation.start,
token.sourceLocation.end - token.sourceLocation.start
);
marks.push(
doc.markText(location.startPos, location.endPos, {
className: className,
})
);
}
});
}
}
================================================
FILE: Public/js/views/dsl_view.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import Editor from "./editor";
import ErrorMessage from "./error_message";
export class DSLView extends EventDispatcher {
constructor(container) {
super();
this.container = container;
this.init(container);
}
get value() {
return this.editor.getValue();
}
set value(val) {
this.editor.setValue(val);
}
set error(error) {
const editor = this.editor;
const widgets = this.widgets;
editor.operation(function () {
for (const widget of widgets) {
editor.removeLineWidget(widget);
}
widgets.length = 0;
if (!error) {
return;
}
if (typeof error === "string" || error instanceof String) {
widgets.push(
editor.addLineWidget(0, ErrorMessage.create(error), {
coverGutter: false,
noHScroll: true,
above: true,
}),
);
} else {
for (const e of error) {
const message = ErrorMessage.create(e.message);
widgets.push(
editor.addLineWidget(0, message, {
coverGutter: false,
noHScroll: true,
above: true,
}),
);
}
}
});
}
init(container) {
this.editor = Editor.create(
container,
{
lineNumbers: true,
lineWrapping: false,
matchBrackets: true,
mode: "swift",
readOnly: true,
screenReaderLabel: "Build DSL View",
},
"100%",
"100%",
);
this.widgets = [];
}
}
================================================
FILE: Public/js/views/editor.js
================================================
"use strict";
import CodeMirror from "codemirror";
import "codemirror/mode/swift/swift";
import Utils from "../misc/utils";
const Editor = {};
export default Editor;
Editor.create = (target, opts = {}, width = "100%", height = "100%") => {
const keys = {};
const o = Utils.copy(
{
extraKeys: keys,
indentWithTabs: false,
lineNumbers: false,
mode: "null",
specialChars:
/[ \u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/,
specialCharPlaceholder: (ch) =>
createElement("span", ch === " " ? "cm-space" : "cm-special", " "), // needs to be a space so wrapping works
tabSize: 2,
},
opts,
);
const cm = CodeMirror(target, o);
cm.setSize(width, height);
if (cm.getOption("maxLength")) {
cm.on("beforeChange", Editor.enforceMaxLength);
}
if (cm.getOption("singleLine")) {
cm.on("beforeChange", Editor.enforceSingleLine);
}
return cm;
};
Editor.enforceMaxLength = (cm, change) => {
let maxLength = cm.getOption("maxLength");
if (maxLength && change.update) {
let str = change.text.join("\n");
let delta =
str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from));
if (delta <= 0) {
return true;
}
delta = cm.getValue().length + delta - maxLength;
if (delta > 0) {
str = str.substr(0, str.length - delta);
change.update(change.from, change.to, str.split("\n"));
}
}
return true;
};
Editor.enforceSingleLine = (cm, change) => {
if (change.update) {
let str = change.text.join("").replace(/(\n|\r)/g, "");
change.update(change.from, change.to, [str]);
}
return true;
};
Editor.selectAll = (cm) => {
cm.focus();
cm.setSelection({ ch: 0, line: 0 }, { ch: 0, line: cm.lineCount() });
};
Editor.calcRangePos = (cm, i, l = 0, o = {}) => {
let doc = cm.getDoc();
o.startPos = doc.posFromIndex(i);
o.endPos = doc.posFromIndex(i + l);
return o;
};
function createElement(type, className, content, parent) {
let element = document.createElement(type || "div");
if (className) {
element.className = className;
}
if (content) {
if (content instanceof HTMLElement) {
element.appendChild(content);
} else {
element.innerHTML = content;
}
}
if (parent) {
parent.appendChild(element);
}
return element;
}
================================================
FILE: Public/js/views/error_message.js
================================================
"use strict";
const ErrorMessage = {};
export default ErrorMessage;
ErrorMessage.create = (message) => {
const container = document.createElement("div");
container.classList.add("error-message", "d-flex", "flex-row");
const wrapper = document.createElement("div");
wrapper.classList.add("d-flex", "flex-row", "overflow-hidden", "w-100");
container.appendChild(wrapper);
const iconWrapper = document.createElement("div");
wrapper.appendChild(iconWrapper);
const icon = document.createElement("span");
icon.classList.add(
"fa-solid",
"fa-octagon-xmark",
"fa-xs",
"text-danger",
"px-2",
);
iconWrapper.appendChild(icon);
const messageWrapper = document.createElement("div");
messageWrapper.classList.add("text-nowrap");
messageWrapper.textContent = message;
wrapper.appendChild(messageWrapper);
return container;
};
================================================
FILE: Public/js/views/expression_field.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import tippy from "tippy.js";
import Editor from "./editor";
import ExpressionHighlighter from "./expression_highlighter";
import Utils from "../misc/utils";
export class ExpressionField extends EventDispatcher {
constructor(container) {
super();
this.container = container;
this.init(container);
}
get value() {
return this.editor.getValue();
}
set value(val) {
this.editor.setValue(val);
}
set tokens(tokens) {
this.expressionTokens = tokens;
this.highlighter.draw(tokens);
this.resetTooltips();
}
set error(error) {
if (error.length) {
let message = "";
if (typeof error === "string" || error instanceof String) {
const errorMessage = Utils.htmlSafe(error);
message = `<span class="fw-bolder text-danger">Parse Error:</span> ${errorMessage}`;
} else {
message = error
.map((e) => {
const errorMessage = Utils.htmlSafe(e.message);
return `<span class="fw-bolder text-danger">${e.behavior}:</span> ${errorMessage}`;
})
.join("<br>");
this.highlighter.drawError(error);
}
this.errorMessageTooltip.setContent(message);
document
.getElementById("expression-field-error")
.classList.remove("d-none");
} else {
this.errorMessageTooltip.setContent("");
document.getElementById("expression-field-error").classList.add("d-none");
this.highlighter.clearError();
}
tippy(".exp-syntax-error", {
allowHTML: true,
animation: false,
placement: "bottom",
});
}
init(container) {
this.editor = Editor.create(
container,
{
autofocus: true,
maxLength: 2500,
singleLine: true,
screenReaderLabel: "Regular Expression Field",
},
"100%",
"100%",
);
this.editor.on("change", (editor, event) =>
this.onEditorChange(editor, event),
);
this.highlighter = new ExpressionHighlighter(this.editor);
this.expressionTokens = [];
this.activeTooltips = [];
this.errorMessageTooltip = tippy(
document.getElementById("expression-field-error"),
{
...tooltipProps,
},
);
}
setDefaultValue() {
this.editor.setValue(defaultValue);
this.editor.setCursor(this.editor.lineCount(), 0);
}
resetTooltips() {
for (const tooltip of this.activeTooltips) {
tooltip.destroy();
}
this.activeTooltips = tippy(tooltipSelector, {
...tooltipProps,
onShow: (instance) => {
const index = instance.reference.dataset.tokenIndex;
if (index === undefined) {
return false;
}
const token = this.expressionTokens[index];
this.onHover(token, instance);
return false;
},
});
}
deferUpdate() {
Utils.defer(() => this.update(), "ExpressionField.update");
}
update() {
this.dispatchEvent("change");
}
onEditorChange(editor, event) {
this.deferUpdate();
}
onHover(token, tippyInstance) {
this.hoverToken = token;
this.highlighter.drawHover(token);
this.dispatchEvent("hover");
for (const tooltip of this.activeTooltips) {
if (tooltip !== tippyInstance) {
tooltip.destroy();
}
}
this.activeTooltips = tippy(tooltipSelector, {
...tooltipProps,
onUntrigger: (instance) => {
this.highlighter.clearHover();
this.resetTooltips();
this.dispatchEvent("unhover");
},
});
}
}
const defaultValue = `(CREDIT|DEBIT)\\s+(\\d{1,2}/\\d{1,2}/\\d{4})`;
const tooltipSelector = "#expression-field-container span[data-tippy-content]";
const tooltipProps = {
allowHTML: true,
animation: false,
placement: "bottom",
};
================================================
FILE: Public/js/views/expression_highlighter.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import { Reference } from "../docs/reference";
import Editor from "./editor";
export default class ExpressionHighlighter extends EventDispatcher {
constructor(editor) {
super();
this.editor = editor;
this.activeMarks = [];
this.hoverMarks = [];
this.widgets = [];
}
draw(tokens) {
this.clear();
const pre = ExpressionHighlighter.CSS_PREFIX;
const editor = this.editor;
editor.operation(() => {
const doc = editor.getDoc();
const marks = this.activeMarks;
for (const [i, token] of Object.entries(tokens)) {
const location = Editor.calcRangePos(
this.editor,
token.location.start,
token.location.end - token.location.start,
);
const tooltipAttr = (() => {
if (token.tooltip) {
const reference = Reference.get(
token.tooltip.category,
token.tooltip.key,
);
let title = reference ? reference.title : token.tooltip.category;
let detail = reference ? reference.detail : token.tooltip.key;
for (const [k, v] of Object.entries(token.tooltip.substitution)) {
title = title.replaceAll(k, v);
detail = detail.replaceAll(k, v);
}
return {
"data-tippy-content": makeTooltip(title, detail),
};
} else {
return {};
}
})();
marks.push(
doc.markText(location.startPos, location.endPos, {
className: `${token.classes.map((c) => `${pre}-${c}`).join(" ")}`,
attributes: {
...tooltipAttr,
"data-token-index": i,
},
}),
);
}
});
}
drawError(errors) {
this.clearError();
const pre = ExpressionHighlighter.CSS_PREFIX;
const editor = this.editor;
editor.operation(() => {
for (const error of errors) {
const location = Editor.calcRangePos(
this.editor,
error.location.start,
error.location.end - error.location.start,
);
const widget = document.createElement("span");
widget.className = `${pre}-syntax-error`;
widget.style.height = `5px`;
widget.style.zIndex = "10";
widget.setAttribute(
"data-tippy-content",
`<span class="fw-bolder text-danger">${error.behavior}:</span> ${error.message}`,
);
editor.addWidget(location.startPos, widget);
const startCoords = editor.charCoords(location.startPos, "local");
const endCoords = editor.charCoords(location.endPos, "local");
widget.style.left = `${startCoords.left + 1}px`;
widget.style.top = `${startCoords.bottom - 1}px`;
if (error.location.start === error.location.end) {
widget.style.width = `${editor.defaultCharWidth()}px`;
} else {
widget.style.width = `${endCoords.left - startCoords.left - 2}px`;
}
this.widgets.push(widget);
}
});
}
clear() {
this.editor.operation(() => {
for (const mark of this.activeMarks) {
mark.clear();
}
this.activeMarks.length = 0;
});
}
clearError() {
this.editor.operation(() => {
for (const widget of this.widgets) {
widget.parentNode.removeChild(widget);
}
this.widgets.length = 0;
});
}
drawHover(token) {
const selection = token.selection;
const related = token.related;
if ((!selection && !related) || this.hoverMarks.length) {
return;
}
this.clearHover();
if (selection) {
this.drawBorder(selection, "selected");
}
if (related) {
this.drawBorder(related.location, "related");
}
}
drawBorder(range, className) {
const editor = this.editor;
const doc = editor.getDoc();
const pre = ExpressionHighlighter.CSS_PREFIX;
const left = Editor.calcRangePos(this.editor, range.start, 1);
const location = Editor.calcRangePos(
this.editor,
range.start,
range.end - range.start,
);
const right = Editor.calcRangePos(this.editor, range.end - 1, 1);
this.hoverMarks.push(
doc.markText(left.startPos, left.endPos, {
className: `${pre}-${className}-left`,
}),
);
this.hoverMarks.push(
doc.markText(location.startPos, location.endPos, {
className: `${pre}-${className}`,
}),
);
this.hoverMarks.push(
doc.markText(right.startPos, right.endPos, {
className: `${pre}-${className}-right`,
}),
);
}
clearHover() {
this.editor.operation(() => {
for (const mark of this.hoverMarks) {
mark.clear();
}
this.hoverMarks.length = 0;
});
}
}
function makeTooltip(label, desc) {
return `<div class="text-start"><span class="fw-bolder">${label}.</span><span> ${desc}</span></div>`;
}
ExpressionHighlighter.CSS_PREFIX = "exp";
================================================
FILE: Public/js/views/match_options.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
export class MatchOptions extends EventDispatcher {
constructor() {
super();
this.init();
}
init() {
document.querySelectorAll(".match-options-item").forEach((listItem) => {
listItem.addEventListener("click", (event) => {
listItem.classList.toggle("active-tick");
this.dispatchEvent("change");
});
});
}
get value() {
const options = [];
document.querySelectorAll(".match-options-item").forEach((listItem) => {
if (listItem.classList.contains("active-tick")) {
options.push(listItem.dataset.value);
}
});
return options;
}
set value(options) {
document.querySelectorAll(".match-options-item").forEach((listItem) => {
if (options.includes(listItem.dataset.value)) {
listItem.classList.add("active-tick");
}
});
}
setDefaultValue() {
this.value = ["g", "m"];
}
}
================================================
FILE: Public/js/views/test_editor.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import tippy from "tippy.js";
import Editor from "./editor";
import TestHighlighter from "./test_highlighter";
import ErrorMessage from "./error_message";
import Utils from "../misc/utils";
const defaultValue = `KIND DATE INSTITUTION AMOUNT
----------------------------------------------------------------
CREDIT 03/01/2022 Payroll from employer $200.23
CREDIT 03/03/2022 Suspect A $2,000,000.00
DEBIT 03/03/2022 Ted's Pet Rock Sanctuary $2,000,000.00
DEBIT 03/05/2022 Doug's Dugout Dogs $33.27
DEBIT 06/03/2022 Oxford Comma Supply Ltd. £57.33
`;
export class TestEditor extends EventDispatcher {
static defaultValue = defaultValue;
constructor(container) {
super();
this.container = container;
this.init(container);
}
get value() {
return this.editor.getValue();
}
set value(val) {
this.editor.setValue(val);
}
set error(error) {
const editor = this.editor;
const widgets = this.widgets;
editor.operation(function () {
for (const widget of widgets) {
editor.removeLineWidget(widget);
}
widgets.length = 0;
if (!error) {
return;
}
widgets.push(
editor.addLineWidget(0, ErrorMessage.create(error), {
coverGutter: false,
noHScroll: true,
above: true,
}),
);
});
}
set matches(matches) {
this.highlighter.draw(matches);
tippy(".test-editor-container span[data-tippy-content]", {
allowHTML: true,
animation: false,
placement: "bottom",
});
}
init(container) {
const editor = Editor.create(
container,
{ lineWrapping: true, screenReaderLabel: "Pattern Test View" },
"100%",
"100%",
);
this.editor = editor;
this.highlighter = new TestHighlighter(editor);
this.widgets = [];
editor.on("change", (editor, event) => this.onEditorChange(editor, event));
}
setDefaultValue() {
this.editor.setValue(defaultValue);
}
deferUpdate() {
Utils.defer(() => this.update(), "TestEditor.update");
}
update() {
this.dispatchEvent("change");
}
onEditorChange(editor, event) {
this.deferUpdate();
}
}
================================================
FILE: Public/js/views/test_highlighter.js
================================================
"use strict";
import { EventDispatcher } from "@createjs/easeljs";
import Editor from "./editor";
import Utils from "../misc/utils";
export default class TestHighlighter extends EventDispatcher {
constructor(editor) {
super();
this.editor = editor;
this.activeMarks = [];
this.widgets = [];
this.textHeight = editor.defaultTextHeight();
}
draw(tokens) {
this.clear();
const editor = this.editor;
editor.operation(() => {
const doc = editor.getDoc();
const marks = this.activeMarks;
for (const token of tokens) {
const match = Utils.htmlSafe(token.value);
let tooltip = `<div class="text-start font-monospace">
<div><span class="fw-bolder">match:</span> ${match}</div>
<div class="fw-bolder">range: ${token.location.start}-${token.location.end}</div>
</div>`;
if (token.captures.length) {
tooltip += "<hr>";
for (const [i, capture] of token.captures.entries()) {
const value = Utils.htmlSafe(capture.value || "");
const name = Utils.htmlSafe(capture.name || "");
tooltip += `<div class="text-start font-monospace">
<div><span class="fw-bolder">group #${i + 1}${
name ? ` ${name}` : ""
}:</span> ${value === "" ? "empty string" : value}</div>
</div>`;
}
}
if (token.location.start < token.location.end) {
const location = Editor.calcRangePos(
editor,
token.location.start,
token.location.end - token.location.start,
);
marks.push(
doc.markText(location.startPos, location.endPos, {
className: "match-char",
attributes: {
"data-tippy-content": tooltip,
},
}),
);
} else {
const location = Editor.calcRangePos(editor, token.location.start, 1);
if (
location.startPos.line === location.endPos.line &&
location.startPos.ch === location.endPos.ch
) {
this.addLeftAnchor(location, { "data-tippy-content": tooltip });
}
if (location.startPos.line === location.endPos.line) {
if (location.startPos.ch < location.endPos.ch) {
marks.push(
doc.markText(location.startPos, location.endPos, {
className: "match-left",
attributes: {
"data-tippy-content": tooltip,
},
}),
);
} else {
// this.addRightAnchor(location, { "data-tippy-content": tooltip });
}
} else {
if (location.startPos.ch === 0 && location.endPos.ch === 0) {
this.addLeftAnchor(location, { "data-tippy-content": tooltip });
} else {
this.addRightAnchor(location, { "data-tippy-content": tooltip });
}
}
}
}
});
}
addLeftAnchor(location, attributes = {}) {
const widget = document.createElement("span");
widget.className = "match-left";
widget.style.height = `${this.textHeight * 1.5}px`;
widget.style.width = "1px";
widget.style.zIndex = "10";
for (const [key, value] of Object.entries(attributes)) {
widget.setAttribute(key, value);
}
this.editor.addWidget(location.startPos, widget);
const coords = this.editor.charCoords(location.startPos, "local");
widget.style.left = `${coords.left}px`;
widget.style.top = `${coords.top + 2}px`;
this.widgets.push(widget);
}
addRightAnchor(location, attributes = {}) {
const widget = document.createElement("span");
widget.className = "match-right";
widget.style.height = `${this.textHeight * 1.5}px`;
widget.style.width = "1px";
widget.style.zIndex = "10";
for (const [key, value] of Object.entries(attributes)) {
widget.setAttribute(key, value);
}
this.editor.addWidget(location.endPos, widget);
const coords = this.editor.charCoords(location.startPos, "local");
widget.style.left = `${coords.left}px`;
widget.style.top = `${coords.top + 2}px`;
this.widgets.push(widget);
}
clear() {
this.editor.operation(() => {
let marks = this.activeMarks;
for (var i = 0, l = marks.length; i < l; i++) {
marks[i].clear();
}
marks.length = 0;
for (const widget of this.widgets) {
widget.parentNode.removeChild(widget);
}
this.widgets.length = 0;
});
}
}
================================================
FILE: Public/robots.txt
================================================
================================================
FILE: Public/scss/default.scss
================================================
$table-cell-padding-y-sm: 0.05rem;
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/tables";
@import "bootstrap/scss/forms";
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
@import "bootstrap/scss/dropdown";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/close";
@import "bootstrap/scss/modal";
@import "bootstrap/scss/tooltip";
@import "bootstrap/scss/helpers";
@import "bootstrap/scss/utilities/api";
================================================
FILE: README.md
================================================
<p>
<a href="https://github.com/kishikawakatsumi/swiftregex/actions">
<img src="https://github.com/kishikawakatsumi/swiftregex/workflows/CI/badge.svg">
</a>
<img src="https://img.shields.io/badge/os-macOS/Linux-green.svg?style=flat" alt="macOS/Linux">
<a href="http://swift.org">
<img src="https://img.shields.io/badge/swift-5.7-orange.svg?style=flat" alt="Swift 5.7 Compatible">
</a>
<a href="https://github.com/kishikawakatsumi/swiftregex/blob/master/LICENSE">
<img src="https://img.shields.io/badge/license-MIT-yellow.svg?style=flat" alt="MIT">
</a>
</p>
# Swift Regex
Regular Expression Tester with highlighting for Swift Regex. Quickly test and debug your regex and Regex Builder.
<a href="https://swiftregex.com"><img width="1280" alt="Screen Shot" src="https://user-images.githubusercontent.com/40610/176885327-6b7b9b42-a9d8-4ac0-bf7d-4df6be021d89.png"></a>
<a href="https://swiftregex.com"><img width="1280" alt="Screen Shot" src="https://user-images.githubusercontent.com/40610/176885334-dec898ce-a0b8-46a4-8ce0-81e8892d2e6e.png"></a>
https://swiftregex.com
================================================
FILE: SECURITY.md
================================================
# Security Policy
For security related problems, please don't use the public issue tracker, but email [@kishikawakatsumi](https://github.com/kishikawakatsumi).
================================================
FILE: Sources/App/Debugger/Context.swift
================================================
import Foundation
extension Debugger {
class Context {
var instructions: [String] = []
var programCounter = 0
var stepCount = 0
var breakPoint: Int?
var start: Int = 0
var current: Int = 0
var failurePosition: Int = 0
var totalCycleCount = 0
var resets = 0
var backtracks = 0
init(stepCount: Int = 0, breakPoint: Int? = nil) {
self.stepCount = stepCount
self.breakPoint = breakPoint
}
func reset() {
instructions = []
programCounter = 0
stepCount = 0
breakPoint = nil
start = 0
current = 0
failurePosition = 0
totalCycleCount = 0
resets = 0
backtracks = 0
}
}
}
================================================
FILE: Sources/App/Debugger/Debugger.swift
================================================
import Foundation
@testable import _RegexParser
@testable @_spi(RegexBenchmark) import _StringProcessing
struct Debugger {
func run(pattern: String, text: String, matchingOptions: [String] = [], context: Debugger.Context) throws {
let ast = try _RegexParser.parse(pattern, .traditional)
var sequence = [AST.MatchingOption]()
if matchingOptions.contains("i") {
sequence.append(.init(.caseInsensitive, location: .fake))
}
if matchingOptions.contains("s") {
sequence.append(.init(.singleLine, location: .fake))
}
if matchingOptions.contains("asciiOnlyWordCharacters") {
sequence.append(.init(.asciiOnlyWord, location: .fake))
}
if matchingOptions.contains("asciiOnlyDigits") {
sequence.append(.init(.asciiOnlyDigit, location: .fake))
}
if matchingOptions.contains("asciiOnlyWhitespace") {
sequence.append(.init(.asciiOnlySpace, location: .fake))
}
if matchingOptions.contains("asciiOnlyCharacterClasses") {
sequence.append(.init(.asciiOnlyPOSIXProps, location: .fake))
}
sequence.append(.init(.graphemeClusterSemantics, location: .fake))
var options = MatchingOptions()
options.apply(AST.MatchingOptionSequence(adding: sequence))
let program = try compile(ast, options: options)
context.instructions = program.instructions.map {
$0.description
}
let inputRange = text.startIndex..<text.endIndex
var cpu = Processor(
program: program,
input: text,
subjectBounds: inputRange,
searchBounds: inputRange,
matchMode: .partialFromFront
)
do {
_ = try Executor<AnyRegexOutput>._firstMatch(
program,
using: &cpu,
context: context
)
} catch {}
}
func compile(_ ast: AST, options: MatchingOptions) throws -> MEProgram {
let compiler = Compiler(tree: ast.dslTree, compileOptions: [.enableMetrics])
compiler.options = options
return try compiler.emit()
}
struct Metrics: Codable {
var instructions: [String]
var programCounter: Int
var stepCount: Int
var step: Int
var totalCycleCount: Int
var resets: Int
var backtracks: Int
var traces: [Trace]
var failure: Location
}
struct Trace: Codable {
let location: Location
}
struct Location: Codable {
let start: Int
let end: Int
}
}
================================================
FILE: Sources/App/Debugger/Executor.swift
================================================
@testable import _RegexParser
@testable import _StringProcessing
enum Executor<Output> {
static func prefixMatch(
_ program: MEProgram,
_ input: String,
subjectBounds: Range<String.Index>,
searchBounds: Range<String.Index>,
context: Debugger.Context
) throws -> Regex<Output>.Match? {
try Executor._run(
program,
input,
subjectBounds: subjectBounds,
searchBounds: searchBounds,
mode: .partialFromFront,
context: context
)
}
static func wholeMatch(
_ program: MEProgram,
_ input: String,
subjectBounds: Range<String.Index>,
searchBounds: Range<String.Index>,
context: Debugger.Context
) throws -> Regex<Output>.Match? {
try Executor._run(
program,
input,
subjectBounds: subjectBounds,
searchBounds: searchBounds,
mode: .wholeString,
context: context
)
}
static func firstMatch(
_ program: MEProgram,
_ input: String,
subjectBounds: Range<String.Index>,
searchBounds: Range<String.Index>,
context: Debugger.Context
) throws -> Regex<Output>.Match? {
var cpu = Processor(
program: program,
input: input,
subjectBounds: subjectBounds,
searchBounds: searchBounds,
matchMode: .partialFromFront
)
return try Executor._firstMatch(
program,
using: &cpu,
context: context
)
}
static func _firstMatch(
_ program: MEProgram,
using cpu: inout Processor,
context: Debugger.Context
) throws -> Regex<Output>.Match? {
let isGraphemeSemantic = program.initialOptions.semanticLevel == .graphemeCluster
var low = cpu.searchBounds.lowerBound
let high = cpu.searchBounds.upperBound
while true {
if let m = try Executor._run(program, &cpu, context) {
return m
}
// Fast-path for start-anchored regex
if program.canOnlyMatchAtStart {
return nil
}
if low == high { return nil }
if isGraphemeSemantic {
cpu.input.formIndex(after: &low)
} else {
cpu.input.unicodeScalars.formIndex(after: &low)
}
guard low <= high else {
return nil
}
cpu.reset(currentPosition: low, searchBounds: cpu.searchBounds)
}
}
}
extension Executor {
static func _run(
_ program: MEProgram,
_ input: String,
subjectBounds: Range<String.Index>,
searchBounds: Range<String.Index>,
mode: MatchMode,
context: Debugger.Context
) throws -> Regex<Output>.Match? {
var cpu = Processor(
program: program,
input: input,
subjectBounds: subjectBounds,
searchBounds: searchBounds,
matchMode: mode)
return try _run(program, &cpu, context)
}
static func _run(
_ program: MEProgram,
_ cpu: inout Processor,
_ context: Debugger.Context
) throws -> Regex<Output>.Match? {
let startPosition = cpu.currentPosition
context.start = startPosition.utf16Offset(in: cpu.input)
guard let endIdx = try cpu.run(context) else {
return nil
}
let range = startPosition..<endIdx
let anyRegexOutput = AnyRegexOutput(
input: cpu.input, elements: []
)
return .init(anyRegexOutput: anyRegexOutput, range: range)
}
}
extension Processor {
fileprivate mutating func run(_ context: Debugger.Context) throws -> Input.Index? {
if self.state == .fail {
if let e = failureReason {
throw e
}
return nil
}
assert(isReset())
while true {
context.programCounter = controller.pc.rawValue
switch self.state {
case .accept:
return self.currentPosition
case .fail:
if let e = failureReason {
throw e
}
return nil
case .inProgress:
let failurePosition = currentPosition.utf16Offset(in: input)
self.cycle()
context.stepCount += 1
context.current = currentPosition.utf16Offset(in: input)
context.failurePosition = failurePosition
#if PROCESSOR_MEASUREMENTS_ENABLED
context.totalCycleCount = metrics.cycleCount
context.resets = metrics.resets
context.backtracks = metrics.backtracks
#endif
if context.stepCount == context.breakPoint {
throw CancellationError()
}
}
}
}
}
================================================
FILE: Sources/App/Middlewares/CommonErrorMiddleware.swift
================================================
import Vapor
final class CommonErrorMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
return next.respond(to: request).flatMapError { (error) in
let headers: HTTPHeaders
let status: HTTPResponseStatus
switch error {
case let abort as AbortError:
headers = abort.headers
status = abort.status
default:
headers = [:]
status = .internalServerError
}
let errorTitles: [UInt: String] = [
400: "Bad Request",
401: "Unauthorized",
403: "Access Denied",
404: "Resource not found",
500: "Webservice currently unavailable",
503: "Webservice currently unavailable",
]
let errorReasons: [UInt: String] = [
400: "The server cannot process the request due to something that is perceived to be a client error.",
401: "The requested resource requires an authentication.",
403: "The requested resource requires an authentication.",
404: "The requested resource could not be found but may be available again in the future.",
500: "An unexpected condition was encountered. Our service team has been dispatched to bring it back online.",
503: "We've got some trouble with our backend upstream cluster. Our service team has been dispatched to bring it back online.",
]
if request.headers[.accept].map({ $0.lowercased() }).contains("application/json") {
return request.eventLoop.makeSucceededFuture(["error": status.code])
.encodeResponse(status: status, headers: headers, for: request)
} else {
return request.view.render(
"error",
[
"title": "We've got some trouble",
"error": errorTitles[status.code],
"reason": errorReasons[status.code],
"status": "\(status.code)",
]
)
.encodeResponse(status: status, headers: headers, for: request)
}
}
}
}
================================================
FILE: Sources/App/Middlewares/CustomHeaderMiddleware.swift
================================================
import Vapor
final class CustomHeaderMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
return next.respond(to: request).map { (response) in
response.headers.add(name: "X-Frame-Options", value: "DENY")
response.headers.add(name: "Permissions-Policy", value: "interest-cohort=()")
return response
}
}
}
================================================
FILE: Sources/App/Models/ExecRequest.swift
================================================
import Foundation
struct ExecRequest: Codable {
let method: RequestMethod
let pattern: String
let text: String
let matchOptions: [String]
let step: String?
}
enum RequestMethod: String, Codable {
case parseExpression
case convertToDSL
case match
case debug
}
================================================
FILE: Sources/App/Models/ResultResponse.swift
================================================
import Foundation
import Vapor
struct ResultResponse: Content {
let method: RequestMethod
let result: String
let error: String
}
================================================
FILE: Sources/App/configure.swift
================================================
import Leaf
import Vapor
public func configure(_ app: Application) async throws {
app.middleware = Middlewares()
app.middleware.use(CommonErrorMiddleware())
app.middleware.use(CustomHeaderMiddleware())
let publicDirectory = "\(app.directory.publicDirectory)/dist"
app.middleware.use(FileMiddleware(publicDirectory: publicDirectory))
app.http.server.configuration.port = Environment.process.PORT.flatMap { Int($0) } ?? 8080
app.http.server.configuration.requestDecompression = .enabled
app.http.server.configuration.responseCompression = .enabled
app.http.server.configuration.supportPipelining = true
app.views.use(.leaf)
app.leaf.configuration.rootDirectory = publicDirectory
app.leaf.cache.isEnabled = app.environment.isRelease
try routes(app)
}
================================================
FILE: Sources/App/entrypoint.swift
================================================
import Vapor
@main
enum Entrypoint {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = try await Application.make(env)
do {
try await configure(app)
try await app.execute()
} catch {
app.logger.report(error: error)
try? await app.asyncShutdown()
throw error
}
try await app.asyncShutdown()
}
}
================================================
FILE: Sources/App/routes.swift
================================================
import Vapor
func routes(_ app: Application) throws {
app.get("health") { _ in ["status": "pass"] }
app.get("healthz") { _ in ["status": "pass"] }
app.get { (req) in req.view.render("index") }
app.webSocket("api", "ws") { (req, ws) in
ws.onBinary { (ws, buffer) in
do {
guard let data = buffer.getData(at: 0, length: buffer.readableBytes) else { return }
let decoder = JSONDecoder()
let request = try decoder.decode(ExecRequest.self, from: data)
let encoder = JSONEncoder()
switch request.method {
case .parseExpression:
let pattern = request.pattern
let matchOptions = request.matchOptions
let response = try parseExpression(pattern: pattern, matchOptions: matchOptions)
if let message = String(data: try encoder.encode(response), encoding: .utf8) {
ws.send(message)
}
case .convertToDSL:
let pattern = request.pattern
let matchOptions = request.matchOptions
let response = try convertToDSL(pattern: pattern, matchOptions: matchOptions)
if let message = String(data: try encoder.encode(response), encoding: .utf8) {
ws.send(message)
}
case .match:
let pattern = request.pattern
let text = request.text
let matchOptions = request.matchOptions
let response = try match(pattern: pattern, text: text, matchOptions: matchOptions)
if let message = String(data: try encoder.encode(response), encoding: .utf8) {
ws.send(message)
}
case .debug:
let pattern = request.pattern
let text = request.text
let matchOptions = request.matchOptions
let step = request.step
let response = try debug(pattern: pattern, text: text, matchOptions: matchOptions, step: step)
if let message = String(data: try encoder.encode(response), encoding: .utf8) {
ws.send(message)
}
}
} catch {
req.logger.error("\(error)")
}
}
}
app.on(.POST, "api", "rest", "parseExpression", body: .collect(maxSize: "1mb")) { (req) -> ResultResponse in
guard let request = try? req.content.decode(ExecRequest.self) else {
throw Abort(.badRequest)
}
let pattern = request.pattern
let matchOptions = request.matchOptions
let response = try parseExpression(pattern: pattern, matchOptions: matchOptions)
return response
}
app.on(.POST, "api", "rest", "convertToDSL", body: .collect(maxSize: "1mb")) { (req) -> ResultResponse in
guard let request = try? req.content.decode(ExecRequest.self) else {
throw Abort(.badRequest)
}
let pattern = request.pattern
let matchOptions = request.matchOptions
let response = try convertToDSL(pattern: pattern, matchOptions: matchOptions)
return response
}
app.on(.POST, "api", "rest", "match", body: .collect(maxSize: "1mb")) { (req) -> ResultResponse in
guard let request = try? req.content.decode(ExecRequest.self) else {
throw Abort(.badRequest)
}
let pattern = request.pattern
let text = request.text
let matchOptions = request.matchOptions
let response = try match(pattern: pattern, text: text, matchOptions: matchOptions)
return response
}
app.on(.POST, "api", "rest", "debug", body: .collect(maxSize: "1mb")) { (req) -> ResultResponse in
guard let request = try? req.content.decode(ExecRequest.self) else {
throw Abort(.badRequest)
}
let pattern = request.pattern
let text = request.text
let matchOptions = request.matchOptions
let step = request.step
let response = try debug(pattern: pattern, text: text, matchOptions: matchOptions, step: step)
return response
}
func parseExpression(pattern: String, matchOptions: [String]) throws -> ResultResponse {
let (stdout, stderr) = try exec(command: "ExpressionParser", arguments: pattern, matchOptions.joined(separator: ","))
return ResultResponse(method: .parseExpression, result: stdout, error: stderr)
}
func convertToDSL(pattern: String, matchOptions: [String]) throws -> ResultResponse {
let (stdout, stderr) = try exec(command: "DSLConverter", arguments: pattern, matchOptions.joined(separator: ","))
return ResultResponse(method: .convertToDSL, result: stdout, error: stderr)
}
func match(pattern: String, text: String, matchOptions: [String]) throws -> ResultResponse {
let (stdout, stderr) = try exec(command: "Matcher", arguments: pattern, text, matchOptions.joined(separator: ","))
return ResultResponse(method: .match, result: stdout, error: stderr)
}
func debug(pattern: String, text: String, matchOptions: [String], step: String?) throws -> ResultResponse {
let context = Debugger.Context()
func run(pattern: String, text: String, matchingOptions: [String] = [], until step: Int? = nil) throws {
context.reset()
context.breakPoint = step
let debugger = Debugger()
try debugger.run(pattern: pattern, text: text, matchingOptions: matchingOptions, context: context)
}
let breakPoint: Int?
if let step {
breakPoint = Int(step)
} else {
breakPoint = nil
}
try run(pattern: pattern, text: text, matchingOptions: matchOptions)
let stepCount = context.stepCount
try run(pattern: pattern, text: text, matchingOptions: matchOptions, until: breakPoint)
let metrics = Debugger.Metrics(
instructions: context.instructions,
programCounter: context.programCounter,
stepCount: stepCount,
step: breakPoint ?? 1,
totalCycleCount: context.totalCycleCount,
resets: context.resets,
backtracks: context.backtracks,
traces: [
Debugger.Trace(
location: Debugger.Location(
start: context.start,
end: context.current
)
)
],
failure: Debugger.Location(start: context.current, end: context.failurePosition),
)
let data = try JSONEncoder().encode(metrics)
let result = String(data: data, encoding: .utf8) ?? ""
let response = ResultResponse(method: .debug, result: result, error: "")
return response
}
func exec(command: String, arguments: String...) throws -> (stdout: String, stderr: String) {
let process = Process()
let executableURL = URL(
fileURLWithPath: app.directory.workingDirectory
)
.appendingPathComponent(command)
process.executableURL = executableURL
process.arguments = arguments
let standardOutput = Pipe()
let standardError = Pipe()
process.standardOutput = standardOutput
process.standardError = standardError
var stdoutData = Data()
var stderrData = Data()
let group = DispatchGroup()
group.enter()
standardOutput.fileHandleForReading.readabilityHandler = { handle in
let chunk = handle.availableData
if chunk.isEmpty {
standardOutput.fileHandleForReading.readabilityHandler = nil
group.leave()
} else {
stdoutData.append(chunk)
}
}
group.enter()
standardError.fileHandleForReading.readabilityHandler = { handle in
let chunk = handle.availableData
if chunk.isEmpty {
standardError.fileHandleForReading.readabilityHandler = nil
group.leave()
} else {
stderrData.append(chunk)
}
}
try process.run()
group.wait()
process.waitUntilExit()
guard let stdout = String(data: stdoutData, encoding: .utf8) else {
throw Abort(.internalServerError)
}
guard let stderr = String(data: stderrData, encoding: .utf8) else {
throw Abort(.internalServerError)
}
return (stdout, stderr)
}
}
================================================
FILE: Sources/DSLConverter/DSLConverter.swift
================================================
import Foundation
@testable import _RegexParser
@testable @_spi(RegexBuilder) import _StringProcessing
@testable @_spi(PatternConverter) import _StringProcessing
class DSLConverter {
private(set) var diagnostics: Diagnostics?
func convert(_ pattern: String, matchingOptions: [String] = []) throws -> String {
let ast = _RegexParser.parseWithRecovery(pattern, .traditional)
diagnostics = ast.diags
var builderDSL = renderAsBuilderDSL(ast: ast)
if builderDSL.last == "\n" {
builderDSL = String(builderDSL.dropLast())
}
if matchingOptions.contains("m") {
builderDSL.append("\n")
builderDSL.append(".anchorsMatchLineEndings()")
}
if matchingOptions.contains("i") {
builderDSL.append("\n")
builderDSL.append(".ignoresCase()")
}
if matchingOptions.contains("s") {
builderDSL.append("\n")
builderDSL.append(".dotMatchesNewlines()")
}
if matchingOptions.contains("asciiOnlyWordCharacters") {
builderDSL.append("\n")
builderDSL.append(".asciiOnlyWordCharacters()")
}
if matchingOptions.contains("asciiOnlyDigits") {
builderDSL.append("\n")
builderDSL.append(".asciiOnlyDigits()")
}
if matchingOptions.contains("asciiOnlyWhitespace") {
builderDSL.append("\n")
builderDSL.append(".asciiOnlyWhitespace()")
}
if matchingOptions.contains("asciiOnlyCharacterClasses") {
builderDSL.append("\n")
builderDSL.append(".asciiOnlyCharacterClasses()")
}
builderDSL.append("\n")
return builderDSL
}
}
================================================
FILE: Sources/DSLConverter/Main.swift
================================================
import Foundation
@main
struct Main {
static func main() throws {
do {
let pattern = CommandLine.arguments[1]
let matchingOptions = CommandLine.arguments[2]
.split(separator: ",", omittingEmptySubsequences: true)
.map { String($0) }
let converter = DSLConverter()
let builderDSL = try converter.convert(pattern, matchingOptions: matchingOptions)
let data = try JSONEncoder().encode(builderDSL)
print(String(data: data, encoding: .utf8) ?? "")
if let diagnostics = converter.diagnostics {
let errors = diagnostics.diags.map {
let location = $0.location
let (start, end) = (location.start, location.end)
let behavior =
switch $0.behavior {
case .fatalError:
"Fatal Error"
case .error:
"Error"
case .warning:
"Warning"
}
return LocatedMessage(
behavior: behavior,
message: $0.message,
location: Location(
start: start.utf16Offset(in: pattern), end: end.utf16Offset(in: pattern)
)
)
}
let data = try JSONEncoder().encode(errors)
print(String(data: data, encoding: .utf8) ?? "", to: &standardError)
}
} catch {
print("\(error)", to: &standardError)
}
}
}
var standardError = FileHandle.standardError
extension FileHandle: @retroactive TextOutputStream {
public func write(_ string: String) {
guard let data = string.data(using: .utf8) else { return }
self.write(data)
}
}
struct Location: Codable {
let start: Int
let end: Int
}
struct LocatedMessage: Codable {
let behavior: String
let message: String
let location: Location
}
================================================
FILE: Sources/ExpressionParser/ExpressionParser.swift
================================================
import Foundation
@testable import _RegexParser
@testable @_spi(RegexBuilder) import _StringProcessing
struct ExpressionParser {
struct Modes {
let matchingOptions: [String]
var i: Bool { matchingOptions.contains("i") }
var s: Bool { matchingOptions.contains("s") }
}
private(set) var tokens = [Token]()
private(set) var diagnostics: Diagnostics?
private let pattern: String
private let matchingOptions: [String]
private let modes: Modes
private var depth = 0
private var groupCount = 0
init(pattern: String, matchingOptions: [String]) {
self.pattern = pattern
self.matchingOptions = matchingOptions
modes = Modes(matchingOptions: matchingOptions)
}
mutating func parse() {
let ast = _RegexParser.parseWithRecovery(pattern, .traditional)
diagnostics = ast.diags
emitNode(ast.root)
}
private mutating func emitNode(_ node: AST.Node) {
switch node {
case .alternation(let alt):
emitAlternation(alt)
case .concatenation(let concatenation):
for node in concatenation.children {
emitNode(node)
}
case .group(let group):
emitGroup(group)
case .conditional(let conditional):
emitConditional(conditional)
case .quantification(let quant):
emitQuantification(quant)
case .quote(let quote):
emitQuote(quote)
case .trivia(let trivia):
emitTrivia(trivia)
case .interpolation(let interpolation):
emitInterpolation(interpolation)
case .atom(let atom):
emitAtom(atom)
case .customCharacterClass(let ccc):
emitCustomCharacterClass(ccc)
case .absentFunction(let absentFunction):
emitAbsentFunction(absentFunction)
case .empty(let empty):
emitEmpty(empty)
}
}
private mutating func emitAlternation(_ alt: AST.Alternation) {
let children = alt.children
for node in children.dropLast() {
emitNode(node)
}
for pipe in alt.pipes {
tokens.append(
Token(
classes: ["alt"],
location: Location(
start: pipe.start.utf16Offset(in: pattern),
end: pipe.end.utf16Offset(in: pattern)
),
selection: Location(
start: pipe.start.utf16Offset(in: pattern),
end: pipe.end.utf16Offset(in: pattern)
),
related: Related(
location: Location(
start: alt.startPosition.utf16Offset(in: pattern),
end: alt.endPosition.utf16Offset(in: pattern)
)
),
tooltip: Tooltip(category: "quants", key: "alt")
)
)
}
emitNode(children.last!)
}
private mutating func emitGroup(_ group: AST.Group) {
let category: String
let key: String
var substitution = [String: String]()
switch group.kind.value {
case .capture:
groupCount += 1
category = "groups"
key = "group"
substitution = ["{{group.num}}": "\(groupCount)"]
case .namedCapture(let name):
groupCount += 1
category = "groups"
key = "namedgroup"
substitution = ["{{name}}": name.value]
case .balancedCapture(_):
groupCount += 1
category = "groups"
key = "balancedcapture"
case .nonCapture:
category = "groups"
key = "noncapgroup"
case .nonCaptureReset:
category = "groups"
key = "branchreset"
case .atomicNonCapturing:
category = "groups"
key = "atomic"
case .lookahead:
category = "lookaround"
key = "poslookahead"
case .negativeLookahead:
category = "lookaround"
key = "neglookahead"
case .nonAtomicLookahead:
category = "lookaround"
key = "nonatomicposlookahead"
case .lookbehind:
category = "lookaround"
key = "poslookbehind"
case .negativeLookbehind:
category = "lookaround"
key = "neglookbehind"
case .nonAtomicLookbehind:
category = "lookaround"
key = "nonatomicposlookbehind"
case .scriptRun:
category = "Script run. "
key = ""
case .atomicScriptRun:
category = "Atomic script run. "
key = ""
case .changeMatchingOptions(_):
category = "Change matching options"
key = ""
}
// Content
tokens.append(
Token(
classes: ["group-\(depth)"],
location: Location(
start: group.startPosition.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
)
)
)
// Open parenthesis
tokens.append(
Token(
classes: ["group", "group-\(depth)"],
location: Location(
start: group.kind.location.start.utf16Offset(in: pattern),
end: group.kind.location.end.utf16Offset(in: pattern)
),
selection: Location(
start: group.startPosition.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: category, key: key, substitution: substitution)
)
)
// Close parenthesis
tokens.append(
Token(
classes: ["group", "group-\(depth)"],
location: Location(
start: group.child.location.end.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: group.startPosition.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: category, key: key, substitution: substitution)
)
)
depth += 1
for node in group.children {
emitNode(node)
}
depth -= 1
}
private mutating func emitConditional(_ conditional: AST.Conditional) {
let category: String
let key: String
var substitution = [String: String]()
switch conditional.condition.kind {
case .groupMatched(let ref):
switch ref.kind {
case .absolute(let n):
category = "other"
key = "conditionalgroup"
substitution = ["{{name}}": "\(n)"]
case .relative(let n):
category = "other"
key = "conditionalgroup"
substitution = ["{{name}}": "\(n)"]
case .named(let name):
category = "other"
key = "conditionalgroup"
substitution = ["{{name}}": "\(name)"]
}
tokens.append(
Token(
classes: ["special"],
location: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.condition.location.end.utf16Offset(in: pattern) + 1
),
selection: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: category, key: key, substitution: substitution)
)
)
case .recursionCheck:
break
case .groupRecursionCheck(_):
tokens.append(
Token(
classes: ["special"],
location: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.condition.location.end.utf16Offset(in: pattern) + 1
),
selection: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "other", key: "recursion")
)
)
case .defineGroup:
break
case .pcreVersionCheck(_):
break
case .group(let group):
tokens.append(
Token(
classes: ["special"],
location: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.condition.location.start.utf16Offset(in: pattern)
),
selection: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "other", key: "conditional")
)
)
// Open parenthesis
tokens.append(
Token(
classes: ["special"],
location: Location(
start: group.kind.location.start.utf16Offset(in: pattern),
end: group.kind.location.end.utf16Offset(in: pattern)
),
selection: Location(
start: group.startPosition.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "misc", key: "condition")
)
)
// Close parenthesis
tokens.append(
Token(
classes: ["special"],
location: Location(
start: group.child.location.end.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: group.startPosition.utf16Offset(in: pattern),
end: group.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "misc", key: "condition")
)
)
for node in group.children {
emitNode(node)
}
}
emitNode(conditional.trueBranch)
if let pipe = conditional.pipe {
tokens.append(
Token(
classes: ["special"],
location: Location(
start: pipe.start.utf16Offset(in: pattern),
end: pipe.end.utf16Offset(in: pattern)
),
selection: Location(
start: pipe.start.utf16Offset(in: pattern),
end: pipe.end.utf16Offset(in: pattern)
),
related: Related(
location: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
)
),
tooltip: Tooltip(category: "misc", key: "conditionalelse")
)
)
}
emitNode(conditional.falseBranch)
tokens.append(
Token(
classes: ["special"],
location: Location(
start: conditional.falseBranch.location.end.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: conditional.startPosition.utf16Offset(in: pattern),
end: conditional.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "other", key: "conditional")
)
)
}
private mutating func emitQuantification(_ quant: AST.Quantification) {
emitNode(quant.child)
let substitution: [String: String]
switch quant.amount.value {
case .zeroOrMore: // *
substitution = ["{{getQuant()}}": "0 or more"]
case .oneOrMore: // +
substitution = ["{{getQuant()}}": "1 or more"]
case .zeroOrOne: // ?
substitution = ["{{getQuant()}}": "between 0 and 1"]
case .exactly(let n): // {n}
substitution = ["{{getQuant()}}": String(pattern[n.location.range])]
case .nOrMore(let n): // {n,}
substitution = ["{{getQuant()}}": "\(pattern[n.location.range]) or more"]
case .upToN(let n): // {,n}
substitution = ["{{getQuant()}}": "between 0 and \(pattern[n.location.range])"]
case .range(let n, let m): // {n,m}
substitution = ["{{getQuant()}}": "between \(pattern[n.location.range]) and \(pattern[m.location.range])"]
}
tokens.append(
Token(
classes: ["quant"],
location: Location(
start: quant.amount.location.start.utf16Offset(in: pattern),
end: quant.amount.location.end.utf16Offset(in: pattern)
),
selection: Location(
start: quant.amount.location.start.utf16Offset(in: pattern),
end: quant.amount.location.end.utf16Offset(in: pattern)
),
related: Related(
location: Location(
start: quant.startPosition.utf16Offset(in: pattern),
end: quant.endPosition.utf16Offset(in: pattern)
)
),
tooltip: Tooltip(category: "quants", key: "quant", substitution: substitution)
)
)
switch quant.kind.value {
case .eager:
break
case .reluctant:
tokens.append(
Token(
classes: ["lazy"],
location: Location(
start: quant.kind.location.start.utf16Offset(in: pattern),
end: quant.kind.location.end.utf16Offset(in: pattern)
),
selection: Location(
start: quant.kind.location.start.utf16Offset(in: pattern),
end: quant.kind.location.end.utf16Offset(in: pattern)
),
related: Related(
location: Location(
start: quant.amount.location.start.utf16Offset(in: pattern),
end: quant.amount.location.end.utf16Offset(in: pattern)
)
),
tooltip: Tooltip(category: "quants", key: "lazy")
)
)
case .possessive:
tokens.append(
Token(
classes: ["possessive"],
location: Location(
start: quant.kind.location.start.utf16Offset(in: pattern),
end: quant.kind.location.end.utf16Offset(in: pattern)
),
selection: Location(
start: quant.kind.location.start.utf16Offset(in: pattern),
end: quant.kind.location.end.utf16Offset(in: pattern)
),
related: Related(
location: Location(
start: quant.amount.location.start.utf16Offset(in: pattern),
end: quant.amount.location.end.utf16Offset(in: pattern)
)
),
tooltip: Tooltip(category: "quants", key: "possessive")
)
)
}
}
private mutating func emitQuote(_ quote: AST.Quote) {
tokens.append(
Token(
classes: ["esc"],
location: Location(
start: quote.startPosition.utf16Offset(in: pattern),
end: quote.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: quote.startPosition.utf16Offset(in: pattern),
end: quote.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "escchars", key: "escsequence", substitution: ["{{value}}": quote.literal])
)
)
}
private mutating func emitTrivia(_ trivia: AST.Trivia) {
tokens.append(
Token(
classes: ["comment"],
location: Location(
start: trivia.startPosition.utf16Offset(in: pattern),
end: trivia.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: trivia.startPosition.utf16Offset(in: pattern),
end: trivia.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "other", key: "comment")
)
)
}
private mutating func emitInterpolation(_ interpolation: AST.Interpolation) {
tokens.append(
Token(
classes: ["interpolation"],
location: Location(
start: interpolation.startPosition.utf16Offset(in: pattern),
end: interpolation.endPosition.utf16Offset(in: pattern)
),
selection: Location(
start: interpolation.startPosition.utf16Offset(in: pattern),
end: interpolation.endPosition.utf16Offset(in: pattern)
),
tooltip: Tooltip(category: "other", key: "interpolation")
)
)
}
private mutating func emitAtom(_ atom: AST.Atom) {
let `class`: String
let category: String
let key: String
var substitution = [String: String]()
switch atom.kind {
case .char(let c):
let charcode = c.unicodeScalars.map { String(format: "U+%X", $0.value) }.joined(separator: " ")
let value = String(pattern[atom.location.range])
if value.hasPrefix("\\") {
`class` = "esc"
category = "misc"
key = "escchar"
substitution = [
"{{getChar()}}": #""\#(c)""#,
"{{code}}": charcode,
"{{getInsensitive()}}": "Case \(modes.i ? "in" : "")sensitive",
]
} else {
`class` = "char"
category = "misc"
key = "char"
substitution = [
"{{getChar()}}": #""\#(c)""#,
"{{code}}": charcode,
"{{getInsensitive()}}": "Case \(modes.i ? "in" : "")sensitive",
]
}
case .scalar(let scalar):
`class` = "char"
category = "misc"
key = "char"
substitution = [
"{{getChar()}}": #""\#(String(scalar.value))""#,
"{{code}}": String(format: "U+%X", scalar.value.value),
"{{getInsensitive()}}": "Case \(modes.i ? "in" : "")sensitive",
]
case .scalarSequence(let scalarSequence):
let scalars = scalarSequence.scalars
let value = scalars.map { String($0.value) }.joined()
let charcode = scalars.map { String(format: "U+%X", $0.value.value) }.joined(separator: " ")
`class` = "char"
category = "misc"
key = "char"
substitution = [
"{{getChar()}}": #""\#(value)""#,
"{{code}}": charcode,
"{{getInsensitive()}}": "Case \(modes.i ? "in" : "")sensitive",
]
case .property(let prop):
`class` = "charclass"
switch prop.kind {
case .any:
category = "misc"
key = "any"
case .assigned:
category = "misc"
key = "assigned"
case .ascii:
category = "misc"
key = "ascii"
case .generalCategory(let cat):
let uniCat: String
switch cat {
case .other:
uniCat = "Other"
case .control:
uniCat = "Control"
case .format:
uniCat = "Format"
case .unassigned:
uniCat = "Unassigned"
case .privateUse:
uniCat = "Private use"
case .surrogate:
uniCat = "Surrogate"
case .letter:
uniCat = "Letter"
case .casedLetter:
uniCat = "Cased letter"
case .lowercaseLetter:
uniCat = "Lower case letter"
case .modifierLetter:
uniCat = "Modifier letter"
case .otherLetter:
uniCat = "Other letter"
case .titlecaseLetter:
uniCat = "Title case letter"
case .uppercaseLetter:
uniCat = "Upper case letter"
case .mark:
uniCat = "Mark"
case .spacingMark:
uniCat = "Spacing mark"
case .enclosingMark:
uniCat = "Enclosing mark"
case .nonspacingMark:
uniCat = "Non-spacing mark"
case .number:
uniCat = "Number"
case .decimalNumber:
uniCat = "Decimal number"
case .letterNumber:
uniCat = "Letter number"
case .otherNumber:
uniCat = "Other number"
case .punctuation:
uniCat = "Punctuation"
case .connectorPunctuation:
uniCat = "Connector punctuation"
case .dashPunctuation:
uniCat = "Dash punctuation"
case .closePunctuation:
uniCat = "Close punctuation"
case .finalPunctuation:
uniCat = "Final punctuation"
case .initialPunctuation:
uniCat = "Initial punctuation"
case .otherPunctuation:
uniCat = "Other punctuation"
case .openPunctuation:
uniCat = "Open punctuation"
case .symbol:
uniCat = "Symbol"
case .currencySymbol:
uniCat = "Currency symbol"
case .modifierSymbol:
uniCat = "Modifier symbol"
case .mathSymbol:
uniCat = "Mathematical symbol"
case .otherSymbol:
uniCat = "Other symbol"
case .separator:
uniCat = "Separator"
case .lineSeparator:
uniCat = "Line separator"
case .paragraphSeparator:
uniCat = "Paragraph separator"
case .spaceSeparator:
uniCat = "Space separator"
}
category = "charclasses"
key = "unicodecat"
substitution = ["{{getUniCat()}}": uniCat]
case .binary(let property, let value):
switch property {
case .asciiHexDigit:
break
case .alphabetic:
break
case .bidiControl:
break
case .bidiMirrored:
break
case .cased:
break
case .compositionExclusion:
break
case .caseIgnorable:
break
case .changesWhenCasefolded:
break
case .changesWhenCasemapped:
break
case .changesWhenNFKCCasefolded:
break
case .changesWhenLowercased:
break
case .changesWhenTitlecased:
break
case .changesWhenUppercased:
break
case .dash:
break
case .deprecated:
break
case .defaultIgnorableCodePoint:
break
case .diacratic:
break
case .emojiModifierBase:
break
case .emojiComponent:
break
case .emojiModifier:
break
case .emoji:
break
case .emojiPresentation:
break
case .extender:
break
case .extendedPictographic:
break
case .fullCompositionExclusion:
break
case .graphemeBase:
break
case .graphemeExtended:
break
case .graphemeLink:
break
case .hexDigit:
break
case .hyphen:
break
case .idContinue:
break
case .ideographic:
break
case .idStart:
break
case .idsBinaryOperator:
break
case .idsTrinaryOperator:
break
case .joinControl:
break
case .logicalOrderException:
break
case .lowercase:
break
case .math:
break
case .noncharacterCodePoint:
break
case .otherAlphabetic:
break
case .otherDefaultIgnorableCodePoint:
break
case .otherGraphemeExtended:
break
case .otherIDContinue:
break
case .otherIDStart:
break
case .otherLowercase:
break
case .otherMath:
break
case .otherUppercase:
break
case .patternSyntax:
break
case .patternWhitespace:
break
case .prependedConcatenationMark:
break
case .quotationMark:
break
case .radical:
break
case .regionalIndicator:
break
case .softDotted:
break
case .sentenceTerminal:
break
case .terminalPunctuation:
break
case .unifiedIdiograph:
break
case .uppercase:
break
case .variationSelector:
break
case .whitespace:
break
case .xidContinue:
break
case .xidStart:
break
case .expandsOnNFC:
break
case .expandsOnNFD:
break
case .expandsOnNFKC:
break
case .expandsOnNFKD:
break
}
category = "charclasses"
key = "binary"
case .script(_):
category = "charclasses"
key = "script"
case .scriptExte
gitextract_upkxoslv/ ├── .github/ │ ├── FUNDING.yml │ ├── renovate.json │ └── workflows/ │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── .swift-format ├── .swiftpm/ │ └── xcode/ │ ├── package.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ ├── App.xcscheme │ ├── DSLConverter.xcscheme │ ├── ExpressionParser.xcscheme │ ├── Matcher.xcscheme │ └── swiftregex-Package.xcscheme ├── .vscode/ │ ├── launch.json │ └── settings.json ├── DEPLOYMENT.md ├── Dockerfile ├── LICENSE ├── Package.resolved ├── Package.swift ├── Public/ │ ├── css/ │ │ ├── common.css │ │ └── highlight.css │ ├── error.html │ ├── favicons/ │ │ ├── browserconfig.xml │ │ └── site.webmanifest │ ├── index.html │ ├── index.js │ ├── js/ │ │ ├── app.js │ │ ├── docs/ │ │ │ └── reference.js │ │ ├── misc/ │ │ │ ├── icons.js │ │ │ └── utils.js │ │ ├── runner.js │ │ ├── state/ │ │ │ ├── decoder.js │ │ │ ├── encoder.js │ │ │ └── worker.js │ │ └── views/ │ │ ├── debugger_highlighter.js │ │ ├── debugger_text.js │ │ ├── dsl_editor.js │ │ ├── dsl_highlighter.js │ │ ├── dsl_view.js │ │ ├── editor.js │ │ ├── error_message.js │ │ ├── expression_field.js │ │ ├── expression_highlighter.js │ │ ├── match_options.js │ │ ├── test_editor.js │ │ └── test_highlighter.js │ ├── robots.txt │ └── scss/ │ └── default.scss ├── README.md ├── SECURITY.md ├── Sources/ │ ├── App/ │ │ ├── Debugger/ │ │ │ ├── Context.swift │ │ │ ├── Debugger.swift │ │ │ └── Executor.swift │ │ ├── Middlewares/ │ │ │ ├── CommonErrorMiddleware.swift │ │ │ └── CustomHeaderMiddleware.swift │ │ ├── Models/ │ │ │ ├── ExecRequest.swift │ │ │ └── ResultResponse.swift │ │ ├── configure.swift │ │ ├── entrypoint.swift │ │ └── routes.swift │ ├── DSLConverter/ │ │ ├── DSLConverter.swift │ │ └── Main.swift │ ├── ExpressionParser/ │ │ ├── ExpressionParser.swift │ │ └── Main.swift │ └── Matcher/ │ ├── Main.swift │ └── Matcher.swift ├── Tests/ │ └── RegexTests/ │ ├── ConverterTests.swift │ ├── ExpressionParserTests.swift │ └── MatcherTests.swift ├── package.json ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js
SYMBOL INDEX (103 symbols across 16 files)
FILE: Public/js/app.js
class App (line 12) | class App {
method constructor (line 13) | constructor() {
method init (line 17) | init() {
method startStateRestoration (line 121) | startStateRestoration() {
method encodeState (line 166) | encodeState() {
method decodeState (line 185) | decodeState() {
method updateMatchCount (line 196) | updateMatchCount(count, id) {
method launchDebugger (line 207) | launchDebugger() {
method onExpressionFieldChange (line 229) | onExpressionFieldChange() {
method run (line 244) | run() {
method onMatchOptionsChange (line 280) | onMatchOptionsChange() {
method onPatternTestEditorChange (line 284) | onPatternTestEditorChange() {
method onDebuggerStepChange (line 313) | onDebuggerStepChange() {
method onRunnerReady (line 341) | onRunnerReady() {
method onRunnerResponse (line 348) | onRunnerResponse(response) {
FILE: Public/js/docs/reference.js
class Reference (line 3) | class Reference {
method get (line 4) | static get(category, key) {
FILE: Public/js/runner.js
class Runner (line 5) | class Runner {
method constructor (line 6) | constructor() {
method isReady (line 14) | get isReady() {
method run (line 18) | run(request) {
method createConnection (line 23) | createConnection(endpoint) {
method endpoint (line 58) | endpoint() {
FILE: Public/js/state/decoder.js
class Decoder (line 5) | class Decoder {
method decode (line 6) | static decode(string) {
FILE: Public/js/state/encoder.js
class Encoder (line 5) | class Encoder {
method encode (line 6) | static encode(data) {
FILE: Public/js/views/debugger_highlighter.js
class DebuggerHighlighter (line 5) | class DebuggerHighlighter {
method constructor (line 6) | constructor(editor) {
method draw (line 12) | draw(traces, backtrack) {
method clear (line 78) | clear() {
FILE: Public/js/views/debugger_text.js
class DebuggerText (line 6) | class DebuggerText {
method constructor (line 7) | constructor(container) {
method value (line 12) | get value() {
method value (line 16) | set value(val) {
method init (line 20) | init(container) {
FILE: Public/js/views/dsl_editor.js
class DSLEditor (line 35) | class DSLEditor extends EventDispatcher {
method constructor (line 38) | constructor(container) {
method value (line 44) | get value() {
method value (line 48) | set value(val) {
method error (line 52) | set error(error) {
method init (line 76) | init(container) {
method setDefaultValue (line 98) | setDefaultValue() {
method deferUpdate (line 102) | deferUpdate() {
method update (line 106) | update() {
method onEditorChange (line 110) | onEditorChange(editor, event) {
FILE: Public/js/views/dsl_highlighter.js
class DSLHighlighter (line 6) | class DSLHighlighter extends EventDispatcher {
method constructor (line 7) | constructor(editor) {
method clear (line 13) | clear() {
method draw (line 23) | draw(tokens) {
FILE: Public/js/views/dsl_view.js
class DSLView (line 7) | class DSLView extends EventDispatcher {
method constructor (line 8) | constructor(container) {
method value (line 14) | get value() {
method value (line 18) | set value(val) {
method error (line 22) | set error(error) {
method init (line 58) | init(container) {
FILE: Public/js/views/editor.js
function createElement (line 80) | function createElement(type, className, content, parent) {
FILE: Public/js/views/expression_field.js
class ExpressionField (line 10) | class ExpressionField extends EventDispatcher {
method constructor (line 11) | constructor(container) {
method value (line 17) | get value() {
method value (line 21) | set value(val) {
method tokens (line 25) | set tokens(tokens) {
method error (line 31) | set error(error) {
method init (line 63) | init(container) {
method setDefaultValue (line 92) | setDefaultValue() {
method resetTooltips (line 97) | resetTooltips() {
method deferUpdate (line 115) | deferUpdate() {
method update (line 119) | update() {
method onEditorChange (line 123) | onEditorChange(editor, event) {
method onHover (line 127) | onHover(token, tippyInstance) {
FILE: Public/js/views/expression_highlighter.js
class ExpressionHighlighter (line 7) | class ExpressionHighlighter extends EventDispatcher {
method constructor (line 8) | constructor(editor) {
method draw (line 16) | draw(tokens) {
method drawError (line 64) | drawError(errors) {
method clear (line 102) | clear() {
method clearError (line 111) | clearError() {
method drawHover (line 120) | drawHover(token) {
method drawBorder (line 137) | drawBorder(range, className) {
method clearHover (line 166) | clearHover() {
function makeTooltip (line 176) | function makeTooltip(label, desc) {
FILE: Public/js/views/match_options.js
class MatchOptions (line 5) | class MatchOptions extends EventDispatcher {
method constructor (line 6) | constructor() {
method init (line 11) | init() {
method value (line 20) | get value() {
method value (line 30) | set value(options) {
method setDefaultValue (line 38) | setDefaultValue() {
FILE: Public/js/views/test_editor.js
class TestEditor (line 20) | class TestEditor extends EventDispatcher {
method constructor (line 23) | constructor(container) {
method value (line 29) | get value() {
method value (line 33) | set value(val) {
method error (line 37) | set error(error) {
method matches (line 61) | set matches(matches) {
method init (line 70) | init(container) {
method setDefaultValue (line 85) | setDefaultValue() {
method deferUpdate (line 89) | deferUpdate() {
method update (line 93) | update() {
method onEditorChange (line 97) | onEditorChange(editor, event) {
FILE: Public/js/views/test_highlighter.js
class TestHighlighter (line 7) | class TestHighlighter extends EventDispatcher {
method constructor (line 8) | constructor(editor) {
method draw (line 18) | draw(tokens) {
method addLeftAnchor (line 94) | addLeftAnchor(location, attributes = {}) {
method addRightAnchor (line 115) | addRightAnchor(location, attributes = {}) {
method clear (line 136) | clear() {
Condensed preview — 73 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (246K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 25,
"preview": "github: kishikawakatsumi\n"
},
{
"path": ".github/renovate.json",
"chars": 212,
"preview": "{\n \"extends\": [\n \"config:recommended\"\n ],\n \"packageRules\": [\n {\n \"matchUpdateTypes\": [\n \"minor\",\n "
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2481,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/test.yml",
"chars": 371,
"preview": "name: CI\non:\n pull_request:\n branches: [main]\n workflow_dispatch:\n\nenv:\n FONTAWESOME_TOKEN: ${{ secrets.FONTAWESOM"
},
{
"path": ".gitignore",
"chars": 5000,
"preview": "### Generated by gibo (https://github.com/simonwhitaker/gibo)\n### https://raw.github.com/github/gitignore/e5323759e387ba"
},
{
"path": ".swift-format",
"chars": 82,
"preview": "{\n \"version\": 1,\n \"lineLength\": 10000,\n \"indentation\": {\n \"spaces\": 2\n }\n}\n"
},
{
"path": ".swiftpm/xcode/package.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": ".swiftpm/xcode/xcshareddata/xcschemes/App.xcscheme",
"chars": 2731,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1620\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/DSLConverter.xcscheme",
"chars": 2812,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1620\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/ExpressionParser.xcscheme",
"chars": 2848,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1620\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/Matcher.xcscheme",
"chars": 2767,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1620\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/swiftregex-Package.xcscheme",
"chars": 4704,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1620\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": ".vscode/launch.json",
"chars": 4047,
"preview": "{\n \"configurations\": [\n {\n \"type\": \"lldb\",\n \"request\": \"launch\",\n \"name\": \"Debug DSLParser\",\n \"p"
},
{
"path": ".vscode/settings.json",
"chars": 144,
"preview": "{\n \"lldb.library\": \"/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB\",\n \"lldb.launch.e"
},
{
"path": "DEPLOYMENT.md",
"chars": 1021,
"preview": "# Deployment Instructions\n\n## Prerequisites\n\nBefore deploying, make sure you have the following software installed on yo"
},
{
"path": "Dockerfile",
"chars": 1919,
"preview": "FROM node:lts-slim as node\n\nWORKDIR /build\n\nARG FONTAWESOME_TOKEN\nCOPY package*.json ./\nRUN echo \"@fortawesome:registry="
},
{
"path": "LICENSE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2022 Kishikawa Katsumi\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "Package.resolved",
"chars": 9185,
"preview": "{\n \"pins\" : [\n {\n \"identity\" : \"async-http-client\",\n \"kind\" : \"remoteSourceControl\",\n \"location\" : \"h"
},
{
"path": "Package.swift",
"chars": 2638,
"preview": "// swift-tools-version:5.9\nimport PackageDescription\n\nlet package = Package(\n name: \"swiftregex\",\n platforms: [\n .m"
},
{
"path": "Public/css/common.css",
"chars": 3311,
"preview": ".active-tick .checkable::after {\n font-family: \"Font Awesome 6 Pro\";\n content: \"\\f00c\";\n color: #0d6efd;\n display: n"
},
{
"path": "Public/css/highlight.css",
"chars": 2321,
"preview": ".exp-related {\n border-bottom: solid 1px rgba(16, 17, 18, 0.3);\n border-top: solid 1px rgba(16, 17, 18, 0.3);\n margin"
},
{
"path": "Public/error.html",
"chars": 6482,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/Ht"
},
{
"path": "Public/favicons/browserconfig.xml",
"chars": 246,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square150x150logo"
},
{
"path": "Public/favicons/site.webmanifest",
"chars": 426,
"preview": "{\n \"name\": \"\",\n \"short_name\": \"\",\n \"icons\": [\n {\n \"src\": \"/android-chrome-192x192.png\",\n "
},
{
"path": "Public/index.html",
"chars": 13828,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "Public/index.js",
"chars": 268,
"preview": "\"use strict\";\n\nimport \"./scss/default.scss\";\nimport \"codemirror/lib/codemirror.css\";\nimport \"tippy.js/dist/tippy.css\";\ni"
},
{
"path": "Public/js/app.js",
"chars": 13499,
"preview": "\"use strict\";\n\nimport { Tooltip } from \"bootstrap\";\nimport { ExpressionField } from \"./views/expression_field\";\nimport {"
},
{
"path": "Public/js/docs/reference.js",
"chars": 23714,
"preview": "\"use strict\";\n\nexport class Reference {\n static get(category, key) {\n return references[category][key];\n }\n}\n\nconst"
},
{
"path": "Public/js/misc/icons.js",
"chars": 776,
"preview": "\"use strict\";\n\nimport { config, library, dom } from \"@fortawesome/fontawesome-svg-core\";\nimport {\n faFlag,\n faOctagonX"
},
{
"path": "Public/js/misc/utils.js",
"chars": 3180,
"preview": "\"use strict\";\n\nconst Utils = {};\nexport default Utils;\n\nUtils.copy = function (target, source) {\n for (let n in source)"
},
{
"path": "Public/js/runner.js",
"chars": 1561,
"preview": "\"use strict\";\n\nimport ReconnectingWebSocket from \"reconnecting-websocket\";\n\nexport class Runner {\n constructor() {\n "
},
{
"path": "Public/js/state/decoder.js",
"chars": 303,
"preview": "\"use strict\";\n\nimport { ungzip } from \"pako\";\n\nexport class Decoder {\n static decode(string) {\n const base64 = decod"
},
{
"path": "Public/js/state/encoder.js",
"chars": 264,
"preview": "\"use strict\";\n\nimport { gzip } from \"pako\";\n\nexport class Encoder {\n static encode(data) {\n const json = JSON.string"
},
{
"path": "Public/js/state/worker.js",
"chars": 1236,
"preview": "\"use strict\";\n\nimport { Decoder } from \"./decoder.js\";\nimport { Encoder } from \"./encoder.js\";\n\nonmessage = (e) => {\n i"
},
{
"path": "Public/js/views/debugger_highlighter.js",
"chars": 2423,
"preview": "\"use strict\";\n\nimport Editor from \"./editor\";\n\nexport default class DebuggerHighlighter {\n constructor(editor) {\n th"
},
{
"path": "Public/js/views/debugger_text.js",
"chars": 646,
"preview": "\"use strict\";\n\nimport Editor from \"./editor\";\nimport DebuggerHighlighter from \"./debugger_highlighter\";\n\nexport class De"
},
{
"path": "Public/js/views/dsl_editor.js",
"chars": 2053,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\n\nimport Editor from \"./editor\";\nimport ErrorMessage "
},
{
"path": "Public/js/views/dsl_highlighter.js",
"chars": 1045,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport Editor from \"./editor\";\n\nexport class DSLHigh"
},
{
"path": "Public/js/views/dsl_view.js",
"chars": 1606,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport Editor from \"./editor\";\nimport ErrorMessage f"
},
{
"path": "Public/js/views/editor.js",
"chars": 2362,
"preview": "\"use strict\";\n\nimport CodeMirror from \"codemirror\";\nimport \"codemirror/mode/swift/swift\";\n\nimport Utils from \"../misc/ut"
},
{
"path": "Public/js/views/error_message.js",
"chars": 868,
"preview": "\"use strict\";\n\nconst ErrorMessage = {};\nexport default ErrorMessage;\n\nErrorMessage.create = (message) => {\n const conta"
},
{
"path": "Public/js/views/expression_field.js",
"chars": 3834,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport tippy from \"tippy.js\";\n\nimport Editor from \"."
},
{
"path": "Public/js/views/expression_highlighter.js",
"chars": 5036,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport { Reference } from \"../docs/reference\";\nimpor"
},
{
"path": "Public/js/views/match_options.js",
"chars": 966,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\n\nexport class MatchOptions extends EventDispatcher {"
},
{
"path": "Public/js/views/test_editor.js",
"chars": 2335,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport tippy from \"tippy.js\";\n\nimport Editor from \"."
},
{
"path": "Public/js/views/test_highlighter.js",
"chars": 4618,
"preview": "\"use strict\";\n\nimport { EventDispatcher } from \"@createjs/easeljs\";\nimport Editor from \"./editor\";\nimport Utils from \".."
},
{
"path": "Public/robots.txt",
"chars": 0,
"preview": ""
},
{
"path": "Public/scss/default.scss",
"chars": 790,
"preview": "$table-cell-padding-y-sm: 0.05rem;\n\n@import \"bootstrap/scss/functions\";\n@import \"bootstrap/scss/variables\";\n@import \"boo"
},
{
"path": "README.md",
"chars": 1072,
"preview": "<p>\n<a href=\"https://github.com/kishikawakatsumi/swiftregex/actions\">\n<img src=\"https://github.com/kishikawakatsumi/swif"
},
{
"path": "SECURITY.md",
"chars": 161,
"preview": "# Security Policy\n\nFor security related problems, please don't use the public issue tracker, but email [@kishikawakatsum"
},
{
"path": "Sources/App/Debugger/Context.swift",
"chars": 703,
"preview": "import Foundation\n\nextension Debugger {\n class Context {\n var instructions: [String] = []\n var programCounter = 0"
},
{
"path": "Sources/App/Debugger/Debugger.swift",
"chars": 2366,
"preview": "import Foundation\n\n@testable import _RegexParser\n@testable @_spi(RegexBenchmark) import _StringProcessing\n\nstruct Debugg"
},
{
"path": "Sources/App/Debugger/Executor.swift",
"chars": 4325,
"preview": "@testable import _RegexParser\n@testable import _StringProcessing\n\nenum Executor<Output> {\n static func prefixMatch(\n "
},
{
"path": "Sources/App/Middlewares/CommonErrorMiddleware.swift",
"chars": 2038,
"preview": "import Vapor\n\nfinal class CommonErrorMiddleware: Middleware {\n func respond(to request: Request, chainingTo next: Respo"
},
{
"path": "Sources/App/Middlewares/CustomHeaderMiddleware.swift",
"chars": 400,
"preview": "import Vapor\n\nfinal class CustomHeaderMiddleware: Middleware {\n func respond(to request: Request, chainingTo next: Resp"
},
{
"path": "Sources/App/Models/ExecRequest.swift",
"chars": 279,
"preview": "import Foundation\n\nstruct ExecRequest: Codable {\n let method: RequestMethod\n let pattern: String\n let text: String\n "
},
{
"path": "Sources/App/Models/ResultResponse.swift",
"chars": 136,
"preview": "import Foundation\nimport Vapor\n\nstruct ResultResponse: Content {\n let method: RequestMethod\n let result: String\n let "
},
{
"path": "Sources/App/configure.swift",
"chars": 779,
"preview": "import Leaf\nimport Vapor\n\npublic func configure(_ app: Application) async throws {\n app.middleware = Middlewares()\n ap"
},
{
"path": "Sources/App/entrypoint.swift",
"chars": 428,
"preview": "import Vapor\n\n@main\nenum Entrypoint {\n static func main() async throws {\n var env = try Environment.detect()\n try"
},
{
"path": "Sources/App/routes.swift",
"chars": 7790,
"preview": "import Vapor\n\nfunc routes(_ app: Application) throws {\n app.get(\"health\") { _ in [\"status\": \"pass\"] }\n app.get(\"health"
},
{
"path": "Sources/DSLConverter/DSLConverter.swift",
"chars": 1563,
"preview": "import Foundation\n\n@testable import _RegexParser\n@testable @_spi(RegexBuilder) import _StringProcessing\n@testable @_spi("
},
{
"path": "Sources/DSLConverter/Main.swift",
"chars": 1787,
"preview": "import Foundation\n\n@main\nstruct Main {\n static func main() throws {\n do {\n let pattern = CommandLine.arguments["
},
{
"path": "Sources/ExpressionParser/ExpressionParser.swift",
"chars": 44148,
"preview": "import Foundation\n\n@testable import _RegexParser\n@testable @_spi(RegexBuilder) import _StringProcessing\n\nstruct Expressi"
},
{
"path": "Sources/ExpressionParser/Main.swift",
"chars": 1730,
"preview": "import Foundation\n\n@main\nstruct Main {\n static func main() {\n do {\n let pattern = CommandLine.arguments[1]\n "
},
{
"path": "Sources/Matcher/Main.swift",
"chars": 950,
"preview": "import Foundation\nimport _RegexParser\n\n@main\nstruct Main {\n static func main() throws {\n do {\n let pattern = Co"
},
{
"path": "Sources/Matcher/Matcher.swift",
"chars": 1897,
"preview": "import Foundation\n\n@testable @_spi(RegexBuilder) import _StringProcessing\n\nstruct Matcher {\n static func match(pattern:"
},
{
"path": "Tests/RegexTests/ConverterTests.swift",
"chars": 596,
"preview": "import Foundation\nimport XCTest\n\n@testable import DSLConverter\n\nclass ConverterTests: XCTestCase {\n func testConvertPat"
},
{
"path": "Tests/RegexTests/ExpressionParserTests.swift",
"chars": 5843,
"preview": "import Foundation\nimport XCTest\n\n@testable import ExpressionParser\n\nclass ParserTests: XCTestCase {\n func testParseExpr"
},
{
"path": "Tests/RegexTests/MatcherTests.swift",
"chars": 1761,
"preview": "import Foundation\nimport XCTest\n\n@testable import Matcher\n\nclass MatchTest: XCTestCase {\n func testMatch() throws {\n "
},
{
"path": "package.json",
"chars": 1209,
"preview": "{\n \"name\": \"swiftregex\",\n \"scripts\": {\n \"prod\": \"webpack --progress --config webpack.prod.js\",\n \"dev\": \"webpack "
},
{
"path": "webpack.common.js",
"chars": 1894,
"preview": "const path = require(\"path\");\nconst CopyWebbackPlugin = require(\"copy-webpack-plugin\");\nconst HtmlWebpackPlugin = requir"
},
{
"path": "webpack.dev.js",
"chars": 184,
"preview": "const { merge } = require(\"webpack-merge\");\nconst common = require(\"./webpack.common.js\");\n\nmodule.exports = merge(commo"
},
{
"path": "webpack.prod.js",
"chars": 361,
"preview": "const { merge } = require(\"webpack-merge\");\nconst common = require(\"./webpack.common.js\");\nconst BundleAnalyzerPlugin =\n"
}
]
About this extraction
This page contains the full source code of the SwiftFiddle/swiftregex GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 73 files (223.2 KB), approximately 58.0k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. 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.