summary refs log tree commit diff stats
path: root/nim/semstmts.pas
blob: 1ece720238c7e20758d03ac568df394332610638 (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
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
package svc // import "github.com/getwtxt/getwtxt/svc"

import (
	"net"
	"reflect"
	"strings"
	"testing"

	"github.com/getwtxt/registry"
)

func Test_parseQueryOut(t *testing.T) {
	initTestConf()

	urls := "https://gbmor.dev/twtxt.txt"
	nick := "gbmor"

	out, _, err := registry.GetTwtxt(urls)
	if err != nil {
		t.Errorf("Couldn't set up test: %v\n", err)
	}

	statusmap, err := registry.ParseUserTwtxt(out, nick, urls)
	if err != nil {
		t.Errorf("Couldn't set up test: %v\n", err)
	}

	twtxtCache.AddUser(nick, urls, "", net.ParseIP("127.0.0.1"), statusmap)

	t.Run("Parsing Status Query", func(t *testing.T) {
		data, err := twtxtCache.QueryAllStatuses()
		if err != nil {
			t.Errorf("%v\n", err)
		}

		out := parseQueryOut(data)

		conv := strings.Split(string(out), "\n")

		if !reflect.DeepEqual(data, conv) {
			t.Errorf("Pre- and Post- parseQueryOut data are inequal:\n%#v\n%#v\n", data, conv)
		}
	})
}

func Benchmark_parseQueryOut(b *testing.B) {
	initTestConf()

	urls := "https://gbmor.dev/twtxt.txt"
	nick := "gbmor"

	out, _, err := registry.GetTwtxt(urls)
	if err != nil {
		b.Errorf("Couldn't set up test: %v\n", err)
	}

	statusmap, err := registry.ParseUserTwtxt(out, nick, urls)
	if err != nil {
		b.Errorf("Couldn't set up test: %v\n", err)
	}

	twtxtCache.AddUser(nick, urls, "", net.ParseIP("127.0.0.1"), statusmap)

	data, err := twtxtCache.QueryAllStatuses()
	if err != nil {
		b.Errorf("%v\n", err)
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		parseQueryOut(data)
	}

}

func Test_compositeStatusQuery(t *testing.T) {
	initTestConf()
	mockRegistry()

	t.Run("Composite Query Test", func(t *testing.T) {
		out1, err := twtxtCache.QueryInStatus("sqlite")
		if err != nil {
			t.Errorf("%v\n", err)
		}
		out2, err := twtxtCache.QueryInStatus("Sqlite")
		if err != nil {
			t.Errorf("%v\n", err)
		}
		out3, err := twtxtCache.QueryInStatus("SQLITE")
		if err != nil {
			t.Errorf("%v\n", err)
		}

		outro := make([]string, 0)
		outro = append(outro, out1...)
		outro = append(outro, out2...)
		outro = append(outro, out3...)
		out := dedupe(outro)

		data := compositeStatusQuery("sqlite", nil)

		if !reflect.DeepEqual(out, data) {
			t.Errorf("Returning different data.\nManual: %v\nCompositeQuery: %v\n", out, data)
		}
	})
}

func Benchmark_compositeStatusQuery(b *testing.B) {
	initTestConf()
	statuses, _, _ := registry.GetTwtxt("https://gbmor.dev/twtxt.txt")
	parsed, _ := registry.ParseUserTwtxt(statuses, "gbmor", "https://gbmor.dev/twtxt.txt")
	_ = twtxtCache.AddUser("gbmor", "https://gbmor.dev/twtxt.txt", "1", net.ParseIP("127.0.0.1"), parsed)
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		compositeStatusQuery("sqlite", nil)
	}

}
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
//
//
//           The Nimrod Compiler
//        (c) Copyright 2009 Andreas Rumpf
//
//    See the file "copying.txt", included in this
//    distribution, for details about the copyright.
//

// this module does the semantic checking of statements

function semWhen(c: PContext; n: PNode): PNode;
var
  i: int;
  it, e: PNode;
begin
  result := nil;
  for i := 0 to sonsLen(n)-1 do begin
    it := n.sons[i];
    if it = nil then illFormedAst(n);
    case it.kind of
      nkElifBranch: begin
        checkSonsLen(it, 2);
        e := semConstExpr(c, it.sons[0]);
        checkBool(e);
        if (e.kind <> nkIntLit) then InternalError(n.info, 'semWhen');
        if (e.intVal <> 0) and (result = nil) then
          result := semStmt(c, it.sons[1]); // do not open a new scope!
      end;
      nkElse: begin
        checkSonsLen(it, 1);
        if result = nil then result := semStmt(c, it.sons[0])
        // do not open a new scope!
      end;
      else illFormedAst(n)
    end
  end;
  if result = nil then result := newNodeI(nkNilLit, n.info);
  // The ``when`` statement implements the mechanism for platform dependant
  // code. Thus we try to ensure here consistent ID allocation after the
  // ``when`` statement.
  IDsynchronizationPoint(200);
end;

function semIf(c: PContext; n: PNode): PNode;
var
  i: int;
  it: PNode;
begin
  result := n;
  for i := 0 to sonsLen(n)-1 do begin
    it := n.sons[i];
    if it = nil then illFormedAst(n);
    case it.kind of
      nkElifBranch: begin
        checkSonsLen(it, 2);
        openScope(c.tab);
        it.sons[0] := semExprWithType(c, it.sons[0]);
        checkBool(it.sons[0]);
        it.sons[1] := semStmt(c, it.sons[1]);
        closeScope(c.tab);
      end;
      nkElse: begin
        if sonsLen(it) = 1 then it.sons[0] := semStmtScope(c, it.sons[0])
        else illFormedAst(it)
      end;
      else illFormedAst(n)
    end
  end
