pmacs3/mode/scala.py

424 lines
14 KiB
Python

from tab import StackTabber2
from mode import Fundamental
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule
from mode.sh import ShGrammar
from mode.xml import XMLGrammar
from mode.pipe import Pipe
from method.shell import Interact
from method import Method, arg
import default
import urllib2
import os
import re
from subprocess import Popen, PIPE, STDOUT
chr1 = '[a-zA-Z_]'
chr2 = '[a-zA-Z_0-9]'
word = chr1 + chr2 + '*'
class NestedCommentGrammar(Grammar): pass
NestedCommentGrammar.rules = [
RegionRule('comment', r'/\*', NestedCommentGrammar, r'\*/'),
PatternRule('data', r'(?:[^\*]|\*(?!/))+'),
]
class StringGrammar(Grammar):
rules = [
PatternRule('escaped', r"\\u[0-9A-Fa-f]{4}|\\[0-7]{1,3}|\\[btnfr\"'\\]"),
PatternRule('data', r'[^\\"]+'),
]
class SubTypeGrammar(Grammar): pass
SubTypeGrammar.rules = [
RegionRule('sub', r'\[', SubTypeGrammar, r'\]'),
PatternRule('scala.type', '[a-zA-Z_][a-zA-Z0-9_.#]*'),
PatternRule('spaces', ' +'),
PatternRule('delimiter', r'(?:;|=>|{|}|\(|\)|,|\.|<(?![a-zA-Z_])|>|:|/|\+|-|\*|=|#)'),
PatternRule('scala.annotation', '@[a-zA-Z_][a-zA-Z0-9_.]*'),
RegionRule('scala.string', '"', StringGrammar, '"'),
]
class ScalaGrammar(Grammar):
rules = [
PatternRule('scala.comment', '//.*$'),
RegionRule('scala.comment', r'/\*', NestedCommentGrammar, r'\*/'),
RegionRule('scala.script', r'#!.+$', ShGrammar, r'!#'),
# determine types based on context
PatternMatchRule('x', r'(?<=[a-zA-Z0-9_ ])(:)([a-zA-Z0-9_]+)',
'delimiter', 'scala.type'),
PatternMatchRule('x', r'(?<=[a-zA-Z0-9_ ])(:)( +)([a-zA-Z0-9_]+)',
'delimiter', 'spaces', 'scala.type'),
PatternMatchRule('x', r'(?<=[^a-zA-Z0-9_])(new|extends|with)( +)([^0-9:\[\( ][^:\[\( ]*)',
'scala.reserved', 'spaces', 'scala.type'),
# class, object, and trait
PatternRule('scala.class', '(?<=(?<![a-zA-Z0-9_])class )[^0-9:\[\](){} ][^:\[(){} ]*'),
PatternRule('scala.object', '(?<=(?<![a-zA-Z0-9_])object )[^0-9:\[\](){} ][^:\[\](){} ]*'),
PatternRule('scala.trait', '(?<=(?<![a-zA-Z0-9_])trait )[^0-9:\[\](){} ][^:\[\]{}() ]*'),
# method names
PatternRule('scala.def', '(?<=(?<![a-zA-Z0-9_])def )[^0-9:\[\](){} ][^:\[\]{}() ]*'),
# package names
PatternRule('scala.package', '(?<=(?<![a-zA-Z0-9_])package )[a-zA-Z0-9_.]+'),
# used for type param lists and type parameterization
RegionRule('sub', r'\[', SubTypeGrammar, r'\]'),
# match various scala delimiters and operators
PatternRule('delimiter', r'(?:;|=>|{|}|\(|\)|,|\.|<(?![a-zA-Z_])|>|:|/|\+|-|\*|=|#)'),
# semi-hack to support XML
RegionRule('scala.inline', r'(?:^| )(?=<[a-zA-Z_])', XMLGrammar, '^[ \t]*$'),
PatternRule('spaces', r'(?:\t| )+'),
PatternRule('eol', r'\n'),
# these are some constants that show up a lot
PatternRule('scala.pseudo', '(?:true|null|false)(?!%s)' % word),
PatternRule('scala.reserved', '(?:yield|with|while|var|val|type|true|try|trait|throw|this|super|sealed|return|protected|private|package|override|object|null|new|match|macro|lazy|import|implicit|if|forSome|for|finally|final|false|extends|else|do|def|class|catch|case object|case class|case|abstract)(?!%s)' % word),
PatternRule('scala.float', r'-?[0-9]+\.[0-9]*[Ff]?'), # FIXME
PatternRule('scala.integer', '-?(?:0|[1-9])[0-9]*[Ll]?'),
PatternRule('scala.integer', '-?0x[0-9A-Fa-f]+[Ll]?'),
PatternRule('scala.integer', '-?0[0-7]+[Ll]?'),
PatternRule('scala.char', r"'(?:[^'\\]|\\u[0-9A-Fa-f]{4}|\\[0-7]{1,3}|\\[btnfr\"'\\])'"),
RegionRule('scala.string', '"""', Grammar, '"""+'),
RegionRule('scala.string', '"', StringGrammar, '"'),
PatternRule('scala.symbol', "'[a-zA-Z_][a-zA-Z0-9_]*"),
PatternRule('scala.annotation', '@[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.type', '[A-Z][a-zA-Z0-9_]*'),
PatternRule('scala.bareword', '[a-zA-Z_][a-zA-Z0-9_]*'),
]
class ScalaTabber(StackTabber2):
open_tokens = {'delimiter': {'{': '}', '(': ')'},
'sub.start': {'[': ']'}}
close_tokens = {'delimiter': {'}': '{', ')': '('},
'sub.end': {']': '['}}
control_tokens = {'scala.reserved': set(['if', 'else', 'while', 'do', 'for'])}
case_tokens = {'scala.reserved': set(['case'])}
end_at_eof = True
start_free_tokens = {'string.start': 'string.end'}
end_free_tokens = {'string.end': 'string.start'}
is_ignored_tokens = set(['spaces', 'eol', 'comment', 'comment.start',
'comment.data', 'comment.null', 'comment.end'])
is_indent_tokens = set(['spaces'])
fixed_indent = True
def _is_base(self, y):
# the first line is always safe
if y == 0: return True
# if there are no tokens we don't really have any info
tokens = self._get_tokens(y)
if not tokens: return False
# if it looks like a top-level class or object, then say ok
if tokens[0].fqmatchs('scala.reserved', ('class', 'object')):
return True
# the default is to assume no
return False
class ScalaStart(Interact):
args = []
modename = 'scalapipe'
reuse = True
def _execute(self, w, **vargs):
Interact._execute(self, w, bname='*Scala*', cmd='scala')
class ScalaDecompile(Method):
args = [arg('classname', t='string', p='Class: ', h='The class to decompile')]
ins_re = re.compile(r'^(\d+):')
def _abbrev(self, s):
s = s.replace('/', '.')
s = s.replace('java.lang.', '')
s = s.replace('scala.', '')
if self.pkg is not None:
s = s.replace(self.pkg, '')
return s
pkg = None
obj_re = re.compile(r'^.+ (?:class|interface) ([^ ]+) ')
def _parse_obj(self, line):
s = self._abbrev(line[:-1])
m = self.obj_re.match(line)
obj = m.group(1)
toks = obj.split('.')
if len(toks) > 1:
self.pkg = '.'.join(toks[:-1]) + '.'
else:
self.pkg = None
return self._abbrev(line[:-1])
def _parse_method(self, line):
return self._abbrev(line[:-1]).replace("(", " (")
prims = {
'B': 'byte',
'C': 'char',
'D': 'double',
'F': 'float',
'I': 'int',
'J': 'long',
'S': 'short',
'V': 'void',
'Z': 'bool',
}
def _parse_args(self, line):
args = []
i = 0
arr = ""
while i < len(line):
if line[i] in self.prims:
args.append(self.prims[line[i]] + arr)
arr = ""
elif line[i] == "[":
arr += "[]"
elif line[i] == "L":
j = i + 1
while line[j] != ';': j += 1
args.append(self._abbrev(line[i + 1:j]) + arr)
arr = ""
i = j
else:
raise Exception("huh? saw %r (%r)" % (line[i:], line))
i += 1
return args
typs = {
'InterfaceMethod': 'iface ',
'Method': 'method',
}
field_re = re.compile('^Field\s+([^:]+):(.+)$')
long_re = re.compile('^long\s+(\d+)[lL]$')
double_re = re.compile('^double\s+([\d.]+)[dD]$')
sig_re = re.compile('^(\w+)\s+([^:]+):\(([^)]*)\)(.+)$')
def _parse_sig(self, line):
if line.startswith('class'):
_, name = line.split()
return "class " + self._abbrev(name)
elif line.startswith('String'):
return 'string "%s"' % line[7:]
elif line.startswith('Field'):
m = self.field_re.match(line)
if not m: raise Exception("failed to match (1) %r" % line)
name = self._abbrev(m.group(1))
result = self._parse_args(m.group(2))[0]
#return "field %s -> %s" % (name, result)
return "field %s %s" % (result, name)
elif line.startswith('int'):
return 'int %s' % line[4:]
elif line.startswith('long'):
return 'long %s' % line[5:]
elif line.startswith('float'):
return 'double %s' % line[6:]
elif line.startswith('double'):
return 'double %s' % line[7:]
else:
m = self.sig_re.match(line)
if not m: raise Exception("failed to match (3) %r" % line)
typ = self.typs[m.group(1)]
name = self._abbrev(m.group(2))
args = self._parse_args(m.group(3))
result = self._parse_args(m.group(4))[0]
#return "%s %s (%s) -> %s" % (typ, name, ', '.join(args), result)
return "%s %s %s (%s)" % (typ, result, name, ', '.join(args))
def _parse_instruction(self, line):
if '//' in line:
real, x = line.split('//')
sig = self._parse_sig(x.strip())
else:
real, sig = line, None
toks = real.split()
n = toks[0][:-1]
ins = toks[1]
rest = [t[:-1] for t in toks[2:]]
args = " ".join(rest)
if sig:
#return "%4s %-22s -- %s" % (n, ins + " " + args, sig)
return "%4s %-22s --%s" % (n, ins + " " + args, sig)
else:
return "%4s %-22s" % (n, ins + " " + args)
def _execute(self, w, **vargs):
clsname = vargs['classname']
cp = '.:target/scala-2.9.1.final/classes' #FIXME
argv = ['javap', '-classpath', cp, '-c', '-private', clsname]
p = Popen(argv, stdout=PIPE, stderr=STDOUT)
lines = p.stdout.readlines()
obj = None
methods = []
curr = None
instructions = []
for i, line in enumerate(lines):
line = line.strip()
if i == 0:
continue
elif i == 1:
obj = self._parse_obj(line)
elif line == "Code:":
continue
elif line == "" or line == "}":
if curr:
methods.append((curr, instructions))
curr = None
instructions = []
elif self.ins_re.match(line):
instructions.append(self._parse_instruction(line))
else:
import sys
curr = self._parse_method(line)
outlines = []
if self.pkg:
outlines.append('package ' + self.pkg[:-1])
outlines.append('')
outlines.append(str(obj) + " {")
for method, instructions in methods:
if instructions:
outlines.append(" " + method + " {")
for ins in instructions:
outlines.append(" " + ins)
outlines.append(" }")
else:
outlines.append(" " + method + " {}")
outlines.append("}")
output = "\n".join(outlines)
name = "*Javap:%s*" % clsname
w.application.data_buffer(name, output, modename='javap', switch_to=True)
class ScalaSetClasspath(Method):
'''Set Scala's classpath'''
args = [arg("lib", dt='path', p="Lib: ", dv=lambda w: '.')]
def _execute(self, w, **vargs):
w.application.config['scala.cp'] = vargs['lib'].split(':')
class ScalaAddClasspath(ScalaSetClasspath):
'''Add path(s) to Scala's classpath'''
def _execute(self, w, **vargs):
w.application.config['scala.cp'].extend(vargs['lib'].split(':'))
class ScalaShowClasspath(ScalaSetClasspath):
'''Display Scala's classpath'''
def _execute(self, w, **vargs):
w.set_error(w.application.config.get('scala.cp'))
w.application.config['scala.cp'].extend(vargs['lib'].split(':'))
# white is for delimiters, operators, numbers
default = ('default', 'default')
# magenta is for keywords/builtins
lo_magenta = ('magenta202', 'default')
hi_magenta = ('magenta414', 'default')
# red is for comments
lo_red = ('red300', 'default')
hi_red = ('red511', 'default')
# orange is for macro definitions, headers and constants
hi_orange = ('yellow531', 'default')
lo_orange = ('yellow520', 'default')
# yellow is for parts of macros
hi_yellow = ('yellow551', 'default')
lo_yellow = ('yellow330', 'default')
# green is for strings and characters
lo_green = ('green030', 'default')
hi_green = ('green050', 'default')
# cyan is for types
lo_cyan = ('cyan033', 'default')
hi_cyan = ('cyan155', 'default')
# blue is definitions, functions and some macros
lo_blue = ('blue113', 'default')
hi_blue = ('blue225', 'default')
class Scala(Fundamental):
name = 'Scala'
extensions = ['.scala']
tabwidth = 2
tabbercls = ScalaTabber
grammar = ScalaGrammar
commentc = '//'
actions = [ScalaStart, ScalaDecompile, ScalaSetClasspath,
ScalaAddClasspath, ScalaShowClasspath]
opentokens = ('delimiter', 'sub.start', 'sub.sub.start', 'sub.sub.sub.start')
opentags = {'(': ')', '[': ']', '{': '}'}
closetokens = ('delimiter', 'sub.end', 'sub.sub.end', 'sub.sub.sub.end')
closetags = {')': '(', ']': '[', '}': '{'}
config = {
'scala.api': 'http://www.scala-lang.org/api/current/allclasses.html',
'scala.api-base': 'http://www.scala-lang.org/api/current',
'scala.type-abbrev': True,
}
colors = {
'scala.script.start': hi_red,
'scala.script.end': hi_red,
'scala.annotation': lo_orange,
'scala.reserved': hi_cyan,
'scala.pseudo': hi_magenta,
'scala.integer': default,
'scala.float': default,
'scala.bareword': default,
'scala.symbol': hi_orange,
'scala.package': hi_orange,
'scala.class': hi_yellow,
'scala.object': hi_yellow,
'scala.trait': hi_yellow,
'scala.def': hi_blue,
'scala.type': hi_magenta,
}
_bindings = {
#'scala-get-type': ('M-,',),
#'scala-goto-definition': ('C-c ,',),
'close-paren': (')',),
'close-brace': ('}',),
'close-bracket': (']',),
}
class ScalaPipe(Pipe):
name = 'scalapipe'
grammar = ScalaGrammar
def install(*args):
Scala.install(*args)
ScalaPipe.install(*args)