Skip to content

Commit

Permalink
nixd/Sema: add basic function lowering
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Sep 18, 2023
1 parent a5b0bd7 commit 9947e27
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 28 deletions.
14 changes: 14 additions & 0 deletions nixd/include/nixd/Sema/EvalContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once

#include "nixd/Support/GCPool.h"

#include <nix/nixexpr.hh>

namespace nixd {

struct EvalContext {
GCPool<nix::Expr> Pool;
GCPool<nix::Formals> FormalsPool;
};

} // namespace nixd
22 changes: 22 additions & 0 deletions nixd/include/nixd/Sema/Lowering.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "nixd/Sema/EvalContext.h"
#include "nixd/Syntax/Diagnostic.h"
#include "nixd/Syntax/Nodes.h"

#include <nix/nixexpr.hh>
#include <nix/symbol-table.hh>

namespace nixd {

struct Lowering {
nix::SymbolTable &STable;
nix::PosTable &PTable;
std::vector<syntax::Diagnostic> &Diags;

nix::Expr *lower(EvalContext &Ctx, syntax::Node *Root);
nix::ExprLambda *lowerFunction(EvalContext &Ctx, syntax::Function *Fn);
nix::Formal lowerFormal(EvalContext &Ctx, const syntax::Formal &Formal);
};

} // namespace nixd
130 changes: 130 additions & 0 deletions nixd/lib/Sema/Lowering.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include "nixd/Sema/Lowering.h"
#include "nixd/Sema/EvalContext.h"
#include "nixd/Syntax/Diagnostic.h"
#include "nixd/Syntax/Nodes.h"

namespace nixd {

nix::Formal Lowering::lowerFormal(EvalContext &Ctx,
const syntax::Formal &Formal) {
auto *Def = Lowering::lower(Ctx, Formal.Default);
nix::Formal F;

F.pos = Formal.Range.Begin;
F.name = Formal.ID->Symbol;
F.def = Def;
return F;
}

nix::ExprLambda *Lowering::lowerFunction(EvalContext &Ctx,
syntax::Function *Fn) {
// Implementation note:
// The official parser does this in the semantic action, and we deferred it
// here, as a part of the progressive lowering process.
// We should be consistant with the official implementation, not strictly
// required.

// @toFormals
// Link: src/libexpr/parser.y#L156 2a52ec4e928c254338a612a6b40355512298ef38

if (!Fn)
return nullptr;

auto *Formals = Fn->FunctionFormals;

nix::Formals *NixFormals = nullptr;

if (Formals) {
std::sort(Formals->Formals.begin(), Formals->Formals.end(),
[](syntax::Formal *A, syntax::Formal *B) {
auto APos = A->Range.Begin;
auto BPos = B->Range.Begin;
return std::tie(A->ID->Symbol, APos) <
std::tie(B->ID->Symbol, BPos);
});

// Check if there is a formal duplicated to another, if so, emit a
// diagnostic expressively to our user
//
// We are not using upstream O(n) algorithm because we need to detect all
// duplicated stuff, instead of the first one.
std::map<nix::Symbol, const nixd::syntax::Formal *> Names;
for (const auto *Formal : Formals->Formals) {
auto Sym = Formal->ID->Symbol;
if (Names.contains(Sym)) {
// We have seen this symbol before, so this formal is duplicated
const syntax::Formal *Previous = Names[Sym];
syntax::Diagnostic Diag;
Diag.Kind = syntax::Diagnostic::Error;
Diag.Msg = "duplicated function formal declaration";
Diag.Range = Formal->Range;

syntax::Note PrevDef;
PrevDef.Msg = "previously declared here";
PrevDef.Range = Previous->Range;
Diag.Notes.emplace_back(std::move(PrevDef));

Diags.emplace_back(std::move(Diag));
} else {
Names[Sym] = Formal;
}
}

// Check if the function argument is duplicated with the formal
if (Fn->Arg) {
auto ArgSym = Fn->Arg->Symbol;
if (Names.contains(ArgSym)) {
const syntax::Formal *Previous = Names[ArgSym];
syntax::Diagnostic Diag;
Diag.Kind = syntax::Diagnostic::Error;
Diag.Msg = "function argument duplicated to a function formal";
Diag.Range = Fn->Arg->Range;

syntax::Note PrevDef;
PrevDef.Msg = "duplicated to this formal";
PrevDef.Range = Previous->Range;
Diag.Notes.emplace_back(std::move(PrevDef));

Diags.emplace_back(std::move(Diag));
}
}
// Construct nix::Formals, from ours
auto *NixFormals = Ctx.FormalsPool.record(new nix::Formals);
NixFormals->ellipsis = Formals->Ellipsis;
for (auto [_, Formal] : Names) {
nix::Formal F = lowerFormal(Ctx, *Formal);
NixFormals->formals.emplace_back(F);
}
}

auto *Body = lower(Ctx, Fn->Body);

nix::ExprLambda *NewLambda;
if (Fn->Arg) {
NewLambda =
new nix::ExprLambda(Fn->Range.Begin, Fn->Arg->Symbol, NixFormals, Body);
} else {
NewLambda = new nix::ExprLambda(Fn->Range.Begin, NixFormals, Body);
}
Ctx.Pool.record(NewLambda);

return NewLambda;
}

nix::Expr *Lowering::lower(EvalContext &Ctx, nixd::syntax::Node *Root) {
if (!Root)
return nullptr;

using syntax::Node;

switch (Root->getKind()) {
case Node::NK_Function: {
auto *Fn = dynamic_cast<syntax::Function *>(Root);
return lowerFunction(Ctx, Fn);
}
}

return nullptr;
}

} // namespace nixd
8 changes: 2 additions & 6 deletions nixd/lib/Sema/meson.build
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
libnixdSemaDeps = [ nixd_lsp_server
, nix_all
, nixdAST
, llvm
]
libnixdSemaDeps = [ nixdSyntax ]