end;

function semDiscard(c: PContext; n: PNode): PNode;
begin
  result := n;
  checkSonsLen(n, 1);
  n.sons[0] := semExprWithType(c, n.sons[0]);
  if n.sons[0].typ = nil then liMessage(n.info, errInvalidDiscard);
end;

function semBreakOrContinue(c: PContext; n: PNode): PNode;
var
  s: PSym;
  x: PNode;
begin
  result := n;
  checkSonsLen(n, 1);
  if n.sons[0] <> nil then begin
    case n.sons[0].kind of
      nkIdent: s := lookUp(c, n.sons[0]);
      nkSym: s := n.sons[0].sym;
      else illFormedAst(n)
    end;
    if (s.kind = skLabel) and (s.owner.id = c.p.owner.id) then begin
      x := newSymNode(s);
      x.info := n.info;
      include(s.flags, sfUsed);
      n.sons[0] := x
    end
    else
      liMessage(n.info, errInvalidControlFlowX, s.name.s)
  end
  else if (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0) then
    liMessage(n.info, errInvalidControlFlowX,
              renderTree(n, {@set}[renderNoComments]))
end;

function semBlock(c: PContext; n: PNode): PNode;
var
  labl: PSym;
begin
  result := n;
  Inc(c.p.nestedBlockCounter);
  checkSonsLen(n, 2);
  openScope(c.tab); // BUGFIX: label is in the scope of block!
  if n.sons[0] <> nil then begin
    labl := newSymS(skLabel, n.sons[0], c);
    addDecl(c, labl);
    n.sons[0] := newSymNode(labl); // BUGFIX
  end;
  n.sons[1] := semStmt(c, n.sons[1]);
  closeScope(c.tab);
  Dec(c.p.nestedBlockCounter);
end;

function semAsm(con: PContext; n: PNode): PNode;
var
  str, sub: string;
  a, b, c: int;
  e: PSym;
  marker: char;
begin
  result := n;
  checkSonsLen(n, 2);
  marker := pragmaAsm(con, n.sons[0]);
  if marker = #0 then marker := '`'; // default marker
  case n.sons[1].kind of
    nkStrLit, nkRStrLit, nkTripleStrLit: begin
      result := copyNode(n);
      str := n.sons[1].strVal;
      if str = '' then liMessage(n.info, errEmptyAsm);
      // now parse the string literal and substitute symbols:
      a := strStart;
      repeat
        b := strutils.find(str, marker, a);
        if b < strStart then
          sub := ncopy(str, a)
        else
          sub := ncopy(str, a, b-1);
        if sub <> '' then
          addSon(result, newStrNode(nkStrLit, sub));

        if b < strStart then break;
        c := strutils.find(str, marker, b+1);
        if c < strStart then
          sub := ncopy(str, b+1)
        else
          sub := ncopy(str, b+1, c-1);
        if sub <> '' then begin
          e := SymtabGet(con.tab, getIdent(sub));
          if e <> nil then begin
            if e.kind = skStub then loadStub(e);
            addSon(result, newSymNode(e))
          end
          else
            addSon(result, newStrNode(nkStrLit, sub));
        end;
        if c < strStart then break;
        a := c+1;
      until false;
    end;
    else illFormedAst(n)
  end
end;

function semWhile(c: PContext; n: PNode): PNode;
begin
  result := n;
  checkSonsLen(n, 2);
  openScope(c.tab);
  n.sons[0] := semExprWithType(c, n.sons[0]);
  CheckBool(n.sons[0]);
  inc(c.p.nestedLoopCounter);
  n.sons[1] := semStmt(c, n.sons[1]);
  dec(c.p.nestedLoopCounter);
  closeScope(c.tab);
end;

function semCase(c: PContext; n: PNode): PNode;
var
  i, len: int;
  covered: biggestint;
  // for some types we count to check if all cases have been covered
  chckCovered: boolean;
  x: PNode;
begin
  // check selector:
  result := n;
  checkMinSonsLen(n, 2);
  openScope(c.tab);
  n.sons[0] := semExprWithType(c, n.sons[0]);
  chckCovered := false;
  covered := 0;
  case skipTypes(n.sons[0].Typ, abstractVarRange).Kind of
    tyInt..tyInt64, tyChar, tyEnum: chckCovered := true;
    tyFloat..tyFloat128, tyString: begin end
    else liMessage(n.info, errSelectorMustBeOfCertainTypes);
  end;
  for i := 1 to sonsLen(n)-1 do begin
    x := n.sons[i];
    case x.kind of
      nkOfBranch: begin
        checkMinSonsLen(x, 2);
        semCaseBranch(c, n, x, i, covered);
        len := sonsLen(x);
        x.sons[len-1] := semStmtScope(c, x.sons[len-1]);
      end;
      nkElifBranch: begin
        chckCovered := false;
        checkSonsLen(x, 2);
        x.sons[0] := semExprWithType(c, x.sons[0]);
        checkBool(x.sons[0]);
        x.sons[1] := semStmtScope(c, x.sons[1])
      end;
      nkElse: begin
        chckCovered := false;
        checkSonsLen(x, 1);
        x.sons[0] := semStmtScope(c, x.sons[0])
      end;
      else illFormedAst(x);
    end;
  end;
  if chckCovered and (covered <> lengthOrd(n.sons[0].typ)) then
    liMessage(n.info, errNotAllCasesCovered);
  closeScope(c.tab);
