@bardsballad/verse
v0.2.5
Published
A custom typed scripting language for BardsBallad with Monaco editor integration
Maintainers
Readme
Verse Script Language
A custom typed scripting language designed for tabletop RPG (TTRPG) systems with full type inference, Monaco editor integration, and compile-to-JavaScript capabilities.
🎲 Features
- Type Inference: Automatically infers return types from your scripts
- Monaco Editor Integration: Full IDE-like experience with autocomplete, hover info, and error checking
- Type-Safe: Catch errors before runtime with static type checking
- Compiles to JavaScript: Execute scripts in any JavaScript environment
- TTRPG-Focused: Built specifically for character sheets, game logic, and system automation
- Nested Context Support: Access parent scope variables in dynamic lists
🚀 Try It Online
Visit the Live Playground to try it out!
📦 Installation
npm install @bardsballad/verse
# or
yarn add @bardsballad/verse🎮 Quick Start
Basic Usage
import { VerseScriptCompiler, BUILTIN_TYPES } from '@bardsballad/verse';
// Define your types
const SpellType = VerseScriptCompiler.createObjectType('Spell', {
name: BUILTIN_TYPES.string,
level: BUILTIN_TYPES.number,
damage: BUILTIN_TYPES.string,
});
const contextTypes = {
casting: SpellCastingType,
slot: SpellSlotType,
};
// Create compiler
const compiler = new VerseScriptCompiler(contextTypes);
// Compile a script
const result = compiler.compile(`
const spells = casting.spells.filter(s => s.level <= slot.level)
return spells
`);
console.log(result.returnType); // "Spell[]"
console.log(result.code); // Compiled JavaScriptMonaco Editor Integration
import { createVerseScriptEditor } from '@bardsballad/verse/monaco';
// Create editor with language service
const { editor, languageService } = createVerseScriptEditor(
document.getElementById('editor'),
contextTypes,
'return casting.spells'
);
// Listen for changes
editor.onDidChangeModelContent(() => {
const result = languageService.compile(editor.getValue());
if (result.success) {
console.log('Return type:', result.returnType);
}
});📚 Language Syntax
Variables
let x = 5
const name = "Gandalf"Functions
fn calculateDamage(spell, level) {
const baseDamage = spell.damage
const bonus = level * 2
return baseDamage + bonus
}Control Flow
if hp <= 0 {
return "Dead"
} else {
return "Alive"
}
for spell in casting.spells {
if spell.level <= 3 {
// Do something
}
}Expressions
// Ternary
const status = hp > 0 ? "alive" : "dead"
// Array methods
const lowLevelSpells = spells.filter(s => s.level <= 2)
const spellNames = spells.map(s => s.name)
const healing = spells.find(s => s.name == "Cure Wounds")
// Property access
const currentHp = characterState.hp
const slots = casting.slots[0].current🏗️ Project Structure
ttrpg-script-lang/
├── src/
│ ├── compiler/
│ │ ├── lexer.ts # Tokenization
│ │ ├── parser.ts # AST generation
│ │ ├── type-checker.ts # Type inference
│ │ ├── code-generator.ts # JavaScript compilation
│ │ └── index.ts # Main compiler API
│ ├── monaco/
│ │ └── language-service.ts # Monaco integration
│ └── index.ts
├── playground/
│ ├── index.html # Web playground
│ └── styles.css
├── examples/
│ ├── basic.ttrpg
│ ├── dnd5e-spells.ttrpg
│ └── nested-context.ttrpg
├── tests/
│ ├── lexer.test.ts
│ ├── parser.test.ts
│ ├── type-checker.test.ts
│ └── compiler.test.ts
├── docs/
│ ├── language-reference.md
│ ├── type-system.md
│ └── api.md
├── package.json
├── tsconfig.json
├── vite.config.ts # For playground
└── README.md🔧 Development
Setup
# Clone the repository
git clone https://github.com/BardsBallad/Verse.git
cd Verse
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Start playground
npm run playgroundRunning Tests
# Run all tests
npm test
# Run specific test suite
npm test -- lexer
# Watch mode
npm test -- --watchBuilding the Playground
# Development
npm run playground:dev
# Production build
npm run playground:build
# Preview production build
npm run playground:preview📖 Documentation
- Language Reference - Complete syntax guide
- Type System - Understanding types and inference
- API Documentation - Compiler and Monaco API
- Examples - Sample scripts and use cases
🎯 Use Cases
Character Sheet Automation
// Calculate attack bonus
fn getAttackBonus(character) {
const proficiency = character.proficiencyBonus
const modifier = character.stats.strength.modifier
return proficiency + modifier
}Dynamic List Filtering
// Get available spells for current slot level
const availableSpells = casting.spells.filter(s => {
return s.level == slot.level && slot.current > 0
})
return availableSpellsSpell Slot Management
// Cast a spell
if slot.current > 0 {
slot.current = slot.current - 1
return { success: true, remaining: slot.current }
} else {
return { success: false, error: "No spell slots remaining" }
}🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
Development Roadmap
- [ ] Generics support
- [X] Type annotations (
let x: number = 5) - [ ] Dice roll syntax (
roll 2d6 + 3) - [ ] Pattern matching
- [ ] Async/await support
- [ ] Standard library of TTRPG functions
- [ ] Language Server Protocol (LSP)
- [ ] VS Code extension
📝 License
MIT License - see LICENSE for details
🙏 Acknowledgments
- Built with Monaco Editor
- Inspired by TypeScript type system
- Designed for the TTRPG community
📧 Contact
- GitHub: @KingCosmic
- Issues: GitHub Issues
Made with ❤️ for the TTRPG community
Example Scripts
Filter Spells by Level
const filtered = casting.spells.filter(s => s.level <= 2)
return filtered
// Return type: Spell[]Nested Context Access
const spells = casting.spells.filter(s => s.level == slot.level)
return spells
// Return type: Spell[]Conditional Returns
if slot.current <= 0 {
return null
}
return casting.spells.filter(s => s.level == slot.level)
// Return type: Spell[] | nullFunction with Inference
fn getSpellsForLevel(level) {
return casting.spells.filter(s => s.level <= level)
}
return getSpellsForLevel(3)
// Return type: Spell[]