A complete, working example: Arduino
Below is a fully working plugin for Arduino-style code (C/C++ syntax with Arduino functions). Use it as a template and adapt the parts marked with comments to your language.
// Register new language - Arduino
// Plugin: Arduino (C/C++-like with Arduino functions)
PWAxcode.registerLanguage({
id: 'arduino',
// 2.1 Detect: return a confidence score [0..1]
detect: (txt) => /void\s+setup\s*\(|void\s+loop\s*\(|#include\s*<Arduino\.h>/.test(txt) ? 1 : 0,
// 2.2 Tokenize: convert source into [{ type?, text }, ...]
tokenize: (src) => {
const tokens = [];
const push = (type, text) => tokens.push({ type, text });
const pushTxt = (text) => tokens.push({ text });
// Language vocabularies (customize per language)
const KW = new Set(['if','else','for','while','do','switch','case','break','continue','return',
'const','volatile','static','struct','enum','typedef','sizeof','inline']);
const TYPE = new Set(['void','bool','char','short','int','long','float','double','unsigned','signed',
'uint8_t','uint16_t','uint32_t','uint64_t','size_t']);
const AFN = new Set(['setup','loop','pinMode','digitalWrite','digitalRead','analogWrite','analogRead',
'delay','millis','micros','attachInterrupt','detachInterrupt','Serial','Serial1','Serial2']);
let i = 0, n = src.length;
const isWS = c => /\s/.test(c);
const readWhile = pred => { const j=i; while (i<n && pred(src[i])) i++; return src.slice(j,i); };
const readNumber = () => {
const j=i;
if (src[i] === '-') i++;
while (/[0-9]/.test(src[i])) i++;
if (src[i] === '.') { i++; while (/[0-9]/.test(src[i])) i++; }
if (/[eE]/.test(src[i])) { i++; if (/[+-]/.test(src[i])) i++; while (/[0-9]/.test(src[i])) i++; }
return src.slice(j,i);
};
const readQuoted = q => {
let out = src[i++]; // opening quote
while (i<n) {
const c = src[i]; out += c; i++;
if (c === q) { break; }
if (c === '\\' && i<n) { out += src[i]; i++; }
}
return out;
};
while (i<n) {
const ch = src[i], nx = src[i+1];
// 1) newline and whitespace must be preserved exactly
if (ch === '\n') { pushTxt('\n'); i++; continue; }
if (isWS(ch)) { pushTxt(readWhile(isWS)); continue; }
// 2) preprocessor / directives
if (ch === '#') {
let j=i; while (j<n && src[j] !== '\n') j++;
push('pre', src.slice(i,j)); i=j; continue;
}
// 3) comments (// and /* ... */)
if (ch==='/' && nx==='/' ) { let j=i+2; while (j<n && src[j] !== '\n') j++; push('com', src.slice(i,j)); i=j; continue; }
if (ch==='/' && nx==='*' ) { let j=i+2; while (j<n && !(src[j]==='*' && src[j+1]==='/')) j++; j=Math.min(n, j+2); push('com', src.slice(i,j)); i=j; continue; }
// 4) strings / chars
if (ch==='"' || ch==="'") { push('str', readQuoted(ch)); continue; }
// 5) numbers
if (/[0-9]/.test(ch) || (ch==='.' && /[0-9]/.test(nx))) { push('num', readNumber()); continue; }
// 6) identifiers and function names
if (/[A-Za-z_]/.test(ch)) {
const j=i; while (i<n && /[A-Za-z0-9_]/.test(src[i])) i++;
const id = src.slice(j,i);
const low = id.toLowerCase();
if (KW.has(low)) { push('kw', id); continue; }
if (TYPE.has(low)) { push('type', id); continue; }
if (AFN.has(id)) { push('fn', id); continue; }
// treat as function if immediately followed by '(' (after optional spaces)
let k=i; while (k<n && /\s/.test(src[k])) k++;
if (src[k] === '(') { push('fn', id); continue; }
pushTxt(id); continue;
}
// 7) punctuation / operators
if (',;(){}[].:+-/*%<>=!&|^~?'.includes(ch)) { push('pun', ch); i++; continue; }
// 8) fallback
pushTxt(ch); i++;
}
return tokens;
},
// 2.3 Auto-indent: brace-based with comment/string awareness
autoIndent: (text, size=2) => {
const lines = text.replace(/\r\n?/g, '\n').split('\n');
let depth=0, inBlockCom=false, inStr=false, q='';
const out=[];
for (let raw of lines) {
const trimmed = raw.replace(/^[ \t]+/, '');
let level = depth;
if (trimmed.startsWith('}') || trimmed.startsWith(');') || trimmed === '}') {
level = Math.max(0, level-1);
}
out.push(' '.repeat(level*size) + trimmed);
let i=0, n=raw.length, inLineCom=false;
while (i<n) {
const ch = raw[i], nx = raw[i+1];
if (inLineCom) break;
if (inBlockCom) { if (ch==='*' && nx=== '/') { inBlockCom=false; i+=2; continue; } i++; continue; }
if (inStr) { if (ch==='\\'){ i+=2; continue; } if (ch===q){ inStr=false; } i++; continue; }
if (ch==='/'&&nx==='/' ){ inLineCom=true; break; }
if (ch==='/'&&nx==='*' ){ inBlockCom=true; i+=2; continue; }
if (ch==='"'||ch==="'"){ inStr=true; q=ch; i++; continue; }
if (ch==='{'){ depth++; i++; continue; }
if (ch==='}'){ depth = Math.max(0, depth-1); i++; continue; }
i++;
}
}
return out.join('\n');
}
});