end;

function semAsgn(c: PContext; n: PNode): PNode;
var
  le: PType;
  a: PNode;
  id: PIdent;
begin
  checkSonsLen(n, 2);
  a := n.sons[0];
  case a.kind of
    nkDotExpr: begin
      // r.f = x
      // --> `f=` (r, x)
      checkSonsLen(a, 2);
      id := considerAcc(a.sons[1]);
      result := newNodeI(nkCall, n.info);
      addSon(result, newIdentNode(getIdent(id.s+'='), n.info));
      addSon(result, semExpr(c, a.sons[0]));
      addSon(result, semExpr(c, n.sons[1]));
      result := semDirectCallAnalyseEffects(c, result, {@set}[]);
      if result <> nil then begin
        fixAbstractType(c, result);
        analyseIfAddressTakenInCall(c, result);
        exit;
      end
    end;
    nkBracketExpr: begin
      // a[i..j] = x
      // --> `[..]=`(a, i, j, x)
      result := newNodeI(nkCall, n.info);
      checkSonsLen(a, 2);
      if a.sons[1].kind = nkRange then begin
        checkSonsLen(a.sons[1], 2);
        addSon(result, newIdentNode(getIdent(whichSliceOpr(a.sons[1])+'='),
                                    n.info));
        addSon(result, semExpr(c, a.sons[0]));
        addSonIfNotNil(result, semExpr(c, a.sons[1].sons[0]));
        addSonIfNotNil(result, semExpr(c, a.sons[1].sons[1]));
        addSon(result, semExpr(c, n.sons[1]));
        result := semDirectCallAnalyseEffects(c, result, {@set}[]);
        if result <> nil then begin
          fixAbstractType(c, result);
          analyseIfAddressTakenInCall(c, result);
          exit;
        end
      end
      else begin
        addSon(result, newIdentNode(getIdent('[]='), n.info));
        addSon(result, semExpr(c, a.sons[0]));
        addSon(result, semExpr(c, a.sons[1]));
        addSon(result, semExpr(c, n.sons[1]));
        result := semDirectCallAnalyseEffects(c, result, {@set}[]);
        if result <> nil then begin
          fixAbstractType(c, result);
          analyseIfAddressTakenInCall(c, result);
          exit;
        end
      end;
    end;
    else begin end;
  end;
  n.sons[0] := semExprWithType(c, n.sons[0], {@set}[efLValue]);
  n.sons[1] := semExprWithType(c, n.sons[1]);
  le := n.sons[0].typ;
  if (skipTypes(le, {@set}[tyGenericInst]).kind <> tyVar) 
  and (IsAssignable(n.sons[0]) = arNone) then begin
    // Direct assignment to a discriminant is allowed!
    liMessage(n.sons[0].info, errXCannotBeAssignedTo,
              renderTree(n.sons[0], {@set}[renderNoComments]));
  end
  else begin
    n.sons[1] := fitNode(c, le, n.sons[1]);
    fixAbstractType(c, n);
  end;
  result := n;
end;

function SemReturn(c: PContext; n: PNode): PNode;
var
  restype: PType;
  a: PNode; // temporary assignment for code generator
begin
  result := n;
  checkSonsLen(n, 1);
  if not (c.p.owner.kind in [skConverter, skMethod, skProc, skMacro]) then
    liMessage(n.info, errXNotAllowedHere, '''return''');
  if (n.sons[0] <> nil) then begin
    n.sons[0] := SemExprWithType(c, n.sons[0]);
    // check for type compatibility:
    restype := c.p.owner.typ.sons[0];
    if (restype <> nil) then begin
      a := newNodeI(nkAsgn, n.sons[0].info);

      n.sons[0] := fitNode(c, restype, n.sons[0]);
      // optimize away ``return result``, because it would be transformed
      // to ``result = result; return``:
      if (n.sons[0].kind = nkSym) and (sfResult in n.sons[0].sym.flags) then
      begin
        n.sons[0] := nil;
      end
      else begin
        if (c.p.resultSym = nil) then InternalError(n.info, 'semReturn');
        addSon(a, semExprWithType(c, newSymNode(c.p.resultSym)));
        addSon(a, n.sons[0]);
        n.sons[0] := a;
      end
    end
    else
      liMessage(n.info, errCannotReturnExpr);
  end;
end;

function SemYield(c: PContext; n: PNode): PNode;
var
  restype: PType;
begin
  result := n;
  checkSonsLen(n, 1);
  if (c.p.owner = nil) or (c.p.owner.kind <> skIterator) then
    liMessage(n.info, errYieldNotAllowedHere);
  if (n.sons[0] <> nil) then begin
    n.sons[0] := SemExprWithType(c, n.sons[0]);
    // check for type compatibility:
    restype := c.p.owner.typ.sons[0];
    if (restype <> nil) then begin
      n.sons[0] := fitNode(c, restype, n.sons[0]);
      if (n.sons[0].typ = nil) then InternalError(n.info, 'semYield');
    end
    else
      liMessage(n.info, errCannotReturnExpr);
  end
end;

function fitRemoveHiddenConv(c: PContext; typ: Ptype; n: PNode): PNode;
begin
  result := fitNode(c, typ, n);
  if (result.kind in [nkHiddenStdConv, nkHiddenSubConv]) then begin
    changeType(result.sons[1], typ);
    result := result.sons[1];
  end
  else if not sameType(result.typ, typ) then
    changeType(result, typ)
