Skip to content

Commit

Permalink
[script] add:: infrastructure for an language server script context
Browse files Browse the repository at this point in the history
  • Loading branch information
jd28 committed Nov 20, 2023
1 parent 3ab9029 commit 71cd1a0
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/api/enum/nw__script__DiagnosticLevel.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nw::script::DiagnosticLevel
===========================

.. doxygenenum:: nw::script::DiagnosticLevel
4 changes: 4 additions & 0 deletions docs/api/enum/nw__script__DiagnosticType.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nw::script::DiagnosticType
==========================

.. doxygenenum:: nw::script::DiagnosticType
6 changes: 6 additions & 0 deletions docs/api/struct/nw__script__Diagnostic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
nw::script::Diagnostic
======================

.. doxygenstruct:: nw::script::Diagnostic
:members:
:undoc-members:
7 changes: 7 additions & 0 deletions docs/api/struct/nw__script__LspContext.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

nw::script::LspContext
======================

.. doxygenstruct:: nw::script::LspContext
:members:
:undoc-members:
32 changes: 32 additions & 0 deletions docs/fake/rollnw/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,38 @@ def __init__(self, command_script: str = "nwscript"):
pass


class DiagnosticType(enum.IntEnum):
lexical = auto()
parse = auto()
semantic = auto()


class DiagnosticLevel(enum.IntEnum):
warning = auto()
error = auto()


class Diagnostic:
"""Parsed script Diagnostic
Attributes:
type (DiagnosticType)
level (DiagnosticLevel)
script (str)
message (str)
location (SourceLocation)"""


class LspContext:
"""Provides a context built around providing diagnostics to a language server"""

def __init__(self, command_script: str = "nwscript"):
pass

def diagnostics(self) -> List[Diagnostic]:
return []


class Nss:
"""Implementation of nwscript"""

Expand Down
1 change: 1 addition & 0 deletions lib/nw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ add_library(nw STATIC

script/Ast.cpp
script/Context.cpp
script/LspContext.cpp
script/Nss.cpp
script/NssLexer.cpp
script/NssParser.cpp
Expand Down
50 changes: 50 additions & 0 deletions lib/nw/script/LspContext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "LspContext.hpp"

#include "Nss.hpp"

namespace nw::script {

LspContext::LspContext(std::string command_script)
: Context(std::move(command_script))
{
}

const std::vector<Diagnostic>& LspContext::diagnostics() const
{
return diagnostics_;
}

void LspContext::lexical_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc)
{
Diagnostic result;
result.type = DiagnosticType::lexical;
result.level = is_warning ? DiagnosticLevel::warning : DiagnosticLevel::error;
result.script = script ? script->name() : "<source>";
result.message = std::string(msg);
result.location = loc;
diagnostics_.push_back(std::move(result));
}

void LspContext::parse_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc)
{
Diagnostic result;
result.type = DiagnosticType::parse;
result.level = is_warning ? DiagnosticLevel::warning : DiagnosticLevel::error;
result.script = script ? script->name() : "<source>";
result.message = std::string(msg);
result.location = loc;
diagnostics_.push_back(std::move(result));
}

void LspContext::semantic_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc)
{
Diagnostic result;
result.type = DiagnosticType::semantic;
result.level = is_warning ? DiagnosticLevel::warning : DiagnosticLevel::error;
result.script = script ? script->name() : "<source>";
result.message = std::string(msg);
result.location = loc;
diagnostics_.push_back(std::move(result));
}

} // namespace nw::script
39 changes: 39 additions & 0 deletions lib/nw/script/LspContext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include "Context.hpp"

namespace nw::script {

enum struct DiagnosticType {
lexical,
parse,
semantic,
};

enum struct DiagnosticLevel {
warning,
error,
};

struct Diagnostic {
DiagnosticType type;
DiagnosticLevel level;
std::string script;
std::string message;
SourceLocation location;
};

struct LspContext : public Context {
LspContext(std::string command_script = "nwscript");
virtual ~LspContext() = default;

std::vector<Diagnostic> diagnostics_;

const std::vector<Diagnostic>& diagnostics() const;

virtual void lexical_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc) override;
virtual void parse_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc) override;
virtual void semantic_diagnostic(Nss* script, std::string_view msg, bool is_warning, SourceLocation loc) override;
};

} // namespace nw::script
22 changes: 22 additions & 0 deletions rollnw-py/wrapper_script.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "opaque_types.hpp"

