# coding=utf-8
from __future__ import absolute_import, division, print_function, unicode_literals

import sys
import os
import logging
import ntpath
import datetime as dt
import re
import tokenize
import keyword
from collections import OrderedDict

import isort

import antlr4

from .VisualFoxpro9Visitor import VisualFoxpro9Visitor

from . import vfpfunc
from .vfpfunc import DB

from .function_abbreviations import expander as function_expander

if sys.version_info < (3,):
    str=unicode
    CHR = chr
    def chr(x):
        return CHR(x).decode('ascii')

class CodeStr(str):
    def __repr__(self):
        return str(self)

    def __add__(self, val):
        return CodeStr('{} + {}'.format(self, repr(val)))

    def __radd__(self, val):
        return CodeStr('{} + {}'.format(repr(val), self))

    def __sub__(self, val):
        return CodeStr('{} - {}'.format(self, repr(val)))

    def __rsub__(self, val):
        return CodeStr('{} - {}'.format(repr(val), self))

    def __mul__(self, val):
        return CodeStr('{} * {}'.format(self, repr(val)))

    def __rmul__(self, val):
        return CodeStr('{} * {}'.format(repr(val), self))
PASS = CodeStr('pass')

class RedirectedBuiltin(object):
    def __init__(self, func):
        self.func = func
        self.name = func.__name__

    def __call__(self, *args, **kwargs):
        try:
            return self.func(*args, **kwargs)
        except:
            return make_func_code(self.name, *args, **kwargs)

for func in (chr, int, str, float):
    globals()[func.__name__] = RedirectedBuiltin(func)

real_isinstance = isinstance
def isinstance(obj, istype):
    if not real_isinstance(istype, tuple):
        istype = (istype,)
    istype = tuple(x.func if real_isinstance(x, RedirectedBuiltin) else x for x in istype)
    return real_isinstance(obj, istype)

class OperatorExpr(object):
    precedence = -1
    operator = '?'

    def __init__(self, *args):
        self.args = args

    def wrap_arg(self, arg):
        if isinstance(arg, OperatorExpr) and arg.precedence < self.precedence:
            return '({})'.format(repr(arg))
        return arg

    def __repr__(self):
        args = [self.wrap_arg(arg) for arg in self.args]
        if len(args) == 1:
            return '{}{}'.format(self.operator, args[0])
        else:
            return '{}{}{}'.format(args[0], self.operator, args[1])

class OrExpr(OperatorExpr):
    precedence = 0
    operator = ' or '

class AndExpr(OperatorExpr):
    precedence = 1
    operator = ' and '

class NotExpr(OperatorExpr):
    precedence = 2
    operator = 'not '

def make_func_code(funcname, *args, **kwargs):
    args = [repr(x) for x in args]
    if not all(valid_identifier(name) for name in kwargs):
        args.append(add_args_to_code('**{}', [kwargs]))
    else:
        args += ['{}={}'.format(key, repr(kwargs[key])) for key in kwargs]
    return CodeStr('{}({})'.format(funcname, ', '.join(args)))

def string_type(val):
    return isinstance(val, str) and not isinstance(val, CodeStr)

def create_string(val):
    try:
        return str(val)
    except UnicodeEncodeError: #can this happen?
        return val

def add_args_to_code(codestr, args):
    return CodeStr(codestr.format(*[repr(arg) for arg in args]))

def valid_identifier(name):
    return re.match(tokenize.Name + '$', name) and not keyword.iskeyword(name)

class PythonConvertVisitor(VisualFoxpro9Visitor):
    def __init__(self, filename):
        super(PythonConvertVisitor, self).__init__()
        self.filename = filename
        self.filesystem_caseless = True
        self.imports = []
        self.scope = True
        self.withid = ''
        self.class_list = []
        self.function_list = []
        self.skip_extract = False

    def visit(self, ctx):
        if ctx:
            return super(type(self), self).visit(ctx)

    def visit_with_disabled_scope(self, ctx):
        self.scope = False
        retval = self.visit(ctx)
        self.scope = True
        return retval

    def list_visit(self, list_ctx):
        return [self.visit(x) for x in list_ctx]

    def getCtxText(self, ctx):
        start, stop = ctx.getSourceInterval()
        tokens = ctx.parser._input.tokens[start:stop+1]
        return ''.join(token.text for token in tokens)

    def visitPrg(self, ctx):
        if ctx.classDef():
            self.class_list = [self.visit(classDef.classDefStart())[0] for classDef in ctx.classDef()]
        if ctx.funcDef():
            self.function_list = [self.visit(funcdef.funcDefStart())[0] for funcdef in ctx.funcDef()]

        self.imports = ['from __future__ import division, print_function']
        self.imports.append('from vfp2py import vfpfunc')
        self.imports.append('from vfp2py.vfpfunc import DB, Array, C, F, M, S')
        self.imports.append('from vfp2py.vfpfunc import parameters, lparameters, vfpclass')
        defs = []

        for i, child in enumerate(ctx.children):
            if isinstance(child, ctx.parser.FuncDefContext):
                funcname, decorator, funcbody = self.visit(child)
                if i == 0 and funcname == '_program_main':
                    funcname = CodeStr('MAIN')
                defs += [
                    add_args_to_code('@{}', (decorator,)),
                    add_args_to_code('def {}():', (funcname,)),
                    funcbody
                ]
                if child.lineComment():
                    defs += sum((self.visit(comment) for comment in child.lineComment()), [])
            elif not isinstance(child, antlr4.tree.Tree.TerminalNodeImpl):
                defs += self.visit(child)

        imports = isort.SortImports(file_contents='\n'.join(set(self.imports)), line_length=100000).output.splitlines()
        return  [CodeStr(imp) for imp in imports] + defs

    def visitLine(self, ctx):
        try:
            retval = self.visit(ctx.cmd() or ctx.controlStmt() or ctx.lineComment())
            if retval is None:
                if ctx.MACROLINE():
                    retval = make_func_code('vfpfunc.macro_eval', create_string(ctx.MACROLINE().getText()))
                else:
                    raise Exception('just to jump to except block')
        except Exception as err:
            logging.getLogger(__name__).exception(str(err))
            lines = self.getCtxText(ctx)
            print(lines)
            retval = [CodeStr('#FIX ME: {}'.format(line)) for line in lines.split('\n') if line]
        return retval if isinstance(retval, list) else [retval]

    def visitLineComment(self, ctx):
        fixer = re.compile('^\s*(&&|\*!\*|\**)(.*[^*; ]\s*|.*[^* ];\s*|;\s*)?(\**)\s*;*$')
        def repl(match):
            groups = match.groups()
            if not any(groups):
                return ''
            start = '*' if not groups[0] or groups[0] in ('&&', '*!*') else groups[0]
            middle = groups[1] or ''
            end = groups[2] or ''
            if len(start) == 1 and not end:
                middle = middle.strip()
            return ('#' * len(start) + middle + '#' * len(end)).strip()
        comments = [comment.strip() for comment in self.getCtxText(ctx).splitlines()]
        return [CodeStr(fixer.sub(repl, comment)) for comment in comments]

    def visitLines(self, ctx):
        retval = sum((self.visit(line) for line in ctx.line()), [])
        def badline(line):
            return line.startswith('#') or not line if hasattr(line, 'startswith') else not line
        if not retval or all(badline(l) for l in retval):
            retval.append(PASS)
        return retval

    def visitNongreedyLines(self, ctx):
        return self.visitLines(ctx)

    def modify_superclass(self, supername):
        if hasattr(vfpfunc, supername):
            supername = add_args_to_code('{}.{}', (CodeStr('vfpfunc'), supername))
        elif supername in self.class_list:
            supername = add_args_to_code('{}Type()', (supername,))
        else:
            supername = add_args_to_code('C[{}]', (str(supername),))
        return supername

    def visitClassDef(self, ctx):
        assignments = []
        subclasses = {}

        funcdefs = [x.funcDef() for x in ctx.classProperty() if x.funcDef()]
        classassigns = [self.visitClassAssign(stmt.cmd()) for stmt in ctx.classProperty() if isinstance(stmt.cmd(), ctx.parser.AssignContext)]
        for stmt in ctx.classProperty():
            stmt = stmt.lineComment() or stmt.cmd()
            if isinstance(stmt, ctx.parser.LineCommentContext):
                assignments += self.visit(stmt)
            elif isinstance(stmt, ctx.parser.AssignContext):
                assignments += [CodeStr('self.' + ident + value) for (ident, value) in self.visitClassAssign(stmt) if '.' not in ident]
            elif isinstance(stmt, ctx.parser.AddObjectContext):
                name, obj = self.visit(stmt)
                for assignment in classassigns:
                    for (ident, value) in assignment:
                        if '.' in ident:
                            parent, ident = ident.split('.', 1)
                            if parent == name:
                                obj['args'][ident] = CodeStr(value.replace(' = ', '', 1))
                obj['functions'] = {}
                for funcdef in funcdefs:
                    funcname, decorator, funcbody = self.visit(funcdef)
                    if '.' in funcname:
                        func_parent, funcname = funcname.rsplit('.', 1)
                        if func_parent == name:
                            obj['functions'][funcname] = [decorator, funcbody]
                obj['args']['parent'] = CodeStr('self')
                obj['args']['name'] = name
                if obj['functions']:
                    subclass = 'SubClass' + name.title()
                    subclasses[subclass] = {key: obj[key] for key in ('parent_type', 'functions')}
                    obj['parent_type'] = 'self.' + str(subclass)
                    self.class_list.append(obj['parent_type'])
                assignments.append(add_args_to_code('self.{} = {}', [CodeStr(name), self.func_call('createobject', obj['parent_type'], **obj['args'])]))


        funcs = OrderedDict((
            ('_assign', [None, None, float('inf')]),
        ))
        for funcdef in funcdefs:
            funcname, decorator, funcbody = self.visit(funcdef)
            if '.' not in funcname:
                funcs[funcname] = [decorator, funcbody, funcdef.start.line]
            assignments += sum((self.visit(comment) for comment in funcdef.lineComment()), [])

        classname, supername = self.visit(ctx.classDefStart())

        funcbody = [CodeStr('BaseClass._assign(self)')] + assignments
        self.modify_func_body(funcbody)
        funcs['_assign'][1] = funcbody
        funcs['_assign'][0] = make_func_code('lparameters')

        retval = [
            add_args_to_code('BaseClass = {}', (supername,)),
            CodeStr('class {}(BaseClass):'.format(classname)),
        ]
        if funcs:
            for name in subclasses:
                subclass = subclasses[name]
                supername = self.modify_superclass(CodeStr(subclass['parent_type']))
                subclass_code = [CodeStr('class {}({}):'.format(name, supername))]
                for funcname in subclass['functions']:
                    decorator, funcbody = subclass['functions'][funcname]
                    subclass_code.append([
                        add_args_to_code('@{}', (decorator,)),
                        add_args_to_code('def {}(self):', (CodeStr(funcname),)),
                        funcbody,
                    ])
                retval.append(subclass_code)
            for funcname in funcs:
                decorator, funcbody, line_number = funcs[funcname]
                retval.append([
                    add_args_to_code('@{}', (decorator,)),
                    add_args_to_code('def {}(self):', (CodeStr(funcname),)),
                    funcbody,
                ])
        else:
            retval.append([PASS])
        retval.append(add_args_to_code('return {}', (classname,)))
        retval = [
            CodeStr('@vfpclass'),
            add_args_to_code('def {}():', (classname,)),
            retval,
        ]

        return retval + sum((self.visit(comment) for comment in ctx.lineComment()), [])

    def visitClassDefStart(self, ctx):
        names = [self.visit(ctx.identifier())] + [self.visit(ctx.asTypeOf())[0]]
        names = [CodeStr(name.title()) for name in names]
        if len(names) < 2:
            names.append('Custom')
        classname, supername = names
        if hasattr(vfpfunc, classname):
            raise Exception(str(classname) + ' is a reserved classname')
        supername = self.modify_superclass(supername)
        return classname, supername

    def visitClassAssign(self, assign):
        #FIXME - come up with a less hacky way to make this work
        args1 = self.visit_with_disabled_scope(assign)
        args2 = self.visit(assign)
        args = []
        for arg1, arg2 in zip(args1, args2):
            ident = arg1[:arg1.find(' = ')]
            value = arg2[arg2.find(' = '):]
            args.append((ident, value))
        return args

    def visitAddObject(self, ctx):
        name = str(self.visit_with_disabled_scope(ctx.identifier()))
        keywords = [self.visit_with_disabled_scope(idAttr) for idAttr in ctx.idAttr()]
        kwargs = {key: self.visit(expr) for key, expr in zip(keywords, ctx.expr())}
        objtype = create_string(self.visit_with_disabled_scope(ctx.asType())).title()
        return name, {'parent_type': objtype, 'args': kwargs}

    def visitFuncDefStart(self, ctx):
        params = self.visit_with_disabled_scope(ctx.parameters()) or []
        return self.visit(ctx.idAttr2()), params

    def visitParameter(self, ctx):
        return self.visit(ctx.idAttr())

    def visitParameters(self, ctx):
        return self.list_visit(ctx.parameter())

    def modify_func_body(self, body):
        while len(body) > 0 and (not body[-1] or (isinstance(body[-1], CodeStr) and (body[-1] == 'return'))):
            body.pop()
        if len(body) == 0:
            body.append(PASS)
        while PASS in body:
            body.pop(body.index(PASS))
        if not body:
            body.append(PASS)

    def visitFuncDef(self, ctx):
        name, parameters = self.visit(ctx.funcDefStart())
        if parameters:
            parameter_type = 'l'
        else:
            try:
                parameter_line = next(line for line in ctx.lines().line() if not line.lineComment())
                parameter_cmd = parameter_line.cmd()
                parameter_type = parameter_cmd.PARAMETER().symbol.text.lower()[0]
                parameters = [self.visit_with_disabled_scope(p)[0] for p in parameter_cmd.declarationItem()]
                lines = ctx.lines()
                children = [c for c in lines.children if c is not parameter_line]
                while lines.children:
                    lines.removeLastChild()
                for child in children:
                    lines.addChild(child)
            except (StopIteration, AttributeError):
                parameters = []
                parameter_type = 'l'
        if parameter_type != 'l':
            parameter_type = ''
        parameter_type += 'parameters'
        parameters = [str(p) for p in parameters]
        decorator = make_func_code(parameter_type, *parameters)
        global FUNCNAME
        FUNCNAME = name