end;

function semVar(c: PContext; n: PNode): PNode;
var
  i, j, len: int;
  a, b, def: PNode;
  typ, tup: PType;
  v: PSym;
begin
  result := copyNode(n);
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind = nkCommentStmt then continue;
    if (a.kind <> nkIdentDefs) and (a.kind <> nkVarTuple) then IllFormedAst(a);
    checkMinSonsLen(a, 3);
    len := sonsLen(a);
    if a.sons[len-2] <> nil then 
      typ := semTypeNode(c, a.sons[len-2], nil)
    else
      typ := nil;
    if a.sons[len-1] <> nil then begin
      def := semExprWithType(c, a.sons[len-1]);
      // BUGFIX: ``fitNode`` is needed here!
      // check type compability between def.typ and typ:
      if (typ <> nil) then def := fitNode(c, typ, def)
      else typ := def.typ;
    end
    else
      def := nil;
    if not typeAllowed(typ, skVar) then begin
      //debug(typ);
      liMessage(a.info, errXisNoType, typeToString(typ));
    end;
    tup := skipTypes(typ, {@set}[tyGenericInst]);
    if a.kind = nkVarTuple then begin
      if tup.kind <> tyTuple then liMessage(a.info, errXExpected, 'tuple');
      if len-2 <> sonsLen(tup) then
        liMessage(a.info, errWrongNumberOfVariables);
      b := newNodeI(nkVarTuple, a.info);
      newSons(b, len);
      b.sons[len-2] := nil; // no type desc
      b.sons[len-1] := def;
      addSon(result, b);
    end;
    for j := 0 to len-3 do begin
      if (c.p.owner.kind = skModule) then begin
        v := semIdentWithPragma(c, skVar, a.sons[j], {@set}[sfStar, sfMinus]);
        include(v.flags, sfGlobal);
      end
      else
        v := semIdentWithPragma(c, skVar, a.sons[j], {@set}[]);
      if v.flags * [sfStar, sfMinus] <> {@set}[] then
        include(v.flags, sfInInterface);
      addInterfaceDecl(c, v);
      if a.kind <> nkVarTuple then begin
        v.typ := typ;
        b := newNodeI(nkIdentDefs, a.info);
        addSon(b, newSymNode(v));
        addSon(b, nil); // no type description
        addSon(b, copyTree(def));
        addSon(result, b);
      end
      else begin
        v.typ := tup.sons[j];
        b.sons[j] := newSymNode(v);
      end
    end
  end
end;

function semConst(c: PContext; n: PNode): PNode;
var
  a, def, b: PNode;
  i: int;
  v: PSym;
  typ: PType;
begin
  result := copyNode(n);
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind = nkCommentStmt then continue;
    if (a.kind <> nkConstDef) then IllFormedAst(a);
    checkSonsLen(a, 3);
    if (c.p.owner.kind = skModule) then begin
      v := semIdentWithPragma(c, skConst, a.sons[0], {@set}[sfStar, sfMinus]);
      include(v.flags, sfGlobal);
    end
    else
      v := semIdentWithPragma(c, skConst, a.sons[0], {@set}[]);

    if a.sons[1] <> nil then typ := semTypeNode(c, a.sons[1], nil)
    else typ := nil;
    def := semAndEvalConstExpr(c, a.sons[2]);
    // check type compability between def.typ and typ:
    if (typ <> nil) then begin
      def := fitRemoveHiddenConv(c, typ, def);
    end
    else typ := def.typ;
    if not typeAllowed(typ, skConst) then
      liMessage(a.info, errXisNoType, typeToString(typ));

    v.typ := typ;
    v.ast := def; // no need to copy
    if v.flags * [sfStar, sfMinus] <> {@set}[] then
      include(v.flags, sfInInterface);
    addInterfaceDecl(c, v);
    b := newNodeI(nkConstDef, a.info);
    addSon(b, newSymNode(v));
    addSon(b, nil); // no type description
    addSon(b, copyTree(def));
    addSon(result, b);
  end;
end;

function semFor(c: PContext; n: PNode): PNode;
var
  i, len: int;
  v, countup: PSym;
  iter: PType;
  countupNode, call: PNode;
begin
  result := n;
  checkMinSonsLen(n, 3);
  len := sonsLen(n);
  openScope(c.tab);
  if n.sons[len-2].kind = nkRange then begin
    checkSonsLen(n.sons[len-2], 2);
    // convert ``in 3..5`` to ``in countup(3, 5)``
    countupNode := newNodeI(nkCall, n.sons[len-2].info);    
    countUp := StrTableGet(magicsys.systemModule.Tab, getIdent('countup'));
    if (countUp = nil) then
      liMessage(countupNode.info, errSystemNeeds, 'countup');
    newSons(countupNode, 3);
    countupnode.sons[0] := newSymNode(countup);
    countupNode.sons[1] := n.sons[len-2].sons[0];
    countupNode.sons[2] := n.sons[len-2].sons[1];
    
    n.sons[len-2] := countupNode;
  end;
  n.sons[len-2] := semExprWithType(c, n.sons[len-2], {@set}[efWantIterator]);
  call := n.sons[len-2];
  if (call.kind <> nkCall) or (call.sons[0].kind <> nkSym)
  or (call.sons[0].sym.kind <> skIterator) then
    liMessage(n.sons[len-2].info, errIteratorExpected);
  iter := skipTypes(n.sons[len-2].typ, {@set}[tyGenericInst]);
  if iter.kind <> tyTuple then begin
    if len <> 3 then liMessage(n.info, errWrongNumberOfVariables);
    v := newSymS(skForVar, n.sons[0], c);
    v.typ := iter;
    n.sons[0] := newSymNode(v);
    addDecl(c, v);
  end
  else begin
    if len-2 <> sonsLen(iter) then liMessage(n.info, errWrongNumberOfVariables);
    for i := 0 to len-3 do begin
      v := newSymS(skForVar, n.sons[i], c);
      v.typ := iter.sons[i];
      n.sons[i] := newSymNode(v);
      addDecl(c, v);
    end
  end;
  // semantic checking for the sub statements:
  Inc(c.p.nestedLoopCounter);
  n.sons[len-1] := SemStmt(c, n.sons[len-1]);
  closeScope(c.tab);
  Dec(c.p.nestedLoopCounter);
