"""
The main test case for the syntax highlighter is the Cobra compiler itself:
cobra -highlight -files:files-to-compile.text
TODO:
[ ] The stylesheet path should be computed rather than hardcoded as ..\
[ ] Files that are in a deeper subdirectory, like gen-html\BackEndClr\, don't have a correct path to the stylesheet
[ ] Partial classes are not getting the 'tdn' style because they are merged into their classes. Likewise, their members are not getting 'mdn' style.
[ ] String substitution should not color the brackets [] the same color as the string text.
[ ] Negative constants like "-1" don't get highlighted correctly. The - is treated as an operator.
[ ] Right now "bool" is a bolded keyword and types like String and Shape are normal. Maybe there should be a "type" highlight.
[ ] Use what's found in OperatorSpecs instead of duplicating. This also requires completing a TODO item in OperatorSpecs
# TODO: move the binary op and unary op specs here
"""
class Compiler is partial
def highlightFiles
require
.modules.count
body
Directory.createDirectory(.targetDirectory)
for mod in .modules
if mod inherits CobraModule
if not mod.isImplicit
mod.writeHtmlHighlightedFileTo(.targetDirectory)
get targetDirectory as String
return 'gen-html'
class CobraModule is partial
def writeHtmlHighlightedFileTo(targetDirectory as String)
htmlFileName = Path.combine(targetDirectory, .fileName + '.html')
dir = Path.getDirectoryName(htmlFileName)
Directory.createDirectory(dir) # the .fileName may have relative subdirs in it
print 'Writing [htmlFileName]'
using tw = File.createText(htmlFileName)
.writeHtmlHighlightedFileTo(tw)
def writeHtmlHighlightedFileTo(tw as TextWriter)
tw.writeLine('')
tw.writeLine('
')
tw.writeLine('')
tw.writeLine('[.fileName]')
tw.writeLine('')
tw.writeLine('')
tw.writeLine('')
tw.writeLine('')
tw.write(.htmlSource)
tw.writeLine('')
tw.writeLine('')
def htmlSource as String
require .typeProvider
return .htmlSource(nil)
def htmlSource(typeProvider as ITypeProvider?) as String
require .typeProvider or typeProvider
if typeProvider
saveProvider = Node.typeProvider
Node.typeProvider = typeProvider
try
source = File.readAllText(.fileName)
marks = Marks(.fileName)
# first do token stream which covers all tokens including comments
# not expecting any errors here since we got past this phase before
tokenizer = CobraTokenizer(typeProvider=.typeProvider, willReturnComments=true)
tokens = tokenizer.startSource(.fileName, source).allTokens
marks.add(tokens)
# now do nodes -- gives better semantic highlighting than just tokens
.topNameSpace.highlight(marks)
sb = StringBuilder('
')
marks.combine(source, sb)
sb.append('
')
return sb.toString
finally
if typeProvider
Node.typeProvider = saveProvider
class Marks
"""
A collection of marks as in "mark up".
Used for syntax highlighting of source files.
For example, source code could be rewritten as HTML with tags for each mark.
"""
shared
var _ignoreTokens = Set()
var _invisibleTokens = {'DEDENT', 'EOL', 'INDENT'}
var _normalTokens = {
'BANG', 'BANG_EQUALS', 'CARET', 'COLON', 'COMMA', 'DOT', 'DOTDOT', 'LCURLY', 'LPAREN', 'PERCENTPERCENT', 'QUESTION', 'QUESTION_EQUALS', 'RCURLY', 'RPAREN', 'INT_SIZE', 'UINT_SIZE',
}
get ignoreTokens as Set
if _ignoreTokens.count == 0
# 2009-03-13 Mono bug re: method overload resolution. https://bugzilla.novell.com/show_bug.cgi?id=485378
# _ignoreTokens.addRange(_invisibleTokens)
# _ignoreTokens.addRange(_normalTokens)
for token in _invisibleTokens, _ignoreTokens.add(token)
for token in _normalTokens, _ignoreTokens.add(token)
return _ignoreTokens
var _tokenToStyleSpecs = [
'TRUE kw-s',
'FALSE kw-s',
'NIL kw-s',
'BASE kw-b',
'THIS kw-t',
'CHAR_LIT_SINGLE lc',
'CHAR_LIT_DOUBLE lc',
'DECIMAL_LIT ld',
'AT_ID di',
'DIRECTIVE di',
'COMMA no',
'COMMENT c',
'DOC_STRING_START ds',
'DOC_STRING_BODY_TEXT ds',
'DOC_STRING_STOP ds',
'DOC_STRING_LINE ds',
'ID i',
'FLOAT_LIT lf',
'FRACTIONAL_LIT lfr',
'INTEGER_LIT li',
'STRING_DOUBLE ls',
'STRING_SINGLE ls',
'STRING_PART_FORMAT spf',
'TOQ kw',
]
var _ops = 'ARRAY_OPEN ASSIGN EQ NE GT LT GE LE DOUBLE_GT LBRACKET RBRACKET PLUS MINUS STAR STARSTAR SLASH SLASHSLASH PERCENT MINUS_EQUALS PLUS_EQUALS'
var _tokenToStyle = Dictionary()
get tokenToStyle as Dictionary
if _tokenToStyle.count == 0
for spec in _tokenToStyleSpecs
parts = spec.split
_tokenToStyle[parts[0]] = parts[1]
for op in _ops.split
_tokenToStyle[op] = 'op'
return _tokenToStyle
var _marks = Dictionary()
cue init(fileName as String)
base.init
_fileName = fileName
get fileName from var as String
def add(tokens as IToken*)
ignoreTokens, tokenToStyle = .ignoreTokens, .tokenToStyle
for token in tokens
# print token.which, token.text, token.charNum, token.isKeyword
if token.which in ignoreTokens
pass
else if tokenToStyle.containsKey(token.which)
.add(token, tokenToStyle[token.which])
else if token.isKeyword
.add(token, 'kw')
else
charNum = token.charNum - 1
len = token.text.length
text = token.text
branch token.which
on 'OPEN_CALL'
.add(token, 'i')
on 'OPEN_DO'
.add(charNum, 2, 'kw', 'do')
on 'OPEN_GENERIC'
.add(token, 'i')
on 'OPEN_IF'
.add(charNum, len-1, 'kw', text[:-1])
on 'SHARP_SINGLE' or 'SHARP_DOUBLE' # sharp'...'
.add(charNum, 5, 'kw', 'sharp')
.add(charNum+5, len-5, 'ls', text[5:])
on 'SHARP_OPEN'
.add(charNum, 6, 'kw', '$sharp') # deprecated
on 'STRING_START_SINGLE' or 'STRING_START_DOUBLE'
.add(charNum, len-1, 'ls', text[:-1])
.add(charNum+len-1, 1, 'lslb', '\[')
on 'STRING_PART_SINGLE' or 'STRING_PART_DOUBLE'
.add(charNum, 1, 'lsrb', ']')
if len > 2, .add(charNum+1, len-2, 'ls', text[1:-1]) # else text is ']['
.add(charNum+len-1, 1, 'lslb', '\[')
on 'STRING_STOP_SINGLE' or 'STRING_STOP_DOUBLE'
.add(charNum, 1, 'lsrb', ']')
.add(charNum+1, len-1, 'ls', text[1:])
else, print '*** unknown token type: [token.which]; [token]'
def add(token as IToken, kind as String)
# second check below guards against partial classes split across files
if not token.isEmpty and token.fileName.endsWith(.fileName)
# The OPEN_CALL and OPEN_GENERIC tokens come from multiple sources such as declarations
# and expressions. So we check for them here in one place.
charNum = token.charNum - 1
branch token.which
on 'OPEN_CALL'
.add(charNum, token.text.length-1, kind, token.text[:-1])
on 'OPEN_GENERIC'
.add(charNum, token.text.length-3, kind, token.text[:-3])
.add(charNum+token.text.length-3, 2, 'kw', 'of')
else, .add(charNum, token.text.length, kind, token.text)
def add(charNum as int, length as int, kind as String)
.add(charNum, length, kind, '')
def add(charNum as int, length as int, kind as String, text as String)
_marks[charNum] = Mark(charNum, length, kind, text)
def combine(plainSource as String, sb as StringBuilder)
# CC: TODO: next line causes exception in the compiler
# marks = _marks.values.toList.sorted
marks = List(_marks.values).sorted
closeSpansAt = Set()
ci = mi = 0
for c in plainSource
if ci in closeSpansAt
sb.append('')
closeSpansAt.remove(ci)
if mi < marks.count
mark = marks[mi]
if mark.charNum == ci
sb.append('')
# use next statement in place of above for debugging:
# sb.append('')
closeSpansAt.add(mark.charNum + mark.length)
while mi < marks.count and marks[mi].charNum <= ci
mi += 1
branch c
on c'\t', sb.append(' ')
on c'<', sb.append('<')
on c'>', sb.append('>')
on c'&', sb.append('&')
on c'"', sb.append('"')
else, sb.append(c)
if c <> c'\r', ci += 1
class Mark implements IComparable
cue init(charNum as int, length as int, kind as String, text as String)
require
charNum >= 0
length > 0
kind <> ''
body
base.init
_charNum, _length, _kind, _text = charNum, length, kind, text
get charNum from var as int
get length from var as int
get kind from var as String
get text from var as String
def toString as String is override
return '[.getType.name]([.charNum], [.length], [.kind], [.text])'
def compareTo(other as Mark) as int
diff = .charNum - other.charNum
if diff == 0
diff = .length - other.length
if diff == 0
diff = .kind.compareTo(other.kind)
return diff
class Container is partial
def highlight(marks as Marks)
h = Highlighter(marks)
for decl as dynamic in .declsInOrder
h.dispatch(decl)
class Highlighter inherits Visitor
cue init(marks as Marks)
base.init
_marks = marks
get methodName as String is override
return 'highlight'
get marks from var as Marks
def mark(token as IToken, kind as String)
.marks.add(token, kind)
def highlight(ns as NameSpace)
.mark(ns.token, 'tdn')
.dispatch(ns.declsInOrder)
def highlight(box as Box)
.mark(box.idToken, 'tdn')
.dispatch(box.declsInOrder)
def highlight(enumDecl as EnumDecl)
.mark(enumDecl.idToken, 'tdn')
def highlight(member as BoxMember)
.mark(member.token, 'kw-md')
.mark(member.idToken, 'mdn')
def highlight(method as AbstractMethod)
.mark(method.token, 'kw-md')
.mark(method.idToken, 'mdn')
.dispatch(method.statements)
def highlight(stmt as Stmt)
# nothing for now
pass