#        body = self.modify_func_body(self.visit(ctx.lines()))
        body = self.visit(ctx.lines())
        return name, decorator, body

    def visitPrintStmt(self, ctx):
        kwargs = {}
        if len([child for child in ctx.children if child.getText() == '?']) > 1:
            kwargs['end'] = ''
        if ctx.DEBUGOUT():
            self.imports.append('import sys')
            kwargs['file'] = CodeStr('sys.stderr')
        return [make_func_code('print', *(self.visit(ctx.args()) or []), **kwargs)]

    def visitAtPos(self, ctx):
        if ctx.SAY() and len(ctx.SAY()) > 1:
            raise Exception('Invalid command')
        if ctx.sayExpr:
            func = make_func_code('print', self.visit(ctx.sayExpr))
        else:
            func = make_func_code('print')
        return add_args_to_code('{} # {}', [func, CodeStr(self.getCtxText(ctx))])

    def visitIfStart(self, ctx):
        return self.visit(ctx.expr())

    def visitIfStmt(self, ctx):
        evaluation = self.visit(ctx.ifStart())

        ifBlock = self.visit(ctx.ifBody)
        retval = [CodeStr('if {}:'.format(evaluation)), ifBlock]

        if ctx.elseBody:
            elseBlock = self.visit(ctx.elseBody)
            retval += [CodeStr('else:'), elseBlock]

        return retval

    def visitCaseStmt(self, ctx):
        retval = self.list_visit(ctx.lineComment())

        items = self.list_visit(ctx.singleCase())

        if not items:
            retval += [CodeStr('if True:'), [PASS]]
        else:
            expr, lines = items[0]
            retval += [CodeStr('if {}:'.format(expr)), lines]
            for expr, lines in items[1:]:
                retval += [CodeStr('elif {}:'.format(expr)), lines]

        if ctx.otherwise():
            retval += [CodeStr('else:'), self.visit(ctx.otherwise())]
        return retval

    def visitSingleCase(self, ctx):
        return self.visit(ctx.expr()), self.visit(ctx.nongreedyLines())

    def visitOtherwise(self, ctx):
        return self.visit(ctx.lines())

    def visitForStart(self, ctx):
        loopvar = self.visit(ctx.idAttr())
        if ctx.EACH():
            iterator = self.visit(ctx.expr(0))
        else:
            args = [int(self.visit(ctx.loopStart)), int(self.visit(ctx.loopStop)) + 1]
            if ctx.loopStep:
                args.append(int(self.visit(ctx.loopStep)))
            iterator = make_func_code('range', *args)
        return add_args_to_code('for {} in {}:', (loopvar, iterator))

    def visitForStmt(self, ctx):
        return [self.visit(ctx.forStart()), self.visit(ctx.lines())]

    def visitWhileStart(self, ctx):
        return CodeStr('while {}:'.format(self.visit(ctx.expr())))

    def visitWhileStmt(self, ctx):
        return [self.visit(ctx.whileStart()), self.visit(ctx.lines())]

    def visitWithStmt(self, ctx):
        self.withid = self.visit(ctx.idAttr())
        lines = self.visit(ctx.lines())
        self.withid = ''
        return lines

    def visitScanStmt(self, ctx):
        lines = self.visit(ctx.lines())
        kwargs = OrderedDict()
        if ctx.FOR():
            kwargs['condition'] = add_args_to_code('lambda: {}', [self.visit(ctx.expr())])
        kwargs['scope'] = self.visit(ctx.scopeClause()) or ('rest',)
        func = make_func_code('DB.scanner', **kwargs)
        return [add_args_to_code('for _ in {}:', [func]), lines]

    def visitTryStmt(self, ctx):
        try_lines = self.visit(ctx.tryLines)
        finally_lines = self.visit(ctx.finallyLines) or []
        if not ctx.CATCH():
            return try_lines + finally_lines

        try_lines = [CodeStr('try:'), try_lines]

        if ctx.identifier():
            identifier = add_args_to_code('S.{}', (self.visit(ctx.identifier()[0]),))

            catch_lines = [CodeStr('except Exception as err:')]
            catch_lines.append([add_args_to_code('{} = {}', [identifier, make_func_code('vfpfunc.Exception.from_pyexception', CodeStr('err'))])])
        else:
            catch_lines = [CodeStr('except Exception:')]
        if ctx.expr():
            when = self.visit(ctx.expr()[0])
            catch_lines.append([add_args_to_code('if not ({}):', (when,)), [CodeStr('raise')]])
        catch_lines.append(self.visit(ctx.catchLines))

        finally_lines = [CodeStr('finally:'), finally_lines] if finally_lines else []

        return try_lines + catch_lines + finally_lines

    def visitProgramControl(self, ctx):
        action = ctx.PROGRAMCONTROL().symbol.text.lower()
        action = {
            'loop': 'continue',
            'exit': 'break',
            'quit': 'vfpfunc.quit()',
        }.get(action, None)
        return CodeStr(action) if action else None

    def visitDeclaration(self, ctx):
        if ctx.EXTERNAL():
            return
        scope = ctx.SCOPE().getText().lower() if ctx.SCOPE() else None
        if ctx.PARAMETER():
            return
        names = [self.visit_with_disabled_scope(x)[0] for x in ctx.declarationItem()]
        inds = [self.visit(x)[1] for x in ctx.declarationItem()]
        if ctx.ARRAY() or ctx.DIMENSION() or ctx.DECLARE():
            inds = [ind or (1,) for ind in inds]

        if scope in ('hidden', 'protected'):
            names = [add_args_to_code('self.{}', [name]) if valid_identifier(name) else CodeStr('getattr(self, {}'.format(name)) for name in names]

        arrays = [(name, make_func_code('Array', *ind)) for name, ind in zip(names, inds) if ind]

        if scope in ('public', 'private', 'local'):
            func = 'M.add_'  + scope
            kwargs = {str(name): array for name, array in arrays}
            names = [str(name) for name, ind in zip(names, inds) if not ind]
            return make_func_code(func, *names, **kwargs)
        else:
            names = [name for name, ind in zip(names, inds) if not ind]
            return [CodeStr(' = '.join(repr(arg) for arg in (names + [False])))] if names else [] + \
                   [add_args_to_code('{} = {}', (name, array)) for name, array in arrays]

    def visitDeclarationItem(self, ctx):
        return self.visit(ctx.idAttr() or ctx.idAttr2()), self.visit(ctx.arrayIndex())

    def visitAsTypeOf(self, ctx):
        return self.visit(ctx.asType()), self.visit(ctx.specialExpr())

    def visitAsType(self, ctx):
        return self.visit_with_disabled_scope(ctx.datatype().idAttr())

    def visitAssign(self, ctx):
        value = self.visit(ctx.expr())
        args = []
        for var in ctx.idAttr():
            trailer = self.visit(var.trailer()) or []
            if len(trailer) > 0 and isinstance(trailer[-1], list):
                identifier = self.visit(ctx.idAttr()[0].identifier())
                arg = self.createIdAttr(identifier, trailer[:-1])
                args.append('{}[{}]'.format(arg, ','.join(repr(x) for x in trailer[-1])))
            else:
                args.append(self.visit(var))
        if len(args) == 1:
            try:
                if isinstance(value, CodeStr):
                    for op in ('+', '-', '*', '/'):
                        start = str(args[0]) + ' ' + op + ' '
                        if value.startswith(start):
                            return [CodeStr('{} {}= {}'.format(args[0], op, value[len(start):]))]
            except Exception as e:
                pass
        return [CodeStr(' = '.join(args + [repr(value)]))]

    def visitArgs(self, ctx):
        exprs = [ctx.expr()] + [arg.expr() for arg in ctx.argsItem()]
        return [self.visit(expr) if expr else False for expr in exprs]

    def visitSpecialArgs(self, ctx):
        return self.list_visit(ctx.specialExpr())

    def visitComparison(self, ctx):
        symbol_dict = {
            ctx.parser.GREATERTHAN: '>',
            ctx.parser.GTEQ: '>=',
            ctx.parser.LESSTHAN: '<',
            ctx.parser.LTEQ: '<=',
            ctx.parser.NOTEQUALS: '!=',
            ctx.parser.HASH: '!=',
            ctx.parser.EQUALS: '==',
            ctx.parser.DOUBLEEQUALS: '==',
            ctx.parser.DOLLAR: 'in',
            ctx.parser.OR: 'or',
            ctx.parser.OTHEROR: 'or',
            ctx.parser.AND: 'and',
            ctx.parser.OTHERAND: 'and',
        }
        left = self.visit(ctx.expr(0))
        right = self.visit(ctx.expr(1))
        symbol = symbol_dict[ctx.op.type]
        return CodeStr('{} {} {}'.format(repr(left), symbol, repr(right)))

    def visitBooleanOr(self, ctx):
        left = self.visit(ctx.expr(0))
        right = self.visit(ctx.expr(1))
        return OrExpr(left, right)

    def visitBooleanAnd(self, ctx):
        left = self.visit(ctx.expr(0))
        right = self.visit(ctx.expr(1))
        return AndExpr(left, right)

    def visitUnaryNegation(self, ctx):
        return add_args_to_code('{}' if ctx.op.type == ctx.parser.PLUS_SIGN else '-{}', (self.visit(ctx.expr()),))

    def visitBooleanNegation(self, ctx):
        return NotExpr(self.visit(ctx.expr()))

    def func_call(self, funcname, *args, **kwargs):
        funcname = str(funcname)
        funcname = function_expander.get(funcname, funcname)
        if not kwargs and len(args) == 1 and isinstance(args[0], (list, tuple)):
            args = args[0]
        args = list(args)
        funcname = {
            'at_c': 'at',
            'atcc': 'atc',
            'atcline': 'atline',
            'chrtranc': 'chrtran',
            'leftc': 'left',
            'lenc': 'len',
            'likec': 'like',
            'ratc': 'rat',
            'rightc': 'right',
            'select': 'select_function',
            'stuffc': 'stuff',
            'substrc': 'substr',
            'sys': 'vfp_sys',
        }.get(funcname, funcname)
        if funcname == 'dodefault':
            return make_func_code('super(type(self), self).{}'.format(FUNCNAME), *args)
        if funcname in self.function_list:
            return make_func_code(funcname, *args)
        if funcname == 'chr' and len(args) == 1:
            return chr(int(args[0]))
        if funcname == 'val' and len(args) == 1:
            return float(args[0])
        if funcname == 'space' and len(args) == 1:
            args[0] = int(args[0])
            if isinstance(args[0], int) and args[0] > 8:
                args[0] = CodeStr(args[0])
            return args[0] * ' '
        if funcname == 'asc':
            return make_func_code('ord', CodeStr(str(repr(args[0])) + '[0]'))
        if funcname == 'len':
            return make_func_code('len', *args)
        if funcname == 'alen':
            if len(args) == 1:
                return make_func_code('len', *args)
            else:
                args[1] = int(args[1])
                return add_args_to_code('{}.alen({})', args)
        if funcname == 'ascan':
            if len(args) == 3:
                args = [add_args_to_code('{}[{}:]', [args[0], args[2]]), args[1]]
            elif len(args) == 4:
                args = [add_args_to_code('{}[{}:({} + {})]', [args[0], args[2], args[2], args[3]]), args[1]]
            if len(args) == 2:
                return add_args_to_code('{}.index({})', args)
        if funcname == 'ains':
            return make_func_code(add_args_to_code('{}.insert', args[:1]), *([None] + args[1:]))
        if funcname == 'afields':
            localscode = make_func_code('locals')
            arrname = args.pop(0)
            if not args:
                args.append(None)
            replace_string = 'S.'
            if arrname.startswith(replace_string):
                arrname = str(arrname[len(replace_string):]) #FIXME
            else:
                arrname = str(arrname)
            args.append(arrname)
            args.append((localscode, CodeStr('S')))
        if funcname == 'acopy':
            func = add_args_to_code('{}.copy', (args[0],))
            arrname = args[1]
            replace_string = 'S.'
            if arrname.startswith(replace_string):
                arrname = str(arrname[len(replace_string):]) #FIXME
            else:
                arrname = str(arrname)
            return make_func_code(func, arrname, *args[2:])
        if funcname == 'empty':
            return add_args_to_code('(not {} if {} is not None else False)', args + args)
        if funcname == 'occurs':
            return add_args_to_code('{}.count({})', reversed(args))
        if funcname in ('atc',):
            funcname = funcname[:-1]
            args[0] = add_args_to_code('{}.lower()', [args[0]])
            args[1] = add_args_to_code('{}.lower()', [args[1]])
        if funcname in ('at', 'rat'):
            funcname = {
                'at': 'find',
                'rat': 'rfind',
            }[funcname]
            return add_args_to_code('{}.{}({})', [args[1], CodeStr(funcname), args[0]]) + 1
        if funcname == 'replicate' and len(args) == 2:
            args[1] = int(args[1])
            return add_args_to_code('{}', args[:1]) * add_args_to_code('{}', args[1:])
        if funcname in ('date', 'datetime', 'time', 'dtot'):
            self.imports.append('import datetime as dt')
            if len(args) == 0:
                if funcname == 'date':
                    return make_func_code('dt.datetime.now().date')
                elif funcname == 'datetime':
                    return make_func_code('dt.datetime.now')
                elif funcname == 'time':
                    return make_func_code('dt.datetime.now().time().strftime', '%H:%M:%S')
            else:
                if funcname == 'date':
                    return make_func_code('dt.date', *args)
                elif funcname == 'datetime':
                    return make_func_code('dt.datetime', *args)
                elif funcname == 'time':
                    return add_args_to_code('{}[:11]', [make_func_code('dt.datetime.now().time().strftime', '%H:%M:%S.%f')])
            if funcname == 'dtot':
                return make_func_code('dt.datetime.combine', args[0], make_func_code('dt.datetime.min.time'))
        if funcname in ('year', 'month', 'day', 'hour', 'minute', 'sec', 'dow', 'cdow', 'cmonth', 'dmy'):
            self.imports.append('import datetime as dt')
            funcname = {
                'sec': 'second',
                'dow': 'weekday()',
                'cdow': "strftime('%A')",
                'cmonth': "strftime('%B')",
                'dmy': "strftime('%d %B %Y')",
            }.get(funcname, funcname)
            retval = add_args_to_code('{}.{}', [args[0], CodeStr(funcname)])
            if funcname == 'weekday()':
                return make_func_code('vfpfunc.dow_fix', retval, *args[1:])
            return retval
        if funcname in ('dtoc', 'dtos'):
            if len(args) == 1 or args[1] == 1:
                if len(args) < 2:
                    args.append('')
                if args[1] == 1 or funcname == 'dtos':
                    if args[0] == 'dt.datetime.now()':
                        args[1] = '%Y%m%d%H%M%S'
                    elif args[0] == 'dt.datetime.now().date()':
                        args[0] = CodeStr('dt.datetime.now()')
                        args[1] = '%Y%m%d'
                    else:
                        return make_func_code('vfpfunc.dtos', args[0])
                else:
                    return make_func_code('vfpfunc.dtoc', args[0])
                return make_func_code('{}.{}'.format(args[0], 'strftime'), args[1])
        if funcname == 'iif' and len(args) == 3:
            return add_args_to_code('({} if {} else {})', [args[i] for i in (1, 0, 2)])
        if funcname == 'between':
            return add_args_to_code('({} <= {} <= {})', [args[i] for i in (1, 0, 2)])
        if funcname == 'nvl':
            return add_args_to_code('({} if {} is not None else {})', [args[0], args[0], args[1]])
        if funcname == 'evl':
            return add_args_to_code('({} or {})', args)
        if funcname == 'sign':
            return add_args_to_code('1 if {} > 0 else (-1 if {} < 0 else 0)', [args[0], args[0]])
        if funcname in ('alltrim', 'ltrim', 'rtrim', 'lower', 'upper', 'padr', 'padl', 'padc', 'proper'):
            funcname = {
                'alltrim': 'strip',
                'ltrim': 'lstrip',
                'rtrim': 'rstrip',
                'padr': 'ljust',
                'padl': 'rjust',
                'padc': 'center',
                'proper': 'title',
            }.get(funcname, funcname)
            funcname = '{}.{}'.format(repr(args[0]), funcname)
            return make_func_code(funcname, *args[1:])
        if funcname == 'strtran':
            args = args[:6]
            if len(args) > 3:
                args[3:] = [int(arg) for arg in args[3:]]
            if len(args) == 6 and int(args[5]) in (0, 2):
                args.pop()
            if len(args) == 2:
                args.append('')
            str_replace = add_args_to_code('{}.replace', [args[0]])
            if len(args) == 3:
                return make_func_code(str_replace, *args[1:])
            elif len(args) == 4 and args[3] < 2:
                args.pop()
                return make_func_code(str_replace, *args[1:])
            elif len(args) == 5 and args[3] < 2:
                args[3] = args[4]
                args.pop()
                return make_func_code(str_replace, *args[1:])
        if funcname == 'strconv' and len(args) == 2:
            self.imports.append('import base64')
            if args[1] == 13:
                return make_func_code('base64.b64encode', args[0])
            if args[1] == 14:
                return make_func_code('base64.b64decode', args[0])
        if funcname == 'right':
            args[1] = int(args[1])
            return add_args_to_code('{}[-{}:]', args)
        if funcname == 'left' and len(args) == 2:
            args[1] = int(args[1])
            return add_args_to_code('{}[:{}]', args)
        if funcname == 'substr':
            args[1:] = [int(arg) for arg in args[1:]]
            args[1] -= 1
            if len(args) < 3:
                return add_args_to_code('{}[{}:]', args)
            if args[2] == 1:
                return add_args_to_code('{}[{}]', args[:2])
            if args[1] == 0:
               return add_args_to_code('{}[:{}]', (args[0], args[2]))
            args[2] += args[1]
            return add_args_to_code('{}[{}:{}]', args)
        if funcname == 'getenv':
            args.append('')
            args[0] = args[0].upper() if string_type(args[0]) else add_args_to_code('{}.upper()', args[0])
            return make_func_code('os.environ.get', *args)
        if funcname == 'getwordcount':
            if len(args) < 2:
                args.append(CodeStr(''))
            return add_args_to_code('len([w for w in {}.split({}) if w])', args)
        if funcname == 'rand':
            self.imports.append('import random')
            return make_func_code('random.random')
        if funcname in ('ceiling', 'exp', 'log', 'log10', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'pi', 'sqrt', 'dtor', 'rtod'):
            self.imports.append('import math')
            if funcname == 'pi':
                return CodeStr('math.pi')
            funcname = {
                'ceiling': 'ceil',
                'atn2': 'atan2',
                'rtod': 'degrees',
                'dtor': 'radians',
            }.get(funcname, funcname)
            funcname = 'math.' + funcname
            return make_func_code(funcname, *args)
        if funcname in ('bitand', 'bitclear', 'bitlshift', 'bitnot', 'bitor', 'bitrshift', 'bitset', 'bittest', 'bitxor'):
            op = {
                'bitand': '({} & {})',
                'bitclear': '({} & ((1 << {}) ^ 0xffffffff))',
                'bitlshift': '({} << {})',
                'bitnot': '~{}',
                'bitor': '({} | {})',
                'bitrshift': '({} >> {})',
                'bitset': '({} | (1 << {}))',
                'bittest': '(({} & (1 << {})) > 0)',
                'bitxor': '({} ^ {})'
            }
            return add_args_to_code(op[funcname], [int(arg) for arg in args])
        if funcname in ('abs', 'round', 'max', 'min'):
            return make_func_code(funcname, *args)
        if funcname == 'mod':
            return add_args_to_code('({} % {})', args)
        if funcname == 'int':
            return int(args[0])
        if funcname == 'isnull':
            return add_args_to_code('{} == {}', [args[0], None])
        if funcname in ('isalpha', 'islower', 'isdigit', 'isupper'):
            return add_args_to_code('{}[:1].{}()', [args[0], CodeStr(funcname)])
        if funcname == 'inlist':
            return add_args_to_code('({} in {})', [args[0], tuple(args[1:])])
        if funcname == 'parameters':
            return CodeStr('vfpfunc.PARAMETERS')
        if funcname == 'pythonfunctioncall' and len(args) == 3:
            self.imports.append('import {}'.format(args[0]))
            if isinstance(args[2], tuple):
                return make_func_code('{}.{}'.format(args[0], args[1]), *args[2])
            else:
                return make_func_code('{}.{}'.format(args[0], args[1]), add_args_to_code('*{}', (args[2],)))
        if funcname == 'createobject':
            if len(args) > 0 and string_type(args[0]) and args[0].lower() == 'pythontuple':
                return tuple(args[1:])
            elif len(args) > 0 and string_type(args[0]) and args[0].lower() == 'pythonlist':
                if len(args) > 1 and isinstance(args[1], list):
                    return add_args_to_code('{}.data[:]', args[1])
                return []
            elif len(args) > 0 and string_type(args[0]) and args[0].lower() == 'pythondictionary':
                return {}
            elif len(args) > 0 and string_type(args[0]):
                objtype = args[0]
                if not objtype.startswith('self.'):
                    objtype = objtype.title()
                args = args[1:]
                if objtype in self.class_list:
                    return make_func_code(objtype, *args, **kwargs)
                elif hasattr(vfpfunc, objtype):
                    objtype = 'vfpfunc.{}'.format(objtype)
                    return make_func_code(objtype, *args, **kwargs)
                else:
                    return make_func_code('vfpfunc.create_object', *([objtype] + args), **kwargs)
            else:
                return make_func_code('vfpfunc.create_object', *args, **kwargs)
        if funcname in ('fcreate', 'fopen'):
            opentypes = ('w', 'r') if funcname == 'fcreate' else ('r', 'w', 'r+')
            if len(args) > 1 and args[1] <= len(opentypes):
                args[1] = int(args[1])
                if isinstance(args[1], int):
                    args[1] = opentypes[args[1]]
                else:
                    args[1] = add_args_to_code({}[{}]).format(opentypes, args[1])
            else:
                args.append(opentypes[0])
            return make_func_code('open', *args)
        if funcname == 'fclose':
            return add_args_to_code('{}.close()', args)
        if funcname in ('fputs', 'fwrite'):
            if len(args) == 3:
                args[2] = int(args[2])
                args[1] = add_args_to_code('{}[:{}]', args[1:])
            if funcname == 'fputs':
                args[1] += '\r\n'
            return add_args_to_code('{}.write({})', args)
        if funcname in ('fgets', 'fread'):
            if funcname == 'fgets':
                code = '{}.readline({}).strip(\'\\r\\n\')'
            else:
                code = '{}.read({})'
            if len(args) < 2:
                args.append(CodeStr(''))
            else:
                args[1] = int(args[1])
            return add_args_to_code(code, args)
        if funcname == 'fseek':
            funcname = '{}.seek'.format(args[0])
            return make_func_code(funcname, *args[1:])
        if funcname in ('file', 'directory', 'justdrive', 'justpath', 'justfname', 'juststem', 'justext', 'forceext', 'addbs', 'curdir'):
            if self.filesystem_caseless:
                args = [arg.lower() if string_type(arg) else arg for arg in args]
            self.imports.append('import os')
            operation = {
                'file': [make_func_code, ['os.path.isfile'] + args],
                'directory': [make_func_code, ['os.path.isdir'] + args],
                'justdrive': [add_args_to_code, ('os.path.splitdrive({})[0]', args)],
                'justpath': [make_func_code, ['os.path.dirname'] + args],
                'justfname': [make_func_code, ['os.path.basename'] + args],
                'juststem': [add_args_to_code, ('os.path.splitext(os.path.basename({}))[0]', args)],
                'justext': [add_args_to_code, ('os.path.splitext({})[1][1:]', args)],
                'forceext': [add_args_to_code, ('os.path.splitext({})[0] + \'.\' + {}', args)],
                'addbs': [make_func_code, ['os.path.join'] + args + ['']],
                'curdir': [make_func_code, ['os.getcwd']],
            }[funcname]
            return operation[0](*operation[1])
        if funcname == 'set' and len(args) > 0 and string_type(args[0]):
            args[0] = args[0].lower()
        if funcname == 'select_function' and not args:
            args = (add_args_to_code('{} if {} else {}', (0, CodeStr('vfpfunc.set(\'compatible\') == \'OFF\''), None)),)
        if funcname in dir(vfpfunc):
            funcname = 'vfpfunc.' + funcname
        elif funcname in dir(DB):
            funcname = 'DB.' + funcname
        else:
            funcname = self.scopeId(funcname, 'func')
        return make_func_code(funcname, *args)

    def scopeId(self, identifier, vartype):
        scope = CodeStr({
            'val': 'S',
            'func': 'F',
        }[vartype])
        if scope != 'F':
            if not self.scope:
                return identifier
            elif identifier == 'this':
                return CodeStr('self')
            elif identifier == 'thisform':
                return CodeStr('self.parentform')
        if valid_identifier(identifier):
            return add_args_to_code('{}.{}', [scope, CodeStr(identifier)])
        else:
            return add_args_to_code('{}[{}]', [scope, identifier])

    def createIdAttr(self, identifier, trailer):
        if trailer and len(trailer) == 1 and isinstance(trailer[0], list):
            args = trailer[0]
            return self.func_call(identifier, args)
        elif trailer and len(trailer) > 1 and trailer[-2] == 'setitem' and isinstance(trailer[-1], list) and len(trailer[-1]) == 2:
            return add_args_to_code('{}[{}] = {}', [self.createIdAttr(identifier, trailer[:-2])] + trailer[-1])
        elif trailer and len(trailer) > 1 and trailer[-2] == 'getitem' and isinstance(trailer[-1], list) and len(trailer[-1]) == 1:
            return add_args_to_code('{}[{}]', [self.createIdAttr(identifier, trailer[:-2])] +  trailer[-1])
        elif trailer and len(trailer) > 1 and trailer[-2] == 'callmethod' and isinstance(trailer[-1], list) and len(trailer[-1]) == 2:
            trailer[-1][0] = CodeStr(trailer[-1][0])
            func = add_args_to_code('{}.{}', [self.createIdAttr(identifier, trailer[:-2])] + trailer[-1][:1])
            return make_func_code(func, *trailer[-1][1])
        else:
            trailer = [self.convert_trailer_args(t) for t in trailer or ()]
            trailer = CodeStr(''.join(trailer))
        if identifier.islower():
            identifier = self.scopeId(identifier, 'val')
        return add_args_to_code('{}{}', (identifier, trailer))

    def convert_trailer_args(self, trailer):
        if isinstance(trailer, list):
            return make_func_code('', *trailer)
        else:
            return add_args_to_code('.{}', (trailer,))

    def visitFuncCallTrailer(self, ctx):
        trailer = self.visit(ctx.trailer()) or []
        retval = [[x for x in self.visit(ctx.args()) or []]]
        return retval + trailer

    def visitIdentTrailer(self, ctx):
        trailer = self.visit(ctx.trailer()) or []
        retval = [self.visit_with_disabled_scope(ctx.identifier())]
        return retval + trailer

    def visitIdAttr(self, ctx):
        identifier = self.visit(ctx.identifier())
        trailer = self.visit(ctx.trailer())
        if ctx.PERIOD() and self.withid:
            trailer = [identifier] + (trailer or [])
            identifier = self.withid
        return self.createIdAttr(identifier, trailer)

    def visitIdAttr2(self, ctx):
        return CodeStr('.'.join(([self.withid] if ctx.startPeriod else []) + self.list_visit(ctx.identifier())))

    datatypes_map = {
        'w': 'blob',
        'blob': 'blob',
        'c': 'character',
        'char': 'character',
        'character': 'character',
        'y': 'currency',
        'currency': 'currency',
        'd': 'date',
        'date': 'date',
        't': 'datetime',
        'datetime': 'datetime',
        'b': 'double',
        'double': 'double',
        'f': 'float',
        'float': 'float',
        'g': 'general',
        'general': 'general',
        'i': 'integer',
        'int': 'integer',
        'integer': 'integer',
        'l': 'logical',
        'logical': 'logical',
        'm': 'memo',
        'memo': 'memo',
        'n': 'numeric',
        'num': 'numeric',
        'numeric': 'numeric',
        'q': 'varbinary',
        'varbinary': 'varbinary',
        'v': 'varchar',
        'varchar': 'varchar',
    }

    def visitCastExpr(self, ctx):
        func = {
            'character': 'str',
            'varchar': 'str',
            'memo': 'str',
            'general': 'str',
            'numeric': 'float',
            'currency': 'float',
            'float': 'float',
            'double': 'float',
            'integer': 'int',
            'logical': 'bool',
            'blob': 'bytearray',
            'varbinary': 'bytearray',
            'date': 'dt.date',
            'datetime': 'dt.datetime',
        }[self.datatypes_map[self.visit(ctx.asType())]]
        expr = self.visit(ctx.expr())
        return make_func_code(func, expr)

    def visitDatatype(self, ctx):
        dtype = self.visit_with_disabled_scope(ctx.idAttr())
        try:
            return self.datatypes_map[dtype]
        except KeyError:
            raise ValueError("invalid datatype '{}'".format(dtype))

    def visitAtomExpr(self, ctx):
        atom = self.visit(ctx.atom())
        trailer = self.visit(ctx.trailer())
        if ctx.PERIOD() and self.withid:
            trailer = [atom] + (trailer or [])
            atom = self.withid

        if ctx.idAttr():
            trailer = [atom] + (trailer or [])
            atom = CodeStr(self.visit_with_disabled_scope(ctx.idAttr()).title())

        if trailer and len(trailer) > 0 and not isinstance(trailer[-1], list) and isinstance(atom, CodeStr) and isinstance(ctx.parentCtx, ctx.parser.CmdContext):
            return make_func_code(self.createIdAttr(atom, trailer))
        if isinstance(atom, CodeStr):
            return self.createIdAttr(atom, trailer)
        elif trailer:
            for i, t in enumerate(trailer):
                if isinstance(t, list):
                    trailer[i] = add_args_to_code('({})', t)
                else:
                    trailer[i] = '.' + trailer[i]
            return CodeStr(''.join([repr(self.visit(ctx.atom()))] + trailer))
        else:
            return self.visit(ctx.atom())

    def visitComplexId(self, ctx):
        return self.visitAtomExpr(ctx)

    def visitDeleteFile(self, ctx):
        filename = self.visit(ctx.specialExpr())
        if not filename:
            filename = make_func_code('vfpfunc.getfile', '', 'Select file to', 'Delete', 0, 'Delete')
        if ctx.RECYCLE():
            self.imports.append('from send2trash import send2trash')
            return make_func_code('send2trash', filename)
        else:
            self.imports.append('import os')
            return make_func_code('os.remove', filename)

    def visitCopyMoveFile(self, ctx):
        self.imports.append('import shutil')
        args = self.list_visit(ctx.specialExpr()) #args = [fromFile, toFile]
        if ctx.RENAME():
            return make_func_code('shutil.move', *args)
        else:
            return make_func_code('shutil.copyfile', *args)

    def visitChMkRmDir(self, ctx):
        self.imports.append('import os')
        funcname = 'os.' + {
            ctx.parser.CHDIR: 'chdir',
            ctx.parser.MKDIR: 'mkdir',
            ctx.parser.RMDIR: 'rmdir',
        }[ctx.children[0].symbol.type]
        return make_func_code(funcname, self.visit(ctx.specialExpr()))

    def visitSpecialExpr(self, ctx):
        expr = self.visit(ctx.pathname() or ctx.expr())
        return expr.lower() if string_type(expr) else expr

    def visitPathname(self, ctx):
        return create_string(ctx.getText())

    def convert_number(self, num_literal):
        num = num_literal.getText().lower()
        if 'x' in num or 'e' in num:
            if ('x' not in num and num[-1] == 'e') or ('x' in num and len(num) == 2):
                num += '0'
            return CodeStr(num)

        try:
            return int(num).real
        except:
            return float(num)

    def visitNumberOrCurrency(self, ctx):
        if ctx.children[0].symbol.type == ctx.parser.DOLLAR:
            return round(float(ctx.NUMBER_LITERAL().getText()), 4)
        return self.convert_number(ctx.NUMBER_LITERAL())

    def visitBlob(self, ctx):
        blob = ctx.BLOB_LITERAL().getText()[2:]
        if len(blob) % 2:
            blob = '0' + blob
        blob_iter = iter(blob)
        return bytearray([int(x + y, 16) for x, y in zip(blob_iter, blob_iter)])

    def visitBoolOrNull(self, ctx):
        if ctx.NULL():
            return None
        txt = ctx.BOOLEANCHAR().getText().lower()
        if len(txt) == 1 and txt in 'fnty':
            return txt in 'ty'
        raise Exception('Can\'t convert boolean:' + ctx.getText())

    def visitDate(self, ctx):
        if not ctx.NUMBER_LITERAL():
            return None
        numbers = [self.convert_number(num) for num in ctx.NUMBER_LITERAL()]
        am_pm = (self.visit(ctx.identifier()) or '').lower()
        if any(not isinstance(num, int) for num in numbers) or am_pm not in ('', 'a', 'am', 'p', 'pm'):
            raise ValueError('invalid date/datetime')
        if am_pm in ('p', 'pm'):
            numbers[3] += 12
        if len(numbers) < 4:
            return make_func_code('dt.date', *numbers)
        return make_func_code('dt.datetime', *numbers)

    def visitString(self, ctx):
        return create_string(self.getCtxText(ctx)[1:-1])

    def visitPower(self, ctx):
        return self.operationExpr(ctx, '**')

    def visitMultiplication(self, ctx):
        return self.operationExpr(ctx, ctx.op.type)

    def visitAddition(self, ctx):
        return self.operationExpr(ctx, ctx.op.type)

    def visitModulo(self, ctx):
        return self.operationExpr(ctx, '%')

    def extract_args_from_addbs(self, ctx):
        leftctx, rightctx = ctx.expr()
        if isinstance(leftctx, ctx.parser.AtomExprContext) and self.visit(leftctx.atom()) == 'addbs' and isinstance(leftctx.trailer(), ctx.parser.FuncCallTrailerContext):
            leftctx = leftctx.trailer().args().expr()
            if isinstance(leftctx, ctx.parser.AdditionContext):
                return self.extract_args_from_addbs(leftctx) + [rightctx]
        return [leftctx, rightctx]

    def operationExpr(self, ctx, operation):
        if not self.skip_extract and operation == ctx.parser.PLUS_SIGN:
            args = self.extract_args_from_addbs(ctx)
            self.skip_extract = True
            args = [self.visit(arg) for arg in args]
            check_expr = self.visit(ctx.expr(0))
            self.skip_extract = False
            if len(args) > 2 or args[0] != check_expr:
                return make_func_code('os.path.join', *args)
        def add_parens(parent, child):
            expr = self.visit(child)
            if isinstance(child, ctx.parser.SubExprContext):
                return add_args_to_code('({})', (expr,))
            return expr
        left, right = [add_parens(ctx, expr) for expr in ctx.expr()]
        symbols = {
            '**': '**',
            '%': '%',
            ctx.parser.ASTERISK: '*',
            ctx.parser.FORWARDSLASH: '/',
            ctx.parser.PLUS_SIGN: '+',
            ctx.parser.MINUS_SIGN: '-'
        }
        if string_type(left) and string_type(right) and operation == ctx.parser.PLUS_SIGN:
            return left + right
        return add_args_to_code('{} {} {}', (left, CodeStr(symbols[operation]), right))

    def visitSubExpr(self, ctx):
        return self.visit(ctx.expr())

    def visitFuncDo(self, ctx):
        func = self.visit(ctx.specialExpr(0))
        namespace = self.visit(ctx.specialExpr(1)) if ctx.IN() else ''
        args = self.visit(ctx.args(0)) or []
        kwargs = {}

        if ctx.FORM():
            if ctx.NAME():
                kwargs['name'] = str(self.visit(ctx.nameId))
                if ctx.LINKED():
                    kwargs['linked'] = True
            if args:
                kwargs['args'] = args
            if ctx.NOSHOW():
                kwargs['noshow'] = True
            form_call = make_func_code('vfpfunc.do_form', func, **kwargs)
            if ctx.TO():
                return add_args_to_code('{} = {}', (self.visit(ctx.toId), form_call))
            else:
                return form_call

        if os.path.splitext(namespace)[0] == self.filename:
            namespace = ''

        if not namespace:
            if func in self.function_list:
                return make_func_code(func, *args)

            if os.path.splitext(func)[1] in ('.prg', '.mpr', '.spr'):
                namespace = os.path.splitext(func)[0]
                if os.path.splitext(func)[1] in ('.mpr', '.spr'):
                    namespace += os.path.splitext(func)[1].replace('.', '_')
                func = 'MAIN'
            else:
                func = self.scopeId(func, 'func')
                return make_func_code(func, *args)

        if namespace.endswith('.prg'):
            namespace = namespace[:-4]

        if string_type(namespace) and valid_identifier(namespace):
            namespace = ntpath.normpath(ntpath.splitext(namespace)[0]).replace(ntpath.sep, '.')
            if namespace != 'vfpfunc':
                self.imports.append('import ' + namespace)
            mod = CodeStr(namespace)
        else:
            if string_type(namespace):
                namespace = CodeStr(repr(namespace))
            mod = make_func_code('vfpfunc.module', namespace)
        if string_type(func):
            func = CodeStr(func)
            func = add_args_to_code('{}.{}', (mod, func))
            if string_type(namespace):
                return make_func_code(func, *args)
        else:
            func = make_func_code('getattr', mod, func)

        return make_func_code(func, *args)

    def visitClearStmt(self, ctx):
        command = None
        args = []
        if len(ctx.children) > 1:
            if ctx.DLLS():
                args = self.visit(ctx.specialArgs())
                return make_func_code('F.dll_clear', *args)
            if ctx.expr():
                args.append(self.visit(ctx.expr()))
            elif ctx.specialExpr():
                args.append(self.visit(ctx.specialExpr()))
            elif ctx.specialArgs():
                args += self.visit(ctx.specialArgs())
            elif ctx.ALL():
                args.append('all')
            if not ctx.ALL() or ctx.READ():
                command = create_string(ctx.children[1].getText().lower())
        return make_func_code('vfpfunc.clear', command, *args)

    def visitDllDeclare(self, ctx):
        dll_name = self.visit_with_disabled_scope(ctx.specialExpr())
        funcname = str(self.visit_with_disabled_scope(ctx.identifier()[0]))
        alias = str(self.visit_with_disabled_scope(ctx.alias)) if ctx.alias else None
        return make_func_code('F.dll_declare', dll_name, funcname, alias)

    def visitReadEvent(self, ctx):
        if ctx.EVENTS():
            return make_func_code('vfpfunc.read_events')

    def on_event(self, ctx, func_prefix):
        func = self.visit(ctx.cmd())
        if func:
            if isinstance(func, list) and len(func) == 1:
                func = func[0]
            func = add_args_to_code('lambda: {}', (func,))
        return [add_args_to_code('{} = {}', (CodeStr(func_prefix), func),)]

    def visitOnStmt(self, ctx):
        if ctx.KEY():
            keys = [repr(str(self.visit(i))) for i in ctx.identifier()]
            return self.on_event(ctx, 'vfpfunc.on_key[{}]'.format(', '.join(keys)))
        elif ctx.SELECTION():
            return
        elif ctx.PAD() or ctx.BAR():
            if ctx.PAD():
                args = ['pad', self.visit(ctx.specialExpr(0)), self.visit(ctx.specialExpr(1))]
            else:
                args = ['bar', self.convert_number(ctx.NUMBER_LITERAL()), self.visit(ctx.specialExpr(0))]
            if ctx.ACTIVATE():
                args.append(('popup' if ctx.POPUP() else 'menu', self.visit(ctx.specialExpr()[-1])))
            return make_func_code('vfpfunc.on_pad_bar', *args)

        event = self.visit(ctx.identifier(0))
        if event == 'error':
            return self.on_event(ctx, 'vfpfunc.error_func')
        elif event == 'shutdown':
            return self.on_event(ctx, 'vfpfunc.shutdown_func')

    def visitRaiseError(self, ctx):
        expr = [self.visit(ctx.expr())] or []
        return make_func_code('raise Exception', *expr)

    def visitIdentifier(self, ctx):
        altermap = {
            'class': 'classtype'
        }
        identifier = ctx.getText().lower()
        identifier = altermap.get(identifier, identifier)
        return CodeStr(identifier)

    def visitReference(self, ctx):
        return [self.visit(ctx.idAttr())]

    def visitArrayIndex(self, ctx):
        if ctx.twoExpr():
            return self.visit(ctx.twoExpr())
        else:
            return [self.visit(ctx.expr())]

    def visitTwoExpr(self, ctx):
        return self.list_visit(ctx.expr())

    def visitFile(self, ctx):
        return ctx.getText()

    def visitRelease(self, ctx):
        if ctx.ALL():
            args = []
        else:
            args = self.visit_with_disabled_scope(ctx.args())
            args = [str(arg) for arg in args] or None
        retval = []
        if args is not None:
            if ctx.PROCEDURE():
                retval.append(make_func_code('F.release_procedure', *args))
            elif ctx.POPUP():
                kwargs = {}
                if ctx.EXTENDED():
                    kwargs['extended'] = True
                retval.append(make_func_code('F.release_popups', *args, **kwargs))
            elif ctx.ALL():
                retval.append(make_func_code('M.release'))
            else:
                thisargs = [arg for arg in args if arg in ('this', 'thisform')]
                args = [arg for arg in args if arg not in ('this', 'thisform')]
                for arg in thisargs:
                    if arg == 'this':
                        retval.append(make_func_code('self.release()'))
                    if arg == 'thisform':
                        retval.append(make_func_code('self.parentform.release()'))
                if args:
                    args = [add_args_to_code('M.{}', [CodeStr(arg)]) for arg in args]
                    retval.append(CodeStr('del {}'.format(', '.join(args))))
        return retval

    def visitCloseStmt(self, ctx):
        allflag = not not ctx.ALL()
        if ctx.TABLES():
            return make_func_code('DB.close_tables', allflag)
        if ctx.INDEXES():
            return make_func_code('DB.close_indexes', allflag)
        if ctx.DATABASE():
            return make_func_code('DB.close_databases', allflag)
        return make_func_code('DB.close_all')

    def visitWaitCmd(self, ctx):
        message = self.visit(ctx.message) or ''
        to_expr = self.visit(ctx.toExpr) if ctx.TO() else None
        window = ([self.visit(ctx.atExpr1), self.visit(ctx.atExpr2)] if ctx.AT() else [-1, -1]) if ctx.WINDOW() else []
        nowait = ctx.NOWAIT() != None
        noclear = ctx.NOCLEAR() != None
        timeout = self.visit(ctx.timeout) if ctx.TIMEOUT() else -1
        return make_func_code('vfpfunc.wait', message, to=to_expr, window=window, nowait=nowait, noclear=noclear, timeout=timeout)

    def visitDeactivate(self, ctx):
        if ctx.MENU():
            func = 'vfpfunc.deactivate_menu'
        else:
            func = 'vfpfunc.deactivate_popup'
        args = self.visit_with_disabled_scope(ctx.parameters()) if not ctx.ALL() else []
        return make_func_code(func, *[str(arg) for arg in args])

    def visitThrowError(self, ctx):
        return self.visitRaiseError(ctx) if ctx.expr() else CodeStr('raise')

    def visitCreateTable(self, ctx):
        if ctx.TABLE():
            func = 'DB.create_table'
        elif ctx.DBF():
            func = 'DB.create_dbf'
        elif ctx.CURSOR():
            func = 'DB.create_cursor'
        tablename = self.visit(ctx.specialExpr())
        setupstring = '; '.join(self.visit(f) for f in ctx.tableField())
        free = 'free' if ctx.FREE() else ''
        return make_func_code(func, tablename, setupstring, free)

    def visitTableField(self, ctx):
        fieldname = self.visit(ctx.identifier(0))
        fieldtype = self.visit(ctx.identifier(1))
        fieldsize = self.visit(ctx.arrayIndex()) or (1,)
        if fieldtype.upper() == 'L' and len(fieldsize) == 1 and fieldsize[0] == 1:
            return '{} {}'.format(fieldname, fieldtype)
        else:
            return '{} {}({})'.format(fieldname, fieldtype, ', '.join(str(int(i)) for i in fieldsize))

    def visitAlterTable(self, ctx):
        tablename = self.visit(ctx.specialExpr())
        if ctx.ADD():
            setupstring = '; '.join(self.visit(f) for f in ctx.tableField())
        else:
            setupstring = str(self.visit(ctx.identifier(0)))
        return make_func_code('DB.alter_table', tablename, 'add' if ctx.ADD() else 'drop', setupstring)

    def visitSelect(self, ctx):
        if ctx.tablename:
            return make_func_code('DB.select', self.visit(ctx.tablename))
        else:
            args = [arg for arg in self.visit(ctx.specialArgs())] if ctx.specialArgs() else ('*',)
            from_table = self.visit(ctx.fromExpr)
            into_table = self.visit(ctx.intoExpr)
            where_expr = self.visit(ctx.whereExpr)
            order_by = self.visit(ctx.orderbyid)
            distinct = 'distinct' if ctx.DISTINCT() else None
            return make_func_code('DB.sqlselect', args, from_table, into_table, where_expr, order_by, distinct)

    def visitGoRecord(self, ctx):
        if ctx.TOP():
            record = 0
        elif ctx.BOTTOM():
            record = -1
        else:
            record = self.visit(ctx.expr())
        name = self.visit(ctx.specialExpr())
        return make_func_code('DB.goto', name, record)

    def visitUse(self, ctx):
        kwargs = OrderedDict()
        shared = ctx.SHARED()
        exclusive = ctx.EXCLUSIVE()
        if shared and exclusive:
            raise Exception('cannot combine shared and exclusive')
        elif shared:
            opentype = 'shared'
        elif exclusive:
            opentype = 'exclusive'
        else:
            opentype = None
        if ctx.aliasExpr:
            kwargs['alias'] = self.visit(ctx.aliasExpr)
        name = self.visit(ctx.name)
        workarea = self.visit(ctx.workArea)
        if isinstance(workarea, float):
            workarea = int(workarea)
        return make_func_code('DB.use', name, workarea, opentype, **kwargs)

    def visitLocate(self, ctx):
        kwargs = OrderedDict()
        scope, for_cond, while_cond, nooptimize = self.getQueryConditions(ctx.queryCondition())
        if for_cond:
            kwargs['for_cond'] = for_cond
        if while_cond:
            kwargs['while_cond'] = while_cond
            scope = scope or ('rest',)
        else:
            scope = scope or ('all',)
        if nooptimize:
            kwargs['nooptimize'] = True
        return make_func_code('DB.locate', **kwargs)

    def visitContinueLocate(self, ctx):
        return make_func_code('DB.continue_locate')

    def visitAppendFrom(self, ctx):
        if ctx.ARRAY():
            return make_func_code('DB.insert', None, self.visit(ctx.expr()))
        sourcename = self.visit(ctx.specialExpr(0))
        kwargs = {}
        if ctx.FOR():
            kwargs['for_cond'] = add_args_to_code('lambda: {}', [self.visit(ctx.expr())])
        if ctx.typeExpr:
            kwargs['filetype'] = self.visit(ctx.typeExpr)
        return make_func_code('DB.append_from', None, sourcename, **kwargs)

    def visitAppend(self, ctx):
        menupopup = not ctx.BLANK()
        tablename = self.visit(ctx.specialExpr())
        return make_func_code('DB.append', tablename, menupopup)

    def visitInsert(self, ctx):
        table = self.visit(ctx.specialExpr())
        if ctx.ARRAY() or ctx.NAME() or ctx.MEMVAR():
            values = self.visit(ctx.expr())
        else:
            values = self.visit(ctx.args())
            fields = self.visit(ctx.specialArgs())
            if fields:
                if len(fields) != len(values):
                    raise Exception('number of fields must match number of values')
                values = {field: value for field, value in zip(fields, values)}
            else:
                values = tuple(values)
        return make_func_code('DB.insert', table, values)

    def visitReplace(self, ctx):
        value = self.visit(ctx.expr(0))
        field = self.visit_with_disabled_scope(ctx.specialExpr(0))
        scope, for_cond, while_cond, nooptimize = self.getQueryConditions(ctx.queryCondition())
        scope = scope or ('next', 1)
        if string_type(field):
            field = field.lower().rsplit('.', 1)
            tablename = field[0] if len(field) == 2 else None
            field = field[-1]
        else:
            tablename = None
        return make_func_code('DB.replace', tablename, scope, field, value)

    def visitSkipRecord(self, ctx):
        table = self.visit(ctx.specialExpr())
        skipnum = self.visit(ctx.expr()) or 1
        return make_func_code('DB.skip', table, skipnum)

    def visitCopyTo(self, ctx):
        copyTo = self.visit(ctx.specialExpr())
        if ctx.STRUCTURE():
            return make_func_code('DB.copy_structure', copyTo)

    def visitDeleteRecord(self, ctx):
        kwargs = OrderedDict()
        scope, for_cond, while_cond, nooptimize = self.getQueryConditions(ctx.queryCondition())
        scope = scope or ('next', 1)
        name = self.visit(ctx.inExpr)
        if for_cond:
            kwargs['for_cond'] = for_cond
        if while_cond:
            kwargs['while_cond'] = while_cond
        if ctx.RECALL():
            kwargs['recall'] = True
        return make_func_code('DB.delete_record', name, scope, **kwargs)

    def visitPack(self, ctx):
        if ctx.DATABASE():
            return make_func_code('DB.pack_database')
        elif ctx.DBF():
            pack = 'dbf'
        elif ctx.MEMO():
            pack = 'memo'
        else:
            pack = 'both'
        tablename = self.visit(ctx.tableName)
        workarea = self.visit(ctx.workArea)
        return make_func_code('DB.pack', pack, tablename, workarea)

    def visitIndexOn(self, ctx):
        field = self.visit(ctx.specialExpr()[0])
        indexname = self.visit(ctx.specialExpr()[1])
        tag_flag = not not ctx.TAG()
        compact_flag = not not ctx.COMPACT()
        if ctx.ASCENDING() and ctx.DESCENDING():
            raise Exception('Invalid statement: {}'.format(self.getCtxText(ctx)))
        order = 'descending' if ctx.DESCENDING() else 'ascending'
        unique_flag = not not ctx.UNIQUE()
        return make_func_code('DB.index_on', field, indexname, order, tag_flag, compact_flag, unique_flag)

    def visitCount(self, ctx):
        kwargs = OrderedDict()
        scope, for_cond, while_cond, nooptimize = self.getQueryConditions(ctx.queryCondition())
        if for_cond:
            kwargs['for_cond'] = for_cond
        if while_cond:
            kwargs['while_cond'] = while_cond
            scope = scope or ('rest',)
        else:
            scope = scope or ('all',)
        if nooptimize:
            kwargs['nooptimize'] = True
        return add_args_to_code('{} = {}', (self.visit(ctx.toExpr), make_func_code('DB.count', None, scope, **kwargs)))

    def visitSum(self, ctx):
        kwargs = OrderedDict()
        scope, for_cond, while_cond, nooptimize = self.getQueryConditions(ctx.queryCondition())
        if for_cond:
            kwargs['for_cond'] = for_cond
        if while_cond:
            kwargs['while_cond'] = while_cond
            scope = scope or ('rest',)
        else:
            scope = scope or ('all',)
        if nooptimize:
            kwargs['nooptimize'] = True
        sumexpr = add_args_to_code('lambda: {}', [self.visit(ctx.sumExpr)])
        return add_args_to_code('{} = {}', (self.visit(ctx.toExpr), make_func_code('DB.sum', None, scope, sumexpr, **kwargs)))

    def getQueryConditions(self, conditions):
        scope, for_cond, while_cond, nooptimize = None, None, None, None
        condition_types = [(condition.FOR() or condition.WHILE() or condition.NOOPTIMIZE() or type(condition.scopeClause())) for condition in conditions]
        condition_types = [condition_type or condition_type.symbol.type for condition_type in condition_types]
        if len(set(condition_types)) < len(condition_types):
            raise Exception('Bad Query Condition')
        for condition in conditions:
            if condition.FOR():
                for_cond = add_args_to_code('lambda: {}', [self.visit(condition.expr())])
            if condition.WHILE():
                while_cond = add_args_to_code('lambda: {}', [self.visit(condition.expr())])
            if condition.scopeClause():
                scope = self.visit(condition.scopeClause())
            if condition.NOOPTIMIZE():
                nooptimize = True
        return scope, for_cond, while_cond, nooptimize


    def visitReindex(self, ctx):
        return make_func_code('DB.reindex', not not ctx.COMPACT())

    def visitUpdateCmd(self, ctx):
        table = self.visit(ctx.tableExpr)
        set_fields = [(str(self.visit_with_disabled_scope(i)), self.visit(e)) for i, e in zip(ctx.identifier(), ctx.expr())]
        kwargs = {}
        if ctx.whereExpr:
            kwargs['where'] = add_args_to_code('lambda: {}', [self.visit(ctx.whereExpr)])
        if ctx.joinArgs:
            kwargs['join'] = self.visit(ctx.joinArgs)
        if ctx.fromArgs:
            kwargs['from_args'] = self.visit(ctx.fromArgs)
        return make_func_code('DB.update', table, set_fields, **kwargs)

    def visitSeekRecord(self, ctx):
        tablename = self.visit(ctx.tablenameExpr)
        seek_expr = self.visit(ctx.seekExpr)
        kwargs = OrderedDict()
        if ctx.orderExpr or ctx.tagName:
            kwargs['key_index'] = self.visit(ctx.orderExpr or ctx.tagName)
        if ctx.cdxFileExpr or ctx.idxFileExpr:
            kwargs['key_index_file'] = self.visit(ctx.cdxFileExpr or ctx.idxFileExpr)
        if ctx.DESCENDING():
            kwargs['descending'] = True
        return make_func_code('DB.seek', tablename, seek_expr, **kwargs)

    def visitZapTable(self, ctx):
        return make_func_code('DB.zap', self.visit(ctx.specialExpr()))

    def visitBrowse(self, ctx):
        return make_func_code('DB.browse')

    def visitScatterExpr(self, ctx):
        kwargs = {}
        if ctx.FIELDS():
            fields = self.visit(ctx.args(0))
            if ctx.LIKE():
                kwargs['type'] = 'like'
            elif ctx.EXCEPT():
                kwargs['type'] = 'except'
            kwargs['fields'] = fields
        if ctx.MEMO():
            kwargs['memo'] = True
        if ctx.BLANK():
            kwargs['blank'] = True
        if ctx.NAME():
            name = self.visit(ctx.expr(0))
            if ctx.ADDITIVE():
                kwargs['additive'] = name
            kwargs['totype'] = 'name'
        elif ctx.TO():
            name = self.visit(ctx.expr(0))
            kwargs['totype'] = 'array'
        func = make_func_code('vfpfunc.scatter', **kwargs)
        if not ctx.MEMVAR():
            return add_args_to_code('{} = {}', (name, func))
        return func

    def visitGatherExpr(self, ctx):
        kwargs = {}
        if ctx.FIELDS():
            kwargs['fields'] = self.visit(ctx.args(0))
            if ctx.LIKE():
                kwargs['type'] = 'like'
            elif ctx.EXCEPT():
                kwargs['type'] = 'except'
        if ctx.MEMO():
            kwargs['memo'] = True
        if ctx.NAME() or ctx.FROM():
            kwargs['val'] = self.visit(ctx.expr(0))
        return make_func_code('vfpfunc.gather', **kwargs)

    def visitScopeClause(self, ctx):
        if ctx.ALL():
            return 'all',
        elif ctx.NEXT():
            return 'next', self.visit(ctx.expr())
        elif ctx.RECORD():
            return 'record', self.visit(ctx.expr())
        elif ctx.REST():
            return 'rest',

    def visitReport(self, ctx):
        return make_func_code('vfpfunc.report_form', self.visit(ctx.specialExpr()))

    def visitSetCmd(self, ctx):
        setword = ctx.setword.text.lower()
        kwargs = {'set_value': True}
        args = ()
        if ctx.BAR():
            setword += ' bar'
        if setword == 'printer':
            if ctx.TO():
                if ctx.DEFAULT():
                    kwargs.update({'Default': True})
                elif ctx.NAME():
                    kwargs.update({'Name': self.visit(ctx.specialExpr()[0])})
                elif ctx.specialExpr():
                    kwargs.update({'File': self.visit(ctx.specialExpr()[0])})
                    if ctx.ADDITIVE():
                        kwargs.update({'additive': True})
            else:
                args = ('ON' if ctx.ON() else 'OFF',)
                kwargs.update({'prompt': True} if ctx.PROMPT() else {})
        elif setword == 'typeahead':
            args = (self.visit(ctx.expr()[0]),)
        elif setword == 'procedure':
            kwargs.update({'additive': True} if ctx.ADDITIVE() else {})
            args = self.list_visit(ctx.specialExpr())
        elif setword == 'bell':
            args = ('TO', self.visit(ctx.specialExpr()[0])) if ctx.TO() else ('ON' if ctx.ON() else 'OFF',)
        elif setword in ('cursor', 'deleted', 'exact', 'exclusive', 'multilocks', 'near', 'status', 'status bar', 'tableprompt', 'talk', 'unique'):
            args = ('ON' if ctx.ON() else 'OFF',)
        elif setword == 'century':
            if ctx.TO():
                if len(ctx.expr()) > 0:
                    kwargs.update({'century': self.visit(ctx.expr()[0])})
                else:
                    kwargs.update({'century': 19})
                if len(ctx.expr()) > 1:
                    kwargs.update({'rollover': self.visit(ctx.expr()[1])})
                else:
                    kwargs.update({'rollover': 67})
            else:
                args = ('ON' if ctx.ON() else 'OFF',)
        elif setword == 'classlib':
            args = (self.visit(ctx.specialExpr(0)),)
            if ctx.IN():
                kwargs['class_file'] = self.visit(ctx.specialExpr(1))
            if ctx.ALIAS():
                kwargs['alias'] = self.visit(ctx.specialExpr(2 if ctx.IN() else 1))
            if ctx.ADDITIVE():
                kwargs['additive'] = True
        elif setword == 'compatible':
            args = ('ON' if ctx.ON() or ctx.DB4() else 'OFF',)
            if ctx.PROMPT() or ctx.NOPROMPT():
                args = (args[0], 'PROMPT' if ctx.PROMPT() else 'NOPROMPT')
        elif setword == 'sysmenu':
            args = [x.symbol.text.lower() for x in (ctx.ON(), ctx.OFF(), ctx.TO(), ctx.SAVE(), ctx.NOSAVE()) if x]
            if ctx.expr():
                args += [self.visit(ctx.expr()[0])]
            elif ctx.DEFAULT():
                args += ['default']
        elif setword == 'date':
            args = (str(self.visit_with_disabled_scope(ctx.identifier())),)
        elif setword == 'refresh':
            args = self.list_visit(ctx.expr())
            if len(args) < 2:
                args.append(5)
        elif setword == 'notify':
            arg = 'ON' if ctx.ON() else 'OFF'
            if ctx.CURSOR():
                kwargs.update({'cursor': arg})
            else:
                args = (arg,)
        elif setword == 'clock':
            args = [x.symbol.text.lower() for x in (ctx.ON(), ctx.OFF(), ctx.TO(), ctx.STATUS()) if x]
            if ctx.expr():
                args += self.list_visit(ctx.expr())
        elif setword == 'memowidth':
            args = (self.visit(ctx.expr()[0]),)
        elif setword == 'library':
            kwargs.update({'additive': True} if ctx.ADDITIVE() else {})
            args = self.list_visit(ctx.specialExpr())
        elif setword == 'filter':
            args = self.list_visit(ctx.specialExpr())
        elif setword == 'order':
            order = self.visit(ctx.specialExpr(0))
            of_expr = self.visit(ctx.ofExpr)
            in_expr = self.visit(ctx.inExpr)
            kwargs.update({'descending': True} if ctx.DESCENDING() else {})
            kwargs.update({'tag': True} if ctx.TAG() else {})
            args = (order, of_expr, in_expr)
        elif setword == 'index':
            args = (self.visit(ctx.specialExpr(0)),)
        else:
            return
        return make_func_code('vfpfunc.set', setword, *args, **kwargs)

    def visitPush(self, ctx):
        pass

    def visitPop(self, ctx):
        pass

    def visitShellRun(self, ctx):
        start, stop = ctx.getSourceInterval()
        if ctx.identifier():
            pass #Add /N options
            start = ctx.identifier().getSourceInterval()[0]
        tokens = ctx.parser._input.tokens[start + 1:stop + 1]
        # FIXME: Need more cleanup on the arguments.
        command = ''.join(create_string(tok.text) for tok in tokens).strip().split()
        for i, arg in enumerate(command):
            if arg.startswith('&'):
                command[i] = CodeStr(arg[1:])
        self.imports.append('import subprocess')
        return make_func_code('subprocess.call', command)

    def visitReturnStmt(self, ctx):
        if not ctx.expr():
            return [CodeStr('return')]
        return [add_args_to_code('return {}', [self.visit(ctx.expr())])]

    def visitAssert(self, ctx):
        if ctx.expr(1):
            return add_args_to_code('assert {}, {}', (self.visit(ctx.expr(0)), self.visit(ctx.expr(1))))
        else:
            return add_args_to_code('assert {}', (self.visit(ctx.expr(0)),))

    def visitListStmt(self, ctx):
        pass

    def visitSaveToCmd(self, ctx):
        pass

    def visitUnlockCmd(self, ctx):
        pass

    def visitCompileCmd(self, ctx):
        pass

    def visitSortCmd(self, ctx):
        pass

    def visitCopyToArray(self, ctx):
        pass

    def visitRestoreCmd(self, ctx):
        pass

    def visitZoomCmd(self, ctx):
        pass

    def visitTextBlock(self, ctx):
        kwargs = {}
        if ctx.NOSHOW():
            kwargs['show'] = False
        text = self.visit(ctx.textChunk())
        val = make_func_code('vfpfunc.text', text, **kwargs)
        if ctx.TO():
            name = self.visit(ctx.idAttr(0))
            return add_args_to_code('{} = {}', [name, val])
        else:
            return val

    def visitTextChunk(self, ctx):
        start, stop = ctx.getSourceInterval()
        ltoks = ctx.parser._input.getHiddenTokensToLeft(start) or []
        rtoks = ctx.parser._input.getHiddenTokensToRight(stop) or []
        toks = ctx.parser._input.tokens[start:stop+1]
        text = ''.join(t.text for t in ltoks + toks + rtoks)
        return CodeStr('[' + ',\n'.join(repr(l) for l in text.splitlines()) + ']')

    def visitDefineMenu(self, ctx):
        menu_name = self.visit(ctx.specialExpr()[0])
        kwargs = {}
        if len(ctx.specialExpr()) > 1:
            kwargs['window'] = self.visit(ctx.specialExpr()[1])
        elif ctx.SCREEN():
            kwargs['window'] = CodeStr('vfpfunc.SCREEN')
        if ctx.BAR():
            kwargs['bar'] = True
            if ctx.NUMBER_LITERAL():
                kwargs['line'] = self.convert_number(ctx.NUMBER_LITERAL())
        if ctx.NOMARGIN():
            kwargs['nomargin'] = True
        return make_func_code('vfpfunc.define_menu', menu_name, **kwargs)

    def visitDefinePad(self, ctx):
        if ctx.AT() or ctx.BEFORE() or ctx.AFTER() or ctx.NEGOTIATE() or ctx.FONT() or not ctx.MESSAGE() or not ctx.KEY() or ctx.MARK() or ctx.SKIPKW() or not ctx.COLOR():
            pass
        pad_name = str(self.visit(ctx.specialExpr(0)))
        menu_name = str(self.visit(ctx.specialExpr(1)))
        prompt = str(self.visit(ctx.expr(0)))
        kwargs = {}
        kwargs['message'] = self.visit(ctx.expr(1))
        kwargs['key'] = tuple(['+'.join(self.visit(identifier) for identifier in ctx.identifier())] + [self.visit(ctx.expr(2))] if len(ctx.expr()) > 2 else [])
        kwargs['color_scheme'] = self.convert_number(ctx.NUMBER_LITERAL(0))
        return make_func_code('vfpfunc.define_pad', pad_name, menu_name, prompt, **kwargs)

    def visitDefinePopup(self, ctx):
        popup_name = self.visit(ctx.specialExpr())
        kwargs = {}
        if ctx.SHADOW():
            kwargs['shadow'] = True
        if ctx.MARGIN():
            kwargs['margin'] = True
        if ctx.RELATIVE():
            kwargs['relative'] = True
        if ctx.NUMBER_LITERAL():
            kwargs['color_scheme'] = self.convert_number(ctx.NUMBER_LITERAL())
        return make_func_code('vfpfunc.define_popup', popup_name, **kwargs)

    def visitDefineBar(self, ctx):
        bar_number = self.convert_number(ctx.NUMBER_LITERAL())
        menu_name = self.visit(ctx.specialExpr())
        prompt = self.visit(ctx.expr(0))
        kwargs = {}
        if ctx.MESSAGE():
            kwargs['message'] = self.visit(ctx.expr(1))
        return make_func_code('vfpfunc.define_bar', bar_number, menu_name, prompt, **kwargs)

    def visitActivateWindow(self, ctx):
        pass

    def visitActivateScreen(self, ctx):
        pass

    def visitActivateMenu(self, ctx):
        menu_name = self.visit(ctx.specialExpr(0))
        kwargs = {}
        if ctx.NOWAIT():
            kwargs['nowait'] = True
        if ctx.PAD():
            kwargs['pad'] = self.visit(ctx.specialExpr(1))
        return make_func_code('vfpfunc.activate_menu', menu_name, **kwargs)

    def visitActivatePopup(self, ctx):
        pass

    def visitShowCmd(self, ctx):
        pass

    def visitHideCmd(self, ctx):
        pass

    def visitModifyWindow(self, ctx):
        pass

    def visitModifyFile(self, ctx):
        pass