end;

function semRaise(c: PContext; n: PNode): PNode;
var
  typ: PType;
begin
  result := n;
  checkSonsLen(n, 1);
  if n.sons[0] <> nil then begin
    n.sons[0] := semExprWithType(c, n.sons[0]);
    typ := n.sons[0].typ;
    if (typ.kind <> tyRef) or (typ.sons[0].kind <> tyObject) then
      liMessage(n.info, errExprCannotBeRaised)
  end;
end;

function semTry(c: PContext; n: PNode): PNode;
var
  i, j, len: int;
  a: PNode;
  typ: PType;
  check: TIntSet;
begin
  result := n;
  checkMinSonsLen(n, 2);
  n.sons[0] := semStmtScope(c, n.sons[0]);
  IntSetInit(check);
  for i := 1 to sonsLen(n)-1 do begin
    a := n.sons[i];
    checkMinSonsLen(a, 1);
    len := sonsLen(a);
    if a.kind = nkExceptBranch then begin
      for j := 0 to len-2 do begin
        typ := semTypeNode(c, a.sons[j], nil);
        if typ.kind = tyRef then typ := typ.sons[0];
        if (typ.kind <> tyObject) then
          liMessage(a.sons[j].info, errExprCannotBeRaised);
        a.sons[j] := newNodeI(nkType, a.sons[j].info);
        a.sons[j].typ := typ;
        if IntSetContainsOrIncl(check, typ.id) then
          liMessage(a.sons[j].info, errExceptionAlreadyHandled);
      end
    end
    else if a.kind <> nkFinally then
      illFormedAst(n);
    // last child of an nkExcept/nkFinally branch is a statement:
    a.sons[len-1] := semStmtScope(c, a.sons[len-1]);
  end;
end;

function semGenericParamList(c: PContext; n: PNode; father: PType = nil): PNode;
var
  i, j, L: int;
  s: PSym;
  a, def: PNode;
  typ: PType;
begin
  result := copyNode(n);
  if n.kind <> nkGenericParams then
    InternalError(n.info, 'semGenericParamList');
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind <> nkIdentDefs then illFormedAst(n);
    L := sonsLen(a);
    def := a.sons[L-1];
    if a.sons[L-2] <> nil then
      typ := semTypeNode(c, a.sons[L-2], nil)
    else if def <> nil then
      typ := newTypeS(tyExpr, c)
    else
      typ := nil;
    for j := 0 to L-3 do begin
      if (typ = nil) or (typ.kind = tyTypeDesc) then begin
        s := newSymS(skType, a.sons[j], c);      
        s.typ := newTypeS(tyGenericParam, c)
      end
      else begin
        s := newSymS(skGenericParam, a.sons[j], c);
        s.typ := typ
      end;
      s.ast := def;
      s.typ.sym := s;
      if father <> nil then addSon(father, s.typ);
      s.position := i;
      addSon(result, newSymNode(s));
      addDecl(c, s);
    end
  end
end;

procedure addGenericParamListToScope(c: PContext; n: PNode);
var
  i: int;
  a: PNode;
begin
  if n.kind <> nkGenericParams then
    InternalError(n.info, 'addGenericParamListToScope');
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind <> nkSym then internalError(a.info, 'addGenericParamListToScope');
    addDecl(c, a.sym)
  end
end;

function SemTypeSection(c: PContext; n: PNode): PNode;
var
  i: int;
  s: PSym;
  t, body: PType;
  a: PNode;