libnixdSema = library('nixdSema'
, 'CompletionBuilder.cpp'
, 'Lowering.cpp'
, include_directories: nixd_inc
, dependencies: libnixdSemaDeps
, install: true
Expand Down
1 change: 1 addition & 0 deletions nixd/tools/nixd-lint/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ nixd_lint = executable('nixd-lint'
, 'nixd-lint.cpp'
, dependencies: [ boost
, nixdSyntax
, nixdSema
] + nix_all
, install: true
)
Expand Down
59 changes: 37 additions & 22 deletions nixd/tools/nixd-lint/nixd-lint.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include "nixd/Sema/EvalContext.h"
#include "nixd/Sema/Lowering.h"
#include "nixd/Syntax/Diagnostic.h"
#include "nixd/Syntax/Parser.h"
#include "nixd/Syntax/Parser/Require.h"
Expand All @@ -24,24 +26,25 @@ opt<std::string> Filename(Positional, desc("<input file>"), init("-"),
const OptionCategory *Cat[] = {&Misc};

static void printCodeLines(std::ostream &Out, const std::string &Prefix,
const nix::AbstractPos &BeginPos,
const nix::AbstractPos &EndPos,
std::shared_ptr<nix::AbstractPos> BeginPos,
std::shared_ptr<nix::AbstractPos> EndPos,
const nix::LinesOfCode &LOC) {
using namespace nix;
// previous line of code.
if (LOC.prevLineOfCode.has_value()) {
Out << std::endl
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos.line - 1),
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line - 1),
*LOC.prevLineOfCode);
}

