Skip to main content

Get Started

Introduction

Next is an advanced Interface Definition Language (IDL) designed for efficient and flexible code generation across multiple programming languages.

Next allows developers to define constants, enums, structures, interfaces, and more in a language-agnostic manner. It then uses a powerful template system to generate code for various target languages, making it an ideal choice for projects that need to maintain consistency across different programming environments.

How It Works

  1. Define Once: Write your data structures, enums, and interfaces in Next.
  2. Annotate: Use annotations to provide language-specific details or additional metadata.
  3. Template: Create or use existing templates for your target languages.
  4. Generate: Use the Next compiler to generate code for multiple languages from your single definition.

Installation

To install Next, you can use the following command:

curl --proto '=https' --tlsv1.2 -sSf https://getnext.sh | sh

After installing Next, run next version to check the installed version:

next version

Editor Extensions

Install Visual Studio Code Extension for Next from marketplace.

This extension provides syntax highlighting, and other helpful features for Next development in Visual Studio Code. You can install it directly from the provided link or search for next lang in the Visual Studio Code Extensions marketplace.

Write a Next source file

First of all, You need write a Next source file which has extension .next used to define constants, enums, structures and interfaces. Here is an example Next file:

demo.next
@next(
go_package="github.com/mkideal/next/website/example/gen/go/demo",
// Import statements for go_imports:
// - "net/http" is imported for go_alias "*net/http.HandlerFunc"
// - "fmt" is imported to demonstrate importing with go type
//
// Format for go_imports:
// - path/to/pkg.Var (for functions, variables, constants, etc.)
// - *path/to/pkg.Type (for types, "*" is required for importing types)
//
// Note: Avoid duplicating import paths. Use comma separation for multiple imports.
go_imports="*net/http.HandlerFunc,*fmt.Stringer",
cpp_package="demo",
c_package="DEMO_",
java_package="com.example.demo",
csharp_package="Demo",
)
package demo;

const Version = "1.0.0"; // String constant
const MaxRetries = 3; // Integer constant
const Timeout = MaxRetries * 1000.0; // Float constant expression

// Color represents different color options
// Values: Red (1), Green (2), Blue (4), Yellow (8)
@next(type=int8)
enum Color {
Red = 1 << iota;
Green;
Blue;
Yellow;
}

// MathConstants represents mathematical constants
@next(available="!protobuf & !php")
enum MathConstants {
Pi = 3.14159265358979323846;
E = 2.71828182845904523536;
}

// User represents a user in the system
struct User {
@next(tokens="ID")
int64 id;
string username;
vector<string> tags;
map<string, int> scores;
array<float64, 3> coordinates;
array<array<int, 2>, 3> matrix;
@deprecated string email;
@deprecated(message="favoriteColor is deprecated, use tags instead")
Color favoriteColor;

// @next(tokens) applies to the node name:
// - For snake_case: "last_login_ip"
// - For camelCase: "lastLoginIP"
// - For PascalCase: "LastLoginIP"
// - For kebab-case: "last-login-ip"
string lastLoginIP;
any extra;
}

// uint64 represents a 64-bit unsigned integer.
// - In Go, it is aliased as uint64
// - In C++, it is aliased as uint64_t
// - In Java, it is aliased as long
// - In Rust, it is aliased as u64
// - In C#, it is aliased as ulong
// - In Protobuf, it is represented as uint64
// - In other languages, it is represented as a struct with low and high 32-bit integers.
@next(
go_alias="uint64",
cpp_alias="uint64_t",
java_alias="long",
rust_alias="u64",
csharp_alias="ulong",
protobuf_alias="uint64",
)
struct uint64 {
int32 low;
int32 high;
}

// uint128 represents a 128-bit unsigned integer.
// - In rust, it is aliased as u128
// - In other languages, it is represented as a struct with low and high 64-bit integers.
@next(rust_alias="u128")
struct uint128 {
uint64 low;
uint64 high;
}

// Contract represents a smart contract
struct Contract {
uint128 address;
any data;
}

// OperatingSystem represents different operating systems
@next(available="!protobuf")
enum OperatingSystem {
Windows = "windows";
Linux = "linux";
MacOS = "macos";
Android = "android";
IOS = "ios";
}

// LoginRequest represents a login request message (type 101)
// @message annotation is a custom annotation that generates message types.
@message(type=101, req)
struct LoginRequest {
string username;
string password;

// @optional annotation is a builtin annotation that marks a field as optional.
@optional string device;
@next(
available="!protobuf",
default=OperatingSystem.IOS,
)
@optional OperatingSystem os;

// @optional is not supported for array, vector, and map fields:
//
// Bad example:
// @optional array<float64, 3> coordinates;
// @optional vector<string> tags;
// @optional map<string, int> scores;

@next(protobuf_alias="fixed64")
int64 timestamp;
}