begin
  result := n;
  // process the symbols on the left side for the whole type section, before
  // we even look at the type definitions on the right
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind = nkCommentStmt then continue;
    if (a.kind <> nkTypeDef) then IllFormedAst(a);
    checkSonsLen(a, 3);
    if (c.p.owner.kind = skModule) then begin
      s := semIdentWithPragma(c, skType, a.sons[0], {@set}[sfStar, sfMinus]);
      include(s.flags, sfGlobal);
    end
    else
      s := semIdentWithPragma(c, skType, a.sons[0], {@set}[]);
    if s.flags * [sfStar, sfMinus] <> {@set}[] then
      include(s.flags, sfInInterface);
    s.typ := newTypeS(tyForward, c);
    s.typ.sym := s;
    // process pragmas:
    if a.sons[0].kind = nkPragmaExpr then
      pragma(c, s, a.sons[0].sons[1], typePragmas);
    // add it here, so that recursive types are possible:
    addInterfaceDecl(c, s);
    a.sons[0] := newSymNode(s);
  end;

  // process the right side of the types:
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind = nkCommentStmt then continue;
    if (a.kind <> nkTypeDef) then IllFormedAst(a);
    checkSonsLen(a, 3);
    if (a.sons[0].kind <> nkSym) then IllFormedAst(a);
    s := a.sons[0].sym;
    if (s.magic = mNone) and (a.sons[2] = nil) then
      liMessage(a.info, errImplOfXexpected, s.name.s);
    if s.magic <> mNone then processMagicType(c, s);
    if a.sons[1] <> nil then begin
      // We have a generic type declaration here. In generic types,
      // symbol lookup needs to be done here.
      openScope(c.tab);
      pushOwner(s);
      s.typ.kind := tyGenericBody;
      if s.typ.containerID <> 0 then
        InternalError(a.info, 'semTypeSection: containerID');
      s.typ.containerID := getID();
      a.sons[1] := semGenericParamList(c, a.sons[1], s.typ);
      addSon(s.typ, nil); // to be filled out later
      s.ast := a;
      body := semTypeNode(c, a.sons[2], nil);
      if body <> nil then body.sym := s;
      s.typ.sons[sonsLen(s.typ)-1] := body;
      //debug(s.typ);
      popOwner();
      closeScope(c.tab);
    end
    else if a.sons[2] <> nil then begin
      // process the type's body:
      pushOwner(s);
      t := semTypeNode(c, a.sons[2], s.typ);
      if (t <> s.typ) and (s.typ <> nil) then
        internalError(a.info, 'semTypeSection()');
      s.typ := t;
      s.ast := a;
      popOwner();
    end;
  end;
  // unfortunately we need another pass over the section for checking of
  // illegal recursions and type aliases:
  for i := 0 to sonsLen(n)-1 do begin
    a := n.sons[i];
    if a.kind = nkCommentStmt then continue;
    if (a.sons[0].kind <> nkSym) then IllFormedAst(a);
    s := a.sons[0].sym;
    // compute the type's size and check for illegal recursions:
    if a.sons[1] = nil then begin
      if (a.sons[2] <> nil)
      and (a.sons[2].kind in [nkSym, nkIdent, nkAccQuoted]) then begin
        // type aliases are hard:
        //MessageOut('for type ' + typeToString(s.typ));
        t := semTypeNode(c, a.sons[2], nil);
        if t.kind in [tyObject, tyEnum] then begin
          assignType(s.typ, t);
          s.typ.id := t.id; // same id
        end
      end;
      checkConstructedType(s.info, s.typ);
    end
  end
end;

procedure semParamList(c: PContext; n, genericParams: PNode; s: PSym);
begin
  s.typ := semProcTypeNode(c, n, genericParams, nil);
end;

procedure addParams(c: PContext; n: PNode);
var
  i: int;
begin
  for i := 1 to sonsLen(n)-1 do begin
    if (n.sons[i].kind <> nkSym) then InternalError(n.info, 'addParams');
    addDecl(c, n.sons[i].sym);
  end
end;

procedure semBorrow(c: PContext; n: PNode; s: PSym);
var
  b: PSym;
begin
  // search for the correct alias:
  b := SearchForBorrowProc(c, s, c.tab.tos-2);
  if b = nil then liMessage(n.info, errNoSymbolToBorrowFromFound);
  // store the alias:
  n.sons[codePos] := newSymNode(b);
end;

procedure sideEffectsCheck(c: PContext; s: PSym);
begin
  if [sfNoSideEffect, sfSideEffect] * s.flags = 
     [sfNoSideEffect, sfSideEffect] then 
    liMessage(s.info, errXhasSideEffects, s.name.s);
end;

procedure addResult(c: PContext; t: PType; const info: TLineInfo);
var
  s: PSym;
begin
  if t <> nil then begin
    s := newSym(skVar, getIdent('result'), getCurrOwner());
    s.info := info;
    s.typ := t;
    Include(s.flags, sfResult);
    Include(s.flags, sfUsed);
    addDecl(c, s);
    c.p.resultSym := s;
  end
end;

procedure addResultNode(c: PContext; n: PNode);
begin
  if c.p.resultSym <> nil then addSon(n, newSymNode(c.p.resultSym));
end;

function semLambda(c: PContext; n: PNode): PNode;
var
  s: PSym;
  oldP: PProcCon;
begin
  result := n;
  checkSonsLen(n, codePos+1);
  s := newSym(skProc, getIdent(':anonymous'), getCurrOwner());
  s.info := n.info;

  oldP := c.p; // restore later
  s.ast := n;
  n.sons[namePos] := newSymNode(s);

  pushOwner(s);
  openScope(c.tab);
  if (n.sons[genericParamsPos] <> nil) then illFormedAst(n);
  // process parameters:
  if n.sons[paramsPos] <> nil then begin
    semParamList(c, n.sons[ParamsPos], nil, s);
    addParams(c, s.typ.n);
  end
  else begin
    s.typ := newTypeS(tyProc, c);
    addSon(s.typ, nil);
  end;

  // we are in a nested proc:
  s.typ.callConv := ccClosure;
  if n.sons[pragmasPos] <> nil then
    pragma(c, s, n.sons[pragmasPos], lambdaPragmas);

  s.options := gOptions;
  if n.sons[codePos] <> nil then begin
    if sfImportc in s.flags then
      liMessage(n.sons[codePos].info, errImplOfXNotAllowed, s.name.s);
    c.p := newProcCon(s);
    addResult(c, s.typ.sons[0], n.info);
    n.sons[codePos] := semStmtScope(c, n.sons[codePos]);
    addResultNode(c, n);
  end
  else
    liMessage(n.info, errImplOfXexpected, s.name.s);
  closeScope(c.tab); // close scope for parameters
  popOwner();
  c.p := oldP; // restore
  result.typ := s.typ;
