summary refs log tree commit diff stats
path: root/compiler/syntaxes.nim
blob: 6b325c77fc5b1a884d099b0188afce89ee3dc725 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Implements the dispatcher for the different parsers.

import
  llstream, ast, idents, lexer, options, msgs, parser,
  filters, filter_tmpl, renderer, lineinfos, pathutils

import std/strutils
when defined(nimPreviewSlimSystem):
  import std/[syncio, assertions]

export Parser, parseAll, parseTopLevelStmt, checkFirstLineIndentation, closeParser

type
  FilterKind = enum
    filtNone = "none"
    filtTemplate = "stdtmpl"
    filtReplace = "replace"
    filtStrip = "strip"

proc utf8Bom(s: string): int =
  if s.len >= 3 and s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF':
    3
  else:
    0

proc containsShebang(s: string, i: int): bool =
  if i+1 < s.len and s[i] == '#' and s[i+1] == '!':
    var j = i + 2
    while j < s.len and s[j] in Whitespace: inc(j)
    result = s[j] == '/'
  else:
    result = false

proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache;
               config: ConfigRef): PNode =
  result = newNode(nkEmpty)
  var s = llStreamOpen(filename, fmRead)
  if s != nil:
    var line = newStringOfCap(80)
    discard llStreamReadLine(s, line)
    var i = utf8Bom(line)
    var linenumber = 1
    if containsShebang(line, i):
      discard llStreamReadLine(s, line)
      i = 0
      inc linenumber
    if i+1 < line.len and line[i] == '#' and line[i+1] == '?':
      when defined(nimpretty):
        # XXX this is a bit hacky, but oh well...
        config.quitOrRaise "can't nimpretty a source code filter: " & $filename
      else:
        inc(i, 2)
        while i < line.len and line[i] in Whitespace: inc(i)
        var p: Parser = default(Parser)
        openParser(p, filename, llStreamOpen(substr(line, i)), cache, config)
        result = parseAll(p)
        closeParser(p)
    llStreamClose(s)

proc getFilter(ident: PIdent): FilterKind =
  result = filtNone
  for i in FilterKind:
    if cmpIgnoreStyle(ident.s, $i) == 0:
      return i

proc getCallee(conf: ConfigRef; n: PNode): PIdent =
  if n.kind in nkCallKinds and n[0].kind == nkIdent:
    result = n[0].ident
  elif n.kind == nkIdent:
    result = n.ident
  else:
    result = nil
    localError(conf, n.info, "invalid filter: " & renderTree(n))

proc applyFilter(p: var Parser, n: PNode, filename: AbsoluteFile,
                 stdin: PLLStream): PLLStream =
  var f = getFilter(getCallee(p.lex.config, n))
  result = case f
           of filtNone:
             stdin
           of filtTemplate:
             filterTmpl(p.lex.config, stdin, filename, n)
           of filtStrip:
             filterStrip(p.lex.config, stdin, filename, n)
           of filtReplace:
             filterReplace(p.lex.config, stdin, filename, n)
  if f != filtNone:
    assert p.lex.config != nil
    if p.lex.config.hasHint(hintCodeBegin):
      rawMessage(p.lex.config, hintCodeBegin, "")
      msgWriteln(p.lex.config, result.s)
      rawMessage(p.lex.config, hintCodeEnd, "")

proc evalPipe(p: var Parser, n: PNode, filename: AbsoluteFile,
              start: PLLStream): PLLStream =
  assert p.lex.config != nil
  result = start
  if n.kind == nkEmpty: return
  if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|":
    for i in 1..2:
      if n[i].kind == nkInfix:
        result = evalPipe(p, n[i], filename, result)
      else:
        result = applyFilter(p, n[i], filename, result)
  elif n.kind == nkStmtList:
    result = evalPipe(p, n[0], filename, result)
  else:
    result = applyFilter(p, n, filename, result)

proc openParser*(p: var Parser, fileIdx: FileIndex, inputstream: PLLStream;
                  cache: IdentCache; config: ConfigRef) =
  assert config != nil
  let filename = toFullPathConsiderDirty(config, fileIdx)
  var pipe = parsePipe(filename, inputstream, cache, config)
  p.lex.config = config
  let s = if pipe != nil: evalPipe(p, pipe, filename, inputstream)
          else: inputstream
  parser.openParser(p, fileIdx, s, cache, config)

proc setupParser*(p: var Parser; fileIdx: FileIndex; cache: IdentCache;
                   config: ConfigRef): bool =
  let filename = toFullPathConsiderDirty(config, fileIdx)
  var f: File = default(File)
  if not open(f, filename.string):
    rawMessage(config, errGenerated, "cannot open file: " & filename.string)
    return false
  openParser(p, fileIdx, llStreamOpen(f), cache, config)
  result = true

proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode =
  var p: Parser = default(Parser)
  if setupParser(p, fileIdx, cache, config):
    result = parseAll(p)
    closeParser(p)
  else:
    result = nil