// LoginResponse represents a login response message (type 102)
@message(type=102)
struct LoginResponse {
string token;
User user;
}

// Reader provides reading functionality
interface Reader {
// @next(error) applies to the method:
// - For Go: The method may return an error
// - For C++/Java: The method throws an exception
//
// @next(mut) applies to the method:
// - For C++: The method is non-const
// - For other languages: This annotation may not have a direct effect
//
// @next(mut) applies to the parameter buffer:
// - For C++: The parameter is non-const, allowing modification
// - For other languages: This annotation may not have a direct effect,
// but indicates that the buffer content may be modified
@next(error, mut)
read(@next(mut) bytes buffer) int;
}

@next(
available="go|java",
go_alias="net/http.Handler",
java_alias="java.util.function.Function<com.sun.net.httpserver.HttpExchange, String>",
)
interface HTTPHandler {}

// HTTPServer provides HTTP server functionality.
//
// @next(available="go|java") indicates that the interface is available for Go and Java.
@next(available="go|java")
interface HTTPServer {
// @next(error) indicates that the method may return an error:
// - For Go: The method returns (LoginResponse, error)
// - For C++/Java: The method throws an exception
@next(error)
handle(string path, HTTPHandler handler);
}

// HTTPClient provides HTTP request functionality
interface HTTPClient {
// Available for all languages
request(string url, string method, string body) string;
request2(string url, string method, string body) string;

// Available for Go and Java
@next(error, available="go|java")
get(string url) string;

// Available for C++
@next(error, available="cpp")
post(string url, string body) string;
}
tip

See Language Specification for details about next language specification.

Write a Next template file

Then, write a Next template file which has extension .npl used to conctrol how to generate code for specified language. Here is an example template file used to generate c++ code.

file.cpp.npl
{{- /* Overrides "next/cpp/struct.fields": add method 'message_type' for each message after fields */ -}}
{{- define "cpp/struct.fields" -}}
{{super . | linespace}}
{{- with .Decl.Annotations.message.type}}
static inline int message_type() { return {{.}}; }
{{- end}}
{{- end}}

{{- head}}

{{next this}}
  • {{head}}: Generate a header like this: // Code generated by "next"; DO NOT EDIT.
  • {{next this}}: Generate this declaration. By default, this is a file if the meta/this not specified.
tip

See API/Context/meta for details about meta.

Generate code

Finally, run next command to generate code using demo.next and file.cpp.npl:

next -T cpp=file.cpp.npl -O cpp=gen/cpp/ demo.next
Output
demo.h
// Code generated by "next"; DO NOT EDIT

#pragma once

#include <any>
#include <array>
#include <cstdint>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>

namespace demo {
// Enums forward declarations
enum class Color;
/* enum */ class MathConstants;
/* enum */ class OperatingSystem;

// Classes forward declarations
class User;
class Uint128;
class Contract;
class LoginRequest;
class LoginResponse;

inline constexpr auto Version = "1.0.0"; // String constant
inline constexpr auto MaxRetries = 3; // Integer constant
inline constexpr auto Timeout = 3000.0F; // Float constant expression

// Color represents different color options
// Values: Red (1), Green (2), Blue (4), Yellow (8)
enum class Color : int8_t {
Red = 1,
Green = 2,
Blue = 4,
Yellow = 8,
};

/* enum */ class MathConstants {
private:
double value;

public:
static inline constexpr double Pi = 3.14159265358979323846;
static inline constexpr double E = 2.71828182845904523536;

MathConstants(const double& v) : value(v) {}

bool operator==(const MathConstants& other) const {
return value == other.value;
}

operator double() const {
return value;
}
};

/* enum */ class OperatingSystem {
private:
std::string value;

public:
static inline const std::string Windows = "windows";
static inline const std::string Linux = "linux";
static inline const std::string MacOS = "macos";
static inline const std::string Android = "android";
static inline const std::string IOS = "ios";

OperatingSystem(const std::string& v) : value(v) {}

bool operator==(const OperatingSystem& other) const {
return value == other.value;
}

operator std::string() const {
return value;
}
};

// User represents a user in the system
class User {
public:
int64_t id = {0};
std::string username = {""};
std::vector<std::string> tags;
std::unordered_map<std::string, int> scores;
std::array<double, 3> coordinates = {double};
std::array<std::array<int, 2>, 3> matrix = {std::array<int, 2>};
std::string email = {""};
Color favorite_color = {Color(0)};
// @next(tokens) applies to the node name:
// - For snake_case: "last_login_ip"
// - For camelCase: "lastLoginIP"
// - For PascalCase: "LastLoginIP"
// - For kebab-case: "last-login-ip"
std::string last_login_ip = {""};
std::any extra;

public:
User() = default;
~User() = default;
};

// uint128 represents a 128-bit unsigned integer.
// - In rust, it is aliased as u128
// - In other languages, it is represented as a struct with low and high 64-bit integers.
class Uint128 {
public:
uint64_t low;
uint64_t high;

public:
Uint128() = default;
~Uint128() = default;
};

// Contract represents a smart contract
class Contract {
public:
Uint128 address;
std::any data;

public:
Contract() = default;
~Contract() = default;
};

// LoginRequest represents a login request message (type 101)
// @message annotation is a custom annotation that generates message types.
class LoginRequest {
public:
std::string username = {""};
std::string password = {""};
// @optional annotation is a builtin annotation that marks a field as optional.
std::string device = {""};
OperatingSystem os = {OperatingSystem("")};
int64_t timestamp = {0};

static inline int message_type() { return 101; }

public:
LoginRequest() = default;
~LoginRequest() = default;
};

// LoginResponse represents a login response message (type 102)
class LoginResponse {
public:
std::string token = {""};
User user;

static inline int message_type() { return 102; }

public:
LoginResponse() = default;
~LoginResponse() = default;
};

// Reader provides reading functionality
class Reader {
public:
virtual ~Reader() = default;
// @next(error) applies to the method:
// - For Go: The method may return an error
// - For C++/Java: The method throws an exception
//
// @next(mut) applies to the method:
// - For C++: The method is non-const
// - For other languages: This annotation may not have a direct effect
//
// @next(mut) applies to the parameter buffer:
// - For C++: The parameter is non-const, allowing modification
// - For other languages: This annotation may not have a direct effect,
// but indicates that the buffer content may be modified
virtual int read(std::vector<unsigned char>& buffer) = 0;
};

// HTTPClient provides HTTP request functionality
class HTTPClient {
public:
virtual ~HTTPClient() = default;
// Available for all languages
virtual std::string request(const std::string& url, const std::string& method, const std::string& body) const = 0;
virtual std::string request2(const std::string& url, const std::string& method, const std::string& body) const = 0;
// Available for C++
virtual std::string post(const std::string& url, const std::string& body) const = 0;
};
} // namespace demo