end;

function semProcAux(c: PContext; n: PNode; kind: TSymKind;
                    const validPragmas: TSpecialWords): PNode;
var
  s, proto: PSym;
  oldP: PProcCon;
  gp: PNode;
begin
  result := n;
  checkSonsLen(n, codePos+1);
  if c.p.owner.kind = skModule then begin
    s := semIdentVis(c, kind, n.sons[0], {@set}[sfStar]);
    include(s.flags, sfGlobal);
  end
  else
    s := semIdentVis(c, kind, n.sons[0], {@set}[]);
  n.sons[namePos] := newSymNode(s);
  oldP := c.p; // restore later
  if sfStar in s.flags then include(s.flags, sfInInterface);
  s.ast := n;

  pushOwner(s);
  openScope(c.tab);
  if n.sons[genericParamsPos] <> nil then begin
    n.sons[genericParamsPos] := semGenericParamList(c, n.sons[genericParamsPos]);
    gp := n.sons[genericParamsPos]
  end
  else
    gp := newNodeI(nkGenericParams, n.info);
  // process parameters:
  if n.sons[paramsPos] <> nil then begin
    semParamList(c, n.sons[ParamsPos], gp, s);
    if sonsLen(gp) > 0 then n.sons[genericParamsPos] := gp;
    addParams(c, s.typ.n);
  end
  else begin
    s.typ := newTypeS(tyProc, c);
    addSon(s.typ, nil);
  end;

  proto := SearchForProc(c, s, c.tab.tos-2); // -2 because we have a scope open
                                             // for parameters
  if proto = nil then begin
    if oldP.owner.kind <> skModule then // we are in a nested proc
      s.typ.callConv := ccClosure
    else
      s.typ.callConv := lastOptionEntry(c).defaultCC;
    // add it here, so that recursive procs are possible:
    // -2 because we have a scope open for parameters
    if kind in OverloadableSyms then
      addInterfaceOverloadableSymAt(c, s, c.tab.tos-2)
    else
      addDeclAt(c, s, c.tab.tos-2);
    if n.sons[pragmasPos] <> nil then
      pragma(c, s, n.sons[pragmasPos], validPragmas)
  end
  else begin
    if n.sons[pragmasPos] <> nil then
      liMessage(n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProc);
    if not (sfForward in proto.flags) then
      liMessage(n.info, errAttemptToRedefineX, proto.name.s);
    exclude(proto.flags, sfForward);
    closeScope(c.tab); // close scope with wrong parameter symbols
    openScope(c.tab); // open scope for old (correct) parameter symbols
    if proto.ast.sons[genericParamsPos] <> nil then
      addGenericParamListToScope(c, proto.ast.sons[genericParamsPos]);
    addParams(c, proto.typ.n);
    proto.info := s.info; // more accurate line information
    s.typ := proto.typ;
    s := proto;
    n.sons[genericParamsPos] := proto.ast.sons[genericParamsPos];
    n.sons[paramsPos] := proto.ast.sons[paramsPos];
    if (n.sons[namePos].kind <> nkSym) then InternalError(n.info, 'semProcAux');
    n.sons[namePos].sym := proto;
    proto.ast := n; // needed for code generation
    popOwner();
    pushOwner(s);
  end;

  s.options := gOptions;
  if n.sons[codePos] <> nil then begin
    if [sfImportc, sfBorrow] * s.flags <> [] then
      liMessage(n.sons[codePos].info, errImplOfXNotAllowed, s.name.s);
    if (n.sons[genericParamsPos] = nil) then begin
      c.p := newProcCon(s);
      if (s.typ.sons[0] <> nil) and (kind <> skIterator) then
        addResult(c, s.typ.sons[0], n.info);
      n.sons[codePos] := semStmtScope(c, n.sons[codePos]);
      if (s.typ.sons[0] <> nil) and (kind <> skIterator) then
        addResultNode(c, n);
    end
    else begin
      if (s.typ.sons[0] <> nil) and (kind <> skIterator) then
        addDecl(c, newSym(skUnknown, getIdent('result'), nil));
      n.sons[codePos] := semGenericStmtScope(c, n.sons[codePos]);
    end
  end
  else begin
    if proto <> nil then
      liMessage(n.info, errImplOfXexpected, proto.name.s);
    if [sfImportc, sfBorrow] * s.flags = [] then Include(s.flags, sfForward)
    else if sfBorrow in s.flags then 
      semBorrow(c, n, s);
  end;
  sideEffectsCheck(c, s);
  closeScope(c.tab); // close scope for parameters
  popOwner();
  c.p := oldP; // restore
end;

function semIterator(c: PContext; n: PNode): PNode;
var
  t: PType;
  s: PSym;
begin
  result := semProcAux(c, n, skIterator, iteratorPragmas);
  s := result.sons[namePos].sym;
  t := s.typ;
  if t.sons[0] = nil then liMessage(n.info, errXNeedsReturnType, 'iterator');
  if n.sons[codePos] = nil then liMessage(n.info, errImplOfXexpected, s.name.s);
end;

function semProc(c: PContext; n: PNode): PNode;
begin
  result := semProcAux(c, n, skProc, procPragmas);
end;

function semMethod(c: PContext; n: PNode): PNode;
begin
  if not isTopLevel(c) then
    liMessage(n.info, errXOnlyAtModuleScope, 'method');
  result := semProcAux(c, n, skMethod, methodPragmas);
end;

function semConverterDef(c: PContext; n: PNode): PNode;
var
  t: PType;
  s: PSym;
