summaryrefslogtreecommitdiffstats
path: root/Mac/Tools/IDE/PyFontify.py
blob: d60456c67696540803d94ec1199fdc5447d5922e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""Module to analyze Python source code; for syntax coloring tools.

Interface:
	tags = fontify(pytext, searchfrom, searchto)

The 'pytext' argument is a string containing Python source code.
The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext. 
The returned value is a list of tuples, formatted like this:
	[('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ]
The tuple contents are always like this:
	(tag, startindex, endindex, sublist)
tag is one of 'keyword', 'string', 'comment' or 'identifier'
sublist is not used, hence always None. 
"""

# Based on FontText.py by Mitchell S. Chapman,
# which was modified by Zachary Roadhouse,
# then un-Tk'd by Just van Rossum.
# Many thanks for regular expression debugging & authoring are due to:
#	Tim (the-incredib-ly y'rs) Peters and Cristian Tismer
# So, who owns the copyright? ;-) How about this:
# Copyright 1996-1997: 
#	Mitchell S. Chapman,
#	Zachary Roadhouse,
#	Tim Peters,
#	Just van Rossum

__version__ = "0.3.2"

import string, regex

# First a little helper, since I don't like to repeat things. (Tismer speaking)
import string
def replace(where, what, with):
	return string.join(string.split(where, what), with)

# This list of keywords is taken from ref/node13.html of the
# Python 1.3 HTML documentation. ("access" is intentionally omitted.)
keywordsList = [
	"assert", "exec",
	"del", "from", "lambda", "return",
	"and", "elif", "global", "not", "try",
	"break", "else", "if", "or", "while",
	"class", "except", "import", "pass",
	"continue", "finally", "in", "print",
	"def", "for", "is", "raise"]

# Build up a regular expression which will match anything
# interesting, including multi-line triple-quoted strings.
commentPat = "#.*"

pat = "q[^\q\n]*\(\\\\[\000-\377][^\q\n]*\)*q"
quotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')

# Way to go, Tim!
pat = """
	qqq
	[^\\q]*
	\(
		\(	\\\\[\000-\377]
		\|	q
			\(	\\\\[\000-\377]
			\|	[^\\q]
			\|	q
				\(	\\\\[\000-\377]
				\|	[^\\q]
				\)
			\)
		\)
		[^\\q]*
	\)*
	qqq
"""
pat = string.join(string.split(pat), '')	# get rid of whitespace
tripleQuotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')

# Build up a regular expression which matches all and only
# Python keywords. This will let us skip the uninteresting
# identifier references.
# nonKeyPat identifies characters which may legally precede
# a keyword pattern.
nonKeyPat = "\(^\|[^a-zA-Z0-9_.\"']\)"

keyPat = nonKeyPat + "\("
for keyword in keywordsList:
	keyPat = keyPat + keyword + "\|"
keyPat = keyPat[:-2] + "\)" + nonKeyPat

matchPat = keyPat + "\|" + commentPat + "\|" + tripleQuotePat + "\|" + quotePat
matchRE = regex.compile(matchPat)

idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*"	# Ident w. leading whitespace.
idRE = regex.compile(idKeyPat)


def fontify(pytext, searchfrom = 0, searchto = None):
	if searchto is None:
		searchto = len(pytext)
	# Cache a few attributes for quicker reference.
	search = matchRE.search
	group = matchRE.group
	idSearch = idRE.search
	idGroup = idRE.group
	
	tags = []
	tags_append = tags.append
	commentTag = 'comment'
	stringTag = 'string'
	keywordTag = 'keyword'
	identifierTag = 'identifier'
	
	start = 0
	end = searchfrom
	while 1:
		start = search(pytext, end)
		if start < 0 or start >= searchto:
			break	# EXIT LOOP
		match = group(0)
		end = start + len(match)
		c = match[0]
		if c not in "#'\"":
			# Must have matched a keyword.
			if start <> searchfrom:
				# there's still a redundant char before and after it, strip!
				match = match[1:-1]
				start = start + 1
			else:
				# this is the first keyword in the text.
				# Only a space at the end.
				match = match[:-1]
			end = end - 1
			tags_append((keywordTag, start, end, None))
			# If this was a defining keyword, look ahead to the
			# following identifier.
			if match in ["def", "class"]:
				start = idSearch(pytext, end)
				if start == end:
					match = idGroup(0)
					end = start + len(match)
					tags_append((identifierTag, start, end, None))
		elif c == "#":
			tags_append((commentTag, start, end, None))
		else:
			tags_append((stringTag, start, end, None))
	return tags


def test(path):
	f = open(path)
	text = f.read()
	f.close()
	tags = fontify(text)
	for tag, start, end, sublist in tags:
		print tag, `text[start:end]`