How to generate multi-language codes?

It's easy to generate many other langauge codes by writing template files. Here is an example to generate java code.

{{- define "meta/this"}}package{{end -}}
{{- define "meta/_class"}}Constants{{end -}}
{{- define "meta/path"}}{{render "package:name" this.Package | replace "." "/"}}/{{meta._class}}.java{{end -}}
{{- define "meta/skip"}}{{eq 0 (len this.Decls.Consts.List)}}{{end -}}

{{- define "java/package" -}}
package {{render "package:name" .}};

public class {{meta._class}} {
{{- next .Decls.Consts | indent | linespace -}}
}
{{end -}}

{{head}}

{{next this}}

This template file is used to generate all constants in a file Constants.java which define a Constants class to holds all constants of a package. We add some metadata and override next/java/package by defining java/package. We need define meta/this with package.

Output
// Code generated by "next"; DO NOT EDIT

package com.example.demo;

public class Constants {
public static final String VERSION = "1.0.0"; // String constant
public static final int MAX_RETRIES = 3; // Integer constant
public static final float TIMEOUT = 3000.0F; // Float constant expression
}
note

To generate java code, we need write templates for each declaration types: enum, struct, interface. Specially, we write a template file to generate all constants in a single file Constants.java.

Run next to generate multi-language codes as following(we assume *.java.npl files placed in dir java):

next -T cpp=file.cpp.npl -O cpp=gen/cpp/ -T java=java/ -O java=gen/java/ demo.next

When you have write templates for other langauges, you can generate codes like this:

next \
-O c=gen/c -T c=templates/c \
-O cpp=gen/cpp -T cpp=templates/cpp \
-O csharp=gen/csharp -T csharp=templates/csharp \
-O go=gen/go -T go=templates/go \
-O java=gen/java -T java=templates/java \
-O js=gen/js -T js=templates/js \
-O lua=gen/lua -T lua=templates/lua \
-O protobuf=gen/protobuf -T protobuf=templates/protobuf \
-O python=gen/python -T python=templates/python \
-O php=gen/php -T php=templates/php \
-O rust=gen/rust/src -T rust=templates/rust \
-O ts=gen/ts -T ts=templates/ts \
-M "c.vector=void*" -M "c.map=void*" \
next/source/dir/ # or source files

Builtin supported langauges

Usually, to generate code for a language, you need a Map file and at least one template file. Currently, Next has created some builtin map files and base templates. See builtin for more informarion.

Here is an example for C#:

ext=.cs
comment=// %T%

int=int
int8=sbyte
int16=short
int32=int
int64=long
float32=float
float64=double
bool=bool
string=string
byte=byte
bytes=byte[]
any=object
map=Dictionary<%K%, %V%>
vector=List<%T%>
array=%T%[]