begin
  if not isTopLevel(c) then
    liMessage(n.info, errXOnlyAtModuleScope, 'converter');
  checkSonsLen(n, codePos+1);
  if n.sons[genericParamsPos] <> nil then
    liMessage(n.info, errNoGenericParamsAllowedForX, 'converter');
  result := semProcAux(c, n, skConverter, converterPragmas);
  s := result.sons[namePos].sym;
  t := s.typ;
  if t.sons[0] = nil then liMessage(n.info, errXNeedsReturnType, 'converter');
  if sonsLen(t) <> 2 then liMessage(n.info, errXRequiresOneArgument, 'converter');
  addConverter(c, s);
end;

function semMacroDef(c: PContext; n: PNode): PNode;
var
  t: PType;
  s: PSym;
begin
  checkSonsLen(n, codePos+1);
  if n.sons[genericParamsPos] <> nil then
    liMessage(n.info, errNoGenericParamsAllowedForX, 'macro');
  result := semProcAux(c, n, skMacro, macroPragmas);
  s := result.sons[namePos].sym;
  t := s.typ;
  if t.sons[0] = nil then liMessage(n.info, errXNeedsReturnType, 'macro');
  if sonsLen(t) <> 2 then liMessage(n.info, errXRequiresOneArgument, 'macro');
  if n.sons[codePos] = nil then liMessage(n.info, errImplOfXexpected, s.name.s);
end;

function evalInclude(c: PContext; n: PNode): PNode;
var
  i, fileIndex: int;
  f: string;
begin
  result := newNodeI(nkStmtList, n.info);
  addSon(result, n); // the rodwriter needs include information!
  for i := 0 to sonsLen(n)-1 do begin
    f := getModuleFile(n.sons[i]);
    fileIndex := includeFilename(f);
    if IntSetContainsOrIncl(c.includedFiles, fileIndex) then
      liMessage(n.info, errRecursiveDependencyX, f);
    addSon(result, semStmt(c, gIncludeFile(f)));
    IntSetExcl(c.includedFiles, fileIndex);
  end;
end;

function semCommand(c: PContext; n: PNode): PNode;
begin
  result := semExpr(c, n);
  if result.typ <> nil then liMessage(n.info, errDiscardValue);
end;

function SemStmt(c: PContext; n: PNode): PNode;
const
  // must be last statements in a block:
  LastBlockStmts = {@set}[nkRaiseStmt, nkReturnStmt, nkBreakStmt,
                          nkContinueStmt];
var
  len, i, j: int;
begin
  result := n;
  if n = nil then exit;
  if nfSem in n.flags then exit;
  case n.kind of
    nkAsgn: result := semAsgn(c, n);
    nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkMacroStmt, nkCallStrLit:
      result := semCommand(c, n);
    nkEmpty, nkCommentStmt, nkNilLit: begin end;
    nkBlockStmt: result := semBlock(c, n);
    nkStmtList: begin
      len := sonsLen(n);
      for i := 0 to len-1 do begin
        n.sons[i] := semStmt(c, n.sons[i]);
        if (n.sons[i].kind in LastBlockStmts) then begin
          for j := i+1 to len-1 do
            case n.sons[j].kind of
              nkPragma, nkCommentStmt, nkNilLit, nkEmpty: begin end;
              else liMessage(n.sons[j].info, errStmtInvalidAfterReturn);
            end
        end
      end
    end;
    nkRaiseStmt: result := semRaise(c, n);
    nkVarSection: result := semVar(c, n);
    nkConstSection: result := semConst(c, n);
    nkTypeSection: result := SemTypeSection(c, n);
    nkIfStmt: result := SemIf(c, n);
    nkWhenStmt: result := semWhen(c, n);
    nkDiscardStmt: result := semDiscard(c, n);
    nkWhileStmt: result := semWhile(c, n);
    nkTryStmt: result := semTry(c, n);
    nkBreakStmt, nkContinueStmt: result := semBreakOrContinue(c, n);
    nkForStmt: result := semFor(c, n);
    nkCaseStmt: result := semCase(c, n);
    nkReturnStmt: result := semReturn(c, n);
    nkAsmStmt: result := semAsm(c, n);
    nkYieldStmt: result := semYield(c, n);
    nkPragma: pragma(c, c.p.owner, n, stmtPragmas);
    nkIteratorDef: result := semIterator(c, n);
    nkProcDef: result := semProc(c, n);
    nkMethodDef: result := semMethod(c, n);
    nkConverterDef: result := semConverterDef(c, n);
    nkMacroDef: result := semMacroDef(c, n);
    nkTemplateDef: result := semTemplateDef(c, n);
    nkImportStmt: begin
      if not isTopLevel(c) then
        liMessage(n.info, errXOnlyAtModuleScope, 'import');
      result := evalImport(c, n);
    end;
    nkFromStmt: begin
      if not isTopLevel(c) then
        liMessage(n.info, errXOnlyAtModuleScope, 'from');
      result := evalFrom(c, n);
    end;
    nkIncludeStmt: begin
      if not isTopLevel(c) then
        liMessage(n.info, errXOnlyAtModuleScope, 'include');
      result := evalInclude(c, n);
    end;
    else liMessage(n.info, errStmtExpected);
  end;
  if result = nil then InternalError(n.info, 'SemStmt: result = nil');
  include(result.flags, nfSem);
end;

function semStmtScope(c: PContext; n: PNode): PNode;
begin
  openScope(c.tab);
  result := semStmt(c, n);
  closeScope(c.tab);
end;