Components
Monaco Language Studio Professional language definition toolkit
Loading preview...
import React, { useState, useEffect } from 'react';
import { FileCode, Eye, Download, Upload, Check, AlertCircle, Copy, Palette, Code2 } from 'lucide-react';
const MonacoLanguageStudio = () => {
const [input, setInput] = useState('');
const [parsed, setParsed] = useState(null);
const [error, setError] = useState('');
const [activeTab, setActiveTab] = useState('parse');
const [beautified, setBeautified] = useState('');
const [validation, setValidation] = useState([]);
const parseAMDModule = (code) => {
try {
const defineMatch = code.match(/define\s*\(\s*["']([^"']+)["']\s*,\s*\[([^\]]+)\]\s*,\s*\(?\s*function\s*\(([^)]*)\)\s*\{([\s\S]*)\}\s*\)?\s*\)/);
if (!defineMatch) {
throw new Error('Not a valid AMD module definition');
}
const [, moduleName, dependencies, params, body] = defineMatch;
const confMatch = body.match(/const\s+n\s*=\s*e\.conf/);
const langMatch = body.match(/const\s+g\s*=\s*\{([\s\S]*?)\};/);
if (!langMatch) {
throw new Error('Could not extract language definition');
}
const langObjStr = '{' + langMatch[1] + '}';
const keywordsMatch = langObjStr.match(/keywords:\s*\[([^\]]+)\]/);
const keywords = keywordsMatch ?
keywordsMatch[1].split(',').map(k => k.trim().replace(/['"]/g, '')) : [];
const operatorsMatch = langObjStr.match(/operators:\s*e\.language\.operators/);
return {
moduleName: moduleName.trim(),
dependencies: dependencies.split(',').map(d => d.trim().replace(/['"]/g, '')),
parameters: params.split(',').map(p => p.trim()),
language: {
keywords,
hasOperators: !!operatorsMatch,
defaultToken: 'invalid',
tokenPostfix: '.js'
}
};
} catch (err) {
throw new Error(`Parse error: ${err.message}`);
}
};
const validateLanguageDef = (def) => {
const issues = [];
if (!def) {
issues.push({ type: 'error', message: 'No definition provided' });
return issues;
}
if (!def.language) {
issues.push({ type: 'error', message: 'Missing language object' });
return issues;
}
if (!def.language.keywords || def.language.keywords.length === 0) {
issues.push({ type: 'warning', message: 'No keywords defined' });
}
if (!def.language.defaultToken) {
issues.push({ type: 'warning', message: 'No defaultToken specified' });
}
if (def.language.keywords && def.language.keywords.length > 100) {
issues.push({ type: 'info', message: `Large keyword set: ${def.language.keywords.length} keywords` });
}
if (issues.length === 0) {
issues.push({ type: 'success', message: 'Language definition is valid' });
}
return issues;
};
const beautifyDefinition = (def) => {
if (!def) return '';
const langDef = {
id: def.moduleName.split('/').pop(),
keywords: def.language.keywords,
defaultToken: def.language.defaultToken,
tokenPostfix: def.language.tokenPostfix,
hasOperators: def.language.hasOperators
};
return JSON.stringify(langDef, null, 2);
};
const generateMonacoLanguage = (def) => {
if (!def) return '';
const langId = def.moduleName.split('/').pop().replace(/^javascript-.*$/, 'javascript');
return `// Monaco Editor Language Definition for ${langId}
monaco.languages.register({ id: '${langId}' });
monaco.languages.setMonarchTokensProvider('${langId}', {
defaultToken: '${def.language.defaultToken}',
tokenPostfix: '${def.language.tokenPostfix}',
keywords: [
${def.language.keywords.map(k => `'${k}'`).join(',\n ')}
],
operators: [
'<=', '>=', '==', '!=', '===', '!==', '=>',
'+', '-', '**', '*', '/', '%',
'++', '--', '<<', '>>', '>>>', '&', '|', '^', '!',
'~', '&&', '||', '??', '?', ':', '=',
'+=', '-=', '*=', '/=', '%=', '&=', '|=',
'^=', '<<=', '>>=', '>>>='
],
symbols: /[=><!~?:&|+\\-*\\/\\^%]+/,
tokenizer: {
root: [
[/[a-z_$][\\w$]*/, {
cases: {
'@keywords': 'keyword',
'@default': 'identifier'
}
}],
[/[A-Z][\\w\\$]*/, 'type.identifier'],
{ include: '@whitespace' },
[/[{}()\\[\\]]/, '@brackets'],
[/@symbols/, {
cases: {
'@operators': 'operator',
'@default': ''
}
}],
[/\\d*\\.\\d+([eE][\\-+]?\\d+)?/, 'number.float'],
[/0[xX][0-9a-fA-F]+/, 'number.hex'],
[/\\d+/, 'number'],
[/["']([^"'\\\\]|\\\\.)*["']/, 'string']
],
whitespace: [
[/[ \\t\\r\\n]+/, ''],
[/\\/\\*/, 'comment', '@comment'],
[/\\/\\/.*$/, 'comment']
],
comment: [
[/[^\\/*]+/, 'comment'],
[/\\*\\//, 'comment', '@pop'],
[/[\\/*]/, 'comment']
]
}
});`;
};
const handleParse = () => {
try {
const result = parseAMDModule(input);
setParsed(result);
setBeautified(beautifyDefinition(result));
setValidation(validateLanguageDef(result));
setError('');
} catch (err) {
setError(err.message);
setParsed(null);
setBeautified('');
setValidation([]);
}
};
const handleFileUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
setInput(event.target.result);
};
reader.readAsText(file);
}
};
const copyToClipboard = (text) => {
navigator.clipboard.writeText(text);
};
const downloadFile = (content, filename) => {
const blob = new Blob([content], { type: 'text/javascript' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
};
useEffect(() => {
if (input) {
handleParse();
}
}, [input]);
const getValidationIcon = (type) => {
switch (type) {
case 'success': return <Check className="w-4 h-4 text-emerald-500" />;
case 'error': return <AlertCircle className="w-4 h-4 text-red-400" />;
case 'warning': return <AlertCircle className="w-4 h-4 text-amber-400" />;
default: return <AlertCircle className="w-4 h-4 text-blue-400" />;
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 p-6">
<style>{`
.carbon-texture {
background-color: linear-gradient(135deg, rgba(90,188,40,0.5) 0%, rgba(40,81,90,0.7) 100%);
background-image: url('https://pub-f741fb7b98024df48a8a6d8ac4804414.r2.dev/PILL_TRI.webp');
background-size: 115px 95px;
background-repeat: repeat;
position: relative;
}
.carbon-texture::before {
content: '';
position: absolute;
inset: 1;
background-image: url('https://pub-f741fb7b98024df48a8a6d8ac4804414.r2.dev/PILL_TRI.webp');
background-size: 1000px -100px;
background-repeat: repeat;
opacity: 80.;
mix-blend-mode: hard-light;
pointer-events: none;
}
.carbon-texture::after {
content: '';
position: absolute;
inset: 1;
background: linear-gradient(135deg, rgba(0,1,0,0.1) 0%, rgba(0,1,0,0.7) 100%);
pointer-events: none;
}
.carbon-content {
position: relative;
z-index: 1;
}
`}</style>
<div className="max-w-7xl mx-auto">
<div className="bg-black rounded-3xl shadow-2xl overflow-hidden">
{/* Header */}
<div className="relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-gray-400 to-gray-500 opacity-90"></div>
<div className="absolute inset-0 bg-gradient-to-b from-black/30 to-transparent"></div>
<div className="relative p-8">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-3xl bg-black/20 backdrop-blur-sm flex items-center justify-center border border-black/40">
<FileCode className="w-8 h-8 text-white" />
</div>
<div>
<h1 className="text-4xl font-bold text-white tracking-tight">Monaco Language Studio</h1>
<p className="text-white/90 mt-2 text-lg">Professional language definition toolkit</p>
</div>
</div>
</div>
</div>
{/* Input Section - Carbon Fiber Background */}
<div className="carbon-texture p-8">
<div className="carbon-content">
<div className="flex items-center gap-4 mb-6">
<label className="relative group cursor-pointer">
<div className="px-6 py-3 bg-gradient-to-br from-gray-400 to-gray-500 hover:from-gray-500 hover:to-gray-600 text-white rounded-2xl transition-all duration-300 flex items-center gap-3 shadow-lg">
<Upload className="w-5 h-5" />
<span className="font-medium">Upload Module</span>
</div>
<input type="file" accept=".js,.ts" onChange={handleFileUpload} className="hidden" />
</label>
<span className="text-white drop-shadow-lg text-sm font-medium">or paste your AMD module definition below</span>
</div>
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Paste your AMD module definition here..."
className="w-full h-56 bg-black/40 backdrop-blur-sm text-gray-100 placeholder-gray-400 rounded-2xl p-6 font-mono text-sm border-2 border-red/20 focus:border-red/40 focus:outline-none resize-none shadow-2xl transition-colors"
/>
{error && (
<div className="mt-4 p-4 bg-red-900/80 backdrop-blur-sm border-2 border-red-500/50 rounded-2xl flex items-center gap-3 text-red-100">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<span className="font-medium">{error}</span>
</div>
)}
</div>
</div>
{/* Tabs - Carbon Fiber Background */}
{parsed && (
<div className="carbon-texture border-b-2 border-gray-800/30">
<div className="carbon-content flex gap-2 px-8">
{['parse', 'validate', 'beautify', 'generate'].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-8 py-4 font-semibold transition-all duration-300 relative ${
activeTab === tab
? 'text-white drop-shadow-lg'
: 'text-gray-300 hover:text-red'
}`}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
{activeTab === tab && (
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-gray-300 to-white rounded-full shadow-lg"></div>
)}
</button>
))}
</div>
</div>
)}
{/* Content - Carbon Fiber Background */}
{parsed && (
<div className="carbon-texture p-8">
<div className="carbon-content">
{activeTab === 'parse' && (
<div className="space-y-6">
<div className="bg-black/40 backdrop-blur-md rounded-2xl p-6 border-2 border-white/20 shadow-2xl">
<h3 className="text-white font-bold text-lg mb-4 drop-shadow-lg">Module Information</h3>
<div className="space-y-3 text-sm">
<div className="flex gap-3">
<span className="text-gray-300 font-semibold min-w-32">Module:</span>
<span className="text-white font-mono">{parsed.moduleName}</span>
</div>
<div className="flex gap-3">
<span className="text-gray-300 font-semibold min-w-32">Dependencies:</span>
<span className="text-white font-mono">{parsed.dependencies.join(', ')}</span>
</div>
</div>
</div>
<div className="bg-black/40 backdrop-blur-md rounded-2xl p-6 border-2 border-red/20 shadow-2xl">
<h3 className="text-white font-bold text-lg mb-4 drop-shadow-lg">Keywords ({parsed.language.keywords.length})</h3>
<div className="flex flex-wrap gap-2 max-h-80 overflow-y-auto">
{parsed.language.keywords.map((kw, i) => (
<span key={i} className="px-4 py-2 bg-gray-700/60 backdrop-blur-sm text-gray-100 rounded-full text-sm font-medium shadow-lg border border-gray-500/50">
{kw}
</span>
))}
</div>
</div>
</div>
)}
{activeTab === 'validate' && (
<div className="space-y-4">
{validation.map((issue, i) => (
<div
key={i}
className={`p-5 backdrop-blur-md rounded-2xl flex items-center gap-4 border-2 shadow-2xl ${
issue.type === 'success' ? 'bg-emerald-900/60 border-emerald-500/50' :
issue.type === 'error' ? 'bg-red-900/60 border-red-500/50' :
issue.type === 'warning' ? 'bg-amber-900/60 border-amber-500/50' :
'bg-blue-900/60 border-blue-500/50'
}`}
>
{getValidationIcon(issue.type)}
<span className={`font-medium ${
issue.type === 'success' ? 'text-emerald-100' :
issue.type === 'error' ? 'text-red-100' :
issue.type === 'warning' ? 'text-amber-100' :
'text-blue-100'
}`}>{issue.message}</span>
</div>
))}
</div>
)}
{activeTab === 'beautify' && (
<div className="space-y-6">
<div className="flex gap-3">
<button
onClick={() => copyToClipboard(beautified)}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-br from-gray-400 to-gray-500 hover:from-gray-500 hover:to-gray-600 text-white rounded-2xl transition-all duration-300 shadow-lg font-medium"
>
<Copy className="w-4 h-4" />
Copy JSON
</button>
<button
onClick={() => downloadFile(beautified, 'language-definition.json')}
className="flex items-center gap-2 px-6 py-3 bg-black/40 hover:bg-black/60 backdrop-blur-sm text-red rounded-2xl transition-all duration-300 shadow-lg border-2 border-red/20 font-medium"
>
<Download className="w-4 h-4" />
Download
</button>
</div>
<pre className="bg-black/40 backdrop-blur-md text-gray-100 rounded-2xl p-6 overflow-x-auto border-2 border-white/20 font-mono text-sm shadow-2xl">
{beautified}
</pre>
</div>
)}
{activeTab === 'generate' && (
<div className="space-y-6">
<div className="flex gap-3">
<button
onClick={() => copyToClipboard(generateMonacoLanguage(parsed))}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-br from-gray-400 to-gray-500 hover:from-gray-500 hover:to-gray-600 text-red rounded-2xl transition-all duration-300 shadow-lg font-medium"
>
<Copy className="w-4 h-4" />
Copy Code
</button>
<button
onClick={() => downloadFile(generateMonacoLanguage(parsed), 'monaco-language.js')}
className="flex items-center gap-2 px-6 py-3 bg-black/40 hover:bg-black/60 backdrop-blur-sm text-red rounded-2xl transition-all duration-300 shadow-lg border-2 border-white/20 font-medium"
>
<Download className="w-4 h-4" />
Download
</button>
</div>
<pre className="bg-black/40 backdrop-blur-md text-gray-100 rounded-2xl p-6 overflow-x-auto border-2 border-white/20 font-mono text-sm max-h-96 shadow-2xl">
{generateMonacoLanguage(parsed)}
</pre>
</div>
)}
</div>
</div>
)}
{!parsed && !error && (
<div className="carbon-texture p-20 text-center">
<div className="carbon-content">
<div className="w-20 h-20 mx-auto mb-6 rounded-3xl bg-black/40 backdrop-blur-md flex items-center justify-center border-2 border-white/20 shadow-2xl">
<Code2 className="w-10 h-10 text-gray-300" />
</div>
<p className="text-white drop-shadow-lg text-lg font-medium">Upload or paste an AMD module definition to begin</p>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default MonacoLanguageStudio;