aboutsummaryrefslogtreecommitdiff
path: root/nix/builder/parser.nix
blob: eb6f75ef30757dc14bfe289fe8afbc331c137a12 (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
# Parse go.mod in Nix
# Returns a Nix structure with the contents of the go.mod passed in
# in normalised form.

let
  inherit (builtins) elemAt mapAttrs split foldl' match filter typeOf hasAttr length;

  # Strip lines with comments & other junk
  stripStr = s: elemAt (split "^ *" (elemAt (split " *$" s) 0)) 2;
  stripLines = initialLines: foldl' (acc: f: f acc) initialLines [
    # Strip comments
    (lines: map
      (l: stripStr (elemAt (splitString "//" l) 0))
      lines)

    # Strip leading tabs characters
    (lines: map (l: elemAt (match "(\t)?(.*)" l) 1) lines)

    # Filter empty lines
    (filter (l: l != ""))
  ];

  # Parse lines into a structure
  parseLines = lines: (foldl'
    (acc: l:
      let
        m = match "([^ )]*) *(.*)" l;
        directive = elemAt m 0;
        rest = elemAt m 1;

        # Maintain parser state (inside parens or not)
        inDirective =
          if rest == "(" then directive
          else if rest == ")" then null
          else acc.inDirective
        ;

      in
      {
        data = (acc.data // (
          if directive == "" && rest == ")" then { }
          else if inDirective != null && rest == "(" && ! hasAttr inDirective acc.data then {
            ${inDirective} = { };
          }
          else if rest == "(" || rest == ")" then { }
          else if inDirective != null then {
            ${inDirective} = acc.data.${inDirective} // { ${directive} = rest; };
          } else if directive == "replace" then
            (
              let
                segments = split " => " rest;
                getSegment = elemAt segments;
              in
              assert length segments == 3; {
                replace = acc.data.replace // {
                  ${getSegment 0} = "=> ${getSegment 2}";
                };
              }
            )
          else {
            ${directive} = rest;
          }
        )
        );
        inherit inDirective;
      })
    {
      inDirective = null;
      data = {
        require = { };
        replace = { };
        exclude = { };
      };
    }
    lines
  ).data;

  normaliseDirectives = data: (
    let
      normaliseString = s:
        let
          m = builtins.match "([^ ]+) (.+)" s;
        in
        {
          ${elemAt m 0} = elemAt m 1;
        };
      require = data.require or { };
      replace = data.replace or { };
      exclude = data.exclude or { };
    in
    data // {
      require =
        if typeOf require == "string" then normaliseString require
        else require;
      replace =
        if typeOf replace == "string" then normaliseString replace
        else replace;
    }
  );

  parseVersion = ver:
    let
      m = elemAt (match "([^-]+)-?([^-]*)-?([^-]*)" ver);
      v = elemAt (match "([^+]+)\\+?(.*)" (m 0));
    in
    {
      version = v 0;
      versionSuffix = v 1;
      date = m 1;
      rev = m 2;
    };

  parseReplace = data: (
    data // {
      replace =
        mapAttrs
          (_: v:
            let
              m = match "=> ([^ ]+) (.+)" v;
              m2 = match "=> (.*+)" v;
            in
            if m != null then {
              goPackagePath = elemAt m 0;
              version = elemAt m 1;
            } else {
              path = elemAt m2 0;
            })
          data.replace;
    }
  );

  splitString = sep: s: filter (t: t != [ ]) (split sep s);

in
contents:
foldl' (acc: f: f acc) (splitString "\n" contents) [
  stripLines
  parseLines
  normaliseDirectives
  parseReplace
]