#include <nw/kernel/Resources.hpp>
#include <nw/script/LspContext.hpp>
#include <nw/script/Nss.hpp>
#include <nw/script/NssLexer.hpp>
#include <nw/script/Token.hpp>
Expand All @@ -23,6 +24,27 @@ void init_script(py::module& nw)
.def(py::init<>())
.def(py::init<std::string>());

py::enum_<nw::script::DiagnosticType>(nw, "DiagnosticType")
.value("lexical", nws::DiagnosticType::lexical)
.value("parse", nws::DiagnosticType::parse)
.value("semantic", nws::DiagnosticType::semantic);

py::enum_<nw::script::DiagnosticLevel>(nw, "DiagnosticLevel")
.value("warning", nws::DiagnosticLevel::warning)
.value("error", nws::DiagnosticLevel::error);

py::class_<nws::Diagnostic>(nw, "Diagnostic")
.def_readonly("type", &nws::Diagnostic::type)
.def_readonly("level", &nws::Diagnostic::level)
.def_readonly("script", &nws::Diagnostic::script)
.def_readonly("message", &nws::Diagnostic::message)
.def_readonly("location", &nws::Diagnostic::location);

py::class_<nws::LspContext, nws::Context>(nw, "LspContext")
.def(py::init<>())
.def(py::init<std::string>())
.def("diagnostics", &nws::LspContext::diagnostics);

py::enum_<nws::NssTokenType>(nw, "NssTokenType")
.value("INVALID", nws::NssTokenType::INVALID)
.value("END", nws::NssTokenType::END)
Expand Down
26 changes: 26 additions & 0 deletions rollnw-stubs/script.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ class Context:
def __init__(self, command_script: str = "nwscript") -> None: ...


class DiagnosticType:
__members__: ClassVar[dict] = ... # read-only
lexical: ClassVar[NssTokenType] = ...
parse: ClassVar[NssTokenType] = ...
semantic: ClassVar[NssTokenType] = ...


class DiagnosticLevel:
__members__: ClassVar[dict] = ... # read-only
warning: ClassVar[NssTokenType] = ...
error: ClassVar[NssTokenType] = ...


class Diagnostic:
type: DiagnosticType
level: DiagnosticLevel
script: str
message: str
location: SourceLocation


class LspContext(Context):
def __init__(self, command_script: str = "nwscript") -> None: ...
def diagnostics(self) -> List[Diagnostic]: ...


class AssignExpression(Expression):
def __init__(self, *args, **kwargs) -> None: ...
@property
Expand Down
22 changes: 22 additions & 0 deletions tests/script_nss.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "nw/script/LspContext.hpp"
#include <gtest/gtest.h>

#include <nw/log.hpp>
Expand Down Expand Up @@ -1000,3 +1001,24 @@ TEST(Nss, While)
EXPECT_NO_THROW(nss1.resolve());
EXPECT_EQ(nss1.errors(), 1);
}

TEST(Nss, LspContext)
{
auto ctx = std::make_unique<nw::script::LspContext>();

script::Nss nss3(R"(
struct a { int test1; };
void main() {
struct a s;
s.test2;
}
)"sv,
ctx.get());

EXPECT_NO_THROW(nss3.parse());
EXPECT_NO_THROW(nss3.resolve());
EXPECT_EQ(ctx->diagnostics().size(), 1);
EXPECT_EQ(ctx->diagnostics()[0].type, nw::script::DiagnosticType::semantic);
EXPECT_EQ(ctx->diagnostics()[0].level, nw::script::DiagnosticLevel::error);
}
11 changes: 11 additions & 0 deletions tests/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ def test_function_decl():
assert isinstance(decl[0], VarDecl)


def test_function_decl2():
ctx = LspContext("nwscript")
nss = Nss.from_string("void test_function(string s, int b)", ctx)
try:
nss.parse()
except:
pass
nss.resolve()
assert len(ctx.diagnostics()) == 1


def test_var_decl():
ctx = Context("nwscript")
nss = Nss.from_string("int TRUE = 1; const int MY_GLOBAL = 1;", ctx)
Expand Down

0 comments on commit 71cd1a0

Please sign in to comment.