Spaces:
Sleeping
Sleeping
Commit ·
d0808a4
1
Parent(s): c765308
Deploy Liftoscript MCP validator
Browse files- Minimal Lezer-based validator for Liftoscript
- Supports both planner and expression validation
- MCP server endpoint for Claude integration
- Includes JavaScript parsers and Gradio interface
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- README.md +51 -4
- app.py +301 -0
- liftoscript-parser.js +27 -0
- minimal-validator.js +161 -0
- package.json +16 -0
- packages.txt +3 -0
- planner-parser.js +27 -0
- requirements.txt +2 -0
README.md
CHANGED
|
@@ -1,12 +1,59 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Liftoscript MCP Validator
|
| 3 |
+
emoji: 💪
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.0.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Liftoscript MCP Validator
|
| 13 |
+
|
| 14 |
+
A Model Context Protocol (MCP) server that validates [Liftoscript](https://www.liftosaur.com/docs/docs/liftoscript) workout programs.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
- ✅ Validates both **Planner syntax** (Week/Day format) and **Liftoscript expressions**
|
| 19 |
+
- 🚀 Lightweight Lezer-based parser
|
| 20 |
+
- 🤖 MCP integration for Claude Desktop
|
| 21 |
+
- 📍 Detailed error messages with line/column information
|
| 22 |
+
|
| 23 |
+
## MCP Configuration
|
| 24 |
+
|
| 25 |
+
Add this to your Claude Desktop config:
|
| 26 |
+
|
| 27 |
+
```json
|
| 28 |
+
{
|
| 29 |
+
"mcpServers": {
|
| 30 |
+
"liftoscript": {
|
| 31 |
+
"command": "npx",
|
| 32 |
+
"args": ["mcp-remote", "https://davanstrien-liftosaur-mcp.hf.space/gradio_api/mcp/sse"]
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Example Usage
|
| 39 |
+
|
| 40 |
+
### Planner Format
|
| 41 |
+
```
|
| 42 |
+
# Week 1
|
| 43 |
+
## Day 1
|
| 44 |
+
Squat / 3x5 / progress: lp(5lb)
|
| 45 |
+
Bench Press / 3x8
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### Liftoscript Expression
|
| 49 |
+
```javascript
|
| 50 |
+
if (completedReps >= reps) {
|
| 51 |
+
state.weight += 5lb
|
| 52 |
+
}
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
## Resources
|
| 56 |
+
|
| 57 |
+
- [Liftoscript Documentation](https://www.liftosaur.com/docs/docs/liftoscript)
|
| 58 |
+
- [Liftosaur App](https://www.liftosaur.com/)
|
| 59 |
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
app.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import subprocess
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
from typing import Dict, Any
|
| 6 |
+
|
| 7 |
+
def setup_parser():
|
| 8 |
+
"""Setup the minimal parser - just needs npm install."""
|
| 9 |
+
try:
|
| 10 |
+
# Check if Node.js is available
|
| 11 |
+
result = subprocess.run(['node', '--version'], capture_output=True, text=True)
|
| 12 |
+
if result.returncode != 0:
|
| 13 |
+
return False, "Node.js not found. Please ensure Node.js is installed."
|
| 14 |
+
|
| 15 |
+
# Check if dependencies are installed
|
| 16 |
+
if not os.path.exists('node_modules/@lezer/lr'):
|
| 17 |
+
print("Installing dependencies...")
|
| 18 |
+
install_result = subprocess.run(
|
| 19 |
+
['npm', 'install'],
|
| 20 |
+
capture_output=True,
|
| 21 |
+
text=True,
|
| 22 |
+
timeout=60
|
| 23 |
+
)
|
| 24 |
+
if install_result.returncode != 0:
|
| 25 |
+
return False, f"Failed to install dependencies: {install_result.stderr}"
|
| 26 |
+
|
| 27 |
+
# Check if parser files exist
|
| 28 |
+
required_files = ['minimal-validator.js', 'liftoscript-parser.js', 'planner-parser.js']
|
| 29 |
+
missing_files = [f for f in required_files if not os.path.exists(f)]
|
| 30 |
+
|
| 31 |
+
if missing_files:
|
| 32 |
+
return False, f"Missing required files: {', '.join(missing_files)}"
|
| 33 |
+
|
| 34 |
+
# Test the validator
|
| 35 |
+
test_result = subprocess.run(
|
| 36 |
+
['node', 'minimal-validator.js', 'state.weight = 100lb', '--json'],
|
| 37 |
+
capture_output=True,
|
| 38 |
+
text=True,
|
| 39 |
+
timeout=5
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
if test_result.returncode != 0:
|
| 43 |
+
return False, "Validator test failed"
|
| 44 |
+
|
| 45 |
+
return True, "Parser ready (minimal Lezer-based validator)"
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
return False, f"Setup error: {str(e)}"
|
| 49 |
+
|
| 50 |
+
def validate_liftoscript(script: str) -> Dict[str, Any]:
|
| 51 |
+
"""
|
| 52 |
+
Validate a Liftoscript program using the minimal Lezer-based parser.
|
| 53 |
+
|
| 54 |
+
This tool validates Liftoscript workout programs and returns detailed error information
|
| 55 |
+
if the syntax is invalid. It supports both planner format (Week/Day/Exercise) and
|
| 56 |
+
pure Liftoscript expressions.
|
| 57 |
+
|
| 58 |
+
Args:
|
| 59 |
+
script: The Liftoscript code to validate (either planner format or expression)
|
| 60 |
+
|
| 61 |
+
Returns:
|
| 62 |
+
Validation result dictionary with keys:
|
| 63 |
+
- valid (bool): Whether the script is syntactically valid
|
| 64 |
+
- error (str|None): Error message if invalid
|
| 65 |
+
- line (int|None): Line number of error
|
| 66 |
+
- column (int|None): Column number of error
|
| 67 |
+
- type (str|None): Detected script type ('planner' or 'liftoscript')
|
| 68 |
+
"""
|
| 69 |
+
if not script or not script.strip():
|
| 70 |
+
return {
|
| 71 |
+
"valid": False,
|
| 72 |
+
"error": "Empty script provided",
|
| 73 |
+
"line": None,
|
| 74 |
+
"column": None,
|
| 75 |
+
"type": None
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
try:
|
| 79 |
+
# Use the minimal parser wrapper
|
| 80 |
+
wrapper = 'parser-wrapper-minimal.js' if os.path.exists('parser-wrapper-minimal.js') else 'minimal-validator.js'
|
| 81 |
+
|
| 82 |
+
# Call the validator
|
| 83 |
+
result = subprocess.run(
|
| 84 |
+
['node', wrapper, '-'] if wrapper == 'parser-wrapper-minimal.js' else ['node', wrapper, '-', '--json'],
|
| 85 |
+
input=script,
|
| 86 |
+
capture_output=True,
|
| 87 |
+
text=True,
|
| 88 |
+
timeout=10
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
if result.returncode != 0 and not result.stdout:
|
| 92 |
+
return {
|
| 93 |
+
"valid": False,
|
| 94 |
+
"error": f"Validator error: {result.stderr}",
|
| 95 |
+
"line": None,
|
| 96 |
+
"column": None,
|
| 97 |
+
"type": None
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
# Parse JSON response
|
| 101 |
+
try:
|
| 102 |
+
validation_result = json.loads(result.stdout)
|
| 103 |
+
|
| 104 |
+
# Handle both formats (wrapper and direct)
|
| 105 |
+
if 'errors' in validation_result:
|
| 106 |
+
# Wrapper format
|
| 107 |
+
if validation_result.get('errors') and len(validation_result['errors']) > 0:
|
| 108 |
+
first_error = validation_result['errors'][0]
|
| 109 |
+
return {
|
| 110 |
+
"valid": False,
|
| 111 |
+
"error": first_error.get('message', 'Unknown error'),
|
| 112 |
+
"line": first_error.get('line'),
|
| 113 |
+
"column": first_error.get('column'),
|
| 114 |
+
"type": validation_result.get('type')
|
| 115 |
+
}
|
| 116 |
+
else:
|
| 117 |
+
return {
|
| 118 |
+
"valid": True,
|
| 119 |
+
"error": None,
|
| 120 |
+
"line": None,
|
| 121 |
+
"column": None,
|
| 122 |
+
"type": validation_result.get('type', 'unknown')
|
| 123 |
+
}
|
| 124 |
+
else:
|
| 125 |
+
# Direct format
|
| 126 |
+
if validation_result.get('valid'):
|
| 127 |
+
return {
|
| 128 |
+
"valid": True,
|
| 129 |
+
"error": None,
|
| 130 |
+
"line": None,
|
| 131 |
+
"column": None,
|
| 132 |
+
"type": validation_result.get('type', 'unknown')
|
| 133 |
+
}
|
| 134 |
+
else:
|
| 135 |
+
error = validation_result.get('error', {})
|
| 136 |
+
return {
|
| 137 |
+
"valid": False,
|
| 138 |
+
"error": error.get('message', 'Unknown error'),
|
| 139 |
+
"line": error.get('line'),
|
| 140 |
+
"column": error.get('column'),
|
| 141 |
+
"type": error.get('type')
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
except json.JSONDecodeError:
|
| 145 |
+
return {
|
| 146 |
+
"valid": False,
|
| 147 |
+
"error": "Invalid parser response",
|
| 148 |
+
"line": None,
|
| 149 |
+
"column": None,
|
| 150 |
+
"type": None
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
except subprocess.TimeoutExpired:
|
| 154 |
+
return {
|
| 155 |
+
"valid": False,
|
| 156 |
+
"error": "Parser timeout - script too complex",
|
| 157 |
+
"line": None,
|
| 158 |
+
"column": None,
|
| 159 |
+
"type": None
|
| 160 |
+
}
|
| 161 |
+
except Exception as e:
|
| 162 |
+
return {
|
| 163 |
+
"valid": False,
|
| 164 |
+
"error": f"Validation error: {str(e)}",
|
| 165 |
+
"line": None,
|
| 166 |
+
"column": None,
|
| 167 |
+
"type": None
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
# Create Gradio interface
|
| 171 |
+
with gr.Blocks(title="Liftoscript Validator MCP Server") as app:
|
| 172 |
+
gr.Markdown("""
|
| 173 |
+
# 💪 Liftoscript Validator MCP Server
|
| 174 |
+
|
| 175 |
+
This tool validates [Liftoscript](https://www.liftosaur.com/docs/docs/liftoscript) workout programs using a **minimal Lezer-based parser** extracted from the Liftosaur repository.
|
| 176 |
+
|
| 177 |
+
## 🚀 Lightweight Implementation
|
| 178 |
+
|
| 179 |
+
This proof-of-concept uses only:
|
| 180 |
+
- The compiled Lezer parsers (no grammar compilation needed)
|
| 181 |
+
- Basic syntax validation (no semantic analysis)
|
| 182 |
+
- Zero dependencies on the full Liftosaur codebase
|
| 183 |
+
|
| 184 |
+
## ✅ Features:
|
| 185 |
+
- Validates both **Planner syntax** (Week/Day format) and **Liftoscript expressions**
|
| 186 |
+
- Automatic detection of script type
|
| 187 |
+
- Detailed error messages with line/column information
|
| 188 |
+
- Lightweight and fast (no heavy builds required)
|
| 189 |
+
|
| 190 |
+
## 📦 MCP Server Configuration
|
| 191 |
+
|
| 192 |
+
Add to your `claude_desktop_config.json`:
|
| 193 |
+
```json
|
| 194 |
+
{
|
| 195 |
+
"mcpServers": {
|
| 196 |
+
"liftoscript": {
|
| 197 |
+
"command": "npx",
|
| 198 |
+
"args": ["mcp-remote", "https://davanstrien-liftosaur-mcp.hf.space/gradio_api/mcp/sse"]
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
```
|
| 203 |
+
""")
|
| 204 |
+
|
| 205 |
+
# Check parser setup
|
| 206 |
+
parser_ready, setup_message = setup_parser()
|
| 207 |
+
|
| 208 |
+
with gr.Row():
|
| 209 |
+
with gr.Column():
|
| 210 |
+
if parser_ready:
|
| 211 |
+
gr.Markdown(f"**✅ Parser Status**: {setup_message}")
|
| 212 |
+
else:
|
| 213 |
+
gr.Markdown(f"**⚠️ Parser Status**: {setup_message}")
|
| 214 |
+
|
| 215 |
+
with gr.Row():
|
| 216 |
+
with gr.Column():
|
| 217 |
+
script_input = gr.Textbox(
|
| 218 |
+
label="Liftoscript Code",
|
| 219 |
+
placeholder="""Enter either Planner format:
|
| 220 |
+
|
| 221 |
+
# Week 1
|
| 222 |
+
## Day 1
|
| 223 |
+
Squat / 3x5 / progress: lp(5lb)
|
| 224 |
+
Bench Press / 3x8 @8
|
| 225 |
+
|
| 226 |
+
Or pure Liftoscript expressions:
|
| 227 |
+
|
| 228 |
+
if (completedReps >= reps) {
|
| 229 |
+
state.weight = state.weight + 5lb
|
| 230 |
+
}""",
|
| 231 |
+
lines=15
|
| 232 |
+
)
|
| 233 |
+
validate_btn = gr.Button("Validate", variant="primary", interactive=parser_ready)
|
| 234 |
+
|
| 235 |
+
with gr.Column():
|
| 236 |
+
validation_output = gr.JSON(label="Validation Result")
|
| 237 |
+
|
| 238 |
+
gr.Markdown("""
|
| 239 |
+
### 📝 Example Scripts
|
| 240 |
+
|
| 241 |
+
Click any example below to test:
|
| 242 |
+
""")
|
| 243 |
+
|
| 244 |
+
gr.Examples(
|
| 245 |
+
examples=[
|
| 246 |
+
# Planner examples
|
| 247 |
+
["# Week 1\n## Day 1\nSquat / 3x5 / progress: lp(5lb)\nBench Press / 3x8"],
|
| 248 |
+
["Deadlift / 1x5 / warmup: 1x5 135lb, 1x3 225lb, 1x1 315lb"],
|
| 249 |
+
["Bench Press / 3x8-12 @8 / progress: dp"],
|
| 250 |
+
|
| 251 |
+
# Liftoscript examples
|
| 252 |
+
["state.weight = 100lb"],
|
| 253 |
+
["if (completedReps >= reps) { state.weight += 5lb }"],
|
| 254 |
+
["for (r in completedReps) {\n if (r < reps[index]) {\n state.weight -= 5%\n }\n}"],
|
| 255 |
+
|
| 256 |
+
# Invalid examples (for testing)
|
| 257 |
+
["// Missing closing brace\nif (true) { state.weight = 100lb"],
|
| 258 |
+
["// Invalid format\nSquat 3x5"],
|
| 259 |
+
],
|
| 260 |
+
inputs=script_input,
|
| 261 |
+
label="Example Scripts"
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
validate_btn.click(
|
| 265 |
+
fn=validate_liftoscript,
|
| 266 |
+
inputs=script_input,
|
| 267 |
+
outputs=validation_output
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
gr.Markdown("""
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
## 🎯 Value Proposition
|
| 274 |
+
|
| 275 |
+
This minimal validator demonstrates that:
|
| 276 |
+
1. **Syntax validation** can be separated from full program evaluation
|
| 277 |
+
2. The Lezer parsers can be used **independently**
|
| 278 |
+
3. A standalone parser package would enable many tools and integrations
|
| 279 |
+
|
| 280 |
+
## 🤝 For Liftosaur Maintainers
|
| 281 |
+
|
| 282 |
+
This PoC shows demand for a standalone parser package. Benefits:
|
| 283 |
+
- Enable third-party tools (editors, linters, formatters)
|
| 284 |
+
- Support AI assistants like Claude via MCP
|
| 285 |
+
- Grow the Liftoscript ecosystem
|
| 286 |
+
- Minimal maintenance burden (just the parser, not the full runtime)
|
| 287 |
+
|
| 288 |
+
## 📚 Resources
|
| 289 |
+
- [Liftoscript Documentation](https://www.liftosaur.com/docs/docs/liftoscript)
|
| 290 |
+
- [Liftosaur App](https://www.liftosaur.com/)
|
| 291 |
+
- [GitHub Repository](https://github.com/astashov/liftosaur)
|
| 292 |
+
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
| 293 |
+
""")
|
| 294 |
+
|
| 295 |
+
# Launch with MCP server enabled
|
| 296 |
+
if __name__ == "__main__":
|
| 297 |
+
app.launch(
|
| 298 |
+
mcp_server=True,
|
| 299 |
+
server_name="0.0.0.0",
|
| 300 |
+
share=False
|
| 301 |
+
)
|
liftoscript-parser.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Liftoscript parser - compiled from Lezer grammar
|
| 2 |
+
// This is a standalone version extracted from Liftosaur
|
| 3 |
+
const { LRParser } = require("@lezer/lr");
|
| 4 |
+
|
| 5 |
+
const spec_Keyword = { __proto__: null, if: 100, else: 102, for: 104 };
|
| 6 |
+
const parser = LRParser.deserialize({
|
| 7 |
+
version: 14,
|
| 8 |
+
states:
|
| 9 |
+
",fQcQPOOO!iQPO'#C}OOQO'#Cd'#CdO#cQPO'#CdO${QPO'#DXOcQPO'#CiO%SQPO'#CjO%ZQPO'#ClO%`QPO'#CnO%eQQO'#CrO%mQPO'#CuO'ZQPO'#DXOcQPO'#C{OOQO'#DX'#DXQcQPOOOcQPO,58yOcQPO,58yOcQPO,58yOcQPO,58yOcQPO,59VOOQO,59O,59OOOQO,59Q,59QO'eQPO,59TOOQO,59U,59UO'lQPO,59UO'sQPO,59WO'xQPO,59YO'}QPO,59^OcQPO,59^O(SQSO,59aO(ZQPO,59fOcQPO,59]OcQPO,59dOOQO,59g,59gOOQO-E6{-E6{O)uQPO1G.eOOQO1G.e1G.eO)|QPO1G.eO*TQPO1G.eO+hQPO1G.qOOQO1G.o1G.oOOQO1G.p1G.pO+oQPO1G.rO-VQQO1G.tOOQO1G.x1G.xO-[QPO'#CtO-cQPO1G.xO-hQPO'#CvOOQO'#Cw'#CwOOQO'#Cv'#CvO-rQPO1G.{O-zQPO1G/QOcQPO'#DQO.UQPO1G/QOOQO1G/Q1G/QO.^QPO1G.wO/dQPO1G/OOcQPO7+$]O0jQPO7+$^O0rQPO7+$^OcQPO7+$`O2YQQO7+$dO(SQSO'#DPO2_QPO7+$gOOQO7+$g7+$gO2gQPO7+$lOOQO7+$l7+$lO2oQPO,59lOOQO-E7O-E7OO2yQPO<<GwO%ZQPO,59jOOQO<<Gx<<GxO0jQPO<<GxOOQO-E6|-E6|O4PQPO'#CpO4WQPO<<GzO4]QPO<<HOOOQO,59k,59kOOQO-E6}-E6}OOQO<<HR<<HROOQO<<HW<<HWO'sQPO1G/UOOQOAN=dAN=dP0mQPO'#DOO'sQPOAN=fOOQOAN=jAN=jOOQO7+$p7+$pOOQOG23QG23Q",
|
| 10 |
+
stateData:
|
| 11 |
+
"4m~OwOSPOSxOSyOSzOS~OSROXQO[]OaYOcZOgXOp[O|TO!OUO!SVO!UWO~OS_OT`OUaOVbO!QcO~OXqX[qXaqXcqXgqXpqXuqX|qX!OqX!SqX!UqX!PqX~P!WOXdO~OS{XT{XU{XV{XX{X[{Xa{Xc{Xg{Xp{Xu{X|{X!O{X!Q{X!S{X!U{X}{X!P{X!R{X!X{X!]{X~OZeO~P#hO!PgO~PcO|TO~O|jO~O!WlO!YkO~O|nO!WmOSiXTiXUiXViXXiX[iXaiXciXgiXmiXpiXuiX!OiX!QiX!SiX!UiX![iX}iX!PiX!RiX!XiX!]iX~OmpO![oO~P#hO}xO~P!WO!PyO~PcO!OUO~Oc{O~Oa|O~O!Z!QO~PcO}!WO!]!UO~PcOT`OURiVRiXRi[RiaRicRigRipRiuRi|Ri!ORi!QRi!SRi!URi}Ri!PRi!RRi!XRi!]Ri~OSRi~P(eOS_O~P(eOS_OT`OUaOVRiXRi[RiaRicRigRipRiuRi|Ri!ORi!QRi!SRi!URi}Ri!PRi!RRi!XRi!]Ri~O!R!ZO~P!WO!T![OS`iT`iU`iV`iX`i[`ia`ic`ig`ip`iu`i|`i!O`i!Q`i!S`i!U`i}`i!P`i!R`i!X`i!]`i~O!V!^O~O!XhX~P!WO!X!_O~O!RjX!XjX~P!WO!R!`O!X!bO~O}!dO!]!UO~P!WO}!dO!]!UO~OXei[eiaeiceigeipeiuei|ei!Oei!Sei!Uei}ei!Pei!Rei!Xei!]ei~P!WOXli[lialicliglipliuli|li!Oli!Sli!Uli}li!Pli!Rli!Xli!]li~P!WO!OUO!S!hO~O!T!jOS`qT`qU`qV`qX`q[`qa`qc`qg`qp`qu`q|`q!O`q!Q`q!S`q!U`q}`q!P`q!R`q!X`q!]`q~O!Y!nO~O!R!`O!X!qO~O}!rO!]!UO~O}ta!]ta~P!WOX_y[_ya_yc_yg_yp_yu_y|_y!O_y!S_y!U_y}_y!P_y!R_y!X_y!]_y~P!WO}dX~P!WO}!vO~Oa!wO~OPTg[XScZaZ~",
|
| 12 |
+
goto:
|
| 13 |
+
"'O|PPP}PPPP!eP}PP!{#i}}P}P$`}$cP$y$c$|%S}P}}P%W%b%h%nPPPPPP%xy]OTU[^_`abchlmnop!U!Z!^!`ySOTU[^_`abchlmnop!U!Z!^!`x]OTU[^_`abchlmnop!U!Z!^!`QiVR!s!hx]OTU[^_`abchlmnop!U!Z!^!`QziQ!i![Q!t!jQ!x!sR!y!vR!m!^yZOTU[^_`abchlmnop!U!Z!^!`R!OlQ!SmR!o!`T!Rm!`Q^OQhUTr^hQ!]zR!k!]Q!a!SR!p!aQ!VnQ!c!TT!f!V!cWPOU^hQfTQq[Qs_Qt`QuaQvbQwcQ}lS!Pm!`Q!TnQ!XoQ!YpQ!e!UQ!g!ZR!l!^",
|
| 14 |
+
nodeNames:
|
| 15 |
+
"⚠ LineComment Program BinaryExpression Plus Times Cmp AndOr NumberExpression Number WeightExpression Unit Percentage ParenthesisExpression BlockExpression Ternary IfExpression Keyword ForExpression Variable ForInExpression AssignmentExpression StateVariable StateKeyword StateVariableIndex VariableExpression VariableIndex Wildcard IncAssignmentExpression IncAssignment BuiltinFunctionExpression UnaryExpression Not",
|
| 16 |
+
maxTerm: 59,
|
| 17 |
+
skippedNodes: [0, 1],
|
| 18 |
+
repeatNodeCount: 4,
|
| 19 |
+
tokenData:
|
| 20 |
+
"1S~R}X^$Opq$Oqr$suv%Qvw%Vxy%byz%gz{%l{|%{|}&T}!O%{!O!P&Y!P!Q&r!QP!c!})U!}#O)g#P#Q)l#T#])U#]#^)q#^#_)U#_#`*m#`#a+i#a#g)U#g#h,Q#h#j)U#j#k.b#k#o)U#o#p0_#p#q0l#q#r0r#r#s0w#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$TYw~X^$Opq$O#y#z$O$f$g$O#BY#BZ$O$IS$I_$O$I|$JO$O$JT$JU$O$KV$KW$O&FU&FV$O~$xPp~!_!`${~%QOU~P%VOTP~%YPvw%]~%bOV~~%gO|~~%lO}~T%sP!ZSTP!_!`%vP%{OmP~&QPS~!_!`%v~&YO!]~V&_P!YQ!Q![&bT&gQXTuv&m!Q![&bT&rO[T~&wQTP!P!Q&}!_!`%v~'SSP~OY&}Z;'S&};'S;=`'`<%lO&}~'cP;=`<%l&}T'kRXTuv&m!O!P't!Q!['fT'yQXTuv&m!Q![(PT(URXTuv&m!O!P(_!Q![(PT(bP!Q![(P~(jO!R~~(oOx~~(tPU~!_!`${~(|P![~!_!`${~)UO!Q~T)ZSaT!Q![)U!c!})U#R#S)U#T#o)U~)lO!W~~)qO!X~V)vUaT!Q![)U!c!})U#R#S)U#T#b)U#b#c*Y#c#o)UV*aS!VQaT!Q![)U!c!})U#R#S)U#T#o)U~*rUaT!Q![)U!c!})U#R#S)U#T#Z)U#Z#[+U#[#o)U~+]SZ~aT!Q![)U!c!})U#R#S)U#T#o)U~+nUaT!Q![)U!c!})U#R#S)U#T#U)U#U#V+U#V#o)U~,VUaT!Q![)U!c!})U#R#S)U#T#h)U#h#i,i#i#o)U~,nTaT!Q![)U!c!})U#R#S)U#T#U,}#U#o)U~-SUaT!Q![)U!c!})U#R#S)U#T#h)U#h#i-f#i#o)U~-kUaT!Q![)U!c!})U#R#S)U#T#X)U#X#Y-}#Y#o)U~.USg~aT!Q![)U!c!})U#R#S)U#T#o)U~.gTaT!Q![)U!c!})U#R#S)U#T#U.v#U#o)U~.{UaT!Q![)U!c!})U#R#S)U#T#f)U#f#g/_#g#o)U~/dTaT!O!P/s!Q![)U!c!})U#R#S)U#T#o)U~/vQ!c!}/|#T#o/|~0RSc~!Q![/|!c!}/|#R#S/|#T#o/|~0dP!O~#r#s0g~0lOy~~0oP#p#q%]~0wO!P~~0zP#q#r0}~1SOz~",
|
| 21 |
+
tokenizers: [0, 1, 2],
|
| 22 |
+
topRules: { Program: [0, 2] },
|
| 23 |
+
specialized: [{ term: 17, get: (value) => spec_Keyword[value] || -1 }],
|
| 24 |
+
tokenPrec: 892,
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
module.exports = { parser };
|
minimal-validator.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
// Minimal Liftoscript validator using Lezer parsers
|
| 4 |
+
// No dependencies on the full Liftosaur codebase
|
| 5 |
+
|
| 6 |
+
const { parser: liftoscriptParser } = require('./liftoscript-parser.js');
|
| 7 |
+
const { parser: plannerParser } = require('./planner-parser.js');
|
| 8 |
+
|
| 9 |
+
function getLineAndColumn(text, position) {
|
| 10 |
+
const lines = text.split('\n');
|
| 11 |
+
let currentPos = 0;
|
| 12 |
+
|
| 13 |
+
for (let i = 0; i < lines.length; i++) {
|
| 14 |
+
const lineLength = lines[i].length + 1; // +1 for newline
|
| 15 |
+
if (position < currentPos + lineLength) {
|
| 16 |
+
return {
|
| 17 |
+
line: i + 1,
|
| 18 |
+
column: position - currentPos + 1
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
currentPos += lineLength;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
return { line: lines.length, column: 1 };
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function detectScriptType(script) {
|
| 28 |
+
// Detect if this is planner syntax or pure Liftoscript
|
| 29 |
+
const plannerIndicators = [
|
| 30 |
+
/^#\s+Week/m,
|
| 31 |
+
/^##\s+Day/m,
|
| 32 |
+
/^\s*\w+\s*\/\s*\d+x\d+/m, // Exercise format like "Squat / 3x5"
|
| 33 |
+
/\/\s*progress:/,
|
| 34 |
+
/\/\s*warmup:/,
|
| 35 |
+
/\/\s*update:/
|
| 36 |
+
];
|
| 37 |
+
|
| 38 |
+
return plannerIndicators.some(regex => regex.test(script)) ? 'planner' : 'liftoscript';
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function validateLiftoscript(script) {
|
| 42 |
+
try {
|
| 43 |
+
const scriptType = detectScriptType(script);
|
| 44 |
+
let tree;
|
| 45 |
+
|
| 46 |
+
if (scriptType === 'planner') {
|
| 47 |
+
// Parse with planner parser
|
| 48 |
+
tree = plannerParser.parse(script);
|
| 49 |
+
} else {
|
| 50 |
+
// Parse as pure Liftoscript
|
| 51 |
+
tree = liftoscriptParser.parse(script);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
// Check for error nodes
|
| 55 |
+
let hasError = false;
|
| 56 |
+
let errorNode = null;
|
| 57 |
+
let errorType = null;
|
| 58 |
+
|
| 59 |
+
tree.iterate({
|
| 60 |
+
enter: (node) => {
|
| 61 |
+
if (node.type.isError) {
|
| 62 |
+
hasError = true;
|
| 63 |
+
errorNode = node;
|
| 64 |
+
errorType = node.type.name;
|
| 65 |
+
return false; // Stop iteration
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
if (hasError && errorNode) {
|
| 71 |
+
const { line, column } = getLineAndColumn(script, errorNode.from);
|
| 72 |
+
|
| 73 |
+
// Try to provide more helpful error messages
|
| 74 |
+
let message = `Syntax error`;
|
| 75 |
+
const problemText = script.substring(errorNode.from, Math.min(errorNode.to, errorNode.from + 20));
|
| 76 |
+
|
| 77 |
+
if (scriptType === 'planner') {
|
| 78 |
+
if (problemText.includes('/')) {
|
| 79 |
+
message = "Invalid exercise format. Expected: 'Exercise / Sets x Reps'";
|
| 80 |
+
} else if (problemText.includes(':')) {
|
| 81 |
+
message = "Invalid property format. Expected: 'property: value' or 'property: function(args)'";
|
| 82 |
+
}
|
| 83 |
+
} else {
|
| 84 |
+
if (problemText.includes('=')) {
|
| 85 |
+
message = "Invalid assignment. Check variable names and syntax";
|
| 86 |
+
} else if (problemText.includes('{') || problemText.includes('}')) {
|
| 87 |
+
message = "Unmatched braces";
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
return {
|
| 92 |
+
valid: false,
|
| 93 |
+
error: {
|
| 94 |
+
message: `${message} at "${problemText.trim()}"`,
|
| 95 |
+
line: line,
|
| 96 |
+
column: column,
|
| 97 |
+
type: scriptType
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// No syntax errors found
|
| 103 |
+
return {
|
| 104 |
+
valid: true,
|
| 105 |
+
error: null,
|
| 106 |
+
type: scriptType
|
| 107 |
+
};
|
| 108 |
+
} catch (e) {
|
| 109 |
+
return {
|
| 110 |
+
valid: false,
|
| 111 |
+
error: {
|
| 112 |
+
message: `Parser error: ${e.message}`,
|
| 113 |
+
line: 0,
|
| 114 |
+
column: 0
|
| 115 |
+
}
|
| 116 |
+
};
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Export for use
|
| 121 |
+
module.exports = { validateLiftoscript };
|
| 122 |
+
|
| 123 |
+
// CLI interface
|
| 124 |
+
if (require.main === module) {
|
| 125 |
+
const fs = require('fs');
|
| 126 |
+
const args = process.argv.slice(2);
|
| 127 |
+
|
| 128 |
+
if (args.length === 0) {
|
| 129 |
+
console.log('Liftoscript Validator');
|
| 130 |
+
console.log('Usage: node minimal-validator.js <script or -> [--json]');
|
| 131 |
+
console.log('\nExamples:');
|
| 132 |
+
console.log(' node minimal-validator.js "state.weight = 100lb"');
|
| 133 |
+
console.log(' node minimal-validator.js - < program.liftoscript');
|
| 134 |
+
console.log(' echo "Squat / 3x5" | node minimal-validator.js - --json');
|
| 135 |
+
process.exit(0);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
let script;
|
| 139 |
+
const jsonOutput = args.includes('--json');
|
| 140 |
+
|
| 141 |
+
if (args[0] === '-') {
|
| 142 |
+
// Read from stdin
|
| 143 |
+
script = fs.readFileSync(0, 'utf-8');
|
| 144 |
+
} else {
|
| 145 |
+
script = args[0];
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
const result = validateLiftoscript(script);
|
| 149 |
+
|
| 150 |
+
if (jsonOutput) {
|
| 151 |
+
console.log(JSON.stringify(result, null, 2));
|
| 152 |
+
} else {
|
| 153 |
+
if (result.valid) {
|
| 154 |
+
console.log(`✅ Valid ${result.type} syntax`);
|
| 155 |
+
} else {
|
| 156 |
+
console.error(`❌ Invalid syntax (${result.error.type || 'unknown'} mode)`);
|
| 157 |
+
console.error(` Line ${result.error.line}, Column ${result.error.column}: ${result.error.message}`);
|
| 158 |
+
process.exit(1);
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "liftoscript-mcp-validator",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "Minimal Liftoscript validator for MCP server",
|
| 5 |
+
"main": "minimal-validator.js",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"test": "node test-validator.js"
|
| 8 |
+
},
|
| 9 |
+
"dependencies": {
|
| 10 |
+
"@lezer/lr": "^1.3.14"
|
| 11 |
+
},
|
| 12 |
+
"devDependencies": {},
|
| 13 |
+
"keywords": ["liftoscript", "validator", "mcp"],
|
| 14 |
+
"author": "",
|
| 15 |
+
"license": "MIT"
|
| 16 |
+
}
|
packages.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
nodejs
|
| 2 |
+
npm
|
| 3 |
+
git
|
planner-parser.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Planner parser - compiled from Lezer grammar
|
| 2 |
+
// This parses the Week/Day/Exercise format
|
| 3 |
+
const { LRParser } = require("@lezer/lr");
|
| 4 |
+
|
| 5 |
+
const spec_Keyword = { __proto__: null, none: 148 };
|
| 6 |
+
const parser = LRParser.deserialize({
|
| 7 |
+
version: 14,
|
| 8 |
+
states:
|
| 9 |
+
"2SQVQPOOOOQO'#D`'#D`OkQQO'#CcO|QQO'#CbOOQO'#D^'#D^OOQO'#Dk'#DkOOQO'#D_'#D_QVQPOOOOQO-E7^-E7^O!XQSO'#CeO!oQSO'#DbO#SQQO,58|OOQO,58|,58|O#SQQO,58|OOQO-E7]-E7]OOQO'#Cf'#CfO#[QSO,59PO#_QSO,59PO#gQWO'#CfOOQO'#Cl'#ClO#rQSO'#CwO#}QPO'#C{O$SQSO'#CkOOQO'#DR'#DRO$XQWO'#DUO$aQSO'#DVO%RQSO'#DWO#}QPO'#DXOOQO'#Df'#DfO%sQSO'#DSO&TQSO'#DQO!^QSO'#DQO&cQQO'#DYO&qQQO'#CjOOQO,59|,59|OOQO-E7`-E7`OOQO1G.h1G.hO&|QQO1G.hO!XQSO,59SO!XQSO'#DaO'UQSO1G.kOOQO1G.k1G.kOOQO,59o,59oOOQO'#Cs'#CsO'^QSO,59cOOQO,59c,59cOOQO,59g,59gO(RQSO,59VO!XQSO,59pO(dQWO,59pOOQO,59q,59qOOQO,59r,59rO(iQPO,59sOOQO-E7d-E7dO!^QSO'#DgO(qQSO,59lO(qQSO,59lO)PQWO'#DZOOQO,59t,59tO)XQPO,59UOOQO7+$S7+$SOOQO1G.n1G.nO)^QSO,59{OOQO,59{,59{OOQO-E7_-E7_OOQO7+$V7+$VOOQO1G.}1G.}OOQO'#Co'#CoO)iQQO'#CnO)}QWO'#DOOOQO'#Dd'#DdO*iQSO'#C}O+QQSO'#C|OOQO'#DP'#DPOOQO1G.q1G.qO,QQSO1G/[O+`QSO1G/[O!XQSO1G/[OOQO1G/_1G/_OOQO,5:R,5:ROOQO-E7e-E7eO,XQSO1G/WOOQO'#D['#D[O,gQSO,59uOOQO1G.p1G.pO,oQSO'#CzOOQO,59Y,59YO-`QSO,59YO!XQSO,59jOOQO-E7b-E7bO-jQSO'#DeO-uQSO,59hOOQO7+$v7+$vO.uQSO7+$vO.TQSO7+$vOOQO1G/a1G/aO)PQWO1G/aO.|QPO,59fO/RQSO'#CfO/^QSO'#ChOOQO'#Cq'#CqO/cQSO'#CqO/kQSO'#CxOOQO'#Cp'#CpO,tQSO'#DcO/sQSO1G.tO/{QQO1G.tO/sQSO1G.tOOQO1G/U1G/UOOQO,5:P,5:POOQO-E7c-E7cOOQO<<Hb<<HbO0^QPO7+${O0cQPO'#CcOOQO1G/Q1G/QOOQO,59],59]O0kQSO,59dO1PQSO,59dOOQO,59},59}OOQO-E7a-E7aO1UQQO7+$`OOQO7+$`7+$`O1gQSO7+$`OOQO<<Hg<<HgOOQO1G/O1G/OO1oQSO1G/OOOQO<<Gz<<GzO2TQQO<<GzOOQO7+$j7+$jOOQOAN=fAN=fO#}QPO'#C{",
|
| 10 |
+
stateData:
|
| 11 |
+
"2u~O!^OS~OQTORTOSTOTTOWPO!pSO~OWPO]VX!`VX!qVX!oVX~O]YO!`XO!q[O~OZ_O~OZbOiiOjjO!ekO!fdO~OacO!ieO!mgO]!UX!q!UX~P!^O]YO!qtO~O!avO!bwO!cyO~O!nzOfYX!kYX~OZ{Of}Oh{O~OWPO~O!d!PO~Of!RO!k!QO~Of!SOZyX]yXiyXjyX!byX!eyX!fyX!oyX!qyX~Of!TOZzX]zXizXjzX!bzX!ezX!fzX!ozX!qzX~O]vX!bvX!ovX!qvX~P!^O!b!WO]tX!otX!qtX~O!`!ZO]|X!o|X!q|X~O!o!]O]^X!q^X~O]YO!q!^O~O!bwO!c!cO~Of!dOZka]kaikajka!bka!eka!fka!oka!qka!gka~OZ_Oa!eOi!hOj!hO!l!kO~O!k!oO~OWPO!g!pO~O!b!WO]ta!ota!qta~OZ!tO!P!tO~O!p!vO~O!avO!b!Ta!c!Ta~Om!xO!e!yO!h!wO]bX!obX!qbX~O!k!zOZrX]rXirXjrX!brX!orX!qrX~OZ_Oi!hOj!hO]qX!bqX!oqX!qqX~O!b!|O]pX!opX!qpX~Of#OOZxi]xiixijxi!bxi!exi!fxi!oxi!qxi~O!avO~P+`O!b!WO]ti!oti!qti~O!c#RO!d#SO~O!i#vO~OZ#UOa#YOf#XOh{Oi#ZOj#ZO!a#XO!fdO~O!b#[O!g#^O~P,tOZ_Oi!hOj!hO~O!b!|O]pa!opa!qpa~Of#cOZxq]xqixqjxq!bxq!exq!fxq!oxq!qxq~O!avO~P.TO!j#fO~O!aYX!bgX!ggX~O!avO~OZ{Oh{O~Of#iO!d#hO~O!b#[O!g#lO~Om#mO!h!wO]bi!obi!qbi~O!c#oO~OWPO!jVX~OZ{Of#XOh{Oi#pOj#pO!a#XO~O!d#qO~Om#rO!h!wO]bq!obq!qbq~O!b#[O!g#sO~OZ{Of#XOh{Oi#tOj#tO!a#XO~Om#uO!h!wO]by!oby!qby~Oiajh!o!`!cWTZSRQR~",
|
| 12 |
+
goto:
|
| 13 |
+
"'|!`PPPPPP!a!eP!m!pP#cP#s#v#yP#|$P$S$YP$dPPP$p#oP$z%W#|%^%d#|#v%i%l%u%u%u%u%u#v%{&OP!a&U&[&l&s&}'X'`'f'nPPP'xTTOVSROVT!Oe#vR]RQ`XWhYmo!WQ!_vQ!`wU!g!P!i!|Q!m!QQ#P!oS#V!y#[R#`!zQaXQ!awQ!n!QQ#Q!oT#Z!y#[RrYRqYRfYR!l!PR!f!PQ#_!yR#j#[S#Z!y#[Q#p#hR#t#qQ|dW#W!y#[#h#qR#g#XWlYmo!WT#Z!y#[Q!x!fQ#m#^Q#r#lR#u#sQpYR#T!wQ!j!PR#a!|V!h!P!i!|RoYQnYQ!YoR!q!WXlYmo!WR![pQ!u!ZR#d#SQVOR^VUQOVeUWQ!U#eQ!UkR#e#vSx`aR!bxQZRSsZuRu]Q#]!yS#k#]#nR#n#_S!i!P!|R!{!iQ!}!jR#b!}UmYo!WR!VmQ!XnS!r!X!sR!s!YTUOV",
|
| 14 |
+
nodeNames:
|
| 15 |
+
"⚠ Program LineComment TripleLineComment Week Day ExerciseExpression ExerciseName NonSeparator Repeat Rep Int RepRange SectionSeparator ExerciseSection ExerciseProperty ExercisePropertyName Keyword FunctionExpression FunctionName FunctionArgument Number Plus PosNumber Float Weight Percentage Rpe KeyValue Liftoscript ReuseLiftoscript ReuseSection WarmupExerciseSets WarmupExerciseSet WarmupSetPart None ExerciseSets CurrentVariation ExerciseSet Timer SetPart WeightWithPlus PercentageWithPlus SetLabel ReuseSectionWithWeekDay WeekDay WeekOrDay Current EmptyExpression",
|
| 16 |
+
maxTerm: 79,
|
| 17 |
+
skippedNodes: [0],
|
| 18 |
+
repeatNodeCount: 9,
|
| 19 |
+
tokenData:
|
| 20 |
+
"KQ~R{OX#xXY$|YZ%XZ]#x]^%`^p#xpq$|qr%jrs#xst&jtx#xxy*lyz*qz{#x{|*v|}3z}!O4z!O!P6Q!P!Q:i!Q![>s![!]AZ!]!b#x!b!cBZ!c!}CZ!}#ODj#O#PDo#P#QEo#Q#R#x#R#SEt#S#T#x#T#gCZ#g#hGV#h#lCZ#l#mHh#m#oCZ#o#pIy#p#q#x#q#rJv#r;'S#x;'S;=`$v<%l~#x~O#x~~J{R#}]WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xR$yP;=`<%l#x~%RQ!^~XY$|pq$|_%`O!pP!q^_%gP!pP!q^YZ%XV%q]!mSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x~&mZOY'`YZ(OZ]'`]^(T^s'`st({t;'S'`;'S;=`(u<%l~'`~O'`~~(O~'cXOY'`YZ(OZ]'`]^(T^;'S'`;'S;=`(u<%l~'`~O'`~~(O~(TOS~~(YXS~OY'`YZ(OZ]'`]^(T^;'S'`;'S;=`(u<%l~'`~O'`~~(O~(xP;=`<%l'`~)OXOY({YZ)kZ]({]^)r^;'S({;'S;=`*f<%l~({~O({~~)k~)rOT~S~~)yXT~S~OY({YZ)kZ]({]^)r^;'S({;'S;=`*f<%l~({~O({~~)k~*iP;=`<%l({~*qO!e~~*vO!g~_*}_f[WROX#xZ]#x^p#xqs#xtx#xz!O#x!O!P+|!Q![2g![!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_,R^WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![,}![!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_-ScWROX#xZ]#x^p#xqs#xtu#xuv._vx#xz!P#x!Q![,}![!}#x#O#P#x#Q#_#x#_#`/_#`#a1c#a#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_.f]j[WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_/d_WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#Z#x#Z#[0c#[#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_0j]i[WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_1h_WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#U#x#U#V0c#V#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_2ldWROX#xZ]#x^p#xqs#xtu#xuv._vx#xz!O#x!O!P+|!Q![2g![!}#x#O#P#x#Q#_#x#_#`/_#`#a1c#a#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_4R]!b[WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_5R_!aSWROX#xZ]#x^p#xqs#xtx#xz!O#x!O!P+|!Q![2g![!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_6V_WROX#xZ]#x^p#xqs#xtx#xz!O#x!O!P7U!Q![9V![!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xV7Z^WROX#xZ]#x^p#xqs#xtx#xz!O#x!O!P8V!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xV8^]!iSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_9^chSWROX#xZ]#x^p#xqs#xtu#xuv._vx#xz!P#x!Q![9V![!}#x#O#P#x#Q#_#x#_#`/_#`#a1c#a#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_:nP]^!P!Q:qP:tZOY;gYZ<VZ];g]^<[^!P;g!P!Q=S!Q;'S;g;'S;=`<|<%l~;g~O;g~~<VP;jXOY;gYZ<VZ];g]^<[^;'S;g;'S;=`<|<%l~;g~O;g~~<VP<[OQPP<aXQPOY;gYZ<VZ];g]^<[^;'S;g;'S;=`<|<%l~;g~O;g~~<VP=PP;=`<%l;gP=VXOY=SYZ=rZ]=S]^=y^;'S=S;'S;=`>m<%l~=S~O=S~~=rP=yORPQPP>QXRPQPOY=SYZ=rZ]=S]^=y^;'S=S;'S;=`>m<%l~=S~O=S~~=rP>pP;=`<%l=S_>zdWRZ[OX#xZ]#x^p#xqs#xtu#xuv._vx#xz!O#x!O!P@Y!Q![>s![!}#x#O#P#x#Q#_#x#_#`/_#`#a1c#a#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x_@_^WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![9V![!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xVAb]!dSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xVBb]!fSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#xVCbbaSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![CZ![!c#x!c!}CZ#O#P#x#Q#R#x#R#SCZ#S#T#x#T#oCZ#p#q#x#r;'S#x;'S;=`$v<%lO#x~DoO!`~~Dv]!o~WROX#xZ]#x^p#xqs#xtx#xz!P#x!Q!}#x#O#P#x#Q#o#x#p#q#x#r;'S#x;'S;=`$v<%lO#x~EtO!c~_E}b!PWaSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![CZ![!c#x!c!}CZ#O#P#x#Q#R#x#R#SCZ#S#T#x#T#oCZ#p#q#x#r;'S#x;'S;=`$v<%lO#x_G`b!nWaSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![CZ![!c#x!c!}CZ#O#P#x#Q#R#x#R#SCZ#S#T#x#T#oCZ#p#q#x#r;'S#x;'S;=`$v<%lO#x_Hqb!kWaSWROX#xZ]#x^p#xqs#xtx#xz!P#x!Q![CZ![!c#x!c!}CZ#O#P#x#Q#R#x#R#SCZ#S#T#x#T#oCZ#p#q#x#r;'S#x;'S;=`$v<%lO#x~JOP!h~#r#sJR~JUTO#rJR#r#sJe#s;'SJR;'S;=`Jp<%lOJR~JhP#q#rJk~JpOm~~JsP;=`<%lJR~J{O!j~^KQO!q^",
|
| 21 |
+
tokenizers: [0, 1, 2, 3],
|
| 22 |
+
topRules: { Program: [0, 1] },
|
| 23 |
+
specialized: [{ term: 17, get: (value) => spec_Keyword[value] || -1 }],
|
| 24 |
+
tokenPrec: 804,
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
module.exports = { parser };
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio[mcp]>=5.0.0
|
| 2 |
+
requests>=2.31.0
|