if (LOC.errLineOfCode.has_value()) {
// line of code containing the error.
Out << std::endl
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos.line), *LOC.errLineOfCode);
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line),
*LOC.errLineOfCode);
// error arrows for the column range.
if (BeginPos.column > 0) {
auto Start = BeginPos.column;
if (BeginPos->column > 0) {
auto Start = BeginPos->column;
std::string Spaces;
for (auto I = 0; I < Start; ++I) {
Spaces.append(" ");
Expand All @@ -53,10 +56,10 @@ static void printCodeLines(std::ostream &Out, const std::string &Prefix,
<< fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, Prefix, Spaces,
arrows);

if (BeginPos.line == EndPos.line) {
if (BeginPos->line == EndPos->line) {
Out << ANSI_RED;
for (auto I = BeginPos.column + 1; I < EndPos.column; I++) {
Out << (I == EndPos.column - 1 ? "^" : "~");
for (auto I = BeginPos->column + 1; I < EndPos->column; I++) {
Out << (I == EndPos->column - 1 ? "^" : "~");
}
Out << ANSI_NORMAL;
}
Expand All @@ -66,7 +69,7 @@ static void printCodeLines(std::ostream &Out, const std::string &Prefix,
// next line of code.
if (LOC.nextLineOfCode.has_value()) {
Out << std::endl
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos.line + 1),
<< fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line + 1),
*LOC.nextLineOfCode);
}
}
Expand Down Expand Up @@ -96,9 +99,14 @@ int main(int argc, char *argv[]) {
nixd::syntax::ParseData Data{.State = S, .Origin = Origin};
nixd::syntax::parse(Buffer, &Data);

nixd::Lowering Lowering{
.STable = *STable, .PTable = *PTable, .Diags = Data.Diags};
nixd::EvalContext Ctx;
Lowering.lower(Ctx, Data.Result);

for (const auto &Diag : Data.Diags) {
std::shared_ptr<nix::AbstractPos> BeginAPos = (*PTable)[Diag.Range.Begin];
std::shared_ptr<nix::AbstractPos> EndAPos = (*PTable)[Diag.Range.End];
auto BeginPos = (*PTable)[Diag.Range.Begin];
auto EndPos = (*PTable)[Diag.Range.End];
switch (Diag.Kind) {
case nixd::syntax::Diagnostic::Warning:
std::cout << ANSI_WARNING "warning: " ANSI_NORMAL;
Expand All @@ -108,27 +116,34 @@ int main(int argc, char *argv[]) {
break;
}
std::cout << Diag.Msg << "\n";
if (BeginAPos) {
if (BeginPos) {
std::cout << "\n"
<< ANSI_BLUE << "at " ANSI_WARNING
<< (*PTable)[Diag.Range.Begin] << ANSI_NORMAL << ":";
if (auto Lines = BeginAPos->getCodeLines()) {
<< ANSI_BLUE << "at " ANSI_WARNING << BeginPos << ANSI_NORMAL
<< ":";
if (auto Lines =
std::shared_ptr<nix::AbstractPos>(BeginPos)->getCodeLines()) {
std::cout << "\n";
printCodeLines(std::cout, "", *BeginAPos, *EndAPos, *Lines);
printCodeLines(std::cout, "", BeginPos, EndPos, *Lines);
std::cout << "\n";
}
}

for (const auto &Note : Diag.Notes) {
auto NotePos = (*PTable)[Note.Range.Begin];
auto NoteBegin = (*PTable)[Note.Range.Begin];
auto NoteEnd = (*PTable)[Note.Range.End];
std::cout << "\n";
std::cout << ANSI_CYAN "note: " ANSI_NORMAL;
if (NotePos) {
std::cout << Note.Msg << "\n";

if (NoteBegin) {
std::cout << "\n"
<< ANSI_BLUE << "at " ANSI_WARNING << NotePos << ANSI_NORMAL
<< ANSI_BLUE << "at " ANSI_WARNING << NoteBegin << ANSI_NORMAL
<< ":";
if (auto Lines =
std::shared_ptr<nix::AbstractPos>(NotePos)->getCodeLines()) {
nix::printCodeLines(std::cout, "", *BeginAPos, *Lines);
std::shared_ptr<nix::AbstractPos>(NoteBegin)->getCodeLines()) {
std::cout << "\n";
printCodeLines(std::cout, "", NoteBegin, NoteEnd, *Lines);
std::cout << "\n";
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions nixd/tools/nixd-lint/test/duplicated-fn-arg.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# RUN: nixd-lint %s | FileCheck %s

# CHECK: duplicated function formal declaration
a @ { b
# CHECK: previously declared here
, a ? 1
# CHECK: function argument duplicated to a function forma
, a
# CHECK: duplicated to this formal
}: { }

0 comments on commit 9947e27

Please sign in to comment.